From 1b065b1755e4f40e27c6726028cc312116232d06 Mon Sep 17 00:00:00 2001 From: kiki1373639299 Date: Wed, 10 Sep 2025 17:50:03 +0800 Subject: [PATCH] =?UTF-8?q?fix(system/file):=20=E4=BF=AE=E5=A4=8D=E5=88=9B?= =?UTF-8?q?=E5=BB=BA=E4=B8=8A=E7=BA=A7=E6=96=87=E4=BB=B6=E5=A4=B9=E7=9A=84?= =?UTF-8?q?=E5=B9=B6=E5=8F=91=E9=97=AE=E9=A2=98?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: kiki1373639299 # message auto-generated for no-merge-commit merge: merge 2025-09-10 into dev fix(system): 修复创建上级文件夹的并发问题 Created-by: kiki1373639299 Commit-by: kiki1373639299 Merged-by: Charles_7c Description: ## PR 类型 - [ ] 新 feature - [X] Bug 修复 - [ ] 功能增强 - [ ] 文档变更 - [ ] 代码样式变更 - [ ] 重构 - [ ] 性能改进 - [ ] 单元测试 - [ ] CI/CD - [ ] 其他 ## PR 目的 修复创建上级文件夹的并发问题 ## 解决方案 使用redis做锁 ## PR 测试 ## Changelog | 模块 | Changelog | Related issues | |-----|-----------| -------------- | | continew-system | 为文件服务添加Redis分布式锁防止并发创建目录冲突,使用Lock:storageCode:parentPath作为锁键,使用try-with-resources确保锁的正确释放 | [#33](https://gitcode.com/continew/continew-admin/issues/33) | ## 其他信息 ## 提交前确认 - [X] PR 代码经过了完整测试,并且通过了代码规范检查 - [X] 已经完整填写 Changelog,并链接到了相关 issues - [X] PR 代码将要提交到 dev 分支 See merge request: continew/continew-admin!12 --- .../system/service/impl/FileServiceImpl.java | 67 ++++++++++--------- 1 file changed, 37 insertions(+), 30 deletions(-) diff --git a/continew-system/src/main/java/top/continew/admin/system/service/impl/FileServiceImpl.java b/continew-system/src/main/java/top/continew/admin/system/service/impl/FileServiceImpl.java index 81ce465a..08332c19 100644 --- a/continew-system/src/main/java/top/continew/admin/system/service/impl/FileServiceImpl.java +++ b/continew-system/src/main/java/top/continew/admin/system/service/impl/FileServiceImpl.java @@ -43,6 +43,7 @@ import top.continew.admin.system.model.resp.file.FileResp; import top.continew.admin.system.model.resp.file.FileStatisticsResp; import top.continew.admin.system.service.FileService; import top.continew.admin.system.service.StorageService; +import top.continew.starter.cache.redisson.util.RedisLockUtils; import top.continew.starter.core.constant.StringConstants; import top.continew.starter.core.util.StrUtils; import top.continew.starter.core.util.validation.CheckUtils; @@ -280,38 +281,44 @@ public class FileServiceImpl extends BaseServiceImpl user、avatar - String[] parentPathParts = StrUtil.split(parentPath, StringConstants.SLASH, false, true).toArray(String[]::new); - String lastPath = StringConstants.SLASH; - StringBuilder currentPathBuilder = new StringBuilder(); - for (int i = 0; i < parentPathParts.length; i++) { - String parentPathPart = parentPathParts[i]; - if (i > 0) { - lastPath = currentPathBuilder.toString(); + String lockKey = StrUtil.format("Lock:{}:{}", storage.getCode(), parentPath); + try (RedisLockUtils lock = RedisLockUtils.tryLock(lockKey)) { + if (!lock.isLocked()) { + return; // 获取锁失败,直接返回 } - // /user、/user/avatar - currentPathBuilder.append(StringConstants.SLASH).append(parentPathPart); - String currentPath = currentPathBuilder.toString(); - // 文件夹和文件存储引擎需要一致 - FileDO dirFile = baseMapper.lambdaQuery() - .eq(FileDO::getPath, currentPath) - .eq(FileDO::getType, FileTypeEnum.DIR) - .one(); - if (dirFile != null) { - CheckUtils.throwIfNotEqual(dirFile.getStorageId(), storage.getId(), "文件夹和上传文件存储引擎不一致"); - continue; + if (StrUtil.isBlank(parentPath) || StringConstants.SLASH.equals(parentPath)) { + return; + } + // user/avatar/ => user、avatar + String[] parentPathParts = StrUtil.split(parentPath, StringConstants.SLASH, false, true).toArray(String[]::new); + String lastPath = StringConstants.SLASH; + StringBuilder currentPathBuilder = new StringBuilder(); + for (int i = 0; i < parentPathParts.length; i++) { + String parentPathPart = parentPathParts[i]; + if (i > 0) { + lastPath = currentPathBuilder.toString(); + } + // /user、/user/avatar + currentPathBuilder.append(StringConstants.SLASH).append(parentPathPart); + String currentPath = currentPathBuilder.toString(); + // 文件夹和文件存储引擎需要一致 + FileDO dirFile = baseMapper.lambdaQuery() + .eq(FileDO::getPath, currentPath) + .eq(FileDO::getType, FileTypeEnum.DIR) + .one(); + if (dirFile != null) { + CheckUtils.throwIfNotEqual(dirFile.getStorageId(), storage.getId(), "文件夹和上传文件存储引擎不一致"); + continue; + } + FileDO file = new FileDO(); + file.setName(parentPathPart); + file.setOriginalName(parentPathPart); + file.setPath(currentPath); + file.setParentPath(lastPath); + file.setType(FileTypeEnum.DIR); + file.setStorageId(storage.getId()); + baseMapper.insert(file); } - FileDO file = new FileDO(); - file.setName(parentPathPart); - file.setOriginalName(parentPathPart); - file.setPath(currentPath); - file.setParentPath(lastPath); - file.setType(FileTypeEnum.DIR); - file.setStorageId(storage.getId()); - baseMapper.insert(file); } } } \ No newline at end of file