From 4ad2ddeabe36490187c6ea37605f7083401e9a02 Mon Sep 17 00:00:00 2001
From: hongawen <83944980@qq.com>
Date: Fri, 15 May 2026 09:48:36 +0800
Subject: [PATCH] =?UTF-8?q?feat(file):=20=E6=89=A9=E5=B1=95=E6=96=87?=
=?UTF-8?q?=E4=BB=B6=E4=B8=8A=E4=BC=A0=E5=93=8D=E5=BA=94=E4=BF=A1=E6=81=AF?=
=?UTF-8?q?=E5=B9=B6=E5=A2=9E=E5=BC=BA=E5=8C=BF=E5=90=8D=E6=96=87=E4=BB=B6?=
=?UTF-8?q?=E8=AE=BF=E9=97=AE=E5=AE=89=E5=85=A8=E6=8E=A7=E5=88=B6?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
- 在FileUploadRespVO中增加configId和path字段,丰富文件上传返回信息
- 新增selectByConfigIdAndPath方法用于按配置ID和路径查询文件记录
- 在getFileContent服务中添加存在性校验,防止已删除文件被匿名访问
- 更新getFileContent接口注释为"获取文件内容(匿名)",明确使用场景
- 为图片文件添加缓存控制头,设置max-age为一天并使用ETag实现条件缓存
- 通过DigestUtil计算文件内容SHA256作为ETag值,优化CDN和网关层缓存命中
---
.../controller/admin/file/FileController.java | 6 +++---
.../admin/file/vo/file/FileUploadRespVO.java | 6 ++++++
.../module/system/dal/mysql/file/FileMapper.java | 14 ++++++++++++++
.../framework/file/core/utils/FileTypeUtils.java | 5 +++++
.../system/service/file/FileServiceImpl.java | 6 ++++++
5 files changed, 34 insertions(+), 3 deletions(-)
diff --git a/rdms-system/rdms-system-boot/src/main/java/com/njcn/rdms/module/system/controller/admin/file/FileController.java b/rdms-system/rdms-system-boot/src/main/java/com/njcn/rdms/module/system/controller/admin/file/FileController.java
index 702db48..16fb56f 100644
--- a/rdms-system/rdms-system-boot/src/main/java/com/njcn/rdms/module/system/controller/admin/file/FileController.java
+++ b/rdms-system/rdms-system-boot/src/main/java/com/njcn/rdms/module/system/controller/admin/file/FileController.java
@@ -51,7 +51,8 @@ public class FileController {
byte[] content = IoUtil.readBytes(file.getInputStream());
FileDO fileDO = fileService.createFile(content, file.getOriginalFilename(),
uploadReqVO.getDirectory(), file.getContentType());
- return success(new FileUploadRespVO(String.valueOf(fileDO.getId()), fileDO.getUrl()));
+ return success(new FileUploadRespVO(String.valueOf(fileDO.getId()),
+ fileDO.getConfigId(), fileDO.getPath(), fileDO.getUrl()));
}
@GetMapping("/presigned-url")
@@ -115,8 +116,7 @@ public class FileController {
@GetMapping("/{configId}/get/**")
@PermitAll
-
- @Operation(summary = "下载文件")
+ @Operation(summary = "获取文件内容(匿名)", description = "富文本
等匿名场景使用:图片走 inline 内联渲染,其它类型走 attachment 下载")
@Parameter(name = "configId", description = "配置编号", required = true)
public void getFileContent(HttpServletRequest request,
HttpServletResponse response,
diff --git a/rdms-system/rdms-system-boot/src/main/java/com/njcn/rdms/module/system/controller/admin/file/vo/file/FileUploadRespVO.java b/rdms-system/rdms-system-boot/src/main/java/com/njcn/rdms/module/system/controller/admin/file/vo/file/FileUploadRespVO.java
index e09a244..1a8ab45 100644
--- a/rdms-system/rdms-system-boot/src/main/java/com/njcn/rdms/module/system/controller/admin/file/vo/file/FileUploadRespVO.java
+++ b/rdms-system/rdms-system-boot/src/main/java/com/njcn/rdms/module/system/controller/admin/file/vo/file/FileUploadRespVO.java
@@ -14,6 +14,12 @@ public class FileUploadRespVO {
@Schema(description = "文件编号,Long 以字符串返回,避免前端精度丢失", requiredMode = Schema.RequiredMode.REQUIRED, example = "1024")
private String id;
+ @Schema(description = "配置编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "22")
+ private Long configId;
+
+ @Schema(description = "文件相对路径,含目录与文件名", requiredMode = Schema.RequiredMode.REQUIRED, example = "20260514/avatar_1747273800000.png")
+ private String path;
+
@Schema(description = "文件 URL", requiredMode = Schema.RequiredMode.REQUIRED, example = "https://www.iocoder.cn/rdms.jpg")
private String url;
diff --git a/rdms-system/rdms-system-boot/src/main/java/com/njcn/rdms/module/system/dal/mysql/file/FileMapper.java b/rdms-system/rdms-system-boot/src/main/java/com/njcn/rdms/module/system/dal/mysql/file/FileMapper.java
index 8a19781..e12c5f4 100644
--- a/rdms-system/rdms-system-boot/src/main/java/com/njcn/rdms/module/system/dal/mysql/file/FileMapper.java
+++ b/rdms-system/rdms-system-boot/src/main/java/com/njcn/rdms/module/system/dal/mysql/file/FileMapper.java
@@ -41,4 +41,18 @@ public interface FileMapper extends BaseMapperX {
return file;
}
+ /**
+ * 按 (configId, path) 查询文件记录
+ *
+ * 用于匿名图片代理接口 {@code GET /system/file/{configId}/get/**} 的存在性校验:
+ * 当 infra_file 已被逻辑删除时,MyBatis-Plus 全局过滤会自动加上 deleted=0,
+ * 这里查不到即视为已删除,由调用方返 404 给浏览器(裂图),避免外部探测到对象存储仍可读
+ */
+ default FileDO selectByConfigIdAndPath(Long configId, String path) {
+ return selectOne(new LambdaQueryWrapperX()
+ .eq(FileDO::getConfigId, configId)
+ .eq(FileDO::getPath, path)
+ .last("LIMIT 1"));
+ }
+
}
diff --git a/rdms-system/rdms-system-boot/src/main/java/com/njcn/rdms/module/system/framework/file/core/utils/FileTypeUtils.java b/rdms-system/rdms-system-boot/src/main/java/com/njcn/rdms/module/system/framework/file/core/utils/FileTypeUtils.java
index fb604de..3f90ce0 100644
--- a/rdms-system/rdms-system-boot/src/main/java/com/njcn/rdms/module/system/framework/file/core/utils/FileTypeUtils.java
+++ b/rdms-system/rdms-system-boot/src/main/java/com/njcn/rdms/module/system/framework/file/core/utils/FileTypeUtils.java
@@ -2,6 +2,7 @@ package com.njcn.rdms.module.system.framework.file.core.utils;
import cn.hutool.core.io.IoUtil;
import cn.hutool.core.util.StrUtil;
+import cn.hutool.crypto.digest.DigestUtil;
import com.njcn.rdms.framework.common.util.http.HttpUtils;
import jakarta.servlet.http.HttpServletResponse;
import lombok.SneakyThrows;
@@ -86,6 +87,10 @@ public class FileTypeUtils {
if (isImage(mineType)) {
// 参见 https://github.com/YunaiV/ruoyi-vue-pro/issues/692 讨论
response.setHeader("Content-Disposition", "inline;filename=" + HttpUtils.encodeUtf8(filename));
+ // 图片匿名代理的主要消费方是富文本
,反复访问同一张图,下发缓存头让浏览器 max-age 内不再回源
+ // ETag 用字节 sha256,让 CDN / 网关层有条件做 304 命中
+ response.setHeader("Cache-Control", "public, max-age=86400");
+ response.setHeader("ETag", "\"" + DigestUtil.sha256Hex(content) + "\"");
} else {
response.setHeader("Content-Disposition", "attachment;filename=" + HttpUtils.encodeUtf8(filename));
}
diff --git a/rdms-system/rdms-system-boot/src/main/java/com/njcn/rdms/module/system/service/file/FileServiceImpl.java b/rdms-system/rdms-system-boot/src/main/java/com/njcn/rdms/module/system/service/file/FileServiceImpl.java
index b827f7c..02d17d3 100644
--- a/rdms-system/rdms-system-boot/src/main/java/com/njcn/rdms/module/system/service/file/FileServiceImpl.java
+++ b/rdms-system/rdms-system-boot/src/main/java/com/njcn/rdms/module/system/service/file/FileServiceImpl.java
@@ -207,6 +207,12 @@ public class FileServiceImpl implements FileService {
@Override
public byte[] getFileContent(Long configId, String path) throws Exception {
+ // 软删 / 不存在的记录直接拒绝
+ // 该方法被匿名图片代理 (FileController#getFileContent) 复用,必须先按 DB 校验存在性,
+ // 否则历史路径在 infra_file 被逻辑删除后,外部仍能匿名拉到对象存储字节
+ if (fileMapper.selectByConfigIdAndPath(configId, path) == null) {
+ return null;
+ }
FileClient client = fileConfigService.getFileClient(configId);
Assert.notNull(client, "客户端({}) 不能为空", configId);
return client.getContent(path);