文件上传下载

0、文件下载-MP4分段下载、pdf多图片下载、zip压缩下载、普通下载

​
package com.northking.core.service.business;

import cn.hutool.core.codec.Base64;
import cn.hutool.crypto.SecureUtil;
import com.alibaba.fastjson.JSONObject;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.northking.base.limiter.LimitDownload;
import com.northking.common.cache.CfgMetadataCache;
import com.northking.common.cache.SysConfigCache;
import com.northking.common.constant.Constants;
import com.northking.common.constant.SysConfigConstants;
import com.northking.common.core.redis.RedisCache;
import com.northking.common.domain.entity.business.BusinessFile;
import com.northking.common.domain.entity.business.BusinessFileVerDownload;
import com.northking.common.domain.entity.business.BusinessFileVersion;
import com.northking.common.domain.entity.business.TokenSecretParams;
import com.northking.common.domain.vo.business.BusinessFileQueryVo;
import com.northking.common.enums.IsNoEnum;
import com.northking.common.enums.ResultEnum;
import com.northking.common.mapper.BusinessFileMapper;
import com.northking.common.mapper.BusinessFileVersionMapper;
import com.northking.common.utils.FileTokenUtils;
import com.northking.common.utils.aes.AESUtils;
import com.northking.common.utils.file.ImageUtils;
import com.northking.common.utils.file.ZipFileUtil;
import com.northking.core.Entity.cache.DownloadFileUrlCache;
import com.northking.core.Entity.request.RequestToDownloadFile;
import com.northking.core.Entity.request.RequestToMutiDownloadFile;
import com.northking.core.Entity.response.ResponseBody;
import com.northking.core.service.file.DownloadFileService;
import org.apache.commons.lang3.RandomUtils;
import org.apache.http.protocol.HTTP;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.BeanUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.util.CollectionUtils;
import org.springframework.web.util.UriUtils;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.*;
import java.math.BigDecimal;
import java.nio.charset.Charset;
import java.nio.charset.StandardCharsets;
import java.util.*;
import java.util.stream.Collectors;

/**
 * <p>
 * 文件下载 服务实现类
 * </p>
 *
 * @author northking
 * @since 2021-04-28
 */
@Service
public class  BusinessDownloadFileServiceImpl extends ServiceImpl<BusinessFileMapper, BusinessFile> {

    @Autowired
    private BusinessFileMapper businessFileMapper;

    @Autowired
    private SysConfigCache sysConfigCache;

    @Autowired
    private CfgMetadataCache cfgMetadataCache;

    @Autowired
    private RedisCache redisCache;

    @Autowired
    private DownloadFileService downloadFileService;

    @Autowired
    private BusinessFileVersionMapper businessFileVersionMapper;

    @Autowired
    private BusinessMetadataService businessMetadataService;

    protected final Logger log = LoggerFactory.getLogger(BusinessDownloadFileServiceImpl.class);

    /**
     * 获取文件二进制流
     * 根据分库分表策略: 加上SysCode和BatchNo分片键对数据库进行操作,否则会增删改查全库!!!
     * @param downloadFile 下载文件实体类
     * @return
     */
    public ResponseBody getFileStream(RequestToDownloadFile downloadFile,HttpServletResponse response, HttpServletRequest request) throws Exception{
        log.info("**********单文件下载初始化参数---下载请求参数为:{}",JSONObject.toJSONString(downloadFile));
        ResponseBody ret = checkParamsLegal(downloadFile,response,request);
        if (ret != null) return ret;
        BusinessFileVerDownload file = getFile(downloadFile); // stream: 获取指定文件的下载文件信息
        downLoadStreamFile(downloadFile, file, response, request); // 根据文件信息、文件流,向响应里填充文件信息
        return null;
    }

    public void downLoadStreamFile(RequestToDownloadFile downloadFile, BusinessFileVerDownload verDownload, HttpServletResponse response, HttpServletRequest request) throws Exception{
        InputStream stream = verDownload.getStream();
        BusinessFileVersion fileInfo = verDownload.getFileInfo();
        String fileName = fileInfo.getFileName();
        String isZipSave = fileInfo.getIsZipSave();

        //单文件上传 上传统一流处理 影响待分析 若为压缩存储,进行解压并将字节数组存放为字节数组流
        if(IsNoEnum.IS.getValue().equals(isZipSave)){
            int downloadFileSize = stream.available();
            byte[] buffer = new byte[downloadFileSize];
            stream.read(buffer);

            //进行解压处理
            byte[] bytes = ZipFileUtil.decompressOneByZip(buffer);
            stream=new ByteArrayInputStream(bytes);
        }

        response.reset(); // 清空response
        response.addHeader(Constants.RESPONSE_HEADER_CONTENT_DISPOSITION, "attachment;filename=" + UriUtils.encode(fileName, Charset.forName(Constants.UTF8))); // 设置response的Header
        response.setContentType(Constants.DOWNLOAD_CONTENT_TYPE_VAL); // 描述消息内容类型( 二进制流,不知道下载文件类型)
        response.setHeader(Constants.ACCESS_CONTROL_EXPOSE_HEADERS,Constants.RESPONSE_HEADER_CONTENT_DISPOSITION);

        extracted(downloadFile, response, stream, request);
        log.info("************************返回一个附件************************");
        // 文件流形式下载文件,必须返回null,否则出现convert issue
    }

    /**
     * 获取文件base64
     * @param downloadFile 下载文件实体类
     * @return
     */
    public ResponseBody getFileBase64(RequestToDownloadFile downloadFile,HttpServletResponse response, HttpServletRequest request) throws Exception {
        ResponseBody ret = checkParamsLegal(downloadFile,response,request);

        if (ret != null) return ret;
        BusinessFileVerDownload verDownload = getFile(downloadFile); // base64: 获取指定文件的下载文件信息
        InputStream stream = verDownload.getStream();
        // 转换格式
        String base64Str = Base64.encode(stream);
        InputStream base64Stream = new ByteArrayInputStream(base64Str.getBytes(StandardCharsets.UTF_8));

        extracted(downloadFile, response, base64Stream, request);

        return ResponseBody.success();
    }


    /**
     * 限速下载
     * @param downloadFile
     * @param response
     * @param inputStream
     * @throws IOException
     * @throws InterruptedException
     */
    private void extracted(RequestToDownloadFile downloadFile, HttpServletResponse response, InputStream inputStream, HttpServletRequest request) throws Exception{
        String rangeString = request.getHeader(Constants.VIDEO_DOWNLOAD_SIZE_RANGE);
        if(rangeString != null) {
            downloadRangeFile(inputStream, response, request);
        } else {
            // 非mp4下载
            if (downloadFile != null && downloadFile.isLimit()) {
                LimitDownload.limitDownloadFileBySpeed(inputStream, downloadFile.getSpeed(), response);
            } else {
                downloadFile(inputStream, response);
            }
        }

    }

    /**
     * 限速下载 - 根据文件存储路径
     * @param downloadFile
     * @param response
     * @param filePath
     * @throws IOException
     * @throws InterruptedException
     */
    private void extracted(RequestToDownloadFile downloadFile, HttpServletResponse response, String filePath, HttpServletRequest request) throws Exception {
        FileInputStream fileInputStream = new FileInputStream(filePath);
        extracted(downloadFile,response,fileInputStream,request);
    }

    /**
     * 流形式下载文件
     * @param fis
     * @param response
     * @throws IOException
     */
    private void downloadFileUnlimited(InputStream fis,HttpServletResponse response, HttpServletRequest request) throws IOException{
        String rangeString = request.getHeader(Constants.VIDEO_DOWNLOAD_SIZE_RANGE);
        if(rangeString != null) {
            downloadRangeFile(fis, response, request);
        } else {
            downloadFile(fis, response);
        }
    }

    /**
     * 文件流下载输出
     * @param fis
     * @param response
     */
    private void downloadFile(InputStream fis,HttpServletResponse response) throws IOException{
        OutputStream toClient = null;
        try {
            int downloadFileSize = fis.available();
            byte[] buffer = new byte[downloadFileSize];
            fis.read(buffer);
            toClient = response.getOutputStream();
            toClient.write(buffer);
            toClient.flush();
        } catch (IOException e) {
            log.error("文件下载异常 - 异常信息 \n{}",e);
            throw e;
        } finally {
            if(fis != null ) {
                try {
                    fis.close();
                } catch (IOException e) {
                    log.error("文件下载Finally - 关闭输入流异常 \n{}",e);
                }
            }
            if (toClient != null ) {
                try {
                    toClient.close();
                } catch (IOException e) {
                    log.error("文件下载Finally - 关闭输出流异常 \n{}",e);
                }
            }
        }
    }
    /**
     * 视频流-分片下载
     * 根据请求头获取参数下载,并响应返回range
     * @param fis 文件流
     * @param response
     * @param request
     * @throws IOException
     */
    private void downloadRangeFile(InputStream fis,HttpServletResponse response, HttpServletRequest request) throws IOException{
        //获取从那个字节开始读取文件
        String rangeString = request.getHeader(Constants.VIDEO_DOWNLOAD_SIZE_RANGE);
        OutputStream toClient = null;
        try {
            int fileLength = fis.available();
            if(rangeString != null){
                long startIndex = Long.valueOf(rangeString.substring(rangeString.indexOf("=") + 1, rangeString.indexOf("-")));
                int fileDownSize = BigDecimal.valueOf(fileLength).subtract(BigDecimal.valueOf(startIndex)).intValue();
                int fileDownloadSize = 1024 * 1000; // 1M一个返回响应
                //设置内容类型
                response.setHeader(Constants.RESPONSE_HEADER_CONTENT_TYPE,Constants.VIDEO_DOWNLOAD_CONTENT_TYPE);
                //设置此次相应返回的数据长度
                response.setHeader(Constants.RESPONSE_HEADER_CONTENT_LENGTH, String.valueOf(fileDownloadSize));
                //设置此次相应返回的数据范围
                BigDecimal endIndex = BigDecimal.valueOf(startIndex).add(BigDecimal.valueOf(fileDownloadSize)).subtract(BigDecimal.valueOf(1));
                response.setHeader(Constants.RESPONSE_HEADER_CONTENT_RANGE,
                        "bytes "
                                +startIndex+"-"
                                +(endIndex.compareTo(BigDecimal.valueOf(fileDownloadSize)) > 0 ? fileLength-1 : endIndex)
                                +"/"+fileLength); // Content-Range: bytes 2818048-2861992/2861993
                //返回码需要为206,而不是200
                response.setStatus(HttpServletResponse.SC_PARTIAL_CONTENT);
                response.setHeader(HTTP.CONN_DIRECTIVE, HTTP.CONN_KEEP_ALIVE);

                //设定文件读取开始位置(以字节为单位)
                fis.skip(startIndex);

    //            int downloadFileSize = fis.available(); // fis.available();   1024 * 1000
                byte[] buffer = new byte[1024 * 1000]; //
                fis.read(buffer);
                toClient = response.getOutputStream();
                toClient.write(buffer);
                toClient.flush();
            }
        } catch (IOException e) {
            log.error("文件Range下载异常 - 异常信息 \n{}",e);
            throw e;
        } finally {
            if(fis != null ) {
                try {
                    fis.close();
                } catch (IOException e) {
                    log.error("文件Range下载Finally - 关闭输入流异常 \n{}",e);
                }
            }
            if(toClient != null ) {
                try {
                    toClient.close();
                } catch (IOException e) {
                    log.error("文件Range下载Finally - 关闭输出流异常 \n{}",e);
                }
            }
        }
    }


    /**
     * 获取下载文件流
     * @param downloadFile
     * @return
     * @throws IOException
     */
    public BusinessFileVerDownload getFile(RequestToDownloadFile downloadFile) throws IOException{
        String fileId = downloadFile.getFileId();
        String batchNo = downloadFile.getBatchNo();
        String sysCode = downloadFile.getSysCode();
        boolean thumbDownload = downloadFile.isThumbDownload(); // false:默认原图
        boolean markDownload = downloadFile.isNoMarkDownload(); // false:默认加水印

        BusinessFileVerDownload verDownload = new BusinessFileVerDownload();
        //查询文件信息
        BusinessFileVersion fileInfo = businessFileVersionMapper.getFileVersionByIdAndSysCodeAndBusiNo(fileId, sysCode, batchNo);
        if(Objects.isNull(fileInfo)){
            throw new  RuntimeException("********查不到指定的文件********");
        }
        BusinessFile businessFile = new BusinessFile();
        BeanUtils.copyProperties(fileInfo,businessFile);
        String imgDownloadMethod = thumbDownload?Constants.IMAGE_DOWNLOAD_METHOD_THUMB:""; //单个文件下载-图片原图下载,可用户指定是否缩略图下载
        String addWatermarkMethod = markDownload?Constants.IMAGE_DOWNLOAD_METHOD_NO_MARK :""; //单个文件下载-图片原图下载,可用户指定是否加水印下载
        businessFile.setImgDownloadMethod(imgDownloadMethod);
        businessFile.setAddWatermarkMethod(addWatermarkMethod);
        businessFile.setOperId(downloadFile.getOperId());
        // fileInfo.getBucketId(),fileInfo.getBucketKey(),saveType,sysCode,imgDownloadMethod,addWatermarkMethod
        InputStream stream = downloadFileService.getFileStream(businessFile); //单个文件下载,图片原图下载,按需加水印
        verDownload.setStream(stream);
        verDownload.setFileInfo(fileInfo);
        return verDownload;
    }

