Skip to content

Commit bc057da

Browse files
committed
refactor(system): 重构存储配置及文件上传相关代码
存储配置自动处理:domain 不能以 / 结尾,bucket 必须以 / 结尾 文件上传:path 自动处理 Closes #IC6V43
1 parent cd4adcf commit bc057da

File tree

9 files changed

+258
-216
lines changed

9 files changed

+258
-216
lines changed

continew-module-system/src/main/java/top/continew/admin/system/config/file/FileRecorderImpl.java

Lines changed: 13 additions & 47 deletions
Original file line numberDiff line numberDiff line change
@@ -16,26 +16,21 @@
1616

1717
package top.continew.admin.system.config.file;
1818

19-
import cn.hutool.core.date.DateUtil;
2019
import cn.hutool.core.util.ClassUtil;
21-
import cn.hutool.core.util.EscapeUtil;
2220
import cn.hutool.core.util.StrUtil;
2321
import cn.hutool.core.util.URLUtil;
24-
import cn.hutool.json.JSONUtil;
25-
import com.baomidou.mybatisplus.core.incrementer.IdentifierGenerator;
2622
import lombok.RequiredArgsConstructor;
2723
import lombok.extern.slf4j.Slf4j;
2824
import org.dromara.x.file.storage.core.FileInfo;
2925
import org.dromara.x.file.storage.core.recorder.FileRecorder;
3026
import org.dromara.x.file.storage.core.upload.FilePartInfo;
3127
import org.springframework.stereotype.Component;
32-
import top.continew.admin.common.context.UserContextHolder;
33-
import top.continew.admin.system.enums.FileTypeEnum;
3428
import top.continew.admin.system.mapper.FileMapper;
3529
import top.continew.admin.system.mapper.StorageMapper;
3630
import top.continew.admin.system.model.entity.FileDO;
3731
import top.continew.admin.system.model.entity.StorageDO;
3832
import top.continew.starter.core.constant.StringConstants;
33+
import top.continew.starter.core.util.URLUtils;
3934

4035
import java.util.Optional;
4136

@@ -52,53 +47,24 @@ public class FileRecorderImpl implements FileRecorder {
5247

5348
private final FileMapper fileMapper;
5449
private final StorageMapper storageMapper;
55-
private final IdentifierGenerator identifierGenerator;
5650

57-
/**
58-
* 文件信息存储
59-
*
60-
* @param fileInfo 文件信息对象
61-
* @return 是否保存成功
62-
*/
6351
@Override
6452
public boolean save(FileInfo fileInfo) {
65-
FileDO file = new FileDO();
66-
Number id = identifierGenerator.nextId(fileInfo);
67-
file.setId(id.longValue());
68-
fileInfo.setId(String.valueOf(id.longValue()));
69-
String originalFilename = EscapeUtil.unescape(fileInfo.getOriginalFilename());
70-
file.setName(StrUtil.contains(originalFilename, StringConstants.DOT)
71-
? StrUtil.subBefore(originalFilename, StringConstants.DOT, true)
72-
: originalFilename);
53+
// 保存文件信息
54+
FileDO file = new FileDO(fileInfo);
7355
StorageDO storage = (StorageDO)fileInfo.getAttr().get(ClassUtil.getClassName(StorageDO.class, false));
74-
String filePath = StrUtil.appendIfMissing(fileInfo.getPath(), StringConstants.SLASH);
75-
// 处理fileInfo中存储的地址
76-
fileInfo.setUrl(URLUtil.normalize(storage.getDomain() + filePath + fileInfo.getFilename()));
77-
fileInfo.setThUrl(URLUtil.normalize(storage.getDomain() + filePath + fileInfo.getThFilename()));
78-
file.setUrl(fileInfo.getUrl());
79-
file.setSize(fileInfo.getSize());
80-
String absPath = fileInfo.getPath();
81-
String tempAbsPath = absPath.length() > 1 ? StrUtil.removeSuffix(absPath, StringConstants.SLASH) : absPath;
82-
String[] pathArr = tempAbsPath.split(StringConstants.SLASH);
83-
if (pathArr.length > 1) {
84-
file.setParentPath(pathArr[pathArr.length - 1]);
85-
} else {
86-
file.setParentPath(StringConstants.SLASH);
87-
}
88-
file.setAbsPath(tempAbsPath);
89-
file.setExtension(fileInfo.getExt());
90-
file.setType(FileTypeEnum.getByExtension(file.getExtension()));
91-
file.setContentType(fileInfo.getContentType());
92-
file.setSha256(fileInfo.getHashInfo().getSha256());
93-
file.setMetadata(JSONUtil.toJsonStr(fileInfo.getMetadata()));
94-
file.setThumbnailUrl(fileInfo.getThUrl());
95-
file.setThumbnailSize(fileInfo.getThSize());
96-
file.setThumbnailMetadata(JSONUtil.toJsonStr(fileInfo.getThMetadata()));
9756
file.setStorageId(storage.getId());
98-
file.setCreateTime(DateUtil.toLocalDateTime(fileInfo.getCreateTime()));
99-
file.setUpdateUser(UserContextHolder.getUserId());
100-
file.setUpdateTime(file.getCreateTime());
10157
fileMapper.insert(file);
58+
// 方便文件上传完成后获取文件信息
59+
fileInfo.setId(String.valueOf(file.getId()));
60+
if (!URLUtils.isHttpUrl(fileInfo.getUrl())) {
61+
String prefix = StrUtil.appendIfMissing(storage.getDomain(), StringConstants.SLASH);
62+
String url = URLUtil.normalize(prefix + fileInfo.getUrl());
63+
fileInfo.setUrl(url);
64+
if (StrUtil.isNotBlank(fileInfo.getThUrl())) {
65+
fileInfo.setThUrl(URLUtil.normalize(prefix + fileInfo.getThUrl()));
66+
}
67+
}
10268
return true;
10369
}
10470

continew-module-system/src/main/java/top/continew/admin/system/config/file/FileStorageConfigLoader.java

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -24,8 +24,8 @@
2424
import org.springframework.boot.ApplicationRunner;
2525
import org.springframework.stereotype.Component;
2626
import top.continew.admin.common.enums.DisEnableStatusEnum;
27+
import top.continew.admin.system.model.entity.StorageDO;
2728
import top.continew.admin.system.model.query.StorageQuery;
28-
import top.continew.admin.system.model.req.StorageReq;
2929
import top.continew.admin.system.model.resp.StorageResp;
3030
import top.continew.admin.system.service.StorageService;
3131

@@ -52,6 +52,6 @@ public void run(ApplicationArguments args) {
5252
if (CollUtil.isEmpty(storageList)) {
5353
return;
5454
}
55-
storageList.forEach(s -> storageService.load(BeanUtil.copyProperties(s, StorageReq.class)));
55+
storageList.forEach(storage -> storageService.load(BeanUtil.copyProperties(storage, StorageDO.class)));
5656
}
5757
}

