Commit 87670d18fdf8c96f5a61861b41381d8adf9cecc7
1 parent
cdcecd0d
完成新 File 的功能
Showing
25 changed files
with
258 additions
and
186 deletions
yudao-framework/yudao-spring-boot-starter-file/src/main/java/cn/iocoder/yudao/framework/file/core/client/AbstractFileClient.java
| 1 | package cn.iocoder.yudao.framework.file.core.client; | 1 | package cn.iocoder.yudao.framework.file.core.client; |
| 2 | 2 | ||
| 3 | import cn.hutool.core.util.StrUtil; | 3 | import cn.hutool.core.util.StrUtil; |
| 4 | -import cn.iocoder.yudao.framework.file.core.client.FileClient; | ||
| 5 | -import cn.iocoder.yudao.framework.file.core.client.FileClientConfig; | ||
| 6 | import lombok.extern.slf4j.Slf4j; | 4 | import lombok.extern.slf4j.Slf4j; |
| 7 | 5 | ||
| 8 | /** | 6 | /** |
| @@ -65,7 +63,7 @@ public abstract class AbstractFileClient<Config extends FileClientConfig> implem | @@ -65,7 +63,7 @@ public abstract class AbstractFileClient<Config extends FileClientConfig> implem | ||
| 65 | * @return URL 访问地址 | 63 | * @return URL 访问地址 |
| 66 | */ | 64 | */ |
| 67 | protected String formatFileUrl(String domain, String path) { | 65 | protected String formatFileUrl(String domain, String path) { |
| 68 | - return StrUtil.format("{}/system-api/{}/get/{}", domain, getId(), path); | 66 | + return StrUtil.format("{}/admin-api/infra/file/{}/get/{}", domain, getId(), path); |
| 69 | } | 67 | } |
| 70 | 68 | ||
| 71 | } | 69 | } |
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<DBFileClientConfig> { | @@ -18,24 +18,31 @@ public class DBFileClient extends AbstractFileClient<DBFileClientConfig> { | ||
| 18 | 18 | ||
| 19 | @Override | 19 | @Override |
| 20 | protected void doInit() { | 20 | protected void doInit() { |
| 21 | - dao = SpringUtil.getBean(DBFileContentFrameworkDAO.class); | ||
| 22 | } | 21 | } |
| 23 | 22 | ||
| 24 | @Override | 23 | @Override |
| 25 | public String upload(byte[] content, String path) { | 24 | public String upload(byte[] content, String path) { |
| 26 | - dao.insert(getId(), path, content); | 25 | + getDao().insert(getId(), path, content); |
| 27 | // 拼接返回路径 | 26 | // 拼接返回路径 |
| 28 | return super.formatFileUrl(config.getDomain(), path); | 27 | return super.formatFileUrl(config.getDomain(), path); |
| 29 | } | 28 | } |
| 30 | 29 | ||
| 31 | @Override | 30 | @Override |
| 32 | public void delete(String path) { | 31 | public void delete(String path) { |
| 33 | - dao.delete(getId(), path); | 32 | + getDao().delete(getId(), path); |
| 34 | } | 33 | } |
| 35 | 34 | ||
| 36 | @Override | 35 | @Override |
| 37 | public byte[] getContent(String path) { | 36 | public byte[] getContent(String path) { |
| 38 | - return dao.selectContent(getId(), path); | 37 | + return getDao().selectContent(getId(), path); |
| 38 | + } | ||
| 39 | + | ||
| 40 | + private DBFileContentFrameworkDAO getDao() { | ||
| 41 | + // 延迟获取,因为 SpringUtil 初始化太慢 | ||
| 42 | + if (dao == null) { | ||
| 43 | + dao = SpringUtil.getBean(DBFileContentFrameworkDAO.class); | ||
| 44 | + } | ||
| 45 | + return dao; | ||
| 39 | } | 46 | } |
| 40 | 47 | ||
| 41 | } | 48 | } |
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; | @@ -7,10 +7,30 @@ package cn.iocoder.yudao.framework.file.core.client.db; | ||
| 7 | */ | 7 | */ |
| 8 | public interface DBFileContentFrameworkDAO { | 8 | public interface DBFileContentFrameworkDAO { |
| 9 | 9 | ||
| 10 | + /** | ||
| 11 | + * 插入文件内容 | ||
| 12 | + * | ||
| 13 | + * @param configId 配置编号 | ||
| 14 | + * @param path 路径 | ||
| 15 | + * @param content 内容 | ||
| 16 | + */ | ||
| 10 | void insert(Long configId, String path, byte[] content); | 17 | void insert(Long configId, String path, byte[] content); |
| 11 | 18 | ||
| 19 | + /** | ||
| 20 | + * 删除文件内容 | ||
| 21 | + * | ||
| 22 | + * @param configId 配置编号 | ||
| 23 | + * @param path 路径 | ||
| 24 | + */ | ||
| 12 | void delete(Long configId, String path); | 25 | void delete(Long configId, String path); |
| 13 | 26 | ||
| 27 | + /** | ||
| 28 | + * 获得文件内容 | ||
| 29 | + * | ||
| 30 | + * @param configId 配置编号 | ||
| 31 | + * @param path 路径 | ||
| 32 | + * @return 内容 | ||
| 33 | + */ | ||
| 14 | byte[] selectContent(Long configId, String path); | 34 | byte[] selectContent(Long configId, String path); |
| 15 | 35 | ||
| 16 | } | 36 | } |
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; | @@ -4,8 +4,8 @@ import cn.hutool.core.io.IoUtil; | ||
| 4 | import cn.iocoder.yudao.framework.common.pojo.CommonResult; | 4 | import cn.iocoder.yudao.framework.common.pojo.CommonResult; |
| 5 | import cn.iocoder.yudao.framework.common.pojo.PageResult; | 5 | import cn.iocoder.yudao.framework.common.pojo.PageResult; |
| 6 | import cn.iocoder.yudao.framework.common.util.servlet.ServletUtils; | 6 | import cn.iocoder.yudao.framework.common.util.servlet.ServletUtils; |
| 7 | -import cn.iocoder.yudao.module.infra.controller.admin.file.vo.FilePageReqVO; | ||
| 8 | -import cn.iocoder.yudao.module.infra.controller.admin.file.vo.FileRespVO; | 7 | +import cn.iocoder.yudao.module.infra.controller.admin.file.vo.file.FilePageReqVO; |
| 8 | +import cn.iocoder.yudao.module.infra.controller.admin.file.vo.file.FileRespVO; | ||
| 9 | import cn.iocoder.yudao.module.infra.convert.file.FileConvert; | 9 | import cn.iocoder.yudao.module.infra.convert.file.FileConvert; |
| 10 | import cn.iocoder.yudao.module.infra.dal.dataobject.file.FileDO; | 10 | import cn.iocoder.yudao.module.infra.dal.dataobject.file.FileDO; |
| 11 | import cn.iocoder.yudao.module.infra.service.file.FileService; | 11 | import cn.iocoder.yudao.module.infra.service.file.FileService; |
| @@ -50,9 +50,9 @@ public class FileController { | @@ -50,9 +50,9 @@ public class FileController { | ||
| 50 | 50 | ||
| 51 | @DeleteMapping("/delete") | 51 | @DeleteMapping("/delete") |
| 52 | @ApiOperation("删除文件") | 52 | @ApiOperation("删除文件") |
| 53 | - @ApiImplicitParam(name = "id", value = "编号", required = true, dataTypeClass = String.class) | 53 | + @ApiImplicitParam(name = "id", value = "编号", required = true, dataTypeClass = Long.class) |
| 54 | @PreAuthorize("@ss.hasPermission('infra:file:delete')") | 54 | @PreAuthorize("@ss.hasPermission('infra:file:delete')") |
| 55 | - public CommonResult<Boolean> deleteFile(@RequestParam("id") String id) { | 55 | + public CommonResult<Boolean> deleteFile(@RequestParam("id") Long id) { |
| 56 | fileService.deleteFile(id); | 56 | fileService.deleteFile(id); |
| 57 | return success(true); | 57 | return success(true); |
| 58 | } | 58 | } |
| @@ -60,19 +60,19 @@ public class FileController { | @@ -60,19 +60,19 @@ public class FileController { | ||
| 60 | @GetMapping("/{configId}/get/{path}") | 60 | @GetMapping("/{configId}/get/{path}") |
| 61 | @ApiOperation("下载文件") | 61 | @ApiOperation("下载文件") |
| 62 | @ApiImplicitParams({ | 62 | @ApiImplicitParams({ |
| 63 | - @ApiImplicitParam(name = "configId", value = "配置编号", required = true, dataTypeClass = String.class), | 63 | + @ApiImplicitParam(name = "configId", value = "配置编号", required = true, dataTypeClass = Long.class), |
| 64 | @ApiImplicitParam(name = "path", value = "文件路径", required = true, dataTypeClass = String.class) | 64 | @ApiImplicitParam(name = "path", value = "文件路径", required = true, dataTypeClass = String.class) |
| 65 | }) | 65 | }) |
| 66 | - public void getFile(HttpServletResponse response, | ||
| 67 | - @PathVariable("configId") String configId, | ||
| 68 | - @PathVariable("path") String path) throws IOException { | ||
| 69 | - FileDO file = fileService.getFile(path); | ||
| 70 | - if (file == null) { | ||
| 71 | - log.warn("[getFile][path({}) 文件不存在]", path); | 66 | + public void getFileContent(HttpServletResponse response, |
| 67 | + @PathVariable("configId") Long configId, | ||
| 68 | + @PathVariable("path") String path) throws IOException { | ||
| 69 | + byte[] content = fileService.getFileContent(configId, path); | ||
| 70 | + if (content == null) { | ||
| 71 | + log.warn("[getFileContent][configId({}) path({}) 文件不存在]", configId, path); | ||
| 72 | response.setStatus(HttpStatus.NOT_FOUND.value()); | 72 | response.setStatus(HttpStatus.NOT_FOUND.value()); |
| 73 | return; | 73 | return; |
| 74 | } | 74 | } |
| 75 | - ServletUtils.writeAttachment(response, path, file.getContent()); | 75 | + ServletUtils.writeAttachment(response, path, content); |
| 76 | } | 76 | } |
| 77 | 77 | ||
| 78 | @GetMapping("/page") | 78 | @GetMapping("/page") |
yudao-module-infra/yudao-module-infra-impl/src/main/java/cn/iocoder/yudao/module/infra/controller/admin/file/vo/FilePageReqVO.java renamed to yudao-module-infra/yudao-module-infra-impl/src/main/java/cn/iocoder/yudao/module/infra/controller/admin/file/vo/file/FilePageReqVO.java
| 1 | -package cn.iocoder.yudao.module.infra.controller.admin.file.vo; | 1 | +package cn.iocoder.yudao.module.infra.controller.admin.file.vo.file; |
| 2 | 2 | ||
| 3 | import cn.iocoder.yudao.framework.common.pojo.PageParam; | 3 | import cn.iocoder.yudao.framework.common.pojo.PageParam; |
| 4 | import io.swagger.annotations.ApiModel; | 4 | import io.swagger.annotations.ApiModel; |
| @@ -19,7 +19,7 @@ import static cn.iocoder.yudao.framework.common.util.date.DateUtils.FORMAT_YEAR_ | @@ -19,7 +19,7 @@ import static cn.iocoder.yudao.framework.common.util.date.DateUtils.FORMAT_YEAR_ | ||
| 19 | public class FilePageReqVO extends PageParam { | 19 | public class FilePageReqVO extends PageParam { |
| 20 | 20 | ||
| 21 | @ApiModelProperty(value = "文件路径", example = "yudao", notes = "模糊匹配") | 21 | @ApiModelProperty(value = "文件路径", example = "yudao", notes = "模糊匹配") |
| 22 | - private String id; | 22 | + private String path; |
| 23 | 23 | ||
| 24 | @ApiModelProperty(value = "文件类型", example = "jpg", notes = "模糊匹配") | 24 | @ApiModelProperty(value = "文件类型", example = "jpg", notes = "模糊匹配") |
| 25 | private String type; | 25 | private String type; |
yudao-module-infra/yudao-module-infra-impl/src/main/java/cn/iocoder/yudao/module/infra/controller/admin/file/vo/FileRespVO.java renamed to yudao-module-infra/yudao-module-infra-impl/src/main/java/cn/iocoder/yudao/module/infra/controller/admin/file/vo/file/FileRespVO.java
| 1 | -package cn.iocoder.yudao.module.infra.controller.admin.file.vo; | 1 | +package cn.iocoder.yudao.module.infra.controller.admin.file.vo.file; |
| 2 | 2 | ||
| 3 | import io.swagger.annotations.ApiModel; | 3 | import io.swagger.annotations.ApiModel; |
| 4 | import io.swagger.annotations.ApiModelProperty; | 4 | import io.swagger.annotations.ApiModelProperty; |
| @@ -10,12 +10,21 @@ import java.util.Date; | @@ -10,12 +10,21 @@ import java.util.Date; | ||
| 10 | @Data | 10 | @Data |
| 11 | public class FileRespVO { | 11 | public class FileRespVO { |
| 12 | 12 | ||
| 13 | + @ApiModelProperty(value = "文件编号", required = true, example = "1024") | ||
| 14 | + private Long id; | ||
| 15 | + | ||
| 13 | @ApiModelProperty(value = "文件路径", required = true, example = "yudao.jpg") | 16 | @ApiModelProperty(value = "文件路径", required = true, example = "yudao.jpg") |
| 14 | - private String id; | 17 | + private String path; |
| 18 | + | ||
| 19 | + @ApiModelProperty(value = "文件 URL", required = true, example = "https://www.iocoder.cn/yudao.jpg") | ||
| 20 | + private String url; | ||
| 15 | 21 | ||
| 16 | - @ApiModelProperty(value = "文件类型", required = true, example = "jpg") | 22 | + @ApiModelProperty(value = "文件类型", example = "jpg") |
| 17 | private String type; | 23 | private String type; |
| 18 | 24 | ||
| 25 | + @ApiModelProperty(value = "文件大小", example = "2048", required = true) | ||
| 26 | + private Integer size; | ||
| 27 | + | ||
| 19 | @ApiModelProperty(value = "创建时间", required = true) | 28 | @ApiModelProperty(value = "创建时间", required = true) |
| 20 | private Date createTime; | 29 | private Date createTime; |
| 21 | 30 |
yudao-module-infra/yudao-module-infra-impl/src/main/java/cn/iocoder/yudao/module/infra/convert/file/FileConvert.java
| 1 | package cn.iocoder.yudao.module.infra.convert.file; | 1 | package cn.iocoder.yudao.module.infra.convert.file; |
| 2 | 2 | ||
| 3 | import cn.iocoder.yudao.framework.common.pojo.PageResult; | 3 | import cn.iocoder.yudao.framework.common.pojo.PageResult; |
| 4 | -import cn.iocoder.yudao.module.infra.controller.admin.file.vo.FileRespVO; | 4 | +import cn.iocoder.yudao.module.infra.controller.admin.file.vo.file.FileRespVO; |
| 5 | import cn.iocoder.yudao.module.infra.dal.dataobject.file.FileDO; | 5 | import cn.iocoder.yudao.module.infra.dal.dataobject.file.FileDO; |
| 6 | import org.mapstruct.Mapper; | 6 | import org.mapstruct.Mapper; |
| 7 | import org.mapstruct.factory.Mappers; | 7 | import org.mapstruct.factory.Mappers; |
yudao-module-infra/yudao-module-infra-impl/src/main/java/cn/iocoder/yudao/module/infra/dal/dataobject/file/FileDO.java
| 1 | package cn.iocoder.yudao.module.infra.dal.dataobject.file; | 1 | package cn.iocoder.yudao.module.infra.dal.dataobject.file; |
| 2 | 2 | ||
| 3 | import cn.iocoder.yudao.framework.mybatis.core.dataobject.BaseDO; | 3 | import cn.iocoder.yudao.framework.mybatis.core.dataobject.BaseDO; |
| 4 | -import com.baomidou.mybatisplus.annotation.IdType; | ||
| 5 | import com.baomidou.mybatisplus.annotation.TableField; | 4 | import com.baomidou.mybatisplus.annotation.TableField; |
| 6 | -import com.baomidou.mybatisplus.annotation.TableId; | ||
| 7 | import com.baomidou.mybatisplus.annotation.TableName; | 5 | import com.baomidou.mybatisplus.annotation.TableName; |
| 8 | import lombok.*; | 6 | import lombok.*; |
| 9 | 7 | ||
| @@ -27,8 +25,7 @@ public class FileDO extends BaseDO { | @@ -27,8 +25,7 @@ public class FileDO extends BaseDO { | ||
| 27 | /** | 25 | /** |
| 28 | * 编号,数据库自增 | 26 | * 编号,数据库自增 |
| 29 | */ | 27 | */ |
| 30 | - @TableId(type = IdType.INPUT) | ||
| 31 | - private String id; | 28 | + private Long id; |
| 32 | /** | 29 | /** |
| 33 | * 配置编号 | 30 | * 配置编号 |
| 34 | * | 31 | * |
| @@ -40,6 +37,10 @@ public class FileDO extends BaseDO { | @@ -40,6 +37,10 @@ public class FileDO extends BaseDO { | ||
| 40 | */ | 37 | */ |
| 41 | private String path; | 38 | private String path; |
| 42 | /** | 39 | /** |
| 40 | + * 访问地址 | ||
| 41 | + */ | ||
| 42 | + private String url; | ||
| 43 | + /** | ||
| 43 | * 文件类型 | 44 | * 文件类型 |
| 44 | * | 45 | * |
| 45 | * 通过 {@link cn.hutool.core.io.FileTypeUtil#getType(InputStream)} 获取 | 46 | * 通过 {@link cn.hutool.core.io.FileTypeUtil#getType(InputStream)} 获取 |
| @@ -47,17 +48,8 @@ public class FileDO extends BaseDO { | @@ -47,17 +48,8 @@ public class FileDO extends BaseDO { | ||
| 47 | @TableField(value = "`type`") | 48 | @TableField(value = "`type`") |
| 48 | private String type; | 49 | private String type; |
| 49 | /** | 50 | /** |
| 50 | - * 访问地址 | ||
| 51 | - */ | ||
| 52 | - private String url; | ||
| 53 | - /** | ||
| 54 | * 文件大小 | 51 | * 文件大小 |
| 55 | */ | 52 | */ |
| 56 | private Integer size; | 53 | private Integer size; |
| 57 | - /** | ||
| 58 | - * 文件内容 | ||
| 59 | - */ | ||
| 60 | - @Deprecated | ||
| 61 | - private byte[] content; | ||
| 62 | 54 | ||
| 63 | } | 55 | } |
yudao-module-infra/yudao-module-infra-impl/src/main/java/cn/iocoder/yudao/module/infra/dal/mysql/file/FileContentDAOImpl.java
0 → 100644
| 1 | +package cn.iocoder.yudao.module.infra.dal.mysql.file; | ||
| 2 | + | ||
| 3 | +import cn.iocoder.yudao.framework.file.core.client.db.DBFileContentFrameworkDAO; | ||
| 4 | +import cn.iocoder.yudao.module.infra.dal.dataobject.file.FileContentDO; | ||
| 5 | +import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; | ||
| 6 | +import org.springframework.stereotype.Repository; | ||
| 7 | + | ||
| 8 | +import javax.annotation.Resource; | ||
| 9 | + | ||
| 10 | +@Repository | ||
| 11 | +public class FileContentDAOImpl implements DBFileContentFrameworkDAO { | ||
| 12 | + | ||
| 13 | + @Resource | ||
| 14 | + private FileContentMapper fileContentMapper; | ||
| 15 | + | ||
| 16 | + @Override | ||
| 17 | + public void insert(Long configId, String path, byte[] content) { | ||
| 18 | + FileContentDO entity = new FileContentDO().setConfigId(configId) | ||
| 19 | + .setPath(path).setContent(content); | ||
| 20 | + fileContentMapper.insert(entity); | ||
| 21 | + } | ||
| 22 | + | ||
| 23 | + @Override | ||
| 24 | + public void delete(Long configId, String path) { | ||
| 25 | + fileContentMapper.delete(buildQuery(configId, path)); | ||
| 26 | + } | ||
| 27 | + | ||
| 28 | + @Override | ||
| 29 | + public byte[] selectContent(Long configId, String path) { | ||
| 30 | + FileContentDO fileContentDO = fileContentMapper.selectOne( | ||
| 31 | + buildQuery(configId, path).select(FileContentDO::getContent)); | ||
| 32 | + return fileContentDO != null ? fileContentDO.getContent() : null; | ||
| 33 | + } | ||
| 34 | + | ||
| 35 | + private LambdaQueryWrapper<FileContentDO> buildQuery(Long configId, String path) { | ||
| 36 | + return new LambdaQueryWrapper<FileContentDO>() | ||
| 37 | + .eq(FileContentDO::getConfigId, configId) | ||
| 38 | + .eq(FileContentDO::getPath, path); | ||
| 39 | + } | ||
| 40 | + | ||
| 41 | +} |
yudao-module-infra/yudao-module-infra-impl/src/main/java/cn/iocoder/yudao/module/infra/dal/mysql/file/FileContentMapper.java
0 → 100644
| 1 | +package cn.iocoder.yudao.module.infra.dal.mysql.file; | ||
| 2 | + | ||
| 3 | +import cn.iocoder.yudao.module.infra.dal.dataobject.file.FileContentDO; | ||
| 4 | +import com.baomidou.mybatisplus.core.mapper.BaseMapper; | ||
| 5 | +import org.apache.ibatis.annotations.Mapper; | ||
| 6 | + | ||
| 7 | +@Mapper | ||
| 8 | +public interface FileContentMapper extends BaseMapper<FileContentDO> { | ||
| 9 | +} |
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; | @@ -3,7 +3,7 @@ package cn.iocoder.yudao.module.infra.dal.mysql.file; | ||
| 3 | import cn.iocoder.yudao.framework.common.pojo.PageResult; | 3 | import cn.iocoder.yudao.framework.common.pojo.PageResult; |
| 4 | import cn.iocoder.yudao.framework.mybatis.core.mapper.BaseMapperX; | 4 | import cn.iocoder.yudao.framework.mybatis.core.mapper.BaseMapperX; |
| 5 | import cn.iocoder.yudao.framework.mybatis.core.query.QueryWrapperX; | 5 | import cn.iocoder.yudao.framework.mybatis.core.query.QueryWrapperX; |
| 6 | -import cn.iocoder.yudao.module.infra.controller.admin.file.vo.FilePageReqVO; | 6 | +import cn.iocoder.yudao.module.infra.controller.admin.file.vo.file.FilePageReqVO; |
| 7 | import cn.iocoder.yudao.module.infra.dal.dataobject.file.FileDO; | 7 | import cn.iocoder.yudao.module.infra.dal.dataobject.file.FileDO; |
| 8 | import org.apache.ibatis.annotations.Mapper; | 8 | import org.apache.ibatis.annotations.Mapper; |
| 9 | 9 | ||
| @@ -17,25 +17,10 @@ public interface FileMapper extends BaseMapperX<FileDO> { | @@ -17,25 +17,10 @@ public interface FileMapper extends BaseMapperX<FileDO> { | ||
| 17 | 17 | ||
| 18 | default PageResult<FileDO> selectPage(FilePageReqVO reqVO) { | 18 | default PageResult<FileDO> selectPage(FilePageReqVO reqVO) { |
| 19 | return selectPage(reqVO, new QueryWrapperX<FileDO>() | 19 | return selectPage(reqVO, new QueryWrapperX<FileDO>() |
| 20 | - .likeIfPresent("id", reqVO.getId()) | 20 | + .likeIfPresent("path", reqVO.getPath()) |
| 21 | .likeIfPresent("type", reqVO.getType()) | 21 | .likeIfPresent("type", reqVO.getType()) |
| 22 | .betweenIfPresent("create_time", reqVO.getBeginCreateTime(), reqVO.getEndCreateTime()) | 22 | .betweenIfPresent("create_time", reqVO.getBeginCreateTime(), reqVO.getEndCreateTime()) |
| 23 | .orderByDesc("create_time")); | 23 | .orderByDesc("create_time")); |
| 24 | } | 24 | } |
| 25 | 25 | ||
| 26 | - default Long selectCountById(String id) { | ||
| 27 | - return selectCount(FileDO::getId, id); | ||
| 28 | - } | ||
| 29 | - | ||
| 30 | - /** | ||
| 31 | - * 基于 Path 获取文件 | ||
| 32 | - * 实际上,是基于 ID 查询 | ||
| 33 | - * | ||
| 34 | - * @param path 路径 | ||
| 35 | - * @return 文件 | ||
| 36 | - */ | ||
| 37 | - default FileDO selectByPath(String path) { | ||
| 38 | - return selectById(path); | ||
| 39 | - } | ||
| 40 | - | ||
| 41 | } | 26 | } |
yudao-module-infra/yudao-module-infra-impl/src/main/java/cn/iocoder/yudao/module/infra/framework/file/config/FileConfiguration.java deleted
| 1 | -package cn.iocoder.yudao.module.infra.framework.file.config; | ||
| 2 | - | ||
| 3 | -import org.springframework.boot.context.properties.EnableConfigurationProperties; | ||
| 4 | -import org.springframework.context.annotation.Configuration; | ||
| 5 | - | ||
| 6 | -/** | ||
| 7 | - * 文件 配置类 | ||
| 8 | - */ | ||
| 9 | -@Configuration | ||
| 10 | -@EnableConfigurationProperties(FileProperties.class) | ||
| 11 | -public class FileConfiguration { | ||
| 12 | -} |
yudao-module-infra/yudao-module-infra-impl/src/main/java/cn/iocoder/yudao/module/infra/framework/file/config/FileProperties.java deleted
| 1 | -package cn.iocoder.yudao.module.infra.framework.file.config; | ||
| 2 | - | ||
| 3 | -import lombok.Data; | ||
| 4 | -import org.springframework.boot.context.properties.ConfigurationProperties; | ||
| 5 | -import org.springframework.validation.annotation.Validated; | ||
| 6 | - | ||
| 7 | -import javax.validation.constraints.NotNull; | ||
| 8 | - | ||
| 9 | -@ConfigurationProperties(prefix = "yudao.file") | ||
| 10 | -@Validated | ||
| 11 | -@Data | ||
| 12 | -public class FileProperties { | ||
| 13 | - | ||
| 14 | - /** | ||
| 15 | - * 对应 FileController 的 getFile 方法 | ||
| 16 | - */ | ||
| 17 | - @NotNull(message = "基础文件路径不能为空") | ||
| 18 | - private String basePath; | ||
| 19 | - | ||
| 20 | - // TODO 七牛、等等 | ||
| 21 | - | ||
| 22 | -} |
yudao-module-infra/yudao-module-infra-impl/src/main/java/cn/iocoder/yudao/module/infra/framework/file/package-info.java deleted
| 1 | -/** | ||
| 2 | - * 文件的存储,推荐使用七牛、阿里云、华为云、腾讯云等文件服务 | ||
| 3 | - * | ||
| 4 | - * 在不采用云服务的情况下,我们有几种技术选型: | ||
| 5 | - * 方案 1. 使用自建的文件服务,例如说 minIO、FastDFS 等等 | ||
| 6 | - * 方案 2. 使用服务器的文件系统存储 | ||
| 7 | - * 方案 3. 使用数据库进行存储 | ||
| 8 | - * | ||
| 9 | - * 如果考虑额外在搭建服务,推荐方案 1。 | ||
| 10 | - * 对于方案 2 来说,如果要实现文件存储的高可用,需要多台服务器之间做实时同步,可以基于 rsync + inotify 来做 | ||
| 11 | - * 对于方案 3 的话,实现起来最简单,但是数据库本身不适合存储海量的文件 | ||
| 12 | - * | ||
| 13 | - * 综合考虑,暂时使用方案 3 的方式,比较适合这样一个 all in one 的项目。 | ||
| 14 | - * 随着文件的量级大了之后,还是推荐采用云服务。 | ||
| 15 | - */ | ||
| 16 | -package cn.iocoder.yudao.module.infra.framework.file; |
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 { | @@ -36,7 +36,7 @@ public class SecurityConfiguration { | ||
| 36 | registry.antMatchers(adminSeverContextPath).anonymous() | 36 | registry.antMatchers(adminSeverContextPath).anonymous() |
| 37 | .antMatchers(adminSeverContextPath + "/**").anonymous(); | 37 | .antMatchers(adminSeverContextPath + "/**").anonymous(); |
| 38 | // 文件的获取接口,可匿名访问 | 38 | // 文件的获取接口,可匿名访问 |
| 39 | - registry.antMatchers(buildAdminApi("/infra/file/get/**"), buildAppApi("/infra/file/get/**")).anonymous(); | 39 | + registry.antMatchers(buildAdminApi("/infra/file/*/get/**"), buildAppApi("/infra/file/get/**")).permitAll(); |
| 40 | } | 40 | } |
| 41 | 41 | ||
| 42 | }; | 42 | }; |
yudao-module-infra/yudao-module-infra-impl/src/main/java/cn/iocoder/yudao/module/infra/service/file/FileConfigService.java
| 1 | package cn.iocoder.yudao.module.infra.service.file; | 1 | package cn.iocoder.yudao.module.infra.service.file; |
| 2 | 2 | ||
| 3 | import cn.iocoder.yudao.framework.common.pojo.PageResult; | 3 | import cn.iocoder.yudao.framework.common.pojo.PageResult; |
| 4 | +import cn.iocoder.yudao.framework.file.core.client.FileClient; | ||
| 4 | import cn.iocoder.yudao.module.infra.controller.admin.file.vo.config.FileConfigCreateReqVO; | 5 | import cn.iocoder.yudao.module.infra.controller.admin.file.vo.config.FileConfigCreateReqVO; |
| 5 | import cn.iocoder.yudao.module.infra.controller.admin.file.vo.config.FileConfigPageReqVO; | 6 | import cn.iocoder.yudao.module.infra.controller.admin.file.vo.config.FileConfigPageReqVO; |
| 6 | import cn.iocoder.yudao.module.infra.controller.admin.file.vo.config.FileConfigUpdateReqVO; | 7 | import cn.iocoder.yudao.module.infra.controller.admin.file.vo.config.FileConfigUpdateReqVO; |
| @@ -83,4 +84,19 @@ public interface FileConfigService { | @@ -83,4 +84,19 @@ public interface FileConfigService { | ||
| 83 | */ | 84 | */ |
| 84 | String testFileConfig(Long id); | 85 | String testFileConfig(Long id); |
| 85 | 86 | ||
| 87 | + /** | ||
| 88 | + * 获得指定编号的文件客户端 | ||
| 89 | + * | ||
| 90 | + * @param id 配置编号 | ||
| 91 | + * @return 文件客户端 | ||
| 92 | + */ | ||
| 93 | + FileClient getFileClient(Long id); | ||
| 94 | + | ||
| 95 | + /** | ||
| 96 | + * 获得 Master 文件客户端 | ||
| 97 | + * | ||
| 98 | + * @return 文件客户端 | ||
| 99 | + */ | ||
| 100 | + FileClient getMasterFileClient(); | ||
| 101 | + | ||
| 86 | } | 102 | } |
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 { | @@ -233,4 +233,9 @@ public class FileConfigServiceImpl implements FileConfigService { | ||
| 233 | return fileClientFactory.getFileClient(id).upload(content, IdUtil.fastSimpleUUID() + ".jpg"); | 233 | return fileClientFactory.getFileClient(id).upload(content, IdUtil.fastSimpleUUID() + ".jpg"); |
| 234 | } | 234 | } |
| 235 | 235 | ||
| 236 | + @Override | ||
| 237 | + public FileClient getFileClient(Long id) { | ||
| 238 | + return fileClientFactory.getFileClient(id); | ||
| 239 | + } | ||
| 240 | + | ||
| 236 | } | 241 | } |
yudao-module-infra/yudao-module-infra-impl/src/main/java/cn/iocoder/yudao/module/infra/service/file/FileService.java
| 1 | package cn.iocoder.yudao.module.infra.service.file; | 1 | package cn.iocoder.yudao.module.infra.service.file; |
| 2 | 2 | ||
| 3 | -import cn.iocoder.yudao.module.infra.controller.admin.file.vo.FilePageReqVO; | 3 | +import cn.iocoder.yudao.module.infra.controller.admin.file.vo.file.FilePageReqVO; |
| 4 | import cn.iocoder.yudao.framework.common.pojo.PageResult; | 4 | import cn.iocoder.yudao.framework.common.pojo.PageResult; |
| 5 | import cn.iocoder.yudao.module.infra.dal.dataobject.file.FileDO; | 5 | import cn.iocoder.yudao.module.infra.dal.dataobject.file.FileDO; |
| 6 | 6 | ||
| @@ -33,14 +33,15 @@ public interface FileService { | @@ -33,14 +33,15 @@ public interface FileService { | ||
| 33 | * | 33 | * |
| 34 | * @param id 编号 | 34 | * @param id 编号 |
| 35 | */ | 35 | */ |
| 36 | - void deleteFile(String id); | 36 | + void deleteFile(Long id); |
| 37 | 37 | ||
| 38 | /** | 38 | /** |
| 39 | - * 获得文件 | 39 | + * 获得文件内容 |
| 40 | * | 40 | * |
| 41 | + * @param configId 配置编号 | ||
| 41 | * @param path 文件路径 | 42 | * @param path 文件路径 |
| 42 | - * @return 文件 | 43 | + * @return 文件内容 |
| 43 | */ | 44 | */ |
| 44 | - FileDO getFile(String path); | 45 | + byte[] getFileContent(Long configId, String path); |
| 45 | 46 | ||
| 46 | } | 47 | } |
yudao-module-infra/yudao-module-infra-impl/src/main/java/cn/iocoder/yudao/module/infra/service/file/FileServiceImpl.java
| 1 | package cn.iocoder.yudao.module.infra.service.file; | 1 | package cn.iocoder.yudao.module.infra.service.file; |
| 2 | 2 | ||
| 3 | import cn.hutool.core.io.FileTypeUtil; | 3 | import cn.hutool.core.io.FileTypeUtil; |
| 4 | +import cn.hutool.core.lang.Assert; | ||
| 4 | import cn.iocoder.yudao.framework.common.pojo.PageResult; | 5 | import cn.iocoder.yudao.framework.common.pojo.PageResult; |
| 5 | -import cn.iocoder.yudao.module.infra.controller.admin.file.vo.FilePageReqVO; | 6 | +import cn.iocoder.yudao.framework.file.core.client.FileClient; |
| 7 | +import cn.iocoder.yudao.module.infra.controller.admin.file.vo.file.FilePageReqVO; | ||
| 6 | import cn.iocoder.yudao.module.infra.dal.dataobject.file.FileDO; | 8 | import cn.iocoder.yudao.module.infra.dal.dataobject.file.FileDO; |
| 7 | import cn.iocoder.yudao.module.infra.dal.mysql.file.FileMapper; | 9 | import cn.iocoder.yudao.module.infra.dal.mysql.file.FileMapper; |
| 8 | -import cn.iocoder.yudao.module.infra.framework.file.config.FileProperties; | ||
| 9 | import org.springframework.stereotype.Service; | 10 | import org.springframework.stereotype.Service; |
| 10 | 11 | ||
| 11 | import javax.annotation.Resource; | 12 | import javax.annotation.Resource; |
| 12 | import java.io.ByteArrayInputStream; | 13 | import java.io.ByteArrayInputStream; |
| 13 | 14 | ||
| 14 | import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception; | 15 | import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception; |
| 15 | -import static cn.iocoder.yudao.module.infra.enums.ErrorCodeConstants.*; | 16 | +import static cn.iocoder.yudao.module.infra.enums.ErrorCodeConstants.FILE_NOT_EXISTS; |
| 16 | 17 | ||
| 17 | /** | 18 | /** |
| 18 | * 文件 Service 实现类 | 19 | * 文件 Service 实现类 |
| @@ -23,10 +24,10 @@ import static cn.iocoder.yudao.module.infra.enums.ErrorCodeConstants.*; | @@ -23,10 +24,10 @@ import static cn.iocoder.yudao.module.infra.enums.ErrorCodeConstants.*; | ||
| 23 | public class FileServiceImpl implements FileService { | 24 | public class FileServiceImpl implements FileService { |
| 24 | 25 | ||
| 25 | @Resource | 26 | @Resource |
| 26 | - private FileMapper fileMapper; | 27 | + private FileConfigService fileConfigService; |
| 27 | 28 | ||
| 28 | @Resource | 29 | @Resource |
| 29 | - private FileProperties fileProperties; | 30 | + private FileMapper fileMapper; |
| 30 | 31 | ||
| 31 | @Override | 32 | @Override |
| 32 | public PageResult<FileDO> getFilePage(FilePageReqVO pageReqVO) { | 33 | public PageResult<FileDO> getFilePage(FilePageReqVO pageReqVO) { |
| @@ -35,36 +36,49 @@ public class FileServiceImpl implements FileService { | @@ -35,36 +36,49 @@ public class FileServiceImpl implements FileService { | ||
| 35 | 36 | ||
| 36 | @Override | 37 | @Override |
| 37 | public String createFile(String path, byte[] content) { | 38 | public String createFile(String path, byte[] content) { |
| 38 | - if (fileMapper.selectCountById(path) > 0) { | ||
| 39 | - throw exception(FILE_PATH_EXISTS); | ||
| 40 | - } | 39 | + // 上传到文件存储器 |
| 40 | + FileClient client = fileConfigService.getMasterFileClient(); | ||
| 41 | + Assert.notNull(client, "客户端(master) 不能为空"); | ||
| 42 | + String url = client.upload(content, path); | ||
| 43 | + | ||
| 41 | // 保存到数据库 | 44 | // 保存到数据库 |
| 42 | FileDO file = new FileDO(); | 45 | FileDO file = new FileDO(); |
| 43 | - file.setId(path); | 46 | + file.setConfigId(client.getId()); |
| 47 | + file.setPath(path); | ||
| 48 | + file.setUrl(url); | ||
| 44 | file.setType(FileTypeUtil.getType(new ByteArrayInputStream(content))); | 49 | file.setType(FileTypeUtil.getType(new ByteArrayInputStream(content))); |
| 45 | - file.setContent(content); | 50 | + file.setSize(content.length); |
| 46 | fileMapper.insert(file); | 51 | fileMapper.insert(file); |
| 47 | - // 拼接路径返回 | ||
| 48 | - return fileProperties.getBasePath() + path; | 52 | + return url; |
| 49 | } | 53 | } |
| 50 | 54 | ||
| 51 | @Override | 55 | @Override |
| 52 | - public void deleteFile(String id) { | 56 | + public void deleteFile(Long id) { |
| 53 | // 校验存在 | 57 | // 校验存在 |
| 54 | - this.validateFileExists(id); | ||
| 55 | - // 更新 | 58 | + FileDO file = this.validateFileExists(id); |
| 59 | + | ||
| 60 | + // 从文件存储器中删除 | ||
| 61 | + FileClient client = fileConfigService.getFileClient(file.getConfigId()); | ||
| 62 | + Assert.notNull(client, "客户端({}) 不能为空", file.getConfigId()); | ||
| 63 | + client.delete(file.getPath()); | ||
| 64 | + | ||
| 65 | + // 删除记录 | ||
| 56 | fileMapper.deleteById(id); | 66 | fileMapper.deleteById(id); |
| 57 | } | 67 | } |
| 58 | 68 | ||
| 59 | - private void validateFileExists(String id) { | ||
| 60 | - if (fileMapper.selectById(id) == null) { | 69 | + private FileDO validateFileExists(Long id) { |
| 70 | + FileDO fileDO = fileMapper.selectById(id); | ||
| 71 | + if (fileDO == null) { | ||
| 61 | throw exception(FILE_NOT_EXISTS); | 72 | throw exception(FILE_NOT_EXISTS); |
| 62 | } | 73 | } |
| 74 | + return fileDO; | ||
| 63 | } | 75 | } |
| 64 | 76 | ||
| 65 | @Override | 77 | @Override |
| 66 | - public FileDO getFile(String path) { | ||
| 67 | - return fileMapper.selectByPath(path); | 78 | + public byte[] getFileContent(Long configId, String path) { |
| 79 | + FileClient client = fileConfigService.getFileClient(configId); | ||
| 80 | + Assert.notNull(client, "客户端({}) 不能为空", configId); | ||
| 81 | + return client.getContent(path); | ||
| 68 | } | 82 | } |
| 69 | 83 | ||
| 70 | } | 84 | } |
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; | @@ -3,11 +3,11 @@ package cn.iocoder.yudao.module.infra.service.file; | ||
| 3 | import cn.hutool.core.io.resource.ResourceUtil; | 3 | import cn.hutool.core.io.resource.ResourceUtil; |
| 4 | import cn.iocoder.yudao.framework.common.pojo.PageResult; | 4 | import cn.iocoder.yudao.framework.common.pojo.PageResult; |
| 5 | import cn.iocoder.yudao.framework.common.util.object.ObjectUtils; | 5 | import cn.iocoder.yudao.framework.common.util.object.ObjectUtils; |
| 6 | +import cn.iocoder.yudao.framework.file.core.client.FileClient; | ||
| 6 | import cn.iocoder.yudao.framework.test.core.util.AssertUtils; | 7 | import cn.iocoder.yudao.framework.test.core.util.AssertUtils; |
| 7 | -import cn.iocoder.yudao.module.infra.controller.admin.file.vo.FilePageReqVO; | 8 | +import cn.iocoder.yudao.module.infra.controller.admin.file.vo.file.FilePageReqVO; |
| 8 | import cn.iocoder.yudao.module.infra.dal.dataobject.file.FileDO; | 9 | import cn.iocoder.yudao.module.infra.dal.dataobject.file.FileDO; |
| 9 | import cn.iocoder.yudao.module.infra.dal.mysql.file.FileMapper; | 10 | import cn.iocoder.yudao.module.infra.dal.mysql.file.FileMapper; |
| 10 | -import cn.iocoder.yudao.module.infra.framework.file.config.FileProperties; | ||
| 11 | import cn.iocoder.yudao.module.infra.test.BaseDbUnitTest; | 11 | import cn.iocoder.yudao.module.infra.test.BaseDbUnitTest; |
| 12 | import org.junit.jupiter.api.Test; | 12 | import org.junit.jupiter.api.Test; |
| 13 | import org.springframework.boot.test.mock.mockito.MockBean; | 13 | import org.springframework.boot.test.mock.mockito.MockBean; |
| @@ -17,47 +17,46 @@ import javax.annotation.Resource; | @@ -17,47 +17,46 @@ import javax.annotation.Resource; | ||
| 17 | 17 | ||
| 18 | import static cn.iocoder.yudao.framework.common.util.date.DateUtils.buildTime; | 18 | import static cn.iocoder.yudao.framework.common.util.date.DateUtils.buildTime; |
| 19 | import static cn.iocoder.yudao.framework.test.core.util.AssertUtils.assertServiceException; | 19 | import static cn.iocoder.yudao.framework.test.core.util.AssertUtils.assertServiceException; |
| 20 | -import static cn.iocoder.yudao.framework.test.core.util.RandomUtils.randomPojo; | ||
| 21 | -import static cn.iocoder.yudao.framework.test.core.util.RandomUtils.randomString; | 20 | +import static cn.iocoder.yudao.framework.test.core.util.RandomUtils.*; |
| 22 | import static cn.iocoder.yudao.module.infra.enums.ErrorCodeConstants.*; | 21 | import static cn.iocoder.yudao.module.infra.enums.ErrorCodeConstants.*; |
| 23 | import static org.junit.jupiter.api.Assertions.*; | 22 | import static org.junit.jupiter.api.Assertions.*; |
| 23 | +import static org.mockito.ArgumentMatchers.same; | ||
| 24 | +import static org.mockito.Mockito.*; | ||
| 24 | 25 | ||
| 25 | -@Import({FileServiceImpl.class, FileProperties.class}) | 26 | +@Import({FileServiceImpl.class}) |
| 26 | public class FileServiceTest extends BaseDbUnitTest { | 27 | public class FileServiceTest extends BaseDbUnitTest { |
| 27 | 28 | ||
| 28 | @Resource | 29 | @Resource |
| 29 | private FileService fileService; | 30 | private FileService fileService; |
| 30 | 31 | ||
| 31 | - @MockBean | ||
| 32 | - private FileProperties fileProperties; | ||
| 33 | - | ||
| 34 | @Resource | 32 | @Resource |
| 35 | private FileMapper fileMapper; | 33 | private FileMapper fileMapper; |
| 36 | 34 | ||
| 35 | + @MockBean | ||
| 36 | + private FileConfigService fileConfigService; | ||
| 37 | + | ||
| 37 | @Test | 38 | @Test |
| 38 | public void testGetFilePage() { | 39 | public void testGetFilePage() { |
| 39 | // mock 数据 | 40 | // mock 数据 |
| 40 | FileDO dbFile = randomPojo(FileDO.class, o -> { // 等会查询到 | 41 | FileDO dbFile = randomPojo(FileDO.class, o -> { // 等会查询到 |
| 41 | - o.setId("yunai"); | 42 | + o.setPath("yunai"); |
| 42 | o.setType("jpg"); | 43 | o.setType("jpg"); |
| 43 | o.setCreateTime(buildTime(2021, 1, 15)); | 44 | o.setCreateTime(buildTime(2021, 1, 15)); |
| 44 | }); | 45 | }); |
| 45 | fileMapper.insert(dbFile); | 46 | fileMapper.insert(dbFile); |
| 46 | - // 测试 id 不匹配 | ||
| 47 | - fileMapper.insert(ObjectUtils.cloneIgnoreId(dbFile, o -> o.setId("tudou"))); | 47 | + // 测试 path 不匹配 |
| 48 | + fileMapper.insert(ObjectUtils.cloneIgnoreId(dbFile, o -> o.setPath("tudou"))); | ||
| 48 | // 测试 type 不匹配 | 49 | // 测试 type 不匹配 |
| 49 | fileMapper.insert(ObjectUtils.cloneIgnoreId(dbFile, o -> { | 50 | fileMapper.insert(ObjectUtils.cloneIgnoreId(dbFile, o -> { |
| 50 | - o.setId("yunai02"); | ||
| 51 | o.setType("png"); | 51 | o.setType("png"); |
| 52 | })); | 52 | })); |
| 53 | // 测试 createTime 不匹配 | 53 | // 测试 createTime 不匹配 |
| 54 | fileMapper.insert(ObjectUtils.cloneIgnoreId(dbFile, o -> { | 54 | fileMapper.insert(ObjectUtils.cloneIgnoreId(dbFile, o -> { |
| 55 | - o.setId("yunai03"); | ||
| 56 | o.setCreateTime(buildTime(2020, 1, 15)); | 55 | o.setCreateTime(buildTime(2020, 1, 15)); |
| 57 | })); | 56 | })); |
| 58 | // 准备参数 | 57 | // 准备参数 |
| 59 | FilePageReqVO reqVO = new FilePageReqVO(); | 58 | FilePageReqVO reqVO = new FilePageReqVO(); |
| 60 | - reqVO.setId("yunai"); | 59 | + reqVO.setPath("yunai"); |
| 61 | reqVO.setType("jp"); | 60 | reqVO.setType("jp"); |
| 62 | reqVO.setBeginCreateTime(buildTime(2021, 1, 10)); | 61 | reqVO.setBeginCreateTime(buildTime(2021, 1, 10)); |
| 63 | reqVO.setEndCreateTime(buildTime(2021, 1, 20)); | 62 | reqVO.setEndCreateTime(buildTime(2021, 1, 20)); |
| @@ -67,7 +66,7 @@ public class FileServiceTest extends BaseDbUnitTest { | @@ -67,7 +66,7 @@ public class FileServiceTest extends BaseDbUnitTest { | ||
| 67 | // 断言 | 66 | // 断言 |
| 68 | assertEquals(1, pageResult.getTotal()); | 67 | assertEquals(1, pageResult.getTotal()); |
| 69 | assertEquals(1, pageResult.getList().size()); | 68 | assertEquals(1, pageResult.getList().size()); |
| 70 | - AssertUtils.assertPojoEquals(dbFile, pageResult.getList().get(0), "content"); | 69 | + AssertUtils.assertPojoEquals(dbFile, pageResult.getList().get(0)); |
| 71 | } | 70 | } |
| 72 | 71 | ||
| 73 | @Test | 72 | @Test |
| @@ -75,52 +74,68 @@ public class FileServiceTest extends BaseDbUnitTest { | @@ -75,52 +74,68 @@ public class FileServiceTest extends BaseDbUnitTest { | ||
| 75 | // 准备参数 | 74 | // 准备参数 |
| 76 | String path = randomString(); | 75 | String path = randomString(); |
| 77 | byte[] content = ResourceUtil.readBytes("file/erweima.jpg"); | 76 | byte[] content = ResourceUtil.readBytes("file/erweima.jpg"); |
| 77 | + // mock Master 文件客户端 | ||
| 78 | + FileClient client = mock(FileClient.class); | ||
| 79 | + when(fileConfigService.getMasterFileClient()).thenReturn(client); | ||
| 80 | + String url = randomString(); | ||
| 81 | + when(client.upload(same(content), same(path))).thenReturn(url); | ||
| 82 | + when(client.getId()).thenReturn(10L); | ||
| 78 | 83 | ||
| 79 | // 调用 | 84 | // 调用 |
| 80 | - String url = fileService.createFile(path, content); | 85 | + String result = fileService.createFile(path, content); |
| 81 | // 断言 | 86 | // 断言 |
| 82 | - assertEquals(fileProperties.getBasePath() + path, url); | 87 | + assertEquals(result, url); |
| 83 | // 校验数据 | 88 | // 校验数据 |
| 84 | - FileDO file = fileMapper.selectById(path); | ||
| 85 | - assertEquals(path, file.getId()); | 89 | + FileDO file = fileMapper.selectOne(FileDO::getPath, path); |
| 90 | + assertEquals(10L, file.getConfigId()); | ||
| 91 | + assertEquals(path, file.getPath()); | ||
| 92 | + assertEquals(url, file.getUrl()); | ||
| 86 | assertEquals("jpg", file.getType()); | 93 | assertEquals("jpg", file.getType()); |
| 87 | - assertArrayEquals(content, file.getContent()); | ||
| 88 | - } | ||
| 89 | - | ||
| 90 | - @Test | ||
| 91 | - public void testCreateFile_exists() { | ||
| 92 | - // mock 数据 | ||
| 93 | - FileDO dbFile = randomPojo(FileDO.class); | ||
| 94 | - fileMapper.insert(dbFile); | ||
| 95 | - // 准备参数 | ||
| 96 | - String path = dbFile.getId(); // 模拟已存在 | ||
| 97 | - byte[] content = ResourceUtil.readBytes("file/erweima.jpg"); | ||
| 98 | - | ||
| 99 | - // 调用,并断言异常 | ||
| 100 | - assertServiceException(() -> fileService.createFile(path, content), FILE_PATH_EXISTS); | 94 | + assertEquals(content.length, file.getSize()); |
| 101 | } | 95 | } |
| 102 | 96 | ||
| 103 | @Test | 97 | @Test |
| 104 | public void testDeleteFile_success() { | 98 | public void testDeleteFile_success() { |
| 105 | // mock 数据 | 99 | // mock 数据 |
| 106 | - FileDO dbFile = randomPojo(FileDO.class); | 100 | + FileDO dbFile = randomPojo(FileDO.class, o -> o.setConfigId(10L).setPath("tudou.jpg")); |
| 107 | fileMapper.insert(dbFile);// @Sql: 先插入出一条存在的数据 | 101 | fileMapper.insert(dbFile);// @Sql: 先插入出一条存在的数据 |
| 102 | + // mock Master 文件客户端 | ||
| 103 | + FileClient client = mock(FileClient.class); | ||
| 104 | + when(fileConfigService.getFileClient(eq(10L))).thenReturn(client); | ||
| 108 | // 准备参数 | 105 | // 准备参数 |
| 109 | - String id = dbFile.getId(); | 106 | + Long id = dbFile.getId(); |
| 110 | 107 | ||
| 111 | // 调用 | 108 | // 调用 |
| 112 | fileService.deleteFile(id); | 109 | fileService.deleteFile(id); |
| 113 | // 校验数据不存在了 | 110 | // 校验数据不存在了 |
| 114 | assertNull(fileMapper.selectById(id)); | 111 | assertNull(fileMapper.selectById(id)); |
| 112 | + // 校验调用 | ||
| 113 | + verify(client).delete(eq("tudou.jpg")); | ||
| 115 | } | 114 | } |
| 116 | 115 | ||
| 117 | @Test | 116 | @Test |
| 118 | public void testDeleteFile_notExists() { | 117 | public void testDeleteFile_notExists() { |
| 119 | // 准备参数 | 118 | // 准备参数 |
| 120 | - String id = randomString(); | 119 | + Long id = randomLongId(); |
| 121 | 120 | ||
| 122 | // 调用, 并断言异常 | 121 | // 调用, 并断言异常 |
| 123 | assertServiceException(() -> fileService.deleteFile(id), FILE_NOT_EXISTS); | 122 | assertServiceException(() -> fileService.deleteFile(id), FILE_NOT_EXISTS); |
| 124 | } | 123 | } |
| 125 | 124 | ||
| 125 | + @Test | ||
| 126 | + public void testGetFileContent() { | ||
| 127 | + // 准备参数 | ||
| 128 | + Long configId = 10L; | ||
| 129 | + String path = "tudou.jpg"; | ||
| 130 | + // mock 方法 | ||
| 131 | + FileClient client = mock(FileClient.class); | ||
| 132 | + when(fileConfigService.getFileClient(eq(10L))).thenReturn(client); | ||
| 133 | + byte[] content = new byte[]{}; | ||
| 134 | + when(client.getContent(eq("tudou.jpg"))).thenReturn(content); | ||
| 135 | + | ||
| 136 | + // 调用 | ||
| 137 | + byte[] result = fileService.getFileContent(configId, path); | ||
| 138 | + // 断言 | ||
| 139 | + assertSame(result, content); | ||
| 140 | + } | ||
| 126 | } | 141 | } |
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" ( | @@ -32,9 +32,12 @@ CREATE TABLE IF NOT EXISTS "infra_file_config" ( | ||
| 32 | ) COMMENT '文件配置表'; | 32 | ) COMMENT '文件配置表'; |
| 33 | 33 | ||
| 34 | CREATE TABLE IF NOT EXISTS "infra_file" ( | 34 | CREATE TABLE IF NOT EXISTS "infra_file" ( |
| 35 | - "id" varchar(188) NOT NULL, | 35 | + "id" bigint NOT NULL GENERATED BY DEFAULT AS IDENTITY, |
| 36 | + "config_id" bigint NOT NULL, | ||
| 37 | + "path" varchar(512), | ||
| 38 | + "url" varchar(1024), | ||
| 36 | "type" varchar(63) DEFAULT NULL, | 39 | "type" varchar(63) DEFAULT NULL, |
| 37 | - "content" blob NOT NULL, | 40 | + "size" bigint NOT NULL, |
| 38 | "creator" varchar(64) DEFAULT '', | 41 | "creator" varchar(64) DEFAULT '', |
| 39 | "create_time" timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP, | 42 | "create_time" timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP, |
| 40 | "updater" varchar(64) DEFAULT '', | 43 | "updater" varchar(64) DEFAULT '', |
yudao-server/src/main/resources/application-dev.yaml
| @@ -172,8 +172,6 @@ yudao: | @@ -172,8 +172,6 @@ yudao: | ||
| 172 | session-timeout: 30m | 172 | session-timeout: 30m |
| 173 | mock-enable: true | 173 | mock-enable: true |
| 174 | mock-secret: test | 174 | mock-secret: test |
| 175 | - file: | ||
| 176 | - base-path: http://api-dashboard.yudao.iocoder.cn${yudao.web.admin-api.prefix}/infra/file/get/ | ||
| 177 | xss: | 175 | xss: |
| 178 | enable: false | 176 | enable: false |
| 179 | exclude-urls: # 如下两个 url,仅仅是为了演示,去掉配置也没关系 | 177 | exclude-urls: # 如下两个 url,仅仅是为了演示,去掉配置也没关系 |
yudao-server/src/main/resources/application-local.yaml
| @@ -184,8 +184,6 @@ yudao: | @@ -184,8 +184,6 @@ yudao: | ||
| 184 | session-timeout: 1d | 184 | session-timeout: 1d |
| 185 | mock-enable: true | 185 | mock-enable: true |
| 186 | mock-secret: test | 186 | mock-secret: test |
| 187 | - file: | ||
| 188 | - base-path: http://127.0.0.1:${server.port}${yudao.web.admin-api.prefix}/infra/file/get/ | ||
| 189 | xss: | 187 | xss: |
| 190 | enable: false | 188 | enable: false |
| 191 | exclude-urls: # 如下两个 url,仅仅是为了演示,去掉配置也没关系 | 189 | exclude-urls: # 如下两个 url,仅仅是为了演示,去掉配置也没关系 |
yudao-server/src/main/resources/application.yaml
| @@ -92,7 +92,7 @@ yudao: | @@ -92,7 +92,7 @@ yudao: | ||
| 92 | ignore-urls: | 92 | ignore-urls: |
| 93 | - /admin-api/system/tenant/get-id-by-name # 基于名字获取租户,不许带租户编号 | 93 | - /admin-api/system/tenant/get-id-by-name # 基于名字获取租户,不许带租户编号 |
| 94 | - /admin-api/system/captcha/get-image # 获取图片验证码,和租户无关 | 94 | - /admin-api/system/captcha/get-image # 获取图片验证码,和租户无关 |
| 95 | - - /admin-api/infra/file/get/* # 获取图片,和租户无关 | 95 | + - /admin-api/infra/file/*/get/** # 获取图片,和租户无关 |
| 96 | - /admin-api/system/sms/callback/* # 短信回调接口,无法带上租户编号 | 96 | - /admin-api/system/sms/callback/* # 短信回调接口,无法带上租户编号 |
| 97 | ignore-tables: | 97 | ignore-tables: |
| 98 | - system_tenant | 98 | - system_tenant |
yudao-ui-admin/src/views/infra/file/index.vue
| @@ -3,8 +3,8 @@ | @@ -3,8 +3,8 @@ | ||
| 3 | 3 | ||
| 4 | <!-- 搜索工作栏 --> | 4 | <!-- 搜索工作栏 --> |
| 5 | <el-form :model="queryParams" ref="queryForm" :inline="true" v-show="showSearch" label-width="68px"> | 5 | <el-form :model="queryParams" ref="queryForm" :inline="true" v-show="showSearch" label-width="68px"> |
| 6 | - <el-form-item label="文件路径" prop="id"> | ||
| 7 | - <el-input v-model="queryParams.id" placeholder="请输入文件路径" clearable size="small" @keyup.enter.native="handleQuery"/> | 6 | + <el-form-item label="文件路径" prop="path"> |
| 7 | + <el-input v-model="queryParams.path" placeholder="请输入文件路径" clearable size="small" @keyup.enter.native="handleQuery"/> | ||
| 8 | </el-form-item> | 8 | </el-form-item> |
| 9 | <el-form-item label="文件类型" prop="type"> | 9 | <el-form-item label="文件类型" prop="type"> |
| 10 | <el-select v-model="queryParams.type" placeholder="请选择文件类型" clearable size="small"> | 10 | <el-select v-model="queryParams.type" placeholder="请选择文件类型" clearable size="small"> |
| @@ -31,21 +31,23 @@ | @@ -31,21 +31,23 @@ | ||
| 31 | 31 | ||
| 32 | <!-- 列表 --> | 32 | <!-- 列表 --> |
| 33 | <el-table v-loading="loading" :data="list"> | 33 | <el-table v-loading="loading" :data="list"> |
| 34 | - <el-table-column label="文件路径" align="center" prop="id" width="300" /> | 34 | + <el-table-column label="文件名" align="center" prop="path" /> |
| 35 | + <el-table-column label="URL" align="center" prop="url" /> | ||
| 36 | + <el-table-column label="文件大小" align="center" prop="size" width="120" :formatter="sizeFormat" /> | ||
| 35 | <el-table-column label="文件类型" align="center" prop="type" width="80" /> | 37 | <el-table-column label="文件类型" align="center" prop="type" width="80" /> |
| 36 | - <el-table-column label="文件内容" align="center" prop="content"> | ||
| 37 | - <template slot-scope="scope"> | ||
| 38 | - <img v-if="scope.row.type === 'jpg' || scope.row.type === 'png' || scope.row.type === 'gif'" | ||
| 39 | - width="200px" :src="getFileUrl + scope.row.id"> | ||
| 40 | - <i v-else>非图片,无法预览</i> | ||
| 41 | - </template> | ||
| 42 | - </el-table-column> | ||
| 43 | - <el-table-column label="创建时间" align="center" prop="createTime" width="180"> | 38 | +<!-- <el-table-column label="文件内容" align="center" prop="content">--> |
| 39 | +<!-- <template slot-scope="scope">--> | ||
| 40 | +<!-- <img v-if="scope.row.type === 'jpg' || scope.row.type === 'png' || scope.row.type === 'gif'"--> | ||
| 41 | +<!-- width="200px" :src="getFileUrl + scope.row.id">--> | ||
| 42 | +<!-- <i v-else>非图片,无法预览</i>--> | ||
| 43 | +<!-- </template>--> | ||
| 44 | +<!-- </el-table-column>--> | ||
| 45 | + <el-table-column label="上传时间" align="center" prop="createTime" width="180"> | ||
| 44 | <template slot-scope="scope"> | 46 | <template slot-scope="scope"> |
| 45 | <span>{{ parseTime(scope.row.createTime) }}</span> | 47 | <span>{{ parseTime(scope.row.createTime) }}</span> |
| 46 | </template> | 48 | </template> |
| 47 | </el-table-column> | 49 | </el-table-column> |
| 48 | - <el-table-column label="操作" align="center" class-name="small-padding fixed-width"> | 50 | + <el-table-column label="操作" align="center" class-name="small-padding fixed-width" width="100"> |
| 49 | <template slot-scope="scope"> | 51 | <template slot-scope="scope"> |
| 50 | <el-button size="mini" type="text" icon="el-icon-delete" @click="handleDelete(scope.row)" | 52 | <el-button size="mini" type="text" icon="el-icon-delete" @click="handleDelete(scope.row)" |
| 51 | v-hasPermi="['infra:file:delete']">删除</el-button> | 53 | v-hasPermi="['infra:file:delete']">删除</el-button> |
| @@ -102,7 +104,7 @@ export default { | @@ -102,7 +104,7 @@ export default { | ||
| 102 | queryParams: { | 104 | queryParams: { |
| 103 | pageNo: 1, | 105 | pageNo: 1, |
| 104 | pageSize: 10, | 106 | pageSize: 10, |
| 105 | - id: null, | 107 | + path: null, |
| 106 | type: null, | 108 | type: null, |
| 107 | }, | 109 | }, |
| 108 | // 用户导入参数 | 110 | // 用户导入参数 |
| @@ -193,6 +195,15 @@ export default { | @@ -193,6 +195,15 @@ export default { | ||
| 193 | this.$modal.msgSuccess("删除成功"); | 195 | this.$modal.msgSuccess("删除成功"); |
| 194 | }).catch(() => {}); | 196 | }).catch(() => {}); |
| 195 | }, | 197 | }, |
| 198 | + // 用户昵称展示 | ||
| 199 | + sizeFormat(row, column) { | ||
| 200 | + const unitArr = ["Bytes","KB","MB","GB","TB","PB","EB","ZB","YB"]; | ||
| 201 | + const srcSize = parseFloat(row.size); | ||
| 202 | + const index = Math.floor(Math.log(srcSize) / Math.log(1024)); | ||
| 203 | + let size =srcSize/Math.pow(1024,index); | ||
| 204 | + size = size.toFixed(2);//保留的小数位数 | ||
| 205 | + return size + ' ' + unitArr[index]; | ||
| 206 | + }, | ||
| 196 | } | 207 | } |
| 197 | }; | 208 | }; |
| 198 | </script> | 209 | </script> |