    /**
     * 参数合法性校验
     * @param downloadFile
     * @return
     */
    private ResponseBody checkParamsLegal(RequestToDownloadFile downloadFile,HttpServletResponse response, HttpServletRequest request) throws Exception {

        String metadataName = downloadFile.getMetadataName();
        String ownerSysCode = downloadFile.getOwnerSysCode();

        String batchNo = downloadFile.getBatchNo();
        String fileId = downloadFile.getFileId();

        //1. token验证
        TokenSecretParams tokenSecretParams = new TokenSecretParams();
        tokenSecretParams.setBatchNo(batchNo);
        tokenSecretParams.setFileId(fileId);
        tokenSecretParams.setRandom(downloadFile.getRandom());
        tokenSecretParams.setTimeOut(downloadFile.getTimeOut()+"");
        tokenSecretParams.setIsLimit(downloadFile.isLimit());
        tokenSecretParams.setSysCode(downloadFile.getSysCode());
        tokenSecretParams.setSpeed(downloadFile.getSpeed()+"");

        String tokenStr = FileTokenUtils.getTokenString(tokenSecretParams);

        String token = SecureUtil.md5(AESUtils.encryptPwd(tokenStr));
        if (!token.equals(downloadFile.getToken())){
            //token校验不通过
//            return ResponseBody.error(ResultEnum.SYS_ERR.getTransCode(),String.format("url中token校验不通过,token:[%s]", downloadFile.getToken()));
        }
        //2. 重放攻击 以及 超时校验
        DownloadFileUrlCache urlCache = redisCache.getCacheObject(String.format("%s:%s", batchNo, fileId));
        int fileUrlPerSecNum = Integer.parseInt(sysConfigCache.getConfigValueByConfigKey(SysConfigConstants.FILE_URL_PER_SEC_ACCESS_NUM));
        if (null == urlCache ){
            Integer timeout = downloadFile.getTimeOut()!=null?downloadFile.getTimeOut():Integer.parseInt(Constants.timeOut);
            //首次访问
            redisCache.setCacheObject(String.format("%s:%s", batchNo, fileId),new DownloadFileUrlCache(1,System.currentTimeMillis(), timeout));
        }else {
            //非首次访问
            Integer accessNum = urlCache.getAccessNum();
            if (accessNum > fileUrlPerSecNum){
                redisCache.deleteObject(String.format("%s:%s", batchNo, fileId));
                return ResponseBody.error(ResultEnum.SYS_ERR.getTransCode(),"每秒访问频率过高,请稍后访问");
            }
            if (urlCache.getFirstAccessTime()+urlCache.getTimeOutSec()+1000 > System.currentTimeMillis()){
                redisCache.deleteObject(String.format("%s:%s", batchNo, fileId));
                return ResponseBody.error(ResultEnum.SYS_ERR.getTransCode(),"下载文件URL超时,请重试");
            }
        }
        //3. 权限验证,验证访问系统是否拥有访问此资源的权限
        //访问系统
        //生成流水的系统
        //3.跨系统访问其他系统文件验证
/*        if(StrUtil.isNotBlank(metadataName) && StrUtil.isNotBlank(ownerSysCode))
        {
            CfgMetadataPo cfgMetadataPo =new CfgMetadataPo();
            cfgMetadataPo.setOwnerSysCode(ownerSysCode);
            cfgMetadataPo.setMetadataName(metadataName);
            businessMetadataService.selectCfgMetadaOneCodeAndSysCodes(cfgMetadataPo,downloadFile,response,request);
        }else{
            return ResponseBody.error(ResultEnum.SYS_ERR.getTransCode(),String.format("系统[%s]无权下载此文件",sys));
        }*/
        return null;
    }


    /**
     * 文件打包下载
     * @param mutiDownloadFile 下载参数
     * @param response 文件返回流响应
     * @return
     * @throws Exception
     */
    public ResponseBody downloadPackageFiles(RequestToMutiDownloadFile mutiDownloadFile, HttpServletResponse response, HttpServletRequest request) throws Exception {
        log.info("************************批量ZIP下载---请求参数:{}", JSONObject.toJSONString(mutiDownloadFile));
        BusinessFileQueryVo businessFileVo = new BusinessFileQueryVo();
        BeanUtils.copyProperties(mutiDownloadFile,businessFileVo);
        RequestToDownloadFile downloadFile = mutiDownloadFile.getDownloadFile();

        List<BusinessFile> fileList = businessFileMapper.businessFileList(businessFileVo);
        if(CollectionUtils.isEmpty(fileList)){
            throw new RuntimeException("无法找到您的文件,请选择正确文件后再试");
        }
        List fileNames = fileList.stream().map(file->file.getFileName()).distinct().collect(Collectors.toList());
        if(fileNames.size() != fileList.size()){
            throw new RuntimeException("您选择下载的文件名相同,无法打包下载,请重命名后再试!");
        }

        //zip批量下载
        Map<String,byte[]> srcFilesMap = new HashMap<>();
        for (BusinessFile file : fileList) {
            file.setOperId(mutiDownloadFile.getOperId());
            setFileList(file, srcFilesMap);
        }

        try {
            byte[] bytes = ZipFileUtil.compressByZip(srcFilesMap); //获取文件流后压缩
            ByteArrayInputStream inputStream = new ByteArrayInputStream(bytes);

            String fileName = new Date().getTime() + "_"+ RandomUtils.nextLong()+".zip";
            response.reset(); // 清空response
            response.addHeader(Constants.RESPONSE_HEADER_CONTENT_DISPOSITION, String.format("attachment;filename=%s", UriUtils.encode(fileName, Charset.forName(Constants.UTF8)))); // 设置response的Header
            response.setContentType(Constants.DOWNLOAD_CONTENT_TYPE_VAL); // 描述消息内容类型( 二进制流,不知道下载文件类型)
            response.setHeader(Constants.ACCESS_CONTROL_EXPOSE_HEADERS,Constants.RESPONSE_HEADER_CONTENT_DISPOSITION);

            // 下载压缩文件
            extracted(downloadFile,response,inputStream,request);
            log.info("**************批量下载完成,返回附件**************");
        } catch (Exception e) {
            throw new RuntimeException("打包过程出错", e);
        }
        // 文件流形式下载文件,必须返回null,否则出现convert issue
        return null;
    }


    /**
     * 多图片文件PDF下载
     * @param mutiDownloadFile
     * @param response
     * @return
     * @throws Exception
     */
    public ResponseBody downloadPdfForPictures(RequestToMutiDownloadFile mutiDownloadFile, HttpServletResponse response, HttpServletRequest request) throws Exception {
        log.info("************************批量PDF下载---请求参数:{}", JSONObject.toJSONString(mutiDownloadFile));
        BusinessFileQueryVo businessFileVo = new BusinessFileQueryVo();
        BeanUtils.copyProperties(mutiDownloadFile,businessFileVo);
        RequestToDownloadFile downloadFile = mutiDownloadFile.getDownloadFile();

        List<BusinessFile> fileList= businessFileMapper.businessFileList(businessFileVo);

        if(fileList.isEmpty()){
            throw new RuntimeException("无法找到您的文件,请选择正确文件后再试!");
        }

        Date currentDate = new Date();
        String fileFullPath = currentDate.getTime() + "_"+ RandomUtils.nextLong() + ".pdf";
        log.info("*************************获取PDF路径******************************"+fileFullPath);
        OutputStream pdfOutputStream = new FileOutputStream(fileFullPath);

        Map<BusinessFile,InputStream> imagesMap = new HashMap<>();
         //PDF批量下载 修改为流式穿参
        for(BusinessFile file : fileList){
            if(ImageUtils.isImage(file.getFileType())) {
                // bucketId, bucketKey, storeType,sysCode
                file.setOperId(mutiDownloadFile.getOperId());
                InputStream stream = downloadFileService.getFileStream(file); //Pdf下载,图片下载为原图,不加水印

                imagesMap.put(file,stream);
            }
        }
        ImageUtils.writeImagesToPdf(imagesMap, pdfOutputStream);

        response.reset(); // 清空response
        response.addHeader(Constants.RESPONSE_HEADER_CONTENT_DISPOSITION, String.format("attachment;filename=%s", UriUtils.encode(fileFullPath, Charset.forName(Constants.UTF8)))); // 设置response的Header
        response.setContentType(Constants.DOWNLOAD_CONTENT_TYPE_VAL); // 描述消息内容类型( 二进制流,不知道下载文件类型)
        response.setHeader(Constants.ACCESS_CONTROL_EXPOSE_HEADERS,Constants.RESPONSE_HEADER_CONTENT_DISPOSITION);

        // 下载pdf文件
        extracted(downloadFile, response, fileFullPath,request);

        File delPdf = new File(fileFullPath);
        log.info("*************************返回PDF附件******************************");
        delPdf.delete();// 生成pdf时,写到了硬盘上作为临时文件,在写到response后,需要删除,否则占用空间

        return null;
    }

    /**
     * 根据不同存储方式获取文件,并将文件放到文件列表里
     * 1、多图片pdf合成下载
     * 2、多文件zip打包下载
     * @param file
     *
     * @param filesMap
     * @throws IOException
     */
    private void setFileList(BusinessFile file,Map<String,byte[]> filesMap) throws Exception{
        // bucketId, bucketKey, storeType,sysCode
        InputStream stream = downloadFileService.getFileStream(file); //Zip下载,图片下载原图,不加水印

        byte[] bytes = new byte[stream.available()];
        stream.read(bytes);

        //将流进行关闭
        try {
            stream.close();
        }catch (Exception e){
            e.printStackTrace();
            throw  e;
        }finally {
            if(stream!=null){
                stream.close();
            }
        }

        //若为压缩存储,进行解压处理
        if(IsNoEnum.IS.getValue().equals(file.getIsZipSave())){
            bytes = ZipFileUtil.decompressOneByZip(bytes);
        }

        filesMap.put(file.getFileName(),bytes);
    }
}

​

1、base64上传

package com.northking.core.service.business;

import cn.hutool.core.thread.NamedThreadFactory;
import com.alibaba.fastjson.JSONObject;
import com.alibaba.nacos.common.utils.CollectionUtils;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.northking.common.cache.CfgAccessSystemCache;
import com.northking.common.cache.SysConfigCache;
import com.northking.common.constant.Constants;
import com.northking.common.constant.SysConfigConstants;
import com.northking.common.constant.TableColumnConstants;
import com.northking.common.domain.dto.other.RedisStatisticParams;
import com.northking.common.domain.entity.business.*;
import com.northking.common.domain.entity.business.file.BusinessMetadataDto;
import com.northking.common.domain.entity.business.file.BusinessMetadataInfoRq;
import com.northking.common.domain.entity.params.CfgAccessSystem;
import com.northking.common.elasticsearch.entity.EsBusinessFileEntity;
import com.northking.common.elasticsearch.service.EsFileService;
import com.northking.common.mapper.BusinessBatchMapper;
import com.northking.common.mapper.BusinessFileMapper;
import com.northking.common.mapper.BusinessFileVersionMapper;
import com.northking.common.utils.FileTokenUtils;
import com.northking.common.utils.FileUtil;
import com.northking.common.utils.StringUtils;
import com.northking.common.utils.file.ImageUtils;
import com.northking.common.utils.statistic.StatisticUtils;
import com.northking.core.Entity.base.BaseBusinessBo;
import com.northking.core.Entity.base.BaseRequestBody;
import com.northking.core.Entity.bo.BatchUploadFileBo;
import com.northking.core.Entity.bo.UploadsFileBo;
import com.northking.core.Entity.request.RequestBodyToBatchUploadFile;
import com.northking.core.Entity.response.ResponseBody;
import com.northking.core.constants.RequestFieldConstants;
import com.northking.core.service.base.AbstractBusiService;
import com.northking.core.service.file.CommonFileService;
import com.northking.core.service.file.UploadFileService;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.BeanUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import javax.annotation.Resource;
import java.util.*;
import java.util.concurrent.*;
import java.util.stream.Collectors;

import static java.lang.String.format;

@Slf4j
@Service
public class ServiceOfUploadBatchUploadFile extends AbstractBusiService {


    @Autowired
    public CfgAccessSystemCache accessSystemCache;

    @Autowired
    public SysConfigCache sysConfigCache;

    @Resource
    private BusinessBatchMapper businessBatchMapper;

    @Resource
    private BusinessFileMapper businessFileMapper;

    @Resource
    private BusinessFileVersionMapper businessFileVersionMapper;

    @Autowired
    private UploadFileService uploadFileService;

    @Autowired
    private CommonFileService commonFileService;

    @Autowired
    private BusinessMetadataService businessMetadataService;
    @Autowired
    private BusinessFileInfoService businessFileInfoService;

    @Autowired
    private EsFileService esFileService;


    private ExecutorService saveExecutorService = Executors.newCachedThreadPool(new NamedThreadFactory("SaveFileThreads-",false));
    private ExecutorService uploadExecutorService = Executors.newCachedThreadPool(new NamedThreadFactory("UploadFileThreads-",false));

    /**
     * 请求报文校验
     *
     * @param jsonObject
     * @return
     */
    @Override
    public BaseRequestBody checkParam(JSONObject jsonObject) {
        String bizNo = jsonObject.getString(RequestFieldConstants.BUSINO);
        String sysId = jsonObject.getString(RequestFieldConstants.SYS_ID);
        String token = jsonObject.getString(Constants.TOKEN);
        BaseRequestBody builder = new RequestBodyToBatchUploadFile.Builder()
                .setBusiNo(bizNo)
                .setToken(token) // 返回上传后图片完全下载路径
                .checkIsValidAccessSys(bizNo, sysId)
                .setFileList(jsonObject.getJSONArray(RequestFieldConstants.FILE_LIST).toJavaList(RequestBodyToBatchUploadFile.FileInfo.class))
                .setSysId(sysId)
                .setTradeCode(jsonObject.getString(RequestFieldConstants.TRADE_CODE))
                .setSysKey(jsonObject.getString(RequestFieldConstants.SYS_KEY))
                .setOrderId(jsonObject.getIntValue(RequestFieldConstants.ORDET_ID))
                .setOperId(jsonObject.getString(RequestFieldConstants.OPER_ID))
                .build();
        return builder;
    }

    /**
     * 保存报文的信息
     *
     * @param requestBody
     * @return
     */
    @Override
    public boolean saveLog(BaseRequestBody requestBody) {
        return true;
    }

