package com.yunzhupaas.controller;

import cn.xuyanwu.spring.file.storage.FileInfo;
import com.alibaba.fastjson.JSONObject;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.Parameter;
import io.swagger.v3.oas.annotations.Parameters;
import io.swagger.v3.oas.annotations.tags.Tag;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import com.yunzhupaas.base.ActionResult;
import com.yunzhupaas.base.UserInfo;
import com.yunzhupaas.base.entity.DictionaryDataEntity;
import com.yunzhupaas.base.service.DictionaryDataService;
import com.yunzhupaas.base.util.OptimizeUtil;
import com.yunzhupaas.base.vo.DownloadVO;
import com.yunzhupaas.base.vo.ListVO;
import com.yunzhupaas.config.ConfigValueUtil;
import com.yunzhupaas.constant.FileTypeConstant;
import com.yunzhupaas.constant.MsgCode;
import com.yunzhupaas.consts.DeviceType;
import com.yunzhupaas.entity.FileDetail;
import com.yunzhupaas.exception.DataException;
import com.yunzhupaas.model.*;
import com.yunzhupaas.util.*;
import com.yunzhupaas.utils.YozoUtils;
import lombok.Cleanup;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.codec.binary.Base64;
import org.apache.commons.io.FileUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.MediaType;
import org.springframework.web.bind.annotation.*;
import org.springframework.web.multipart.MultipartFile;

import java.io.*;
import java.nio.channels.Channels;
import java.nio.channels.FileChannel;
import java.nio.channels.WritableByteChannel;
import java.nio.file.Files;
import java.nio.file.Paths;
import java.time.LocalDate;
import java.time.format.DateTimeFormatter;
import java.util.*;
import java.util.stream.Collectors;

/**
 * 通用控制器
 *
 * @author 云筑产品开发平台组
 * @version v5.2.7
 * @copyright 深圳市乐程软件有限公司
 * @date 2024-09-26 上午9:18
 */
@Slf4j
@Tag(name = "公共", description = "file")
@RestController
@RequestMapping("/api/file")
public class UtilsController {

    @Autowired
    private ConfigValueUtil configValueUtil;
    @Autowired
    private RedisUtil redisUtil;
    @Autowired
    private DictionaryDataService dictionaryDataService;
    @Autowired
    private YozoUtils yozoUtils;

    /**
     * 语言列表
     *
     * @return
     */
    @Operation(summary = "语言列表")
    @GetMapping("/Language")
    public ActionResult<ListVO<LanguageVO>> getList() {
        String dictionaryTypeId = "dc6b2542d94b407cac61ec1d59592901";
        List<DictionaryDataEntity> list = dictionaryDataService.getList(dictionaryTypeId);
        List<LanguageVO> language = JsonUtil.getJsonToList(list, LanguageVO.class);
        ListVO vo = new ListVO();
        vo.setList(language);
        return ActionResult.success(vo);
    }

    /**
     * 图形验证码
     *
     * @return
     */
    @NoDataSourceBind()
    @Operation(summary = "图形验证码")
    @GetMapping("/ImageCode/{timestamp}")
    @Parameters({
            @Parameter(name = "timestamp", description = "时间戳", required = true),
    })
    public void imageCode(@PathVariable("timestamp") String timestamp) {
        DownUtil.downCode(null);
        redisUtil.insert(timestamp, ServletUtil.getSession().getAttribute(CodeUtil.RANDOMCODEKEY), 120);
    }

