package com.artfess.file.persistence.manager.impl;

import cn.hutool.core.date.DateUnit;
import cn.hutool.core.date.DateUtil;
import com.artfess.base.attachment.AttachmentService;
import com.artfess.base.attachment.AttachmentServiceFactory;
import com.artfess.base.constants.CodePrefix;
import com.artfess.base.context.BaseContext;
import com.artfess.base.enums.FileTypeEnum;
import com.artfess.base.exception.ApplicationException;
import com.artfess.base.exception.BaseException;
import com.artfess.base.exception.RequiredException;
import com.artfess.base.feign.UCFeignService;
import com.artfess.base.handler.MultiTenantHandler;
import com.artfess.base.handler.MultiTenantIgnoreResult;
import com.artfess.base.manager.impl.BaseManagerImpl;
import com.artfess.base.model.CommonResult;
import com.artfess.base.util.AppUtil;
import com.artfess.base.util.BeanUtils;
import com.artfess.base.util.FileUtil;
import com.artfess.base.util.JsonUtil;
import com.artfess.base.util.StringUtil;
import com.artfess.base.util.UniqueIdUtil;
import com.artfess.base.util.ZipUtil;
import com.artfess.base.util.time.TimeUtil;
import com.artfess.file.attachmentService.FtpAttachmentServiceImpl;
import com.artfess.file.config.UploadResult;
import com.artfess.file.model.DefaultFile;
import com.artfess.file.params.FileStorageConfigDTO;
import com.artfess.file.params.FlowUploadPropertiesStorageDTO;
import com.artfess.file.persistence.dao.FileDao;
import com.artfess.file.persistence.manager.FileConfigManager;
import com.artfess.file.persistence.manager.FileManager;
import com.artfess.file.persistence.manager.FlowUploadPropertiesManager;
import com.artfess.file.service.FilePreview;
import com.artfess.file.service.FilePreviewFactory;
import com.artfess.file.util.AppFileUtil;
import com.artfess.file.util.FileUtils;
import com.artfess.file.util.UploadM3u8;
import com.artfess.file.vo.FileReqVo;
import com.artfess.file.vo.MultipartCompleteParam;
import com.artfess.file.vo.MultipartInitParam;
import com.artfess.file.vo.MultipartInitRespVo;
import com.artfess.redis.util.RedisUtil;
import com.artfess.sysConfig.util.SysPropertyUtil;
import com.artfess.uc.api.model.IUser;
import com.artfess.uc.api.service.IUserService;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.fasterxml.jackson.databind.node.ArrayNode;
import com.fasterxml.jackson.databind.node.ObjectNode;
import lombok.extern.slf4j.Slf4j;
import org.apache.http.client.methods.CloseableHttpResponse;
import org.apache.http.client.methods.HttpPut;
import org.apache.http.entity.ByteArrayEntity;
import org.apache.http.impl.client.CloseableHttpClient;
import org.apache.http.impl.client.HttpClientBuilder;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.util.Assert;
import org.springframework.util.DigestUtils;
import org.springframework.web.multipart.MultipartFile;

import javax.annotation.Resource;
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.io.Serializable;
import java.time.Instant;
import java.time.LocalDateTime;
import java.time.ZoneId;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Date;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.concurrent.Callable;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Future;

@Slf4j
@Service("fileManager")
public class FileManagerImpl extends BaseManagerImpl<FileDao, DefaultFile> implements FileManager {
    @Resource
    UCFeignService ucFeignService;
    @Resource
    IUserService userService;
    @Resource
    BaseContext baseContext;
    @Resource
    FileConfigManager fileConfigManager;

    @Autowired
    private RedisUtil redisUtil;
    @Resource(name = "bmpExecutorService")
    private ExecutorService executorService;
    @Resource
    FilePreviewFactory previewFactory;

    private AttachmentService attachmentService;

    private static final String CHUNK_UPLOAD_PREFIX = "/file/v1/chunkUpload";

    // 初始化当前附件处理器
    private void initCurrentHandler(String propertiesId) throws Exception {
        AttachmentServiceFactory attachmentHandlerFactory = AppUtil.getBean(AttachmentServiceFactory.class);
        attachmentService = attachmentHandlerFactory.getCurrentServices(AppFileUtil.getSaveType(propertiesId));
    }

    // 初始化当前附件处理器
    private void initCurrentHandlerByBizCode(String bizCode) throws Exception {
        AttachmentServiceFactory attachmentHandlerFactory = AppUtil.getBean(AttachmentServiceFactory.class);
        attachmentService = attachmentHandlerFactory.getCurrentServices(AppFileUtil.getFileSaveTypeByConfig(bizCode));
    }

    @Override
    public List<DefaultFile> getAllByExt(String[] allowFiles) {
        return baseMapper.getAllByExt(allowFiles);
    }

    @Override
    @Transactional
    public void delSysFileByIds(String[] ids) throws Exception {
        String propertiesId = "";
        if (BeanUtils.isNotEmpty(ids) && ids.length > 0) {
            DefaultFile file = this.get(ids[0]);
            if (BeanUtils.isNotEmpty(file)) {
                propertiesId = file.getProp6();
            }
        }
        initCurrentHandler(propertiesId);
        for (String id : ids) {
            DefaultFile def = this.get(id);
            attachmentService.remove(def, BeanUtils.isNotEmpty(def) ? def.getProp6() : "");
            baseMapper.deleteById(id);
        }
    }