    /***
     * 组织响应报文
     * @param endBusinessBo
     * @return
     */
    @Override
    public ResponseBody packResult(BaseBusinessBo endBusinessBo) {
        BatchUploadFileBo uploadFileBo = (BatchUploadFileBo) endBusinessBo;
        List<UploadsFileBo> data = new ArrayList<>();
        log.info("*************** 上传文件-整理返回结果开始 ***************");
        String fileOnlinePreviewAddr = sysConfigCache.getConfigValueByConfigKey(SysConfigConstants.FILE_ONLINE_PREVIEW_ADDR);
        for (BatchUploadFileBo.FileListResult result : uploadFileBo.getRetFileList()) {
            UploadsFileBo bo = new UploadsFileBo();
            TokenSecretParams tokenSecretParams = new TokenSecretParams();
            String token = uploadFileBo.getToken();
            String sysCode = result.getSysId();
            CfgAccessSystem accessSystem = accessSystemCache.getAccessSystemBySysId(sysCode);
            String originalFilePath = getDownloadPath(result, token); // 上传文件,返回文件下载地址

            // 1.返回上传后【源文件】的完全下载地址
            result.setFileUrl(originalFilePath);

            // 2.返回上传后【图片缩略图】的完全下载地址
            tokenSecretParams.setThumbDownload(true);
            if(ImageUtils.isImageThumbRel(result.getFileType(), sysCode)){
                result.setImageThumbPath(getDownloadPath(result, token,"true")); // 上传文件,返回缩略图下载地址
            }

            //3.返回上传后图片【预览】的完全下载地址
            BusinessOnlinePreviewParams previewParams = new BusinessOnlinePreviewParams();
            previewParams.setSysCode(sysCode);
            previewParams.setFileName(result.getFileName());
            previewParams.setFilePath(originalFilePath);
            previewParams.setCurrentSystemName(accessSystem.getSysName());
            previewParams.setCurrentUser(result.getOperaId());
            previewParams.setPreviewFileNo(result.getFileId()+sysCode+result.getBusiNo()); // 预览-文件缓存名称
            bo.setFileOnlinePreviewAddr(fileOnlinePreviewAddr + FileTokenUtils.getPreviewSecretParams(previewParams)); //base上传文件-返回预览地址

            BeanUtils.copyProperties(result, bo);
            data.add(bo);
        }
        ResponseBody body = ResponseBody.success();
        body.put(ResponseBody.TRADE_DATA, data);
        log.info("*************** 上传文件-整理返回结果结束。返回结果为:{} ***************",JSONObject.toJSONString(body));
        return body;
    }

    /**
     * 扩展任务逻辑(图片转缩略图,ocr,pdf转换)
     *
     * @param baseBusinessBo
     * @return
     */
    @Override
    public BaseBusinessBo endCoreWork(BaseBusinessBo baseBusinessBo) {
        return baseBusinessBo;
    }

    /***
     * 执行核心操作
     * @param requestBody
     * @return
     */
    @Override
    public BaseBusinessBo doCoreWork(BaseRequestBody requestBody) {
        log.info("*************** Base64批量上传文件开始...***************");
        BatchUploadFileBo uploadFileBo = new BatchUploadFileBo();
        Date currentDate = new Date();
        RequestBodyToBatchUploadFile body = (RequestBodyToBatchUploadFile) requestBody;

        String bizNo = body.getBusiNo();
        String sysId = body.getSysId();
        String operId = body.getOperId();
        // 无流水号,则新增流水信息
        BusinessBatch batch = businessBatchMapper.selectSysIdAndSysCode(bizNo, sysId);
        if (batch == null || StringUtils.isEmpty(batch.getBatchNo())) {
            BusinessBatch businessBatch = new BusinessBatch();
            businessBatch.setBatchNo(bizNo);
            businessBatch.setSysCode(sysId);
            businessBatch.setCreatedBy(operId);
            businessBatch.setCreatedTime(currentDate);
            businessBatch.setUpdatedTime(currentDate);
            businessBatch.setStatus(Constants.VALID);

            int insertResult = businessBatchMapper.insert(businessBatch);

            log.info("流水号加缓存 - 流水统计-小文件上传-无流水号时创建流水-开始");
            RedisStatisticParams statisticParams = new RedisStatisticParams();
            statisticParams.setOperName("流水统计-小文件上传-无流水号时创建流水");
            statisticParams.setOperResult(insertResult);
            statisticParams.setSysCode(businessBatch.getSysCode());
            StatisticUtils.increaseForBatch(statisticParams);
            log.info("流水号加缓存 - 流水统计-小文件上传-无流水号时创建流水-开始");

            log.info("************** 向数据库新增不存在batchId的业务流信息 **************");
        }

        //标记文件上传顺序
//        AtomicInteger count = new AtomicInteger(1);
        //获取异步返回结果
        List<Future<BatchUploadFileBo.FileListResult>> futures = new ArrayList<>();
        for (RequestBodyToBatchUploadFile.FileInfo info : body.getFileList()) {
            String fileId = UUID.randomUUID().toString().replace("-","");
            info.setOperaId(operId);
            info.setFileId(fileId);
            info.setSysCode(body.getSysId());
            info.setBatchNo(body.getBusiNo());
            info.setToken(body.getToken());
            //使用自定义线程池,保证任务互不影响
            Future<BatchUploadFileBo.FileListResult> future = saveExecutorService.submit(() -> getResult(info,body));
            futures.add(future);
        }
        //循环获取结果
        List<BatchUploadFileBo.FileListResult> resultList = futures.stream().map(future -> {
            BatchUploadFileBo.FileListResult fileResult = new BatchUploadFileBo.FileListResult();
            try {
                fileResult = future.get();
            } catch (InterruptedException | ExecutionException e) {
                log.error("影像批量上传失败,线程中断:{}", e);
            }
            return fileResult;
        }).collect(Collectors.toList());

        uploadFileBo.setRetFileList(resultList);
        uploadFileBo.setToken(requestBody.getToken()); // 返回上传后图片完全下载路径
        log.info("*************** Base64批量上传文件结束 ***************");
        return uploadFileBo;
    }

    private BatchUploadFileBo.FileListResult getResult(RequestBodyToBatchUploadFile.FileInfo info,RequestBodyToBatchUploadFile body) {
        log.info("*************** Base64上传文件开始... ***************");
        BatchUploadFileBo.FileListResult result = new BatchUploadFileBo.FileListResult();
        String fileBase64 = info.getBase64();
        String fileType = info.getFileType();
        String targetFileId = info.getTargetFileId();
        String fileId = info.getFileId();
        try {
            // AA.上传实际文件 ---- 不返回上传结果,用 execute,性能好(可查看历史记录);返回上传结果,用submit,返回结果
            Future<Boolean> future = uploadExecutorService.submit(()->{
                String sysCode = info.getSysCode();
                String batchNo = info.getBatchNo();
                log.info("上传文件开始");
                boolean uploadRet = uploadFileService.uploadFile2Base64(sysCode, batchNo,
                        fileId, fileBase64, fileType);
                log.info("上传文件结束");
                if (!uploadRet) {
                    log.error("真实上传文件失败:{},{},{}", sysCode,batchNo,fileId);// throw new RuntimeException
                }
                return uploadRet;
            });

            boolean uploadResult = future.get();
            result.setUploadTrueFileSucc(uploadResult); // 实际文件上传结果返回
            result.setFileId(fileId);
            result.setTargetFileId(targetFileId); // 更新时使用
            // 水印 + ocr识别使用
            result.setFileType(fileType);
            //保存数据库
            if(uploadResult) {
                int dbUploadResult = saveBusinessToDataBase(info, result, body);
                // AA.上传文件信息 ---- 历史文件表插入成功算上传成功
                result.setUploadFileInfoSucc(dbUploadResult >0 ? true: false);
            }
            log.info("*************** Base64上传文件结束 ***************");
        } catch (Exception e) {
            result.setSuccess(false);
            result.setBillType(info.getBillType());
            log.error("-------------上传异常请求: {}", JSONObject.toJSONString(result));
        }
        return result;
    }

    /**
     * 保存文件表,文件版本表,文件源数据表
     * // !!!!!! 修改此方法,需要同步 BusinessFileInfoServiceImpl.saveBusinessToDataBase(...) !!!!!!
     *
     * @param info 各个文件的请求参数
     * @param result 各个文件数据库存储结果
     */
    @Transactional
    public int saveBusinessToDataBase(RequestBodyToBatchUploadFile.FileInfo info, BatchUploadFileBo.FileListResult result,RequestBodyToBatchUploadFile body) {
        String dbOperMsg = "";
        Date currentDate = new Date();
        String targetFileId = result.getTargetFileId();
        log.info("************** 向数据库保存文件存储成功信息开始 ... **************");
        String fileId = result.getFileId();
        String bizNo = info.getBatchNo();
        String sysId = info.getSysCode();
        String fileName = info.getFileName();
        String fileVer = commonFileService.getFileVersion(fileId);

        // 获取文件下载全路径地址 - 用于ocr识别
        result.setBusiNo(bizNo);
        result.setSysId(sysId);

        String fileType = info.getFileType();
        String operaId = info.getOperaId();
        String bucket = commonFileService.getBucket(sysId, bizNo); // file storage full folder path
        String key = commonFileService.getKey(sysId, bizNo, fileId, fileType); // full file name
        String filePath = format("%s%s", bucket, key);
        log.info("************** 向数据库保存文件存储请求主参数:fileId: {};bizNo:{}; sysId:{} **************", fileId, bizNo, sysId);

        String userBillType = StringUtils.isNotEmpty(info.getBillType()) ? info.getBillType() : Constants.NO_CLASSIFY;
        // 返回用
        result.setBusiNo(bizNo);
        result.setSysId(sysId);
        result.setFileName(fileName);
        result.setBillType(userBillType);
        result.setOperaId(operaId);

        BusinessFile businessFile = new BusinessFile();

        //数据库存储文件时,保存zip标识。图片上传即压缩
        String isZipSave = Constants.ONE_STR;
        boolean isDealImage = ImageUtils.isImageThumbRel(fileType,sysId);
        boolean isMp4 = "mp4".equals(fileType);
        if(!isDealImage && !isMp4){
            CfgAccessSystem accessSystem = accessSystemCache.getAccessSystemBySysId(sysId);
            isZipSave = accessSystem.getIsZipSave();
        }
        businessFile.setIsZipSave(isZipSave);

        businessFile.setFileId(fileId);
        businessFile.setFileSource("2001");
        businessFile.setBillCode(userBillType);

        businessFile.setFileName(fileName);
        // nas存储需要加密,则再获取md5
        if (commonFileService.getFileSaveIsEncryption()) {
            businessFile.setMd5(FileUtil.getFileMd5(info.getBase64()));
        }
        businessFile.setCreatedBy(operaId);
        businessFile.setCreatedTime(currentDate); // 新增时默认更新时间有值
        businessFile.setStatus(Constants.VALID);
        //默认设置为最新文件
        businessFile.setVersion(fileVer);

        businessFile.setBucketId(bucket);
        businessFile.setBucketKey(key);
        businessFile.setSaveType(commonFileService.getStorageType());

        businessFile.setFileType(fileType);
        businessFile.setBatchNo(bizNo);
        businessFile.setRemark(info.getRemark());
        businessFile.setSysCode(sysId);
        businessFile.setFileSize((long) FileUtil.getFileSize(info.getBase64()));
        businessFile.setFilePath(filePath);
        businessFile.setOrderId(info.getOrderId());
        // 替换现有文件
        if (StringUtils.isNotEmpty(targetFileId)) {
            log.info("************** 已有文件存在,根据已有文件ID更新数据库 - 开始 **************");
            // 更新 business_file
            businessFile.setFileId(targetFileId);
            businessFile.setUpdatedBy(operaId);
            businessFile.setUpdatedTime(currentDate);

            QueryWrapper<BusinessFile> wrapper = new QueryWrapper<>();
            wrapper.eq(TableColumnConstants.SYS_CODE, sysId)
                    .eq(TableColumnConstants.BATCH_NO, bizNo)
                    .eq(TableColumnConstants.FILE_ID, targetFileId);
            int updResult = businessFileMapper.update(businessFile, wrapper);

            if (updResult != 1) {
                dbOperMsg = "在已有文件上上传新版本失败,请重试!";
            }
            log.info("************** 已有文件存在,根据已有文件ID更新数据库{}条数据,错误结果:{} - 结束 **************", updResult, dbOperMsg);
        } else {
            log.info("************** 向根据业务流id向数据库新增文件信息 - 开始:fileId: {};bizNo:{}; sysId:{} **************", fileId, bizNo, sysId);
            businessFile.setFileId(fileId);

            int insertResult = businessFileMapper.insert(businessFile);

            log.info("文件加缓存 - 文件统计-小文件上传-增加相关统计访问数-开始");
            RedisStatisticParams statisticParams = new RedisStatisticParams();
            statisticParams.setOperName("文件统计-小文件上传-增加相关统计访问数");
            statisticParams.setOperResult(insertResult);
            statisticParams.setSysCode(businessFile.getSysCode());
            statisticParams.setFileSize(businessFile.getFileSize());
            statisticParams.setFileGroupType(FileUtil.getFileGroupType(businessFile.getFileType()));
            StatisticUtils.increaseForFile(statisticParams);
            log.info("文件加缓存 - 文件统计-小文件上传-增加相关统计访问数-结束");

            log.info("************** 根据业务流id向数据库新增文件信息 - 结束 **************");
        }

        BusinessFileVersion fileVersion = new BusinessFileVersion();
        BeanUtils.copyProperties(businessFile, fileVersion);
        fileVersion.setVersion(fileVer);
        fileVersion.setVerFileId(fileId);
        fileVersion.setCreateTime(currentDate);

        int insertVerResult = businessFileVersionMapper.insert(fileVersion);
        log.info("************** 根据业务流id及文件Id向数据库新增文件版本信息 **************");

        // 文件添加共享标签,使用已有标签
        String addWarningMsg = addMetadataForFile(info, result);
        result.setWarningMsg(addWarningMsg);

        // 必须在历史文件新增完,再去调用ocr识别,否则文件无法下载
        updateFileInfo(businessFile, fileVersion);

        log.info("************** 向数据库保存文件存储成功信息结束 ... **************");
//        log.info("---------------------- 保存第{}个文件成功 ----------------------", count);
//        count.getAndIncrement(); // 存到磁盘且数据库存储成功后,再加一
        // TODO 机构号 柜员号
        EsBusinessFileEntity esBusinessFileEntity = new EsBusinessFileEntity(StringUtils.isNotEmpty(targetFileId)?targetFileId:fileId,
                sysId, null, businessFile.getBatchNo(), fileName, fileType, userBillType,
                businessFile.getCreatedBy(), currentDate, null, null,null);
        esFileService.addOrUpdEsFile(esBusinessFileEntity);
        log.info("************** es保存文件信息 ... **************");

        return insertVerResult;
    }