    /**
     * 获取全部下载文件链接（打包下载）
     *
     * @return
     */
    @NoDataSourceBind
    @Operation(summary = "获取全部下载文件链接（打包下载）")
    @PostMapping("/PackDownload/{type}")
    public ActionResult packDownloadUrl(@PathVariable("type") String type,
            @RequestBody List<Map<String, String>> fileInfoList) throws Exception {
        type = XSSEscape.escape(type);
        if (fileInfoList == null || fileInfoList.isEmpty()) {
            return ActionResult.fail(MsgCode.FA047.get());
        }
        List<String> filePathList = new ArrayList<String>();

        String zipTempFilePath = null;
        String zipFileId = RandomUtil.uuId() + ".zip";
        List<String> repeatName = new ArrayList<>();
        for (Map fileInfoMap : fileInfoList) {
            String fileId = XSSEscape.escape((String) fileInfoMap.get("fileId")).trim();
            String fileName = XSSEscape.escape((String) fileInfoMap.get("fileName")).trim();
            if (repeatName.contains(fileName)) {
                fileName = fileName.substring(0, fileName.lastIndexOf(".")) + "副本"
                        + UUID.randomUUID().toString().substring(0, 5) + fileName.substring(fileName.lastIndexOf("."));
            } else {
                repeatName.add(fileName);
            }
            if (StringUtil.isEmpty(fileId) || StringUtil.isEmpty(fileName)) {
                continue;
            }
            if (FileUploadUtils.exists(type, fileId)) {
                String typePath = FilePathUtil.getFilePath(type);
                if (fileId.indexOf(",") >= 0) {
                    typePath += fileId.substring(0, fileId.lastIndexOf(",") + 1).replaceAll(",", "/");
                    fileId = fileId.substring(fileId.lastIndexOf(",") + 1);
                }
                byte[] bytes = FileUploadUtils.downloadFileByte(typePath, fileId, false);
                if (zipTempFilePath == null) {
                    zipTempFilePath = FileUploadUtils.getLocalBasePath()
                            + FilePathUtil.getFilePath(FileTypeConstant.FILEZIPDOWNTEMPPATH);
                    if (!new File(zipTempFilePath).exists()) {
                        new File(zipTempFilePath).mkdirs();
                    }
                    zipTempFilePath += zipFileId;
                }
                ZipUtil.fileAddToZip(zipTempFilePath, new ByteArrayInputStream(bytes), fileName);
            }
        }
        if (zipTempFilePath == null) {
            return ActionResult.fail(MsgCode.FA018.get());
        }
        // 将文件上传到默认文件服务器
        String newFileId = zipFileId;
        if (!"local".equals(FileUploadUtils.getDefaultPlatform())) { // 不是本地，说明是其他文件服务器，将zip文件上传到其他服务器里，方便下载
            FileInfo fileInfo = FileUploadUtils.uploadFile(new File(zipTempFilePath),
                    FilePathUtil.getFilePath(FileTypeConstant.FILEZIPDOWNTEMPPATH), zipFileId);
            new File(zipTempFilePath).delete();
            newFileId = fileInfo.getFilename();
        }
        com.yunzhupaas.base.vo.DownloadVO vo = DownloadVO.builder().name(zipFileId)
                .url(UploaderUtil.uploaderFile(newFileId + "#" + FileTypeConstant.FILEZIPDOWNTEMPPATH)).build();
        Map<String, Object> map = new HashMap<String, Object>();
        map.put("downloadVo", vo);
        map.put("downloadName", "文件" + zipFileId);
        return ActionResult.success(map);
    }

    /**
     * 上传文件/图片
     *
     * @return
     */
    @NoDataSourceBind()
    @Operation(summary = "上传文件/图片")
    @PostMapping(value = "/Uploader/{type}", consumes = MediaType.MULTIPART_FORM_DATA_VALUE)
    @Parameters({
            @Parameter(name = "type", description = "类型", required = true)
    })
    public ActionResult<UploaderVO> uploader(@PathVariable("type") String type, MultipartFile file,
            HttpServletRequest httpServletRequest) throws IOException {
        String fileType = UpUtil.getFileType(file);
        // 验证类型
        if (!OptimizeUtil.fileType(configValueUtil.getAllowUploadFileType(), fileType)) {
            return ActionResult.fail(MsgCode.FA017.get());
        }
        PathTypeModel pathTypeModel = new PathTypeModel();
        pathTypeModel.setPathType(httpServletRequest.getParameter("pathType"));
        pathTypeModel.setTimeFormat(httpServletRequest.getParameter("timeFormat"));
        pathTypeModel.setSortRule(httpServletRequest.getParameter("sortRule"));
        pathTypeModel.setFolder(httpServletRequest.getParameter("folder"));
        if ("selfPath".equals(pathTypeModel.getPathType())) {
            if (StringUtil.isNotEmpty(pathTypeModel.getFolder())) {
                String folder = pathTypeModel.getFolder();
                folder = folder.replaceAll("\\\\", "/");
                // String regex = "^[a-z0-9A-Z\\u4e00-\\u9fa5\\\\\\/]+$";
                String regex = "^[a-zA-Z0-9][a-zA-Z0-9_\\-\\\\\\/]{0,99}$";
                if (!folder.matches(regex)) {
                    return ActionResult.fail(MsgCode.FA038.get());
                }
            }
        }
        UploaderVO vo = uploaderVO(file, type, pathTypeModel);
        return ActionResult.success(vo);
    }

    /**
     * 图片转成base64
     *
     * @return
     */
    @NoDataSourceBind()
    @Operation(summary = "图片转成base64")
    @PostMapping(value = "/Uploader/imgToBase64", consumes = MediaType.MULTIPART_FORM_DATA_VALUE)
    public ActionResult imgToBase64(@RequestParam("file") MultipartFile file) throws IOException {
        String encode = cn.hutool.core.codec.Base64.encode(file.getBytes());
        Object base64Name = "";
        if (StringUtil.isNotEmpty(encode)) {
            base64Name = "data:image/jpeg;base64," + encode;
        }
        return ActionResult.success(base64Name);
    }

