Commit b2782e1d9ad77dc06bc077e3ad86f40a3e5d3d6b

Authored by 芋道源码
Committed by Gitee
2 parents 0fba56cd 787b0140

!865 基础设施:前端直连上传文件

Merge pull request !865 from 疯狂的世界/develop
yudao-framework/yudao-spring-boot-starter-file/src/main/java/cn/iocoder/yudao/framework/file/core/client/FileClient.java
1 1 package cn.iocoder.yudao.framework.file.core.client;
2 2  
  3 +import cn.iocoder.yudao.framework.file.core.client.s3.FilePresignedUrlBO;
  4 +
3 5 /**
4 6 * 文件客户端
5 7 *
... ... @@ -18,11 +20,11 @@ public interface FileClient {
18 20 * 上传文件
19 21 *
20 22 * @param content 文件流
21   - * @param path 相对路径
  23 + * @param path 相对路径
22 24 * @return 完整路径,即 HTTP 访问地址
23 25 * @throws Exception 上传文件时,抛出 Exception 异常
24 26 */
25   - String upload(byte[] content, String path, String type) throws Exception;
  27 + String upload(byte[] content, String path, String type) throws Exception;
26 28  
27 29 /**
28 30 * 删除文件
... ... @@ -40,4 +42,14 @@ public interface FileClient {
40 42 */
41 43 byte[] getContent(String path) throws Exception;
42 44  
  45 + /**
  46 + * 获得文件预签名地址
  47 + *
  48 + * @param fileName 文件名称
  49 + * @return 文件预签名地址
  50 + */
  51 + default FilePresignedUrlBO getPresignedObjectUrl(String fileName) throws Exception {
  52 + throw new UnsupportedOperationException("不支持的操作");
  53 + }
  54 +
43 55 }
... ...
yudao-framework/yudao-spring-boot-starter-file/src/main/java/cn/iocoder/yudao/framework/file/core/client/s3/FilePresignedUrlBO.java 0 → 100644
  1 +package cn.iocoder.yudao.framework.file.core.client.s3;
  2 +
  3 +import lombok.AllArgsConstructor;
  4 +import lombok.Data;
  5 +import lombok.NoArgsConstructor;
  6 +
  7 +/**
  8 + * 文件预签名地址 BO
  9 + *
  10 + * @author owen
  11 + */
  12 +@AllArgsConstructor
  13 +@NoArgsConstructor
  14 +@Data
  15 +public class FilePresignedUrlBO {
  16 +
  17 + /**
  18 + * 文件上传 URL(用于上传)
  19 + */
  20 + private String uploadUrl;
  21 +
  22 + /**
  23 + * 文件 URL(用于读取、下载等)
  24 + */
  25 + private String url;
  26 +
  27 +}
... ...
yudao-framework/yudao-spring-boot-starter-file/src/main/java/cn/iocoder/yudao/framework/file/core/client/s3/S3FileClient.java
... ... @@ -5,8 +5,10 @@ import cn.hutool.core.util.StrUtil;
5 5 import cn.hutool.http.HttpUtil;
6 6 import cn.iocoder.yudao.framework.file.core.client.AbstractFileClient;
7 7 import io.minio.*;
  8 +import io.minio.http.Method;
8 9  
9 10 import java.io.ByteArrayInputStream;
  11 +import java.util.concurrent.TimeUnit;
10 12  
11 13 import static cn.iocoder.yudao.framework.file.core.client.s3.S3FileClientConfig.ENDPOINT_ALIYUN;
12 14 import static cn.iocoder.yudao.framework.file.core.client.s3.S3FileClientConfig.ENDPOINT_TENCENT;
... ... @@ -117,4 +119,19 @@ public class S3FileClient extends AbstractFileClient<S3FileClientConfig> {
117 119 return IoUtil.readBytes(response);
118 120 }
119 121  
  122 + @Override
  123 + public FilePresignedUrlBO getPresignedObjectUrl(String fileName) throws Exception {
  124 + String uploadUrl = client.getPresignedObjectUrl(GetPresignedObjectUrlArgs.builder()
  125 + .method(Method.PUT)
  126 + .bucket(config.getBucket())
  127 + .object(fileName)
  128 + /**
  129 + * 过期时间(秒数)取值范围:1秒 ~ 7天
  130 + * {@link GetPresignedObjectUrlArgs.Builder#validateExpiry(int)}
  131 + */
  132 + .expiry(10, TimeUnit.MINUTES)
  133 + .build()
  134 + );
  135 + return new FilePresignedUrlBO(uploadUrl, config.getDomain() + "/" + fileName);
  136 + }