    @Override
    public UploadResult UploadFileForConfig(DefaultFile file, List<MultipartFile> files, String bizCode,String bizId,String bizType, IUser currentUser) throws Exception {
        Iterator<MultipartFile> it = files.iterator();
        initCurrentHandlerByBizCode(bizCode);
        FileStorageConfigDTO fileStorageConfig = fileConfigManager.getFileConfigByCode(bizCode);
        String fileFormates = fileStorageConfig.getFileKinds();
        Double allowSize = fileStorageConfig.getAllowSize();
        int allowMultiple = fileStorageConfig.getAllowMultiple();
        UploadResult result = new UploadResult();
        if (allowMultiple == 0 && files.size() > 1) {
            result.setSuccess(false);
            result.setMessage("系统只能上传一个附件！");
            return result;
        }

        short isdel = 0;
        while (it.hasNext()) {
            Boolean isAdd = false;
            String oldFilePath = "";
            DefaultFile sysFile;
            if (StringUtil.isNotEmpty(file.getId())) {
                sysFile = this.get(file.getId());
                oldFilePath = sysFile.getFilePath();
            } else {
                // 新增的上传文件
                isAdd = true;
                sysFile = new DefaultFile();
                // 生成新的文件ID
                sysFile.setId(UniqueIdUtil.getSuid());
            }
            MultipartFile f = it.next();
            if (StringUtil.isNotEmpty(file.getFileName())) {
                if (!file.getFileName().equals(f.getName())) {
                    throw new RuntimeException("上传名称与文件名称不符");
                }
            }
            String oriFileName = f.getOriginalFilename();
            String extName = FileUtil.getFileExt(oriFileName);

            //文件格式要求
            if (StringUtil.isNotEmpty(fileFormates)) {
                //不符合文件格式要求的就标志为false
                List<String> typeList = Arrays.asList(fileFormates.split(","));
                for (int i = 0; i < typeList.size(); i++) {
                    if (StringUtil.isNotEmpty(typeList.get(i))) {
                        typeList.set(i, typeList.get(i).toLowerCase());
                    }
                }
                if (typeList.indexOf(extName) < 0 && typeList.indexOf("." + extName) < 0) {
                    result.setSuccess(false);
                    result.setMessage("系统不允许该类型文件的上传！:" + extName);
                    return result;
                }
            }

            if(allowSize!=null && allowSize>0d){
                long len = f.getSize();
                double fileSize = (double) len / 1048576;
                if(fileSize>allowSize){
                    result.setSuccess(false);
                    result.setMessage("上传文件【"+oriFileName+"】大小已超过系统限制："+fileSize+"MB！" );
                    return result;
                }
            }

            String fileName = sysFile.getId() + "." + extName;
            String filePath = "";

            String creatorAccount = DefaultFile.FILE_UPLOAD_UNKNOWN;
            // 当前用户的信息
            if (currentUser != null) {
                sysFile.setCreateBy(currentUser.getUserId());
                sysFile.setCreatorName(currentUser.getFullname());
                creatorAccount = currentUser.getAccount();
            } else {
                sysFile.setCreatorName(creatorAccount);
            }

            if ("pictureShow".equals(sysFile.getFileType())) {
                filePath = AppFileUtil.createFilePath(creatorAccount + File.separator + "pictureShow", fileName);
            } else {
                filePath = AppFileUtil.createFilePath(currentUser.getAccount(), fileName);
            }
            // 附件名称
            sysFile.setFileName(oriFileName.lastIndexOf('.') == -1 ? oriFileName : oriFileName.substring(0, oriFileName.lastIndexOf('.')));
            sysFile.setFilePath(filePath);
            sysFile.setBizCode(bizCode);
            sysFile.setBizId(bizId);
            sysFile.setBizType(bizType);
            sysFile.setIsZone(0);
            sysFile.setZoneTotal(1L);
            sysFile.setMd5Value(DigestUtils.md5DigestAsHex(f.getInputStream()));
            sysFile.setServerLocalName(fileName);
            String rootPath = SysPropertyUtil.getByAlias("file.upload");
            sysFile.setServerLocalPath(rootPath+filePath);
            // 上传时间
            sysFile.setCreateTime(LocalDateTime.now());
            // 扩展名
            sysFile.setExtensionName(extName);
            // 字节总数
            sysFile.setByteCount(f.getSize());
            // 说明

            sysFile.setIsDel(isdel);
            sysFile.setNote(FileUtil.getSize(f.getSize()));
            sysFile.setProp6(fileStorageConfig.getStorageId());

            sysFile.setStoreType(fileStorageConfig.getUploadType());
            // 总的字节数

            if (isAdd) {
                sysFile.setBytes(f.getBytes());
                attachmentService.upload(sysFile, f.getInputStream(), BeanUtils.isNotEmpty(sysFile) ? sysFile.getProp6() : "");
                if (!fileStorageConfig.getUploadType().equals(DefaultFile.SAVE_TYPE_DTABASE)) {
                    sysFile.setBytes(null);
                }
                super.create(sysFile);
            } else {
                attachmentService.upload(sysFile, f.getInputStream(), BeanUtils.isNotEmpty(sysFile) ? sysFile.getProp6() : "");
                this.update(sysFile);
                boolean tag = true;
                String newFilePath = sysFile.getFilePath();
                if (StringUtil.isNotEmpty(newFilePath) && StringUtil.isNotEmpty(oldFilePath)) {
                    if (newFilePath.trim().equals(oldFilePath.trim())) {
                        tag = false;
                    }
                }
                // 修改了文件的存放路径，需要删除之前路径下的文件
                if (tag) {
                    sysFile.setFilePath(oldFilePath);
                    attachmentService.remove(sysFile, BeanUtils.isNotEmpty(sysFile) ? sysFile.getProp6() : "");
                }
            }
            result.setSuccess(true);
            result.setFileId(sysFile.getId());
            result.setFileName(oriFileName);
            result.setSize(sysFile.getByteCount());
            result.setUsername(sysFile.getCreatorName());
        }
        return result;
    }

