基础设施:增加前端直连上传文件到S3服务的功能

This commit is contained in:
owen 2024-02-04 19:44:25 +08:00
parent 7bb171512a
commit 478db1bd39
7 changed files with 142 additions and 18 deletions

View File

@ -18,11 +18,11 @@ public interface FileClient {
* 上传文件
*
* @param content 文件流
* @param path 相对路径
* @param path 相对路径
* @return 完整路径 HTTP 访问地址
* @throws Exception 上传文件时抛出 Exception 异常
*/
String upload(byte[] content, String path, String type) throws Exception;
String upload(byte[] content, String path, String type) throws Exception;
/**
* 删除文件
@ -40,4 +40,14 @@ public interface FileClient {
*/
byte[] getContent(String path) throws Exception;
/**
* 获得文件预签名地址
*
* @param fileName 文件名称
* @return 文件预签名地址
*/
default String getPresignedObjectUrl(String fileName) throws Exception {
throw new UnsupportedOperationException("不支持的操作");
}
}

View File

@ -5,8 +5,10 @@ import cn.hutool.core.util.StrUtil;
import cn.hutool.http.HttpUtil;
import cn.iocoder.yudao.framework.file.core.client.AbstractFileClient;
import io.minio.*;
import io.minio.http.Method;
import java.io.ByteArrayInputStream;
import java.util.concurrent.TimeUnit;
import static cn.iocoder.yudao.framework.file.core.client.s3.S3FileClientConfig.ENDPOINT_ALIYUN;
import static cn.iocoder.yudao.framework.file.core.client.s3.S3FileClientConfig.ENDPOINT_TENCENT;
@ -117,4 +119,18 @@ public class S3FileClient extends AbstractFileClient<S3FileClientConfig> {
return IoUtil.readBytes(response);
}
@Override
public String getPresignedObjectUrl(String fileName) throws Exception {
return client.getPresignedObjectUrl(GetPresignedObjectUrlArgs.builder()
.method(Method.PUT)
.bucket(config.getBucket())
.object(fileName)
/**
* 过期时间秒数取值范围1秒 ~ 7天
* {@link GetPresignedObjectUrlArgs.Builder#validateExpiry(int)}
*/
.expiry(10, TimeUnit.MINUTES)
.build()
);
}
}

View File

@ -8,14 +8,17 @@ import cn.iocoder.yudao.framework.common.pojo.PageResult;
import cn.iocoder.yudao.framework.common.util.object.BeanUtils;
import cn.iocoder.yudao.framework.common.util.servlet.ServletUtils;
import cn.iocoder.yudao.framework.operatelog.core.annotations.OperateLog;
import cn.iocoder.yudao.module.infra.controller.admin.file.vo.file.FilePageReqVO;
import cn.iocoder.yudao.module.infra.controller.admin.file.vo.file.FileRespVO;
import cn.iocoder.yudao.module.infra.controller.admin.file.vo.file.FileUploadReqVO;
import cn.iocoder.yudao.module.infra.controller.admin.file.vo.file.*;
import cn.iocoder.yudao.module.infra.dal.dataobject.file.FileDO;
import cn.iocoder.yudao.module.infra.service.file.FileService;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.Parameter;
import io.swagger.v3.oas.annotations.tags.Tag;
import jakarta.annotation.Resource;
import jakarta.annotation.security.PermitAll;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import jakarta.validation.Valid;
import lombok.extern.slf4j.Slf4j;
import org.springframework.http.HttpStatus;
import org.springframework.security.access.prepost.PreAuthorize;
@ -23,12 +26,6 @@ import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.*;
import org.springframework.web.multipart.MultipartFile;
import jakarta.annotation.Resource;
import jakarta.annotation.security.PermitAll;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import jakarta.validation.Valid;
import static cn.iocoder.yudao.framework.common.pojo.CommonResult.success;
@Tag(name = "管理后台 - 文件存储")
@ -50,6 +47,18 @@ public class FileController {
return success(fileService.createFile(file.getOriginalFilename(), path, IoUtil.readBytes(file.getInputStream())));
}
@GetMapping("/presigned-url")
@Operation(summary = "获取文件预签名地址")
public CommonResult<FilePresignedUrlRespVO> getFilePresignedUrl(@RequestParam("fileName") String fileName) throws Exception {
return success(fileService.getFilePresignedUrl(fileName));
}
@PostMapping("/create")
@Operation(summary = "创建文件")
public CommonResult<Long> createFile(@Valid @RequestBody FileCreateReqVO createReqVO) {
return success(fileService.createFile(createReqVO));
}
@DeleteMapping("/delete")
@Operation(summary = "删除文件")
@Parameter(name = "id", description = "编号", required = true)
@ -62,7 +71,7 @@ public class FileController {
@GetMapping("/{configId}/get/**")
@PermitAll
@Operation(summary = "下载文件")
@Parameter(name = "configId", description = "配置编号", required = true)
@Parameter(name = "configId", description = "配置编号", required = true)
public void getFileContent(HttpServletRequest request,
HttpServletResponse response,
@PathVariable("configId") Long configId) throws Exception {

View File

@ -0,0 +1,33 @@
package cn.iocoder.yudao.module.infra.controller.admin.file.vo.file;
import io.swagger.v3.oas.annotations.media.Schema;
import jakarta.validation.constraints.NotNull;
import lombok.Data;
@Schema(description = "管理后台 - 文件创建 Request VO")
@Data
public class FileCreateReqVO {
@NotNull(message = "文件配置编号不能为空")
@Schema(description = "文件配置编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "11")
private Long configId;
@NotNull(message = "文件路径不能为空")
@Schema(description = "文件路径", requiredMode = Schema.RequiredMode.REQUIRED, example = "yudao.jpg")
private String path;
@NotNull(message = "原文件名不能为空")
@Schema(description = "原文件名", requiredMode = Schema.RequiredMode.REQUIRED, example = "yudao.jpg")
private String name;
@NotNull(message = "文件 URL不能为空")
@Schema(description = "文件 URL", requiredMode = Schema.RequiredMode.REQUIRED, example = "https://www.iocoder.cn/yudao.jpg")
private String url;
@Schema(description = "文件MIME类型", example = "application/octet-stream")
private String type;
@Schema(description = "文件大小", example = "2048", requiredMode = Schema.RequiredMode.REQUIRED)
private Integer size;
}

View File

@ -0,0 +1,20 @@
package cn.iocoder.yudao.module.infra.controller.admin.file.vo.file;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
@AllArgsConstructor
@NoArgsConstructor
@Schema(description = "管理后台 - 文件预签名地址 Response VO")
@Data
public class FilePresignedUrlRespVO {
@Schema(description = "配置编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "11")
private Long configId;
@Schema(description = "文件 URL", requiredMode = Schema.RequiredMode.REQUIRED, example = "https://www.iocoder.cn/yudao.jpg")
private String url;
}

View File

@ -1,7 +1,9 @@
package cn.iocoder.yudao.module.infra.service.file;
import cn.iocoder.yudao.module.infra.controller.admin.file.vo.file.FilePageReqVO;
import cn.iocoder.yudao.framework.common.pojo.PageResult;
import cn.iocoder.yudao.module.infra.controller.admin.file.vo.file.FileCreateReqVO;
import cn.iocoder.yudao.module.infra.controller.admin.file.vo.file.FilePageReqVO;
import cn.iocoder.yudao.module.infra.controller.admin.file.vo.file.FilePresignedUrlRespVO;
import cn.iocoder.yudao.module.infra.dal.dataobject.file.FileDO;
/**
@ -22,13 +24,21 @@ public interface FileService {
/**
* 保存文件并返回文件的访问路径
*
* @param name 文件名称
* @param path 文件路径
* @param name 文件名称
* @param path 文件路径
* @param content 文件内容
* @return 文件路径
*/
String createFile(String name, String path, byte[] content);
/**
* 创建文件
*
* @param createReqVO 创建信息
* @return 编号
*/
Long createFile(FileCreateReqVO createReqVO);
/**
* 删除文件
*
@ -40,9 +50,17 @@ public interface FileService {
* 获得文件内容
*
* @param configId 配置编号
* @param path 文件路径
* @param path 文件路径
* @return 文件内容
*/
byte[] getFileContent(Long configId, String path) throws Exception;
/**
* 生成文件预签名地址信息
*
* @param fileName 文件名称
* @return 预签名地址信息
*/
FilePresignedUrlRespVO getFilePresignedUrl(String fileName) throws Exception;
}

View File

@ -4,16 +4,18 @@ import cn.hutool.core.lang.Assert;
import cn.hutool.core.util.StrUtil;
import cn.iocoder.yudao.framework.common.pojo.PageResult;
import cn.iocoder.yudao.framework.common.util.io.FileUtils;
import cn.iocoder.yudao.framework.common.util.object.BeanUtils;
import cn.iocoder.yudao.framework.file.core.client.FileClient;
import cn.iocoder.yudao.framework.file.core.utils.FileTypeUtils;
import cn.iocoder.yudao.module.infra.controller.admin.file.vo.file.FileCreateReqVO;
import cn.iocoder.yudao.module.infra.controller.admin.file.vo.file.FilePageReqVO;
import cn.iocoder.yudao.module.infra.controller.admin.file.vo.file.FilePresignedUrlRespVO;
import cn.iocoder.yudao.module.infra.dal.dataobject.file.FileDO;
import cn.iocoder.yudao.module.infra.dal.mysql.file.FileMapper;
import jakarta.annotation.Resource;
import lombok.SneakyThrows;
import org.springframework.stereotype.Service;
import jakarta.annotation.Resource;
import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception;
import static cn.iocoder.yudao.module.infra.enums.ErrorCodeConstants.FILE_NOT_EXISTS;
@ -66,6 +68,15 @@ public class FileServiceImpl implements FileService {
return url;
}
@Override
public Long createFile(FileCreateReqVO createReqVO) {
// 插入
FileDO file = BeanUtils.toBean(createReqVO, FileDO.class);
fileMapper.insert(file);
// 返回
return file.getId();
}
@Override
public void deleteFile(Long id) throws Exception {
// 校验存在
@ -95,4 +106,11 @@ public class FileServiceImpl implements FileService {
return client.getContent(path);
}
@Override
public FilePresignedUrlRespVO getFilePresignedUrl(String fileName) throws Exception {
FileClient fileClient = fileConfigService.getMasterFileClient();
String url = fileClient.getPresignedObjectUrl(fileName);
return new FilePresignedUrlRespVO(fileClient.getId(), url);
}
}