又拍云upyun 文件上传(Java)

高奇
2023-12-01

ps:时隔两年了没有更新博客了,今天出一篇关于又拍云Java客户端封装的工具类;都自己写的,里面的部分缺失的部分可以根据代码上下文猜出来的,就不发了。

pom.xml: 

<dependency>
    <groupId>com.upyun</groupId>
    <artifactId>java-sdk</artifactId>
    <version>4.2.3</version>
</dependency>

又拍云工具类: UpyOssUtil 

package com.xx.util;

import cn.hutool.core.date.StopWatch;
import cn.hutool.core.lang.Snowflake;
import cn.hutool.core.util.ReflectUtil;
import com.google.common.base.Splitter;
import com.upyun.ParallelUploader;
import com.upyun.RestManager;
import com.upyun.UpYunUtils;
import lombok.extern.slf4j.Slf4j;
import okhttp3.Response;

import java.io.File;
import java.io.InputStream;
import java.lang.reflect.Constructor;
import java.util.*;
import java.util.concurrent.*;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.function.BiFunction;
import java.util.stream.Collectors;


/**
 * 又拍云 对象存储工具类
 * <p>
 * 注:接口返回的如http://xxxx.test.upcdn.net/test/20211215135525.png
 * 如果调用api的时候,添加了content-secret(文件秘钥仅支持大小写的 A-Z 以及数字 0-9 不支持符号),那么就代表这个地址的文件是私有的,
 * 直接用http://xxxx.test.upcdn.net/test/20211215135525.png是只会得到404,
 * 需要用如:http://xxxx.test.upcdn.net/test/20211215135525.png!abc(!:间隔符,abc:content-secret的值)
 *
 * @author lucifer
 */
@Slf4j
public class UpyOssUtil {

    /**
     * 构造对象缓存
     */
    private static final SimpleCache<Class<?>, Object> INSTANCE_CACHE = new SimpleCache<>();


    /**
     * 上传
     *
     * @param upYunConf  又拍云必填配置
     * @param params     又拍云的可选参数(参考:{@link com.upyun.Params})
     * @param biFunction function
     * @return
     */
    private static <T> List<String> upload(UpYunConf upYunConf, Map<String, String> params, Class<T> clazz, BiFunction<T, Map<String, String>, List<String>> biFunction) {
        //校验参数
        check(StrUtils.isNotBlank(upYunConf.getNameSpace()) || StrUtils.isNotBlank(upYunConf.getUserName()) || StrUtils.isNotBlank(upYunConf.getPassword()) || StrUtils.isNotBlank(upYunConf.getAddress())
                , "upYunConf中必填属性可能存在空值");

        //获取对象实例(先从缓存中获取)
        T instance = getInstance(clazz, upYunConf.getNameSpace(), upYunConf.getUserName(), upYunConf.getPassword());
        List<String> urlList = biFunction.apply(instance, params);
        if (CollUtil.isNotEmpty(urlList)) {
            return urlList.stream().map(url -> {
                if (isRewriteUrl(url)) {
                    //得到的URL如:https://v0.api.upyun.com/mall-cert/test/20211215135525.png
                    //需要进行改写:又拍云控制台提供的域名/目录/文件名.文件后缀名 如:http://mall-cert.test.upcdn.net/test/20211215135525.png
                    Object bucketName = ReflectUtil.getFieldValue(instance, "bucketName");
                    return removeFutileSlash(StrUtils.join(getScheme(upYunConf.getAddress()), StrUtils.substring(url, bucketName.toString())));
                }
                return url;
            }).collect(Collectors.toList());
        }
        return urlList;
    }