    /**
     * 获取下载文件链接
     *
     * @return
     */
    @NoDataSourceBind()
    @Operation(summary = "获取下载文件链接")
    @GetMapping("/Download/{type}/{fileName}")
    @Parameters({
            @Parameter(name = "type", description = "类型", required = true),
            @Parameter(name = "fileName", description = "文件名称", required = true),
    })
    public ActionResult downloadUrl(@PathVariable("type") String type, @PathVariable("fileName") String fileName) {
        type = XSSEscape.escape(type);
        fileName = XSSEscape.escape(fileName);
        boolean exists = FileUploadUtils.exists(type, fileName);
        if (exists) {
            DownloadVO vo = DownloadVO.builder().name(fileName).url(UploaderUtil.uploaderFile(fileName + "#" + type))
                    .build();
            return ActionResult.success(vo);
        }
        return ActionResult.fail(MsgCode.FA018.get());
    }

    /**
     * 下载文件链接
     *
     * @return
     */
    @NoDataSourceBind()
    @Operation(summary = "下载文件链接")
    @GetMapping("/Download")
    public void downloadFile() throws DataException {
        HttpServletRequest request = ServletUtil.getRequest();
        String reqJson = request.getParameter("encryption");
        String name = request.getParameter("name");
        String fileNameAll = DesUtil.aesDecode(reqJson);
        if (!StringUtil.isEmpty(fileNameAll)) {
            fileNameAll = fileNameAll.replaceAll("\n", "");
            String[] data = fileNameAll.split("#");
            String cacheKEY = data.length > 0 ? data[0] : "";
            String fileName = XSSEscape.escapePath(data.length > 1 ? data[1] : "");
            String type = data.length > 2 ? data[2] : "";
            Object ticketObj = TicketUtil.parseTicket(cacheKEY);
            // 验证缓存
            if (ticketObj != null) {
                // 某些手机浏览器下载后会有提示窗口, 会访问两次下载地址
                if (UserProvider.getDeviceForAgent().equals(DeviceType.APP) && "".equals(ticketObj)) {
                    TicketUtil.updateTicket(cacheKEY, "1", 30L);
                } else {
                    TicketUtil.deleteTicket(cacheKEY);
                }
                // 下载文件
                String typePath = FilePathUtil.getFilePath(type.toLowerCase());
                if (fileName.indexOf(",") >= 0) {
                    typePath += fileName.substring(0, fileName.lastIndexOf(",") + 1).replaceAll(",", "/");
                    fileName = fileName.substring(fileName.lastIndexOf(",") + 1);
                }
                // String filePath = FilePathUtil.getFilePath(type.toLowerCase());
                byte[] bytes = FileUploadUtils.downloadFileByte(typePath, fileName, false);
                FileDownloadUtil.downloadFile(bytes, fileName, name);
                if (FileTypeConstant.FILEZIPDOWNTEMPPATH.equals(type)) { // 删除打包的临时文件，释放存储
                    FileUploadUtils.deleteFileByPathAndFileName(typePath, fileName);
                }
            } else {
                if (FileTypeConstant.FILEZIPDOWNTEMPPATH.equals(type)) { // 删除打包的临时文件，释放存储
                    String typePath = FilePathUtil.getFilePath(type);
                    FileUploadUtils.deleteFileByPathAndFileName(typePath, fileName);
                }
                throw new DataException(MsgCode.FA039.get());
            }
        } else {
            throw new DataException(MsgCode.FA039.get());
        }
    }

    /**
     * 下载文件链接
     *
     * @return
     */
    @NoDataSourceBind()
    @Operation(summary = "下载模板文件链接")
    @GetMapping("/DownloadModel")
    public void downloadModel() throws DataException {
        HttpServletRequest request = ServletUtil.getRequest();
        String reqJson = request.getParameter("encryption");
        String fileNameAll = DesUtil.aesDecode(reqJson);
        if (!StringUtil.isEmpty(fileNameAll)) {
            String token = fileNameAll.split("#")[0];
            if (TicketUtil.parseTicket(token) != null) {
                TicketUtil.deleteTicket(token);
                String fileName = fileNameAll.split("#")[1];
                String filePath = configValueUtil.getTemplateFilePath();
                // 下载文件
                byte[] bytes = FileUploadUtils.downloadFileByte(filePath, fileName, false);
                FileDownloadUtil.downloadFile(bytes, fileName, null);
            } else {
                throw new DataException(MsgCode.FA039.get());
            }
        } else {
            throw new DataException(MsgCode.FA039.get());
        }
    }

