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 package cn.iocoder.yudao.framework.file.core.client; 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,11 +20,11 @@ public interface FileClient {
18 * 上传文件 20 * 上传文件
19 * 21 *
20 * @param content 文件流 22 * @param content 文件流
21 - * @param path 相对路径 23 + * @param path 相对路径
22 * @return 完整路径,即 HTTP 访问地址 24 * @return 完整路径,即 HTTP 访问地址
23 * @throws Exception 上传文件时,抛出 Exception 异常 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,4 +42,14 @@ public interface FileClient {
40 */ 42 */
41 byte[] getContent(String path) throws Exception; 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,8 +5,10 @@ import cn.hutool.core.util.StrUtil;
5 import cn.hutool.http.HttpUtil; 5 import cn.hutool.http.HttpUtil;
6 import cn.iocoder.yudao.framework.file.core.client.AbstractFileClient; 6 import cn.iocoder.yudao.framework.file.core.client.AbstractFileClient;
7 import io.minio.*; 7 import io.minio.*;
  8 +import io.minio.http.Method;
8 9
9 import java.io.ByteArrayInputStream; 10 import java.io.ByteArrayInputStream;
  11 +import java.util.concurrent.TimeUnit;
10 12
11 import static cn.iocoder.yudao.framework.file.core.client.s3.S3FileClientConfig.ENDPOINT_ALIYUN; 13 import static cn.iocoder.yudao.framework.file.core.client.s3.S3FileClientConfig.ENDPOINT_ALIYUN;
12 import static cn.iocoder.yudao.framework.file.core.client.s3.S3FileClientConfig.ENDPOINT_TENCENT; 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,4 +119,19 @@ public class S3FileClient extends AbstractFileClient<S3FileClientConfig> {
117 return IoUtil.readBytes(response); 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,14 +8,17 @@ import cn.iocoder.yudao.framework.common.pojo.PageResult;
8 import cn.iocoder.yudao.framework.common.util.object.BeanUtils; 8 import cn.iocoder.yudao.framework.common.util.object.BeanUtils;
9 import cn.iocoder.yudao.framework.common.util.servlet.ServletUtils; 9 import cn.iocoder.yudao.framework.common.util.servlet.ServletUtils;
10 import cn.iocoder.yudao.framework.operatelog.core.annotations.OperateLog; 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 import cn.iocoder.yudao.module.infra.dal.dataobject.file.FileDO; 12 import cn.iocoder.yudao.module.infra.dal.dataobject.file.FileDO;
15 import cn.iocoder.yudao.module.infra.service.file.FileService; 13 import cn.iocoder.yudao.module.infra.service.file.FileService;
16 import io.swagger.v3.oas.annotations.Operation; 14 import io.swagger.v3.oas.annotations.Operation;
17 import io.swagger.v3.oas.annotations.Parameter; 15 import io.swagger.v3.oas.annotations.Parameter;
18 import io.swagger.v3.oas.annotations.tags.Tag; 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 import lombok.extern.slf4j.Slf4j; 22 import lombok.extern.slf4j.Slf4j;
20 import org.springframework.http.HttpStatus; 23 import org.springframework.http.HttpStatus;
21 import org.springframework.security.access.prepost.PreAuthorize; 24 import org.springframework.security.access.prepost.PreAuthorize;
@@ -23,12 +26,6 @@ import org.springframework.validation.annotation.Validated; @@ -23,12 +26,6 @@ import org.springframework.validation.annotation.Validated;
23 import org.springframework.web.bind.annotation.*; 26 import org.springframework.web.bind.annotation.*;
24 import org.springframework.web.multipart.MultipartFile; 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 import static cn.iocoder.yudao.framework.common.pojo.CommonResult.success; 29 import static cn.iocoder.yudao.framework.common.pojo.CommonResult.success;
33 30
34 @Tag(name = "管理后台 - 文件存储") 31 @Tag(name = "管理后台 - 文件存储")
@@ -50,6 +47,18 @@ public class FileController { @@ -50,6 +47,18 @@ public class FileController {
50 return success(fileService.createFile(file.getOriginalFilename(), path, IoUtil.readBytes(file.getInputStream()))); 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 @DeleteMapping("/delete") 62 @DeleteMapping("/delete")
54 @Operation(summary = "删除文件") 63 @Operation(summary = "删除文件")
55 @Parameter(name = "id", description = "编号", required = true) 64 @Parameter(name = "id", description = "编号", required = true)
@@ -62,7 +71,7 @@ public class FileController { @@ -62,7 +71,7 @@ public class FileController {
62 @GetMapping("/{configId}/get/**") 71 @GetMapping("/{configId}/get/**")
63 @PermitAll 72 @PermitAll
64 @Operation(summary = "下载文件") 73 @Operation(summary = "下载文件")
65 - @Parameter(name = "configId", description = "配置编号", required = true) 74 + @Parameter(name = "configId", description = "配置编号", required = true)
66 public void getFileContent(HttpServletRequest request, 75 public void getFileContent(HttpServletRequest request,
67 HttpServletResponse response, 76 HttpServletResponse response,
68 @PathVariable("configId") Long configId) throws Exception { 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 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.file.FilePageReqVO;  
4 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.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 import cn.iocoder.yudao.module.infra.dal.dataobject.file.FileDO; 7 import cn.iocoder.yudao.module.infra.dal.dataobject.file.FileDO;
6 8
7 /** 9 /**
@@ -22,14 +24,22 @@ public interface FileService { @@ -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 * @param content 文件内容 29 * @param content 文件内容
28 * @return 文件路径 30 * @return 文件路径
29 */ 31 */
30 String createFile(String name, String path, byte[] content); 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 * @param id 编号 45 * @param id 编号
@@ -40,9 +50,17 @@ public interface FileService { @@ -40,9 +50,17 @@ public interface FileService {
40 * 获得文件内容 50 * 获得文件内容
41 * 51 *
42 * @param configId 配置编号 52 * @param configId 配置编号
43 - * @param path 文件路径 53 + * @param path 文件路径
44 * @return 文件内容 54 * @return 文件内容
45 */ 55 */
46 byte[] getFileContent(Long configId, String path) throws Exception; 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,16 +4,19 @@ import cn.hutool.core.lang.Assert;
4 import cn.hutool.core.util.StrUtil; 4 import cn.hutool.core.util.StrUtil;
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.io.FileUtils; 6 import cn.iocoder.yudao.framework.common.util.io.FileUtils;
  7 +import cn.iocoder.yudao.framework.common.util.object.BeanUtils;
7 import cn.iocoder.yudao.framework.file.core.client.FileClient; 8 import cn.iocoder.yudao.framework.file.core.client.FileClient;
  9 +import cn.iocoder.yudao.framework.file.core.client.s3.FilePresignedUrlBO;
8 import cn.iocoder.yudao.framework.file.core.utils.FileTypeUtils; 10 import cn.iocoder.yudao.framework.file.core.utils.FileTypeUtils;
  11 +import cn.iocoder.yudao.module.infra.controller.admin.file.vo.file.FileCreateReqVO;
9 import cn.iocoder.yudao.module.infra.controller.admin.file.vo.file.FilePageReqVO; 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 import cn.iocoder.yudao.module.infra.dal.dataobject.file.FileDO; 14 import cn.iocoder.yudao.module.infra.dal.dataobject.file.FileDO;
11 import cn.iocoder.yudao.module.infra.dal.mysql.file.FileMapper; 15 import cn.iocoder.yudao.module.infra.dal.mysql.file.FileMapper;
  16 +import jakarta.annotation.Resource;
12 import lombok.SneakyThrows; 17 import lombok.SneakyThrows;
13 import org.springframework.stereotype.Service; 18 import org.springframework.stereotype.Service;
14 19
15 -import jakarta.annotation.Resource;  
16 -  
17 import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception; 20 import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception;
18 import static cn.iocoder.yudao.module.infra.enums.ErrorCodeConstants.FILE_NOT_EXISTS; 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,6 +70,15 @@ public class FileServiceImpl implements FileService {
67 } 70 }
68 71
69 @Override 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 public void deleteFile(Long id) throws Exception { 82 public void deleteFile(Long id) throws Exception {
71 // 校验存在 83 // 校验存在
72 FileDO file = validateFileExists(id); 84 FileDO file = validateFileExists(id);
@@ -95,4 +107,11 @@ public class FileServiceImpl implements FileService { @@ -95,4 +107,11 @@ public class FileServiceImpl implements FileService {
95 return client.getContent(path); 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 }