    @Override
    @Transactional
    public UploadResult uploadFile(DefaultFile file, List<MultipartFile> files, String fileFormates, IUser currentUser, String flowKey) throws Exception {
        Iterator<MultipartFile> it = files.iterator();
        initCurrentHandler(flowKey);
        UploadResult result = new UploadResult();
        boolean mark = true;
        short isdel = 0;
        while (it.hasNext()) {
            Boolean isAdd = false;
            String oldFilePath = "";
            DefaultFile sysFile;
            if (StringUtil.isNotEmpty(file.getId())) {
                sysFile = this.get(file.getId());
                oldFilePath = sysFile.getFilePath();
            } else {
                // 新增的上传文件
                isAdd = true;
                sysFile = new DefaultFile();
                // 生成新的文件ID
                sysFile.setId(UniqueIdUtil.getSuid());
            }
            MultipartFile f = it.next();
            if (StringUtil.isNotEmpty(file.getFileName())) {
                if (!file.getFileName().equals(f.getName())) {
                    throw new RuntimeException("上传名称与文件名称不符");
                }
            }
            String oriFileName = f.getOriginalFilename();
            String extName = FileUtil.getFileExt(oriFileName);
            //文件格式要求
            if (StringUtil.isNotEmpty(fileFormates)) {
                //不符合文件格式要求的就标志为false
                List<String> typeList = Arrays.asList(fileFormates.split(","));
                for (int i = 0; i < typeList.size(); i++) {
                    if (StringUtil.isNotEmpty(typeList.get(i))) {
                        typeList.set(i, typeList.get(i).toLowerCase());
                    }
                }
                if (typeList.indexOf(extName) < 0 && typeList.indexOf("." + extName) < 0) {
                    mark = false;
                }
            }
            if (mark) {
                String fileName = sysFile.getId() + "." + extName;
                String filePath = "";

                String creatorAccount = DefaultFile.FILE_UPLOAD_UNKNOWN;
                // 当前用户的信息
                if (currentUser != null) {
                    sysFile.setCreateBy(currentUser.getUserId());
                    sysFile.setCreatorName(currentUser.getFullname());
                    creatorAccount = currentUser.getAccount();
                } else {
                    sysFile.setCreatorName(creatorAccount);
                }
                if ("pictureShow".equals(sysFile.getFileType())) {
                    filePath = AppFileUtil.createFilePath(creatorAccount + File.separator + "pictureShow", fileName);
                } else {
                    filePath = AppFileUtil.createFilePath(currentUser.getAccount(), fileName);
                }
                // 附件名称
                sysFile.setFileName(oriFileName.lastIndexOf('.') == -1 ? oriFileName : oriFileName.substring(0, oriFileName.lastIndexOf('.')));
                sysFile.setFilePath(filePath);
                // 上传时间
                sysFile.setCreateTime(LocalDateTime.now());
                // 扩展名
                sysFile.setExtensionName(extName);
                // 字节总数
                sysFile.setByteCount(f.getSize());
                // 说明

                sysFile.setIsDel(isdel);
                sysFile.setNote(FileUtil.getSize(f.getSize()));

                //流程key
                if (StringUtil.isNotEmpty(flowKey)) {
                    FlowUploadPropertiesManager uploadPropertiesManager = AppUtil.getBean(FlowUploadPropertiesManager.class);
                    FlowUploadPropertiesStorageDTO uploadProperties = uploadPropertiesManager.getByFlowKey(flowKey);
                    if (BeanUtils.isNotEmpty(uploadProperties)) {
                        sysFile.setProp6(uploadProperties.getId());
                    }
                }

                String saveType = AppFileUtil.getSaveType(flowKey);
                sysFile.setStoreType(saveType);
                // 总的字节数

                if (isAdd) {
                    sysFile.setBytes(f.getBytes());
                    attachmentService.upload(sysFile, f.getInputStream(), BeanUtils.isNotEmpty(sysFile) ? sysFile.getProp6() : "");
                    if (!saveType.equals(DefaultFile.SAVE_TYPE_DTABASE)) {
                        sysFile.setBytes(null);
                    }
                    super.create(sysFile);
                } else {
                    attachmentService.upload(sysFile, f.getInputStream(), BeanUtils.isNotEmpty(sysFile) ? sysFile.getProp6() : "");
                    this.update(sysFile);
                    boolean tag = true;
                    String newFilePath = sysFile.getFilePath();
                    if (StringUtil.isNotEmpty(newFilePath) && StringUtil.isNotEmpty(oldFilePath)) {
                        if (newFilePath.trim().equals(oldFilePath.trim())) {
                            tag = false;
                        }
                    }
                    // 修改了文件的存放路径，需要删除之前路径下的文件
                    if (tag) {
                        sysFile.setFilePath(oldFilePath);
                        attachmentService.remove(sysFile, BeanUtils.isNotEmpty(sysFile) ? sysFile.getProp6() : "");
                    }
                }
                result.setSuccess(true);
                result.setFileId(sysFile.getId());
                result.setFileName(oriFileName);
                result.setSize(sysFile.getByteCount());
                result.setUsername(sysFile.getCreatorName());

            } else {
                result.setSuccess(false);
                result.setMessage("系统不允许该类型文件的上传！:" + extName);
            }
        }
        return result;
    }