    /**
     * 获取图片
     *
     * @param fileName
     * @param type
     * @return
     */
    @NoDataSourceBind()
    @Operation(summary = "获取图片")
    @GetMapping("/Image/{type}/{fileName}")
    @Parameters({
            @Parameter(name = "type", description = "类型", required = true),
            @Parameter(name = "fileName", description = "名称", required = true),
    })
    public void downLoadImg(@PathVariable("type") String type, @PathVariable("fileName") String fileName) {
        String filePath = FilePathUtil.getFilePath(type.toLowerCase());
        if (fileName.indexOf(",") >= 0) {
            filePath += fileName.substring(0, fileName.lastIndexOf(",") + 1).replaceAll(",", "/");
            fileName = fileName.substring(fileName.lastIndexOf(",") + 1);
        }
        // if ("im".equalsIgnoreCase(type)) {
        // type = "imfile";
        // }
        // else if (FileTypeEnum.ANNEXPIC.equalsIgnoreCase(type)) {
        // type = FileTypeEnum.ANNEX;
        // }
        // 下载文件
        byte[] bytes = FileUploadUtils.downloadFileByte(filePath, fileName, false);
        FileDownloadUtil.flushImage(bytes, fileName);
    }

    /**
     * 获取IM聊天图片
     * 注意 后缀名前端故意把 .替换@
     *
     * @param fileName
     * @return
     */
    @NoDataSourceBind()
    @Operation(summary = "获取IM聊天图片")
    @GetMapping("/IMImage/{fileName}")
    @Parameters({
            @Parameter(name = "fileName", description = "名称", required = true),
    })
    public void imImage(@PathVariable("fileName") String fileName) {
        byte[] bytes = FileUploadUtils.downloadFileByte(configValueUtil.getImContentFilePath(), fileName, false);
        FileDownloadUtil.flushImage(bytes, fileName);
    }

    /**
     * 查看图片
     *
     * @param type     哪个文件夹
     * @param fileName 文件名称
     * @return
     */
    @NoDataSourceBind()
    @Operation(summary = "查看图片")
    @GetMapping("/{type}/{fileName}")
    @Parameters({
            @Parameter(name = "fileName", description = "名称", required = true),
            @Parameter(name = "type", description = "类型", required = true),
    })
    public void img(@PathVariable("type") String type, @PathVariable("fileName") String fileName) {
        // String filePath = configValueUtil.getBiVisualPath() + type + File.separator;
        // if (StorageType.MINIO.equals(configValueUtil.getFileType())) {
        // fileName = "/" + type + "/" + fileName;
        // filePath = configValueUtil.getBiVisualPath().substring(0,
        // configValueUtil.getBiVisualPath().length() - 1);
        // }
        String filePath = FilePathUtil.getFilePath(type.toLowerCase());
        if (fileName.indexOf(",") >= 0) {
            filePath += fileName.substring(0, fileName.lastIndexOf(",") + 1).replaceAll(",", "/");
            fileName = fileName.substring(fileName.lastIndexOf(",") + 1);
        }
        // 下载文件
        byte[] bytes = FileUploadUtils.downloadFileByte(filePath, fileName, false);
        FileDownloadUtil.flushImage(bytes, fileName);
    }

    /**
     * 获取IM聊天语音
     * 注意 后缀名前端故意把 .替换@
     *
     * @param fileName
     * @return
     */
    @NoDataSourceBind()
    @Operation(summary = "获取IM聊天语音")
    @GetMapping("/IMVoice/{fileName}")
    @Parameters({
            @Parameter(name = "fileName", description = "名称", required = true),
    })
    public void imVoice(@PathVariable("fileName") String fileName) {
        fileName = fileName.replaceAll("@", ".");
        byte[] bytes = FileUploadUtils.downloadFileByte(configValueUtil.getImContentFilePath(), fileName, false);
        FileDownloadUtil.flushImage(bytes, fileName);
    }

    /**
     * app启动获取信息
     *
     * @param appName
     * @return
     */
    @NoDataSourceBind()
    @Operation(summary = "app启动获取信息")
    @GetMapping("/AppStartInfo/{appName}")
    @Parameters({
            @Parameter(name = "appName", description = "名称", required = true),
    })
    public ActionResult getAppStartInfo(@PathVariable("appName") String appName) {
        appName = XSSEscape.escape(appName);
        JSONObject object = new JSONObject();
        object.put("AppVersion", configValueUtil.getAppVersion());
        object.put("AppUpdateContent", configValueUtil.getAppUpdateContent());
        return ActionResult.success(object);
    }