continew-module-system/src/main/java/top/continew/admin/system/enums/StorageTypeEnum.java

Lines changed: 43 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -16,9 +16,15 @@
1616

1717
package top.continew.admin.system.enums;
1818

19+
import cn.hutool.core.util.StrUtil;
1920
import lombok.Getter;
2021
import lombok.RequiredArgsConstructor;
22+
import top.continew.admin.system.model.req.StorageReq;
23+
import top.continew.admin.system.validation.ValidationGroup;
24+
import top.continew.starter.core.constant.StringConstants;
2125
import top.continew.starter.core.enums.BaseEnum;
26+
import top.continew.starter.core.util.URLUtils;
27+
import top.continew.starter.core.validation.ValidationUtils;
2228

2329
/**
2430
* 存储类型枚举
@@ -33,13 +39,48 @@ public enum StorageTypeEnum implements BaseEnum<Integer> {
3339
/**
3440
* 本地存储
3541
*/
36-
LOCAL(1, "本地存储"),
42+
LOCAL(1, "本地存储") {
43+
@Override
44+
public void validate(StorageReq req) {
45+
ValidationUtils.validate(req, ValidationGroup.Storage.Local.class);
46+
ValidationUtils.throwIf(!URLUtils.isHttpUrl(req.getDomain()), "访问路径格式不正确");
47+
}
48+
49+
@Override
50+
public void pretreatment(StorageReq req) {
51+
super.pretreatment(req);
52+
req.setBucketName(StrUtil.appendIfMissing(req.getBucketName()
53+
.replace(StringConstants.BACKSLASH, StringConstants.SLASH), StringConstants.SLASH));
54+
}
55+
},
3756

3857
/**
3958
* 对象存储
4059
*/
41-
OSS(2, "对象存储");
60+
OSS(2, "对象存储") {
61+
@Override
62+
public void validate(StorageReq req) {
63+
ValidationUtils.validate(req, ValidationGroup.Storage.OSS.class);
64+
ValidationUtils.throwIf(!URLUtils.isHttpUrl(req.getDomain()), "域名格式不正确");
65+
}
66+
};
4267