    /**
     * 更新文件 - 文件OCR识别分类
     *
     * @param businessFile
     */
    private void updateFileInfo(BusinessFile businessFile, BusinessFileVersion businessFileVersion) {
        businessFileInfoService.updateFileOcrType(businessFile, businessFileVersion);
    }

    /**
     * 获取下载完全路径
     * @param result
     * @param token
     * @param extDownloadRd 扩展请求字段
     *                      1. extDownloadRd[0] ---> 是否缩略图下载
     * @return
     */
    private String getDownloadPath(BatchUploadFileBo.FileListResult result, String token, String... extDownloadRd) {
        String ipWithPort = sysConfigCache.getConfigValueByConfigKey(SysConfigConstants.SYS_IP_PORT_NEW);
        TokenSecretParams tokenSecretParams = new TokenSecretParams();
        boolean thumbDownload = false;
        if(StringUtils.isNotEmpty(extDownloadRd) && "true".equalsIgnoreCase(extDownloadRd[0])){
            thumbDownload = true;
        }
        tokenSecretParams.setBatchNo(result.getBusiNo());
        tokenSecretParams.setFileId(result.getFileId());
        tokenSecretParams.setSysCode(result.getSysId());
        tokenSecretParams.setToken(token); // 返回上传后图片完全下载路径
        tokenSecretParams.setThumbDownload(thumbDownload);
        tokenSecretParams.setOperId(result.getOperaId()); // 下载当前操作人
        String filePath = Constants.HTTP + ipWithPort + FileTokenUtils.getDownloadPath(tokenSecretParams); // base64上传
        return filePath;
    }


    /**
     * 给上传文件添加标签
     * 没有标签输入内容,则代表不添加共享标签
     * @param info
     * @param result
     *
     * @return
     */
    private String addMetadataForFile(RequestBodyToBatchUploadFile.FileInfo info, BatchUploadFileBo.FileListResult result) {
        List<RequestBodyToBatchUploadFile.MetadataInfo>  metadatas  =info.getMetadatas();
//        List<BusinessMetadataDto> metadataDtos = Arrays.stream(metadatas.stream().toArray(BusinessMetadataDto[]::new)).collect(Collectors.toList());
        List<BusinessMetadataDto> metadataDtos = new ArrayList<>();
        if(CollectionUtils.isNotEmpty(metadatas)){
            for (RequestBodyToBatchUploadFile.MetadataInfo metadata : metadatas) {
                BusinessMetadataDto businessMetadataDto =new BusinessMetadataDto();
                businessMetadataDto.setShareToSysCodes(metadata.getShareToSysCodes());
                businessMetadataDto.setMetadataName(metadata.getMetadataName());
                metadataDtos.add(businessMetadataDto);
            }

        }

        // 为一个文件添加多个标签 - 已有标签,未有标签,标签关联文件
        BusinessMetadataInfoRq metadata = new BusinessMetadataInfoRq();
        metadata.setSysCode(info.getSysCode());
        metadata.setBatchNo(info.getBatchNo());
        metadata.setFileId(result.getFileId());
        metadata.setCreateBy(info.getOperaId());
        metadata.setMetadatas(metadataDtos);
        String warningMsg = businessMetadataService.addMetadataForFile(metadata);

        return warningMsg;
    }


}

2、切片上传-验证文件是否存在、切片、合并文件 & 二进制上传-批量携带文件附属信息

package com.northking.core.service.business.impl;

import cn.hutool.core.thread.NamedThreadFactory;
import cn.hutool.core.util.RandomUtil;
import com.alibaba.fastjson.JSONArray;
import com.alibaba.fastjson.JSONObject;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.northking.base.storage.base.IBaseStorage;
import com.northking.base.storage.factory.StorageFactory;
import com.northking.common.cache.CfgAccessSystemCache;
import com.northking.common.cache.SysConfigCache;
import com.northking.common.constant.Constants;
import com.northking.common.constant.SysConfigConstants;
import com.northking.common.constant.TableColumnConstants;
import com.northking.common.domain.dto.other.RedisStatisticParams;
import com.northking.common.domain.entity.business.*;
import com.northking.common.elasticsearch.entity.EsBusinessFileEntity;
import com.northking.common.domain.entity.business.file.*;
import com.northking.common.domain.entity.params.CfgAccessSystem;
import com.northking.common.domain.vo.extend.IdentifiedPicInfoRq;
import com.northking.common.domain.vo.extend.IdentifiedPicRq;
import com.northking.common.elasticsearch.service.EsFileService;
import com.northking.common.enums.IsNoEnum;
import com.northking.common.mapper.*;
import com.northking.common.utils.FileTokenUtils;
import com.northking.common.utils.FileUtil;
import com.northking.common.utils.StringUtils;
import com.northking.common.utils.file.ImageUtils;
import com.northking.common.utils.file.ZipFileUtil;
import com.northking.common.utils.statistic.StatisticUtils;
import com.northking.common.utils.uuid.UUID;
import com.northking.core.config.ExtendsConfiguration;
import com.northking.core.service.business.BusinessFileInfoService;
import com.northking.core.service.business.BusinessMetadataService;
import com.northking.core.service.business.BusinessSystemService;
import com.northking.core.service.file.CommonFileService;
import com.northking.core.service.file.UploadFileService;
import com.northking.core.utils.FileInfoUtils;
import lombok.extern.slf4j.Slf4j;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.BeanUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.web.multipart.MultipartFile;

import javax.servlet.http.HttpServletResponse;

import java.io.*;
import java.nio.file.Files;
import java.nio.file.Paths;
import java.util.*;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Future;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.stream.Collectors;

import static java.lang.String.format;

@Service
@Slf4j
public class BusinessFileInfoServiceImpl implements BusinessFileInfoService {

    @Autowired
    public CfgAccessSystemCache accessSystemCache;

    @Autowired
    public SysConfigCache sysConfigCache;

    @Autowired
    private CommonFileService commonFileService;

    @Autowired
    private UploadFileService uploadFileService;

    @Autowired
    private BusinessSystemService businessSystemService;

    @Autowired
    private BusinessTrunckInfoMapper chunkInfoMapper;

    @Autowired
    private BusinessBatchMapper businessBatchMapper;


    @Autowired
    private BusinessMetadataService businessMetadataService;

    @Autowired
    private BusinessFileMapper businessFileMapper;

    @Autowired
    private BusinessFileVersionMapper businessFileVersionMapper;

    @Autowired
    private EsFileService esFileService;

    private final static Logger logger = LoggerFactory.getLogger(BusinessFileInfoServiceImpl.class);

    private ExecutorService fileSaveExeService = Executors.newCachedThreadPool(new NamedThreadFactory("FileSaveExecuteService-",false));


    @Override
    public int mergeFile(BusinessTrunckFileInfo fileInfo) throws Exception{
        logger.info("***********************获取切片合并文件参数{}:"+ JSONObject.toJSONString(fileInfo));
        int statusCode = HttpServletResponse.SC_UNSUPPORTED_MEDIA_TYPE;

        // 获取必要参数
        String fileMd5  = fileInfo.getIdentifier();
        String fileName = fileInfo.getFileName();
        String bizNo = fileInfo.getBatchNo();
        String sysId = fileInfo.getSysCode();

        String fileType  = StringUtils.isNotEmpty(fileName) && fileName.lastIndexOf(".")>0?fileName.substring(fileName.lastIndexOf(".")+1):"";
        String folder = commonFileService.getChunkBucket(fileInfo.getSysCode(),fileInfo.getBatchNo(),fileMd5);
        String file = folder + commonFileService.getChunkFullKey(fileMd5,fileType);
        String fileNewName = commonFileService.getChunkFullKey(fileMd5,fileType);
        logger.info("***********************获取文件名称******************************"+fileNewName);
        boolean uploadSucc = false;
        String fileId = RandomUtil.randomString(32);
        String bucket = commonFileService.getBucket(sysId, bizNo); // file storage full folder path
        String key = commonFileService.getKey(sysId, bizNo, fileId, fileType); // full file name
        boolean isEncryption = commonFileService.getFileSaveIsEncryption();
        String filePath = format("%s%s", bucket, key);
        logger.info("***********************获取文件路径******************************"+filePath);
        // 填充必要参数:文件存储+数据库记录
        fileInfo.setBucket(bucket);
        fileInfo.setKey(key);
        fileInfo.setFileId(fileId);
        fileInfo.setFileExtType(fileType);
        fileInfo.setFileFullPath(filePath); // 不同类型存储方式,filePath不同

        // 查找是否有相同存储文件
        BusinessFile businessFile = new BusinessFile();
        businessFile.setMd5(fileMd5);
        businessFile.setSysCode(sysId);
        businessFile.setBatchNo(bizNo);
        businessFile.setSaveType(commonFileService.getStorageType());
        BusinessFile businessFileRs = businessSystemService.doesFileTrulyExist(businessFile);

        /* 是否有相同存储文件:有相同文件 - 开始*/
        if(StringUtils.isNotEmpty(businessFileRs.getMd5())){
            // 获取相同文件信息后,向数据库写入文件相关的参数数据
            logger.info("************** 有相同存储文件:获取相同文件信息,保存文件信息到库 开始 ... **************");
            // 复制相同文件的路径到新文件里
            fileInfo.setBucket(businessFileRs.getBucketId());
            fileInfo.setKey(businessFileRs.getBucketKey());
            fileInfo.setFileFullPath(businessFileRs.getFilePath());

            saveBusinessToDataBase(fileInfo,null);
            statusCode = HttpServletResponse.SC_OK;
            logger.info("************** 有相同存储文件:获取相同文件信息,保存文件信息到库 结束 **************");
            return statusCode;
        }
        /* 是否有相同存储文件:有相同文件 - 结束*/

        /* 是否有相同存储文件:无相同文件 - 开始*/
        logger.info("************** 没有相同存储文件:存储文件,保存文件信息到库 开始 ... **************");
        Integer fileSuccess = FileInfoUtils.merge(file, folder, fileNewName.substring(1)); // 文件名去掉路径斜线

        // 合并成功后,删除切片块文件
        fileInfo.setLocation(folder);
        QueryWrapper<BusinessTrunckInfo> wrapper = new QueryWrapper<>();
        wrapper.eq(BusinessTrunckInfo.COL_SYS_CODE, sysId)
                .eq(BusinessTrunckInfo.COL_BATCH_NO, bizNo)
                .eq(BusinessTrunckInfo.COL_IDENTIFIER, fileMd5)
                .eq(BusinessTrunckInfo.COL_FILE_NAME, fileName);
        chunkInfoMapper.delete(wrapper);

        String isZipSave = dealWithOriginalFile(fileInfo, businessFile, isEncryption, fileName, file);

        // 文件合并成功后:nas-1)移动文件到目标路径; ceph-1)调用ceph分片上传存储; 2)保存记录到库
        if (fileSuccess == HttpServletResponse.SC_OK) {
            String currentStorageType = commonFileService.getStorageType().toLowerCase(); // nas, ceph ... commonFileService.getStorageType().toLowerCase()
            switch (currentStorageType){
                case Constants.STORAGE_TYPE_NAS:
                    // 在原有的nas文件路径下截取文件夹,与后面保存到数据库同步
                    String targetFileDirectory = filePath.lastIndexOf("/")>0?filePath.substring(0,filePath.lastIndexOf("/")):"";
                    // 有扩展名的不能丢,无扩展名的不能加“.”
                    String targetFileName = fileId + (StringUtils.isNotEmpty(fileType)?("." + fileType):"");
                    // 1)将合成的临时文件进行移动
                    logger.info("************** 文件合并成功,nas移动文件 开始 ... **************");
                    uploadSucc = FileInfoUtils.move(file, targetFileDirectory, targetFileName);
                    logger.info("************** 文件合并成功,nas移动文件 结束 ... **************");
                    break;
                case Constants.STORAGE_TYPE_CEPH:
                    fileInfo.setNasTempFilePath(file); // 临时源文件,即将要传到ceph上的文件
                    uploadSucc = uploadFileToCeph(fileInfo);
                    logger.error("文件上传ceph成功!");
                    break;
            }
            if(uploadSucc){
                // 2)存储成功后,向数据库写入文件相关的参数数据
                logger.info("************** 文件合并、存储成功,保存文件信息到库 结束 ... **************");
                //合并文件  增加对数据库文件是否zip的字段保存
                saveBusinessToDataBase(fileInfo,isZipSave);
                statusCode = HttpServletResponse.SC_OK;
            }
        }
        logger.info("************** 没有相同存储文件:存储文件,保存文件信息到库 结束 **************");
        return statusCode;
    }