    // ----------大屏图片下载---------
    @NoDataSourceBind()
    @Operation(summary = "获取图片")
    @GetMapping("/VisusalImg/{bivisualpath}/{type}/{fileName}")
    @Parameters({
            @Parameter(name = "type", description = "类型", required = true),
            @Parameter(name = "bivisualpath", description = "路径", required = true),
            @Parameter(name = "fileName", description = "名称", required = true),
    })
    public void downVisusalImg(@PathVariable("type") String type, @PathVariable("bivisualpath") String bivisualpath,
            @PathVariable("fileName") String fileName) {
        fileName = XSSEscape.escape(fileName);
        String filePath = configValueUtil.getBiVisualPath();
        byte[] bytes = FileUploadUtils.downloadFileByte(filePath + type + "/", fileName, false);
        FileDownloadUtil.flushImage(bytes, fileName);
    }

    // ----------------------

    @NoDataSourceBind()
    @Operation(summary = "预览文件")
    @GetMapping("/Uploader/Preview")
    public ActionResult Preview(PreviewParams previewParams) {
        // 读取允许文件预览类型
        String allowPreviewType = configValueUtil.getAllowPreviewFileType();
        String[] fileType = allowPreviewType.split(",");

        String fileName = XSSEscape.escape(previewParams.getFileName());

        // 文件预览类型检验
        String docType = fileName.substring(fileName.lastIndexOf(".") + 1);
        String s = Arrays.asList(fileType).stream().filter(type -> type.equals(docType)).findFirst().orElse(null);

        if (StringUtil.isEmpty(s)) {
            return ActionResult.fail(MsgCode.FA040.get());
        }

        // 解析文件url 获取类型
        String type = configValueUtil.getWebAnnexFilePath();

        String fileNameAll = previewParams.getFileDownloadUrl();
        if (!StringUtil.isEmpty(fileNameAll)) {
            String[] data = fileNameAll.split("/");
            type = data.length > 4 ? data[4] : "";
        }

        String url;
        // 文件预览策略
        if ("yozo".equals(configValueUtil.getPreviewType())) {
            if (StringUtil.isEmpty(previewParams.getFileVersionId())) {
                return ActionResult.fail(MsgCode.FA041.get());
            }

            String fileVersionId = XSSEscape.escape(previewParams.getFileVersionId());

            // 获取签名
            Map<String, String[]> parameter = new HashMap<String, String[]>();
            parameter.put("appId", new String[] { YozoParams.APP_ID });
            parameter.put("fileVersionId", new String[] { fileVersionId });
            String sign = yozoUtils.generateSign(YozoParams.APP_ID, YozoParams.APP_KEY, parameter).getData();
            url = "http://eic.yozocloud.cn/api/view/file?fileVersionId="
                    + fileVersionId
                    + "&appId="
                    + YozoParams.APP_ID
                    + "&sign="
                    + sign;
        } else {
            if (FileUploadUtils.getDefaultPlatform().startsWith("local-plus")) {
                url = YozoParams.YUNZHUPAAS_DOMAINS + "/api/file/filedownload/" + type + "/"
                        + previewParams.getFileName();
            } else {
                // 图像格式
                if ("jpg,gif,png,bmp,jpeg".contains(docType)) {
                    url = YozoParams.YUNZHUPAAS_DOMAINS + "/api/file/filedownload/" + type;
                } else {
                    String[] split = fileNameAll.split("/");
                    if (split.length > 5) {
                        type = FilePathUtil.getFilePath(type) + split[5].replaceAll(",", "/");
                    }
                    FileDetail fileDetail = FileUploadUtils.getFileDetail(type, fileName, false);
                    url = FileUploadUtils.getDomain() + FileUploadUtils.getBucketName() + fileDetail.getBasePath()
                            + fileDetail.getPath();
                }
            }
            // encode编码
            String fileUrl = Base64.encodeBase64String(url.getBytes());
            url = configValueUtil.getKkFileUrl() + "onlinePreview?url=" + fileUrl;
        }
        return ActionResult.success(MsgCode.SU000.get(), url);
    }