120 137 }
... ...
yudao-module-infra/yudao-module-infra-biz/src/main/java/cn/iocoder/yudao/module/infra/controller/admin/file/FileController.java
... ... @@ -8,14 +8,17 @@ import cn.iocoder.yudao.framework.common.pojo.PageResult;
8 8 import cn.iocoder.yudao.framework.common.util.object.BeanUtils;
9 9 import cn.iocoder.yudao.framework.common.util.servlet.ServletUtils;
10 10 import cn.iocoder.yudao.framework.operatelog.core.annotations.OperateLog;
11   -import cn.iocoder.yudao.module.infra.controller.admin.file.vo.file.FilePageReqVO;
12   -import cn.iocoder.yudao.module.infra.controller.admin.file.vo.file.FileRespVO;
13   -import cn.iocoder.yudao.module.infra.controller.admin.file.vo.file.FileUploadReqVO;
  11 +import cn.iocoder.yudao.module.infra.controller.admin.file.vo.file.*;
14 12 import cn.iocoder.yudao.module.infra.dal.dataobject.file.FileDO;
15 13 import cn.iocoder.yudao.module.infra.service.file.FileService;
16 14 import io.swagger.v3.oas.annotations.Operation;
17 15 import io.swagger.v3.oas.annotations.Parameter;
18 16 import io.swagger.v3.oas.annotations.tags.Tag;
  17 +import jakarta.annotation.Resource;
  18 +import jakarta.annotation.security.PermitAll;
  19 +import jakarta.servlet.http.HttpServletRequest;
  20 +import jakarta.servlet.http.HttpServletResponse;
  21 +import jakarta.validation.Valid;
19 22 import lombok.extern.slf4j.Slf4j;
20 23 import org.springframework.http.HttpStatus;
21 24 import org.springframework.security.access.prepost.PreAuthorize;
... ... @@ -23,12 +26,6 @@ import org.springframework.validation.annotation.Validated;
23 26 import org.springframework.web.bind.annotation.*;
24 27 import org.springframework.web.multipart.MultipartFile;
25 28  
26   -import jakarta.annotation.Resource;
27   -import jakarta.annotation.security.PermitAll;
28   -import jakarta.servlet.http.HttpServletRequest;
29   -import jakarta.servlet.http.HttpServletResponse;
30   -import jakarta.validation.Valid;
31   -
32 29 import static cn.iocoder.yudao.framework.common.pojo.CommonResult.success;
33 30  
34 31 @Tag(name = "管理后台 - 文件存储")
... ... @@ -50,6 +47,18 @@ public class FileController {
50 47 return success(fileService.createFile(file.getOriginalFilename(), path, IoUtil.readBytes(file.getInputStream())));
51 48 }
52 49  
  50 + @GetMapping("/presigned-url")
  51 + @Operation(summary = "获取文件预签名地址")
  52 + public CommonResult<FilePresignedUrlRespVO> getFilePresignedUrl(@RequestParam("fileName") String fileName) throws Exception {
  53 + return success(fileService.getFilePresignedUrl(fileName));
  54 + }
  55 +
  56 + @PostMapping("/create")
  57 + @Operation(summary = "创建文件")
  58 + public CommonResult<Long> createFile(@Valid @RequestBody FileCreateReqVO createReqVO) {
  59 + return success(fileService.createFile(createReqVO));
  60 + }
  61 +
