mirror of
				https://gitee.com/hhyykk/ipms-sjy.git
				synced 2025-10-31 02:08:43 +08:00 
			
		
		
		
	完成新 File 的功能
This commit is contained in:
		| @@ -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<Boolean> deleteFile(@RequestParam("id") String id) { | ||||
|     public CommonResult<Boolean> 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") | ||||
|   | ||||
| @@ -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; | ||||
|  | ||||
| } | ||||
| @@ -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; | ||||
| @@ -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; | ||||
|  | ||||
| } | ||||
| @@ -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; | ||||
|   | ||||
| @@ -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; | ||||
|  | ||||
| } | ||||
|   | ||||
| @@ -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<FileContentDO> buildQuery(Long configId, String path) { | ||||
|         return new LambdaQueryWrapper<FileContentDO>() | ||||
|                 .eq(FileContentDO::getConfigId, configId) | ||||
|                 .eq(FileContentDO::getPath, path); | ||||
|     } | ||||
|  | ||||
| } | ||||
| @@ -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<FileContentDO> { | ||||
| } | ||||
| @@ -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<FileDO> { | ||||
|  | ||||
|     default PageResult<FileDO> selectPage(FilePageReqVO reqVO) { | ||||
|         return selectPage(reqVO, new QueryWrapperX<FileDO>() | ||||
|                 .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); | ||||
|     } | ||||
|  | ||||
| } | ||||
|   | ||||
| @@ -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 { | ||||
| } | ||||
| @@ -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 七牛、等等 | ||||
|  | ||||
| } | ||||
| @@ -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; | ||||
| @@ -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(); | ||||
|             } | ||||
|  | ||||
|         }; | ||||
|   | ||||
| @@ -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(); | ||||
|  | ||||
| } | ||||
|   | ||||
| @@ -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); | ||||
|     } | ||||
|  | ||||
| } | ||||
|   | ||||
| @@ -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); | ||||
|  | ||||
| } | ||||
|   | ||||
| @@ -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<FileDO> 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); | ||||
|     } | ||||
|  | ||||
| } | ||||
|   | ||||
| @@ -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); | ||||
|     } | ||||
| } | ||||
|   | ||||
| @@ -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 '', | ||||
|   | ||||
		Reference in New Issue
	
	Block a user
	 YunaiV
					YunaiV