    @Override
    public DefaultFile downloadFile(String fileId, OutputStream outStream) throws Exception {
        DefaultFile sysFile = this.get(fileId);
        initCurrentHandler(BeanUtils.isNotEmpty(sysFile) ? sysFile.getProp6() : "");
        if (BeanUtils.isEmpty(sysFile)) {
            return null;
        }//特殊场景，附件上传方式是ftp，word套打生成的文件是磁盘存储，下载套打文件时特殊处理
        else if (attachmentService instanceof FtpAttachmentServiceImpl && DefaultFile.SAVE_TYPE_FOLDER.equals(sysFile.getStoreType())) {
            AttachmentServiceFactory attachmentHandlerFactory = AppUtil.getBean(AttachmentServiceFactory.class);
            attachmentService = attachmentHandlerFactory.getCurrentServices(DefaultFile.SAVE_TYPE_FOLDER);
        }
        attachmentService.download(sysFile, outStream, BeanUtils.isNotEmpty(sysFile) ? sysFile.getProp6() : "");
        return sysFile;
    }

    @Override
    @Transactional
    public void uploadFile(DefaultFile file, InputStream inputStream) throws Exception {
        initCurrentHandler(BeanUtils.isNotEmpty(file) ? file.getProp6() : "");
        String storeType = file.getStoreType();
        if (StringUtil.isEmpty(file.getId())) {
            file.setId(UniqueIdUtil.getSuid());
        }
        attachmentService.upload(file, inputStream, BeanUtils.isNotEmpty(file) ? file.getProp6() : "");
        if (StringUtil.isEmpty(storeType)) {
            storeType = AppFileUtil.getSaveType(BeanUtils.isNotEmpty(file) ? file.getProp6() : "");
            file.setStoreType(storeType);
        }
        if (!DefaultFile.SAVE_TYPE_DTABASE.equals(storeType)) {
            file.setBytes(null);
        }
        super.create(file);
    }

    @Override
    @Transactional
    public void setXbTypeId(List<String> fileId, String xbTypeId, String type) throws Exception {
        baseMapper.setXbTypeId(fileId, xbTypeId, type);
    }

    @Override
    @Transactional
    public void updateFileExtraProp(List<DefaultFile> files) {
        if (BeanUtils.isNotEmpty(files)) {
            for (DefaultFile defaultFile : files) {
                baseMapper.updateExtraProps(defaultFile);
            }
        }
    }

    @Override
    public byte[] getFileBytesById(String fileId) throws Exception {
        DefaultFile sysFile = this.get(fileId);
        initCurrentHandler(BeanUtils.isNotEmpty(sysFile) ? sysFile.getProp6() : "");
        if (BeanUtils.isEmpty(sysFile)) {
            return null;
        }
        return attachmentService.getFileBytes(sysFile);
    }

    @Override
    public DefaultFile get(Serializable id) {
        DefaultFile file = null;
        try (MultiTenantIgnoreResult setThreadLocalIgnore = MultiTenantHandler.setThreadLocalIgnore()) {
            file = super.get(id);
        } catch (Exception e) {
            throw new BaseException(e.getMessage(), e);
        }
        return file;
    }

    @Override
    public void downloadFileToPath(DefaultFile file, String path) throws Exception {
        initCurrentHandler(BeanUtils.isNotEmpty(file) ? file.getProp6() : "");
        File dfile = new File(path);
        if (!dfile.exists()) {
            if (BeanUtils.isNotEmpty(dfile.getParentFile()) && !dfile.getParentFile().exists()) {
                dfile.getParentFile().mkdir();
            }
            OutputStream out = new FileOutputStream(dfile);
            attachmentService.download(file, out, BeanUtils.isNotEmpty(file) ? file.getProp6() : "");
            out.flush();
            out.close();
        }
    }