53 62 @DeleteMapping("/delete")
54 63 @Operation(summary = "删除文件")
55 64 @Parameter(name = "id", description = "编号", required = true)
... ... @@ -62,7 +71,7 @@ public class FileController {
62 71 @GetMapping("/{configId}/get/**")
63 72 @PermitAll
64 73 @Operation(summary = "下载文件")
65   - @Parameter(name = "configId", description = "配置编号", required = true)
  74 + @Parameter(name = "configId", description = "配置编号", required = true)
66 75 public void getFileContent(HttpServletRequest request,
67 76 HttpServletResponse response,
68 77 @PathVariable("configId") Long configId) throws Exception {
... ...
yudao-module-infra/yudao-module-infra-biz/src/main/java/cn/iocoder/yudao/module/infra/controller/admin/file/vo/file/FileCreateReqVO.java 0 → 100644
  1 +package cn.iocoder.yudao.module.infra.controller.admin.file.vo.file;
  2 +
  3 +import io.swagger.v3.oas.annotations.media.Schema;
  4 +import jakarta.validation.constraints.NotNull;
  5 +import lombok.Data;
  6 +
  7 +@Schema(description = "管理后台 - 文件创建 Request VO")
  8 +@Data
  9 +public class FileCreateReqVO {
  10 +
  11 + @NotNull(message = "文件配置编号不能为空")
  12 + @Schema(description = "文件配置编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "11")
  13 + private Long configId;
  14 +
  15 + @NotNull(message = "文件路径不能为空")
  16 + @Schema(description = "文件路径", requiredMode = Schema.RequiredMode.REQUIRED, example = "yudao.jpg")
  17 + private String path;
  18 +
  19 + @NotNull(message = "原文件名不能为空")
  20 + @Schema(description = "原文件名", requiredMode = Schema.RequiredMode.REQUIRED, example = "yudao.jpg")
  21 + private String name;
  22 +
  23 + @NotNull(message = "文件 URL不能为空")
  24 + @Schema(description = "文件 URL", requiredMode = Schema.RequiredMode.REQUIRED, example = "https://www.iocoder.cn/yudao.jpg")
  25 + private String url;
  26 +
  27 + @Schema(description = "文件MIME类型", example = "application/octet-stream")
  28 + private String type;
  29 +
  30 + @Schema(description = "文件大小", example = "2048", requiredMode = Schema.RequiredMode.REQUIRED)
  31 + private Integer size;
  32 +
  33 +}
... ...
yudao-module-infra/yudao-module-infra-biz/src/main/java/cn/iocoder/yudao/module/infra/controller/admin/file/vo/file/FilePresignedUrlRespVO.java 0 → 100644
  1 +package cn.iocoder.yudao.module.infra.controller.admin.file.vo.file;
  2 +
  3 +import io.swagger.v3.oas.annotations.media.Schema;
  4 +import lombok.AllArgsConstructor;
  5 +import lombok.Data;
  6 +import lombok.NoArgsConstructor;
  7 +
  8 +@AllArgsConstructor
  9 +@NoArgsConstructor
  10 +@Schema(description = "管理后台 - 文件预签名地址 Response VO")
  11 +@Data
  12 +public class FilePresignedUrlRespVO {
  13 +
  14 + @Schema(description = "配置编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "11")
  15 + private Long configId;
  16 +
  17 + @Schema(description = "文件上传 URL", requiredMode = Schema.RequiredMode.REQUIRED, example = "https://www.iocoder.cn/yudao.jpg")
  18 + private String uploadUrl;
  19 +
  20 + @Schema(description = "文件 URL", requiredMode = Schema.RequiredMode.REQUIRED, example = "https://www.iocoder.cn/yudao.jpg")
  21 + private String url;
  22 +
  23 +}
... ...
yudao-module-infra/yudao-module-infra-biz/src/main/java/cn/iocoder/yudao/module/infra/service/file/FileService.java
1 1 package cn.iocoder.yudao.module.infra.service.file;
2 2  
3   -import cn.iocoder.yudao.module.infra.controller.admin.file.vo.file.FilePageReqVO;
4 3 import cn.iocoder.yudao.framework.common.pojo.PageResult;
  4 +import cn.iocoder.yudao.module.infra.controller.admin.file.vo.file.FileCreateReqVO;
  5 +import cn.iocoder.yudao.module.infra.controller.admin.file.vo.file.FilePageReqVO;
  6 +import cn.iocoder.yudao.module.infra.controller.admin.file.vo.file.FilePresignedUrlRespVO;
5 7 import cn.iocoder.yudao.module.infra.dal.dataobject.file.FileDO;
6 8  
7 9 /**
... ... @@ -22,14 +24,22 @@ public interface FileService {
22 24 /**
23 25 * 保存文件,并返回文件的访问路径
24 26 *
25   - * @param name 文件名称
26   - * @param path 文件路径
  27 + * @param name 文件名称
  28 + * @param path 文件路径
27 29 * @param content 文件内容
28 30 * @return 文件路径
29 31 */
30 32 String createFile(String name, String path, byte[] content);
31 33  
32 34 /**
  35 + * 创建文件
  36 + *
  37 + * @param createReqVO 创建信息
  38 + * @return 编号
  39 + */
  40 + Long createFile(FileCreateReqVO createReqVO);
  41 +
  42 + /**
33 43 * 删除文件
34 44 *
35 45 * @param id 编号
... ... @@ -40,9 +50,17 @@ public interface FileService {
40 50 * 获得文件内容
41 51 *
42 52 * @param configId 配置编号
43   - * @param path 文件路径
  53 + * @param path 文件路径
44 54 * @return 文件内容
45 55 */
46 56 byte[] getFileContent(Long configId, String path) throws Exception;
47 57  
  58 + /**
  59 + * 生成文件预签名地址信息
  60 + *
  61 + * @param fileName 文件名称
  62 + * @return 预签名地址信息
  63 + */
  64 + FilePresignedUrlRespVO getFilePresignedUrl(String fileName) throws Exception;
  65 +
48 66 }
... ...
yudao-module-infra/yudao-module-infra-biz/src/main/java/cn/iocoder/yudao/module/infra/service/file/FileServiceImpl.java
... ... @@ -4,16 +4,19 @@ import cn.hutool.core.lang.Assert;
4 4 import cn.hutool.core.util.StrUtil;
5 5 import cn.iocoder.yudao.framework.common.pojo.PageResult;
6 6 import cn.iocoder.yudao.framework.common.util.io.FileUtils;
  7 +import cn.iocoder.yudao.framework.common.util.object.BeanUtils;
7 8 import cn.iocoder.yudao.framework.file.core.client.FileClient;
  9 +import cn.iocoder.yudao.framework.file.core.client.s3.FilePresignedUrlBO;
8 10 import cn.iocoder.yudao.framework.file.core.utils.FileTypeUtils;
  11 +import cn.iocoder.yudao.module.infra.controller.admin.file.vo.file.FileCreateReqVO;
9 12 import cn.iocoder.yudao.module.infra.controller.admin.file.vo.file.FilePageReqVO;
  13 +import cn.iocoder.yudao.module.infra.controller.admin.file.vo.file.FilePresignedUrlRespVO;
10 14 import cn.iocoder.yudao.module.infra.dal.dataobject.file.FileDO;
11 15 import cn.iocoder.yudao.module.infra.dal.mysql.file.FileMapper;
  16 +import jakarta.annotation.Resource;
12 17 import lombok.SneakyThrows;
13 18 import org.springframework.stereotype.Service;
14 19  
15   -import jakarta.annotation.Resource;
16   -
17 20 import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception;
18 21 import static cn.iocoder.yudao.module.infra.enums.ErrorCodeConstants.FILE_NOT_EXISTS;
19 22  
... ... @@ -67,6 +70,15 @@ public class FileServiceImpl implements FileService {
67 70 }
68 71  
69 72 @Override
  73 + public Long createFile(FileCreateReqVO createReqVO) {
  74 + // 插入
  75 + FileDO file = BeanUtils.toBean(createReqVO, FileDO.class);
  76 + fileMapper.insert(file);
  77 + // 返回
  78 + return file.getId();
  79 + }
  80 +
  81 + @Override
70 82 public void deleteFile(Long id) throws Exception {
71 83 // 校验存在
72 84 FileDO file = validateFileExists(id);
... ... @@ -95,4 +107,11 @@ public class FileServiceImpl implements FileService {
95 107 return client.getContent(path);
96 108 }
97 109  
  110 + @Override
  111 + public FilePresignedUrlRespVO getFilePresignedUrl(String fileName) throws Exception {
  112 + FileClient fileClient = fileConfigService.getMasterFileClient();
  113 + FilePresignedUrlBO bo = fileClient.getPresignedObjectUrl(fileName);
  114 + return BeanUtils.toBean(bo, FilePresignedUrlRespVO.class, f -> f.setConfigId(fileClient.getId()));
  115 + }
  116 +
98 117 }
... ...