소스 검색

文件上传

yangshun 3 주 전
부모
커밋
aba7a55926

+ 6 - 6
guoyan-module-infra/src/main/java/com/cy/guoyan/admin/module/infra/api/file/FileApi.java

@@ -17,7 +17,7 @@ public interface FileApi {
      * @return 文件路径
      */
     default String createFile(byte[] content) {
-        return createFile(content, null, null, null);
+        return createFile(content, null, null, null, null);
     }
 
     /**
@@ -28,7 +28,7 @@ public interface FileApi {
      * @return 文件路径
      */
     default String createFile(byte[] content, String name) {
-        return createFile(content, name, null, null);
+        return createFile(content, name, null, null,null);
     }
 
     /**
@@ -41,7 +41,7 @@ public interface FileApi {
      * @return 文件路径
      */
     String createFile(@NotEmpty(message = "文件内容不能为空") byte[] content,
-                      String name, String directory, String type);
+                      String name, String directory, String type,String restricted);
 
     /**
      * 保存文件,并返回文件的访问路径
@@ -49,12 +49,12 @@ public interface FileApi {
      * @param content 文件内容
      * @return 文件路径
      */
-    default Map<String, Object> getFile(String name,byte[] content) {
-        return getFile(null,null,name, null, content);
+    default Map<String, Object> getFile(String name,byte[] content,String restricted) {
+        return getFile(null,null,name, null, content, restricted);
     }
 
 
-    Map<String, Object> getFile(Long businessId, String jobTypeCode, String name, String path, byte[] content);
+    Map<String, Object> getFile(Long businessId, String jobTypeCode, String name, String path, byte[] content, String restricted);
 
 
 }

+ 4 - 4
guoyan-module-infra/src/main/java/com/cy/guoyan/admin/module/infra/api/file/FileApiImpl.java

@@ -20,13 +20,13 @@ public class FileApiImpl implements FileApi {
     private FileService fileService;
 
     @Override
-    public String createFile(byte[] content, String name, String directory, String type) {
-        return fileService.createFile(content, name, directory, type);
+    public String createFile(byte[] content, String name, String directory, String type,String  restricted) {
+        return fileService.createFile(content, name, directory, type,restricted);
     }
 
     @Override
-    public Map<String, Object> getFile(Long businessId, String jobTypeCode, String name, String path, byte[] content) {
-        return fileService.getFile(businessId,jobTypeCode,name, path, content);
+    public Map<String, Object> getFile(Long businessId, String jobTypeCode, String name, String path, byte[] content,String restricted) {
+        return fileService.getFile(businessId,jobTypeCode,name, path, content, restricted);
     }
 
 }

+ 10 - 3
guoyan-module-infra/src/main/java/com/cy/guoyan/admin/module/infra/controller/admin/file/FileController.java

@@ -55,7 +55,7 @@ public class FileController {
         MultipartFile file = uploadReqVO.getFile();
         byte[] content = IoUtil.readBytes(file.getInputStream());
         return success(fileService.createFile(content, file.getOriginalFilename(),
-                uploadReqVO.getDirectory(), file.getContentType()));
+                uploadReqVO.getDirectory(), file.getContentType(),uploadReqVO.getRestricted()));
     }
 
     @GetMapping("/access/**")
@@ -94,10 +94,15 @@ public class FileController {
             response.sendError(HttpServletResponse.SC_NOT_FOUND);
             return;
         }
-
+        // 获取路径中的相对文件名
         String relativePath = fullPath.substring(index + prefix.length());
+        // 处理路径穿越风险
+        if (relativePath.contains("..") || relativePath.contains("/") || relativePath.contains("\\")) {
+            response.sendError(HttpServletResponse.SC_BAD_REQUEST, "非法路径");
+            return;
+        }
+        // 拼接本地路径
         File file = new File(fileUploadDir, relativePath);