    @Override
    public CommonResult<String> importSignature(MultipartFile file,boolean isCover) {
        String rootRealPath = "";
        CommonResult<String> message = new CommonResult<String>(true, "导入成功！");
        try {
            String unZipDir = TimeUtil.getCurrentTimeMillis() + "_" + baseContext.getCurrentUserId();
            rootRealPath = (FileUtil.getIoTmpdir() + "attachFiles/unZip/" + unZipDir).replace("/", File.separator);
            //建立临时文件夹，存放文件
            File folder = new File(rootRealPath);
            if (!folder.exists()) {
                folder.mkdirs();
            }
            ZipUtil.unZipFile(file, rootRealPath);
            File ignatureFloder = new File(rootRealPath);        //获取其file对象
            File[] fs = ignatureFloder.listFiles();    //遍历path下的文件和目录，放在File数组中
            IUser user = null;
            String account = baseContext.getCurrentUserAccout();
            if (StringUtil.isNotEmpty(account)) {
                user = userService.getUserByAccount(account);
            }
            ArrayNode accountFiles = JsonUtil.getMapper().createArrayNode();
            // 创建一个线程池
            ExecutorService pool = Executors.newFixedThreadPool(fs.length);
            FileManager fileManager = AppUtil.getBean(FileManager.class);
            // 创建多个有返回值的任务
            List<Future> list = new ArrayList<Future>();
            for (File signatureFile : fs) {
                if (!signatureFile.isDirectory() && signatureFile.getName().endsWith(".png")) {//如果是.png的图片文件则处理
                    Callable c = new MyCallable(signatureFile, user, fileManager);
                    // 执行任务并获取Future对象
                    Future f = pool.submit(c);
                    list.add(f);
                }
            }
            for (Future f : list) {
                UploadResult result = (UploadResult) f.get();
                if (result.isSuccess()) {
                    ObjectNode accountFile = JsonUtil.getMapper().createObjectNode();
                    String[] names = result.getFileName().split("\\.");
                    accountFile.put("account", names[0]);
                    accountFile.put("fileId", result.getFileId());
                    accountFiles.add(accountFile);
                }
            }
            if (accountFiles.size() > 0) {
                ObjectNode signatureData = JsonUtil.getMapper().createObjectNode();
                signatureData.put("repeatConver", isCover);
                signatureData.set("signatureList", accountFiles);
                CommonResult<String> result = ucFeignService.saveSignatureBatch(signatureData);
                //如果签章处理失败，则删除已上传的附件
                if (!result.getState()) {
                    String[] ids = new String[signatureData.size()];
                    for (int i = 0; i < accountFiles.size(); i++) {
                        ids[i] = accountFiles.get(i).get("fileId").asText();
                    }
                    this.delSysFileByIds(ids);
                    return new CommonResult<String>(false, "导入失败：" + result.getMessage());
                }
            }
        } catch (Exception e) {
            message = new CommonResult<String>(false, "导入失败:" + e.getMessage());
        } finally {
            File fileDir = new File(rootRealPath);
            if (fileDir.exists()) {
                FileUtil.deleteDir(fileDir); // 删除解压后的目录
            }
        }
        return message;
    }

    @Override
    public DefaultFile queryByMd5AndSize(MultipartInitParam initParam) {
        if (StringUtil.isEmpty(initParam.getMd5())) {
            throw new RequiredException("文件hash值不能为空！");
        }
        if (BeanUtils.isEmpty(initParam.getSize())) {
            throw new RequiredException("文件长度不能为空！");
        }
        QueryWrapper<DefaultFile> queryWrapper = new QueryWrapper<>();
        queryWrapper.eq("md5_value_", initParam.getMd5()).eq("byte_count_", initParam.getSize());
        List<DefaultFile> list = this.baseMapper.selectList(queryWrapper);
        if (list != null && list.size() > 0) {
            if (list.size() == 1) {
                return list.get(0);
            } else {
                throw new ApplicationException("有文件hash值重复的文件！");
            }
        } else {
            DefaultFile sysFile = getDefaultFile(initParam);
            this.save(sysFile);
            return sysFile;
        }
    }

    private DefaultFile getDefaultFile(MultipartInitParam initParam) {
        DefaultFile sysFile = new DefaultFile();
        sysFile.setId(UniqueIdUtil.getSuid());
        String oriFileName = initParam.getFilename();
        String extName = FileUtil.getFileExt(oriFileName);
        String fileName = sysFile.getId() + "." + extName;
        String suffixName = FileUtils.getSuffixFromFileName(oriFileName);
        String fileType = FileTypeEnum.getType(suffixName);
        sysFile.setFileType(fileType);
        String account = baseContext.getCurrentUserAccout();
        IUser currentUser = userService.getUserByAccount(account);
        String creatorAccount = DefaultFile.FILE_UPLOAD_UNKNOWN;
        // 当前用户的信息
        if (currentUser != null) {
            sysFile.setCreateBy(currentUser.getUserId());
            sysFile.setCreatorName(currentUser.getFullname());
        } else {
            sysFile.setCreatorName(creatorAccount);
        }
        // 附件名称
        sysFile.setFileName(oriFileName);
        sysFile.setFilePath(oriFileName.lastIndexOf('.') == -1 ? oriFileName : oriFileName.substring(0, oriFileName.lastIndexOf('.')));
        sysFile.setIsZone(1);
        sysFile.setZoneTotal(initParam.getZoneTotal());
        sysFile.setMd5Value(initParam.getMd5());
        sysFile.setServerLocalName(fileName);
        String rootPath = SysPropertyUtil.getByAlias("file.upload");
        // 上传时间
        sysFile.setCreateTime(LocalDateTime.now());
        // 扩展名
        sysFile.setExtensionName(extName);
        // 字节总数
        sysFile.setByteCount(initParam.getSize());
        sysFile.setNote(FileUtil.getSize(initParam.getSize()));

        sysFile.setStoreType(initParam.getStoreType());
        return sysFile;
    }