    /**
     * 保存文件表,文件版本表,文件源数据表
     * // !!!!!! 修改此方法,需要同步 ServiceOfUploadBatchUploadFile.saveBusinessToDataBase(...) !!!!!!
     * @param fileInfo
     */
    private String saveBusinessToDataBase(BusinessTrunckFileInfo fileInfo,String isZipSave) {
        String dbOperMsg = "";
        Date currentDate = new Date();
        String targetFileId = fileInfo.getTargetFileId();
        logger.info("************** 向数据库保存文件存储成功信息开始 ... **************");
        String fileId = fileInfo.getFileId();
        String bizNo = fileInfo.getBatchNo();
        String sysId = fileInfo.getSysCode();
        String fileName = fileInfo.getFileName();
        String billType = StringUtils.isNotEmpty(fileInfo.getBillType())?fileInfo.getBillType():Constants.NO_CLASSIFY;
        String operaId = fileInfo.getOperId();
        String fileVer = commonFileService.getFileVersion(fileId);

        BusinessFile businessFile = new BusinessFile();
        businessFile.setIsZipSave(isZipSave);
        businessFile.setFileSource("2001");
        businessFile.setBillCode(billType);
        businessFile.setFileName(fileName);
        // 计算或者前端获取
        businessFile.setMd5(fileInfo.getIdentifier()); // 前端:identifier();后端:FileUtil.getFileMd5(info.getBase64())
        businessFile.setCreatedBy(operaId);
        businessFile.setCreatedTime(currentDate);
        businessFile.setStatus(Constants.VALID);
        //默认设置为最新文件
        businessFile.setVersion(fileVer);

        businessFile.setBucketId(fileInfo.getBucket());
        businessFile.setBucketKey(fileInfo.getKey());
        businessFile.setSaveType(commonFileService.getStorageType());

        businessFile.setFileType(fileInfo.getFileExtType());
        businessFile.setBatchNo(bizNo);
        businessFile.setSysCode(sysId);
        businessFile.setFileSize(fileInfo.getTotalSize());
        businessFile.setFilePath(fileInfo.getFileFullPath());

        if(StringUtils.isNotEmpty(targetFileId)){
            logger.info("************** 文件已经存在,根据已有文件ID更新数据库 - 开始 **************");
            // 更新 business_file
            businessFile.setFileId(targetFileId);
            businessFile.setUpdatedBy(operaId);
            businessFile.setUpdatedTime(currentDate);

            QueryWrapper<BusinessFile> wrapper = new QueryWrapper<>();
            wrapper.eq(TableColumnConstants.SYS_CODE,sysId)
                   .eq(TableColumnConstants.BATCH_NO,bizNo)
                    .eq(TableColumnConstants.FILE_ID,targetFileId);
            int result = businessFileMapper.update(businessFile,wrapper);

            if(result != 1){
                dbOperMsg = "在已有文件上上传新版本失败,请重试!";
            }
            logger.info("************** 文件已经存在,文件ID更新数据库{}条数据,错误结果:{} - 结束 **************", result, dbOperMsg);
        } else {
            logger.info("************** 根据业务流id向数据库新增文件信息 - 开始:fileId: {};bizNo:{}; sysId:{} **************", fileId, bizNo, sysId);
            businessFile.setFileId(fileId);

            int insertResult = businessFileMapper.insert(businessFile);

            RedisStatisticParams statisticParams = new RedisStatisticParams();
            statisticParams.setOperName("文件统计-大文件切片组合上传-增加相关统计访问数");
            statisticParams.setOperResult(insertResult);
            statisticParams.setSysCode(businessFile.getSysCode());
            statisticParams.setFileSize(businessFile.getFileSize());
            statisticParams.setFileGroupType(FileUtil.getFileGroupType(businessFile.getFileType()));
            StatisticUtils.increaseForFile(statisticParams);

            logger.info("************** 根据业务流id向数据库新增文件信息 - 结束 **************");
        }

        BusinessFileVersion businessFileVersion =new BusinessFileVersion();
        BeanUtils.copyProperties(businessFile, businessFileVersion);
        businessFileVersion.setVersion(fileVer);
        businessFileVersion.setVerFileId(fileId);
        businessFileVersion.setCreateTime(new Date());
        businessFileVersionMapper.insert(businessFileVersion);
        logger.info("************** 根据业务流id及文件Id向数据库新增文件版本信息 **************");
        return dbOperMsg;
    }

    /**
     * 上传处理源文件,处理后上传
     * 1.压缩文件 - 直接原文件压缩文件为zip,保存文件为一份
     * 2.图片转缩略图 - 保留源文件,异步生成缩略图文件后存储
     */
    private String dealWithOriginalFile(BusinessTrunckFileInfo uploadRq,BusinessFile businessFile,boolean isEncryption,String fileName, String path) throws Exception {
        FileInputStream is=null;
        FileOutputStream os=null;
        try {

            log.info("****************对图片进行处理,请求参数:{}****************", JSONObject.toJSONString(uploadRq));
            String sysCode = uploadRq.getSysCode();
            String bucket = uploadRq.getBucket();
            String orgFileId = uploadRq.getFileId();
            String fileNameSuffix = uploadRq.getFileExtType();

            //当为mp4时,不做处理
            if("mp4".equals(fileNameSuffix)){
                return IsNoEnum.NO.getValue();
            }

            boolean isDealImage = ImageUtils.isImageThumbRel(fileNameSuffix,sysCode);

            String isZipSave = accessSystemCache.getAccessSystemBySysId(sysCode).getIsZipSave();
            boolean doZip = Constants.ZERO_STR.equals(isZipSave);

            //当不是图片且不做zip处理,跳过判定
            if(!isDealImage && !doZip){
                return IsNoEnum.NO.getValue();
            }

            File file = new File(path);
            is = new FileInputStream(new File(path));
            byte[] bytes=new byte[is.available()];
            is.read(bytes);
            is.close();

            if(isDealImage){
                // 1.按接入系统配置的是否生成缩略图,生成压缩图片(上传文件本身已异步,此处不再异步生成)
                byte[] smallImgByte = ImageUtils.getThumbImage(bytes, fileNameSuffix, Constants.IMAGE_SCALE);
                String samllImgKey = commonFileService.getKey(sysCode, businessFile.getBatchNo(), orgFileId + Constants.THUMB_IMAGE_NAME_SUFFIX, fileNameSuffix);
                boolean upSucc = uploadFile2Byte(smallImgByte, bucket, samllImgKey, isEncryption, businessFile.getSaveType()); // 上传缩略图
                log.info("****************缩略图生成上传{}****************",upSucc?"成功":"失败");
                return IsNoEnum.NO.getValue();
            } else {
                // 2.按接入系统配置的是否压缩,进行文件压缩上传 (图片已经生成缩略图,不再压缩)
                byte[] zipBytes = ZipFileUtil.compressOneByZip(fileName, bytes);
                file.delete();
                os = new FileOutputStream(file);
                os.write(zipBytes);
                os.flush();
                os.close();
                return IsNoEnum.IS.getValue();
            }
        }catch (Exception e){
            logger.error("文件下载异常 - 异常信息 \n{}",e);
            throw e;
        }finally {
            if(is!=null){
                is.close();
            }
            if(os!=null){
                os.close();
            }
        }
    }

    /**
     * 通过byte数组上传文件
     * @param bytes
     * @param bucket
     * @param key
     * @param isEncryption
     * @param saveType - nas / ceph
     * @return
     */
    private boolean uploadFile2Byte(byte[] bytes,String bucket,String key,boolean isEncryption,String saveType){
        log.info("*************** 以字节数组 {} 形式上传文件开始... ***************",saveType);
        IBaseStorage storage = StorageFactory.createStorage(saveType);
        boolean isUploadSucc = storage.uploadFile(bytes, bucket, key, isEncryption);
        log.info("*************** 以字节数组 {} 形式上传文件结束,结果为:{}... ***************",saveType, isUploadSucc);
        return isUploadSucc;
    }



    /**
     * 切片传到ceph; 将临时合成文件删除
     * @param fileInfo
     * @return
     */
    private boolean uploadFileToCeph(BusinessTrunckFileInfo fileInfo){
        String sysCode = fileInfo.getSysCode();
        String batchNo = fileInfo.getBatchNo();
        String fileId = fileInfo.getFileId();
        String saveType = Constants.STORAGE_TYPE_CEPH;
        String tempFilePath = fileInfo.getNasTempFilePath();

        boolean uploadSucc = uploadFileService.uploadFile2Binary(sysCode,batchNo,fileId,fileInfo.getFileExtType(),tempFilePath,saveType);
        if(uploadSucc){
            try {
                Files.delete(Paths.get(tempFilePath));
            } catch (IOException e) {
                logger.error("删除临时文件失败!");
            }
        }
        return uploadSucc;
    }

    public void checkParamValidation(BusinessUploadFileRq fileInfo){
        String sysCode = fileInfo.getSysCode();
        String batchNo = fileInfo.getBatchNo();
        MultipartFile[] files = fileInfo.getBinaryFiles();
        String filesSpecificInfo = fileInfo.getFilesSpecificInfo();
        String validMsg = "";
        if(StringUtils.isEmpty(sysCode)){
            validMsg = "系统号不可为空";
        } else if(StringUtils.isEmpty(batchNo)){
            validMsg = "流水号不可为空";
        } else if(files !=null && files.length == 0){
            validMsg = "文件不可为空";
        } else if(StringUtils.isNotEmpty(filesSpecificInfo) && !StringUtils.isJsonArrayString(filesSpecificInfo)){
            validMsg = "文件附带属性不是Json形式的字符串数组";
        }
        if(StringUtils.isNotEmpty(validMsg)) {
            throw new RuntimeException(validMsg + ",请更正后再试");
        }
    }

    /**
     * 处理二进制文件上传,仅上传文件
     * @param fileInfo
     * @return
     */
    @Override
    public BusinessUploadFileRs uploadFiles(BusinessUploadFileRq fileInfo) throws Exception {
        checkParamValidation(fileInfo);
        // 文件上传公共请求参数
        String sysCode = fileInfo.getSysCode();
        String batchNo = fileInfo.getBatchNo();
        log.info("文件上传信息:{}, {}", sysCode, batchNo);
        String createBy = fileInfo.getOperId();
        String filesSpecificInfo = fileInfo.getFilesSpecificInfo();
        if(StringUtils.isNotEmpty(filesSpecificInfo)){
            JSONArray jsonArray = JSONObject.parseArray(filesSpecificInfo);
            BusinessFilesSpecificInfoRq[] filesInfo = jsonArray.stream().map(specificFileInfo->{
                String fileStringInfo = specificFileInfo.toString();
                BusinessFilesSpecificInfoRq specificInfoRq = JSONObject.parseObject(fileStringInfo, BusinessFilesSpecificInfoRq.class);
                return specificInfoRq;
            }).toArray(BusinessFilesSpecificInfoRq[]::new);
            fileInfo.setFileSpecificInfoArr(filesInfo);
        }
        BusinessFilesSpecificInfoRq[] fileSpecificInfoArr = fileInfo.getFileSpecificInfoArr();
        CfgAccessSystem accessSystem = accessSystemCache.getAccessSystemBySysId(sysCode);
        fileInfo.setAccessSystem(accessSystem);

        String saveType = commonFileService.getStorageType();
        String bucket = commonFileService.getBucket(sysCode,batchNo);

        MultipartFile[] files = fileInfo.getBinaryFiles();
        List<BusinessFile> uploadFiles = new ArrayList<>(files.length);

        // 循环存储真实文件
        int i = 0;
        for(MultipartFile file : files) {
            BusinessFile uploadFile = new BusinessFile();
            String fileId = UUID.get32UUID();
            Date nowTime = new Date();
            byte[] fileBytes = file.getBytes();
            long fileSize = file.getResource().contentLength();
            String fileName = file.getOriginalFilename();

            String fileSuffix = fileName.lastIndexOf(".") != -1 ? fileName.substring(fileName.lastIndexOf(".") + 1) : "";
            String fileVer = commonFileService.getFileVersion(fileId);
            String key = commonFileService.getKey(sysCode, batchNo, fileId, fileSuffix);
            // 文件用户指定属性
            Long orderId = fileSpecificInfoArr!=null && fileSpecificInfoArr.length>0? fileSpecificInfoArr[i].getOrderId():null;
            String orgBillCode = fileSpecificInfoArr!=null && fileSpecificInfoArr.length>0? fileSpecificInfoArr[i].getBillCode():"";
            String billCode = StringUtils.isNotEmpty(orgBillCode)?orgBillCode:Constants.noClassify;
            String sceneBillCode = StringUtils.isNotEmpty(orgBillCode)?orgBillCode:""; // 特殊需要-场景-ocr识别使用

            // 填充文件存储信息-开始
            uploadFile.setSysCode(sysCode);
            uploadFile.setBatchNo(batchNo);
            uploadFile.setFileId(fileId);
            uploadFile.setBucketId(bucket);
            uploadFile.setBucketKey(key);
            uploadFile.setFilePath(format("%s%s", bucket, key));
            uploadFile.setBillCode(billCode);
            uploadFile.setSceneBillCode(sceneBillCode);
            uploadFile.setFileName(fileName);
            uploadFile.setFileSize(fileSize);
            uploadFile.setOrderId(orderId);
            uploadFile.setFileType(fileSuffix);
            uploadFile.setStatus(Constants.ZERO_STR);
            uploadFile.setIsZipSave(accessSystem.getIsZipSave());
            uploadFile.setSaveType(saveType);
            uploadFile.setVersion(fileVer);
            uploadFile.setCreatedBy(createBy);
            uploadFile.setCreatedTime(nowTime);
            uploadFile.setSrcFileByte(fileBytes);
            uploadFiles.add(uploadFile);
            // 填充文件存储信息-结束
            i++;
        }
        fileInfo.setUploadFileInfoList(uploadFiles);
        // 存储文件信息、及其他与文件相关的信息
        BusinessUploadFileRs uploadFileRs = saveUploadFilesInfo(fileInfo);

        return uploadFileRs;
    }

