From 552d9b4ca3ca59eec40f0da1b5a3569ed870dae7 Mon Sep 17 00:00:00 2001 From: Github_lizhw <171332584@qq.com> Date: Thu, 8 Jan 2026 23:05:39 +0800 Subject: [PATCH] =?UTF-8?q?oss=20=E6=96=87=E4=BB=B6=E6=93=8D=E4=BD=9C?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../luban/api/s3/ctrl/Storage2Controller.java | 200 ++++++++++++++++++ .../com/luban/api/s3/ctrl/StorageWebAPI.java | 112 ++++++++++ 2 files changed, 312 insertions(+) create mode 100644 ruoyi-site/ruoyi-h5/src/main/java/com/luban/api/s3/ctrl/Storage2Controller.java create mode 100644 ruoyi-site/ruoyi-h5/src/main/java/com/luban/api/s3/ctrl/StorageWebAPI.java diff --git a/ruoyi-site/ruoyi-h5/src/main/java/com/luban/api/s3/ctrl/Storage2Controller.java b/ruoyi-site/ruoyi-h5/src/main/java/com/luban/api/s3/ctrl/Storage2Controller.java new file mode 100644 index 000000000..ebec2b6ac --- /dev/null +++ b/ruoyi-site/ruoyi-h5/src/main/java/com/luban/api/s3/ctrl/Storage2Controller.java @@ -0,0 +1,200 @@ +package com.luban.api.s3.ctrl; + +import cn.hutool.core.io.IoUtil; +import lombok.extern.slf4j.Slf4j; +import org.liuxp.minioplus.api.StorageService; +import org.liuxp.minioplus.api.model.vo.CompleteResultVo; +import org.liuxp.minioplus.api.model.vo.FileCheckResultVo; +import org.liuxp.minioplus.api.model.vo.FilePreShardingVo; +import org.liuxp.minioplus.common.enums.MinioPlusErrorCode; +import org.liuxp.minioplus.common.enums.StorageBucketEnums; +import org.liuxp.minioplus.common.exception.MinioPlusException; +import org.liuxp.minioplus.extension.context.Response; +import org.liuxp.minioplus.extension.context.UserHolder; +import org.liuxp.minioplus.extension.dto.FileCheckDTO; +import org.liuxp.minioplus.extension.dto.FileCompleteDTO; +import org.liuxp.minioplus.extension.dto.PreShardingDTO; +import org.springframework.core.io.ClassPathResource; +import org.springframework.http.HttpHeaders; +import org.springframework.http.HttpStatus; +import org.springframework.http.ResponseEntity; +import org.springframework.stereotype.Controller; +import org.springframework.util.FileCopyUtils; +import org.springframework.validation.annotation.Validated; +import org.springframework.web.bind.annotation.RequestBody; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.context.request.RequestContextHolder; +import org.springframework.web.context.request.ServletRequestAttributes; + +import java.io.ByteArrayInputStream; +import java.net.URI; + +/** + * 对象存储标准接口定义 + * 本类的方法是给前端使用的方法 + * + * @author contact@liuxp.me + * @since 2024/6/18 + */ +@Slf4j +@Controller +@RequestMapping("storage2") +public class Storage2Controller implements StorageWebAPI { + + /** + * 存储引擎Service接口定义 + */ + private final StorageService storageService; + + /** + * 构造方法 + * + * @param storageService 注入存储引擎Service接口定义实现类 + */ + public Storage2Controller(StorageService storageService) { + this.storageService = storageService; + } + + /** + * 文件预分片方法 + * 在大文件上传时,为了防止前端重复计算文件MD5值,提供该方法 + * + * @param preShardingDTO 文件预分片入参DTO + * @return 预分片结果 + */ + @Override + public Response sharding(@RequestBody @Validated PreShardingDTO preShardingDTO) { + + FilePreShardingVo resultVo = storageService.sharding(preShardingDTO.getFileSize()); + + return Response.success(resultVo); + } + + /** + * 上传任务初始化 + * 上传前的预检查:秒传、分块上传和断点续传等特性均基于该方法实现 + * + * @param fileCheckDTO 文件预检查入参 + * @return 检查结果 + */ + @Override + public Response init(FileCheckDTO fileCheckDTO) { + + // 取得当前登录用户信息 + String userId = UserHolder.get(); + + FileCheckResultVo resultVo = storageService.init(fileCheckDTO.getFileMd5(), fileCheckDTO.getFullFileName(), fileCheckDTO.getFileSize(), fileCheckDTO.getIsPrivate(), userId); + + return Response.success(resultVo); + } + + /** + * 文件上传完成 + * + * @param fileKey 文件KEY + * @param fileCompleteDTO 文件完成入参DTO + * @return 是否成功 + */ + @Override + public Response complete(String fileKey, FileCompleteDTO fileCompleteDTO) { + + // 取得当前登录用户信息 + String userId = UserHolder.get(); + + // 打印调试日志 + log.debug("合并文件开始fileKey=" + fileKey + ",partMd5List=" + fileCompleteDTO.getPartMd5List()); + CompleteResultVo completeResultVo = storageService.complete(fileKey, fileCompleteDTO.getPartMd5List(), userId); + + return Response.success(completeResultVo); + } + + /** + * 文件下载 + * + * @param fileKey 文件KEY + * @return 文件下载地址 + */ + @Override + public ResponseEntity download(String fileKey) { + + // 取得当前登录用户信息 + String userId = UserHolder.get(); + + HttpHeaders headers = new HttpHeaders(); + headers.setLocation(URI.create(storageService.download(fileKey, userId))); + return new ResponseEntity<>(headers, HttpStatus.SEE_OTHER); + } + + /** + * 获取图像 + * + * @param fileKey 文件KEY + * @return 原图地址 + */ + @Override + public ResponseEntity previewOriginal(String fileKey) { + + // 取得当前登录用户信息 + String userId = UserHolder.get(); + + // 取得文件读取路径 + HttpHeaders headers = new HttpHeaders(); + headers.setLocation(URI.create(storageService.download(fileKey, userId))); + return new ResponseEntity<>(headers, HttpStatus.SEE_OTHER); + } + + /** + * 文件预览 + * 当文件为图片时,返回图片的缩略图 + * 当文件不是图片时,返回文件类型图标 + * + * @param fileKey 文件KEY + * @return 缩略图地址 + */ + @Override + public ResponseEntity previewMedium(String fileKey) { + + // 取得当前登录用户信息 + String userId = UserHolder.get(); + + String url = storageService.preview(fileKey, userId); + if (url.length() < 10) { + // 当返回值为文件类型时,取得图标 + url = ICON_PATH + url; + } + + // 取得文件读取路径 + HttpHeaders headers = new HttpHeaders(); + headers.setLocation(URI.create(url)); + return new ResponseEntity<>(headers, HttpStatus.SEE_OTHER); + } + + /** + * 根据文件类型取得图标 + * + * @param fileType 文件扩展名 + */ + @Override + public void icon(String fileType) { + try { + // 根据文件后缀取得桶 + String storageBucket = StorageBucketEnums.getBucketByFileSuffix(fileType, ""); + + ClassPathResource cpr = new ClassPathResource(storageBucket + ".png"); + + byte[] bytes = FileCopyUtils.copyToByteArray(cpr.getInputStream()); + + ServletRequestAttributes attr = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes(); + attr.getResponse().setHeader("content-disposition", "inline"); + attr.getResponse().setHeader("Content-Length", String.valueOf(bytes.length)); + try (ByteArrayInputStream inputStream = new ByteArrayInputStream(bytes)) { + IoUtil.copy(inputStream, attr.getResponse().getOutputStream()); + } + } catch (Exception e) { + log.error(MinioPlusErrorCode.FILE_ICON_FAILED.getMessage(), e); + // 图标获取失败 + throw new MinioPlusException(MinioPlusErrorCode.FILE_ICON_FAILED); + } + } + +} diff --git a/ruoyi-site/ruoyi-h5/src/main/java/com/luban/api/s3/ctrl/StorageWebAPI.java b/ruoyi-site/ruoyi-h5/src/main/java/com/luban/api/s3/ctrl/StorageWebAPI.java new file mode 100644 index 000000000..f7644b026 --- /dev/null +++ b/ruoyi-site/ruoyi-h5/src/main/java/com/luban/api/s3/ctrl/StorageWebAPI.java @@ -0,0 +1,112 @@ +package com.luban.api.s3.ctrl; + +import io.swagger.v3.oas.annotations.Operation; +import io.swagger.v3.oas.annotations.tags.Tag; +import org.liuxp.minioplus.api.model.vo.FileCheckResultVo; +import org.liuxp.minioplus.api.model.vo.FilePreShardingVo; +import org.liuxp.minioplus.extension.context.Response; +import org.liuxp.minioplus.extension.dto.FileCheckDTO; +import org.liuxp.minioplus.extension.dto.FileCompleteDTO; +import org.liuxp.minioplus.extension.dto.PreShardingDTO; +import org.springframework.http.ResponseEntity; +import org.springframework.validation.annotation.Validated; +import org.springframework.web.bind.annotation.*; + +/** + * 对象存储标准接口定义 + * 本类的方法是给前端使用的方法 + * + * @author contact@liuxp.me + * @since 2024/6/18 + */ +@Tag(name = "MinIO Plus 接口") +public interface StorageWebAPI { + + /** + * 请求地址前缀 + */ + String ROOT_PATH = "/storage"; + + /** + * 图标请求地址 + */ + String ICON_PATH = "/storage/icon/"; + + /** + * 文件预分片方法 + * 在大文件上传时,为了防止前端重复计算文件MD5值,提供该方法 + * + * @param preShardingDTO 文件预分片入参DTO + * @return 预分片结果 + */ + @Operation(summary = "文件预分片") + @PostMapping("/upload/sharding") + @ResponseBody + Response sharding(@RequestBody @Validated PreShardingDTO preShardingDTO); + + /** + * 上传任务初始化 + * 上传前的预检查:秒传、分块上传和断点续传等特性均基于该方法实现 + * + * @param fileCheckDTO 文件预检查入参 + * @return 检查结果 + */ + @Operation(summary = "上传任务初始化") + @PostMapping("/upload/init") + @ResponseBody + Response init(@RequestBody @Validated FileCheckDTO fileCheckDTO); + + /** + * 文件上传完成 + * + * @param fileKey 文件KEY + * @param fileCompleteDTO 文件完成入参DTO + * @return 是否成功 + */ + @Operation(summary = "上传完成") + @PostMapping("/upload/complete/{fileKey}") + @ResponseBody + Response complete(@PathVariable("fileKey") String fileKey, @RequestBody FileCompleteDTO fileCompleteDTO); + + /** + * 文件下载 + * + * @param fileKey 文件KEY + * @return 文件下载地址 + */ + @Operation(summary = "文件下载") + @GetMapping("/download/{fileKey}") + ResponseEntity download(@PathVariable("fileKey") String fileKey); + + /** + * 获取图像 + * + * @param fileKey 文件KEY + * @return 原图地址 + */ + @Operation(summary = "图片预览 - 原图") + @GetMapping("/image/{fileKey}") + ResponseEntity previewOriginal(@PathVariable("fileKey") String fileKey); + + /** + * 文件预览 + * 当文件为图片时,返回图片的缩略图 + * 当文件不是图片时,返回文件类型图标 + * + * @param fileKey 文件KEY + * @return 缩略图地址 + */ + @Operation(summary = "图片预览 - 缩略图") + @GetMapping("/preview/{fileKey}") + ResponseEntity previewMedium(@PathVariable("fileKey") String fileKey); + + /** + * 根据文件类型取得图标 + * + * @param fileType 文件扩展名 + */ + @Operation(summary = "获取图标") + @GetMapping("/icon/{fileType}") + void icon(@PathVariable("fileType") String fileType); + +}