4368
private final Integer value;
4469
private final String description;
70+
71+
/**
72+
* 校验
73+
*
74+
* @param req 请求参数
75+
*/
76+
public abstract void validate(StorageReq req);
77+
78+
/**
79+
* 处理参数
80+
*
81+
* @param req 请求参数
82+
*/
83+
public void pretreatment(StorageReq req) {
84+
req.setDomain(StrUtil.removeSuffix(req.getDomain(), StringConstants.SLASH));
85+
}
4586
}

continew-module-system/src/main/java/top/continew/admin/system/model/entity/FileDO.java

Lines changed: 45 additions & 45 deletions
Original file line numberDiff line numberDiff line change
@@ -16,20 +16,21 @@
1616

1717
package top.continew.admin.system.model.entity;
1818

19+
import cn.hutool.core.date.DateUtil;
20+
import cn.hutool.core.io.file.FileNameUtil;
21+
import cn.hutool.core.util.EscapeUtil;
1922
import cn.hutool.core.util.StrUtil;
2023
import cn.hutool.json.JSONUtil;
2124
import com.baomidou.mybatisplus.annotation.TableName;
2225
import lombok.Data;
23-
import lombok.SneakyThrows;
26+
import lombok.NoArgsConstructor;
2427
import org.dromara.x.file.storage.core.FileInfo;
2528
import top.continew.admin.common.model.entity.BaseDO;
2629
import top.continew.admin.system.enums.FileTypeEnum;
27-
import top.continew.admin.system.enums.StorageTypeEnum;
2830
import top.continew.starter.core.constant.StringConstants;
2931
import top.continew.starter.core.util.StrUtils;
3032

3133
import java.io.Serial;
32-
import java.net.URL;
3334
import java.util.Map;
3435