    /**
     * 处理文件存储信息
     * @param fileInfo
     * @return
     * @throws Exception
     */
    @Override
    public BusinessUploadFileRs saveUploadFilesInfo(BusinessUploadFileRq fileInfo){
        List<BusinessFile> uploadFiles = fileInfo.getUploadFileInfoList();
        BusinessUploadFileRs uploadFileRs = new BusinessUploadFileRs();
        List<BusinessUploadFileInfoRs> uploadFilesInfoRs = new ArrayList<>();

        // 存储流水号信息
        saveUploadBatchInfo(fileInfo);

        // 存储多文件信息
        List<Future<BusinessUploadFileInfoRs>> responseFutures = new ArrayList<>();
        AtomicInteger uploadSeq = new AtomicInteger(0);
        for(BusinessFile file : uploadFiles) {
            // 多文件同时上传上传、存储信息 - 为了多个文件异步上传
            Future<BusinessUploadFileInfoRs> future = fileSaveExeService.submit(() -> saveUploadFileInfo(fileInfo, file, uploadSeq));
            responseFutures.add(future);
        }
        uploadFilesInfoRs = responseFutures.stream().map(resFuture->{
            BusinessUploadFileInfoRs uploadFileInfoRs = new BusinessUploadFileInfoRs();
            try {
                uploadFileInfoRs = resFuture.get();
            } catch (InterruptedException | ExecutionException e) {
                log.error("文件批量上传失败,线程中断:{}", e);
            }
            return uploadFileInfoRs;
        }).collect(Collectors.toList());

        uploadFileRs.setUploadFilesInfoRs(uploadFilesInfoRs);
        return uploadFileRs;
    }

    /**
     * 多文件同时上传上传、存储信息 - 为了多个文件异步上传
     * @param fileInfo
     * @param file
     * @return
     */
    private BusinessUploadFileInfoRs saveUploadFileInfo(BusinessUploadFileRq fileInfo,BusinessFile file,AtomicInteger uploadSeq){
        // 公共定义参数
        String sysCode = file.getSysCode();
        String batchNo = file.getBatchNo();
        String fileId = file.getFileId();
        String token = fileInfo.getToken();
        String operId = fileInfo.getOperId();
        int fileUploadSeq = uploadSeq.get();
        String ipWithPort = sysConfigCache.getConfigValueByConfigKey(SysConfigConstants.SYS_IP_PORT_NEW);
        String filePreviewAddr = sysConfigCache.getConfigValueByConfigKey(SysConfigConstants.FILE_ONLINE_PREVIEW_ADDR);
        CfgAccessSystem accessSystem = fileInfo.getAccessSystem();
        List<BusinessUploadFileInfoRs> uploadFilesInfoRs = new ArrayList<>();
        // 文件预览、下载拼接使用
        TokenSecretParams tokenSecretParams = new TokenSecretParams();
        BusinessOnlinePreviewParams previewParams = new BusinessOnlinePreviewParams();
        BusinessFilesSpecificInfoRq[] fileSpecificInfoArr = fileInfo.getFileSpecificInfoArr();
        // 文件上传响应信息
        BusinessUploadFileInfoRs uploadFileInfoRs = new BusinessUploadFileInfoRs();

        BeanUtils.copyProperties(file, uploadFileInfoRs); // 文件属性信息
        BeanUtils.copyProperties(file, tokenSecretParams); // 文件下载信息
        BeanUtils.copyProperties(file, previewParams); // 文件预览信息

        log.info("上传文件-实际文件上传-开始:{}, {}, {}, {}", sysCode, batchNo, fileId, file.getFileName());
        boolean uploadResult = uploadFileService.uploadFile2Bytes(file.getSrcFileByte(),sysCode,batchNo,fileId,file.getFileType());
        log.info("上传文件-实际文件上传-结束,结果为:{}", uploadResult);
        // 实际文件上传失败,不再进行文件存储,及文件其他操作处理
        if(!uploadResult){
            uploadFileInfoRs.setUploadTrueFileSucc(false);
            uploadFileInfoRs.setUploadFileInfoSucc(false);
            return uploadFileInfoRs;
        }

        tokenSecretParams.setToken(token);
        tokenSecretParams.setOperId(operId); // 下载当前操作人
        String fileDownloadUrl = Constants.HTTP + ipWithPort + FileTokenUtils.getDownloadPath(tokenSecretParams); // 二进制上传-原文件地址
        String imageThumbPath = null;
        previewParams.setFilePath(fileDownloadUrl); // 预览-下载地址
        file.setFileDownloadUrl(fileDownloadUrl); // ocr识别-下载地址
        if(ImageUtils.isImageThumbRel(file.getFileType(),sysCode)) {
            tokenSecretParams.setThumbDownload(true);
            imageThumbPath = Constants.HTTP + ipWithPort + FileTokenUtils.getDownloadPath(tokenSecretParams); // 二进制上传-缩略图地址
        }

        log.info("上传文件-文件信息存储-开始");
        int fileInsRs = businessFileMapper.insert(file);

        BusinessFileVersion fileVersion = new BusinessFileVersion();
        BeanUtils.copyProperties(file, fileVersion);
        fileVersion.setVerFileId(file.getFileId());
        fileVersion.setCreateTime(new Date());
        int fileVerInsRs = businessFileVersionMapper.insert(fileVersion);
        log.info("上传文件-文件信息存储-结束:{}", fileVerInsRs);

        log.info("上传文件-图片OCR识别-开始");
        updateFileOcrType(file, fileVersion);
        log.info("上传文件-图片OCR识别-结束:{}", file.getBillCode());

        // 存储标签、关联文件标签信息 - 若有警示信息,仅起到警示作用不一一返回,做统一返回
        if(fileSpecificInfoArr != null && fileSpecificInfoArr.length != 0) {
            log.info("上传文件-添加关联标签-开始");
            String addMetadataWarnMsg = saveFileMetadataInfo(file, fileSpecificInfoArr[fileUploadSeq]);
            uploadFileInfoRs.setUploadFileTagWarnMsg(addMetadataWarnMsg);
            log.info("上传文件-添加关联标签-结束:{}", addMetadataWarnMsg);
        }

        log.info("上传文件-文件保存返回信息-开始");
        uploadFileInfoRs.setUploadTrueFileSucc(uploadResult);
        uploadFileInfoRs.setUploadFileInfoSucc(fileInsRs>0 && fileVerInsRs>0?true:false);
        uploadFileInfoRs.setFileDownloadUrl(fileDownloadUrl);
        uploadFileInfoRs.setImageThumbPath(imageThumbPath);

        previewParams.setCurrentSystemName(accessSystem.getSysName());
        previewParams.setCurrentUser(operId);
        previewParams.setPreviewFileNo(fileId+sysCode+batchNo); // 预览-文件缓存名称
        uploadFileInfoRs.setFilePreviewUrl(filePreviewAddr + FileTokenUtils.getPreviewSecretParams(previewParams)); // 批量binary-上传返回

        uploadFilesInfoRs.add(uploadFileInfoRs);
        log.info("上传文件-文件上传返回信息-结束:{}", JSONObject.toJSONString(uploadFileInfoRs));

        log.info("上传文件-上传、保存第 {} 个文件成功 ----------------------", uploadSeq.get()+1); // 起始角标为0
        uploadSeq.getAndIncrement();

        // TODO 机构号 柜员号
        EsBusinessFileEntity esBusinessFileEntity = new EsBusinessFileEntity(file.getFileId()
                , file.getSysCode(), null, file.getBatchNo(), file.getFileName(), file.getFileType(),
                file.getBillCode(), file.getCreatedBy(), fileVersion.getCreateTime(), null, null, null);
        esFileService.addOrUpdEsFile(esBusinessFileEntity);

        return uploadFileInfoRs;
    }

    /**
     * 处理流水号信息
     * @param fileInfo
     * @return
     * @throws Exception
     */
    @Override
    public void saveUploadBatchInfo(BusinessUploadFileRq fileInfo){
        QueryWrapper qw = new QueryWrapper();
        qw.eq(TableColumnConstants.SYS_CODE, fileInfo.getSysCode());
        qw.eq(TableColumnConstants.BATCH_NO, fileInfo.getBatchNo());
        int cnt = businessBatchMapper.selectCount(qw);
        if(cnt == 0){
            BusinessBatch batch = new BusinessBatch();
            BeanUtils.copyProperties(fileInfo,batch);
            batch.setStatus(Constants.ZERO_STR);
            batch.setCreatedTime(new Date());
            log.info("上传文件-添加流水号请求参数为:{}", JSONObject.toJSONString(fileInfo.getFilesSpecificInfo()));
            businessBatchMapper.insert(batch);
        }
    }

    /**
     * 处理标签及文件与标签关联关系
     * @param businessFile
     * @param fileInfoRq
     * @return
     */
    @Override
    public String saveFileMetadataInfo(BusinessFile businessFile, BusinessFilesSpecificInfoRq fileInfoRq){
        String ownerSysCode = businessFile.getSysCode();
        String batchNo = businessFile.getBatchNo();
        String createBy = businessFile.getCreatedBy();
        List<BusinessMetadataDto> metadatas = fileInfoRq.getMetadatas();

        // 为一个文件添加多个标签 - 已有标签,未有标签,标签关联文件
        BusinessMetadataInfoRq metadata = new BusinessMetadataInfoRq();
        metadata.setSysCode(ownerSysCode);
        metadata.setBatchNo(batchNo);
        metadata.setFileId(businessFile.getFileId());
        metadata.setCreateBy(createBy);
        metadata.setMetadatas(metadatas);
        String warningMsg = businessMetadataService.addMetadataForFile(metadata);
        return warningMsg;
    }

    /**
     * 处理文件自动分类。上传文件后,更新文件分类 - 获取OCR识别类型 - 一个文件
     * @param businessFile 更新文件 - 分类仅存在于最新表中
     * @param businessFileVersion 校验文件可否下载 - 防止历史表数据未生成,下载文件都走历史表
     */
    @Override
    public void updateFileOcrType(BusinessFile businessFile, BusinessFileVersion businessFileVersion) {
        String fileId = businessFile.getFileId();
        String billType = businessFile.getBillCode();
        String sceneBillType = businessFile.getSceneBillCode();
        Long fileInsertId = businessFileVersion.getId();
        CfgAccessSystem accessSystem = accessSystemCache.getAccessSystemBySysId(businessFile.getSysCode());
        boolean needOcrCheck = Constants.ZERO_STR.equals(accessSystem.getNeedOcrIdentified()) ? true : false;
        boolean isImage = ImageUtils.isImage(businessFile.getFileType());
        if (!isImage || !needOcrCheck || fileInsertId == null) {
            log.info("**********未进行ocr识别!此文件是否是图片:{};此系统是否需要OCR识别:{};历史文件ID:[{}];***********",
                    isImage, needOcrCheck, fileInsertId);
            return;
        }
        log.info("**********文件插入后的文件ID:{};文件信息:{}***********", fileInsertId, JSONObject.toJSONString(businessFile));

        // ocr识别
        String fileDownloadPath = businessFile.getFileDownloadUrl(); // 已存在文件下载地址
        IdentifiedPicRq identifiedPicRq = new IdentifiedPicRq();
        List<IdentifiedPicInfoRq> dataList = new ArrayList<>();
        IdentifiedPicInfoRq picInfoRq = new IdentifiedPicInfoRq(fileId, fileDownloadPath);
        picInfoRq.setFileBillCode(billType);
        dataList.add(picInfoRq);
        identifiedPicRq.setData_list(dataList);
        identifiedPicRq.setOcrIdentifiedUri(ExtendsConfiguration.ocrIdentifiedUri);
        // 仅识别一个文件 - 多文件识别时,时间过长
        List<String> billCodes = ImageUtils.identifiedPicBillCode(needOcrCheck, identifiedPicRq);
        String oneFileOcrBillCode = billCodes != null && billCodes.size() > 0 ? (StringUtils.isNotEmpty(billCodes.get(0))?billCodes.get(0):""):"";
        boolean needCombineOcrRes = StringUtils.isNotEmpty(sceneBillType)
                && StringUtils.isNotEmpty(oneFileOcrBillCode) ? true : false;
        billType = needCombineOcrRes? sceneBillType +"-"+ oneFileOcrBillCode :
                StringUtils.isNotEmpty(oneFileOcrBillCode)? oneFileOcrBillCode :
                        StringUtils.isNotEmpty(billType)? billType : Constants.NO_CLASSIFY;

        QueryWrapper qw = new QueryWrapper();
        qw.eq(TableColumnConstants.BATCH_NO, businessFile.getBatchNo());
        qw.eq(TableColumnConstants.SYS_CODE, businessFile.getSysCode());
        qw.eq(TableColumnConstants.FILE_ID, businessFile.getFileId());

        businessFile.setBillCode(billType);
        businessFile.setUpdatedBy(businessFile.getCreatedBy());
        businessFile.setUpdatedTime(new Date());
        businessFileMapper.update(businessFile, qw); // 文件分类仅存在于最新文件下,无需更新历史列表

        esFileService.updEsFileBillCode(fileId,billType);
    }
}
package com.northking.core.service.business.impl;


import com.alibaba.fastjson.JSONObject;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.northking.common.domain.entity.business.BusinessFile;
import com.northking.common.domain.entity.business.BusinessTrunckInfo;
import com.northking.common.domain.po.business.BusinessTrunckResult;
import com.northking.common.mapper.BusinessTrunckInfoMapper;
import com.northking.common.utils.StringUtils;
import com.northking.core.service.business.BusinessSystemService;
import com.northking.core.service.business.BusinessTrunkInfoService;
import com.northking.core.service.file.CommonFileService;
import com.northking.core.utils.FileInfoUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.web.multipart.MultipartFile;

import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.ArrayList;
import java.util.Date;

