refactor(system/file): 文件添加路径和md5值 (#138)

This commit is contained in:
luoqiz
2025-03-08 17:56:51 +08:00
committed by GitHub
parent 715b950fa4
commit 42970d91eb
8 changed files with 147 additions and 24 deletions

View File

@@ -20,6 +20,7 @@ import cn.hutool.core.date.DateUtil;
import cn.hutool.core.util.ClassUtil; import cn.hutool.core.util.ClassUtil;
import cn.hutool.core.util.EscapeUtil; import cn.hutool.core.util.EscapeUtil;
import cn.hutool.core.util.StrUtil; import cn.hutool.core.util.StrUtil;
import cn.hutool.json.JSONUtil;
import com.baomidou.mybatisplus.core.incrementer.IdentifierGenerator; import com.baomidou.mybatisplus.core.incrementer.IdentifierGenerator;
import lombok.RequiredArgsConstructor; import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j; import lombok.extern.slf4j.Slf4j;
@@ -60,19 +61,34 @@ public class FileRecorderImpl implements FileRecorder {
fileInfo.setId(id.longValue() + ""); fileInfo.setId(id.longValue() + "");
String originalFilename = EscapeUtil.unescape(fileInfo.getOriginalFilename()); String originalFilename = EscapeUtil.unescape(fileInfo.getOriginalFilename());
file.setName(StrUtil.contains(originalFilename, StringConstants.DOT) file.setName(StrUtil.contains(originalFilename, StringConstants.DOT)
? StrUtil.subBefore(originalFilename, StringConstants.DOT, true) ? StrUtil.subBefore(originalFilename, StringConstants.DOT, true)
: originalFilename); : originalFilename);
file.setUrl(fileInfo.getUrl()); file.setUrl(fileInfo.getUrl());
file.setSize(fileInfo.getSize()); file.setSize(fileInfo.getSize());
file.setExtension(fileInfo.getExt()); file.setExtension(fileInfo.getExt());
file.setType(FileTypeEnum.getByExtension(file.getExtension())); file.setType(FileTypeEnum.getByExtension(file.getExtension()));
file.setThumbnailUrl(fileInfo.getThUrl()); file.setThumbnailUrl(fileInfo.getThUrl());
file.setThumbnailSize(fileInfo.getThSize()); file.setThumbnailSize(fileInfo.getThSize());
StorageDO storage = (StorageDO)fileInfo.getAttr().get(ClassUtil.getClassName(StorageDO.class, false)); StorageDO storage = (StorageDO) fileInfo.getAttr().get(ClassUtil.getClassName(StorageDO.class, false));
file.setStorageId(storage.getId()); file.setStorageId(storage.getId());
file.setCreateTime(DateUtil.toLocalDateTime(fileInfo.getCreateTime())); file.setCreateTime(DateUtil.toLocalDateTime(fileInfo.getCreateTime()));
file.setUpdateUser(UserContextHolder.getUserId()); file.setUpdateUser(UserContextHolder.getUserId());
file.setUpdateTime(file.getCreateTime()); file.setUpdateTime(file.getCreateTime());
String absPath = fileInfo.getPath();
if (absPath.endsWith("/")) {
String tempAbsPath = absPath.substring(0, absPath.length() - 1);
String[] pathArr = tempAbsPath.split(StringConstants.SLASH);
if (pathArr.length > 1) {
file.setParentPath(pathArr[pathArr.length - 1]);
} else {
file.setParentPath(StringConstants.SLASH);
}
}
file.setMd5(fileInfo.getHashInfo().getMd5());
file.setAbsPath(fileInfo.getPath());
file.setMetadata(JSONUtil.toJsonStr(fileInfo.getMetadata()));
file.setThumbnailMetadata(JSONUtil.toJsonStr(fileInfo.getThMetadata()));
file.setContentType(fileInfo.getContentType());
fileMapper.insert(file); fileMapper.insert(file);
return true; return true;
} }
@@ -117,8 +133,8 @@ public class FileRecorderImpl implements FileRecorder {
private FileDO getFileByUrl(String url) { private FileDO getFileByUrl(String url) {
Optional<FileDO> fileOptional = fileMapper.lambdaQuery().eq(FileDO::getUrl, url).oneOpt(); Optional<FileDO> fileOptional = fileMapper.lambdaQuery().eq(FileDO::getUrl, url).oneOpt();
return fileOptional.orElseGet(() -> fileMapper.lambdaQuery() return fileOptional.orElseGet(() -> fileMapper.lambdaQuery()
.likeLeft(FileDO::getUrl, StrUtil.subAfter(url, StringConstants.SLASH, true)) .likeLeft(FileDO::getUrl, StrUtil.subAfter(url, StringConstants.SLASH, true))
.oneOpt() .oneOpt()
.orElse(null)); .orElse(null));
} }
} }