    /**
     * 上传
     *
     * @param upYunConf     又拍云配置
     * @param uploadDirPath 上传至又拍云的目录路径 (/test   /test/a/)
     * @param data          文件字节数组
     * @param fileName      文件名,包含文件名后缀(.jpg .txt .png)  注:文件名重复,会出现覆盖,如果该字段不传,那么fileSuffix就必填
     * @param fileSuffix    文件名后缀(.jpg .txt .png),如果文件名fileName参数传值了,这个字段可以不传了,优先级fileName高
     * @param params        又拍云的可选参数(参考:{@link com.upyun.Params})
     * @param checkMD5      是否校验md5
     *                      // 设置待上传文件的 Content-MD5 值
     *                      // 如果又拍云服务端收到的文件MD5值与用户设置的不一致,将会报 406 NotAcceptable 错误
     * @return
     */
    public static String upload(UpYunConf upYunConf, String uploadDirPath, byte[] data, String fileName, String fileSuffix, Map<String, String> params, boolean checkMD5) {
        return upload(upYunConf, params, RestManager.class, (restManager, map) -> {
            String url = "";
            try {
                //校验
                check(isDirectory(uploadDirPath), "上传至又拍云的目录路径不合法,请校验");
                check(StrUtils.isNotBlank(fileName) || StrUtils.isNotBlank(fileSuffix), "文件名fileName或者文件名后缀fileSuffix,两个字段必填其中一个");

                String fileNameTemp = fileName;
                if (StrUtils.isBlank(fileNameTemp)) {
                    fileNameTemp = StrUtils.join(new Snowflake().nextIdStr(), fileSuffix);
                }
                String uploadPath = uploadDirPath.endsWith("/") ? StrUtils.join(uploadDirPath, fileNameTemp) : StrUtils.join(uploadDirPath, "/", fileNameTemp);
                //是否校验md5
                if (checkMD5) {
                    params.put(RestManager.PARAMS.CONTENT_MD5.getValue(), UpYunUtils.md5(data));
                }
                Response response = restManager.writeFile(uploadPath, data, map);
                if (response.isSuccessful()) {
                    url = response.request().url().toString();
                }
                log.debug("文件上传 message:{}", response.message());
            } catch (Exception e) {
                log.error("文件上传失败:error:{}", e);
            }
            return Collections.singletonList(url);
        }).get(0);
    }


    /**
     * 上传
     *
     * @param bucketName    桶
     * @param userName      用户名
     * @param password      密码,需要MD5加密
     * @param hosts         如:www.baidu.com
     * @param uploadDirPath uploadDirPath 上传至又拍云的目录路径 (/test   /test/a/)
     * @param data          字节数组
     * @param fileSuffix    文件后缀名
     * @param params        又拍云的可选参数(参考:{@link com.upyun.Params})
     * @return
     */
    public static String upload(String bucketName, String userName, String password, String hosts, String uploadDirPath, byte[] data, String fileSuffix, Map<String, String> params) {
        return upload(UpYunConf.buildUpYunConf(bucketName, userName, password, hosts), uploadDirPath, data, null, fileSuffix, params, true);
    }

    /**
     * 上传
     *
     * @param bucketName    桶
     * @param userName      用户名
     * @param password      密码,需要MD5加密
     * @param hosts         如:www.baidu.com
     * @param uploadDirPath 上传至又拍云的目录路径 (/test   /test/a/)
     * @param data          字节数组
     * @param fileSuffix    文件后缀名
     * @return
     */
    public static String upload(String bucketName, String userName, String password, String hosts, String uploadDirPath, byte[] data, String fileSuffix) {
        return upload(UpYunConf.buildUpYunConf(bucketName, userName, password, hosts), uploadDirPath, data, null, fileSuffix, new HashMap<>(), true);
    }


    /**
     * 上传,可以自己设置是否校验md5,如果文件过大,建议设置为false
     *
     * @param upYunConf     又拍云配置
     * @param uploadDirPath 上传至又拍云的目录路径 (/test   /test/a/)
     * @param dataList      文件字节数组
     * @param fileSuffix    文件名后缀(.jpg .txt .png)
     * @param checkMD5      是否校验md5
     *                      // 设置待上传文件的 Content-MD5 值
     *                      // 如果又拍云服务端收到的文件MD5值与用户设置的不一致,将会报 406 NotAcceptable 错误
     * @return
     */
    public static List<String> upload(UpYunConf upYunConf, String uploadDirPath, List<byte[]> dataList, String fileSuffix, boolean checkMD5) {
        return upload(upYunConf, uploadDirPath, dataList, fileSuffix, new HashMap<>(), checkMD5);
    }

    /**
     * 上传 (默认校验md5,如果文件过大,不建议使用该方法)
     *
     * @param upYunConf     又拍云配置
     * @param uploadDirPath 上传至又拍云的目录路径 (/test   /test/a/)
     * @param dataList      文件字节数组
     * @param fileSuffix    文件名后缀(.jpg .txt .png)
     * @return
     */
    public static List<String> upload(UpYunConf upYunConf, String uploadDirPath, List<byte[]> dataList, String fileSuffix) {
        return upload(upYunConf, uploadDirPath, dataList, fileSuffix, new HashMap<>(), true);
    }