@Service
public class BusinessTrunkInfoServiceImpl extends ServiceImpl<BusinessTrunckInfoMapper, BusinessTrunckInfo>
        implements BusinessTrunkInfoService {
    @Autowired
    private BusinessTrunckInfoMapper chunkInfoMapper;

    @Autowired
    private CommonFileService commonFileService;
    @Autowired
    private BusinessSystemService businessSystemService;

    protected final Logger log= LoggerFactory.getLogger(BusinessTrunkInfoServiceImpl.class);
    /**
     * 校验当前文件
     * @param chunkInfo
     * @return 秒传?续传?新传?
     */
    @Override
    public BusinessTrunckResult checkChunkState(BusinessTrunckInfo chunkInfo) {
        log.info("******************文件切片上传参数校验{}*********"+ JSONObject.toJSONString(chunkInfo));
        BusinessFile businessFile = new BusinessFile();
        businessFile.setMd5(chunkInfo.getIdentifier());
        businessFile.setSysCode(chunkInfo.getSysCode());
        businessFile.setBatchNo(chunkInfo.getBatchNo());
        businessFile.setSaveType(commonFileService.getStorageType());
        BusinessFile businessFileRs = businessSystemService.doesFileTrulyExist(businessFile);

        BusinessTrunckResult chunkResult = new BusinessTrunckResult();
        // 判断是否有MD5相同的文件 - 同一个流水号,同一系统,同一存储
        if(StringUtils.isNotEmpty(businessFileRs.getMd5())){
            chunkResult.setSkipUpload(true);
            chunkResult.setLocation(businessFileRs.getFilePath());
            chunkResult.setMessage("完整文件已存在,直接跳过上传,实现秒传");
            log.info("*************秒上传文件成功**************");
            return chunkResult;
        }
        // 查找块文件,传递到哪个块
        ArrayList<Integer> list = chunkInfoMapper.selectChunkNumbers(chunkInfo);
        if (list !=null && list.size() > 0) {
            chunkResult.setSkipUpload(false);
            chunkResult.setUploadedChunks(list);
            chunkResult.setMessage("部分文件块已存在,继续上传剩余文件块,实现断点续传");
            return chunkResult;
        }
        log.info("******************断点续传文件切片上传成功*********");
        return chunkResult;
    }

    /**
     * 写文件
     * @param chunk
     * @return
     */
    @Override
    public Integer uploadFile(BusinessTrunckInfo chunk) {
        log.info("*************************文件传参数校验{}**********************"+JSONObject.toJSONString(chunk));
        Integer apiRlt = HttpServletResponse.SC_OK;
        MultipartFile file = chunk.getBinaryFile();
        try {
            byte[] bytes = file.getBytes();
            // 将块文件临时写到nas上
            String fileMd5  = chunk.getIdentifier();
            String fileName = chunk.getFilename();
            String fileType  = StringUtils.isNotEmpty(fileName) && fileName.lastIndexOf(".")>0?fileName.substring(fileName.lastIndexOf(".")+1):"";
            String uploadFolder = commonFileService.getChunkBucket(chunk.getSysCode(),chunk.getBatchNo(),fileMd5);
            String uploadFile = commonFileService.getChunkKey(fileMd5,fileType,chunk.getChunkNumber().toString());
            Path path = Paths.get(FileInfoUtils.generatePath(uploadFolder, uploadFile));
            log.info("*****************获取文件路径*******************"+path);
            Files.write(path, bytes);
            chunk.setCreateTime(new Date());
            if(chunkInfoMapper.insert(chunk) < 0){
                apiRlt = HttpServletResponse.SC_UNSUPPORTED_MEDIA_TYPE;
            }
        } catch (IOException e) {
            log.error("写文件出错:{}"+e.getMessage());
            apiRlt = HttpServletResponse.SC_UNSUPPORTED_MEDIA_TYPE;
        }
        return apiRlt;
    }

}

