From a567c62ae2eb21203e169c4ff5575ba1fd9cbcd8 Mon Sep 17 00:00:00 2001 From: lzh Date: Tue, 17 Mar 2026 17:44:28 +0800 Subject: [PATCH] =?UTF-8?q?feat(infra):=20=E6=96=87=E4=BB=B6=E5=88=97?= =?UTF-8?q?=E8=A1=A8=E5=8F=8A=E8=AF=A6=E6=83=85=E6=8E=A5=E5=8F=A3=E5=A2=9E?= =?UTF-8?q?=E5=8A=A0=E7=A7=81=E6=9C=89=E6=A1=B6=E9=A2=84=E7=AD=BE=E5=90=8D?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - FileController.getFilePage/getFile 对私有桶文件 URL 生成预签名访问地址 Co-Authored-By: Claude Opus 4.6 --- .../controller/admin/file/FileController.java | 286 +++++++++--------- 1 file changed, 149 insertions(+), 137 deletions(-) diff --git a/viewsh-module-infra/viewsh-module-infra-server/src/main/java/com/viewsh/module/infra/controller/admin/file/FileController.java b/viewsh-module-infra/viewsh-module-infra-server/src/main/java/com/viewsh/module/infra/controller/admin/file/FileController.java index effe775..bdb32f3 100644 --- a/viewsh-module-infra/viewsh-module-infra-server/src/main/java/com/viewsh/module/infra/controller/admin/file/FileController.java +++ b/viewsh-module-infra/viewsh-module-infra-server/src/main/java/com/viewsh/module/infra/controller/admin/file/FileController.java @@ -1,137 +1,149 @@ -package com.viewsh.module.infra.controller.admin.file; - -import cn.hutool.core.io.IoUtil; -import cn.hutool.core.util.StrUtil; -import cn.hutool.core.util.URLUtil; -import com.viewsh.framework.common.pojo.CommonResult; -import com.viewsh.framework.common.pojo.PageResult; -import com.viewsh.framework.common.util.object.BeanUtils; -import com.viewsh.framework.tenant.core.aop.TenantIgnore; -import com.viewsh.module.infra.controller.admin.file.vo.file.*; -import com.viewsh.module.infra.dal.dataobject.file.FileDO; -import com.viewsh.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.Parameters; -import io.swagger.v3.oas.annotations.media.Schema; -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; -import org.springframework.validation.annotation.Validated; -import org.springframework.web.bind.annotation.*; -import org.springframework.web.multipart.MultipartFile; - -import java.nio.charset.StandardCharsets; -import java.util.List; - -import static com.viewsh.framework.common.pojo.CommonResult.success; -import static com.viewsh.module.infra.framework.file.core.utils.FileTypeUtils.writeAttachment; - -@Tag(name = "管理后台 - 文件存储") -@RestController -@RequestMapping("/infra/file") -@Validated -@Slf4j -public class FileController { - - @Resource - private FileService fileService; - - @PostMapping("/upload") - @Operation(summary = "上传文件", description = "模式一:后端上传文件") - @Parameter(name = "file", description = "文件附件", required = true, - schema = @Schema(type = "string", format = "binary")) - public CommonResult uploadFile(@Valid FileUploadReqVO uploadReqVO) throws Exception { - MultipartFile file = uploadReqVO.getFile(); - byte[] content = IoUtil.readBytes(file.getInputStream()); - return success(fileService.createFile(content, file.getOriginalFilename(), - uploadReqVO.getDirectory(), file.getContentType())); - } - - @GetMapping("/presigned-url") - @Operation(summary = "获取文件预签名地址(上传)", description = "模式二:前端上传文件:用于前端直接上传七牛、阿里云 OSS 等文件存储器") - @Parameters({ - @Parameter(name = "name", description = "文件名称", required = true), - @Parameter(name = "directory", description = "文件目录") - }) - public CommonResult getFilePresignedUrl( - @RequestParam("name") String name, - @RequestParam(value = "directory", required = false) String directory) { - return success(fileService.presignPutUrl(name, directory)); - } - - @PostMapping("/create") - @Operation(summary = "创建文件", description = "模式二:前端上传文件:配合 presigned-url 接口,记录上传了上传的文件") - public CommonResult createFile(@Valid @RequestBody FileCreateReqVO createReqVO) { - return success(fileService.createFile(createReqVO)); - } - - @GetMapping("/get") - @Operation(summary = "获得文件") - @Parameter(name = "id", description = "编号", required = true) - @PreAuthorize("@ss.hasPermission('infra:file:query')") - public CommonResult getFile(@RequestParam("id") Long id) { - return success(BeanUtils.toBean(fileService.getFile(id), FileRespVO.class)); - } - - @DeleteMapping("/delete") - @Operation(summary = "删除文件") - @Parameter(name = "id", description = "编号", required = true) - @PreAuthorize("@ss.hasPermission('infra:file:delete')") - public CommonResult deleteFile(@RequestParam("id") Long id) throws Exception { - fileService.deleteFile(id); - return success(true); - } - - @DeleteMapping("/delete-list") - @Operation(summary = "批量删除文件") - @Parameter(name = "ids", description = "编号列表", required = true) - @PreAuthorize("@ss.hasPermission('infra:file:delete')") - public CommonResult deleteFileList(@RequestParam("ids") List ids) throws Exception { - fileService.deleteFileList(ids); - return success(true); - } - - @GetMapping("/{configId}/get/**") - @PermitAll - @TenantIgnore - @Operation(summary = "下载文件") - @Parameter(name = "configId", description = "配置编号", required = true) - public void getFileContent(HttpServletRequest request, - HttpServletResponse response, - @PathVariable("configId") Long configId) throws Exception { - // 获取请求的路径 - String path = StrUtil.subAfter(request.getRequestURI(), "/get/", false); - if (StrUtil.isEmpty(path)) { - throw new IllegalArgumentException("结尾的 path 路径必须传递"); - } - // 解码,解决中文路径的问题 - // https://gitee.com/zhijiantianya/ruoyi-vue-pro/pulls/807/ - // https://gitee.com/zhijiantianya/ruoyi-vue-pro/pulls/1432/ - path = URLUtil.decode(path, StandardCharsets.UTF_8, false); - - // 读取内容 - byte[] content = fileService.getFileContent(configId, path); - if (content == null) { - log.warn("[getFileContent][configId({}) path({}) 文件不存在]", configId, path); - response.setStatus(HttpStatus.NOT_FOUND.value()); - return; - } - writeAttachment(response, path, content); - } - - @GetMapping("/page") - @Operation(summary = "获得文件分页") - @PreAuthorize("@ss.hasPermission('infra:file:query')") - public CommonResult> getFilePage(@Valid FilePageReqVO pageVO) { - PageResult pageResult = fileService.getFilePage(pageVO); - return success(BeanUtils.toBean(pageResult, FileRespVO.class)); - } - -} +package com.viewsh.module.infra.controller.admin.file; + +import cn.hutool.core.io.IoUtil; +import cn.hutool.core.util.StrUtil; +import cn.hutool.core.util.URLUtil; +import com.viewsh.framework.common.pojo.CommonResult; +import com.viewsh.framework.common.pojo.PageResult; +import com.viewsh.framework.common.util.object.BeanUtils; +import com.viewsh.framework.tenant.core.aop.TenantIgnore; +import com.viewsh.module.infra.controller.admin.file.vo.file.*; +import com.viewsh.module.infra.dal.dataobject.file.FileDO; +import com.viewsh.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.Parameters; +import io.swagger.v3.oas.annotations.media.Schema; +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; +import org.springframework.validation.annotation.Validated; +import org.springframework.web.bind.annotation.*; +import org.springframework.web.multipart.MultipartFile; + +import java.nio.charset.StandardCharsets; +import java.util.List; + +import static com.viewsh.framework.common.pojo.CommonResult.success; +import static com.viewsh.module.infra.framework.file.core.utils.FileTypeUtils.writeAttachment; + +@Tag(name = "管理后台 - 文件存储") +@RestController +@RequestMapping("/infra/file") +@Validated +@Slf4j +public class FileController { + + @Resource + private FileService fileService; + + @PostMapping("/upload") + @Operation(summary = "上传文件", description = "模式一:后端上传文件") + @Parameter(name = "file", description = "文件附件", required = true, + schema = @Schema(type = "string", format = "binary")) + public CommonResult uploadFile(@Valid FileUploadReqVO uploadReqVO) throws Exception { + MultipartFile file = uploadReqVO.getFile(); + byte[] content = IoUtil.readBytes(file.getInputStream()); + return success(fileService.createFile(content, file.getOriginalFilename(), + uploadReqVO.getDirectory(), file.getContentType())); + } + + @GetMapping("/presigned-url") + @Operation(summary = "获取文件预签名地址(上传)", description = "模式二:前端上传文件:用于前端直接上传七牛、阿里云 OSS 等文件存储器") + @Parameters({ + @Parameter(name = "name", description = "文件名称", required = true), + @Parameter(name = "directory", description = "文件目录") + }) + public CommonResult getFilePresignedUrl( + @RequestParam("name") String name, + @RequestParam(value = "directory", required = false) String directory) { + return success(fileService.presignPutUrl(name, directory)); + } + + @PostMapping("/create") + @Operation(summary = "创建文件", description = "模式二:前端上传文件:配合 presigned-url 接口,记录上传了上传的文件") + public CommonResult createFile(@Valid @RequestBody FileCreateReqVO createReqVO) { + return success(fileService.createFile(createReqVO)); + } + + @GetMapping("/get") + @Operation(summary = "获得文件") + @Parameter(name = "id", description = "编号", required = true) + @PreAuthorize("@ss.hasPermission('infra:file:query')") + public CommonResult getFile(@RequestParam("id") Long id) { + FileRespVO respVO = BeanUtils.toBean(fileService.getFile(id), FileRespVO.class); + // 私有桶:对文件 URL 生成预签名访问地址 + if (respVO != null && StrUtil.isNotEmpty(respVO.getUrl())) { + respVO.setUrl(fileService.presignGetUrl(respVO.getUrl(), null)); + } + return success(respVO); + } + + @DeleteMapping("/delete") + @Operation(summary = "删除文件") + @Parameter(name = "id", description = "编号", required = true) + @PreAuthorize("@ss.hasPermission('infra:file:delete')") + public CommonResult deleteFile(@RequestParam("id") Long id) throws Exception { + fileService.deleteFile(id); + return success(true); + } + + @DeleteMapping("/delete-list") + @Operation(summary = "批量删除文件") + @Parameter(name = "ids", description = "编号列表", required = true) + @PreAuthorize("@ss.hasPermission('infra:file:delete')") + public CommonResult deleteFileList(@RequestParam("ids") List ids) throws Exception { + fileService.deleteFileList(ids); + return success(true); + } + + @GetMapping("/{configId}/get/**") + @PermitAll + @TenantIgnore + @Operation(summary = "下载文件") + @Parameter(name = "configId", description = "配置编号", required = true) + public void getFileContent(HttpServletRequest request, + HttpServletResponse response, + @PathVariable("configId") Long configId) throws Exception { + // 获取请求的路径 + String path = StrUtil.subAfter(request.getRequestURI(), "/get/", false); + if (StrUtil.isEmpty(path)) { + throw new IllegalArgumentException("结尾的 path 路径必须传递"); + } + // 解码,解决中文路径的问题 + // https://gitee.com/zhijiantianya/ruoyi-vue-pro/pulls/807/ + // https://gitee.com/zhijiantianya/ruoyi-vue-pro/pulls/1432/ + path = URLUtil.decode(path, StandardCharsets.UTF_8, false); + + // 读取内容 + byte[] content = fileService.getFileContent(configId, path); + if (content == null) { + log.warn("[getFileContent][configId({}) path({}) 文件不存在]", configId, path); + response.setStatus(HttpStatus.NOT_FOUND.value()); + return; + } + writeAttachment(response, path, content); + } + + @GetMapping("/page") + @Operation(summary = "获得文件分页") + @PreAuthorize("@ss.hasPermission('infra:file:query')") + public CommonResult> getFilePage(@Valid FilePageReqVO pageVO) { + PageResult pageResult = fileService.getFilePage(pageVO); + PageResult voPageResult = BeanUtils.toBean(pageResult, FileRespVO.class); + // 私有桶:对文件 URL 生成预签名访问地址 + voPageResult.getList().forEach(vo -> { + if (StrUtil.isNotEmpty(vo.getUrl())) { + vo.setUrl(fileService.presignGetUrl(vo.getUrl(), null)); + } + }); + return success(voPageResult); + } + +}