    /**
     * 上传
     *
     * @param upYunConf     又拍云配置
     * @param uploadDirPath 上传至又拍云的目录路径 (/test   /test/a/)
     * @param dataList      文件字节数组
     * @param fileSuffix    文件名后缀(.jpg .txt .png)
     * @param params        又拍云的可选参数(参考:{@link com.upyun.Params})
     * @param checkMD5      是否校验md5
     *                      // 设置待上传文件的 Content-MD5 值
     *                      // 如果又拍云服务端收到的文件MD5值与用户设置的不一致,将会报 406 NotAcceptable 错误
     * @return
     */
    public static List<String> upload(UpYunConf upYunConf, String uploadDirPath, List<byte[]> dataList, String fileSuffix, Map<String, String> params, boolean checkMD5) {
        List<String> resultList = new ArrayList<>();
        try {
            List<Callable<String>> tasks = new ArrayList<>();
            for (byte[] bytes : dataList) {
                Callable<String> callable = () -> upload(upYunConf, uploadDirPath, bytes, null, fileSuffix, params, checkMD5);
                tasks.add(callable);
            }
            //提交任务
            resultList.addAll(submit(tasks));
        } catch (Exception ex) {
            log.error("文件上传失败:error:{}", ex);
        }
        return resultList;
    }


    /**
     * 上传 (默认校验md5,如果文件过大,不建议使用该方法,选择可以将md5校验设置为false的方法)
     *
     * @param bucketName 桶
     * @param userName   用户名
     * @param password   密码,需要MD5加密
     * @param hosts      如:www.baidu.com
     * @param uploadPath 上传文件路径(如果上传文件路径:/test,文件名后缀:.jpg,那么就会后台算法生成文件名,得到的路径如:/test/123.jpg;如果上传文件的路径是/test/123.jpg,那么参数fileSuffix可以为空)
     * @param dataList   文件字节数组
     * @param fileSuffix 文件名后缀(.jpg .txt .png)
     * @return
     */
    public static List<String> upload(String bucketName, String userName, String password, String hosts, String uploadPath, List<byte[]> dataList, String fileSuffix) {
        return upload(UpYunConf.buildUpYunConf(bucketName, userName, password, hosts), uploadPath, dataList, fileSuffix, new HashMap<>(), true);
    }

    /**
     * 上传 (默认校验md5,如果文件过大,不建议使用该方法,选择可以将md5校验设置为false的方法)
     *
     * @param bucketName 桶
     * @param userName   用户名
     * @param password   密码,需要MD5加密
     * @param hosts      如:www.baidu.com
     * @param uploadPath 上传文件路径(如果上传文件路径:/test,文件名后缀:.jpg,那么就会后台算法生成文件名,得到的路径如:/test/123.jpg;如果上传文件的路径是/test/123.jpg,那么参数fileSuffix可以为空)
     * @param dataList   文件字节数组
     * @param fileSuffix 文件名后缀(.jpg .txt .png)
     * @param checkMD5   是否校验md5
     *                   // 设置待上传文件的 Content-MD5 值
     *                   // 如果又拍云服务端收到的文件MD5值与用户设置的不一致,将会报 406 NotAcceptable 错误
     * @return
     */
    public static List<String> upload(String bucketName, String userName, String password, String hosts, String uploadPath, List<byte[]> dataList, String fileSuffix, boolean checkMD5) {
        return upload(UpYunConf.buildUpYunConf(bucketName, userName, password, hosts), uploadPath, dataList, fileSuffix, new HashMap<>(), checkMD5);
    }


    /**
     * 上传 (默认校验md5,如果文件过大,不建议使用该方法,选择可以将md5校验设置为false的方法)
     *
     * @param bucketName    桶
     * @param userName      用户名
     * @param password      密码,需要MD5加密
     * @param hosts         如:www.baidu.com
     * @param uploadDirPath 上传至又拍云的目录路径 (/test   /test/a/)
     * @param files         多文件
     * @param params        又拍云的可选参数(参考:{@link com.upyun.Params})
     * @return
     */
    public static List<String> parallelUpload(String bucketName, String userName, String password, String hosts, String uploadDirPath, List<File> files, Map<String, String> params) {
        return parallelUpload(UpYunConf.buildUpYunConf(bucketName, userName, password, hosts), uploadDirPath, files, params);
    }

