Repository navigation
Conversation
|
✅ OpenCodeReview: Review complete: 0 finding(s) across 10 selected item(s). |
luoqiz
force-pushed
the
feat/file-uuid-name
branch
from
October 21, 2026 00:51
1f56b3e to
f87b3e5
Compare
luoqiz
force-pushed
the
feat/file-uuid-name
branch
from
October 21, 2026 09:31
aa342b1 to
93ba86f
Compare
- FileNameGenerator 重写为「32 位唯一 ID + 原扩展名」,删除同名探测与序号重命名 - 单文件与分片上传的落盘名统一走 generateStoredName;分片上传新增 MultipartOriginalNameStore 暂存原始文件名,完成时回填展示名 - 分片初始化改用 FileTypeEnum.getByExtension 校验扩展名,顺带消除 toLowerCase 的地区问题与无扩展名 NPE - 文件记录、上传响应与文档同步区分存储名与原始名 - 补充 FileNameGenerator、MultipartOriginalNameStore、FileRecorderImpl 回填测试 Assisted-by: DeepSeek Harness
- beforeUpdate 对非 null 的 parentPath 归一后与原记录比较,目录实际变化直接拒绝,避免只改记录路径导致实体文件失联 - 将 normalizeParentPath / isSameParentPath 抽到 StoragePathValidator,供服务与测试复用 - validateName 增加 255 字符上限,与 sys_file.name / original_name 列宽对齐,避免落库时 DataTooLong 留下孤儿会话 - 补充名称长度、非法名称/路径、路径归一化与同目录判定测试 Assisted-by: DeepSeek Harness
- 父 POM 2.17.0 → 3.0.0-SNAPSHOT,同步 Spring Boot 4.1 基线 - ServerProperties 迁移至 org.springframework.boot.web.server.autoconfigure - Checkstyle 14.3 的 NonEmptyAtclauseDescription token 由 PARAM_LITERAL 改为 PARAM_BLOCK_TAG Assisted-by: DeepSeek Harness
- MultipartUploadCreateResp 增加 originalFileName,save/get 双向拷贝,保证 Redis 会话往返不丢 - init 向 starter 传入 originalFileName,复用判定与清理键都按原始名 + 上级目录计算 - 完成分片由 starter 从会话回填原始名,删除 MultipartOriginalNameStore、常量、记录器手工回填与相关测试 - 保留原始名为空时沿用已有展示名的兜底 - 补充会话 JSON 往返与展示名兜底测试 Assisted-by: DeepSeek Harness
luoqiz
force-pushed
the
feat/file-uuid-name
branch
from
October 23, 2026 00:32
93ba86f to
5569a39
Compare
Contributor
Author
CI 失败原因:依赖尚未发布的
|
| 提交 | 内容 | 与本次失败的关联 |
|---|---|---|
0fbe14a9 |
build: 升级 starter 至 3.0.0-SNAPSHOT(Spring Boot 4.1) | parent POM 由 2.17.0 改为 3.0.0-SNAPSHOT |
5569a39c |
fix(system): 分片上传改用 starter 会话原始名并移除 Redis 暂存 | 调用 continew-starter#28 新增的 MultipartUploadInitReq#originalFileName |
continew-starter:3.0.0-SNAPSHOT 目前尚未发布到公共仓库,CI 无法解析 parent POM,因此构建必然失败,重跑不会改变结果。
这两个提交互相绑定:5569a39c 用到的 originalFileName 只在 3.0.0 中提供,所以不能只回退 0fbe14a9。
顺带同步一处正文的过期描述:下方「已知限制」中写的“待新版本发布后可改为向启动器传入原始文件名并移除本仓库的 Redis 暂存”,已由 5569a39c 落地,只待版本发布。
解除条件与备选方案
- 待 starter 3.0.0 正式发布后,把 parent 切换到正式版即可转绿,本 PR 无需改动。
- 若希望不等待版本发布就先合并:可将分支回退到
55a12d3e,仅保留两个与 starter 版本无关的特性提交(落盘名改用唯一 ID、拒绝变更上级目录),CI 即可通过;分片上传改造与 starter 升级待 3.0.0 发布后另行提交。
需要按哪种方式处理,请告知。
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
变更类型
破坏性变更
变更目的
当前文件的落盘名直接使用用户上传时的原始文件名(重名时
generateUniqueName追加(1)序号),由此带来三类问题:sys_file的idx_file_path是普通索引而非唯一索引,FileRecorderImpl#save也没有唯一性校验,落盘时REPLACE_EXISTING会静默覆盖。并发上传同名文件、或回收站还原到已存在同名的位置时,两条记录会指向同一个物理对象,内容互相覆盖且无法恢复。generateUniqueName先查询再决定名字,查询与写入之间没有任何锁,并发下两个请求仍可能算出同一个名字。initMultipartUpload在生成唯一名前先做了一次 exists 查重并直接抛「文件名已存在」,导致后面的去重逻辑成为死代码——同名文件连分片上传都不允许。把落盘名换成与业务无关的唯一 ID + 原扩展名后,上述三个问题都在源头消失:物理名天然唯一,不需要查询、不需要重试、不存在覆盖;原始文件名退化为纯展示信息。
解决方案
1. 落盘名 = 唯一 ID + 原扩展名
FileNameGenerator重写为generateStoredName(String originalFileName):IdUtil.fastSimpleUUID()(32 位十六进制)+ 原扩展名(保留大小写,沿用FileNameUtil.extName行为),无扩展名则不加点。零查询、零重试。原generateUniqueName/parseFileName/buildFileNameWithCounter/existsByName/selectNamesByParentPath一并删除(全仓仅两处调用点、无其他引用)。2. 单文件上传(
FileServiceImpl#doUpload)落盘名改调
generateStoredName;originalName仍取用户上传的文件名(starter 用Part#getOriginalFilename填充,不受.fileName()影响)。日志改打原始文件名,避免日志里全是十六进制 ID。3. 分片上传(
MultipartUploadServiceImpl)starter 在完成分片时用会话里的
fileName(此时已是存储名)回填originalName,因此原始文件名必须在init时单独留存、在complete时回填:MultipartOriginalNameStore:init 成功后以setIfAbsent写入multipart:original-name:{uploadId}(MD5 复用同一会话时保留首个名字),TTL 与分片会话一致;写入失败只 warning 不阻断 init(降级为展示名回落存储名)。complete在调用 starter 之前把名字取到局部变量(starter 返回后会清理 session key),兜底补录分支用它覆盖originalName。FileRecorderImpl#update(分片唯一的落库点)在构造FileDO后、落库前回填;更新已有记录时若暂存缺失,沿用原记录的originalName,避免用存储名覆盖。deleteMultipartSession同步清理暂存键。4. 移除同名探测
单文件与分片上传都不再按名字查重,同一目录允许同名文件(展示名可以重复,物理名不会)。
5. 禁止通过修改接口移动文件
beforeUpdate只在上级目录实际发生变化时拒绝:parentPath为null表示未提交该字段(仅重命名),不参与校验;任何非null值(含空串)都先规范化为根目录/(末尾斜杠一并去除,user/、/user统一视为同一目录(写回时补齐前导/)),再与原记录比较,目录未变化则放行并把规范化后的值写回请求。这样既不会误伤「整对象回传」式的重命名,也堵住了用空串把记录路径改到根目录、实体文件却不移动的漏洞(CRUD 更新用的是ignoreNullValue,空串不会被跳过)。根本原因是不搬实体文件、只改记录中的path会让记录指向不存在的对象;FileReq只有originalName/parentPath两个字段,重命名不受影响。6. 顺带修正分片扩展名校验
initMultipartUpload改用FileTypeEnum.getByExtension(与doUpload在 #235 之后的形态一致),避免toLowerCase()在 tr 等地区把GIF判成gıf而误拒,也避免文件无扩展名时 NPE。7. 名称长度上限收到入口
StoragePathValidator#validateName增加 255 字符上限,与sys_file.name/sys_file.original_name的列长对齐。此前单文件上传的MultipartFile#getOriginalFilename()与分片上传的fileName都没有长度校验,超长名称会在落库时才抛 DataTooLong;此时存储实体已写入、启动器的会话清理不会执行,会留下无法重试的孤儿会话。validateName是两个上传入口共用的校验点,一处生效即全覆盖。行为变更(已知、已评估)
originalNameFileResp#nameoriginalName(前端已全部使用originalName,无需改动)fileNamePUT /system/file/{id}parentPath会被拒绝;携带原目录的请求放行(移动功能本就未开放),user/、/user统一视为同一目录(写回时补齐前导/)回滚只需回退代码:新命名与旧命名文件的读取路径完全一致,不存在数据形态依赖。
已知限制(不影响本 PR 合并)
setIfAbsent);后一次上传会落到首次的目录。根因在启动器的复用判定只校验platform/bucket,已另提 continew-starter 修复(会话携带originalFileName,并把原始文件名与上级目录纳入复用判定);待新版本发布后可改为向启动器传入原始文件名并移除本仓库的 Redis 暂存。sys_file.path/parent_path为 varchar(512),parentPath组合长度仍未设上限,超深目录 + 长名称理论上仍会触发同类 DataTooLong,建议后续单独处理。测试情况
新增 4 个测试类、20 个用例,全部通过:
FileNameGeneratorTesta.tar.gz取.gz、无扩展名、null/空串MultipartOriginalNameStoreTestFileRecorderImplTestoriginalName;暂存缺失时保留存储名或已有展示名,不写入空值StoragePathValidatorTestuser/与/user归一化、同目录判定./mvnw verify:11 个模块全部 SUCCESS,Spotless / Checkstyle / SpotBugs 均 0 违规continew-admin-ui未改动:列表、网格、回收站、搜索、重命名、下载名均使用originalName;唯一可见面是详情弹窗的「路径」行会显示存储名Changelog
FileTypeEnum.getByExtension,修复大小写地区敏感与空扩展名 NPE提交前确认
./mvnw verify四道静态门禁与单元测试全部通过(被 Spotless 拦截时使用-Pformat修复)Assisted-by: <智能体>标记(两个提交均已追加Assisted-by: DeepSeek Harness)