    @NoDataSourceBind()
    @Operation(summary = "kk本地文件预览")
    @GetMapping("/filedownload/{type}/{fileName}")
    public void filedownload(@PathVariable("type") String type, @PathVariable("fileName") String fileName,
            HttpServletResponse response) {
        String typePath = FilePathUtil.getFilePath(type);
        if (fileName.indexOf(",") >= 0) {
            typePath += fileName.substring(0, fileName.lastIndexOf(",") + 1).replaceAll(",", "/");
            fileName = fileName.substring(fileName.lastIndexOf(",") + 1);
        }
        String tmpPath = typePath + fileName;
        boolean b = FileUtil.fileIsFile(tmpPath);
        if (!b) {
            FileUploadUtils.downLocal(FilePathUtil.getFilePath(type), FileUploadUtils.getLocalBasePath() + typePath,
                    fileName);
        }
        String filePath = XSSEscape.escapePath(FileUploadUtils.getLocalBasePath() + typePath + fileName);
        OutputStream os = null;
        // 本地取对应文件
        File file = new File(filePath);
        try {
            os = response.getOutputStream();
            String contentType = Files.probeContentType(Paths.get(file.getAbsolutePath()));
            response.setHeader("Content-Type", contentType);
            response.setHeader("Content-Dispostion",
                    "attachment;filename=" + new String(file.getName().getBytes("utf-8"), "ISO8859-1"));
            @Cleanup
            FileInputStream fileInputStream = new FileInputStream(file);

            @Cleanup
            WritableByteChannel writableByteChannel = Channels.newChannel(os);

            @Cleanup
            FileChannel channel = fileInputStream.getChannel();
            channel.transferTo(0, channel.size(), writableByteChannel);
            channel.close();
            os.flush();
            writableByteChannel.close();
        } catch (IOException e) {
            e.printStackTrace();
        } finally {
            try {
                if (os != null) {
                    os.close();
                }
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }

    @Operation(summary = "分片上传获取")
    @GetMapping("/chunk")
    public ActionResult checkChunk(Chunk chunk) {
        String type = chunk.getExtension();
        if (!OptimizeUtil.fileType(configValueUtil.getAllowUploadFileType(), type)) {
            return ActionResult.fail(MsgCode.FA017.get());
        }
        String identifier = chunk.getIdentifier();
        String path = configValueUtil.getTemporaryFilePath();
        String filePath = XSSEscape.escapePath(path + identifier);
        List<File> chunkFiles = FileUtil.getFile(new File(FileUploadUtils.getLocalBasePath() + filePath));
        List<Integer> existsChunk = chunkFiles.stream().filter(f -> {
            if (f.getName().endsWith(".tmp")) {
                FileUtils.deleteQuietly(f);
            } else
                return f.getName().startsWith(identifier);
            return false;
        }).map(f -> Integer.parseInt(f.getName().replace(identifier.concat("-"), ""))).collect(Collectors.toList());
        ChunkRes chunkRes = ChunkRes.builder().merge(chunk.getTotalChunks().equals(existsChunk.size()))
                .chunkNumbers(existsChunk).build();
        return ActionResult.success(chunkRes);
    }

    @Operation(summary = "分片上传附件")
    @PostMapping("/chunk")
    public ActionResult upload(Chunk chunk, @RequestParam("file") MultipartFile file) {
        String type = chunk.getExtension();
        if (!OptimizeUtil.fileType(configValueUtil.getAllowUploadFileType(), type)) {
            return ActionResult.fail(MsgCode.FA017.get());
        }
        ChunkRes chunkRes = ChunkRes.builder().build();
        chunkRes.setMerge(false);
        File chunkFile = null;
        File chunkTmpFile = null;
        try {
            String filePath = FileUploadUtils.getLocalBasePath() + configValueUtil.getTemporaryFilePath();
            Integer chunkNumber = chunk.getChunkNumber();
            String identifier = XSSEscape.escapePath(chunk.getIdentifier());
            String chunkTempPath = XSSEscape.escapePath(filePath + identifier);
            File path = new File(chunkTempPath);
            if (!path.exists()) {
                path.mkdirs();
            }
            String chunkName = XSSEscape.escapePath(identifier.concat("-") + chunkNumber);
            String chunkTmpName = XSSEscape.escapePath(chunkName.concat(".tmp"));
            chunkFile = new File(chunkTempPath, chunkName);
            chunkTmpFile = new File(chunkTempPath, chunkTmpName);
            if (chunkFile.exists() && chunkFile.length() == chunk.getCurrentChunkSize()) {
                System.out.println("该分块已经上传：" + chunkFile.getName());
            } else {
                @Cleanup
                InputStream inputStream = file.getInputStream();
                FileUtils.copyInputStreamToFile(inputStream, chunkTmpFile);
                chunkTmpFile.renameTo(chunkFile);
            }
            int existsSize = (int) FileUtil.getFile(new File(chunkTempPath)).stream()
                    .filter(f -> f.getName().startsWith(identifier) && !f.getName().endsWith(".tmp")).count();
            chunkRes.setMerge(Objects.equals(existsSize, chunk.getTotalChunks()));
        } catch (Exception e) {
            try {
                FileUtils.deleteQuietly(chunkTmpFile);
                FileUtils.deleteQuietly(chunkFile);
            } catch (Exception ee) {
                e.printStackTrace();
            }
            System.out.println("上传异常：" + e);
            return ActionResult.fail(MsgCode.FA033.get());
        }
        return ActionResult.success(chunkRes);
    }

    @Operation(summary = "分片组装")
    @PostMapping("/merge")
    public ActionResult merge(MergeChunkDto mergeChunkDto) {
        String identifier = XSSEscape.escapePath(mergeChunkDto.getIdentifier());
        String path = FileUploadUtils.getLocalBasePath() + configValueUtil.getTemporaryFilePath();
        String filePath = XSSEscape.escapePath(path + identifier);
        String uuid = RandomUtil.uuId();
        String partFile = XSSEscape.escapePath(path + uuid + "." + mergeChunkDto.getExtension());
        UploaderVO vo = UploaderVO.builder().build();
        try {
            List<File> mergeFileList = FileUtil.getFile(new File(filePath));
            @Cleanup
            FileOutputStream destTempfos = new FileOutputStream(partFile, true);
            for (int i = 0; i < mergeFileList.size(); i++) {
                String chunkName = identifier.concat("-") + (i + 1);
                File files = new File(filePath, chunkName);
                if (files.exists()) {
                    FileUtils.copyFile(files, destTempfos);
                }
            }
            File partFiles = new File(partFile);
            if (partFiles.exists()) {
                MultipartFile multipartFile = FileUtil.createFileItem(partFiles);
                String type = mergeChunkDto.getType();
                PathTypeModel pathTypeModel = new PathTypeModel();
                pathTypeModel.setPathType(mergeChunkDto.getPathType());
                pathTypeModel.setSortRule(mergeChunkDto.getSortRule());
                pathTypeModel.setTimeFormat(mergeChunkDto.getTimeFormat());
                pathTypeModel.setFolder(mergeChunkDto.getFolder());
                if ("selfPath".equals(pathTypeModel.getPathType())) {
                    if (StringUtil.isNotEmpty(pathTypeModel.getFolder())) {
                        String folder = pathTypeModel.getFolder();
                        folder = folder.replaceAll("\\\\", "/");
                        // String regex = "^[a-z0-9A-Z\\u4e00-\\u9fa5\\\\\\/]+$";
                        // 文件夹名以字母或数字开头，由字母、数字、下划线和连字符组成，长度不超过100个字符
                        String regex = "^[a-zA-Z0-9][a-zA-Z0-9_\\-\\\\\\/]{0,99}$";
                        if (!folder.matches(regex)) {
                            return ActionResult.fail(MsgCode.FA038.get());
                        }
                    }
                }
                vo = uploaderVO(multipartFile, type, pathTypeModel);
                FileUtil.deleteTmp(multipartFile);
            }
        } catch (Exception e) {
            log.error("合并分片失败: {}", e.getMessage());
            throw new DataException(MsgCode.FA033.get());
        } finally {
            FileUtils.deleteQuietly(new File(filePath));
            FileUtils.deleteQuietly(new File(partFile));
        }
        return ActionResult.success(vo);
    }

    /**
     * 封装上传附件
     *
     * @param file
     * @param type
     * @return
     * @throws IOException
     */
    private UploaderVO uploaderVO(MultipartFile file, String type, PathTypeModel pathTypeModel) throws IOException {
        String orgFileName = file.getOriginalFilename();
        String fileType = UpUtil.getFileType(file);
        // if (OptimizeUtil.fileSize(file.getSize(), 1024000)) {
        // return ActionResult.fail("上传失败，文件大小超过1M");
        // }
        // if ("mail".equals(type)) {
        // type = "temporary";
        // }
        // 实际文件名
        String fileName = DateUtil.dateNow("yyyyMMdd") + "_" + RandomUtil.uuId() + "." + fileType;
        // 文件上传路径
        String filePath = FilePathUtil.getFilePath(type.toLowerCase());

        // 文件自定义路径相对路径
        String relativeFilePath = "";
        if ("selfPath".equals(pathTypeModel.getPathType()) && pathTypeModel.getSortRule() != null) {
            // 按路径规则顺序构建生成目录
            String sortRule = pathTypeModel.getSortRule();
            List<String> rules = null;
            if (sortRule.contains("[")) {
                rules = JsonUtil.getJsonToList(sortRule, String.class);
            } else {
                rules = Arrays.asList(pathTypeModel.getSortRule().split(","));
            }
            for (String rule : rules) {
                // 按用户存储
                if ("1".equals(rule)) {
                    UserInfo userInfo = UserProvider.getUser();
                    relativeFilePath += userInfo.getUserAccount() + "/";
                }
                // 按照时间格式
                else if (StringUtil.isNotEmpty(pathTypeModel.getTimeFormat()) && "2".equals(rule)) {
                    String timeFormat = pathTypeModel.getTimeFormat();
                    timeFormat = timeFormat.replaceAll("YYYY", "yyyy");
                    timeFormat = timeFormat.replaceAll("DD", "dd");
                    LocalDate currentDate = LocalDate.now();
                    DateTimeFormatter formatter = DateTimeFormatter.ofPattern(timeFormat);
                    String currentDateStr = currentDate.format(formatter);
                    relativeFilePath += currentDateStr;
                    if (!currentDateStr.endsWith("/")) {
                        relativeFilePath += "/";
                    }
                }
                // 按自定义目录
                else if (StringUtil.isNotEmpty(pathTypeModel.getFolder()) && "3".equals(rule)) {
                    String folder = pathTypeModel.getFolder();
                    folder = folder.replaceAll("\\\\", "/");
                    relativeFilePath += folder;
                    if (!folder.endsWith("/")) {
                        relativeFilePath += "/";
                    }
                }
            }

            if (StringUtil.isNotEmpty(relativeFilePath)) {
                relativeFilePath = StringUtil.replaceMoreStrToOneStr(relativeFilePath, "/");
                if (relativeFilePath.startsWith("/")) {
                    relativeFilePath = relativeFilePath.substring(1);
                }
                filePath += relativeFilePath;
                fileName = relativeFilePath.replaceAll("/", ",") + fileName;
            }
        }

        UploaderVO vo = UploaderVO.builder().fileSize(file.getSize()).fileExtension(fileType).build();
        FileInfo fileInfo = FileUploadUtils.uploadFile(file, filePath, fileName);
        fileName = fileInfo.getFilename();
        String thFilename = fileInfo.getThFilename();
        if (!StringUtil.isNotEmpty(thFilename)) {
            // 小图没有压缩直接用原图
            thFilename = fileName;
        }
        // 自定义文件实际文件名
        if (StringUtil.isNotEmpty(relativeFilePath)) {
            fileName = relativeFilePath.replaceAll("/", ",") + fileName;
            thFilename = relativeFilePath.replaceAll("/", ",") + thFilename;
        }
        vo.setName(fileName);
        // UploadUtil.uploadFile(configValueUtil.getFileType(), type, fileName, file,
        // filePath);
        if ("useravatar".equalsIgnoreCase(type)) {
            vo.setUrl(UploaderUtil.uploaderImg(fileName));
            vo.setUrl(UploaderUtil.uploaderImg(thFilename));
        } else if ("annex".equalsIgnoreCase(type)) {
            // UserInfo userInfo = UserProvider.getUser();
            // vo.setUrl(UploaderUtil.uploaderFile(userInfo.getId() + "#" + fileName + "#" +
            // type));
            vo.setUrl(UploaderUtil.uploaderImg("/api/file/Image/annex/", fileName));
            vo.setThumbUrl(UploaderUtil.uploaderImg("/api/file/Image/annex/", thFilename));
        } else if ("annexpic".equalsIgnoreCase(type)) {
            vo.setUrl(UploaderUtil.uploaderImg("/api/file/Image/annexpic/", fileName));
            vo.setThumbUrl(UploaderUtil.uploaderImg("/api/file/Image/annexpic/", thFilename));
        } else {
            vo.setUrl(UploaderUtil.uploaderImg("/api/file/Image/" + type.toLowerCase() + "/", fileName));
            vo.setThumbUrl(UploaderUtil.uploaderImg("/api/file/Image/" + type.toLowerCase() + "/", thFilename));
        }

        // 上传到永中
        if ("yozo".equals(configValueUtil.getPreviewType())) {
            try {
                @Cleanup
                InputStream inputStream = file.getInputStream();
                String s = yozoUtils.uploadFileInPreview(inputStream, orgFileName);
                Map<String, Object> map = JsonUtil.stringToMap(s);
                if ("操作成功".equals(map.get("message"))) {
                    Map<String, Object> dataMap = JsonUtil.stringToMap(String.valueOf(map.get("data")));
                    String verId = String.valueOf(dataMap.get("fileVersionId"));
                    vo.setFileVersionId(verId);
                }
            } catch (Exception e) {
                System.out.println("上传到永中失败");
                e.printStackTrace();
            }
        }
        return vo;
    }

    public static void main(String[] args) {
        String path = "../../../windows/win.ini|userAvatar/../../../../windows/win.ini";
        System.out.printf(XSSEscape.escapePath(path));
    }
}