    /**
     * 上传
     *
     * @param bucketName    桶
     * @param userName      用户名
     * @param password      密码,需要MD5加密
     * @param hosts         如:www.baidu.com
     * @param uploadDirPath 上传至又拍云的目录路径 (/test   /test/a/)
     * @param files         多文件
     * @return
     */
    public static List<String> parallelUpload(String bucketName, String userName, String password, String hosts, String uploadDirPath, List<File> files) {
        return parallelUpload(UpYunConf.buildUpYunConf(bucketName, userName, password, hosts), uploadDirPath, files, new HashMap<>());
    }


    /**
     * 上传
     *
     * @param upYunConf     又拍云配置
     * @param uploadDirPath 上传至又拍云的目录路径 (/test   /test/a/)
     * @param files         多文件
     * @return
     */
    public static List<String> parallelUpload(UpYunConf upYunConf, String uploadDirPath, List<File> files) {
        return parallelUpload(upYunConf, uploadDirPath, files, new HashMap<>());
    }

    /**
     * 上传
     *
     * @param upYunConf     又拍云配置
     * @param uploadDirPath 上传至又拍云的目录路径 (/test   /test/a/)
     * @param files         多文件
     * @param params        又拍云的可选参数(参考:{@link com.upyun.Params})
     * @return
     */
    public static List<String> parallelUpload(UpYunConf upYunConf, String uploadDirPath, List<File> files, Map<String, String> params) {
        return parallelUpload(upYunConf, uploadDirPath, files, params, true, true);
    }


    /**
     * 上传
     *
     * @param upYunConf             又拍云配置
     * @param uploadDirPath         上传至又拍云的目录路径 (/test   /test/a/)
     * @param files                 多文件
     * @param checkMD5              是否校验md5,如果文件过大,建议设置为false
     *                              // 设置待上传文件的 Content-MD5 值
     *                              // 如果又拍云服务端收到的文件MD5值与用户设置的不一致,将会报 406 NotAcceptable 错误
     * @param isUseGenerateFileName 是否使用算法生成文件名,TRUE 使用方法中的算法生成文件名;false 则使用文件本身的文件名
     * @return
     */
    public static List<String> parallelUpload(UpYunConf upYunConf, String uploadDirPath, List<File> files, boolean checkMD5, boolean isUseGenerateFileName) {
        return parallelUpload(upYunConf, uploadDirPath, files, new HashMap<>(), checkMD5, isUseGenerateFileName);
    }

    /**
     * 上传
     *
     * @param upYunConf             又拍云配置
     * @param uploadDirPath         上传至又拍云的目录路径 (/test   /test/a/)
     * @param files                 多文件
     * @param isUseGenerateFileName 是否使用算法生成文件名,TRUE 使用方法中的算法生成文件名;false 则使用文件本身的文件名
     * @return
     */
    public static List<String> parallelUpload(UpYunConf upYunConf, String uploadDirPath, List<File> files, boolean isUseGenerateFileName) {
        return parallelUpload(upYunConf, uploadDirPath, files, new HashMap<>(), true, isUseGenerateFileName);
    }


    /**
     * 上传
     *
     * @param upYunConf             又拍云配置
     * @param uploadDirPath         上传至又拍云的目录路径 (/test   /test/a/)
     * @param files                 多文件
     * @param params                又拍云的可选参数(参考:{@link com.upyun.Params})
     * @param checkMD5              是否校验md5,如果文件过大,建议设置为false
     *                              // 设置待上传文件的 Content-MD5 值
     *                              // 如果又拍云服务端收到的文件MD5值与用户设置的不一致,将会报 406 NotAcceptable 错误
     * @param isUseGenerateFileName 是否使用算法生成文件名,TRUE 使用方法中的算法生成文件名;false 则使用文件本身的文件名
     * @return
     */
    public static List<String> parallelUpload(UpYunConf upYunConf, String uploadDirPath, List<File> files, Map<String, String> params, boolean checkMD5, boolean isUseGenerateFileName) {
        return upload(upYunConf, params, ParallelUploader.class, (parallelUploader, map) -> {
            List<String> resultList = new ArrayList<>();
            try {
                check(isDirectory(uploadDirPath), "上传至又拍云的目录路径不合法,请校验");
                //设置上传进度监听
                parallelUploader.setOnProgressListener((index, total) -> log.debug("文件上传中=====:index::{},total::{}", index, index * 100 / total + "%"));
                //设置 MD5 校验
                parallelUploader.setCheckMD5(checkMD5);
                //利用线程池,多文件进行上传
                List<Callable<String>> tasks = new ArrayList<>();
                for (File file : files) {
                    Callable<String> callable = () -> {
                        String fileName = file.getName();
                        //如果使用方法中算法生成的文件名(注:文件名重复,会出现覆盖)
                        if (isUseGenerateFileName) {
                            fileName = StrUtils.join(new Snowflake().nextIdStr(), getFileSuffix(file));
                        }
                        String uploadPath = uploadDirPath.endsWith("/") ? StrUtils.join(uploadDirPath, fileName) : StrUtils.join(uploadDirPath, "/", fileName);
                        boolean upload = parallelUploader.upload(file.getPath(), uploadPath, params);
                        if (upload) {
                            return removeFutileSlash(StrUtils.join(getScheme(upYunConf.getAddress()), uploadPath));
                        }
                        return "";
                    };
                    tasks.add(callable);
                }
                //提交任务
                resultList.addAll(submit(tasks));
            } catch (Exception e) {
                log.error("文件上传失败:error:{}", e);
            }
            return resultList;
        });
    }