    @Override
    public DefaultFile getFileInfo(FileReqVo fileReqVo) {
        QueryWrapper<DefaultFile> queryWrapper = new QueryWrapper<>();
        queryWrapper.eq("FILE_TYPE_", fileReqVo.getFileType());
        queryWrapper.eq("BIZ_ID_", fileReqVo.getBizId());
        DefaultFile defaultFile = this.baseMapper.selectOne(queryWrapper);
        return defaultFile;
    }

    class MyCallable implements Callable<Object> {
        private Logger logger = LoggerFactory.getLogger(FileManagerImpl.class);
        private File file;
        private IUser user;
        private FileManager fileManager;

        MyCallable(File file, IUser user, FileManager fileManager) {
            this.file = file;
            this.user = user;
            this.fileManager = fileManager;
        }

        @Override
        public Object call() throws Exception {
            logger.info(">>>" + file.getName() + "签章附件上传");
            LocalDateTime dateTmp1 = LocalDateTime.now();
            UploadResult result = upload();
            Thread.sleep(200);
            LocalDateTime dateTmp2 = LocalDateTime.now();
            long time = TimeUtil.getTimeMillis(dateTmp2) - TimeUtil.getTimeMillis(dateTmp1);
            logger.info(">>>" + file.getName() + "签章附件上传完成; 花费时间为： " + time);
            return result;
        }

        private UploadResult upload() {
            try {
                FileManager fileManager = AppUtil.getBean(FileManager.class);
                MultipartFile multipartFile = FileUtil.fileToMultipartFile(file);
                List<MultipartFile> files = new ArrayList<MultipartFile>();
                files.add(multipartFile);
                DefaultFile file = new DefaultFile();
                return fileManager.uploadFile(file, files, null, user, "");
            } catch (Exception e) {
                logger.info(">>>" + file.getName() + "签章附件上传失败： " + e.getMessage());
            }
            return null;
        }

    }

    @Override
    public MultipartInitRespVo initMultiPartUpload(MultipartInitParam requestParam) {
        // 文件大小
        long size = requestParam.getSize();
        if (5 * 1024 * 1024 > requestParam.getPartSize()) {
            throw new BaseException("分片大小不能小于5M");
        }
        // 分片数量
        long partCount = size % requestParam.getPartSize() != 0 ? size / requestParam.getPartSize() + 1 : size / requestParam.getPartSize();
        requestParam.setZoneTotal(partCount);
        DefaultFile fileRecord = queryByMd5AndSize(requestParam);
        Long successChunk = 0L;
        if(!StringUtil.isEmpty(fileRecord.getProp3())) {
            successChunk = Long.valueOf(fileRecord.getProp3());
        }
        if (null != fileRecord && !StringUtil.isEmpty(successChunk) && !fileRecord.getZoneTotal().equals(successChunk)) {
            // redis中缓存默认是24小时失效，url链接24小时失效
            // 在24小时之内再次分片上传都可使用之前分片上传但没都上传成功的分片文件。
            // 考虑上传需要耗时，new Date() - createTime < 20小时才使用之前的结果，否则重新分片初始化
            Date now = new Date();
            // 转换为Instant，默认在系统默认时区
            Instant instant = fileRecord.getCreateTime().atZone(ZoneId.systemDefault()).toInstant();
            // 转换为java.util.Date
            Date date = Date.from(instant);
            long hour = DateUtil.between(date, now, DateUnit.HOUR);
            // TODO 20小时
            if (hour < 20) {
                List<String> partList = new ArrayList<>();
                for (int i = 1; i <= fileRecord.getZoneTotal(); i++) {
                    String chunk = "chunk_" + (i - 1);
                    partList.add(CHUNK_UPLOAD_PREFIX + "?uploadId=" + fileRecord.getServerLocalPath() + "&chunk=" + chunk);
                }
                MultipartInitRespVo multipartInitBO = new MultipartInitRespVo();
                multipartInitBO.setUploadId(fileRecord.getServerLocalPath());
                multipartInitBO.setPartCount(fileRecord.getZoneTotal());
                multipartInitBO.setUploadUrlList(partList);
                multipartInitBO.setSuccessChunk(successChunk);
                UploadResult result = getUploadResult(fileRecord);
                multipartInitBO.setUploadResult(result);
                return multipartInitBO;
            }
        }

        MultipartInitRespVo multipartInitBO = multipartUpload(requestParam, fileRecord);
        return multipartInitBO;
    }

