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);