    /**
     * 下载(response.body() 包含文件流信息)
     *
     * @param upYunConf 又拍云配置
     * @param filePath  文件路径 /test/1484464385422077952.jpg
     * @return
     */
    public static byte[] read(UpYunConf upYunConf, String filePath) {
        RestManager restManager = new RestManager(upYunConf.getNameSpace(), upYunConf.getUserName(), upYunConf.getPassword());
        try {
            Response result = restManager.readFile(filePath);
            if (result.isSuccessful()) {
                return result.body().bytes();
            }
        } catch (Exception ex) {
            log.error("文件读取失败:{}", ex);
        }
        return null;
    }

    /**
     * 下载文件,返回未加如data:image/png;base64前缀的字符串(使用Base64编码方案将指定的字节数组编码为字符串)
     *
     * @param upYunConf 又拍云配置
     * @param filePath  又拍云文件路径 /test/1484464385422077952.jpg
     * @return
     */
    public static String readImage(UpYunConf upYunConf, String filePath) {
        byte[] read = read(upYunConf, filePath);
        return Base64.getEncoder().encodeToString(read);
    }

    /**
     * 下载文件,返回未加如data:image/png;base64前缀的字符串(使用Base64编码方案将指定的字节数组编码为字符串)
     *
     * @param spec 要解析为 URL的字符
     * @return
     */
    public static String readImage(String spec) {
        return Base64.getEncoder().encodeToString(IoUtil.readBytes(read(spec)));
    }


    /**
     * 下载文件,使用Base64编码方案将指定的字节数组编码为字符串,并在前面加上类似前缀data:image/png;base64,
     *
     * @param upYunConf
     * @param filePath  文件后缀名 如: jpg,不带.符号
     * @return
     */
    public static String base64StrToImage(UpYunConf upYunConf, String filePath) {
        return StrUtils.join("data:image/", getFileSuffix(filePath), ";base64,", readImage(upYunConf, filePath));
    }

    /**
     * 下载文件,使用Base64编码方案将指定的字节数组编码为字符串,并在前面加上类似前缀data:image/png;base64,
     *
     * @param spec       要解析为 URL的字符串
     * @param fileSuffix 文件后缀名 如: jpg,不带.符号
     * @return
     */
    public static String base64StrToImage(String spec, String fileSuffix) {
        //从要解析为 URL的字符串中获取文件后缀名,由于该文件设置了content-secret,所以获取文件后缀名,就会有pem!abc123,需要去掉content-secret(!abc123)
        return StrUtils.join("data:image/", StrUtils.isNotBlank(fileSuffix) ? fileSuffix : getFileSuffix(spec), ";base64,", readImage(spec));
    }

    /**
     * 下载文件,使用Base64编码方案将指定的字节数组编码为字符串,并在前面加上类似前缀data:image/png;base64,
     *
     * @param spec 要解析为 URL的字符串
     * @return
     */
    public static String base64StrToImage(String spec) {
        return base64StrToImage(spec, null);
    }

    /**
     * 下载文件
     *
     * @param spec 要解析为 URL的字符串
     * @return
     */
    public static InputStream read(String spec) {
        return FileUtil.getFileInputStream(spec);
    }


    /**
     * 获取文件后缀名(带.)
     *
     * @param file 文件
     * @return
     */
    private static String getFileSuffix(File file) {
        return "." + FileUtil.getSuffix(file);
    }

