From 8ab6cb4baef869565060aa21791e84594f53733b Mon Sep 17 00:00:00 2001 From: chenbowen Date: Tue, 22 Jul 2025 19:36:46 +0800 Subject: [PATCH] =?UTF-8?q?1.=20=E9=99=84=E4=BB=B6=E5=8A=A0=E5=AF=86?= =?UTF-8?q?=E4=B8=8A=E4=BC=A0=E4=B8=8B=E8=BD=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- sql/dm/patch.sql | 7 +- sql/mysql/patch.sql | 5 +- .../enums/GlobalErrorCodeConstants.java | 2 +- .../core/util/BusinessDeptHandleUtil.java | 64 ++++++++++ .../BusinessHeaderInterceptor.java | 66 ++-------- .../infra/enums/ErrorCodeConstants.java | 10 ++ .../module/infra/api/file/FileApiImpl.java | 5 +- .../controller/admin/file/FileController.java | 43 ++++++- .../admin/file/vo/file/FileUploadReqVO.java | 9 +- .../file/FileVerificationCodeCheckReqVO.java | 16 +++ .../vo/file/FileVerificationCodeReqVO.java | 23 ++++ .../app/file/AppFileController.java | 2 +- .../infra/dal/dataobject/file/FileDO.java | 5 + .../infra/dal/redis/RedisKeyConstants.java | 9 ++ .../infra/service/file/FileService.java | 26 +++- .../infra/service/file/FileServiceImpl.java | 113 ++++++++++++++++-- .../infra/util/VerificationCodeUtil.java | 57 +++++++++ .../src/main/resources/application-local.yaml | 4 +- .../src/main/resources/application.yaml | 3 + .../service/file/FileServiceImplTest.java | 12 +- .../test/resources/application-unit-test.yaml | 2 + .../src/test/resources/sql/create_tables.sql | 1 + .../src/main/resources/application-local.yaml | 2 +- yudao-server/pom.xml | 15 ++- .../src/main/resources/application-dev.yaml | 2 + .../src/main/resources/application-local.yaml | 2 + 26 files changed, 411 insertions(+), 94 deletions(-) create mode 100644 yudao-framework/yudao-spring-boot-starter-biz-business/src/main/java/cn/iocoder/yudao/framework/business/core/util/BusinessDeptHandleUtil.java create mode 100644 yudao-module-infra/yudao-module-infra-server/src/main/java/cn/iocoder/yudao/module/infra/controller/admin/file/vo/file/FileVerificationCodeCheckReqVO.java create mode 100644 yudao-module-infra/yudao-module-infra-server/src/main/java/cn/iocoder/yudao/module/infra/controller/admin/file/vo/file/FileVerificationCodeReqVO.java create mode 100644 yudao-module-infra/yudao-module-infra-server/src/main/java/cn/iocoder/yudao/module/infra/dal/redis/RedisKeyConstants.java create mode 100644 yudao-module-infra/yudao-module-infra-server/src/main/java/cn/iocoder/yudao/module/infra/util/VerificationCodeUtil.java diff --git a/sql/dm/patch.sql b/sql/dm/patch.sql index 4ecdf651..5607679e 100644 --- a/sql/dm/patch.sql +++ b/sql/dm/patch.sql @@ -1,3 +1,8 @@ ALTER TABLE infra_file ADD hash VARCHAR(64); COMMENT ON COLUMN infra_file.hash IS '文件哈希值(SHA-256)'; -CREATE INDEX idx_infra_file_hash ON infra_file(hash); \ No newline at end of file +CREATE INDEX idx_infra_file_hash ON infra_file(hash); + +-- 2. 附件信息表新增 AES 加密时存储的随机 IV 字段 +ALTER TABLE infra_file ADD aes_iv VARCHAR(128); + +COMMENT ON COLUMN infra_file.aes_iv IS 'AES加密时的随机IV(Base64编码)'; \ No newline at end of file diff --git a/sql/mysql/patch.sql b/sql/mysql/patch.sql index 750ec693..ffd49db9 100644 --- a/sql/mysql/patch.sql +++ b/sql/mysql/patch.sql @@ -1,3 +1,6 @@ -- 1. 附件信息表新增上传文件 Hash 字段,如果上传文件 hash 重复直接复用不进行重复上传 ALTER TABLE infra_file ADD COLUMN hash VARCHAR(64) COMMENT '文件哈希值(SHA-256)'; -CREATE INDEX idx_infra_file_hash ON infra_file(hash); \ No newline at end of file +CREATE INDEX idx_infra_file_hash ON infra_file(hash); + +-- 2. 附件信息表新增 AES 加密时存储的随机 IV 字段 +ALTER TABLE infra_file ADD COLUMN aes_iv VARCHAR(128) NULL COMMENT 'AES加密时的随机IV(Base64编码)'; \ No newline at end of file diff --git a/yudao-framework/yudao-common/src/main/java/cn/iocoder/yudao/framework/common/exception/enums/GlobalErrorCodeConstants.java b/yudao-framework/yudao-common/src/main/java/cn/iocoder/yudao/framework/common/exception/enums/GlobalErrorCodeConstants.java index 3eaa9a19..20566f10 100644 --- a/yudao-framework/yudao-common/src/main/java/cn/iocoder/yudao/framework/common/exception/enums/GlobalErrorCodeConstants.java +++ b/yudao-framework/yudao-common/src/main/java/cn/iocoder/yudao/framework/common/exception/enums/GlobalErrorCodeConstants.java @@ -40,5 +40,5 @@ public interface GlobalErrorCodeConstants { // ========== 业务错误段 ========== // 用户未设置公司信息,无法办理当前业务 - ErrorCode USER_NOT_SET_DEPT = new ErrorCode(1000, "用户未设置有效的公司或部门信息,无法办理当前业务"); + ErrorCode USER_NOT_SET_DEPT = new ErrorCode(1000, "用户未设置有效的公司或部门信息,无法办理当前业务,请确认已实现 BusinessControllerMarker 业务接口标记"); } diff --git a/yudao-framework/yudao-spring-boot-starter-biz-business/src/main/java/cn/iocoder/yudao/framework/business/core/util/BusinessDeptHandleUtil.java b/yudao-framework/yudao-spring-boot-starter-biz-business/src/main/java/cn/iocoder/yudao/framework/business/core/util/BusinessDeptHandleUtil.java new file mode 100644 index 00000000..35377ce6 --- /dev/null +++ b/yudao-framework/yudao-spring-boot-starter-biz-business/src/main/java/cn/iocoder/yudao/framework/business/core/util/BusinessDeptHandleUtil.java @@ -0,0 +1,64 @@ +package cn.iocoder.yudao.framework.business.core.util; + +import cn.hutool.json.JSONObject; +import cn.hutool.json.JSONUtil; +import cn.iocoder.yudao.framework.common.pojo.CompanyDeptInfo; +import cn.iocoder.yudao.framework.security.core.LoginUser; +import jakarta.servlet.http.HttpServletRequest; +import jakarta.servlet.http.HttpServletResponse; + +import java.util.HashMap; +import java.util.HashSet; +import java.util.Optional; +import java.util.Set; +import java.util.stream.Collectors; + +import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.singleton; +import static cn.iocoder.yudao.framework.security.core.util.SecurityFrameworkUtils.getLoginUser; + +/** + * @author chenbowen + */ +public class BusinessDeptHandleUtil { + public static Set getBelongCompanyAndDept(HttpServletRequest request, HttpServletResponse response) throws Exception { + response.setContentType("application/json;charset=UTF-8"); + String companyId = request.getHeader("visit-company-id"); + String deptId = request.getHeader("visit-dept-id"); + LoginUser loginUser = Optional.ofNullable(getLoginUser()).orElse(new LoginUser().setInfo(new HashMap<>())); + Set companyDeptSet = JSONUtil.parseArray(loginUser.getInfo().getOrDefault(LoginUser.INFO_KEY_COMPANY_DEPT_SET, "[]")).stream() + .map(obj -> JSONUtil.toBean((JSONObject) obj, CompanyDeptInfo.class)) + .collect(Collectors.toSet()); + + // 1. 有 companyId + if (companyId != null && !companyId.isBlank()) { + // 根据请求头中的公司 ID 过滤出当前用户的公司部门信息 + Set companyDeptSetByCompanyId = companyDeptSet.stream().filter(companyDeptInfo -> companyDeptInfo.getCompanyId().toString().equals(companyId)).collect(Collectors.toSet()); + if (companyDeptSetByCompanyId.isEmpty()) { + // 当前公司下没有部门 + CompanyDeptInfo data = new CompanyDeptInfo(); + data.setCompanyId(Long.valueOf(companyId)); + data.setDeptId(0L); + return new HashSet<>(singleton(data)); + } + // 如果有 deptId,校验其是否属于该 companyId + if (deptId != null) { + boolean valid = companyDeptSetByCompanyId.stream().anyMatch(info -> String.valueOf(info.getDeptId()).equals(deptId)); + if (!valid) { + return null; + }else{ + // 部门存在,放行 + return new HashSet<>(); + } + } + return companyDeptSetByCompanyId; + } + // 2. 没有公司信息,尝试唯一性自动推断 + // 如果当前用户下只有一个公司和部门的对于关系 + if (companyDeptSet.size() == 1) { + CompanyDeptInfo companyDeptInfo = companyDeptSet.iterator().next(); + return new HashSet<>(singleton(companyDeptInfo)); + } else { + return companyDeptSet; + } + } +} diff --git a/yudao-framework/yudao-spring-boot-starter-biz-business/src/main/java/cn/iocoder/yudao/framework/business/interceptor/BusinessHeaderInterceptor.java b/yudao-framework/yudao-spring-boot-starter-biz-business/src/main/java/cn/iocoder/yudao/framework/business/interceptor/BusinessHeaderInterceptor.java index e7f79394..6880bc12 100644 --- a/yudao-framework/yudao-spring-boot-starter-biz-business/src/main/java/cn/iocoder/yudao/framework/business/interceptor/BusinessHeaderInterceptor.java +++ b/yudao-framework/yudao-spring-boot-starter-biz-business/src/main/java/cn/iocoder/yudao/framework/business/interceptor/BusinessHeaderInterceptor.java @@ -1,11 +1,8 @@ package cn.iocoder.yudao.framework.business.interceptor; -import cn.hutool.json.JSONObject; -import cn.hutool.json.JSONUtil; import cn.iocoder.yudao.framework.common.pojo.CommonResult; import cn.iocoder.yudao.framework.common.pojo.CommonResultCodeEnum; import cn.iocoder.yudao.framework.common.pojo.CompanyDeptInfo; -import cn.iocoder.yudao.framework.security.core.LoginUser; import com.fasterxml.jackson.databind.ObjectMapper; import jakarta.servlet.http.HttpServletRequest; import jakarta.servlet.http.HttpServletResponse; @@ -15,14 +12,9 @@ import org.springframework.lang.NonNull; import org.springframework.web.method.HandlerMethod; import org.springframework.web.servlet.HandlerInterceptor; -import java.util.HashMap; -import java.util.List; -import java.util.Optional; import java.util.Set; -import java.util.stream.Collectors; -import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.singleton; -import static cn.iocoder.yudao.framework.security.core.util.SecurityFrameworkUtils.getLoginUser; +import static cn.iocoder.yudao.framework.business.core.util.BusinessDeptHandleUtil.getBelongCompanyAndDept; /** * @author chenbowen @@ -39,54 +31,18 @@ public class BusinessHeaderInterceptor implements HandlerInterceptor { if (!(bean instanceof BusinessControllerMarker)) { return true; } - - response.setContentType("application/json;charset=UTF-8"); - String companyId = request.getHeader("visit-company-id"); - String deptId = request.getHeader("visit-dept-id"); - LoginUser loginUser = Optional.ofNullable(getLoginUser()).orElse(new LoginUser().setInfo(new HashMap<>())); ObjectMapper objectMapper = new ObjectMapper(); - - Set companyDeptSet = JSONUtil.parseArray(loginUser.getInfo().getOrDefault(LoginUser.INFO_KEY_COMPANY_DEPT_SET, "[]")).stream() - .map(obj -> JSONUtil.toBean((JSONObject) obj, CompanyDeptInfo.class)) - .collect(Collectors.toSet()); - - // 1. 有 companyId - if (companyId != null && !companyId.isBlank()) { - Set companyDeptSetByCompanyId = companyDeptSet.stream().filter(companyDeptInfo -> companyDeptInfo.getCompanyId().toString().equals(companyId)).collect(Collectors.toSet()); - List filtered = companyDeptSetByCompanyId.stream() - .filter(info -> String.valueOf(info.getCompanyId()).equals(companyId)).toList(); - if (filtered.isEmpty()) { - // 当前公司下没有部门 - CompanyDeptInfo data = new CompanyDeptInfo(); - data.setCompanyId(Long.valueOf(companyId)); - data.setDeptId(0L); - return writeResponse(response, HttpStatus.OK.value(), CommonResult.customize(singleton(data), CommonResultCodeEnum.NEED_ADJUST), objectMapper); - } - // 如果有 deptId,校验其是否属于该 companyId - if (deptId != null) { - boolean valid = filtered.stream().anyMatch(info -> String.valueOf(info.getDeptId()).equals(deptId)); - if (!valid) { - return writeResponse(response, HttpStatus.BAD_REQUEST.value(), CommonResult.customize(null, CommonResultCodeEnum.ERROR.getCode(),"当前用户匹配部门不属于此公司"), objectMapper); - }else{ - // 部门存在,放行 - return true; - } - } - if (filtered.size() == 1) { - // 唯一部门 - return writeResponse(response, HttpStatus.OK.value(), CommonResult.customize(filtered,CommonResultCodeEnum.NEED_ADJUST), objectMapper); - } else { - // 多个部门 - return writeResponse(response, HttpStatus.OK.value(), CommonResult.customize(filtered,CommonResultCodeEnum.NEED_ADJUST), objectMapper); - } + Set companyDeptInfos = getBelongCompanyAndDept(request, response); + // 无法获取到有效的用户归属公司与部门信息,提示错误 + if (companyDeptInfos == null) { + return writeResponse(response, HttpStatus.BAD_REQUEST.value(), CommonResult.customize(null, CommonResultCodeEnum.ERROR.getCode(), "当前用户匹配部门不属于此公司"), objectMapper); } - // 2. 没有公司信息,尝试唯一性自动推断 - // 如果当前用户下只有一个公司和部门的对于关系 - if (companyDeptSet.size() == 1) { - CompanyDeptInfo companyDeptInfo = companyDeptSet.iterator().next(); - return writeResponse(response, HttpStatus.OK.value(), CommonResult.customize(singleton(companyDeptInfo),CommonResultCodeEnum.NEED_ADJUST), objectMapper); - } else { - return writeResponse(response, HttpStatus.OK.value(), CommonResult.customize(companyDeptSet,CommonResultCodeEnum.NEED_ADJUST), objectMapper); + // 获取到了有效的一组或多组公司与部门信息,需要返回前端进行补充请求头后的二次请求 + if (!companyDeptInfos.isEmpty()) { + return writeResponse(response, HttpStatus.OK.value(), CommonResult.customize(companyDeptInfos, CommonResultCodeEnum.NEED_ADJUST), objectMapper); + } + else{ + return true; } } diff --git a/yudao-module-infra/yudao-module-infra-api/src/main/java/cn/iocoder/yudao/module/infra/enums/ErrorCodeConstants.java b/yudao-module-infra/yudao-module-infra-api/src/main/java/cn/iocoder/yudao/module/infra/enums/ErrorCodeConstants.java index 2233f353..b30f5e86 100644 --- a/yudao-module-infra/yudao-module-infra-api/src/main/java/cn/iocoder/yudao/module/infra/enums/ErrorCodeConstants.java +++ b/yudao-module-infra/yudao-module-infra-api/src/main/java/cn/iocoder/yudao/module/infra/enums/ErrorCodeConstants.java @@ -33,6 +33,16 @@ public interface ErrorCodeConstants { ErrorCode FILE_PATH_EXISTS = new ErrorCode(1_001_003_000, "文件路径已存在"); ErrorCode FILE_NOT_EXISTS = new ErrorCode(1_001_003_001, "文件不存在"); ErrorCode FILE_IS_EMPTY = new ErrorCode(1_001_003_002, "文件为空"); + // 文件验证码生成失败 + ErrorCode FILE_CAPTCHA_GENERATE_FAIL = new ErrorCode(1_001_003_003, "生成验证码失败,请稍后再试!"); + // 文件验证码有误 + ErrorCode FILE_CAPTCHA_ERROR = new ErrorCode(1_001_003_004, "验证码错误"); + // 文件解密失败 + ErrorCode FILE_DECRYPT_FAIL = new ErrorCode(1_001_003_005, "文件解密失败,请检查文件是否有效"); + // 同一附件60秒内只能获取一次验证码 + ErrorCode FILE_CAPTCHA_EXISTS = new ErrorCode(1_001_003_006, "同一附件60秒内只能获取一次验证码"); + // 同一用户最多同时只能有10个有效验证码 + ErrorCode FILE_CAPTCHA_MAX = new ErrorCode(1_001_003_007, "同一用户最多只能有10个有效验证码"); // ========== 代码生成器 1-001-004-000 ========== ErrorCode CODEGEN_TABLE_EXISTS = new ErrorCode(1_001_004_002, "表定义已经存在"); diff --git a/yudao-module-infra/yudao-module-infra-server/src/main/java/cn/iocoder/yudao/module/infra/api/file/FileApiImpl.java b/yudao-module-infra/yudao-module-infra-server/src/main/java/cn/iocoder/yudao/module/infra/api/file/FileApiImpl.java index 7f299c3e..2afbb7d1 100644 --- a/yudao-module-infra/yudao-module-infra-server/src/main/java/cn/iocoder/yudao/module/infra/api/file/FileApiImpl.java +++ b/yudao-module-infra/yudao-module-infra-server/src/main/java/cn/iocoder/yudao/module/infra/api/file/FileApiImpl.java @@ -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 createFile(FileCreateReqDTO createReqDTO) { return success(fileService.createFile(createReqDTO.getContent(), createReqDTO.getName(), - createReqDTO.getDirectory(), createReqDTO.getType())); + createReqDTO.getDirectory(), createReqDTO.getType(), false)); } } diff --git a/yudao-module-infra/yudao-module-infra-server/src/main/java/cn/iocoder/yudao/module/infra/controller/admin/file/FileController.java b/yudao-module-infra/yudao-module-infra-server/src/main/java/cn/iocoder/yudao/module/infra/controller/admin/file/FileController.java index b7177fbb..002417f4 100644 --- a/yudao-module-infra/yudao-module-infra-server/src/main/java/cn/iocoder/yudao/module/infra/controller/admin/file/FileController.java +++ b/yudao-module-infra/yudao-module-infra-server/src/main/java/cn/iocoder/yudao/module/infra/controller/admin/file/FileController.java @@ -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 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 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 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 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()); + } + } } diff --git a/yudao-module-infra/yudao-module-infra-server/src/main/java/cn/iocoder/yudao/module/infra/controller/admin/file/vo/file/FileUploadReqVO.java b/yudao-module-infra/yudao-module-infra-server/src/main/java/cn/iocoder/yudao/module/infra/controller/admin/file/vo/file/FileUploadReqVO.java index 183462a8..f7a766aa 100644 --- a/yudao-module-infra/yudao-module-infra-server/src/main/java/cn/iocoder/yudao/module/infra/controller/admin/file/vo/file/FileUploadReqVO.java +++ b/yudao-module-infra/yudao-module-infra-server/src/main/java/cn/iocoder/yudao/module/infra/controller/admin/file/vo/file/FileUploadReqVO.java @@ -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; + } diff --git a/yudao-module-infra/yudao-module-infra-server/src/main/java/cn/iocoder/yudao/module/infra/controller/admin/file/vo/file/FileVerificationCodeCheckReqVO.java b/yudao-module-infra/yudao-module-infra-server/src/main/java/cn/iocoder/yudao/module/infra/controller/admin/file/vo/file/FileVerificationCodeCheckReqVO.java new file mode 100644 index 00000000..cac4d0e8 --- /dev/null +++ b/yudao-module-infra/yudao-module-infra-server/src/main/java/cn/iocoder/yudao/module/infra/controller/admin/file/vo/file/FileVerificationCodeCheckReqVO.java @@ -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; +} diff --git a/yudao-module-infra/yudao-module-infra-server/src/main/java/cn/iocoder/yudao/module/infra/controller/admin/file/vo/file/FileVerificationCodeReqVO.java b/yudao-module-infra/yudao-module-infra-server/src/main/java/cn/iocoder/yudao/module/infra/controller/admin/file/vo/file/FileVerificationCodeReqVO.java new file mode 100644 index 00000000..f6bc5fb2 --- /dev/null +++ b/yudao-module-infra/yudao-module-infra-server/src/main/java/cn/iocoder/yudao/module/infra/controller/admin/file/vo/file/FileVerificationCodeReqVO.java @@ -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; + } +} diff --git a/yudao-module-infra/yudao-module-infra-server/src/main/java/cn/iocoder/yudao/module/infra/controller/app/file/AppFileController.java b/yudao-module-infra/yudao-module-infra-server/src/main/java/cn/iocoder/yudao/module/infra/controller/app/file/AppFileController.java index 7f85e996..40d65718 100644 --- a/yudao-module-infra/yudao-module-infra-server/src/main/java/cn/iocoder/yudao/module/infra/controller/app/file/AppFileController.java +++ b/yudao-module-infra/yudao-module-infra-server/src/main/java/cn/iocoder/yudao/module/infra/controller/app/file/AppFileController.java @@ -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") diff --git a/yudao-module-infra/yudao-module-infra-server/src/main/java/cn/iocoder/yudao/module/infra/dal/dataobject/file/FileDO.java b/yudao-module-infra/yudao-module-infra-server/src/main/java/cn/iocoder/yudao/module/infra/dal/dataobject/file/FileDO.java index b0cc337a..f65e9b1f 100644 --- a/yudao-module-infra/yudao-module-infra-server/src/main/java/cn/iocoder/yudao/module/infra/dal/dataobject/file/FileDO.java +++ b/yudao-module-infra/yudao-module-infra-server/src/main/java/cn/iocoder/yudao/module/infra/dal/dataobject/file/FileDO.java @@ -62,4 +62,9 @@ public class FileDO extends BaseDO { */ private String hash; + /** + * AES 加密时的随机 IV(Base64 编码) + */ + private String aesIv; + } diff --git a/yudao-module-infra/yudao-module-infra-server/src/main/java/cn/iocoder/yudao/module/infra/dal/redis/RedisKeyConstants.java b/yudao-module-infra/yudao-module-infra-server/src/main/java/cn/iocoder/yudao/module/infra/dal/redis/RedisKeyConstants.java new file mode 100644 index 00000000..7ce0b862 --- /dev/null +++ b/yudao-module-infra/yudao-module-infra-server/src/main/java/cn/iocoder/yudao/module/infra/dal/redis/RedisKeyConstants.java @@ -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"; +} diff --git a/yudao-module-infra/yudao-module-infra-server/src/main/java/cn/iocoder/yudao/module/infra/service/file/FileService.java b/yudao-module-infra/yudao-module-infra-server/src/main/java/cn/iocoder/yudao/module/infra/service/file/FileService.java index 40b1d5bc..d84fee6d 100644 --- a/yudao-module-infra/yudao-module-infra-server/src/main/java/cn/iocoder/yudao/module/infra/service/file/FileService.java +++ b/yudao-module-infra/yudao-module-infra-server/src/main/java/cn/iocoder/yudao/module/infra/service/file/FileService.java @@ -28,17 +28,18 @@ public interface FileService { * 保存文件,并返回文件的访问路径 * *

会根据文件内容计算哈希值,若已存在相同哈希的文件,则直接复用,不重复上传。

+ * * @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); + } diff --git a/yudao-module-infra/yudao-module-infra-server/src/main/java/cn/iocoder/yudao/module/infra/service/file/FileServiceImpl.java b/yudao-module-infra/yudao-module-infra-server/src/main/java/cn/iocoder/yudao/module/infra/service/file/FileServiceImpl.java index 2fbdd150..30c40410 100644 --- a/yudao-module-infra/yudao-module-infra-server/src/main/java/cn/iocoder/yudao/module/infra/service/file/FileServiceImpl.java +++ b/yudao-module-infra/yudao-module-infra-server/src/main/java/cn/iocoder/yudao/module/infra/service/file/FileServiceImpl.java @@ -5,6 +5,7 @@ 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; @@ -13,17 +14,28 @@ import cn.iocoder.yudao.module.infra.controller.admin.file.vo.file.FilePresigned import cn.iocoder.yudao.module.infra.controller.admin.file.vo.file.FileRespVO; import cn.iocoder.yudao.module.infra.dal.dataobject.file.FileDO; import cn.iocoder.yudao.module.infra.dal.mysql.file.FileMapper; +import cn.iocoder.yudao.module.infra.dal.redis.RedisKeyConstants; import cn.iocoder.yudao.module.infra.framework.file.core.client.FileClient; import cn.iocoder.yudao.module.infra.framework.file.core.client.s3.FilePresignedUrlRespDTO; import cn.iocoder.yudao.module.infra.framework.file.core.utils.FileTypeUtils; +import cn.iocoder.yudao.module.infra.util.VerificationCodeUtil; import com.google.common.annotations.VisibleForTesting; import jakarta.annotation.Resource; import lombok.SneakyThrows; +import org.apache.commons.lang3.StringUtils; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.data.redis.core.StringRedisTemplate; import org.springframework.stereotype.Service; +import javax.crypto.Cipher; +import javax.crypto.spec.IvParameterSpec; +import javax.crypto.spec.SecretKeySpec; +import java.security.SecureRandom; +import java.util.Base64; + import static cn.hutool.core.date.DatePattern.PURE_DATE_PATTERN; import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception; -import static cn.iocoder.yudao.module.infra.enums.ErrorCodeConstants.FILE_NOT_EXISTS; +import static cn.iocoder.yudao.module.infra.enums.ErrorCodeConstants.*; /** * 文件 Service 实现类 @@ -33,6 +45,56 @@ import static cn.iocoder.yudao.module.infra.enums.ErrorCodeConstants.FILE_NOT_EX @Service public class FileServiceImpl implements FileService { + + @Value("${yudao.AES.key}") + private String aesKey; + @Value("${yudao.verify-code:''}") + private String fixedVerifyCode; + + @Resource + private StringRedisTemplate stringRedisTemplate; + @Override + public String generateFileVerificationCode(Long fileId, Long userId) { + // 校验文件是否存在 + 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()); + } + } + + @Override + public byte[] verifyCodeAndGetFile(Long fileId, Long userId, String code) throws Exception { + // 开发模式下,验证码直接获取配置进行比对 + if (StringUtils.isNotEmpty(fixedVerifyCode)) { + if (!fixedVerifyCode.equals(code)) { + throw exception(FILE_CAPTCHA_ERROR, "验证码错误"); + } + } else { + String codeKey = String.format(RedisKeyConstants.FILE_VERIFICATION_CODE, userId, fileId); + boolean valid = VerificationCodeUtil.verifyCode(codeKey, code, stringRedisTemplate); + if (!valid) { + throw exception(FILE_CAPTCHA_ERROR); + } + } + FileDO fileDO = validateFileExists(fileId); + // 获取加密内容 + FileClient client = fileConfigService.getFileClient(fileDO.getConfigId()); + byte[] encrypted = client.getContent(fileDO.getPath()); + // 解密 + try { + SecretKeySpec keySpec = new SecretKeySpec(aesKey.getBytes(), "AES"); + IvParameterSpec ivSpec = new IvParameterSpec(Base64.getDecoder().decode(fileDO.getAesIv())); + Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5Padding"); + cipher.init(Cipher.DECRYPT_MODE, keySpec, ivSpec); + return cipher.doFinal(encrypted); + } catch (Exception e) { + throw exception(FILE_DECRYPT_FAIL); + } + } /** * 上传文件的前缀,是否包含日期(yyyyMMdd) * 目的:按照日期,进行分目录 @@ -58,15 +120,15 @@ public class FileServiceImpl implements FileService { @Override @SneakyThrows - public String createFile(byte[] content, String name, String directory, String type) { - FileDO entity = uploadFile(content, name, directory, type); + public String createFile(byte[] content, String name, String directory, String type, Boolean encrypt) { + FileDO entity = uploadFile(content, name, directory, type, encrypt); return entity.getUrl(); } @SneakyThrows @Override - public FileRespVO createFileWhitReturn(byte[] content, String name, String directory, String type) { - FileDO entity = uploadFile(content, name, directory, type); + public FileRespVO createFileWhitReturn(byte[] content, String name, String directory, String type, Boolean encrypt) { + FileDO entity = uploadFile(content, name, directory, type, encrypt); return new FileRespVO() .setId(entity.getId()) .setName(entity.getName()) @@ -77,7 +139,22 @@ public class FileServiceImpl implements FileService { .setConfigId(entity.getConfigId()); } - private FileDO uploadFile(byte[] content, String name, String directory, String type) throws Exception { + private FileDO uploadFile(byte[] content, String name, String directory, String type, Boolean encrypt) throws Exception { + + String aesIvBase64 = null; + if (Boolean.TRUE.equals(encrypt)) { + // 生成随机 IV + byte[] iv = new byte[16]; + new SecureRandom().nextBytes(iv); + aesIvBase64 = Base64.getEncoder().encodeToString(iv); + // AES 加密 + SecretKeySpec keySpec = new SecretKeySpec(aesKey.getBytes(), "AES"); + IvParameterSpec ivSpec = new IvParameterSpec(iv); + Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5Padding"); + cipher.init(Cipher.ENCRYPT_MODE, keySpec, ivSpec); + content = cipher.doFinal(content); + } + // 1.1 计算文件哈希 String hash = DigestUtil.sha256Hex(content); @@ -108,14 +185,21 @@ public class FileServiceImpl implements FileService { // 2.2 上传到文件存储器 FileClient client = fileConfigService.getMasterFileClient(); Assert.notNull(client, "客户端(master) 不能为空"); - String url = client.upload(content, path, type); FileDO entity = new FileDO().setConfigId(client.getId()) - .setName(name).setPath(path).setUrl(url) + .setName(name).setPath(path).setUrl("") .setType(type).setSize(content.length) - .setHash(hash); - - // 3. 保存到数据库 - fileMapper.insert(entity); + .setHash(hash) + .setAesIv(aesIvBase64); + String url = client.upload(content, path, type); + if (Boolean.TRUE.equals(encrypt)) { + // 先插入获取 fileId + fileMapper.insert(entity); + entity.setUrl(entity.getId() + StrUtil.SLASH + entity.getName()); + fileMapper.updateById(entity); + } else { + entity.setUrl(url); + fileMapper.insert(entity); + } return entity; } @@ -199,5 +283,10 @@ public class FileServiceImpl implements FileService { Assert.notNull(client, "客户端({}) 不能为空", configId); return client.getContent(path); } + @Override + public FileDO getActiveFileById(Long fileId) { + // 由于 FileDO 没有状态字段,直接查主键即为生效中的文件 + return fileMapper.selectById(fileId); + } } diff --git a/yudao-module-infra/yudao-module-infra-server/src/main/java/cn/iocoder/yudao/module/infra/util/VerificationCodeUtil.java b/yudao-module-infra/yudao-module-infra-server/src/main/java/cn/iocoder/yudao/module/infra/util/VerificationCodeUtil.java new file mode 100644 index 00000000..c848270a --- /dev/null +++ b/yudao-module-infra/yudao-module-infra-server/src/main/java/cn/iocoder/yudao/module/infra/util/VerificationCodeUtil.java @@ -0,0 +1,57 @@ +package cn.iocoder.yudao.module.infra.util; + +import org.springframework.data.redis.core.StringRedisTemplate; + +import java.util.Random; +import java.util.concurrent.TimeUnit; + +import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception; +import static cn.iocoder.yudao.module.infra.enums.ErrorCodeConstants.FILE_CAPTCHA_EXISTS; +import static cn.iocoder.yudao.module.infra.enums.ErrorCodeConstants.FILE_CAPTCHA_MAX; + +/** + * @author chenbowen + */ +public class VerificationCodeUtil { + private static final int CODE_EXPIRE_SECONDS = 60; + private static final int MAX_CODE_PER_USER = 10; + + /** + * 生成验证码并存储到 Redis,限制同一 key 在有效期内只能生成一次,且同一用户最多有 MAX_CODE_PER_USER 个有效验证码 + * @param codeKey 验证码 Redis key + * @param userSetKey 用户下验证码集合 Redis key + * @param redisTemplate Redis 操作对象 + * @return 6位数字验证码 + */ + public static String generateCode(String codeKey, String userSetKey, StringRedisTemplate redisTemplate) { + String existCode = redisTemplate.opsForValue().get(codeKey); + if (existCode != null) { + Long ttl = redisTemplate.getExpire(codeKey, TimeUnit.SECONDS); + if (ttl > 10) { + throw exception(FILE_CAPTCHA_EXISTS, "同一附件60秒内只能获取一次验证码"); + } + // ttl <= 10 秒,允许重新获取 + } + Long size = redisTemplate.opsForSet().size(userSetKey); + if (size != null && size >= MAX_CODE_PER_USER) { + 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); + redisTemplate.opsForSet().add(userSetKey, codeKey); + redisTemplate.expire(userSetKey, CODE_EXPIRE_SECONDS, TimeUnit.SECONDS); + return code; + } + + /** + * 校验验证码 + * @param codeKey 验证码 Redis key + * @param code 用户输入的验证码 + * @param redisTemplate Redis 操作对象 + * @return 是否校验通过 + */ + public static boolean verifyCode(String codeKey, String code, StringRedisTemplate redisTemplate) { + String redisCode = redisTemplate.opsForValue().get(codeKey); + return redisCode != null && redisCode.equals(code); + } +} diff --git a/yudao-module-infra/yudao-module-infra-server/src/main/resources/application-local.yaml b/yudao-module-infra/yudao-module-infra-server/src/main/resources/application-local.yaml index aec227d7..eb1abc80 100644 --- a/yudao-module-infra/yudao-module-infra-server/src/main/resources/application-local.yaml +++ b/yudao-module-infra/yudao-module-infra-server/src/main/resources/application-local.yaml @@ -137,4 +137,6 @@ yudao: security: mock-enable: true access-log: # 访问日志的配置项 - enable: true \ No newline at end of file + enable: true + # 固定验证码 + verify-code: 666666 \ No newline at end of file diff --git a/yudao-module-infra/yudao-module-infra-server/src/main/resources/application.yaml b/yudao-module-infra/yudao-module-infra-server/src/main/resources/application.yaml index ef5ee1d2..fce2bfd4 100644 --- a/yudao-module-infra/yudao-module-infra-server/src/main/resources/application.yaml +++ b/yudao-module-infra/yudao-module-infra-server/src/main/resources/application.yaml @@ -129,6 +129,9 @@ xxl: --- #################### 芋道相关配置 #################### yudao: + # 附件加密相关配置 + AES: + key: "0123456789abcdef0123456789abcdef" info: version: 1.0.0 base-package: cn.iocoder.yudao.module.infra diff --git a/yudao-module-infra/yudao-module-infra-server/src/test/java/cn/iocoder/yudao/module/infra/service/file/FileServiceImplTest.java b/yudao-module-infra/yudao-module-infra-server/src/test/java/cn/iocoder/yudao/module/infra/service/file/FileServiceImplTest.java index 514e2798..8476eb38 100644 --- a/yudao-module-infra/yudao-module-infra-server/src/test/java/cn/iocoder/yudao/module/infra/service/file/FileServiceImplTest.java +++ b/yudao-module-infra/yudao-module-infra-server/src/test/java/cn/iocoder/yudao/module/infra/service/file/FileServiceImplTest.java @@ -14,6 +14,7 @@ import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; import org.springframework.boot.test.mock.mockito.MockBean; import org.springframework.context.annotation.Import; +import org.springframework.data.redis.core.StringRedisTemplate; import java.time.LocalDateTime; import java.util.concurrent.atomic.AtomicReference; @@ -32,6 +33,9 @@ public class FileServiceImplTest extends BaseDbUnitTest { @Resource private FileServiceImpl fileService; + @MockBean + private StringRedisTemplate stringRedisTemplate; + @Resource private FileMapper fileMapper; @@ -99,7 +103,7 @@ public class FileServiceImplTest extends BaseDbUnitTest { }), eq(type))).thenReturn(url); when(client.getId()).thenReturn(10L); // 调用 - String result = fileService.createFile(content, name, directory, type); + String result = fileService.createFile(content, name, directory, type, false); // 断言 assertEquals(result, url); // 校验数据 @@ -131,7 +135,7 @@ public class FileServiceImplTest extends BaseDbUnitTest { }), eq(type))).thenReturn(url); when(client.getId()).thenReturn(10L); // 调用 - String result = fileService.createFile(content, null, null, null); + String result = fileService.createFile(content, null, null, null, false); // 断言 assertEquals(result, url); // 校验数据 @@ -330,11 +334,11 @@ public class FileServiceImplTest extends BaseDbUnitTest { when(client.getId()).thenReturn(10L); // 首次上传 - String result1 = fileService.createFile(content, name, directory, type); + String result1 = fileService.createFile(content, name, directory, type, false); assertEquals(result1, url); // 再次上传同样的内容,应该复用已存在的文件 - String result2 = fileService.createFile(content, name, directory, type); + String result2 = fileService.createFile(content, name, directory, type, false); assertEquals(result2, url); // 校验数据 diff --git a/yudao-module-infra/yudao-module-infra-server/src/test/resources/application-unit-test.yaml b/yudao-module-infra/yudao-module-infra-server/src/test/resources/application-unit-test.yaml index f9b5f329..2d836593 100644 --- a/yudao-module-infra/yudao-module-infra-server/src/test/resources/application-unit-test.yaml +++ b/yudao-module-infra/yudao-module-infra-server/src/test/resources/application-unit-test.yaml @@ -50,3 +50,5 @@ mybatis-plus: yudao: info: base-package: cn.iocoder.yudao.module.infra + AES: + key: XDV71a+xqStEA3WH diff --git a/yudao-module-infra/yudao-module-infra-server/src/test/resources/sql/create_tables.sql b/yudao-module-infra/yudao-module-infra-server/src/test/resources/sql/create_tables.sql index 0bca9a28..e81138b6 100644 --- a/yudao-module-infra/yudao-module-infra-server/src/test/resources/sql/create_tables.sql +++ b/yudao-module-infra/yudao-module-infra-server/src/test/resources/sql/create_tables.sql @@ -46,6 +46,7 @@ CREATE TABLE IF NOT EXISTS "infra_file" ( "deleted" bit NOT NULL DEFAULT FALSE, "tenant_id" bigint not null default '0', "hash" varchar(64) DEFAULT NULL COMMENT '文件哈希值(SHA-256)', + "aes_iv" varchar(64) DEFAULT NULL COMMENT '文件哈希值(SHA-256)', PRIMARY KEY ("id") ) COMMENT '文件表'; diff --git a/yudao-module-template/yudao-module-template-server/src/main/resources/application-local.yaml b/yudao-module-template/yudao-module-template-server/src/main/resources/application-local.yaml index f3e02546..cf6e7451 100644 --- a/yudao-module-template/yudao-module-template-server/src/main/resources/application-local.yaml +++ b/yudao-module-template/yudao-module-template-server/src/main/resources/application-local.yaml @@ -130,4 +130,4 @@ yudao: security: mock-enable: true access-log: # 访问日志的配置项 - enable: false + enable: true diff --git a/yudao-server/pom.xml b/yudao-server/pom.xml index 56ca68bf..007cd398 100644 --- a/yudao-server/pom.xml +++ b/yudao-server/pom.xml @@ -31,6 +31,11 @@ yudao-module-infra-server ${revision} + + cn.iocoder.cloud + yudao-module-template-server + ${revision} + @@ -115,11 +120,11 @@ - - cn.iocoder.cloud - yudao-module-template-server - ${revision} - + + + + + diff --git a/yudao-server/src/main/resources/application-dev.yaml b/yudao-server/src/main/resources/application-dev.yaml index 01040681..d70fa758 100644 --- a/yudao-server/src/main/resources/application-dev.yaml +++ b/yudao-server/src/main/resources/application-dev.yaml @@ -149,6 +149,8 @@ yudao: transfer-notify-url: https://yunai.natapp1.cc/admin-api/pay/notify/transfer # 支付渠道的【转账】回调地址 demo: false # 开启演示模式 tencent-lbs-key: TVDBZ-TDILD-4ON4B-PFDZA-RNLKH-VVF6E # QQ 地图的密钥 https://lbs.qq.com/service/staticV2/staticGuide/staticDoc + AES: + key: "0123456789abcdef0123456789abcdef" justauth: enabled: true diff --git a/yudao-server/src/main/resources/application-local.yaml b/yudao-server/src/main/resources/application-local.yaml index 970d03dd..24977433 100644 --- a/yudao-server/src/main/resources/application-local.yaml +++ b/yudao-server/src/main/resources/application-local.yaml @@ -216,6 +216,8 @@ yudao: wxa-subscribe-message: miniprogram-state: developer # 跳转小程序类型:开发版为 “developer”;体验版为 “trial”为;正式版为 “formal” tencent-lbs-key: TVDBZ-TDILD-4ON4B-PFDZA-RNLKH-VVF6E # QQ 地图的密钥 https://lbs.qq.com/service/staticV2/staticGuide/staticDoc + AES: + key: "0123456789abcdef0123456789abcdef" justauth: enabled: true