From 87670d18fdf8c96f5a61861b41381d8adf9cecc7 Mon Sep 17 00:00:00 2001 From: YunaiV Date: Wed, 16 Mar 2022 23:31:26 +0800 Subject: [PATCH] =?UTF-8?q?=E5=AE=8C=E6=88=90=E6=96=B0=20File=20=E7=9A=84?= =?UTF-8?q?=E5=8A=9F=E8=83=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../file/core/client/AbstractFileClient.java | 4 +- .../file/core/client/db/DBFileClient.java | 15 +++- .../client/db/DBFileContentFrameworkDAO.java | 20 +++++ .../controller/admin/file/FileController.java | 24 ++--- .../controller/admin/file/vo/FileRespVO.java | 22 ----- .../file/vo/{ => file}/FilePageReqVO.java | 4 +- .../admin/file/vo/file/FileRespVO.java | 31 +++++++ .../infra/convert/file/FileConvert.java | 2 +- .../infra/dal/dataobject/file/FileDO.java | 18 ++-- .../dal/mysql/file/FileContentDAOImpl.java | 41 +++++++++ .../dal/mysql/file/FileContentMapper.java | 9 ++ .../infra/dal/mysql/file/FileMapper.java | 19 +--- .../file/config/FileConfiguration.java | 12 --- .../framework/file/config/FileProperties.java | 22 ----- .../infra/framework/file/package-info.java | 16 ---- .../config/SecurityConfiguration.java | 2 +- .../infra/service/file/FileConfigService.java | 16 ++++ .../service/file/FileConfigServiceImpl.java | 5 ++ .../infra/service/file/FileService.java | 11 +-- .../infra/service/file/FileServiceImpl.java | 52 +++++++---- .../infra/service/file/FileServiceTest.java | 87 +++++++++++-------- .../src/test/resources/sql/create_tables.sql | 7 +- .../src/main/resources/application-dev.yaml | 2 - .../src/main/resources/application-local.yaml | 2 - .../src/main/resources/application.yaml | 2 +- yudao-ui-admin/src/views/infra/file/index.vue | 37 +++++--- 26 files changed, 277 insertions(+), 205 deletions(-) delete mode 100644 yudao-module-infra/yudao-module-infra-impl/src/main/java/cn/iocoder/yudao/module/infra/controller/admin/file/vo/FileRespVO.java rename yudao-module-infra/yudao-module-infra-impl/src/main/java/cn/iocoder/yudao/module/infra/controller/admin/file/vo/{ => file}/FilePageReqVO.java (92%) create mode 100644 yudao-module-infra/yudao-module-infra-impl/src/main/java/cn/iocoder/yudao/module/infra/controller/admin/file/vo/file/FileRespVO.java create mode 100644 yudao-module-infra/yudao-module-infra-impl/src/main/java/cn/iocoder/yudao/module/infra/dal/mysql/file/FileContentDAOImpl.java create mode 100644 yudao-module-infra/yudao-module-infra-impl/src/main/java/cn/iocoder/yudao/module/infra/dal/mysql/file/FileContentMapper.java delete mode 100644 yudao-module-infra/yudao-module-infra-impl/src/main/java/cn/iocoder/yudao/module/infra/framework/file/config/FileConfiguration.java delete mode 100644 yudao-module-infra/yudao-module-infra-impl/src/main/java/cn/iocoder/yudao/module/infra/framework/file/config/FileProperties.java delete mode 100644 yudao-module-infra/yudao-module-infra-impl/src/main/java/cn/iocoder/yudao/module/infra/framework/file/package-info.java diff --git a/yudao-framework/yudao-spring-boot-starter-file/src/main/java/cn/iocoder/yudao/framework/file/core/client/AbstractFileClient.java b/yudao-framework/yudao-spring-boot-starter-file/src/main/java/cn/iocoder/yudao/framework/file/core/client/AbstractFileClient.java index 262ceab20..a2d7304a8 100644 --- a/yudao-framework/yudao-spring-boot-starter-file/src/main/java/cn/iocoder/yudao/framework/file/core/client/AbstractFileClient.java +++ b/yudao-framework/yudao-spring-boot-starter-file/src/main/java/cn/iocoder/yudao/framework/file/core/client/AbstractFileClient.java @@ -1,8 +1,6 @@ package cn.iocoder.yudao.framework.file.core.client; import cn.hutool.core.util.StrUtil; -import cn.iocoder.yudao.framework.file.core.client.FileClient; -import cn.iocoder.yudao.framework.file.core.client.FileClientConfig; import lombok.extern.slf4j.Slf4j; /** @@ -65,7 +63,7 @@ public abstract class AbstractFileClient implem * @return URL 访问地址 */ protected String formatFileUrl(String domain, String path) { - return StrUtil.format("{}/system-api/{}/get/{}", domain, getId(), path); + return StrUtil.format("{}/admin-api/infra/file/{}/get/{}", domain, getId(), path); } } diff --git a/yudao-framework/yudao-spring-boot-starter-file/src/main/java/cn/iocoder/yudao/framework/file/core/client/db/DBFileClient.java b/yudao-framework/yudao-spring-boot-starter-file/src/main/java/cn/iocoder/yudao/framework/file/core/client/db/DBFileClient.java index 57c649a88..a227cc314 100644 --- a/yudao-framework/yudao-spring-boot-starter-file/src/main/java/cn/iocoder/yudao/framework/file/core/client/db/DBFileClient.java +++ b/yudao-framework/yudao-spring-boot-starter-file/src/main/java/cn/iocoder/yudao/framework/file/core/client/db/DBFileClient.java @@ -18,24 +18,31 @@ public class DBFileClient extends AbstractFileClient { @Override protected void doInit() { - dao = SpringUtil.getBean(DBFileContentFrameworkDAO.class); } @Override public String upload(byte[] content, String path) { - dao.insert(getId(), path, content); + getDao().insert(getId(), path, content); // 拼接返回路径 return super.formatFileUrl(config.getDomain(), path); } @Override public void delete(String path) { - dao.delete(getId(), path); + getDao().delete(getId(), path); } @Override public byte[] getContent(String path) { - return dao.selectContent(getId(), path); + return getDao().selectContent(getId(), path); + } + + private DBFileContentFrameworkDAO getDao() { + // 延迟获取,因为 SpringUtil 初始化太慢 + if (dao == null) { + dao = SpringUtil.getBean(DBFileContentFrameworkDAO.class); + } + return dao; } } diff --git a/yudao-framework/yudao-spring-boot-starter-file/src/main/java/cn/iocoder/yudao/framework/file/core/client/db/DBFileContentFrameworkDAO.java b/yudao-framework/yudao-spring-boot-starter-file/src/main/java/cn/iocoder/yudao/framework/file/core/client/db/DBFileContentFrameworkDAO.java index 44850ed77..9423e065d 100644 --- a/yudao-framework/yudao-spring-boot-starter-file/src/main/java/cn/iocoder/yudao/framework/file/core/client/db/DBFileContentFrameworkDAO.java +++ b/yudao-framework/yudao-spring-boot-starter-file/src/main/java/cn/iocoder/yudao/framework/file/core/client/db/DBFileContentFrameworkDAO.java @@ -7,10 +7,30 @@ package cn.iocoder.yudao.framework.file.core.client.db; */ public interface DBFileContentFrameworkDAO { + /** + * 插入文件内容 + * + * @param configId 配置编号 + * @param path 路径 + * @param content 内容 + */ void insert(Long configId, String path, byte[] content); + /** + * 删除文件内容 + * + * @param configId 配置编号 + * @param path 路径 + */ void delete(Long configId, String path); + /** + * 获得文件内容 + * + * @param configId 配置编号 + * @param path 路径 + * @return 内容 + */ byte[] selectContent(Long configId, String path); } diff --git a/yudao-module-infra/yudao-module-infra-impl/src/main/java/cn/iocoder/yudao/module/infra/controller/admin/file/FileController.java b/yudao-module-infra/yudao-module-infra-impl/src/main/java/cn/iocoder/yudao/module/infra/controller/admin/file/FileController.java index 7d449dbe8..e1cc6d076 100644 --- a/yudao-module-infra/yudao-module-infra-impl/src/main/java/cn/iocoder/yudao/module/infra/controller/admin/file/FileController.java +++ b/yudao-module-infra/yudao-module-infra-impl/src/main/java/cn/iocoder/yudao/module/infra/controller/admin/file/FileController.java @@ -4,8 +4,8 @@ import cn.hutool.core.io.IoUtil; import cn.iocoder.yudao.framework.common.pojo.CommonResult; import cn.iocoder.yudao.framework.common.pojo.PageResult; import cn.iocoder.yudao.framework.common.util.servlet.ServletUtils; -import cn.iocoder.yudao.module.infra.controller.admin.file.vo.FilePageReqVO; -import cn.iocoder.yudao.module.infra.controller.admin.file.vo.FileRespVO; +import cn.iocoder.yudao.module.infra.controller.admin.file.vo.file.FilePageReqVO; +import cn.iocoder.yudao.module.infra.controller.admin.file.vo.file.FileRespVO; import cn.iocoder.yudao.module.infra.convert.file.FileConvert; import cn.iocoder.yudao.module.infra.dal.dataobject.file.FileDO; import cn.iocoder.yudao.module.infra.service.file.FileService; @@ -50,9 +50,9 @@ public class FileController { @DeleteMapping("/delete") @ApiOperation("删除文件") - @ApiImplicitParam(name = "id", value = "编号", required = true, dataTypeClass = String.class) + @ApiImplicitParam(name = "id", value = "编号", required = true, dataTypeClass = Long.class) @PreAuthorize("@ss.hasPermission('infra:file:delete')") - public CommonResult deleteFile(@RequestParam("id") String id) { + public CommonResult deleteFile(@RequestParam("id") Long id) { fileService.deleteFile(id); return success(true); } @@ -60,19 +60,19 @@ public class FileController { @GetMapping("/{configId}/get/{path}") @ApiOperation("下载文件") @ApiImplicitParams({ - @ApiImplicitParam(name = "configId", value = "配置编号", required = true, dataTypeClass = String.class), + @ApiImplicitParam(name = "configId", value = "配置编号", required = true, dataTypeClass = Long.class), @ApiImplicitParam(name = "path", value = "文件路径", required = true, dataTypeClass = String.class) }) - public void getFile(HttpServletResponse response, - @PathVariable("configId") String configId, - @PathVariable("path") String path) throws IOException { - FileDO file = fileService.getFile(path); - if (file == null) { - log.warn("[getFile][path({}) 文件不存在]", path); + public void getFileContent(HttpServletResponse response, + @PathVariable("configId") Long configId, + @PathVariable("path") String path) throws IOException { + byte[] content = fileService.getFileContent(configId, path); + if (content == null) { + log.warn("[getFileContent][configId({}) path({}) 文件不存在]", configId, path); response.setStatus(HttpStatus.NOT_FOUND.value()); return; } - ServletUtils.writeAttachment(response, path, file.getContent()); + ServletUtils.writeAttachment(response, path, content); } @GetMapping("/page") diff --git a/yudao-module-infra/yudao-module-infra-impl/src/main/java/cn/iocoder/yudao/module/infra/controller/admin/file/vo/FileRespVO.java b/yudao-module-infra/yudao-module-infra-impl/src/main/java/cn/iocoder/yudao/module/infra/controller/admin/file/vo/FileRespVO.java deleted file mode 100644 index 838f3272b..000000000 --- a/yudao-module-infra/yudao-module-infra-impl/src/main/java/cn/iocoder/yudao/module/infra/controller/admin/file/vo/FileRespVO.java +++ /dev/null @@ -1,22 +0,0 @@ -package cn.iocoder.yudao.module.infra.controller.admin.file.vo; - -import io.swagger.annotations.ApiModel; -import io.swagger.annotations.ApiModelProperty; -import lombok.Data; - -import java.util.Date; - -@ApiModel(value = "管理后台 - 文件 Response VO", description = "不返回 content 字段,太大") -@Data -public class FileRespVO { - - @ApiModelProperty(value = "文件路径", required = true, example = "yudao.jpg") - private String id; - - @ApiModelProperty(value = "文件类型", required = true, example = "jpg") - private String type; - - @ApiModelProperty(value = "创建时间", required = true) - private Date createTime; - -} diff --git a/yudao-module-infra/yudao-module-infra-impl/src/main/java/cn/iocoder/yudao/module/infra/controller/admin/file/vo/FilePageReqVO.java b/yudao-module-infra/yudao-module-infra-impl/src/main/java/cn/iocoder/yudao/module/infra/controller/admin/file/vo/file/FilePageReqVO.java similarity index 92% rename from yudao-module-infra/yudao-module-infra-impl/src/main/java/cn/iocoder/yudao/module/infra/controller/admin/file/vo/FilePageReqVO.java rename to yudao-module-infra/yudao-module-infra-impl/src/main/java/cn/iocoder/yudao/module/infra/controller/admin/file/vo/file/FilePageReqVO.java index bf67300ab..346314e83 100644 --- a/yudao-module-infra/yudao-module-infra-impl/src/main/java/cn/iocoder/yudao/module/infra/controller/admin/file/vo/FilePageReqVO.java +++ b/yudao-module-infra/yudao-module-infra-impl/src/main/java/cn/iocoder/yudao/module/infra/controller/admin/file/vo/file/FilePageReqVO.java @@ -1,4 +1,4 @@ -package cn.iocoder.yudao.module.infra.controller.admin.file.vo; +package cn.iocoder.yudao.module.infra.controller.admin.file.vo.file; import cn.iocoder.yudao.framework.common.pojo.PageParam; import io.swagger.annotations.ApiModel; @@ -19,7 +19,7 @@ import static cn.iocoder.yudao.framework.common.util.date.DateUtils.FORMAT_YEAR_ public class FilePageReqVO extends PageParam { @ApiModelProperty(value = "文件路径", example = "yudao", notes = "模糊匹配") - private String id; + private String path; @ApiModelProperty(value = "文件类型", example = "jpg", notes = "模糊匹配") private String type; diff --git a/yudao-module-infra/yudao-module-infra-impl/src/main/java/cn/iocoder/yudao/module/infra/controller/admin/file/vo/file/FileRespVO.java b/yudao-module-infra/yudao-module-infra-impl/src/main/java/cn/iocoder/yudao/module/infra/controller/admin/file/vo/file/FileRespVO.java new file mode 100644 index 000000000..8e39a7520 --- /dev/null +++ b/yudao-module-infra/yudao-module-infra-impl/src/main/java/cn/iocoder/yudao/module/infra/controller/admin/file/vo/file/FileRespVO.java @@ -0,0 +1,31 @@ +package cn.iocoder.yudao.module.infra.controller.admin.file.vo.file; + +import io.swagger.annotations.ApiModel; +import io.swagger.annotations.ApiModelProperty; +import lombok.Data; + +import java.util.Date; + +@ApiModel(value = "管理后台 - 文件 Response VO", description = "不返回 content 字段,太大") +@Data +public class FileRespVO { + + @ApiModelProperty(value = "文件编号", required = true, example = "1024") + private Long id; + + @ApiModelProperty(value = "文件路径", required = true, example = "yudao.jpg") + private String path; + + @ApiModelProperty(value = "文件 URL", required = true, example = "https://www.iocoder.cn/yudao.jpg") + private String url; + + @ApiModelProperty(value = "文件类型", example = "jpg") + private String type; + + @ApiModelProperty(value = "文件大小", example = "2048", required = true) + private Integer size; + + @ApiModelProperty(value = "创建时间", required = true) + private Date createTime; + +} diff --git a/yudao-module-infra/yudao-module-infra-impl/src/main/java/cn/iocoder/yudao/module/infra/convert/file/FileConvert.java b/yudao-module-infra/yudao-module-infra-impl/src/main/java/cn/iocoder/yudao/module/infra/convert/file/FileConvert.java index affeddba4..ee7e5186b 100644 --- a/yudao-module-infra/yudao-module-infra-impl/src/main/java/cn/iocoder/yudao/module/infra/convert/file/FileConvert.java +++ b/yudao-module-infra/yudao-module-infra-impl/src/main/java/cn/iocoder/yudao/module/infra/convert/file/FileConvert.java @@ -1,7 +1,7 @@ package cn.iocoder.yudao.module.infra.convert.file; import cn.iocoder.yudao.framework.common.pojo.PageResult; -import cn.iocoder.yudao.module.infra.controller.admin.file.vo.FileRespVO; +import cn.iocoder.yudao.module.infra.controller.admin.file.vo.file.FileRespVO; import cn.iocoder.yudao.module.infra.dal.dataobject.file.FileDO; import org.mapstruct.Mapper; import org.mapstruct.factory.Mappers; diff --git a/yudao-module-infra/yudao-module-infra-impl/src/main/java/cn/iocoder/yudao/module/infra/dal/dataobject/file/FileDO.java b/yudao-module-infra/yudao-module-infra-impl/src/main/java/cn/iocoder/yudao/module/infra/dal/dataobject/file/FileDO.java index 2803c9d10..e080fa2fb 100644 --- a/yudao-module-infra/yudao-module-infra-impl/src/main/java/cn/iocoder/yudao/module/infra/dal/dataobject/file/FileDO.java +++ b/yudao-module-infra/yudao-module-infra-impl/src/main/java/cn/iocoder/yudao/module/infra/dal/dataobject/file/FileDO.java @@ -1,9 +1,7 @@ package cn.iocoder.yudao.module.infra.dal.dataobject.file; import cn.iocoder.yudao.framework.mybatis.core.dataobject.BaseDO; -import com.baomidou.mybatisplus.annotation.IdType; import com.baomidou.mybatisplus.annotation.TableField; -import com.baomidou.mybatisplus.annotation.TableId; import com.baomidou.mybatisplus.annotation.TableName; import lombok.*; @@ -27,8 +25,7 @@ public class FileDO extends BaseDO { /** * 编号,数据库自增 */ - @TableId(type = IdType.INPUT) - private String id; + private Long id; /** * 配置编号 * @@ -39,6 +36,10 @@ public class FileDO extends BaseDO { * 路径,即文件名 */ private String path; + /** + * 访问地址 + */ + private String url; /** * 文件类型 * @@ -46,18 +47,9 @@ public class FileDO extends BaseDO { */ @TableField(value = "`type`") private String type; - /** - * 访问地址 - */ - private String url; /** * 文件大小 */ private Integer size; - /** - * 文件内容 - */ - @Deprecated - private byte[] content; } diff --git a/yudao-module-infra/yudao-module-infra-impl/src/main/java/cn/iocoder/yudao/module/infra/dal/mysql/file/FileContentDAOImpl.java b/yudao-module-infra/yudao-module-infra-impl/src/main/java/cn/iocoder/yudao/module/infra/dal/mysql/file/FileContentDAOImpl.java new file mode 100644 index 000000000..c4dcfe8a0 --- /dev/null +++ b/yudao-module-infra/yudao-module-infra-impl/src/main/java/cn/iocoder/yudao/module/infra/dal/mysql/file/FileContentDAOImpl.java @@ -0,0 +1,41 @@ +package cn.iocoder.yudao.module.infra.dal.mysql.file; + +import cn.iocoder.yudao.framework.file.core.client.db.DBFileContentFrameworkDAO; +import cn.iocoder.yudao.module.infra.dal.dataobject.file.FileContentDO; +import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; +import org.springframework.stereotype.Repository; + +import javax.annotation.Resource; + +@Repository +public class FileContentDAOImpl implements DBFileContentFrameworkDAO { + + @Resource + private FileContentMapper fileContentMapper; + + @Override + public void insert(Long configId, String path, byte[] content) { + FileContentDO entity = new FileContentDO().setConfigId(configId) + .setPath(path).setContent(content); + fileContentMapper.insert(entity); + } + + @Override + public void delete(Long configId, String path) { + fileContentMapper.delete(buildQuery(configId, path)); + } + + @Override + public byte[] selectContent(Long configId, String path) { + FileContentDO fileContentDO = fileContentMapper.selectOne( + buildQuery(configId, path).select(FileContentDO::getContent)); + return fileContentDO != null ? fileContentDO.getContent() : null; + } + + private LambdaQueryWrapper buildQuery(Long configId, String path) { + return new LambdaQueryWrapper() + .eq(FileContentDO::getConfigId, configId) + .eq(FileContentDO::getPath, path); + } + +} diff --git a/yudao-module-infra/yudao-module-infra-impl/src/main/java/cn/iocoder/yudao/module/infra/dal/mysql/file/FileContentMapper.java b/yudao-module-infra/yudao-module-infra-impl/src/main/java/cn/iocoder/yudao/module/infra/dal/mysql/file/FileContentMapper.java new file mode 100644 index 000000000..501979dbe --- /dev/null +++ b/yudao-module-infra/yudao-module-infra-impl/src/main/java/cn/iocoder/yudao/module/infra/dal/mysql/file/FileContentMapper.java @@ -0,0 +1,9 @@ +package cn.iocoder.yudao.module.infra.dal.mysql.file; + +import cn.iocoder.yudao.module.infra.dal.dataobject.file.FileContentDO; +import com.baomidou.mybatisplus.core.mapper.BaseMapper; +import org.apache.ibatis.annotations.Mapper; + +@Mapper +public interface FileContentMapper extends BaseMapper { +} diff --git a/yudao-module-infra/yudao-module-infra-impl/src/main/java/cn/iocoder/yudao/module/infra/dal/mysql/file/FileMapper.java b/yudao-module-infra/yudao-module-infra-impl/src/main/java/cn/iocoder/yudao/module/infra/dal/mysql/file/FileMapper.java index 1786d130f..1938ee285 100644 --- a/yudao-module-infra/yudao-module-infra-impl/src/main/java/cn/iocoder/yudao/module/infra/dal/mysql/file/FileMapper.java +++ b/yudao-module-infra/yudao-module-infra-impl/src/main/java/cn/iocoder/yudao/module/infra/dal/mysql/file/FileMapper.java @@ -3,7 +3,7 @@ package cn.iocoder.yudao.module.infra.dal.mysql.file; import cn.iocoder.yudao.framework.common.pojo.PageResult; import cn.iocoder.yudao.framework.mybatis.core.mapper.BaseMapperX; import cn.iocoder.yudao.framework.mybatis.core.query.QueryWrapperX; -import cn.iocoder.yudao.module.infra.controller.admin.file.vo.FilePageReqVO; +import cn.iocoder.yudao.module.infra.controller.admin.file.vo.file.FilePageReqVO; import cn.iocoder.yudao.module.infra.dal.dataobject.file.FileDO; import org.apache.ibatis.annotations.Mapper; @@ -17,25 +17,10 @@ public interface FileMapper extends BaseMapperX { default PageResult selectPage(FilePageReqVO reqVO) { return selectPage(reqVO, new QueryWrapperX() - .likeIfPresent("id", reqVO.getId()) + .likeIfPresent("path", reqVO.getPath()) .likeIfPresent("type", reqVO.getType()) .betweenIfPresent("create_time", reqVO.getBeginCreateTime(), reqVO.getEndCreateTime()) .orderByDesc("create_time")); } - default Long selectCountById(String id) { - return selectCount(FileDO::getId, id); - } - - /** - * 基于 Path 获取文件 - * 实际上,是基于 ID 查询 - * - * @param path 路径 - * @return 文件 - */ - default FileDO selectByPath(String path) { - return selectById(path); - } - } diff --git a/yudao-module-infra/yudao-module-infra-impl/src/main/java/cn/iocoder/yudao/module/infra/framework/file/config/FileConfiguration.java b/yudao-module-infra/yudao-module-infra-impl/src/main/java/cn/iocoder/yudao/module/infra/framework/file/config/FileConfiguration.java deleted file mode 100644 index f944daa23..000000000 --- a/yudao-module-infra/yudao-module-infra-impl/src/main/java/cn/iocoder/yudao/module/infra/framework/file/config/FileConfiguration.java +++ /dev/null @@ -1,12 +0,0 @@ -package cn.iocoder.yudao.module.infra.framework.file.config; - -import org.springframework.boot.context.properties.EnableConfigurationProperties; -import org.springframework.context.annotation.Configuration; - -/** - * 文件 配置类 - */ -@Configuration -@EnableConfigurationProperties(FileProperties.class) -public class FileConfiguration { -} diff --git a/yudao-module-infra/yudao-module-infra-impl/src/main/java/cn/iocoder/yudao/module/infra/framework/file/config/FileProperties.java b/yudao-module-infra/yudao-module-infra-impl/src/main/java/cn/iocoder/yudao/module/infra/framework/file/config/FileProperties.java deleted file mode 100644 index c112802b0..000000000 --- a/yudao-module-infra/yudao-module-infra-impl/src/main/java/cn/iocoder/yudao/module/infra/framework/file/config/FileProperties.java +++ /dev/null @@ -1,22 +0,0 @@ -package cn.iocoder.yudao.module.infra.framework.file.config; - -import lombok.Data; -import org.springframework.boot.context.properties.ConfigurationProperties; -import org.springframework.validation.annotation.Validated; - -import javax.validation.constraints.NotNull; - -@ConfigurationProperties(prefix = "yudao.file") -@Validated -@Data -public class FileProperties { - - /** - * 对应 FileController 的 getFile 方法 - */ - @NotNull(message = "基础文件路径不能为空") - private String basePath; - - // TODO 七牛、等等 - -} diff --git a/yudao-module-infra/yudao-module-infra-impl/src/main/java/cn/iocoder/yudao/module/infra/framework/file/package-info.java b/yudao-module-infra/yudao-module-infra-impl/src/main/java/cn/iocoder/yudao/module/infra/framework/file/package-info.java deleted file mode 100644 index ee21b922e..000000000 --- a/yudao-module-infra/yudao-module-infra-impl/src/main/java/cn/iocoder/yudao/module/infra/framework/file/package-info.java +++ /dev/null @@ -1,16 +0,0 @@ -/** - * 文件的存储,推荐使用七牛、阿里云、华为云、腾讯云等文件服务 - * - * 在不采用云服务的情况下,我们有几种技术选型: - * 方案 1. 使用自建的文件服务,例如说 minIO、FastDFS 等等 - * 方案 2. 使用服务器的文件系统存储 - * 方案 3. 使用数据库进行存储 - * - * 如果考虑额外在搭建服务,推荐方案 1。 - * 对于方案 2 来说,如果要实现文件存储的高可用,需要多台服务器之间做实时同步,可以基于 rsync + inotify 来做 - * 对于方案 3 的话,实现起来最简单,但是数据库本身不适合存储海量的文件 - * - * 综合考虑,暂时使用方案 3 的方式,比较适合这样一个 all in one 的项目。 - * 随着文件的量级大了之后,还是推荐采用云服务。 - */ -package cn.iocoder.yudao.module.infra.framework.file; diff --git a/yudao-module-infra/yudao-module-infra-impl/src/main/java/cn/iocoder/yudao/module/infra/framework/security/config/SecurityConfiguration.java b/yudao-module-infra/yudao-module-infra-impl/src/main/java/cn/iocoder/yudao/module/infra/framework/security/config/SecurityConfiguration.java index 3b2a5501e..048411769 100644 --- a/yudao-module-infra/yudao-module-infra-impl/src/main/java/cn/iocoder/yudao/module/infra/framework/security/config/SecurityConfiguration.java +++ b/yudao-module-infra/yudao-module-infra-impl/src/main/java/cn/iocoder/yudao/module/infra/framework/security/config/SecurityConfiguration.java @@ -36,7 +36,7 @@ public class SecurityConfiguration { registry.antMatchers(adminSeverContextPath).anonymous() .antMatchers(adminSeverContextPath + "/**").anonymous(); // 文件的获取接口,可匿名访问 - registry.antMatchers(buildAdminApi("/infra/file/get/**"), buildAppApi("/infra/file/get/**")).anonymous(); + registry.antMatchers(buildAdminApi("/infra/file/*/get/**"), buildAppApi("/infra/file/get/**")).permitAll(); } }; diff --git a/yudao-module-infra/yudao-module-infra-impl/src/main/java/cn/iocoder/yudao/module/infra/service/file/FileConfigService.java b/yudao-module-infra/yudao-module-infra-impl/src/main/java/cn/iocoder/yudao/module/infra/service/file/FileConfigService.java index 1d7fcfaaa..326052688 100755 --- a/yudao-module-infra/yudao-module-infra-impl/src/main/java/cn/iocoder/yudao/module/infra/service/file/FileConfigService.java +++ b/yudao-module-infra/yudao-module-infra-impl/src/main/java/cn/iocoder/yudao/module/infra/service/file/FileConfigService.java @@ -1,6 +1,7 @@ package cn.iocoder.yudao.module.infra.service.file; import cn.iocoder.yudao.framework.common.pojo.PageResult; +import cn.iocoder.yudao.framework.file.core.client.FileClient; import cn.iocoder.yudao.module.infra.controller.admin.file.vo.config.FileConfigCreateReqVO; import cn.iocoder.yudao.module.infra.controller.admin.file.vo.config.FileConfigPageReqVO; import cn.iocoder.yudao.module.infra.controller.admin.file.vo.config.FileConfigUpdateReqVO; @@ -83,4 +84,19 @@ public interface FileConfigService { */ String testFileConfig(Long id); + /** + * 获得指定编号的文件客户端 + * + * @param id 配置编号 + * @return 文件客户端 + */ + FileClient getFileClient(Long id); + + /** + * 获得 Master 文件客户端 + * + * @return 文件客户端 + */ + FileClient getMasterFileClient(); + } diff --git a/yudao-module-infra/yudao-module-infra-impl/src/main/java/cn/iocoder/yudao/module/infra/service/file/FileConfigServiceImpl.java b/yudao-module-infra/yudao-module-infra-impl/src/main/java/cn/iocoder/yudao/module/infra/service/file/FileConfigServiceImpl.java index 51cc21bbc..3d79bdeef 100755 --- a/yudao-module-infra/yudao-module-infra-impl/src/main/java/cn/iocoder/yudao/module/infra/service/file/FileConfigServiceImpl.java +++ b/yudao-module-infra/yudao-module-infra-impl/src/main/java/cn/iocoder/yudao/module/infra/service/file/FileConfigServiceImpl.java @@ -233,4 +233,9 @@ public class FileConfigServiceImpl implements FileConfigService { return fileClientFactory.getFileClient(id).upload(content, IdUtil.fastSimpleUUID() + ".jpg"); } + @Override + public FileClient getFileClient(Long id) { + return fileClientFactory.getFileClient(id); + } + } diff --git a/yudao-module-infra/yudao-module-infra-impl/src/main/java/cn/iocoder/yudao/module/infra/service/file/FileService.java b/yudao-module-infra/yudao-module-infra-impl/src/main/java/cn/iocoder/yudao/module/infra/service/file/FileService.java index 4fb658aa5..ea5f31390 100644 --- a/yudao-module-infra/yudao-module-infra-impl/src/main/java/cn/iocoder/yudao/module/infra/service/file/FileService.java +++ b/yudao-module-infra/yudao-module-infra-impl/src/main/java/cn/iocoder/yudao/module/infra/service/file/FileService.java @@ -1,6 +1,6 @@ package cn.iocoder.yudao.module.infra.service.file; -import cn.iocoder.yudao.module.infra.controller.admin.file.vo.FilePageReqVO; +import cn.iocoder.yudao.module.infra.controller.admin.file.vo.file.FilePageReqVO; import cn.iocoder.yudao.framework.common.pojo.PageResult; import cn.iocoder.yudao.module.infra.dal.dataobject.file.FileDO; @@ -33,14 +33,15 @@ public interface FileService { * * @param id 编号 */ - void deleteFile(String id); + void deleteFile(Long id); /** - * 获得文件 + * 获得文件内容 * + * @param configId 配置编号 * @param path 文件路径 - * @return 文件 + * @return 文件内容 */ - FileDO getFile(String path); + byte[] getFileContent(Long configId, String path); } diff --git a/yudao-module-infra/yudao-module-infra-impl/src/main/java/cn/iocoder/yudao/module/infra/service/file/FileServiceImpl.java b/yudao-module-infra/yudao-module-infra-impl/src/main/java/cn/iocoder/yudao/module/infra/service/file/FileServiceImpl.java index 1f1724ecd..3a3c3c010 100644 --- a/yudao-module-infra/yudao-module-infra-impl/src/main/java/cn/iocoder/yudao/module/infra/service/file/FileServiceImpl.java +++ b/yudao-module-infra/yudao-module-infra-impl/src/main/java/cn/iocoder/yudao/module/infra/service/file/FileServiceImpl.java @@ -1,18 +1,19 @@ package cn.iocoder.yudao.module.infra.service.file; import cn.hutool.core.io.FileTypeUtil; +import cn.hutool.core.lang.Assert; import cn.iocoder.yudao.framework.common.pojo.PageResult; -import cn.iocoder.yudao.module.infra.controller.admin.file.vo.FilePageReqVO; +import cn.iocoder.yudao.framework.file.core.client.FileClient; +import cn.iocoder.yudao.module.infra.controller.admin.file.vo.file.FilePageReqVO; import cn.iocoder.yudao.module.infra.dal.dataobject.file.FileDO; import cn.iocoder.yudao.module.infra.dal.mysql.file.FileMapper; -import cn.iocoder.yudao.module.infra.framework.file.config.FileProperties; import org.springframework.stereotype.Service; import javax.annotation.Resource; import java.io.ByteArrayInputStream; import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception; -import static cn.iocoder.yudao.module.infra.enums.ErrorCodeConstants.*; +import static cn.iocoder.yudao.module.infra.enums.ErrorCodeConstants.FILE_NOT_EXISTS; /** * 文件 Service 实现类 @@ -23,10 +24,10 @@ import static cn.iocoder.yudao.module.infra.enums.ErrorCodeConstants.*; public class FileServiceImpl implements FileService { @Resource - private FileMapper fileMapper; + private FileConfigService fileConfigService; @Resource - private FileProperties fileProperties; + private FileMapper fileMapper; @Override public PageResult getFilePage(FilePageReqVO pageReqVO) { @@ -35,36 +36,49 @@ public class FileServiceImpl implements FileService { @Override public String createFile(String path, byte[] content) { - if (fileMapper.selectCountById(path) > 0) { - throw exception(FILE_PATH_EXISTS); - } + // 上传到文件存储器 + FileClient client = fileConfigService.getMasterFileClient(); + Assert.notNull(client, "客户端(master) 不能为空"); + String url = client.upload(content, path); + // 保存到数据库 FileDO file = new FileDO(); - file.setId(path); + file.setConfigId(client.getId()); + file.setPath(path); + file.setUrl(url); file.setType(FileTypeUtil.getType(new ByteArrayInputStream(content))); - file.setContent(content); + file.setSize(content.length); fileMapper.insert(file); - // 拼接路径返回 - return fileProperties.getBasePath() + path; + return url; } @Override - public void deleteFile(String id) { + public void deleteFile(Long id) { // 校验存在 - this.validateFileExists(id); - // 更新 + FileDO file = this.validateFileExists(id); + + // 从文件存储器中删除 + FileClient client = fileConfigService.getFileClient(file.getConfigId()); + Assert.notNull(client, "客户端({}) 不能为空", file.getConfigId()); + client.delete(file.getPath()); + + // 删除记录 fileMapper.deleteById(id); } - private void validateFileExists(String id) { - if (fileMapper.selectById(id) == null) { + private FileDO validateFileExists(Long id) { + FileDO fileDO = fileMapper.selectById(id); + if (fileDO == null) { throw exception(FILE_NOT_EXISTS); } + return fileDO; } @Override - public FileDO getFile(String path) { - return fileMapper.selectByPath(path); + public byte[] getFileContent(Long configId, String path) { + FileClient client = fileConfigService.getFileClient(configId); + Assert.notNull(client, "客户端({}) 不能为空", configId); + return client.getContent(path); } } diff --git a/yudao-module-infra/yudao-module-infra-impl/src/test/java/cn/iocoder/yudao/module/infra/service/file/FileServiceTest.java b/yudao-module-infra/yudao-module-infra-impl/src/test/java/cn/iocoder/yudao/module/infra/service/file/FileServiceTest.java index 259502ca7..6ff74a21f 100644 --- a/yudao-module-infra/yudao-module-infra-impl/src/test/java/cn/iocoder/yudao/module/infra/service/file/FileServiceTest.java +++ b/yudao-module-infra/yudao-module-infra-impl/src/test/java/cn/iocoder/yudao/module/infra/service/file/FileServiceTest.java @@ -3,11 +3,11 @@ package cn.iocoder.yudao.module.infra.service.file; import cn.hutool.core.io.resource.ResourceUtil; import cn.iocoder.yudao.framework.common.pojo.PageResult; import cn.iocoder.yudao.framework.common.util.object.ObjectUtils; +import cn.iocoder.yudao.framework.file.core.client.FileClient; import cn.iocoder.yudao.framework.test.core.util.AssertUtils; -import cn.iocoder.yudao.module.infra.controller.admin.file.vo.FilePageReqVO; +import cn.iocoder.yudao.module.infra.controller.admin.file.vo.file.FilePageReqVO; import cn.iocoder.yudao.module.infra.dal.dataobject.file.FileDO; import cn.iocoder.yudao.module.infra.dal.mysql.file.FileMapper; -import cn.iocoder.yudao.module.infra.framework.file.config.FileProperties; import cn.iocoder.yudao.module.infra.test.BaseDbUnitTest; import org.junit.jupiter.api.Test; import org.springframework.boot.test.mock.mockito.MockBean; @@ -17,47 +17,46 @@ import javax.annotation.Resource; import static cn.iocoder.yudao.framework.common.util.date.DateUtils.buildTime; import static cn.iocoder.yudao.framework.test.core.util.AssertUtils.assertServiceException; -import static cn.iocoder.yudao.framework.test.core.util.RandomUtils.randomPojo; -import static cn.iocoder.yudao.framework.test.core.util.RandomUtils.randomString; +import static cn.iocoder.yudao.framework.test.core.util.RandomUtils.*; import static cn.iocoder.yudao.module.infra.enums.ErrorCodeConstants.*; import static org.junit.jupiter.api.Assertions.*; +import static org.mockito.ArgumentMatchers.same; +import static org.mockito.Mockito.*; -@Import({FileServiceImpl.class, FileProperties.class}) +@Import({FileServiceImpl.class}) public class FileServiceTest extends BaseDbUnitTest { @Resource private FileService fileService; - @MockBean - private FileProperties fileProperties; - @Resource private FileMapper fileMapper; + @MockBean + private FileConfigService fileConfigService; + @Test public void testGetFilePage() { // mock 数据 FileDO dbFile = randomPojo(FileDO.class, o -> { // 等会查询到 - o.setId("yunai"); + o.setPath("yunai"); o.setType("jpg"); o.setCreateTime(buildTime(2021, 1, 15)); }); fileMapper.insert(dbFile); - // 测试 id 不匹配 - fileMapper.insert(ObjectUtils.cloneIgnoreId(dbFile, o -> o.setId("tudou"))); + // 测试 path 不匹配 + fileMapper.insert(ObjectUtils.cloneIgnoreId(dbFile, o -> o.setPath("tudou"))); // 测试 type 不匹配 fileMapper.insert(ObjectUtils.cloneIgnoreId(dbFile, o -> { - o.setId("yunai02"); o.setType("png"); })); // 测试 createTime 不匹配 fileMapper.insert(ObjectUtils.cloneIgnoreId(dbFile, o -> { - o.setId("yunai03"); o.setCreateTime(buildTime(2020, 1, 15)); })); // 准备参数 FilePageReqVO reqVO = new FilePageReqVO(); - reqVO.setId("yunai"); + reqVO.setPath("yunai"); reqVO.setType("jp"); reqVO.setBeginCreateTime(buildTime(2021, 1, 10)); reqVO.setEndCreateTime(buildTime(2021, 1, 20)); @@ -67,7 +66,7 @@ public class FileServiceTest extends BaseDbUnitTest { // 断言 assertEquals(1, pageResult.getTotal()); assertEquals(1, pageResult.getList().size()); - AssertUtils.assertPojoEquals(dbFile, pageResult.getList().get(0), "content"); + AssertUtils.assertPojoEquals(dbFile, pageResult.getList().get(0)); } @Test @@ -75,52 +74,68 @@ public class FileServiceTest extends BaseDbUnitTest { // 准备参数 String path = randomString(); byte[] content = ResourceUtil.readBytes("file/erweima.jpg"); + // mock Master 文件客户端 + FileClient client = mock(FileClient.class); + when(fileConfigService.getMasterFileClient()).thenReturn(client); + String url = randomString(); + when(client.upload(same(content), same(path))).thenReturn(url); + when(client.getId()).thenReturn(10L); // 调用 - String url = fileService.createFile(path, content); + String result = fileService.createFile(path, content); // 断言 - assertEquals(fileProperties.getBasePath() + path, url); + assertEquals(result, url); // 校验数据 - FileDO file = fileMapper.selectById(path); - assertEquals(path, file.getId()); + FileDO file = fileMapper.selectOne(FileDO::getPath, path); + assertEquals(10L, file.getConfigId()); + assertEquals(path, file.getPath()); + assertEquals(url, file.getUrl()); assertEquals("jpg", file.getType()); - assertArrayEquals(content, file.getContent()); - } - - @Test - public void testCreateFile_exists() { - // mock 数据 - FileDO dbFile = randomPojo(FileDO.class); - fileMapper.insert(dbFile); - // 准备参数 - String path = dbFile.getId(); // 模拟已存在 - byte[] content = ResourceUtil.readBytes("file/erweima.jpg"); - - // 调用,并断言异常 - assertServiceException(() -> fileService.createFile(path, content), FILE_PATH_EXISTS); + assertEquals(content.length, file.getSize()); } @Test public void testDeleteFile_success() { // mock 数据 - FileDO dbFile = randomPojo(FileDO.class); + FileDO dbFile = randomPojo(FileDO.class, o -> o.setConfigId(10L).setPath("tudou.jpg")); fileMapper.insert(dbFile);// @Sql: 先插入出一条存在的数据 + // mock Master 文件客户端 + FileClient client = mock(FileClient.class); + when(fileConfigService.getFileClient(eq(10L))).thenReturn(client); // 准备参数 - String id = dbFile.getId(); + Long id = dbFile.getId(); // 调用 fileService.deleteFile(id); // 校验数据不存在了 assertNull(fileMapper.selectById(id)); + // 校验调用 + verify(client).delete(eq("tudou.jpg")); } @Test public void testDeleteFile_notExists() { // 准备参数 - String id = randomString(); + Long id = randomLongId(); // 调用, 并断言异常 assertServiceException(() -> fileService.deleteFile(id), FILE_NOT_EXISTS); } + @Test + public void testGetFileContent() { + // 准备参数 + Long configId = 10L; + String path = "tudou.jpg"; + // mock 方法 + FileClient client = mock(FileClient.class); + when(fileConfigService.getFileClient(eq(10L))).thenReturn(client); + byte[] content = new byte[]{}; + when(client.getContent(eq("tudou.jpg"))).thenReturn(content); + + // 调用 + byte[] result = fileService.getFileContent(configId, path); + // 断言 + assertSame(result, content); + } } diff --git a/yudao-module-infra/yudao-module-infra-impl/src/test/resources/sql/create_tables.sql b/yudao-module-infra/yudao-module-infra-impl/src/test/resources/sql/create_tables.sql index 0265690d0..3c86102e6 100644 --- a/yudao-module-infra/yudao-module-infra-impl/src/test/resources/sql/create_tables.sql +++ b/yudao-module-infra/yudao-module-infra-impl/src/test/resources/sql/create_tables.sql @@ -32,9 +32,12 @@ CREATE TABLE IF NOT EXISTS "infra_file_config" ( ) COMMENT '文件配置表'; CREATE TABLE IF NOT EXISTS "infra_file" ( - "id" varchar(188) NOT NULL, + "id" bigint NOT NULL GENERATED BY DEFAULT AS IDENTITY, + "config_id" bigint NOT NULL, + "path" varchar(512), + "url" varchar(1024), "type" varchar(63) DEFAULT NULL, - "content" blob NOT NULL, + "size" bigint NOT NULL, "creator" varchar(64) DEFAULT '', "create_time" timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP, "updater" varchar(64) DEFAULT '', diff --git a/yudao-server/src/main/resources/application-dev.yaml b/yudao-server/src/main/resources/application-dev.yaml index 6ce1bd981..a2f191b7b 100644 --- a/yudao-server/src/main/resources/application-dev.yaml +++ b/yudao-server/src/main/resources/application-dev.yaml @@ -172,8 +172,6 @@ yudao: session-timeout: 30m mock-enable: true mock-secret: test - file: - base-path: http://api-dashboard.yudao.iocoder.cn${yudao.web.admin-api.prefix}/infra/file/get/ xss: enable: false exclude-urls: # 如下两个 url,仅仅是为了演示,去掉配置也没关系 diff --git a/yudao-server/src/main/resources/application-local.yaml b/yudao-server/src/main/resources/application-local.yaml index 42279a198..81d698300 100644 --- a/yudao-server/src/main/resources/application-local.yaml +++ b/yudao-server/src/main/resources/application-local.yaml @@ -184,8 +184,6 @@ yudao: session-timeout: 1d mock-enable: true mock-secret: test - file: - base-path: http://127.0.0.1:${server.port}${yudao.web.admin-api.prefix}/infra/file/get/ xss: enable: false exclude-urls: # 如下两个 url,仅仅是为了演示,去掉配置也没关系 diff --git a/yudao-server/src/main/resources/application.yaml b/yudao-server/src/main/resources/application.yaml index 7f340cb33..7c99c5f5d 100644 --- a/yudao-server/src/main/resources/application.yaml +++ b/yudao-server/src/main/resources/application.yaml @@ -92,7 +92,7 @@ yudao: ignore-urls: - /admin-api/system/tenant/get-id-by-name # 基于名字获取租户,不许带租户编号 - /admin-api/system/captcha/get-image # 获取图片验证码,和租户无关 - - /admin-api/infra/file/get/* # 获取图片,和租户无关 + - /admin-api/infra/file/*/get/** # 获取图片,和租户无关 - /admin-api/system/sms/callback/* # 短信回调接口,无法带上租户编号 ignore-tables: - system_tenant diff --git a/yudao-ui-admin/src/views/infra/file/index.vue b/yudao-ui-admin/src/views/infra/file/index.vue index 78f1dc17e..4b41401d9 100644 --- a/yudao-ui-admin/src/views/infra/file/index.vue +++ b/yudao-ui-admin/src/views/infra/file/index.vue @@ -3,8 +3,8 @@ - - + + @@ -31,21 +31,23 @@ - + + + - - - - + + + + + + + + - +