    private MultipartInitRespVo multipartUpload(MultipartInitParam requestParam, DefaultFile fileRecord) {
        try {
            MultipartInitRespVo multipartInitBO = new MultipartInitRespVo();
            Long successChunk = 0L;
            if(!StringUtil.isEmpty(fileRecord.getProp3())) {
                successChunk = Long.valueOf(fileRecord.getProp3());
            }
            //合并成功的文件 直接将文件信息返回给前端
            if (!StringUtil.isEmpty(successChunk) && fileRecord.getZoneTotal().equals(successChunk)) {
                UploadResult result = getUploadResult(fileRecord);
                multipartInitBO.setUploadResult(result);
            } else {
                AttachmentServiceFactory attachmentHandlerFactory = AppUtil.getBean(AttachmentServiceFactory.class);
                attachmentService = attachmentHandlerFactory.getCurrentServices(requestParam.getStoreType());
                String extName = FileUtil.getFileExt(requestParam.getFilename());
                String fileNewName = fileRecord.getId() + "." + extName;
                fileRecord.setFileName(fileNewName);
                fileRecord.setContentType(requestParam.getContentType());
                String uploadId = attachmentService.initMultiPartUpload(fileRecord);
                fileRecord.setServerLocalPath(uploadId);
                this.baseMapper.updateById(fileRecord);
                // 存缓存 uploadId : filePath
                String redisKey = CodePrefix.MULTIPART_UPLOADID_KEY.getKey() + ":" + uploadId;
                // TODO 设置分片上传 uploadId缓存1天
                Long time = 10 * 24 * 60 * 60L;
                redisUtil.set(redisKey, fileRecord.getId(), time);

                Map<String, String> chunkMap = new HashMap<>();
                // 给前端
                List<String> partList = new ArrayList<>();
                for (int i = 1; i <= requestParam.getZoneTotal(); i++) {
                    String uploadUrl = attachmentService.getChunkUrl(fileRecord, i, uploadId);
                    String chunk = "chunk_" + (i - 1);
                    chunkMap.put(chunk, uploadUrl);
                    partList.add(CHUNK_UPLOAD_PREFIX + "?uploadId=" + uploadId + "&chunk=" + chunk);
                    String chunkKey = CodePrefix.MULTIPART_UPLOADID_CHUNK_KEY.getKey() + ":" + uploadId + ":" + chunk;
                    redisUtil.set(chunkKey, uploadUrl, time);
                }
                multipartInitBO.setPartCount(requestParam.getZoneTotal());
                multipartInitBO.setUploadId(uploadId);
                multipartInitBO.setUploadUrlList(partList);
                multipartInitBO.setSuccessChunk(successChunk);
                UploadResult result = getUploadResult(fileRecord);
                multipartInitBO.setUploadResult(result);
            }
            return multipartInitBO;
        } catch (Exception e) {
            log.error("分片初始化失败，{}", e.getMessage());
            return null;
        }
    }

    private UploadResult getUploadResult(DefaultFile fileRecord) {

        UploadResult result = new UploadResult();
        Long successChunk = 0L;
        if(!StringUtil.isEmpty(fileRecord.getProp3())) {
            successChunk = Long.valueOf(fileRecord.getProp3());
        }
        if(!StringUtil.isEmpty(successChunk) && successChunk.equals(fileRecord.getZoneTotal())) {
            result.setMergeStatus("1");
        }else{
            result.setMergeStatus("0");
        }
        result.setSuccess(true);
        result.setFileId(fileRecord.getId());
        result.setFileName(fileRecord.getFileName());
        result.setSize(fileRecord.getByteCount());
        result.setUsername(fileRecord.getCreatorName());
        String suffixName = FileUtils.getSuffixFromFileName(fileRecord.getFileName());
        String fileType = fileRecord.getFileType();
        result.setFileExt(suffixName);

        result.setFileType(fileType);
        return result;
    }

    /**
     * 上传当前分片中的文件 并记录成功分片信息
     * @param file
     * @param uploadId
     * @param chunk
     * @return
     */
    @Override
    @Transactional(rollbackFor = Exception.class)
    public boolean chunkUpload(MultipartFile file, String uploadId, String chunk) {
        String chunkKey = CodePrefix.MULTIPART_UPLOADID_CHUNK_KEY.getKey() + ":" + uploadId + ":" + chunk;
        String uploadUrl = redisUtil.get(chunkKey, String.class);
        if (!StringUtil.isEmpty(uploadId) && "1".equals(uploadUrl)) {
            // 断点续传
            return true;
        }

        String redisKey = CodePrefix.MULTIPART_UPLOADID_KEY.getKey() + ":" + uploadId;
        String fileId = redisUtil.get(redisKey, String.class);
        Assert.hasText(fileId, "分片文件不存在");
        DefaultFile defaultFile = this.get(fileId);
        Assert.notNull(defaultFile, "分片文件不存在");
        String msg = putMethod(uploadUrl, file);
        //分片上传成功后 在缓存中进行记录 并记录成功的片数总和
        if (null == msg) {
            Long expire = redisUtil.getExpire(chunkKey);
            // chunk已经上传成功
            redisUtil.set(chunkKey, "1", expire.intValue());
            String chunkNumKey = CodePrefix.MULTIPART_UPLOADID_CHUNKNUM_KEY.getKey() + ":" + uploadId;
            long incr = redisUtil.incr(chunkNumKey, 1);
            defaultFile.setProp3(String.valueOf(incr));
        }
        boolean update = this.updateById(defaultFile);

        return update;
    }