    /**
     * 获取文件后缀名(不带.)
     *
     * @param filePath 文件路径
     * @return
     */
    private static String getFileSuffix(String filePath) {
        String fileSuffix = FileUtil.getSuffix(filePath);
        //标识符可为半角字符:“!”,“-”,“_” 三种,可以在管理中进行更改。(默认是"!")
        if (fileSuffix.contains("!")) {
            fileSuffix = StrUtils.sub(fileSuffix, 0, "!");
        } else if (fileSuffix.contains("-")) {
            fileSuffix = StrUtils.sub(fileSuffix, 0, "-");
        } else if (fileSuffix.contains("_")) {
            fileSuffix = StrUtils.sub(fileSuffix, 0, "_");
        }
        return fileSuffix;
    }


    /**
     * 是否是又拍云的路径
     * 如:
     * /test/           true
     * /test            true
     * /test/a.txt      false
     * /test/a.jpg      false
     *
     * @return
     */
    private static boolean isDirectory(String path) {
        //这里简单判断下,路径包含1个及以上的/,并且不带.
        return StrUtils.countShow(path, "/") >= 1 & !path.contains(".");
    }


    /**
     * 判断是否为true,为false则抛异常
     *
     * @param flag
     * @param msg
     * @param <T>
     */
    public static <T> void check(boolean flag, String msg) {
        if (!flag) {
            throw new IllegalArgumentException(msg);
        }
    }


    /**
     * 是否需要重写URL
     *
     * @param url
     * @return
     */
    private static boolean isRewriteUrl(String url) {
        List<String> apiDomainList = Arrays.asList(RestManager.ED_AUTO, RestManager.ED_CNC, RestManager.ED_CTT, RestManager.ED_TELECOM);
        for (String apiDomain : apiDomainList) {
            if (url.contains(apiDomain)) {
                return true;
            }
        }
        return false;
    }


    /**
     * 将URL中无用的//替换成为/
     * 如:https://xx//test/20211215135525.png 替换成 https:/xx/test/20211215135525.png
     *
     * @param text
     * @return
     */
    private static String removeFutileSlash(String text) {
        //如果url中出现了一次以上的//,则拼接有问题
        if (StrUtils.countShow(text, "//") > 1) {
            //则将第二次及其后面的//替换成/
            //以双斜杠拆分
            List<String> strList = Splitter.on("//").splitToList(text);
            //再将如http://或者https://与后面的字符串用/拼接
            text = StrUtils.join(strList.stream().findFirst().get(), "//", strList.stream().skip(1).collect(Collectors.joining("/")));
        }
        return text;
    }


    /**
     * "http" or "https".
     *
     * @return
     */
    private static String getScheme(String hosts) {
        return hosts.startsWith("http") || hosts.startsWith("https") ? hosts : "http://" + hosts;
    }


    /**
     * 利用反射获取构造方法,并且实例化对象
     * 这里返回的实例对象(可能会是RestManager、SerialUploader、ParallelUploader等其中一个)
     *
     * @param clazz 类
     * @param args  参数
     * @param <T>
     * @return
     */
    private static <T> T getInstance(Class<T> clazz, Object... args) {
        //先从缓存中获取
        T t = (T) INSTANCE_CACHE.get(clazz);
        try {
            if (Objects.isNull(t)) {
                Constructor<T> constructor = ReflectUtil.getConstructor(clazz, String.class, String.class, String.class);
                T newInstance = constructor.newInstance(args);
                INSTANCE_CACHE.put(clazz, newInstance);
                return newInstance;
            }
        } catch (Exception e) {
            log.error("获取构造方法失败,error:{}", e);
        }
        return t;
    }


    /**
     * 线程池 提交任务
     *
     * @return
     */
    public static List<String> submit(List<Callable<String>> tasks) throws Exception {
        List<String> result = new ArrayList<>();

        StopWatch stopWatch = new StopWatch("耗时统计:");
        AtomicInteger taskNum = new AtomicInteger();

        int availableProcessors = Runtime.getRuntime().availableProcessors();
        ThreadPoolExecutor threadPoolExecutor = new ThreadPoolExecutor(availableProcessors,
                availableProcessors + 1,
                60,
                TimeUnit.SECONDS,
                new LinkedBlockingQueue<>(50),
                new ThreadPoolExecutor.CallerRunsPolicy());
        try {
            for (Callable<String> task : tasks) {
                stopWatch.start(StrUtils.join("task:", taskNum.addAndGet(1)));
                result.add(threadPoolExecutor.submit(task).get());
                stopWatch.stop();
            }
            return result;
        } finally {
            //打印耗时结果
            log.debug(stopWatch.prettyPrint(TimeUnit.MILLISECONDS));
            threadPoolExecutor.shutdown();
        }
    }

}

 类似资料: