Commit b2782e1d9ad77dc06bc077e3ad86f40a3e5d3d6b
Committed by
Gitee
!865 基础设施:前端直连上传文件
Merge pull request !865 from 疯狂的世界/develop
Showing
8 changed files
with
176 additions
and
18 deletions
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 | } | ... | ... |