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.validation.Valid;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.http.HttpStatus;
import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.validation.annotation.Validated;
@@ -28,6 +29,8 @@ import org.springframework.web.bind.annotation.*;
import org.springframework.web.multipart.MultipartFile;
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.security.core.util.SecurityFrameworkUtils.getLoginUserId;
@@ -45,6 +48,9 @@ public class FileController {
@Resource
private FileService fileService;
@Value("${yudao.kkfile:}")
private String onlinePreview;
@PostMapping("/upload")
@Operation(summary = "上传文件", description = "模式一:后端上传文件")
@@ -59,7 +65,13 @@ 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(), 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")
@@ -147,12 +159,13 @@ public class FileController {
@Operation(summary = "获取下载验证码")
public CommonResult<String> preDownloadEncrypt(@RequestParam("fileId") Long fileId) {
Long userId = getLoginUserId();
FileDO activeFileById = new FileDO();
try {
activeFileById = fileService.getActiveFileById(fileId);
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());
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")
private String url;
// 附件预览地址
@Schema(description = "附件预览地址", example = "https://www.iocoder.cn/yudao.jpg")
private String previewUrl;
@Schema(description = "文件MIME类型", example = "application/octet-stream")
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.util.StrUtil;
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.util.object.BeanUtils;
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}")
private String aesKey;
@Value("${yudao.verify-code:''}")
@Value("${yudao.verify-code:}")
private String fixedVerifyCode;
@Resource
@@ -59,11 +58,7 @@ public class FileServiceImpl implements FileService {
validateFileExists(fileId);
String codeKey = String.format(RedisKeyConstants.FILE_VERIFICATION_CODE, userId, fileId);
String userSetKey = String.format(RedisKeyConstants.FILE_VERIFICATION_CODE_USER_SET, userId);
try {
return VerificationCodeUtil.generateCode(codeKey, userSetKey, stringRedisTemplate);
} catch (ServiceException e) {
throw exception(FILE_CAPTCHA_GENERATE_FAIL, e.getMessage());
}
return VerificationCodeUtil.generateCode(codeKey, userSetKey, stringRedisTemplate);
}
@Override

View File

@@ -28,13 +28,13 @@ public class VerificationCodeUtil {
if (existCode != null) {
Long ttl = redisTemplate.getExpire(codeKey, TimeUnit.SECONDS);
if (ttl > 10) {
throw exception(FILE_CAPTCHA_EXISTS, "同一附件60秒内只能获取一次验证码");
throw exception(FILE_CAPTCHA_EXISTS, "当前附件已存在生效中的验证码");
}
// ttl <= 10 秒,允许重新获取
}
Long size = redisTemplate.opsForSet().size(userSetKey);
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));
redisTemplate.opsForValue().set(codeKey, code, CODE_EXPIRE_SECONDS, TimeUnit.SECONDS);

View File

@@ -132,6 +132,8 @@ yudao:
# 附件加密相关配置
AES:
key: "0123456789abcdef0123456789abcdef"
# 附件预览
kkfile: "http://172.16.46.63:30012/onlinePreview?url="
info:
version: 1.0.0
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
AES:
key: "0123456789abcdef0123456789abcdef"
verify-code: 666666
justauth:
enabled: true