mirror of
https://github.com/continew-org/continew-admin.git
synced 2025-10-20 07:01:31 +08:00
fix(system/file): 修复创建上级文件夹的并发问题
Co-authored-by: kiki1373639299<zkai0106@163.com> # 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!在提交之前,请务必确保您 PR 的代码经过了完整测试,并且通过了代码规范检查。 --> <!-- 在 [] 中输入 x 来勾选) --> ## PR 类型 <!-- 您的 PR 引入了哪种类型的变更? --> <!-- 只支持选择一种类型,如果有多种类型,可以在更新日志中增加 “类型” 列。 --> - [ ] 新 feature - [X] Bug 修复 - [ ] 功能增强 - [ ] 文档变更 - [ ] 代码样式变更 - [ ] 重构 - [ ] 性能改进 - [ ] 单元测试 - [ ] CI/CD - [ ] 其他 ## PR 目的 <!-- 描述一下您的 PR 解决了什么问题。如果可以,请链接到相关 issues。 --> 修复创建上级文件夹的并发问题 ## 解决方案 <!-- 详细描述您是如何解决的问题 --> 使用redis做锁 ## PR 测试 <!-- 如果可以,请为您的 PR 添加或更新单元测试。 --> <!-- 请描述一下您是如何测试 PR 的。例如:创建/更新单元测试或添加相关的截图。 --> ## Changelog | 模块 | Changelog | Related issues | |-----|-----------| -------------- | | continew-system | 为文件服务添加Redis分布式锁防止并发创建目录冲突,使用Lock:storageCode:parentPath作为锁键,使用try-with-resources确保锁的正确释放 | [#33](https://gitcode.com/continew/continew-admin/issues/33) | <!-- 如果有多种类型的变更,可以在变更日志表中增加 “类型” 列,该列的值与上方 “PR 类型” 相同。 --> <!-- Related issues 格式为 Closes #<issue号>,或者 Fixes #<issue号>,或者 Resolves #<issue号>。 --> ## 其他信息 <!-- 请描述一下还有哪些注意事项。例如:如果引入了一个不向下兼容的变更,请描述其影响。 --> ## 提交前确认 - [X] PR 代码经过了完整测试,并且通过了代码规范检查 - [X] 已经完整填写 Changelog,并链接到了相关 issues - [X] PR 代码将要提交到 dev 分支 See merge request: continew/continew-admin!12
This commit is contained in:
@@ -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<FileMapper, FileDO, FileRes
|
||||
*/
|
||||
@Override
|
||||
public void createParentDir(String parentPath, StorageDO storage) {
|
||||
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();
|
||||
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);
|
||||
}
|
||||
}
|
||||
}
|
Reference in New Issue
Block a user