1. 附件加密上传下载
This commit is contained in:
@@ -3,11 +3,10 @@ package cn.iocoder.yudao.module.infra.api.file;
|
||||
import cn.iocoder.yudao.framework.common.pojo.CommonResult;
|
||||
import cn.iocoder.yudao.module.infra.api.file.dto.FileCreateReqDTO;
|
||||
import cn.iocoder.yudao.module.infra.service.file.FileService;
|
||||
import jakarta.annotation.Resource;
|
||||
import org.springframework.validation.annotation.Validated;
|
||||
import org.springframework.web.bind.annotation.RestController;
|
||||
|
||||
import jakarta.annotation.Resource;
|
||||
|
||||
import static cn.iocoder.yudao.framework.common.pojo.CommonResult.success;
|
||||
|
||||
@RestController // 提供 RESTful API 接口,给 Feign 调用
|
||||
@@ -20,7 +19,7 @@ public class FileApiImpl implements FileApi {
|
||||
@Override
|
||||
public CommonResult<String> createFile(FileCreateReqDTO createReqDTO) {
|
||||
return success(fileService.createFile(createReqDTO.getContent(), createReqDTO.getName(),
|
||||
createReqDTO.getDirectory(), createReqDTO.getType()));
|
||||
createReqDTO.getDirectory(), createReqDTO.getType(), false));
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -3,6 +3,7 @@ package cn.iocoder.yudao.module.infra.controller.admin.file;
|
||||
import cn.hutool.core.io.IoUtil;
|
||||
import cn.hutool.core.util.StrUtil;
|
||||
import cn.hutool.core.util.URLUtil;
|
||||
import cn.iocoder.yudao.framework.common.exception.ServiceException;
|
||||
import cn.iocoder.yudao.framework.common.pojo.CommonResult;
|
||||
import cn.iocoder.yudao.framework.common.pojo.PageResult;
|
||||
import cn.iocoder.yudao.framework.common.util.object.BeanUtils;
|
||||
@@ -29,8 +30,12 @@ import org.springframework.web.multipart.MultipartFile;
|
||||
import java.io.IOException;
|
||||
|
||||
import static cn.iocoder.yudao.framework.common.pojo.CommonResult.success;
|
||||
import static cn.iocoder.yudao.framework.security.core.util.SecurityFrameworkUtils.getLoginUserId;
|
||||
import static cn.iocoder.yudao.module.infra.framework.file.core.utils.FileTypeUtils.writeAttachment;
|
||||
|
||||
/**
|
||||
* @author chenbowen
|
||||
*/
|
||||
@Tag(name = "管理后台 - 文件存储")
|
||||
@RestController
|
||||
@RequestMapping("/infra/file")
|
||||
@@ -46,8 +51,7 @@ public class FileController {
|
||||
public CommonResult<String> uploadFile(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()));
|
||||
return success(fileService.createFile(content, file.getOriginalFilename(), uploadReqVO.getDirectory(), file.getContentType(), uploadReqVO.getEncrypt()));
|
||||
}
|
||||
|
||||
@PostMapping("/upload-with-return")
|
||||
@@ -55,8 +59,7 @@ public class FileController {
|
||||
public CommonResult<FileRespVO> uploadWithReturn(FileUploadReqVO uploadReqVO) throws IOException {
|
||||
MultipartFile file = uploadReqVO.getFile();
|
||||
byte[] content = IoUtil.readBytes(file.getInputStream());
|
||||
return success(fileService.createFileWhitReturn(content, file.getOriginalFilename(),
|
||||
uploadReqVO.getDirectory(), file.getContentType()));
|
||||
return success(fileService.createFileWhitReturn(content, file.getOriginalFilename(), uploadReqVO.getDirectory(), file.getContentType(), uploadReqVO.getEncrypt()));
|
||||
}
|
||||
|
||||
@GetMapping("/presigned-url")
|
||||
@@ -120,4 +123,36 @@ public class FileController {
|
||||
return success(BeanUtils.toBean(pageResult, FileRespVO.class));
|
||||
}
|
||||
|
||||
@GetMapping("/verification-code")
|
||||
@Operation(summary = "获取加密文件下载验证码 ")
|
||||
public CommonResult<String> getVerificationCode(@RequestParam("fileId") Long fileId) {
|
||||
Long userId = getLoginUserId();
|
||||
String code = fileService.generateFileVerificationCode(fileId, userId);
|
||||
return success(code);
|
||||
}
|
||||
|
||||
@GetMapping("/verify-and-download")
|
||||
@Operation(summary = "校验验证码并下载解密文件")
|
||||
public void verifyAndDownload(@Valid @RequestParam Long fileId, @RequestParam String code, HttpServletResponse response) throws Exception {
|
||||
Long userId = getLoginUserId();
|
||||
byte[] content = fileService.verifyCodeAndGetFile(fileId, userId, code);
|
||||
FileDO fileDO = fileService.getActiveFileById(fileId);
|
||||
if (fileDO == null) {
|
||||
response.setStatus(HttpStatus.NOT_FOUND.value());
|
||||
return;
|
||||
}
|
||||
writeAttachment(response, fileDO.getName(), content);
|
||||
}
|
||||
@GetMapping("/generate-download-code")
|
||||
@Operation(summary = "获取下载验证码")
|
||||
public CommonResult<String> preDownloadEncrypt(@RequestParam("fileId") Long fileId) {
|
||||
Long userId = getLoginUserId();
|
||||
try {
|
||||
fileService.generateFileVerificationCode(fileId, userId);
|
||||
FileDO activeFileById = fileService.getActiveFileById(fileId);
|
||||
return CommonResult.customize(activeFileById.getName(), HttpStatus.OK.value(), "验证码已生成,请使用验证码下载文件");
|
||||
} catch (ServiceException e) {
|
||||
return CommonResult.customize("生成验证码失败", HttpStatus.OK.value(), e.getMessage());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,11 +1,13 @@
|
||||
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;
|
||||
import org.springframework.web.multipart.MultipartFile;
|
||||
|
||||
import jakarta.validation.constraints.NotNull;
|
||||
|
||||
/**
|
||||
* @author chenbowen
|
||||
*/
|
||||
@Schema(description = "管理后台 - 上传文件 Request VO")
|
||||
@Data
|
||||
public class FileUploadReqVO {
|
||||
@@ -17,4 +19,7 @@ public class FileUploadReqVO {
|
||||
@Schema(description = "文件目录", example = "XXX/YYY")
|
||||
private String directory;
|
||||
|
||||
@Schema(description = "是否加密", example = "")
|
||||
private Boolean encrypt = false;
|
||||
|
||||
}
|
||||
|
||||
@@ -0,0 +1,16 @@
|
||||
package cn.iocoder.yudao.module.infra.controller.admin.file.vo.file;
|
||||
|
||||
import jakarta.validation.constraints.NotBlank;
|
||||
import jakarta.validation.constraints.NotNull;
|
||||
import lombok.Data;
|
||||
|
||||
/**
|
||||
* @author chenbowen
|
||||
*/
|
||||
@Data
|
||||
public class FileVerificationCodeCheckReqVO {
|
||||
@NotNull(message = "附件ID不能为空")
|
||||
private Long fileId;
|
||||
@NotBlank(message = "验证码不能为空")
|
||||
private String code;
|
||||
}
|
||||
@@ -0,0 +1,23 @@
|
||||
package cn.iocoder.yudao.module.infra.controller.admin.file.vo.file;
|
||||
|
||||
import jakarta.validation.constraints.NotNull;
|
||||
|
||||
public class FileVerificationCodeReqVO {
|
||||
@NotNull(message = "附件ID不能为空")
|
||||
private Long fileId;
|
||||
@NotNull(message = "用户ID不能为空")
|
||||
private Long userId;
|
||||
|
||||
public Long getFileId() {
|
||||
return fileId;
|
||||
}
|
||||
public void setFileId(Long fileId) {
|
||||
this.fileId = fileId;
|
||||
}
|
||||
public Long getUserId() {
|
||||
return userId;
|
||||
}
|
||||
public void setUserId(Long userId) {
|
||||
this.userId = userId;
|
||||
}
|
||||
}
|
||||
@@ -37,7 +37,7 @@ public class AppFileController {
|
||||
MultipartFile file = uploadReqVO.getFile();
|
||||
byte[] content = IoUtil.readBytes(file.getInputStream());
|
||||
return success(fileService.createFile(content, file.getOriginalFilename(),
|
||||
uploadReqVO.getDirectory(), file.getContentType()));
|
||||
uploadReqVO.getDirectory(), file.getContentType(), false));
|
||||
}
|
||||
|
||||
@GetMapping("/presigned-url")
|
||||
|
||||
@@ -62,4 +62,9 @@ public class FileDO extends BaseDO {
|
||||
*/
|
||||
private String hash;
|
||||
|
||||
/**
|
||||
* AES 加密时的随机 IV(Base64 编码)
|
||||
*/
|
||||
private String aesIv;
|
||||
|
||||
}
|
||||
|
||||
@@ -0,0 +1,9 @@
|
||||
package cn.iocoder.yudao.module.infra.dal.redis;
|
||||
|
||||
/**
|
||||
* @author chenbowen
|
||||
*/
|
||||
public class RedisKeyConstants {
|
||||
public static final String FILE_VERIFICATION_CODE = "infra:file:verification_code:%d:%d";
|
||||
public static final String FILE_VERIFICATION_CODE_USER_SET = "infra:file:verification_code:user:%d";
|
||||
}
|
||||
@@ -28,17 +28,18 @@ public interface FileService {
|
||||
* 保存文件,并返回文件的访问路径
|
||||
*
|
||||
* <p>会根据文件内容计算哈希值,若已存在相同哈希的文件,则直接复用,不重复上传。</p>
|
||||
*
|
||||
* @param content 文件内容
|
||||
* @param name 文件名称,允许空
|
||||
* @param directory 目录,允许空
|
||||
* @param type 文件的 MIME 类型,允许空
|
||||
* @param encrypt 是否加密文件内容,允许空
|
||||
* @return 文件路径
|
||||
*/
|
||||
String createFile(@NotEmpty(message = "文件内容不能为空") byte[] content,
|
||||
String name, String directory, String type);
|
||||
String createFile(@NotEmpty(message = "文件内容不能为空") byte[] content, String name, String directory, String type, Boolean encrypt);
|
||||
|
||||
@SneakyThrows
|
||||
FileRespVO createFileWhitReturn(byte[] content, String name, String directory, String type);
|
||||
FileRespVO createFileWhitReturn(byte[] content, String name, String directory, String type, Boolean encrypt);
|
||||
|
||||
/**
|
||||
* 生成文件预签名地址信息
|
||||
@@ -57,6 +58,15 @@ public interface FileService {
|
||||
* @return 编号
|
||||
*/
|
||||
Long createFile(FileCreateReqVO createReqVO);
|
||||
/**
|
||||
* 生成文件验证码,带校验逻辑
|
||||
*/
|
||||
String generateFileVerificationCode(Long fileId, Long userId);
|
||||
|
||||
/**
|
||||
* 校验验证码并返回解密后的文件内容
|
||||
*/
|
||||
byte[] verifyCodeAndGetFile(Long fileId, Long userId, String code) throws Exception;
|
||||
|
||||
/**
|
||||
* 删除文件
|
||||
@@ -65,6 +75,8 @@ public interface FileService {
|
||||
*/
|
||||
void deleteFile(Long id) throws Exception;
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* 获得文件内容
|
||||
*
|
||||
@@ -74,4 +86,12 @@ public interface FileService {
|
||||
*/
|
||||
byte[] getFileContent(Long configId, String path) throws Exception;
|
||||
|
||||
/**
|
||||
* 根据 fileId 查询生效中的 FileDO
|
||||
*
|
||||
* @param fileId 文件编号
|
||||
* @return 生效中的 FileDO,若不存在则返回 null
|
||||
*/
|
||||
FileDO getActiveFileById(Long fileId);
|
||||
|
||||
}
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user