From 34981c30a78e8bbd7791131059a9210f9928b62c Mon Sep 17 00:00:00 2001
From: 刘光辉 <347230014@qq.com>
Date: 星期四, 17 九月 2026 09:24:09 +0800
Subject: [PATCH] Merge remote-tracking branch 'origin/master' into master

---
 jnpf-file/jnpf-file-biz/src/main/java/jnpf/service/impl/FileServiceImpl.java |  457 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++
 1 files changed, 457 insertions(+), 0 deletions(-)

diff --git a/jnpf-file/jnpf-file-biz/src/main/java/jnpf/service/impl/FileServiceImpl.java b/jnpf-file/jnpf-file-biz/src/main/java/jnpf/service/impl/FileServiceImpl.java
new file mode 100644
index 0000000..abf97eb
--- /dev/null
+++ b/jnpf-file/jnpf-file-biz/src/main/java/jnpf/service/impl/FileServiceImpl.java
@@ -0,0 +1,457 @@
+package jnpf.service.impl;
+
+import cn.hutool.core.net.URLEncodeUtil;
+import cn.hutool.core.util.URLUtil;
+import com.google.common.collect.ImmutableList;
+import jakarta.servlet.http.HttpServletResponse;
+import jnpf.base.UserInfo;
+import jnpf.config.ConfigValueUtil;
+import jnpf.constant.FileTypeConstant;
+import jnpf.constant.GlobalConst;
+import jnpf.constant.MsgCode;
+import jnpf.entity.FileParameter;
+import jnpf.exception.DataException;
+import jnpf.model.*;
+import jnpf.service.FileService;
+import jnpf.util.*;
+import lombok.Cleanup;
+import lombok.RequiredArgsConstructor;
+import lombok.extern.slf4j.Slf4j;
+import org.apache.commons.codec.binary.Base64;
+import org.apache.commons.io.FileUtils;
+import org.dromara.x.file.storage.core.FileInfo;
+import org.springframework.http.HttpHeaders;
+import org.springframework.http.HttpStatus;
+import org.springframework.http.MediaType;
+import org.springframework.http.MediaTypeFactory;
+import org.springframework.stereotype.Service;
+import org.springframework.util.StreamUtils;
+import org.springframework.web.multipart.MultipartFile;
+
+import java.io.File;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.time.LocalDate;
+import java.time.format.DateTimeFormatter;
+import java.util.Arrays;
+import java.util.List;
+import java.util.Objects;
+import java.util.stream.Collectors;
+
+
+@Slf4j
+@Service
+@RequiredArgsConstructor
+public class FileServiceImpl implements FileService {
+
+    public static final List<String> IMAGE_PATH = ImmutableList.of("/api/file/Image/");
+
+    private final List<String> whiteImageFolder = Arrays.asList(FileTypeConstant.USERAVATAR.toLowerCase(), FileTypeConstant.BIVISUALPATH.toLowerCase());
+
+
+    private final ConfigValueUtil configValueUtil;
+
+
+    @Override
+    public String getPath(String type) {
+        return FilePathUtil.getFilePath(type);
+    }
+
+    @Override
+    public String getLocalBasePath() {
+        return FileUploadUtils.getLocalBasePath();
+    }
+
+    @Override
+    public UploaderVO uploadFile(MergeChunkDto mergeChunkDto, MultipartFile file) {
+        String fileType = UpUtil.getFileType(file);
+        if (StringUtil.isEmpty(fileType)) {
+            fileType = mergeChunkDto.getFileType();
+        }
+        //楠岃瘉绫诲瀷
+        if (!OptimizeUtil.fileType(configValueUtil.getAllowUploadFileType(), fileType)) {
+            throw new DataException(MsgCode.FA017.get());
+        }
+        PathTypeModel pathTypeModel = new PathTypeModel();
+        pathTypeModel.setPathType(mergeChunkDto.getPathType());
+        pathTypeModel.setTimeFormat(mergeChunkDto.getTimeFormat());
+        pathTypeModel.setSortRule(mergeChunkDto.getSortRule());
+        pathTypeModel.setFolder(mergeChunkDto.getFolder());
+
+        if ("selfPath".equals(pathTypeModel.getPathType()) && StringUtil.isNotEmpty(pathTypeModel.getFolder())) {
+                String folder = pathTypeModel.getFolder();
+                folder = folder.replace("\\\\", "/");
+                //鏂囦欢澶瑰悕浠ュ瓧姣嶆垨鏁板瓧寮�澶达紝鐢卞瓧姣嶃�佹暟瀛椼�佷笅鍒掔嚎鍜岃繛瀛楃缁勬垚锛岄暱搴︿笉瓒呰繃100涓瓧绗�
+                String regex = "^[a-zA-Z0-9][a-zA-Z0-9_\\-\\\\/]{0,99}$";
+                if (!folder.matches(regex)) {
+                    throw new DataException(MsgCode.FA038.get());
+                }
+            }
+
+        //瀹為檯鏂囦欢鍚�
+        String fileName = DateUtil.dateNow("yyyyMMdd") + "_" + RandomUtil.uuId() + "." + fileType;
+        //鏂囦欢涓婁紶璺緞
+        String type = mergeChunkDto.getType();
+
+        //鏂囦欢鑷畾涔夎矾寰勭浉瀵硅矾寰�
+        StringBuilder relativeFilePath = new StringBuilder();
+        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.append(userInfo.getUserAccount()).append("/");
+                }
+                // 鎸夌収鏃堕棿鏍煎紡
+                else if (StringUtil.isNotEmpty(pathTypeModel.getTimeFormat()) && "2".equals(rule)) {
+                    String timeFormat = pathTypeModel.getTimeFormat();
+                    timeFormat = timeFormat.replace("YYYY", "yyyy");
+                    timeFormat = timeFormat.replace("DD", "dd");
+                    LocalDate currentDate = LocalDate.now();
+                    DateTimeFormatter formatter = DateTimeFormatter.ofPattern(timeFormat);
+                    String currentDateStr = currentDate.format(formatter);
+                    relativeFilePath.append(currentDateStr);
+                    if (!currentDateStr.endsWith("/")) {
+                        relativeFilePath.append("/");
+                    }
+                }
+                // 鎸夎嚜瀹氫箟鐩綍
+                else if (StringUtil.isNotEmpty(pathTypeModel.getFolder()) && "3".equals(rule)) {
+                    String folder = pathTypeModel.getFolder();
+                    folder = folder.replace("\\\\", "/");
+                    relativeFilePath.append(folder);
+                    if (!folder.endsWith("/")) {
+                        relativeFilePath.append("/");
+                    }
+                }
+            }
+
+            if (StringUtil.isNotEmpty(relativeFilePath.toString())) {
+                relativeFilePath = new StringBuilder(StringUtil.replaceMoreStrToOneStr(relativeFilePath.toString(), "/"));
+                if (relativeFilePath.toString().startsWith("/")) {
+                    relativeFilePath = new StringBuilder(relativeFilePath.substring(1));
+                }
+                fileName = relativeFilePath.toString().replace("/", ",") + fileName;
+            }
+        }
+
+
+        UploaderVO vo = UploaderVO.builder().fileSize(file.getSize()).fileExtension(fileType).build();
+        FileInfo fileInfo = FileUploadUtils.uploadFile(new FileParameter(type, fileName).setThumbnail(true), file);
+        fileName = fileInfo.getFilename();
+        String thFilename = fileInfo.getThFilename();
+        if (!StringUtil.isNotEmpty(thFilename)) {
+            //灏忓浘娌℃湁鍘嬬缉鐩存帴鐢ㄥ師鍥�
+            thFilename = fileName;
+        }
+        //鑷畾涔夋枃浠跺疄闄呮枃浠跺悕
+        if (StringUtil.isNotEmpty(relativeFilePath.toString())) {
+            fileName = relativeFilePath.toString().replace("/", ",") + fileName;
+            thFilename = relativeFilePath.toString().replace("/", ",") + thFilename;
+        }
+        vo.setName(fileName);
+
+        vo.setUrl(UploaderUtil.uploaderImg(IMAGE_PATH.get(0) + type + "/", fileName));
+        vo.setThumbUrl(UploaderUtil.uploaderImg(IMAGE_PATH.get(0) + type + "/", thFilename));
+
+        //涓婁紶鍒版案涓�
+        configValueUtil.getPreviewType();
+        return vo;
+    }
+
+    @Override
+    public ChunkRes checkChunk(Chunk chunk) {
+        String type = chunk.getExtension();
+        if (!OptimizeUtil.fileType(configValueUtil.getAllowUploadFileType(), type)) {
+            throw new DataException(MsgCode.FA017.get());
+        }
+        String identifier = chunk.getIdentifier();
+        String path = getPath(FileTypeConstant.TEMPORARY);
+        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);
+                return false;
+            } else {
+                return f.getName().startsWith(identifier);
+            }
+        }).map(f -> Integer.parseInt(f.getName().replace(chunk.getIdentifier().concat("-"), ""))).collect(Collectors.toList());
+        return ChunkRes.builder().merge(chunk.getTotalChunks().equals(existsChunk.size())).chunkNumbers(existsChunk).build();
+    }
+
+    @Override
+    public ChunkRes uploadChunk(Chunk chunk, MultipartFile file) {
+        String type = chunk.getExtension();
+        if (!OptimizeUtil.fileType(configValueUtil.getAllowUploadFileType(), type)) {
+            throw new DataException(MsgCode.FA017.get());
+        }
+        ChunkRes chunkRes = ChunkRes.builder().build();
+        chunkRes.setMerge(false);
+        File chunkFile = null;
+        File chunkTmpFile = null;
+        try {
+            String filePath = FileUploadUtils.getLocalBasePath() + getPath(FileTypeConstant.TEMPORARY);
+            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()) {
+                log.info("璇ュ垎鍧楀凡缁忎笂浼狅細" + chunkFile.getName());
+            } else {
+                @Cleanup InputStream inputStream = file.getInputStream();
+                FileUtils.copyInputStreamToFile(inputStream, chunkTmpFile);
+                boolean b = chunkTmpFile.renameTo(chunkFile);
+                if (b){
+                    log.info("鎴愬姛");
+                }else {
+                    log.info("澶辫触");
+                }
+            }
+            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();
+            }
+            log.info("涓婁紶寮傚父锛�" + e);
+            throw new DataException(MsgCode.FA033.get());
+        }
+        return chunkRes;
+    }
+
+    @Override
+    public UploaderVO mergeChunk(MergeChunkDto mergeChunkDto) {
+        String identifier = XSSEscape.escapePath(mergeChunkDto.getIdentifier());
+        String path = FileUploadUtils.getLocalBasePath() + getPath(FileTypeConstant.TEMPORARY);
+        String filePath = XSSEscape.escapePath(path + identifier);
+        String uuid = RandomUtil.uuId();
+        String partFile = XSSEscape.escapePath(path + uuid + "." + mergeChunkDto.getExtension());
+        UploaderVO vo = UploaderVO.builder().build();
+        try (FileOutputStream destTempfos = new FileOutputStream(partFile, true)){
+            List<File> mergeFileList = FileUtil.getFile(new File(filePath));
+            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);
+                vo = uploadFile(mergeChunkDto, multipartFile);
+                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 vo;
+    }
+
+    @Override
+    public void downloadFile(String encryption, String downName) {
+        String fileNameAll = DesUtil.aesDecode(encryption);
+        if (!StringUtil.isEmpty(fileNameAll)) {
+            fileNameAll = fileNameAll.replace("\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);
+            fileName = URLUtil.decode(fileName, GlobalConst.DEFAULT_CHARSET);
+            FileParameter fileParameter = new FileParameter(type, fileName);
+            //楠岃瘉缂撳瓨
+            if (ticketObj != null) {
+                //鏌愪簺鎵嬫満娴忚鍣ㄤ笅杞藉悗浼氭湁鎻愮ず绐楀彛, 浼氳闂袱娆°�佷笁娆′笅杞藉湴鍧�锛岃姹傚ご涓嶆槸app绫诲瀷
+                TicketUtil.updateTicket(cacheKEY, "1", 10L);
+                FileUploadUtils.downloadFile(fileParameter, inputStream ->
+                    FileDownloadUtil.outFile(inputStream, downName)
+                );
+                if (FileTypeConstant.FILEZIPDOWNTEMPPATH.equals(type) ) { //鍒犻櫎鎵撳寘鐨勪复鏃舵枃浠讹紝閲婃斁瀛樺偍
+                    FileUploadUtils.deleteFileByPathAndFileName(fileParameter);
+                }
+            } else {
+                if (FileTypeConstant.FILEZIPDOWNTEMPPATH.equals(type)) { //鍒犻櫎鎵撳寘鐨勪复鏃舵枃浠讹紝閲婃斁瀛樺偍
+                    FileUploadUtils.deleteFileByPathAndFileName(fileParameter);
+                }
+                throw new DataException(MsgCode.FA039.get());
+            }
+        }
+    }
+
+    @Override
+    public boolean fileExists(String path, String fileName) {
+        return FileUploadUtils.exists(new FileParameter(path, fileName));
+    }
+
+    @Override
+    public String previewFile(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.stream(fileType).filter(type -> type.equals(docType)).findFirst().orElse(null);
+
+        if (StringUtil.isEmpty(s)) {
+            throw new DataException(MsgCode.FA040.get());
+        }
+
+        //瑙f瀽鏂囦欢url 鑾峰彇绫诲瀷
+        String type = null;
+
+        String fileNameAll = previewParams.getFileDownloadUrl();
+        if (!StringUtil.isEmpty(fileNameAll)) {
+            String[] data = fileNameAll.split("/");
+            if (data.length > 4) {
+                type = data[4];
+            } else {
+                type = "";
+            }
+
+        }
+        String url;
+        //鏂囦欢棰勮绛栫暐
+        if ("yozo".equals(configValueUtil.getPreviewType())) {
+
+            url = "";
+        } else {
+            String downFileName = fileName;
+            if (downFileName.contains(",")) {
+                downFileName = downFileName.substring(downFileName.lastIndexOf(",") + 1);
+            }
+            url = configValueUtil.getApiDomain() + IMAGE_PATH.get(0) + type + "/" + fileName + "?fullfilename=" + downFileName + "&s=" + UserProvider.getUser().getSecurityKey();
+            //encode缂栫爜
+            String fileUrl = Base64.encodeBase64String(url.getBytes());
+            url = configValueUtil.getKkFileUrl() + "onlinePreview?url=" + fileUrl;
+        }
+        return url;
+    }
+
+    @Override
+    public void flushFile(String type, String fileName, String securityKey, boolean redirect) {
+        //椤甸潰鎾斁瑙嗛鐨勬儏鍐典笅鍙栨秷閲嶅畾鍚�
+        boolean isPlayVideo = StringUtil.isNotEmpty(ServletUtil.getHeader(HttpHeaders.RANGE));
+        //鐩綍鏍¢獙, 寮�鏀綰serAvatar銆丅iVisualPath
+        if (!whiteImageFolder.contains(type.toLowerCase())) {
+            if (StringUtil.isEmpty(securityKey)) {
+                throw new DataException(MsgCode.FA039.get());
+            }
+            String ticket = DesUtil.aesOrDecode(securityKey, false, true);
+            String token = TicketUtil.parseTicket(ticket);
+            if (token == null) {
+                throw new DataException(MsgCode.FA039.get());
+            }
+            UserInfo user = UserProvider.getUser(token);
+            if (user.getUserId() == null) {
+                TicketUtil.deleteTicket(ticket);
+                throw new DataException(MsgCode.FA039.get());
+            }
+        }
+        if (!isPlayVideo && StringUtil.isNotEmpty(securityKey) && redirect) {
+            String downName = fileName;
+            if (downName.contains(",")) {
+                downName = downName.substring(downName.lastIndexOf(",") + 1);
+            }
+            String urlEncodeFileName = URLEncodeUtil.encode(fileName, GlobalConst.DEFAULT_CHARSET);
+            String urlEncodeDownName = URLEncodeUtil.encode(downName, GlobalConst.DEFAULT_CHARSET);
+            String url = configValueUtil.getApiDomain() + UploaderUtil.uploaderFile(urlEncodeFileName + "#" + type) + "&name=" + urlEncodeDownName + "&fullfilename=" + urlEncodeDownName;
+            try {
+                ServletUtil.getResponse().sendRedirect(url);
+            } catch (IOException e) {
+                throw new IllegalArgumentException(e);
+            }
+        } else {
+            FileUploadUtils.downloadFile(new FileParameter(type, fileName), inputStream -> {
+                if (isPlayVideo) {
+                    FileInfo remoteFileInfo = FileUploadUtils.getRemoteFileInfo(new FileParameter(type, fileName));
+                    streamVideo(inputStream, remoteFileInfo.getFilename(), remoteFileInfo.getSize());
+                } else {
+                    FileDownloadUtil.flushFile(inputStream, fileName);
+                }
+            });
+        }
+    }
+
+    /**
+     * 瑙嗛鏂偣缁紶鏂规硶
+     *
+     * @param input
+     * @param fileName
+     * @param length
+     */
+    public void streamVideo(InputStream input, String fileName, long length) {
+        HttpServletResponse response = ServletUtil.getResponse();
+        String rangeHeader = ServletUtil.getHeader(HttpHeaders.RANGE);
+        final long CHUNK_SIZE = (long) 1024 * 1024; // 1MB
+        String range = rangeHeader.replace("bytes=", "");
+        String[] ranges = range.split("-");
+
+        long start = Long.parseLong(ranges[0]);
+        long end;
+
+        if (ranges.length > 1 && !ranges[1].isEmpty()) {
+            // 瀹㈡埛绔槑纭寚瀹氫簡缁撴潫浣嶇疆锛歜ytes=start-end
+            end = Long.parseLong(ranges[1]);
+        } else {
+            // 瀹㈡埛绔彧鎸囧畾浜嗗紑濮嬩綅缃細bytes=start-
+            end = start + CHUNK_SIZE - 1;
+        }
+        // 纭繚涓嶈秴鍑烘枃浠惰竟鐣�
+        end = Math.min(end, length - 1);
+
+        // 杈圭晫妫�鏌�
+        if (start >= length || end < start) {
+            response.setStatus(HttpStatus.REQUESTED_RANGE_NOT_SATISFIABLE.value());
+            response.setHeader("Content-Range", "bytes */" + length);
+            return;
+        }
+        long contentLength = end - start + 1;
+
+        if (fileName == null) {
+            fileName = ".mp4";
+        }
+        // 璁剧疆鍝嶅簲澶�
+        response.setStatus(HttpStatus.PARTIAL_CONTENT.value());
+        response.setContentType(MediaTypeFactory.getMediaType(fileName).orElse(MediaType.ALL).toString());
+        response.setHeader("Content-Length", String.valueOf(contentLength));
+        response.setHeader("Content-Range", "bytes " + start + "-" + end + "/" + length);
+        response.setHeader("Accept-Ranges", "bytes");
+
+        try {
+            // 娉ㄦ剰锛歟nd + 1 鏄洜涓篶opyRange鐨勭粨鏉熶綅缃槸涓嶅寘鍚殑
+            StreamUtils.copyRange(input, response.getOutputStream(), start, end + 1);
+        } catch (Exception e) {
+            log.error(e.getMessage(), e);
+        }
+    }
+}

--
Gitblit v1.8.0