feat(infra): 文件列表及详情接口增加私有桶预签名

- FileController.getFilePage/getFile 对私有桶文件 URL 生成预签名访问地址

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
lzh
2026-03-17 17:44:28 +08:00
parent 6a9aa82bac
commit a567c62ae2

View File

@@ -1,137 +1,149 @@
package com.viewsh.module.infra.controller.admin.file; package com.viewsh.module.infra.controller.admin.file;
import cn.hutool.core.io.IoUtil; import cn.hutool.core.io.IoUtil;
import cn.hutool.core.util.StrUtil; import cn.hutool.core.util.StrUtil;
import cn.hutool.core.util.URLUtil; import cn.hutool.core.util.URLUtil;
import com.viewsh.framework.common.pojo.CommonResult; import com.viewsh.framework.common.pojo.CommonResult;
import com.viewsh.framework.common.pojo.PageResult; import com.viewsh.framework.common.pojo.PageResult;
import com.viewsh.framework.common.util.object.BeanUtils; import com.viewsh.framework.common.util.object.BeanUtils;
import com.viewsh.framework.tenant.core.aop.TenantIgnore; import com.viewsh.framework.tenant.core.aop.TenantIgnore;
import com.viewsh.module.infra.controller.admin.file.vo.file.*; import com.viewsh.module.infra.controller.admin.file.vo.file.*;
import com.viewsh.module.infra.dal.dataobject.file.FileDO; import com.viewsh.module.infra.dal.dataobject.file.FileDO;
import com.viewsh.module.infra.service.file.FileService; import com.viewsh.module.infra.service.file.FileService;
import io.swagger.v3.oas.annotations.Operation; import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.Parameter; import io.swagger.v3.oas.annotations.Parameter;
import io.swagger.v3.oas.annotations.Parameters; import io.swagger.v3.oas.annotations.Parameters;
import io.swagger.v3.oas.annotations.media.Schema; import io.swagger.v3.oas.annotations.media.Schema;
import io.swagger.v3.oas.annotations.tags.Tag; import io.swagger.v3.oas.annotations.tags.Tag;
import jakarta.annotation.Resource; import jakarta.annotation.Resource;
import jakarta.annotation.security.PermitAll; import jakarta.annotation.security.PermitAll;
import jakarta.servlet.http.HttpServletRequest; import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse; import jakarta.servlet.http.HttpServletResponse;
import jakarta.validation.Valid; import jakarta.validation.Valid;
import lombok.extern.slf4j.Slf4j; import lombok.extern.slf4j.Slf4j;
import org.springframework.http.HttpStatus; import org.springframework.http.HttpStatus;
import org.springframework.security.access.prepost.PreAuthorize; import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.validation.annotation.Validated; import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.*; import org.springframework.web.bind.annotation.*;
import org.springframework.web.multipart.MultipartFile; import org.springframework.web.multipart.MultipartFile;
import java.nio.charset.StandardCharsets; import java.nio.charset.StandardCharsets;
import java.util.List; import java.util.List;
import static com.viewsh.framework.common.pojo.CommonResult.success; import static com.viewsh.framework.common.pojo.CommonResult.success;
import static com.viewsh.module.infra.framework.file.core.utils.FileTypeUtils.writeAttachment; import static com.viewsh.module.infra.framework.file.core.utils.FileTypeUtils.writeAttachment;
@Tag(name = "管理后台 - 文件存储") @Tag(name = "管理后台 - 文件存储")
@RestController @RestController
@RequestMapping("/infra/file") @RequestMapping("/infra/file")
@Validated @Validated
@Slf4j @Slf4j
public class FileController { public class FileController {
@Resource @Resource
private FileService fileService; private FileService fileService;
@PostMapping("/upload") @PostMapping("/upload")
@Operation(summary = "上传文件", description = "模式一:后端上传文件") @Operation(summary = "上传文件", description = "模式一:后端上传文件")
@Parameter(name = "file", description = "文件附件", required = true, @Parameter(name = "file", description = "文件附件", required = true,
schema = @Schema(type = "string", format = "binary")) schema = @Schema(type = "string", format = "binary"))
public CommonResult<String> uploadFile(@Valid FileUploadReqVO uploadReqVO) throws Exception { public CommonResult<String> uploadFile(@Valid FileUploadReqVO uploadReqVO) throws Exception {
MultipartFile file = uploadReqVO.getFile(); MultipartFile file = uploadReqVO.getFile();
byte[] content = IoUtil.readBytes(file.getInputStream()); byte[] content = IoUtil.readBytes(file.getInputStream());
return success(fileService.createFile(content, file.getOriginalFilename(), return success(fileService.createFile(content, file.getOriginalFilename(),
uploadReqVO.getDirectory(), file.getContentType())); uploadReqVO.getDirectory(), file.getContentType()));
} }
@GetMapping("/presigned-url") @GetMapping("/presigned-url")
@Operation(summary = "获取文件预签名地址(上传)", description = "模式二:前端上传文件:用于前端直接上传七牛、阿里云 OSS 等文件存储器") @Operation(summary = "获取文件预签名地址(上传)", description = "模式二:前端上传文件:用于前端直接上传七牛、阿里云 OSS 等文件存储器")
@Parameters({ @Parameters({
@Parameter(name = "name", description = "文件名称", required = true), @Parameter(name = "name", description = "文件名称", required = true),
@Parameter(name = "directory", description = "文件目录") @Parameter(name = "directory", description = "文件目录")
}) })
public CommonResult<FilePresignedUrlRespVO> getFilePresignedUrl( public CommonResult<FilePresignedUrlRespVO> getFilePresignedUrl(
@RequestParam("name") String name, @RequestParam("name") String name,
@RequestParam(value = "directory", required = false) String directory) { @RequestParam(value = "directory", required = false) String directory) {
return success(fileService.presignPutUrl(name, directory)); return success(fileService.presignPutUrl(name, directory));
} }
@PostMapping("/create") @PostMapping("/create")
@Operation(summary = "创建文件", description = "模式二:前端上传文件:配合 presigned-url 接口,记录上传了上传的文件") @Operation(summary = "创建文件", description = "模式二:前端上传文件:配合 presigned-url 接口,记录上传了上传的文件")
public CommonResult<Long> createFile(@Valid @RequestBody FileCreateReqVO createReqVO) { public CommonResult<Long> createFile(@Valid @RequestBody FileCreateReqVO createReqVO) {
return success(fileService.createFile(createReqVO)); return success(fileService.createFile(createReqVO));
} }
@GetMapping("/get") @GetMapping("/get")
@Operation(summary = "获得文件") @Operation(summary = "获得文件")
@Parameter(name = "id", description = "编号", required = true) @Parameter(name = "id", description = "编号", required = true)
@PreAuthorize("@ss.hasPermission('infra:file:query')") @PreAuthorize("@ss.hasPermission('infra:file:query')")
public CommonResult<FileRespVO> getFile(@RequestParam("id") Long id) { public CommonResult<FileRespVO> getFile(@RequestParam("id") Long id) {
return success(BeanUtils.toBean(fileService.getFile(id), FileRespVO.class)); FileRespVO respVO = BeanUtils.toBean(fileService.getFile(id), FileRespVO.class);
} // 私有桶:对文件 URL 生成预签名访问地址
if (respVO != null && StrUtil.isNotEmpty(respVO.getUrl())) {
@DeleteMapping("/delete") respVO.setUrl(fileService.presignGetUrl(respVO.getUrl(), null));
@Operation(summary = "删除文件") }
@Parameter(name = "id", description = "编号", required = true) return success(respVO);
@PreAuthorize("@ss.hasPermission('infra:file:delete')") }
public CommonResult<Boolean> deleteFile(@RequestParam("id") Long id) throws Exception {
fileService.deleteFile(id); @DeleteMapping("/delete")
return success(true); @Operation(summary = "删除文件")
} @Parameter(name = "id", description = "编号", required = true)
@PreAuthorize("@ss.hasPermission('infra:file:delete')")
@DeleteMapping("/delete-list") public CommonResult<Boolean> deleteFile(@RequestParam("id") Long id) throws Exception {
@Operation(summary = "批量删除文件") fileService.deleteFile(id);
@Parameter(name = "ids", description = "编号列表", required = true) return success(true);
@PreAuthorize("@ss.hasPermission('infra:file:delete')") }
public CommonResult<Boolean> deleteFileList(@RequestParam("ids") List<Long> ids) throws Exception {
fileService.deleteFileList(ids); @DeleteMapping("/delete-list")
return success(true); @Operation(summary = "批量删除文件")
} @Parameter(name = "ids", description = "编号列表", required = true)
@PreAuthorize("@ss.hasPermission('infra:file:delete')")
@GetMapping("/{configId}/get/**") public CommonResult<Boolean> deleteFileList(@RequestParam("ids") List<Long> ids) throws Exception {
@PermitAll fileService.deleteFileList(ids);
@TenantIgnore return success(true);
@Operation(summary = "下载文件") }
@Parameter(name = "configId", description = "配置编号", required = true)
public void getFileContent(HttpServletRequest request, @GetMapping("/{configId}/get/**")
HttpServletResponse response, @PermitAll
@PathVariable("configId") Long configId) throws Exception { @TenantIgnore
// 获取请求的路径 @Operation(summary = "下载文件")
String path = StrUtil.subAfter(request.getRequestURI(), "/get/", false); @Parameter(name = "configId", description = "配置编号", required = true)
if (StrUtil.isEmpty(path)) { public void getFileContent(HttpServletRequest request,
throw new IllegalArgumentException("结尾的 path 路径必须传递"); HttpServletResponse response,
} @PathVariable("configId") Long configId) throws Exception {
// 解码,解决中文路径的问题 // 获取请求的路径
// https://gitee.com/zhijiantianya/ruoyi-vue-pro/pulls/807/ String path = StrUtil.subAfter(request.getRequestURI(), "/get/", false);
// https://gitee.com/zhijiantianya/ruoyi-vue-pro/pulls/1432/ if (StrUtil.isEmpty(path)) {
path = URLUtil.decode(path, StandardCharsets.UTF_8, false); throw new IllegalArgumentException("结尾的 path 路径必须传递");
}
// 读取内容 // 解码,解决中文路径的问题
byte[] content = fileService.getFileContent(configId, path); // https://gitee.com/zhijiantianya/ruoyi-vue-pro/pulls/807/
if (content == null) { // https://gitee.com/zhijiantianya/ruoyi-vue-pro/pulls/1432/
log.warn("[getFileContent][configId({}) path({}) 文件不存在]", configId, path); path = URLUtil.decode(path, StandardCharsets.UTF_8, false);
response.setStatus(HttpStatus.NOT_FOUND.value());
return; // 读取内容
} byte[] content = fileService.getFileContent(configId, path);
writeAttachment(response, path, content); if (content == null) {
} log.warn("[getFileContent][configId({}) path({}) 文件不存在]", configId, path);
response.setStatus(HttpStatus.NOT_FOUND.value());
@GetMapping("/page") return;
@Operation(summary = "获得文件分页") }
@PreAuthorize("@ss.hasPermission('infra:file:query')") writeAttachment(response, path, content);
public CommonResult<PageResult<FileRespVO>> getFilePage(@Valid FilePageReqVO pageVO) { }
PageResult<FileDO> pageResult = fileService.getFilePage(pageVO);
return success(BeanUtils.toBean(pageResult, FileRespVO.class)); @GetMapping("/page")
} @Operation(summary = "获得文件分页")
@PreAuthorize("@ss.hasPermission('infra:file:query')")
} public CommonResult<PageResult<FileRespVO>> getFilePage(@Valid FilePageReqVO pageVO) {
PageResult<FileDO> pageResult = fileService.getFilePage(pageVO);
PageResult<FileRespVO> 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);
}
}