-
         if (!file.exists()) {
             response.sendError(HttpServletResponse.SC_NOT_FOUND);
             return;
@@ -108,6 +113,7 @@ public class FileController {
         String encodedFileName = URLEncoder.encode(file.getName(), "UTF-8").replaceAll("\\+", "%20");
         response.setHeader("Content-Disposition", "attachment; filename=\"" + encodedFileName + "\"");
 
+        // 写出文件流
         try (InputStream in = new FileInputStream(file);
              OutputStream out = response.getOutputStream()) {
             byte[] buffer = new byte[8192];
@@ -119,6 +125,7 @@ public class FileController {
     }
 
 
+
     @GetMapping("/presigned-url")
     @Operation(summary = "获取文件预签名地址", description = "模式二:前端上传文件:用于前端直接上传七牛、阿里云 OSS 等文件存储器")
     @Parameters({

+ 3 - 0
guoyan-module-infra/src/main/java/com/cy/guoyan/admin/module/infra/controller/admin/file/vo/file/FileUploadReqVO.java

@@ -17,4 +17,7 @@ public class FileUploadReqVO {
     @Schema(description = "文件目录", example = "XXX/YYY")
     private String directory;
 
+    private String restricted;
+
+
 }

+ 1 - 1
guoyan-module-infra/src/main/java/com/cy/guoyan/admin/module/infra/controller/app/file/AppFileController.java

@@ -38,7 +38,7 @@ public class AppFileController {
         MultipartFile file = uploadReqVO.getFile();
         byte[] content = IoUtil.readBytes(file.getInputStream());
         return success(fileService.createFile(content, file.getOriginalFilename(),
-                uploadReqVO.getDirectory(), file.getContentType()));
+                uploadReqVO.getDirectory(), file.getContentType(),uploadReqVO.getRestricted()));
     }
 
     @GetMapping("/presigned-url")

+ 2 - 0
guoyan-module-infra/src/main/java/com/cy/guoyan/admin/module/infra/controller/app/file/vo/AppFileUploadReqVO.java

@@ -17,4 +17,6 @@ public class AppFileUploadReqVO {
     @Schema(description = "文件目录", example = "XXX/YYY")
     private String directory;
 
+    private String restricted;
+
 }

+ 2 - 2
guoyan-module-infra/src/main/java/com/cy/guoyan/admin/module/infra/service/file/FileService.java

@@ -33,9 +33,9 @@ public interface FileService {
      * @return 文件路径
      */
     String createFile(@NotEmpty(message = "文件内容不能为空") byte[] content,
-                      String name, String directory, String type);
+                      String name, String directory, String type,String restricted);
 
-    Map<String, Object> getFile(Long businessId, String jobTypeCode, String name, String path, byte[] content);
+    Map<String, Object> getFile(Long businessId, String jobTypeCode, String name, String path, byte[] content,String  restricted);
 
 
     /**

+ 51 - 21
guoyan-module-infra/src/main/java/com/cy/guoyan/admin/module/infra/service/file/FileServiceImpl.java

@@ -5,6 +5,7 @@ import cn.hutool.core.io.FileTypeUtil;
 import cn.hutool.core.io.FileUtil;
 import cn.hutool.core.io.file.FileNameUtil;
 import cn.hutool.core.lang.Assert;
+import cn.hutool.core.util.IdUtil;
 import cn.hutool.core.util.StrUtil;
 import cn.hutool.crypto.digest.DigestUtil;
 import com.alibaba.fastjson.JSON;
@@ -22,6 +23,7 @@ import com.google.common.annotations.VisibleForTesting;
 import lombok.SneakyThrows;
 import lombok.extern.slf4j.Slf4j;
 import me.chanjar.weixin.common.util.fs.FileUtils;
+import org.apache.commons.lang3.StringUtils;
 import org.springframework.beans.factory.annotation.Value;
 import org.springframework.stereotype.Service;
 
@@ -76,7 +78,7 @@ public class FileServiceImpl implements FileService {
 
     @Override
     @SneakyThrows
-    public String createFile(byte[] content, String name, String directory, String type) {
+    public String createFile(byte[] content, String name, String directory, String type, String restricted) {
         // 1.1 处理 type 为空的情况
         if (StrUtil.isEmpty(type)) {
             type = FileTypeUtils.getMineType(content, name);
@@ -94,10 +96,15 @@ public class FileServiceImpl implements FileService {
         }
 
         // 2.1 生成上传的 path,需要保证唯一
-        String path = generateUploadPath(name, directory);
+        String path = generateUploadPath(content, name);
         String filePath = fileUploadDir + path;
         FileUtil.writeBytes(content, filePath);
-        String url = StrUtil.format("/files/{}", path);
+        String url = "";
+        if (StringUtils.isBlank( restricted)) {
+            url = StrUtil.format("/files/{}", path);
+        }else {
+            url = StrUtil.format("/files/no/{}", path);
+        }
         log.info("📂 文件上传信息如下:");
         log.info("Name:       {}", name);
         log.info("Path:       {}", path);
@@ -128,7 +135,7 @@ public class FileServiceImpl implements FileService {
     }
     @Override
     @SneakyThrows
-    public Map<String, Object> getFile(Long businessId, String jobTypeCode, String name, String path, byte[] content) {
+    public Map<String, Object> getFile(Long businessId, String jobTypeCode, String name, String path, byte[] content,String restricted) {
         // 计算默认的 path 名
         String type = FileTypeUtils.getMineType(content, name);
         if (StrUtil.isEmpty(path)) {
@@ -139,23 +146,28 @@ public class FileServiceImpl implements FileService {
             name = path;
         }
 
-        // 上传到文件存储器
-        FileClient client = fileConfigService.getMasterFileClient();
-        Assert.notNull(client, "客户端(master) 不能为空");
-        String url = client.upload(content, path, type);
-
-        // 保存到数据库
-        FileDO file = new FileDO();
-        file.setConfigId(client.getId());
-        file.setName(name);
-        file.setPath(path);
-        file.setUrl(url);
-        file.setType(type);
-//        file.setBusinessId(businessId);
-//        file.setJobTypeCode(jobTypeCode);
-        file.setSize(content.length);
-        fileMapper.insert(file);
-        Map<String, Object> map = JSON.parseObject(JSON.toJSONString(file), Map.class);
+        path = generateUploadPath(content, name);
+
+        String filePath = fileUploadDir + path;
+        FileUtil.writeBytes(content, filePath);
+        String url = "";
+        if (StringUtils.isBlank( restricted)) {
+            url = StrUtil.format("/files/{}", path);
+        }else {
+            url = StrUtil.format("/restricted/files/{}", path);
+        }
+        log.info("📂 文件上传信息如下:");
+        log.info("Name:       {}", name);
+        log.info("Path:       {}", path);
+        log.info("Url:        {}", url);
+        log.info("Type:       {}", convertMimeToExtension(type));
+        log.info("Size:       {}", formatFileSize(content.length));
+        Map<String, Object> map = new HashMap<>();
+        map.put("url", url);
+        map.put("type", convertMimeToExtension(type));
+        map.put("size", formatFileSize(content.length));
+        map.put("name", name);
+        map.put("path", path);
         return map;
     }
 
@@ -250,6 +262,24 @@ public class FileServiceImpl implements FileService {
         return name;
     }
 
+    public String generateUploadPath(byte[] content, String originalName) {
+        // 1. 生成文件内容的 sha256 作为唯一标识
+        String fileHash = DigestUtil.sha256Hex(content);
+
+        // 2. 生成短随机字符串,防止重复
+        String randomSuffix = IdUtil.fastSimpleUUID().substring(0, 8);
+
+        // 3. 获取文件扩展名
+        String ext = FileUtil.extName(originalName);
+        String extension = StrUtil.isNotEmpty(ext) ? "." + ext : "";
+
+        // 4. 拼接最终文件名:{hash}-{random}.{ext}
+        String fileName = fileHash + "-" + randomSuffix + extension;
+
+        return fileName; // 直接作为相对路径存储,无目录结构
+    }
+
+
     @Override
     @SneakyThrows
     public FilePresignedUrlRespVO getFilePresignedUrl(String name, String directory) {