View File

@@ -35,6 +35,11 @@ import java.util.List;
@RequiredArgsConstructor @RequiredArgsConstructor
public enum FileTypeEnum implements BaseEnum<Integer> { public enum FileTypeEnum implements BaseEnum<Integer> {
/**
* 目录
*/
DIR(0, "目录", Collections.emptyList()),
/** /**
* 其他 * 其他
*/ */
@@ -44,7 +49,7 @@ public enum FileTypeEnum implements BaseEnum<Integer> {
* 图片 * 图片
*/ */
IMAGE(2, "图片", List IMAGE(2, "图片", List
.of("jpg", "jpeg", "png", "gif", "bmp", "webp", "ico", "psd", "tiff", "dwg", "jxr", "apng", "xcf")), .of("jpg", "jpeg", "png", "gif", "bmp", "webp", "ico", "psd", "tiff", "dwg", "jxr", "apng", "xcf")),
/** /**
* 文档 * 文档
@@ -59,7 +64,8 @@ public enum FileTypeEnum implements BaseEnum<Integer> {
/** /**
* 音频 * 音频
*/ */
AUDIO(5, "音频", List.of("mp3", "flac", "wav", "ogg", "midi", "m4a", "aac", "amr", "ac3", "aiff")),; AUDIO(5, "音频", List.of("mp3", "flac", "wav", "ogg", "midi", "m4a", "aac", "amr", "ac3", "aiff")),
;
private final Integer value; private final Integer value;
private final String description; private final String description;
@@ -73,8 +79,8 @@ public enum FileTypeEnum implements BaseEnum<Integer> {
*/ */
public static FileTypeEnum getByExtension(String extension) { public static FileTypeEnum getByExtension(String extension) {
return Arrays.stream(FileTypeEnum.values()) return Arrays.stream(FileTypeEnum.values())
.filter(t -> t.getExtensions().contains(StrUtil.emptyIfNull(extension).toLowerCase())) .filter(t -> t.getExtensions().contains(StrUtil.emptyIfNull(extension).toLowerCase()))
.findFirst() .findFirst()
.orElse(FileTypeEnum.UNKNOWN); .orElse(FileTypeEnum.UNKNOWN);
} }
} }

View File

@@ -17,18 +17,20 @@
package top.continew.admin.system.model.entity; package top.continew.admin.system.model.entity;
import cn.hutool.core.util.StrUtil; import cn.hutool.core.util.StrUtil;
import cn.hutool.json.JSONUtil;
import com.baomidou.mybatisplus.annotation.TableName; import com.baomidou.mybatisplus.annotation.TableName;
import lombok.Data; import lombok.Data;
import lombok.SneakyThrows; import lombok.SneakyThrows;
import org.dromara.x.file.storage.core.FileInfo; import org.dromara.x.file.storage.core.FileInfo;
import top.continew.admin.common.model.entity.BaseDO;
import top.continew.admin.system.enums.FileTypeEnum; import top.continew.admin.system.enums.FileTypeEnum;
import top.continew.admin.system.enums.StorageTypeEnum; import top.continew.admin.system.enums.StorageTypeEnum;
import top.continew.starter.core.constant.StringConstants; import top.continew.starter.core.constant.StringConstants;
import top.continew.starter.core.util.StrUtils; import top.continew.starter.core.util.StrUtils;
import top.continew.admin.common.model.entity.BaseDO;
import java.io.Serial; import java.io.Serial;
import java.net.URL; import java.net.URL;
import java.util.Map;
/** /**
* 文件实体 * 文件实体
@@ -58,6 +60,31 @@ public class FileDO extends BaseDO {
*/ */
private String url; private String url;
/**
* 上级目录
*/
private String parentPath;
/**
* 绝对路径
*/
private String absPath;
/**
* 文件元数据
*/
private String metadata;
/**
* 文件类型
*/
private String contentType;
/**
* 文件md5值
*/
private String md5;
/** /**
* 扩展名 * 扩展名
*/ */
@@ -78,6 +105,11 @@ public class FileDO extends BaseDO {
*/ */
private String thumbnailUrl; private String thumbnailUrl;
/**
* 文件元数据
*/
private String thumbnailMetadata;
/** /**
* 存储 ID * 存储 ID
*/ */
@@ -94,10 +126,10 @@ public class FileDO extends BaseDO {
fileInfo.setUrl(this.url); fileInfo.setUrl(this.url);
fileInfo.setSize(this.size); fileInfo.setSize(this.size);
fileInfo.setFilename(StrUtil.contains(this.url, StringConstants.SLASH) fileInfo.setFilename(StrUtil.contains(this.url, StringConstants.SLASH)
? StrUtil.subAfter(this.url, StringConstants.SLASH, true) ? StrUtil.subAfter(this.url, StringConstants.SLASH, true)
: this.url); : this.url);
fileInfo.setOriginalFilename(StrUtils fileInfo.setOriginalFilename(StrUtils
.blankToDefault(this.extension, this.name, ex -> this.name + StringConstants.DOT + ex)); .blankToDefault(this.extension, this.name, ex -> this.name + StringConstants.DOT + ex));
fileInfo.setBasePath(StringConstants.EMPTY); fileInfo.setBasePath(StringConstants.EMPTY);
// 优化 path 处理 // 优化 path 处理
fileInfo.setPath(extractRelativePath(this.url, storageDO)); fileInfo.setPath(extractRelativePath(this.url, storageDO));
@@ -106,9 +138,15 @@ public class FileDO extends BaseDO {
fileInfo.setPlatform(storageDO.getCode()); fileInfo.setPlatform(storageDO.getCode());
fileInfo.setThUrl(this.thumbnailUrl); fileInfo.setThUrl(this.thumbnailUrl);
fileInfo.setThFilename(StrUtil.contains(this.thumbnailUrl, StringConstants.SLASH) fileInfo.setThFilename(StrUtil.contains(this.thumbnailUrl, StringConstants.SLASH)
? StrUtil.subAfter(this.thumbnailUrl, StringConstants.SLASH, true) ? StrUtil.subAfter(this.thumbnailUrl, StringConstants.SLASH, true)
: this.thumbnailUrl); : this.thumbnailUrl);
fileInfo.setThSize(this.thumbnailSize); fileInfo.setThSize(this.thumbnailSize);
if (StrUtil.isNotBlank(this.thumbnailMetadata)) {
fileInfo.setThMetadata(JSONUtil.toBean(this.thumbnailMetadata, Map.class));
}
if (StrUtil.isNotBlank(this.metadata)) {
fileInfo.setMetadata(JSONUtil.toBean(this.metadata, Map.class));
}
return fileInfo; return fileInfo;
} }

View File

@@ -45,6 +45,13 @@ public class FileQuery implements Serializable {
@Query(type = QueryType.LIKE) @Query(type = QueryType.LIKE)
private String name; private String name;
/**
* 绝对路径
*/
@Schema(description = "绝对路径", example = "/2025")
@Query(type = QueryType.EQ)
private String absPath;
/** /**
* 类型 * 类型
*/ */

View File

@@ -56,6 +56,36 @@ public class FileResp extends BaseDetailResp {
@Schema(description = "URL", example = "https://examplebucket.oss-cn-hangzhou.aliyuncs.com/example/example.jpg") @Schema(description = "URL", example = "https://examplebucket.oss-cn-hangzhou.aliyuncs.com/example/example.jpg")
private String url; private String url;
/**
* 上级目录
*/
@Schema(description = "上级目录", example = "25")
private String parentPath;
/**
* 绝对路径
*/
@Schema(description = "绝对路径", example = "/2025/2/25")
private String absPath;
/**
* 文件元数据
*/
@Schema(description = "文件元数据", example = "{width:1024,height:1024}")
private String metadata;
/**
* 文件md5值
*/
@Schema(description = "文件md5值", example = "abcdefghijklmnopqrstuvwxyz0123456789")
private String md5;
/**
* 文件类型
*/
@Schema(description = "文件md5值", example = "abcdefghijklmnopqrstuvwxyz0123456789")
private String contentType;
/** /**
* 扩展名 * 扩展名
*/ */
@@ -80,6 +110,12 @@ public class FileResp extends BaseDetailResp {
@Schema(description = "缩略图 URL", example = "https://examplebucket.oss-cn-hangzhou.aliyuncs.com/example/example.jpg.min.jpg") @Schema(description = "缩略图 URL", example = "https://examplebucket.oss-cn-hangzhou.aliyuncs.com/example/example.jpg.min.jpg")
private String thumbnailUrl; private String thumbnailUrl;
/**
* 缩略图文件元数据
*/
@Schema(description = "缩略图文件元数据", example = "{width:100,height:100}")
private String thumbnailMetadata;
/** /**
* 存储 ID * 存储 ID
*/ */

View File

@@ -91,11 +91,12 @@ public class FileServiceImpl extends BaseServiceImpl<FileMapper, FileDO, FileRes
} }
LocalDate today = LocalDate.now(); LocalDate today = LocalDate.now();
String path = today.getYear() + StringConstants.SLASH + today.getMonthValue() + StringConstants.SLASH + today String path = today.getYear() + StringConstants.SLASH + today.getMonthValue() + StringConstants.SLASH + today
.getDayOfMonth() + StringConstants.SLASH; .getDayOfMonth() + StringConstants.SLASH;
UploadPretreatment uploadPretreatment = fileStorageService.of(file) UploadPretreatment uploadPretreatment = fileStorageService.of(file)
.setPlatform(storage.getCode()) .setPlatform(storage.getCode())
.putAttr(ClassUtil.getClassName(StorageDO.class, false), storage) .setHashCalculatorMd5(true)
.setPath(path); .putAttr(ClassUtil.getClassName(StorageDO.class, false), storage)
.setPath(path);
// 图片文件生成缩略图 // 图片文件生成缩略图
if (FileTypeEnum.IMAGE.getExtensions().contains(FileNameUtil.extName(file.getOriginalFilename()))) { if (FileTypeEnum.IMAGE.getExtensions().contains(FileNameUtil.extName(file.getOriginalFilename()))) {
uploadPretreatment.thumbnail(img -> img.size(100, 100)); uploadPretreatment.thumbnail(img -> img.size(100, 100));
@@ -154,7 +155,7 @@ public class FileServiceImpl extends BaseServiceImpl<FileMapper, FileDO, FileRes
String url = URLUtil.normalize(prefix + fileResp.getUrl()); String url = URLUtil.normalize(prefix + fileResp.getUrl());
fileResp.setUrl(url); fileResp.setUrl(url);
String thumbnailUrl = StrUtils.blankToDefault(fileResp.getThumbnailUrl(), url, thUrl -> URLUtil String thumbnailUrl = StrUtils.blankToDefault(fileResp.getThumbnailUrl(), url, thUrl -> URLUtil
.normalize(prefix + thUrl)); .normalize(prefix + thUrl));
fileResp.setThumbnailUrl(thumbnailUrl); fileResp.setThumbnailUrl(thumbnailUrl);
fileResp.setStorageName("%s (%s)".formatted(storage.getName(), storage.getCode())); fileResp.setStorageName("%s (%s)".formatted(storage.getName(), storage.getCode()));
} }

View File

@@ -281,10 +281,16 @@ CREATE TABLE IF NOT EXISTS `sys_file` (
`name` varchar(255) NOT NULL COMMENT '名称', `name` varchar(255) NOT NULL COMMENT '名称',
`size` bigint(20) NOT NULL COMMENT '大小(字节)', `size` bigint(20) NOT NULL COMMENT '大小(字节)',
`url` varchar(512) NOT NULL COMMENT 'URL', `url` varchar(512) NOT NULL COMMENT 'URL',
`parent_path` varchar(512) NOT NULL COMMENT '上级目录',
`abs_path` varchar(1024) NOT NULL COMMENT '绝对路径',
`extension` varchar(100) DEFAULT NULL COMMENT '扩展名', `extension` varchar(100) DEFAULT NULL COMMENT '扩展名',
`metadata` TEXT NOT NULL COMMENT '元数据',
`content_type` varchar(64) NOT NULL COMMENT '文件类型',
`md5` varchar(128) NOT NULL COMMENT '文件md5值',
`thumbnail_size` bigint(20) DEFAULT NULL COMMENT '缩略图大小(字节)', `thumbnail_size` bigint(20) DEFAULT NULL COMMENT '缩略图大小(字节)',
`thumbnail_url` varchar(512) DEFAULT NULL COMMENT '缩略图URL', `thumbnail_url` varchar(512) DEFAULT NULL COMMENT '缩略图URL',
`type` tinyint(1) UNSIGNED NOT NULL DEFAULT 1 COMMENT '类型1其他2图片3文档4视频5音频', `thumbnail_metadata` TEXT COMMENT '元数据',
`type` tinyint(1) UNSIGNED NOT NULL DEFAULT 1 COMMENT '类型0: 目录1其他2图片3文档4视频5音频',
`storage_id` bigint(20) NOT NULL COMMENT '存储ID', `storage_id` bigint(20) NOT NULL COMMENT '存储ID',
`create_user` bigint(20) NOT NULL COMMENT '创建人', `create_user` bigint(20) NOT NULL COMMENT '创建人',
`create_time` datetime NOT NULL COMMENT '创建时间', `create_time` datetime NOT NULL COMMENT '创建时间',
@@ -293,6 +299,7 @@ CREATE TABLE IF NOT EXISTS `sys_file` (
PRIMARY KEY (`id`), PRIMARY KEY (`id`),
INDEX `idx_url`(`url`), INDEX `idx_url`(`url`),
INDEX `idx_type`(`type`), INDEX `idx_type`(`type`),
INDEX `idx_sys_file_md5`(`md5`),
INDEX `idx_create_user`(`create_user`), INDEX `idx_create_user`(`create_user`),
INDEX `idx_update_user`(`update_user`) INDEX `idx_update_user`(`update_user`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='文件表'; ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='文件表';

View File

@@ -467,9 +467,15 @@ CREATE TABLE IF NOT EXISTS "sys_file" (
"name" varchar(255) NOT NULL, "name" varchar(255) NOT NULL,
"size" int8 NOT NULL, "size" int8 NOT NULL,
"url" varchar(512) NOT NULL, "url" varchar(512) NOT NULL,
"parent_path" varchar(512) NOT NULL default '/',
"abs_path" varchar(512) NOT NULL,
"extension" varchar(100) DEFAULT NULL, "extension" varchar(100) DEFAULT NULL,
"metadata" varchar(10000) NOT NULL DEFAULT '',
"content_type" varchar(64) NOT NULL,
"md5" varchar(128) NOT NULL DEFAULT '',
"thumbnail_size" int8 DEFAULT NULL, "thumbnail_size" int8 DEFAULT NULL,
"thumbnail_url" varchar(512) DEFAULT NULL, "thumbnail_url" varchar(512) DEFAULT NULL,
"thumbnail_metadata" varchar(10000) DEFAULT NULL,
"type" int2 NOT NULL DEFAULT 1, "type" int2 NOT NULL DEFAULT 1,
"storage_id" int8 NOT NULL, "storage_id" int8 NOT NULL,
"create_user" int8 NOT NULL, "create_user" int8 NOT NULL,
@@ -482,14 +488,20 @@ CREATE INDEX "idx_file_url" ON "sys_file" ("url");
CREATE INDEX "idx_file_type" ON "sys_file" ("type"); CREATE INDEX "idx_file_type" ON "sys_file" ("type");
CREATE INDEX "idx_file_create_user" ON "sys_file" ("create_user"); CREATE INDEX "idx_file_create_user" ON "sys_file" ("create_user");
CREATE INDEX "idx_file_update_user" ON "sys_file" ("update_user"); CREATE INDEX "idx_file_update_user" ON "sys_file" ("update_user");
CREATE INDEX "idx_sys_file_md5" ON "sys_file" ("md5");
COMMENT ON COLUMN "sys_file"."id" IS 'ID'; COMMENT ON COLUMN "sys_file"."id" IS 'ID';
COMMENT ON COLUMN "sys_file"."name" IS '名称'; COMMENT ON COLUMN "sys_file"."name" IS '名称';
COMMENT ON COLUMN "sys_file"."size" IS '大小(字节)'; COMMENT ON COLUMN "sys_file"."size" IS '大小(字节)';
COMMENT ON COLUMN "sys_file"."url" IS 'URL'; COMMENT ON COLUMN "sys_file"."url" IS 'URL';
COMMENT ON COLUMN "sys_file"."parent_path" IS '上级目录';
COMMENT ON COLUMN "sys_file"."abs_path" IS '绝对路径';
COMMENT ON COLUMN "sys_file"."extension" IS '扩展名'; COMMENT ON COLUMN "sys_file"."extension" IS '扩展名';
COMMENT ON COLUMN "sys_file"."thumbnail_size" IS '缩略图大小(字节)'; COMMENT ON COLUMN "sys_file"."thumbnail_size" IS '缩略图大小(字节)';
COMMENT ON COLUMN "sys_file"."thumbnail_url" IS '缩略图URL'; COMMENT ON COLUMN "sys_file"."thumbnail_url" IS '缩略图URL';
COMMENT ON COLUMN "sys_file"."type" IS '类型1其他2图片3文档4视频5音频'; COMMENT ON COLUMN "sys_file"."type" IS '类型(0: 目录;1其他2图片3文档4视频5音频';
COMMENT ON COLUMN "sys_file"."metadata" IS '元数据';
COMMENT ON COLUMN "sys_file"."content_type" IS '文件类型';
COMMENT ON COLUMN "sys_file"."md5" IS '文件md5值';
COMMENT ON COLUMN "sys_file"."storage_id" IS '存储ID'; COMMENT ON COLUMN "sys_file"."storage_id" IS '存储ID';
COMMENT ON COLUMN "sys_file"."create_user" IS '创建人'; COMMENT ON COLUMN "sys_file"."create_user" IS '创建人';
COMMENT ON COLUMN "sys_file"."create_time" IS '创建时间'; COMMENT ON COLUMN "sys_file"."create_time" IS '创建时间';