3536
/**
@@ -39,6 +40,7 @@
3940
* @since 2023/12/23 10:38
4041
*/
4142
@Data
43+
@NoArgsConstructor
4244
@TableName("sys_file")
4345
public class FileDO extends BaseDO {
4446

@@ -116,65 +118,63 @@ public class FileDO extends BaseDO {
116118
private Long storageId;
117119

118120
/**
119-
* 转换为 X-File-Storage 文件信息对象
121+
* 基于 {@link FileInfo} 构建文件信息对象
120122
*
121-
* @param storageDO 存储桶信息
122-
* @return X-File-Storage 文件信息对象
123+
* @param fileInfo {@link FileInfo} 文件信息
124+
*/
125+
public FileDO(FileInfo fileInfo) {
126+
this.name = FileNameUtil.getPrefix(EscapeUtil.unescape(fileInfo.getOriginalFilename()));
127+
this.size = fileInfo.getSize();
128+
this.url = fileInfo.getUrl();
129+
this.absPath = StrUtil.isEmpty(fileInfo.getPath())
130+
? StringConstants.SLASH
131+
: StrUtil.prependIfMissing(fileInfo.getPath(), StringConstants.SLASH);
132+
String[] pathAttr = this.absPath.split(StringConstants.SLASH);
133+
this.parentPath = pathAttr.length > 1 ? pathAttr[pathAttr.length - 1] : StringConstants.SLASH;
134+
this.extension = fileInfo.getExt();
135+
this.contentType = fileInfo.getContentType();
136+
this.type = FileTypeEnum.getByExtension(this.extension);
137+
this.sha256 = fileInfo.getHashInfo().getSha256();
138+
this.metadata = JSONUtil.toJsonStr(fileInfo.getMetadata());
139+
this.thumbnailSize = fileInfo.getThSize();
140+
this.thumbnailUrl = fileInfo.getThUrl();
141+
this.thumbnailMetadata = JSONUtil.toJsonStr(fileInfo.getThMetadata());
142+
this.setCreateTime(DateUtil.toLocalDateTime(fileInfo.getCreateTime()));
143+
}
144+
145+
/**
146+
* 转换为 {@link FileInfo} 文件信息对象
147+
*
148+
* @param storage 存储配置信息
149+
* @return {@link FileInfo} 文件信息对象
123150
*/
124-
public FileInfo toFileInfo(StorageDO storageDO) {
151+
public FileInfo toFileInfo(StorageDO storage) {
125152
FileInfo fileInfo = new FileInfo();
126-
fileInfo.setUrl(this.url);
127-
fileInfo.setSize(this.size);
153+
fileInfo.setPlatform(storage.getCode());
128154
fileInfo.setFilename(StrUtil.contains(this.url, StringConstants.SLASH)
129155
? StrUtil.subAfter(this.url, StringConstants.SLASH, true)
130156
: this.url);
131157
fileInfo.setOriginalFilename(StrUtils
132158
.blankToDefault(this.extension, this.name, ex -> this.name + StringConstants.DOT + ex));
133159
fileInfo.setBasePath(StringConstants.EMPTY);
134-
// 优化 path 处理
135-
fileInfo.setPath(extractRelativePath(this.url, storageDO));
136-
160+
fileInfo.setSize(this.size);
161+
fileInfo.setUrl(this.url);
162+
fileInfo.setPath(StringConstants.SLASH.equals(this.absPath)
163+
? StringConstants.EMPTY
164+
: StrUtil.removePrefix(this.absPath, StringConstants.SLASH));
137165
fileInfo.setExt(this.extension);
138-
fileInfo.setPlatform(storageDO.getCode());
139-
fileInfo.setThUrl(this.thumbnailUrl);
166+
if (StrUtil.isNotBlank(this.metadata)) {
167+
fileInfo.setMetadata(JSONUtil.toBean(this.metadata, Map.class));
168+
}
169+
// 缩略图信息
140170
fileInfo.setThFilename(StrUtil.contains(this.thumbnailUrl, StringConstants.SLASH)
141171
? StrUtil.subAfter(this.thumbnailUrl, StringConstants.SLASH, true)
142172
: this.thumbnailUrl);
143173
fileInfo.setThSize(this.thumbnailSize);
174+
fileInfo.setThUrl(this.thumbnailUrl);
144175
if (StrUtil.isNotBlank(this.thumbnailMetadata)) {
145176
fileInfo.setThMetadata(JSONUtil.toBean(this.thumbnailMetadata, Map.class));
146177
}
147-
if (StrUtil.isNotBlank(this.metadata)) {
148-
fileInfo.setMetadata(JSONUtil.toBean(this.metadata, Map.class));
149-
}
150178
return fileInfo;
151179
}
152-
153-
/**
154-
* 将文件路径处理成资源路径
155-
* 例如:
156-
* http://domain.cn/bucketName/2024/11/27/6746ec3b2907f0de80afdd70.png => 2024/11/27/
157-
* http://bucketName.domain.cn/2024/11/27/6746ec3b2907f0de80afdd70.png => 2024/11/27/
158-
*
159-
* @param url 文件路径
160-
* @param storageDO 存储桶信息
161-
* @return
162-
*/
163-
@SneakyThrows
164-
private static String extractRelativePath(String url, StorageDO storageDO) {
165-
url = StrUtil.subBefore(url, StringConstants.SLASH, true) + StringConstants.SLASH;
166-
if (storageDO.getType().equals(StorageTypeEnum.LOCAL)) {
167-
return url;
168-
}
169-
// 提取 URL 中的路径部分
170-
String fullPath = new URL(url).getPath();
171-
// 移除开头的斜杠
172-
String relativePath = fullPath.startsWith(StringConstants.SLASH) ? fullPath.substring(1) : fullPath;
173-
// 如果路径以 bucketName 开头,则移除 bucketName 例如: bucketName/2024/11/27/ -> 2024/11/27/
174-
if (relativePath.startsWith(storageDO.getBucketName())) {
175-
return StrUtil.subAfter(relativePath, storageDO.getBucketName(), false);
176-
}
177-
return relativePath;
178-
}
179-
180180
}

continew-module-system/src/main/java/top/continew/admin/system/service/FileService.java

Lines changed: 17 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -89,30 +89,34 @@ default FileInfo upload(MultipartFile file, String path) throws IOException {
8989
*/
9090
FileStatisticsResp statistics();
9191

92-
/**
93-
* 获取默认文件路径
94-
*
95-
* @return 默认文件路径
96-
*/
97-
default String getDefaultFilePath() {
98-
LocalDate today = LocalDate.now();
99-
return today.getYear() + StringConstants.SLASH + today.getMonthValue() + StringConstants.SLASH + today
100-
.getDayOfMonth() + StringConstants.SLASH;
101-
}
102-
10392
/**
10493
* 检查文件是否存在
105-
*
94+
*
10695
* @param fileHash 文件 Hash
10796
* @return 响应参数
10897
*/
10998
FileResp check(String fileHash);
11099

111100
/**
112101
* 创建目录
113-
*
102+
*
114103
* @param req 请求参数
115104
* @return ID
116105
*/
117106
IdResp<Long> createDir(FileReq req);
107+
108+
/**
109+
* 获取默认文件路径
110+
*
111+
* <p>
112+
* 默认文件路径:yyyy/MM/dd/
113+
* </p>
114+
*
115+
* @return 默认文件路径
116+
*/
117+
default String getDefaultFilePath() {
118+
LocalDate today = LocalDate.now();
119+
return today.getYear() + StringConstants.SLASH + today.getMonthValue() + StringConstants.SLASH + today
120+
.getDayOfMonth() + StringConstants.SLASH;
121+
}
118122
}

0 commit comments

Comments
 (0)