3、公共读写方法



    /**
     * 将多个图片合成一个PDF文件
     * @param imagesMap 图片文件数组
     * @param pdfOutputStream PDF文件保存地址
     *
     */
    public static void writeImagesToPdf(Map<BusinessFile,InputStream> imagesMap, OutputStream pdfOutputStream) throws Exception{
        //创建一个文档对象
        Document doc = new Document(null, 0, 0, 0, 0);
        try {
            // 创建输出文件的位置
            PdfWriter.getInstance(doc, pdfOutputStream);
            Set<Map.Entry<BusinessFile, InputStream>> entries = imagesMap.entrySet();
            for (Map.Entry<BusinessFile, InputStream> entry : entries) {
                InputStream imageIS=null;
                try {
                    doc.newPage(); // 一个图片一页

                    BusinessFile file = entry.getKey();
                    imageIS = entry.getValue();

                    int available = imageIS.available();
                    byte[] bytes=new byte[available];
                    imageIS.read(bytes);

                    if(IsNoEnum.IS.getValue().equals(file.getIsZipSave())){
                        //进行解压处理
                        bytes = ZipFileUtil.decompressOneByZip(bytes);
                    }
                    ByteArrayInputStream is = new ByteArrayInputStream(bytes);
                    BufferedImage bufferedImg = ImageIO.read(is);
                    doc.setPageSize(new com.lowagie.text.Rectangle(bufferedImg.getWidth(), bufferedImg.getHeight()));
                    com.lowagie.text.Image image = com.lowagie.text.Image.getInstance(bytes);
                    //开启文档
                    doc.open();
                    doc.add(image);
                }catch (Exception e){
                    log.error("图片转pdf异常 - Exception 异常信息:{};\n异常问题路径:",e.getMessage(),e.getStackTrace());
                    throw  e;
                }finally {
                    if(imageIS!=null){
                        imageIS.close();
                    }
                }
            }
            // 关闭文档
            doc.close();
        } catch (IOException e) {
            log.error("图片转pdf异常 - IOException 异常信息:{};\n异常问题路径:",e.getMessage(),e.getStackTrace());
        } catch (BadElementException e) {
            log.error("图片转pdf异常 - BadElementException 异常信息:{};\n异常问题路径:",e.getMessage(),e.getStackTrace());
        } catch (DocumentException e) {
            log.error("图片转pdf异常 - DocumentException 异常信息:{};\n异常问题路径:",e.getMessage(),e.getStackTrace());
        } finally {
            if(doc != null) {
                doc.close();
                log.info("图片转pdf - Finally最终处理 :关闭PDF文件");
            }
            if(pdfOutputStream != null){
                try {
                    pdfOutputStream.close();
                } catch (IOException e) {
                    log.error("图片转pdf异常 - Finally关闭文件流-异常信息:{};\n异常问题路径:",e.getMessage(),e.getStackTrace());
                }
            }
        }
    }







    /**
     * 使用ZIP算法进行压缩.
     * @param sourceFileBytesMap 待压缩文件的Map集合.
     * @return 压缩后的ZIP文件字节数组.
     * @throws Exception 压缩过程中可能发生的异常,若发生异常,则返回的字节数组长度为0.
     */
    public static byte[] compressByZip(Map<String, byte[]> sourceFileBytesMap) throws Exception {
        // 变量定义.
        ZipEntry zipEntry = null;
        ZipOutputStream zipZos = null;
        ByteArrayOutputStream zipBaos = null;

        try {
            // 压缩文件变量初始化.
            zipBaos = new ByteArrayOutputStream();
            zipZos = new ZipOutputStream(zipBaos);
            // 将文件添加到ZIP条目中.
            if (sourceFileBytesMap != null && sourceFileBytesMap.size() > 0) {
                for (Map.Entry<String, byte[]> singleFile : sourceFileBytesMap.entrySet()) {
                    zipEntry = new ZipEntry(singleFile.getKey());
                    zipZos.putNextEntry(zipEntry);
                    zipZos.write(singleFile.getValue());
                }
            } else {
                zipBaos = new ByteArrayOutputStream();
            }
        } finally {
            if (zipBaos != null)
                zipBaos.close();
            if (zipZos != null)
                zipZos.close();
        }
        return zipBaos.toByteArray();
    }

    /**
     * 使用ZIP算法进行解压.
     * @param sourceZipFileBytes ZIP单文件字节数组.
     * @return 解压后的单文件字节数组
     * @throws Exception 解压过程中可能发生的异常,若发生异常抛异常
     */
    public static byte[] decompressOneByZip(byte[] sourceZipFileBytes) throws Exception {
        Map<String, byte[]> fileMap = ZipFileUtil.decompressByZip(sourceZipFileBytes);
        //若结果为空返回空字节数组,若结果不为空,返回第一个字节数组
        if(fileMap ==null || fileMap.size()==0)
            return  new byte[0];
        Map.Entry<String, byte[]> next=fileMap.entrySet().iterator().next();
        return next.getValue();
    }





    /**
     * 按照固定比例生成缩略图
     * @param imageOrgBytes
     * @param imageOrgType
     * @return
     */
    public static byte[] getThumbImage(byte[] imageOrgBytes, String imageOrgType, double imageScale){
        try {
            ByteArrayOutputStream bao = new ByteArrayOutputStream();
            ByteArrayInputStream inputStream = new ByteArrayInputStream(imageOrgBytes);

            Image image = ImageIO.read(inputStream);
            int width = image.getWidth(null); //获取原图宽度
            int height = image.getHeight(null);//获取原图高度
            BigDecimal bigDecimalWidth = new BigDecimal(width);
            BigDecimal bigDecimalHeight = new BigDecimal(height);
            BigDecimal scale = new BigDecimal(imageScale);
            int newWidth = bigDecimalWidth.multiply(scale).intValue();
            int newHeight = bigDecimalHeight.multiply(scale).intValue();
            BufferedImage bufferedImage = new BufferedImage(newWidth, newHeight, BufferedImage.TYPE_INT_RGB);

            //图片缩略图实现
            bufferedImage.getGraphics().drawImage(image.getScaledInstance(newWidth, newHeight, image.SCALE_SMOOTH), 0, 0, null);
            ImageIO.write(bufferedImage, imageOrgType, bao);

            String thumbBaseStr = Base64.getEncoder().encodeToString(bao.toByteArray());
            byte[] thumbImgBytes =  Base64.getDecoder().decode(thumbBaseStr);
            return thumbImgBytes;
        } catch (IOException e) {
            log.error("按照高度和宽度生成缩略图失败{}", e);
            return null;
        }

    }



    /**
     * 创建路径
     * @param uploadFolder
     * @param fileName
     * @return 文件
     */
    public static String generatePath(String uploadFolder, String fileName) {
        StringBuilder sb = new StringBuilder();
        sb.append(uploadFolder);
        //判断uploadFolder/identifier 路径是否存在,不存在则创建
        if (!Files.isWritable(Paths.get(sb.toString()))) {
            log.info("路径不存在,新建路径: {}", sb.toString());
            try {
                Files.createDirectories(Paths.get(sb.toString()));
            } catch (IOException e) {
                log.error("创建路径错误:{},{}",e.getMessage(), e);
            }
        }
        return sb.append(fileName).toString();
    }

    /**
     * 合并文件
     * @param file
     * @param folder
     * @param filename
     * @return 状态
     */
    public static Integer merge(String file, String folder, String filename){
        //默认合并成功
        Integer rlt = HttpServletResponse.SC_OK;
        try {

            //不存在的话,进行合并
            Files.createFile(Paths.get(file));
            Files.list(Paths.get(folder))
                    .filter(path -> !path.getFileName().toString().equals(filename))
                    .sorted((o1, o2) -> {
                        String p1 = o1.getFileName().toString();
                        String p2 = o2.getFileName().toString();
                        int i1 = p1.lastIndexOf("-");
                        int i2 = p2.lastIndexOf("-");
                        return Integer.valueOf(p2.substring(i2)).compareTo(Integer.valueOf(p1.substring(i1)));
                    })
                    .forEach(path -> {
                        try {
                            //以追加的形式写入文件
                            Files.write(Paths.get(file), Files.readAllBytes(path), StandardOpenOption.APPEND);
                            //合并后删除该块
                            Files.delete(path);
                        } catch (IOException e) {
                            log.error("删除文件失败:{},{}",e.getMessage(), e);
                        }
                    });
        } catch (IOException e) {
            log.error("合并失败:{},{}",e.getMessage(), e);
            //合并失败
            rlt = HttpServletResponse.SC_BAD_REQUEST;
        }
        return rlt;
    }
    /**
     * 移動文件
     */
    public static boolean move(String originalFile,String targetFileDirectory,String targetFileName) {
        log.info("move start...");
        boolean moveSucc = false;
        try {
            Path orgFile = Paths.get(originalFile);
            Path tarPath = Paths.get(targetFileDirectory);
            if(!Files.exists(tarPath)){
                Files.createDirectories(tarPath);
            }
            Path tarFile = Paths.get(tarPath + "/" + targetFileName);
            Path path = Files.move(orgFile,tarFile,StandardCopyOption.REPLACE_EXISTING);
            if(path!=null && path.getFileName()!=null && StringUtils.isNotEmpty(path.getFileName().toString())){
                moveSucc = true;
            }
        } catch (IOException e) {
            log.error("文件移动到目标NAS路径失败!\n"+e.getMessage()+"\n"+e.getStackTrace());
            e.printStackTrace();
        }
        log.info("move end...");
        return moveSucc;

    }





    /**
     * 图片添加水印 - 图片、文字、图片和文字
     *
     * @param waterMarVo 水印实体参数类
     * @return 添加完水印的图片的base64字符串
     */
    public static byte[] imageAddWaterMark(WaterMarVo waterMarVo) throws Exception{
        log.info("Enter ImageAddWaterMarkService.imageAddWaterMark  and waterMarVo is "+ waterMarVo.toString());
        byte[] srcImageByte = waterMarVo.getSrcImageBytes();

        byte[] returnBytes = null;
        //如果为添加文字
        if (ImageProcessConstant.WATER_MARK_TEXT.equals(waterMarVo.getType())
                || ImageProcessConstant.WATER_MARK_ALL.equals(waterMarVo.getType())) {
            returnBytes = addTextWaterMark(srcImageByte, waterMarVo);
        }
        String picImageMarkBase = waterMarVo!=null && waterMarVo.getWaterMarPicVo()!=null?waterMarVo.getWaterMarPicVo().getPicImageBase():"";
        if (ImageProcessConstant.WATER_MARK_PIC.equals(waterMarVo.getType()) && StringUtils.isNotEmpty(picImageMarkBase)) {
            byte[] picWaterMark = Base64.getDecoder().decode(picImageMarkBase);
            returnBytes = addPicWaterMark(srcImageByte, waterMarVo, picWaterMark);
        }
        if (ImageProcessConstant.WATER_MARK_ALL.equals(waterMarVo.getType()) && returnBytes != null && StringUtils.isNotEmpty(picImageMarkBase)) {
            byte[] picWaterMark = Base64.getDecoder().decode(picImageMarkBase);
            returnBytes = addPicWaterMark(returnBytes, waterMarVo, picWaterMark);
        }

        log.info("Exit ImageAddWaterMarkService.imageAddWaterMark result length is " + returnBytes.length);
        return returnBytes;
    }

    /**
     * 添加图片水印
     * @param srcImageBytes
     * @param waterMarVo
     * @param picImageBytes
     * @return
     * @throws IOException
     */
    public static byte[] addPicWaterMark(byte[] srcImageBytes, WaterMarVo waterMarVo, byte[] picImageBytes) throws IOException {
        WaterMarPicVo waterMarPicVo = waterMarVo.getWaterMarPicVo();
        try (ByteArrayInputStream srcStream=new ByteArrayInputStream(srcImageBytes);
             ByteArrayOutputStream baos = new ByteArrayOutputStream()) {
            Image image=ImageIO.read(srcStream);
            int imageWidth = image.getWidth(null);
            int imageHeight = image.getHeight(null);
            double rotate = UipConfig.getWatermarkRotate()!=0?UipConfig.getWatermarkRotate():-10;
            BufferedImage bufferedImage = new BufferedImage(imageWidth, imageHeight, BufferedImage.TYPE_INT_RGB);
            Graphics2D graphics2D = bufferedImage.createGraphics();
            /**设置对线段的锯齿状边缘处理**/
            graphics2D.setRenderingHint(RenderingHints.KEY_INTERPOLATION, RenderingHints.VALUE_INTERPOLATION_BILINEAR);
            graphics2D.drawImage(image.getScaledInstance(imageWidth, imageHeight, image.SCALE_SMOOTH), 0, 0, null);
            if (ImageProcessConstant.ALL.equals(waterMarPicVo.getLocation())) {
                /**设置水印旋转**/
                graphics2D.rotate(Math.toRadians(rotate));
            }
            graphics2D.setComposite(AlphaComposite.getInstance(AlphaComposite.SRC_ATOP,
                    waterMarPicVo.getAlpha() == 0 ? 1.0f : waterMarPicVo.getAlpha()));
            //处理水印图片
            ImageIcon imageIcon=new ImageIcon(picImageBytes);
            Image stampImage=imageIcon.getImage();
            addPic(waterMarPicVo.getLocation(), imageWidth, imageHeight, stampImage, graphics2D);
            graphics2D.setComposite(AlphaComposite.getInstance(AlphaComposite.SRC_OVER));
            graphics2D.dispose();

            ImageIO.write(bufferedImage, waterMarVo.getFileType(), baos);
            return  baos.toByteArray();
        } catch (IOException e) {
            throw new IOException(e);
        }


    }


    /**
     * 添加文字水印
     * **** 若汉字乱码,则需要在java.home路径的lib文件夹下放置fonts字体,并重启服务生效 ****
     * @param srcBytes
     * @param waterMarVo
     * @return
     */
    public static byte[] addTextWaterMark(byte[] srcBytes, WaterMarVo waterMarVo) throws IOException {
        WaterMarTextVo waterMarTextVo = waterMarVo.getWaterMarTextVo();
        ByteArrayOutputStream baos = new ByteArrayOutputStream();
        ByteArrayInputStream srcStream = new ByteArrayInputStream(srcBytes);
        try {
            Image image = ImageIO.read(srcStream);
            int imageWidth = image.getWidth(null);
            int imageHeight = image.getHeight(null);
            String fontFamily = StringUtils.isNotEmpty(waterMarTextVo.getFontFamily())?waterMarTextVo.getFontFamily():"微软雅黑";
            double rotate = UipConfig.getWatermarkRotate()!=0?UipConfig.getWatermarkRotate():-10;
            BufferedImage bufferedImage = new BufferedImage(imageWidth, imageHeight, BufferedImage.TYPE_INT_RGB);
            Graphics2D graphics2D = bufferedImage.createGraphics();
            graphics2D.drawImage(image, 0, 0, imageWidth, imageHeight, null);
            // 字体与预览项目配置同步
            Font font = new Font(fontFamily, Font.PLAIN, waterMarTextVo.getTextSize() == 0 ? 24 : waterMarTextVo.getTextSize());
            graphics2D.setColor(waterMarTextVo.getTextColor() == null ? Color.gray : waterMarTextVo.getTextColor());
            graphics2D.setFont(font);
            int length = getWatermarkLength(waterMarTextVo.getText(), graphics2D);
            //当铺满全屏时有旋转
            if (ImageProcessConstant.ALL.equals(waterMarTextVo.getLocation())) {
                //设置水印旋转
                graphics2D.rotate(Math.toRadians(rotate));
            }
            //设置透明度
            graphics2D.setComposite(AlphaComposite.getInstance(AlphaComposite.SRC_ATOP,
                    waterMarTextVo.getAlpha() == 0 ? 1.0f : waterMarTextVo.getAlpha()));
            //添加水印
            addText(waterMarTextVo, graphics2D, length, imageWidth, imageHeight, font);
            graphics2D.dispose();
            ImageIO.write(bufferedImage, waterMarVo.getFileType(), baos);
            return baos.toByteArray();
        } catch (IOException e) {
            throw new IOException(e);
        }
    }

    /**
     * 获取字体的长度
     *
     * @param waterMarkContent
     * @param graphics2D
     * @return
     */
    private static int getWatermarkLength(String waterMarkContent, Graphics2D graphics2D) {
        return graphics2D.getFontMetrics(graphics2D.getFont()).charsWidth(waterMarkContent.toCharArray(), 0, waterMarkContent.length());
    }

    /**
     * 根据不同位置添加文字水印
     *
     * @param waterMarTextVo
     * @param graphics2D
     * @param length
     * @param imageWidth
     * @param font
     * @param imageHeight
     */
    private static void addText(WaterMarTextVo waterMarTextVo, Graphics2D graphics2D, int length, int imageWidth, int imageHeight, Font font) {
        //默认左上
        int x = 20;
        int y = 30;
        String markTxt = waterMarTextVo.getText();
        switch (waterMarTextVo.getLocation()) {
            /*case ImageProcessConstant.TOP_LEFT:
                x = 20;
                y = 30;
                break;*/
            case ImageProcessConstant.TOP_CENTER:
                x = (imageWidth - length) / 2;
                y = 30;
                break;
            case ImageProcessConstant.TOP_RIGHT:
                x = imageWidth - length - 20;
                y = 30;
                break;
            case ImageProcessConstant.BOTTOM_LEFT:
                x = 20;
                y = imageHeight - 30;
                break;
            case ImageProcessConstant.BOTTOM_CENTER:
                x = (imageWidth - length) / 2;
                y = imageHeight - 30;
                break;
            case ImageProcessConstant.BOTTOM_RIGHT:
                x = imageWidth - length - 20;
                y = imageHeight - 30;
                break;
            case ImageProcessConstant.CENTER:
                x = (imageWidth - length) / 2;
                y = imageHeight / 2;
                break;
        }
        if (!ImageProcessConstant.ALL.equals(waterMarTextVo.getLocation())) {
            graphics2D.drawString(markTxt, x, y);
        } else {
            // 整铺
            JLabel label = new JLabel(markTxt);
            FontMetrics metrics = label.getFontMetrics(font);
            int width = metrics.stringWidth(label.getText());//文字水印的宽
            int height = metrics.getHeight();
            int rowsNumber = imageHeight / height;// 图片的高  除以  文字水印的宽    ——> 打印的行数(以文字水印的宽为间隔)
            int columnsNumber = imageWidth / width;//图片的宽 除以 文字水印的宽   ——> 每行打印的列数(以文字水印的宽为间隔)
            //防止图片太小而文字水印太长,所以至少打印一次
            if (rowsNumber < 1) {
                rowsNumber = 1;
            }
            if (columnsNumber < 1) {
                columnsNumber = 1;
            }
            double blankRows = UipConfig.getWatermarkBlankRows()!=0?UipConfig.getWatermarkBlankRows():1.5;
            for (int j = 0; j < rowsNumber; j++) {
                for (int i = 0; i < columnsNumber; i++) {
                    BigDecimal xIdxOrg = new BigDecimal(i * width + i * height);
                    BigDecimal yIdxOrg = new BigDecimal(j * height + j * height);
                    int xIdx = (xIdxOrg.multiply(new BigDecimal(Double.toString(blankRows)))).intValue();
                    int yIdx = (yIdxOrg.multiply(new BigDecimal(Double.toString(blankRows)))).intValue();
                    graphics2D.drawString(markTxt, xIdx, yIdx);
                }
            }
        }
    }

    /**
     * 添加图片水印 默认左上
     *
     * @param location
     * @param srcWidth
     * @param srcHeight
     * @param stampImage
     * @param graphics2D
     */
    private static void addPic(String location, int srcWidth, int srcHeight, Image stampImage, Graphics graphics2D) {
        int imarkWidth = stampImage.getWidth(null);
        int imarkHeight = stampImage.getHeight(null);
        int x = 0;
        int y = 0;
        switch (location) {
            /*case ImageProcessConstant.TOP_LEFT:
                x = 0;
                y = 0;
                break;*/
            case ImageProcessConstant.TOP_CENTER:
                x = (srcWidth - imarkWidth) / 2;
                y = 0;
                break;
            case ImageProcessConstant.TOP_RIGHT:
                x = srcWidth - imarkWidth;
                y = 0;
                break;
            case ImageProcessConstant.BOTTOM_LEFT:
                x = 0;
                y = srcHeight - imarkHeight;
                break;
            case ImageProcessConstant.BOTTOM_CENTER:
                x = (srcWidth - imarkWidth) / 2;
                y = srcHeight - imarkHeight;
                break;
            case ImageProcessConstant.BOTTOM_RIGHT:
                x = srcWidth - imarkWidth;
                y = srcHeight - imarkHeight;
                break;
            case ImageProcessConstant.CENTER:
                x = (srcWidth - imarkWidth) / 2;
                y = (srcHeight - imarkHeight) / 2;
                break;
        }
        if (!ImageProcessConstant.ALL.equals(location)) {
            graphics2D.drawImage(stampImage, x, y, null);
        } else {
            // 整铺
            int rowsNumber = srcHeight / imarkHeight;// 图片的高  除以  文字水印的宽    ——> 打印的行数(以文字水印的宽为间隔)
            int columnsNumber = srcWidth / imarkWidth;//图片的宽 除以 文字水印的宽   ——> 每行打印的列数(以文字水印的宽为间隔)
            //防止图片太小而文字水印太长,所以至少打印一次
            if (rowsNumber < 1) {
                rowsNumber = 1;
            }
            if (columnsNumber < 1) {
                columnsNumber = 1;
            }
            for (int j = 0; j < rowsNumber; j++) {
                for (int i = 0; i < columnsNumber; i++) {
                    graphics2D.drawImage(stampImage, i * imarkWidth + i * imarkHeight, j * imarkHeight + j * imarkHeight, null);
                }
            }
        }

    }

    /**
     * 将多个图片合成一个PDF文件
     * @param imagesMap 图片文件数组
     * @param pdfOutputStream PDF文件保存地址
     *
     */
    public static void writeImagesToPdf(Map<BusinessFile,InputStream> imagesMap, OutputStream pdfOutputStream) throws Exception{
        //创建一个文档对象
        Document doc = new Document(null, 0, 0, 0, 0);
        try {
            // 创建输出文件的位置
            PdfWriter.getInstance(doc, pdfOutputStream);
            Set<Map.Entry<BusinessFile, InputStream>> entries = imagesMap.entrySet();
            for (Map.Entry<BusinessFile, InputStream> entry : entries) {
                InputStream imageIS=null;
                try {
                    doc.newPage(); // 一个图片一页

                    BusinessFile file = entry.getKey();
                    imageIS = entry.getValue();

                    int available = imageIS.available();
                    byte[] bytes=new byte[available];
                    imageIS.read(bytes);

                    if(IsNoEnum.IS.getValue().equals(file.getIsZipSave())){
                        //进行解压处理
                        bytes = ZipFileUtil.decompressOneByZip(bytes);
                    }
                    ByteArrayInputStream is = new ByteArrayInputStream(bytes);
                    BufferedImage bufferedImg = ImageIO.read(is);
                    doc.setPageSize(new com.lowagie.text.Rectangle(bufferedImg.getWidth(), bufferedImg.getHeight()));
                    com.lowagie.text.Image image = com.lowagie.text.Image.getInstance(bytes);
                    //开启文档
                    doc.open();
                    doc.add(image);
                }catch (Exception e){
                    log.error("图片转pdf异常 - Exception 异常信息:{};\n异常问题路径:",e.getMessage(),e.getStackTrace());
                    throw  e;
                }finally {
                    if(imageIS!=null){
                        imageIS.close();
                    }
                }
            }
            // 关闭文档
            doc.close();
        } catch (IOException e) {
            log.error("图片转pdf异常 - IOException 异常信息:{};\n异常问题路径:",e.getMessage(),e.getStackTrace());
        } catch (BadElementException e) {
            log.error("图片转pdf异常 - BadElementException 异常信息:{};\n异常问题路径:",e.getMessage(),e.getStackTrace());
        } catch (DocumentException e) {
            log.error("图片转pdf异常 - DocumentException 异常信息:{};\n异常问题路径:",e.getMessage(),e.getStackTrace());
        } finally {
            if(doc != null) {
                doc.close();
                log.info("图片转pdf - Finally最终处理 :关闭PDF文件");
            }
            if(pdfOutputStream != null){
                try {
                    pdfOutputStream.close();
                } catch (IOException e) {
                    log.error("图片转pdf异常 - Finally关闭文件流-异常信息:{};\n异常问题路径:",e.getMessage(),e.getStackTrace());
                }
            }
        }
    }




  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值