1. 完善附件加密下载验证码获取流程

This commit is contained in:
chenbowen
2025-07-23 17:50:53 +08:00
parent af8f990cc8
commit b7d2cf3802
6 changed files with 28 additions and 12 deletions

View File

@@ -21,6 +21,7 @@ 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.beans.factory.annotation.Value;
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;
@@ -28,6 +29,8 @@ import org.springframework.web.bind.annotation.*;
import org.springframework.web.multipart.MultipartFile; import org.springframework.web.multipart.MultipartFile;
import java.io.IOException; import java.io.IOException;
import java.net.URLEncoder;
import java.nio.charset.StandardCharsets;
import static cn.iocoder.yudao.framework.common.pojo.CommonResult.success; 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.framework.security.core.util.SecurityFrameworkUtils.getLoginUserId;
@@ -45,6 +48,9 @@ public class FileController {
@Resource @Resource
private FileService fileService; private FileService fileService;
@Value("${yudao.kkfile:}")
private String onlinePreview;
@PostMapping("/upload") @PostMapping("/upload")
@Operation(summary = "上传文件", description = "模式一:后端上传文件") @Operation(summary = "上传文件", description = "模式一:后端上传文件")
@@ -59,7 +65,13 @@ public class FileController {
public CommonResult<FileRespVO> uploadWithReturn(FileUploadReqVO uploadReqVO) throws IOException { public CommonResult<FileRespVO> uploadWithReturn(FileUploadReqVO uploadReqVO) throws IOException {
MultipartFile file = uploadReqVO.getFile(); MultipartFile file = uploadReqVO.getFile();
byte[] content = IoUtil.readBytes(file.getInputStream()); byte[] content = IoUtil.readBytes(file.getInputStream());
return success(fileService.createFileWhitReturn(content, file.getOriginalFilename(), uploadReqVO.getDirectory(), file.getContentType(), uploadReqVO.getEncrypt())); FileRespVO fileWhitReturn = fileService.createFileWhitReturn(content, file.getOriginalFilename(), uploadReqVO.getDirectory(), file.getContentType(), uploadReqVO.getEncrypt());
if (StrUtil.isEmpty(onlinePreview) || StrUtil.isEmpty(fileWhitReturn.getUrl())) {
fileWhitReturn.setPreviewUrl(fileWhitReturn.getUrl());
return success(fileWhitReturn);
}
fileWhitReturn.setPreviewUrl(onlinePreview + URLEncoder.encode(fileWhitReturn.getUrl(), StandardCharsets.UTF_8));
return success(fileWhitReturn);
} }
@GetMapping("/presigned-url") @GetMapping("/presigned-url")
@@ -147,12 +159,13 @@ public class FileController {
@Operation(summary = "获取下载验证码") @Operation(summary = "获取下载验证码")
public CommonResult<String> preDownloadEncrypt(@RequestParam("fileId") Long fileId) { public CommonResult<String> preDownloadEncrypt(@RequestParam("fileId") Long fileId) {
Long userId = getLoginUserId(); Long userId = getLoginUserId();
FileDO activeFileById = new FileDO();
try { try {
activeFileById = fileService.getActiveFileById(fileId);
fileService.generateFileVerificationCode(fileId, userId); fileService.generateFileVerificationCode(fileId, userId);
FileDO activeFileById = fileService.getActiveFileById(fileId);
return CommonResult.customize(activeFileById.getName(), HttpStatus.OK.value(), "验证码已生成,请使用验证码下载文件"); return CommonResult.customize(activeFileById.getName(), HttpStatus.OK.value(), "验证码已生成,请使用验证码下载文件");
} catch (ServiceException e) { } catch (ServiceException e) {
return CommonResult.customize("生成验证码失败", HttpStatus.OK.value(), e.getMessage()); return CommonResult.customize(activeFileById.getName(), HttpStatus.BAD_REQUEST.value(), e.getMessage());
} }
} }
} }

View File

@@ -24,6 +24,10 @@ public class FileRespVO {
@Schema(description = "文件 URL", requiredMode = Schema.RequiredMode.REQUIRED, example = "https://www.iocoder.cn/yudao.jpg") @Schema(description = "文件 URL", requiredMode = Schema.RequiredMode.REQUIRED, example = "https://www.iocoder.cn/yudao.jpg")
private String url; private String url;
// 附件预览地址
@Schema(description = "附件预览地址", example = "https://www.iocoder.cn/yudao.jpg")
private String previewUrl;
@Schema(description = "文件MIME类型", example = "application/octet-stream") @Schema(description = "文件MIME类型", example = "application/octet-stream")
private String type; private String type;

View File

@@ -5,7 +5,6 @@ import cn.hutool.core.io.FileUtil;
import cn.hutool.core.lang.Assert; import cn.hutool.core.lang.Assert;
import cn.hutool.core.util.StrUtil; import cn.hutool.core.util.StrUtil;
import cn.hutool.crypto.digest.DigestUtil; import cn.hutool.crypto.digest.DigestUtil;
import cn.iocoder.yudao.framework.common.exception.ServiceException;
import cn.iocoder.yudao.framework.common.pojo.PageResult; import cn.iocoder.yudao.framework.common.pojo.PageResult;
import cn.iocoder.yudao.framework.common.util.object.BeanUtils; import cn.iocoder.yudao.framework.common.util.object.BeanUtils;
import cn.iocoder.yudao.module.infra.controller.admin.file.vo.file.FileCreateReqVO; import cn.iocoder.yudao.module.infra.controller.admin.file.vo.file.FileCreateReqVO;
@@ -48,7 +47,7 @@ public class FileServiceImpl implements FileService {
@Value("${yudao.AES.key}") @Value("${yudao.AES.key}")
private String aesKey; private String aesKey;
@Value("${yudao.verify-code:''}") @Value("${yudao.verify-code:}")
private String fixedVerifyCode; private String fixedVerifyCode;
@Resource @Resource
@@ -59,11 +58,7 @@ public class FileServiceImpl implements FileService {
validateFileExists(fileId); validateFileExists(fileId);
String codeKey = String.format(RedisKeyConstants.FILE_VERIFICATION_CODE, userId, fileId); String codeKey = String.format(RedisKeyConstants.FILE_VERIFICATION_CODE, userId, fileId);
String userSetKey = String.format(RedisKeyConstants.FILE_VERIFICATION_CODE_USER_SET, userId); String userSetKey = String.format(RedisKeyConstants.FILE_VERIFICATION_CODE_USER_SET, userId);
try { return VerificationCodeUtil.generateCode(codeKey, userSetKey, stringRedisTemplate);
return VerificationCodeUtil.generateCode(codeKey, userSetKey, stringRedisTemplate);
} catch (ServiceException e) {
throw exception(FILE_CAPTCHA_GENERATE_FAIL, e.getMessage());
}
} }
@Override @Override

View File

@@ -28,13 +28,13 @@ public class VerificationCodeUtil {
if (existCode != null) { if (existCode != null) {
Long ttl = redisTemplate.getExpire(codeKey, TimeUnit.SECONDS); Long ttl = redisTemplate.getExpire(codeKey, TimeUnit.SECONDS);
if (ttl > 10) { if (ttl > 10) {
throw exception(FILE_CAPTCHA_EXISTS, "同一附件60秒内只能获取一次验证码"); throw exception(FILE_CAPTCHA_EXISTS, "当前附件已存在生效中的验证码");
} }
// ttl <= 10 秒,允许重新获取 // ttl <= 10 秒,允许重新获取
} }
Long size = redisTemplate.opsForSet().size(userSetKey); Long size = redisTemplate.opsForSet().size(userSetKey);
if (size != null && size >= MAX_CODE_PER_USER) { if (size != null && size >= MAX_CODE_PER_USER) {
throw exception(FILE_CAPTCHA_MAX, "同一用户最多只能10个有效验证码"); throw exception(FILE_CAPTCHA_MAX, "同一用户最多只能获取10个有效验证码");
} }
String code = String.format("%06d", new Random().nextInt(1000000)); String code = String.format("%06d", new Random().nextInt(1000000));
redisTemplate.opsForValue().set(codeKey, code, CODE_EXPIRE_SECONDS, TimeUnit.SECONDS); redisTemplate.opsForValue().set(codeKey, code, CODE_EXPIRE_SECONDS, TimeUnit.SECONDS);

View File

@@ -132,6 +132,8 @@ yudao:
# 附件加密相关配置 # 附件加密相关配置
AES: AES:
key: "0123456789abcdef0123456789abcdef" key: "0123456789abcdef0123456789abcdef"
# 附件预览
kkfile: "http://172.16.46.63:30012/onlinePreview?url="
info: info:
version: 1.0.0 version: 1.0.0
base-package: cn.iocoder.yudao.module.infra base-package: cn.iocoder.yudao.module.infra

View File

@@ -218,6 +218,8 @@ yudao:
tencent-lbs-key: TVDBZ-TDILD-4ON4B-PFDZA-RNLKH-VVF6E # QQ 地图的密钥 https://lbs.qq.com/service/staticV2/staticGuide/staticDoc tencent-lbs-key: TVDBZ-TDILD-4ON4B-PFDZA-RNLKH-VVF6E # QQ 地图的密钥 https://lbs.qq.com/service/staticV2/staticGuide/staticDoc
AES: AES:
key: "0123456789abcdef0123456789abcdef" key: "0123456789abcdef0123456789abcdef"
verify-code: 666666
justauth: justauth:
enabled: true enabled: true