    /**
     * 当所有分片上传成功后 进行分片合并
     * @param param
     * @return
     */
    @Override
    public UploadResult mergeMultipartUpload(MultipartCompleteParam param) {
        String redisKey = CodePrefix.MULTIPART_UPLOADID_KEY.getKey() + ":" + param.getUploadId();
        String fileId = redisUtil.get(redisKey, String.class);
        Assert.hasText(fileId, "分片文件不存在");
        DefaultFile defaultFile = this.get(fileId);
        Assert.notNull(defaultFile, "分片文件不存在");
        Long successChunk = 0L;
        if(!StringUtil.isEmpty(defaultFile.getProp3())) {
            successChunk = Long.valueOf(defaultFile.getProp3());
        }
        //如果所有分片上传完成则进行分片合并并修改分片文件合并状态
        String mergeStatus = "0";
        if (!StringUtil.isEmpty(successChunk) && successChunk.equals(defaultFile.getZoneTotal())) {
            mergeMultipartUpload(param.getUploadId(), defaultFile);
            mergeStatus = "1";
        }

        UploadResult result = null;
        boolean update = this.updateById(defaultFile);
        if (update) {
            result = new UploadResult();
            result.setSuccess(true);
            result.setFileId(defaultFile.getId());
            result.setFileName(defaultFile.getFileName());
            result.setSize(defaultFile.getByteCount());
            result.setUsername(defaultFile.getCreatorName());
            String suffixName = FileUtils.getSuffixFromFileName(defaultFile.getFileName());
            String fileType = FileTypeEnum.getType(suffixName);
            result.setFileExt(suffixName);
            result.setMergeStatus(mergeStatus);
            result.setFileType(fileType);
            //如果是视频文件则异步进行转M3U8，并记录M3U8文件上传地址
            if (FileTypeEnum.video.getType().equals(fileType)) {
                executorService.execute(() -> {
                    UploadM3u8 uploadM3u8 = new UploadM3u8();
                    String url = uploadM3u8.uploadVideoToM3U8(defaultFile);
                    defaultFile.setProp4(url);
                    this.updateById(defaultFile);
                    log.info("视屏转换成功，url:{}", url);
                });
            }
            //如果是文档且不是pdf文件 则异步转换为pdf文档
            if (FileTypeEnum.txt.getType().equals(fileType) && !".pdf".equals(suffixName)) {
                executorService.execute(() -> {
                    Map<String, String> map = new HashMap<String, String>();
                    //文档转pdf
                    FilePreview filePreview = previewFactory.get(defaultFile);
                    filePreview.filePreviewHandle(defaultFile, map);
                });

            }
        }

        return result;
    }

    public void mergeMultipartUpload(String uploadId, DefaultFile defaultFile) {
        boolean status = false;
        try {
            String redisKey = CodePrefix.MULTIPART_UPLOADID_KEY.getKey() + ":" + uploadId;
            String fileId = redisUtil.get(redisKey, String.class);
            if (StringUtil.isEmpty(fileId)) {
                throw new BaseException("分片上传失败！");
            }

            String chunkListKeyPattern = CodePrefix.MULTIPART_UPLOADID_CHUNK_KEY.getKey() + ":" + uploadId + ":chunk_*";
            Object data = redisUtil.getAllCache(chunkListKeyPattern);
            if (null == data) {
                // 缓存过期,不能合并
                log.info(">>>>>>>>>>>{}缓存过期，不能合并", uploadId);
                throw new BaseException("分片上传失败！");
            }
            Long successChunk = 0L;
            if(!StringUtil.isEmpty(defaultFile.getProp3())) {
                successChunk = Long.valueOf(defaultFile.getProp3());
            }
            int realyPartNumber = successChunk.intValue();

            AttachmentServiceFactory attachmentHandlerFactory = AppUtil.getBean(AttachmentServiceFactory.class);
            attachmentService = attachmentHandlerFactory.getCurrentServices(defaultFile.getStoreType());
            // 合并成功，isUPloaded设为1，删除redis缓存
            status = attachmentService.mergeMultipartUpload(defaultFile, uploadId, realyPartNumber);
            if (status) {
                redisUtil.del(redisKey);
                redisUtil.delLike(chunkListKeyPattern);
                String chunkNumKey = CodePrefix.MULTIPART_UPLOADID_CHUNKNUM_KEY.getKey() + ":" + uploadId;
                redisUtil.del(chunkNumKey);
            }
        } catch (Exception e) {
            log.error("分片合并失败，{}", e.fillInStackTrace().toString());
        } finally {
            log.info(">>>>>>>>>>>>合并chunk,uploadId:{}, 合并结果：{}", uploadId, status);
        }
    }

    /**
     * 向minio上传分片文件
     *
     * @param filePath
     * @param file
     * @return
     */
    private String putMethod(String filePath, MultipartFile file) {
        InputStream inputStream = null;
        try {
            inputStream = file.getInputStream();
            if(BeanUtils.isNotEmpty(file.getInputStream())) {
                FileUtil.createFolderFile(filePath);
                FileUtil.writeFile(filePath, inputStream);
            }
            return null;
        } catch (IOException e) {
            e.printStackTrace();
        }
        return "上传失败";
    }

}
