From d17fa356ba68ed6c350b6b9e244f980b8fdbc62b Mon Sep 17 00:00:00 2001 From: chenbowen Date: Thu, 17 Jul 2025 15:04:30 +0800 Subject: [PATCH 1/5] =?UTF-8?q?1.=20=E4=BF=AE=E5=A4=8D=E7=94=A8=E6=88=B7?= =?UTF-8?q?=E5=8F=AA=E5=85=B3=E8=81=94=E4=B8=80=E4=B8=AA=E9=83=A8=E9=97=A8?= =?UTF-8?q?=E6=97=B6=EF=BC=8C=E6=97=A0=E6=B3=95=E6=AD=A3=E5=B8=B8=E5=A2=9E?= =?UTF-8?q?=E5=88=A0=E6=94=B9=E4=B8=9A=E5=8A=A1=E6=95=B0=E6=8D=AE=E7=9A=84?= =?UTF-8?q?bug?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- sql/dm/新增用户与组织关系表并迁移.sql | 2 +- .../config/YudaoBusinessAutoConfiguration.java | 2 +- .../interceptor/BusinessHeaderInterceptor.java | 12 +++++------- 3 files changed, 7 insertions(+), 9 deletions(-) diff --git a/sql/dm/新增用户与组织关系表并迁移.sql b/sql/dm/新增用户与组织关系表并迁移.sql index 62efc891..e4747ce0 100644 --- a/sql/dm/新增用户与组织关系表并迁移.sql +++ b/sql/dm/新增用户与组织关系表并迁移.sql @@ -1,5 +1,5 @@ create table "RUOYI-VUE-PRO".SYSTEM_USER_DEPT ( - ID BIGINT primary key, -- 自增主键 + ID BIGINT primary key, USER_ID BIGINT not null, DEPT_ID BIGINT not null, TENANT_ID BIGINT default 0 not null, diff --git a/yudao-framework/yudao-spring-boot-starter-biz-business/src/main/java/cn/iocoder/yudao/framework/business/config/YudaoBusinessAutoConfiguration.java b/yudao-framework/yudao-spring-boot-starter-biz-business/src/main/java/cn/iocoder/yudao/framework/business/config/YudaoBusinessAutoConfiguration.java index 714ca87b..05996ef1 100644 --- a/yudao-framework/yudao-spring-boot-starter-biz-business/src/main/java/cn/iocoder/yudao/framework/business/config/YudaoBusinessAutoConfiguration.java +++ b/yudao-framework/yudao-spring-boot-starter-biz-business/src/main/java/cn/iocoder/yudao/framework/business/config/YudaoBusinessAutoConfiguration.java @@ -15,6 +15,6 @@ public class YudaoBusinessAutoConfiguration implements WebMvcConfigurer { public void addInterceptors(InterceptorRegistry registry) { // 只拦截增删改和 set 相关的 url registry.addInterceptor(new BusinessHeaderInterceptor()) - .addPathPatterns("/**/add**", "/**/create**", "/**/update**", "/**/edit**", "/**/delete**", "/**/remove**", "/**/set**"); + .addPathPatterns("/**/add**", "/**/create**", "/**/update**", "/**/edit**", "/**/set**"); } } 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 33714ada..e7f79394 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,6 +1,5 @@ package cn.iocoder.yudao.framework.business.interceptor; -import cn.hutool.json.JSONArray; import cn.hutool.json.JSONObject; import cn.hutool.json.JSONUtil; import cn.iocoder.yudao.framework.common.pojo.CommonResult; @@ -8,7 +7,6 @@ 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 com.fasterxml.jackson.core.type.TypeReference; import jakarta.servlet.http.HttpServletRequest; import jakarta.servlet.http.HttpServletResponse; import lombok.RequiredArgsConstructor; @@ -17,7 +15,10 @@ import org.springframework.lang.NonNull; import org.springframework.web.method.HandlerMethod; import org.springframework.web.servlet.HandlerInterceptor; -import java.util.*; +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; @@ -82,11 +83,8 @@ public class BusinessHeaderInterceptor implements HandlerInterceptor { // 2. 没有公司信息,尝试唯一性自动推断 // 如果当前用户下只有一个公司和部门的对于关系 if (companyDeptSet.size() == 1) { - CompanyDeptInfo info = new CompanyDeptInfo(); CompanyDeptInfo companyDeptInfo = companyDeptSet.iterator().next(); - info.setCompanyId(companyDeptInfo.getCompanyId()); - info.setDeptId(companyDeptInfo.getDeptId()); - return writeResponse(response, HttpStatus.OK.value(), CommonResult.success(singleton(info)), objectMapper); + 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); } From 8ab6cb4baef869565060aa21791e84594f53733b Mon Sep 17 00:00:00 2001 From: chenbowen Date: Tue, 22 Jul 2025 19:36:46 +0800 Subject: [PATCH 2/5] =?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 From af8f990cc8a407c0a8eac4ed0ba6af9183ef4bef Mon Sep 17 00:00:00 2001 From: chenbowen Date: Tue, 22 Jul 2025 19:36:46 +0800 Subject: [PATCH 3/5] =?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=202.=20=E6=96=B0=E5=A2=9E?= =?UTF-8?q?=E6=95=B0=E6=8D=AE=E5=91=BD=E5=90=8D=E4=B8=8E=E7=AE=80=E5=86=99?= =?UTF-8?q?=E6=A0=87=E5=87=86=E7=AE=A1=E7=90=86=E8=8F=9C=E5=8D=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- sql/dm/ruoyi-vue-pro-dm8.sql | 32 +++++ sql/mysql/ruoyi-vue-pro.sql | 19 +++ .../system/enums/ErrorCodeConstants.java | 2 + .../admin/stdnms/StdNmsController.java | 120 ++++++++++++++++++ .../admin/stdnms/vo/StdNmsPageReqVO.java | 29 +++++ .../admin/stdnms/vo/StdNmsRespVO.java | 35 +++++ .../admin/stdnms/vo/StdNmsSaveReqVO.java | 27 ++++ .../dal/dataobject/stdnms/StdNmsDO.java | 47 +++++++ .../system/dal/mysql/stdnms/StdNmsMapper.java | 29 +++++ .../system/service/stdnms/StdNmsService.java | 62 +++++++++ .../service/stdnms/StdNmsServiceImpl.java | 92 ++++++++++++++ 11 files changed, 494 insertions(+) create mode 100644 yudao-module-system/yudao-module-system-server/src/main/java/cn/iocoder/yudao/module/system/controller/admin/stdnms/StdNmsController.java create mode 100644 yudao-module-system/yudao-module-system-server/src/main/java/cn/iocoder/yudao/module/system/controller/admin/stdnms/vo/StdNmsPageReqVO.java create mode 100644 yudao-module-system/yudao-module-system-server/src/main/java/cn/iocoder/yudao/module/system/controller/admin/stdnms/vo/StdNmsRespVO.java create mode 100644 yudao-module-system/yudao-module-system-server/src/main/java/cn/iocoder/yudao/module/system/controller/admin/stdnms/vo/StdNmsSaveReqVO.java create mode 100644 yudao-module-system/yudao-module-system-server/src/main/java/cn/iocoder/yudao/module/system/dal/dataobject/stdnms/StdNmsDO.java create mode 100644 yudao-module-system/yudao-module-system-server/src/main/java/cn/iocoder/yudao/module/system/dal/mysql/stdnms/StdNmsMapper.java create mode 100644 yudao-module-system/yudao-module-system-server/src/main/java/cn/iocoder/yudao/module/system/service/stdnms/StdNmsService.java create mode 100644 yudao-module-system/yudao-module-system-server/src/main/java/cn/iocoder/yudao/module/system/service/stdnms/StdNmsServiceImpl.java diff --git a/sql/dm/ruoyi-vue-pro-dm8.sql b/sql/dm/ruoyi-vue-pro-dm8.sql index fbb14761..b6f41a91 100644 --- a/sql/dm/ruoyi-vue-pro-dm8.sql +++ b/sql/dm/ruoyi-vue-pro-dm8.sql @@ -4789,3 +4789,35 @@ COMMENT ON COLUMN system_user_dept.create_time IS '创建时间'; COMMENT ON COLUMN system_user_dept.updater IS '更新者'; COMMENT ON COLUMN system_user_dept.deleted IS '是否删除'; COMMENT ON COLUMN system_user_dept.update_time IS '更新时间'; + +-- ---------------------------- +-- Table structure for system_std_nms +-- ---------------------------- +DROP TABLE IF EXISTS system_std_nms; +CREATE TABLE system_std_nms +( + id BIGINT NOT NULL PRIMARY KEY, + word VARCHAR(128) NOT NULL UNIQUE, + abbr VARCHAR(32) NOT NULL UNIQUE, + info VARCHAR(128) NOT NULL UNIQUE, + creator VARCHAR(64) DEFAULT '', + create_time datetime DEFAULT CURRENT_TIMESTAMP NOT NULL, + updater VARCHAR(64) DEFAULT '', + update_time datetime DEFAULT CURRENT_TIMESTAMP NOT NULL, + deleted bit DEFAULT '0' NOT NULL +); + +COMMENT ON TABLE system_std_nms IS '数据命名与简写标准'; +COMMENT ON COLUMN system_std_nms.id IS '主键ID'; +COMMENT ON COLUMN system_std_nms.word IS '英文'; +COMMENT ON COLUMN system_std_nms.abbr IS '简写'; +COMMENT ON COLUMN system_std_nms.info IS '中文意思'; +COMMENT ON COLUMN system_std_nms.creator IS '创建者'; +COMMENT ON COLUMN system_std_nms.create_time IS '创建时间'; +COMMENT ON COLUMN system_std_nms.updater IS '更新者'; +COMMENT ON COLUMN system_std_nms.update_time IS '最后更新时间'; +COMMENT ON COLUMN system_std_nms.deleted IS '是否删除'; + +-- ---------------------------- +-- Records of system_std_nms +-- diff --git a/sql/mysql/ruoyi-vue-pro.sql b/sql/mysql/ruoyi-vue-pro.sql index 1301c1d1..d1a0cce1 100644 --- a/sql/mysql/ruoyi-vue-pro.sql +++ b/sql/mysql/ruoyi-vue-pro.sql @@ -4114,5 +4114,24 @@ CREATE TABLE `system_user_dept` -- ---------------------------- -- Records of system_user_dept -- ---------------------------- +-- ---------------------------- +-- Table structure for system_std_nms +-- ---------------------------- +DROP TABLE IF EXISTS `system_std_nms`; +CREATE TABLE `system_std_nms` +( + `id` BIGINT NOT NULL AUTO_INCREMENT PRIMARY KEY COMMENT '主键ID', + `word` VARCHAR(128) NOT NULL UNIQUE COMMENT '英文', + `abbr` VARCHAR(32) NOT NULL UNIQUE COMMENT '简写', + `info` VARCHAR(128) NOT NULL UNIQUE COMMENT '中文意思', + `creator` VARCHAR(64) NOT NULL DEFAULT '' COMMENT '创建者', + `create_time` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间', + `updater` VARCHAR(64) NOT NULL DEFAULT '' COMMENT '更新者', + `update_time` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '最后更新时间', + `deleted` TINYINT(1) NOT NULL DEFAULT 0 COMMENT '是否删除' +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='数据命名与简写标准' +-- ---------------------------- +-- Records of system_std_nms +-- ---------------------------- SET FOREIGN_KEY_CHECKS = 1; \ No newline at end of file diff --git a/yudao-module-system/yudao-module-system-api/src/main/java/cn/iocoder/yudao/module/system/enums/ErrorCodeConstants.java b/yudao-module-system/yudao-module-system-api/src/main/java/cn/iocoder/yudao/module/system/enums/ErrorCodeConstants.java index 2d66a7f8..c7e3938f 100644 --- a/yudao-module-system/yudao-module-system-api/src/main/java/cn/iocoder/yudao/module/system/enums/ErrorCodeConstants.java +++ b/yudao-module-system/yudao-module-system-api/src/main/java/cn/iocoder/yudao/module/system/enums/ErrorCodeConstants.java @@ -176,4 +176,6 @@ public interface ErrorCodeConstants { // ========== 用户与部门关系 1-002-029-000 ========== ErrorCode USER_DEPT_NOT_EXISTS = new ErrorCode(1_002_029_000, "用户与部门关系不存在"); + // ========== 数据命名与简写标准 ========== + ErrorCode STD_NMS_NOT_EXISTS = new ErrorCode(1_002_030_000, "数据命名与简写标准不存在"); } diff --git a/yudao-module-system/yudao-module-system-server/src/main/java/cn/iocoder/yudao/module/system/controller/admin/stdnms/StdNmsController.java b/yudao-module-system/yudao-module-system-server/src/main/java/cn/iocoder/yudao/module/system/controller/admin/stdnms/StdNmsController.java new file mode 100644 index 00000000..6f2e174d --- /dev/null +++ b/yudao-module-system/yudao-module-system-server/src/main/java/cn/iocoder/yudao/module/system/controller/admin/stdnms/StdNmsController.java @@ -0,0 +1,120 @@ +package cn.iocoder.yudao.module.system.controller.admin.stdnms; + +import cn.iocoder.yudao.framework.apilog.core.annotation.ApiAccessLog; +import cn.iocoder.yudao.framework.common.pojo.CommonResult; +import cn.iocoder.yudao.framework.common.pojo.PageParam; +import cn.iocoder.yudao.framework.common.pojo.PageResult; +import cn.iocoder.yudao.framework.common.util.object.BeanUtils; +import cn.iocoder.yudao.framework.excel.core.util.ExcelUtils; +import cn.iocoder.yudao.framework.tenant.core.aop.TenantIgnore; +import cn.iocoder.yudao.module.system.controller.admin.stdnms.vo.StdNmsPageReqVO; +import cn.iocoder.yudao.module.system.controller.admin.stdnms.vo.StdNmsRespVO; +import cn.iocoder.yudao.module.system.controller.admin.stdnms.vo.StdNmsSaveReqVO; +import cn.iocoder.yudao.module.system.dal.dataobject.stdnms.StdNmsDO; +import cn.iocoder.yudao.module.system.service.stdnms.StdNmsService; +import io.swagger.v3.oas.annotations.Operation; +import io.swagger.v3.oas.annotations.Parameter; +import io.swagger.v3.oas.annotations.tags.Tag; +import jakarta.annotation.Resource; +import jakarta.servlet.http.HttpServletResponse; +import jakarta.validation.Valid; +import org.springframework.dao.DataIntegrityViolationException; +import org.springframework.http.HttpStatus; +import org.springframework.security.access.prepost.PreAuthorize; +import org.springframework.validation.annotation.Validated; +import org.springframework.web.bind.annotation.*; + +import java.io.IOException; +import java.util.List; + +import static cn.iocoder.yudao.framework.apilog.core.enums.OperateTypeEnum.EXPORT; +import static cn.iocoder.yudao.framework.common.pojo.CommonResult.success; + +@Tag(name = "管理后台 - 数据命名与简写标准") +@RestController +@RequestMapping("/system/std-nms") +@Validated +public class StdNmsController { + + @Resource + private StdNmsService stdNmsService; + + @PostMapping("/create") + @Operation(summary = "创建数据命名与简写标准") + @PreAuthorize("@ss.hasPermission('system:std-nms:create')") + @TenantIgnore + public CommonResult createStdNms(@Valid @RequestBody StdNmsSaveReqVO createReqVO) { + try { + return success(stdNmsService.createStdNms(createReqVO)); + } catch (DataIntegrityViolationException e) { + return CommonResult.customize(0L, HttpStatus.BAD_REQUEST.value(), "已存在相同含义的定义"); + } + } + + @PutMapping("/update") + @Operation(summary = "更新数据命名与简写标准") + @PreAuthorize("@ss.hasPermission('system:std-nms:update')") + @TenantIgnore + public CommonResult updateStdNms(@Valid @RequestBody StdNmsSaveReqVO updateReqVO) { + try { + stdNmsService.updateStdNms(updateReqVO); + } catch (Exception e) { + return CommonResult.customize(true,HttpStatus.BAD_REQUEST.value(), "已存在相同含义的定义"); + } + return success(true); + } + + @DeleteMapping("/delete") + @Operation(summary = "删除数据命名与简写标准") + @Parameter(name = "id", description = "编号", required = true) + @PreAuthorize("@ss.hasPermission('system:std-nms:delete')") + @TenantIgnore + public CommonResult deleteStdNms(@RequestParam("id") Long id) { + stdNmsService.deleteStdNms(id); + return success(true); + } + + @DeleteMapping("/delete-list") + @Parameter(name = "ids", description = "编号", required = true) + @Operation(summary = "批量删除数据命名与简写标准") + @PreAuthorize("@ss.hasPermission('system:std-nms:delete')") + @TenantIgnore + public CommonResult deleteStdNmsList(@RequestParam("ids") List ids) { + stdNmsService.deleteStdNmsListByIds(ids); + return success(true); + } + + @GetMapping("/get") + @Operation(summary = "获得数据命名与简写标准") + @Parameter(name = "id", description = "编号", required = true, example = "1024") + @PreAuthorize("@ss.hasPermission('system:std-nms:query')") + @TenantIgnore + public CommonResult getStdNms(@RequestParam("id") Long id) { + StdNmsDO stdNms = stdNmsService.getStdNms(id); + return success(BeanUtils.toBean(stdNms, StdNmsRespVO.class)); + } + + @GetMapping("/page") + @Operation(summary = "获得数据命名与简写标准分页") + @PreAuthorize("@ss.hasPermission('system:std-nms:query')") + @TenantIgnore + public CommonResult> getStdNmsPage(@Valid StdNmsPageReqVO pageReqVO) { + PageResult pageResult = stdNmsService.getStdNmsPage(pageReqVO); + return success(BeanUtils.toBean(pageResult, StdNmsRespVO.class)); + } + + @GetMapping("/export-excel") + @Operation(summary = "导出数据命名与简写标准 Excel") + @PreAuthorize("@ss.hasPermission('system:std-nms:export')") + @TenantIgnore + @ApiAccessLog(operateType = EXPORT) + public void exportStdNmsExcel(@Valid StdNmsPageReqVO pageReqVO, + HttpServletResponse response) throws IOException { + pageReqVO.setPageSize(PageParam.PAGE_SIZE_NONE); + List list = stdNmsService.getStdNmsPage(pageReqVO).getList(); + // 导出 Excel + ExcelUtils.write(response, "数据命名与简写标准.xls", "数据", StdNmsRespVO.class, + BeanUtils.toBean(list, StdNmsRespVO.class)); + } + +} \ No newline at end of file diff --git a/yudao-module-system/yudao-module-system-server/src/main/java/cn/iocoder/yudao/module/system/controller/admin/stdnms/vo/StdNmsPageReqVO.java b/yudao-module-system/yudao-module-system-server/src/main/java/cn/iocoder/yudao/module/system/controller/admin/stdnms/vo/StdNmsPageReqVO.java new file mode 100644 index 00000000..e84c6235 --- /dev/null +++ b/yudao-module-system/yudao-module-system-server/src/main/java/cn/iocoder/yudao/module/system/controller/admin/stdnms/vo/StdNmsPageReqVO.java @@ -0,0 +1,29 @@ +package cn.iocoder.yudao.module.system.controller.admin.stdnms.vo; + +import lombok.*; +import java.util.*; +import io.swagger.v3.oas.annotations.media.Schema; +import cn.iocoder.yudao.framework.common.pojo.PageParam; +import org.springframework.format.annotation.DateTimeFormat; +import java.time.LocalDateTime; + +import static cn.iocoder.yudao.framework.common.util.date.DateUtils.FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND; + +@Schema(description = "管理后台 - 数据命名与简写标准分页 Request VO") +@Data +public class StdNmsPageReqVO extends PageParam { + + @Schema(description = "英文") + private String word; + + @Schema(description = "简写") + private String abbr; + + @Schema(description = "中文意思") + private String info; + + @Schema(description = "创建时间") + @DateTimeFormat(pattern = FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND) + private LocalDateTime[] createTime; + +} \ No newline at end of file diff --git a/yudao-module-system/yudao-module-system-server/src/main/java/cn/iocoder/yudao/module/system/controller/admin/stdnms/vo/StdNmsRespVO.java b/yudao-module-system/yudao-module-system-server/src/main/java/cn/iocoder/yudao/module/system/controller/admin/stdnms/vo/StdNmsRespVO.java new file mode 100644 index 00000000..85f3bd7a --- /dev/null +++ b/yudao-module-system/yudao-module-system-server/src/main/java/cn/iocoder/yudao/module/system/controller/admin/stdnms/vo/StdNmsRespVO.java @@ -0,0 +1,35 @@ +package cn.iocoder.yudao.module.system.controller.admin.stdnms.vo; + +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.*; +import java.util.*; +import org.springframework.format.annotation.DateTimeFormat; +import java.time.LocalDateTime; +import com.alibaba.excel.annotation.*; + +@Schema(description = "管理后台 - 数据命名与简写标准 Response VO") +@Data +@ExcelIgnoreUnannotated +public class StdNmsRespVO { + + @Schema(description = "主键ID", requiredMode = Schema.RequiredMode.REQUIRED, example = "2987") + @ExcelProperty("主键ID") + private Long id; + + @Schema(description = "英文", requiredMode = Schema.RequiredMode.REQUIRED) + @ExcelProperty("英文") + private String word; + + @Schema(description = "简写", requiredMode = Schema.RequiredMode.REQUIRED) + @ExcelProperty("简写") + private String abbr; + + @Schema(description = "中文意思", requiredMode = Schema.RequiredMode.REQUIRED) + @ExcelProperty("中文意思") + private String info; + + @Schema(description = "创建时间", requiredMode = Schema.RequiredMode.REQUIRED) + @ExcelProperty("创建时间") + private LocalDateTime createTime; + +} \ No newline at end of file diff --git a/yudao-module-system/yudao-module-system-server/src/main/java/cn/iocoder/yudao/module/system/controller/admin/stdnms/vo/StdNmsSaveReqVO.java b/yudao-module-system/yudao-module-system-server/src/main/java/cn/iocoder/yudao/module/system/controller/admin/stdnms/vo/StdNmsSaveReqVO.java new file mode 100644 index 00000000..a03f1803 --- /dev/null +++ b/yudao-module-system/yudao-module-system-server/src/main/java/cn/iocoder/yudao/module/system/controller/admin/stdnms/vo/StdNmsSaveReqVO.java @@ -0,0 +1,27 @@ +package cn.iocoder.yudao.module.system.controller.admin.stdnms.vo; + +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.*; +import java.util.*; +import jakarta.validation.constraints.*; + +@Schema(description = "管理后台 - 数据命名与简写标准新增/修改 Request VO") +@Data +public class StdNmsSaveReqVO { + + @Schema(description = "主键ID", requiredMode = Schema.RequiredMode.REQUIRED, example = "2987") + private Long id; + + @Schema(description = "英文", requiredMode = Schema.RequiredMode.REQUIRED) + @NotEmpty(message = "英文不能为空") + private String word; + + @Schema(description = "简写", requiredMode = Schema.RequiredMode.REQUIRED) + @NotEmpty(message = "简写不能为空") + private String abbr; + + @Schema(description = "中文意思", requiredMode = Schema.RequiredMode.REQUIRED) + @NotEmpty(message = "中文意思不能为空") + private String info; + +} \ No newline at end of file diff --git a/yudao-module-system/yudao-module-system-server/src/main/java/cn/iocoder/yudao/module/system/dal/dataobject/stdnms/StdNmsDO.java b/yudao-module-system/yudao-module-system-server/src/main/java/cn/iocoder/yudao/module/system/dal/dataobject/stdnms/StdNmsDO.java new file mode 100644 index 00000000..e1ef563a --- /dev/null +++ b/yudao-module-system/yudao-module-system-server/src/main/java/cn/iocoder/yudao/module/system/dal/dataobject/stdnms/StdNmsDO.java @@ -0,0 +1,47 @@ +package cn.iocoder.yudao.module.system.dal.dataobject.stdnms; + +import lombok.*; +import java.util.*; +import java.time.LocalDateTime; +import java.time.LocalDateTime; +import com.baomidou.mybatisplus.annotation.*; +import cn.iocoder.yudao.framework.mybatis.core.dataobject.BaseDO; +/** + * 数据命名与简写标准 DO + * + * @author 后台管理 + */ +@TableName("system_std_nms") +@KeySequence("system_std_nms_seq") // 用于 Oracle、PostgreSQL、Kingbase、DB2、H2 数据库的主键自增。如果是 MySQL 等数据库,可不写。 +@Data +@EqualsAndHashCode(callSuper = true) +@ToString(callSuper = true) +@Builder +@NoArgsConstructor +@AllArgsConstructor +/** + * 支持业务基类继承:isBusiness=true 时继承 BusinessBaseDO,否则继承 BaseDO + */ +public class StdNmsDO extends BaseDO { + + + /** + * 主键ID + */ + @TableId(type = IdType.ASSIGN_ID) + private Long id; + /** + * 英文 + */ + private String word; + /** + * 简写 + */ + private String abbr; + /** + * 中文意思 + */ + private String info; + + +} \ No newline at end of file diff --git a/yudao-module-system/yudao-module-system-server/src/main/java/cn/iocoder/yudao/module/system/dal/mysql/stdnms/StdNmsMapper.java b/yudao-module-system/yudao-module-system-server/src/main/java/cn/iocoder/yudao/module/system/dal/mysql/stdnms/StdNmsMapper.java new file mode 100644 index 00000000..5bd448ed --- /dev/null +++ b/yudao-module-system/yudao-module-system-server/src/main/java/cn/iocoder/yudao/module/system/dal/mysql/stdnms/StdNmsMapper.java @@ -0,0 +1,29 @@ +package cn.iocoder.yudao.module.system.dal.mysql.stdnms; + +import java.util.*; + +import cn.iocoder.yudao.framework.common.pojo.PageResult; +import cn.iocoder.yudao.framework.mybatis.core.query.LambdaQueryWrapperX; +import cn.iocoder.yudao.framework.mybatis.core.mapper.BaseMapperX; +import cn.iocoder.yudao.module.system.dal.dataobject.stdnms.StdNmsDO; +import org.apache.ibatis.annotations.Mapper; +import cn.iocoder.yudao.module.system.controller.admin.stdnms.vo.*; + +/** + * 数据命名与简写标准 Mapper + * + * @author 后台管理 + */ +@Mapper +public interface StdNmsMapper extends BaseMapperX { + + default PageResult selectPage(StdNmsPageReqVO reqVO) { + return selectPage(reqVO, new LambdaQueryWrapperX() + .eqIfPresent(StdNmsDO::getWord, reqVO.getWord()) + .eqIfPresent(StdNmsDO::getAbbr, reqVO.getAbbr()) + .eqIfPresent(StdNmsDO::getInfo, reqVO.getInfo()) + .betweenIfPresent(StdNmsDO::getCreateTime, reqVO.getCreateTime()) + .orderByDesc(StdNmsDO::getId)); + } + +} \ No newline at end of file diff --git a/yudao-module-system/yudao-module-system-server/src/main/java/cn/iocoder/yudao/module/system/service/stdnms/StdNmsService.java b/yudao-module-system/yudao-module-system-server/src/main/java/cn/iocoder/yudao/module/system/service/stdnms/StdNmsService.java new file mode 100644 index 00000000..3699cd00 --- /dev/null +++ b/yudao-module-system/yudao-module-system-server/src/main/java/cn/iocoder/yudao/module/system/service/stdnms/StdNmsService.java @@ -0,0 +1,62 @@ +package cn.iocoder.yudao.module.system.service.stdnms; + +import java.util.*; +import jakarta.validation.*; +import cn.iocoder.yudao.module.system.controller.admin.stdnms.vo.*; +import cn.iocoder.yudao.module.system.dal.dataobject.stdnms.StdNmsDO; +import cn.iocoder.yudao.framework.common.pojo.PageResult; +import cn.iocoder.yudao.framework.common.pojo.PageParam; + +/** + * 数据命名与简写标准 Service 接口 + * + * @author 后台管理 + */ +public interface StdNmsService { + + /** + * 创建数据命名与简写标准 + * + * @param createReqVO 创建信息 + * @return 编号 + */ + Long createStdNms(@Valid StdNmsSaveReqVO createReqVO); + + /** + * 更新数据命名与简写标准 + * + * @param updateReqVO 更新信息 + */ + void updateStdNms(@Valid StdNmsSaveReqVO updateReqVO); + + /** + * 删除数据命名与简写标准 + * + * @param id 编号 + */ + void deleteStdNms(Long id); + + /** + * 批量删除数据命名与简写标准 + * + * @param ids 编号 + */ + void deleteStdNmsListByIds(List ids); + + /** + * 获得数据命名与简写标准 + * + * @param id 编号 + * @return 数据命名与简写标准 + */ + StdNmsDO getStdNms(Long id); + + /** + * 获得数据命名与简写标准分页 + * + * @param pageReqVO 分页查询 + * @return 数据命名与简写标准分页 + */ + PageResult getStdNmsPage(StdNmsPageReqVO pageReqVO); + +} \ No newline at end of file diff --git a/yudao-module-system/yudao-module-system-server/src/main/java/cn/iocoder/yudao/module/system/service/stdnms/StdNmsServiceImpl.java b/yudao-module-system/yudao-module-system-server/src/main/java/cn/iocoder/yudao/module/system/service/stdnms/StdNmsServiceImpl.java new file mode 100644 index 00000000..3bd104cb --- /dev/null +++ b/yudao-module-system/yudao-module-system-server/src/main/java/cn/iocoder/yudao/module/system/service/stdnms/StdNmsServiceImpl.java @@ -0,0 +1,92 @@ +package cn.iocoder.yudao.module.system.service.stdnms; + +import cn.hutool.core.collection.CollUtil; +import org.springframework.stereotype.Service; +import jakarta.annotation.Resource; +import org.springframework.validation.annotation.Validated; +import org.springframework.transaction.annotation.Transactional; + +import java.util.*; +import cn.iocoder.yudao.module.system.controller.admin.stdnms.vo.*; +import cn.iocoder.yudao.module.system.dal.dataobject.stdnms.StdNmsDO; +import cn.iocoder.yudao.framework.common.pojo.PageResult; +import cn.iocoder.yudao.framework.common.pojo.PageParam; +import cn.iocoder.yudao.framework.common.util.object.BeanUtils; + +import cn.iocoder.yudao.module.system.dal.mysql.stdnms.StdNmsMapper; + +import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception; +import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.convertList; +import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.diffList; +import static cn.iocoder.yudao.module.system.enums.ErrorCodeConstants.*; + +/** + * 数据命名与简写标准 Service 实现类 + * + * @author 后台管理 + */ +@Service +@Validated +public class StdNmsServiceImpl implements StdNmsService { + + @Resource + private StdNmsMapper stdNmsMapper; + + @Override + public Long createStdNms(StdNmsSaveReqVO createReqVO) { + // 插入 + StdNmsDO stdNms = BeanUtils.toBean(createReqVO, StdNmsDO.class); + stdNmsMapper.insert(stdNms); + // 返回 + return stdNms.getId(); + } + + @Override + public void updateStdNms(StdNmsSaveReqVO updateReqVO) { + // 校验存在 + validateStdNmsExists(updateReqVO.getId()); + // 更新 + StdNmsDO updateObj = BeanUtils.toBean(updateReqVO, StdNmsDO.class); + stdNmsMapper.updateById(updateObj); + } + + @Override + public void deleteStdNms(Long id) { + // 校验存在 + validateStdNmsExists(id); + // 删除 + stdNmsMapper.deleteById(id); + } + + @Override + public void deleteStdNmsListByIds(List ids) { + // 校验存在 + validateStdNmsExists(ids); + // 删除 + stdNmsMapper.deleteByIds(ids); + } + + private void validateStdNmsExists(List ids) { + List list = stdNmsMapper.selectByIds(ids); + if (CollUtil.isEmpty(list) || list.size() != ids.size()) { + throw exception(STD_NMS_NOT_EXISTS); + } + } + + private void validateStdNmsExists(Long id) { + if (stdNmsMapper.selectById(id) == null) { + throw exception(STD_NMS_NOT_EXISTS); + } + } + + @Override + public StdNmsDO getStdNms(Long id) { + return stdNmsMapper.selectById(id); + } + + @Override + public PageResult getStdNmsPage(StdNmsPageReqVO pageReqVO) { + return stdNmsMapper.selectPage(pageReqVO); + } + +} \ No newline at end of file From b7d2cf3802618af9664cd1c70cd9bd7c1f274f04 Mon Sep 17 00:00:00 2001 From: chenbowen Date: Wed, 23 Jul 2025 17:50:53 +0800 Subject: [PATCH 4/5] =?UTF-8?q?1.=20=E5=AE=8C=E5=96=84=E9=99=84=E4=BB=B6?= =?UTF-8?q?=E5=8A=A0=E5=AF=86=E4=B8=8B=E8=BD=BD=E9=AA=8C=E8=AF=81=E7=A0=81?= =?UTF-8?q?=E8=8E=B7=E5=8F=96=E6=B5=81=E7=A8=8B?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../controller/admin/file/FileController.java | 19 ++++++++++++++++--- .../admin/file/vo/file/FileRespVO.java | 4 ++++ .../infra/service/file/FileServiceImpl.java | 9 ++------- .../infra/util/VerificationCodeUtil.java | 4 ++-- .../src/main/resources/application.yaml | 2 ++ .../src/main/resources/application-local.yaml | 2 ++ 6 files changed, 28 insertions(+), 12 deletions(-) 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 002417f4..dcb81546 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 @@ -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 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 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()); } } } diff --git a/yudao-module-infra/yudao-module-infra-server/src/main/java/cn/iocoder/yudao/module/infra/controller/admin/file/vo/file/FileRespVO.java b/yudao-module-infra/yudao-module-infra-server/src/main/java/cn/iocoder/yudao/module/infra/controller/admin/file/vo/file/FileRespVO.java index a0357da1..bb20dad4 100644 --- a/yudao-module-infra/yudao-module-infra-server/src/main/java/cn/iocoder/yudao/module/infra/controller/admin/file/vo/file/FileRespVO.java +++ b/yudao-module-infra/yudao-module-infra-server/src/main/java/cn/iocoder/yudao/module/infra/controller/admin/file/vo/file/FileRespVO.java @@ -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; 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 30c40410..61eae94f 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,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 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 index c848270a..8487ec9b 100644 --- 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 @@ -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); 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 fce2bfd4..8abab8d4 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 @@ -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 diff --git a/yudao-server/src/main/resources/application-local.yaml b/yudao-server/src/main/resources/application-local.yaml index 24977433..97d1543b 100644 --- a/yudao-server/src/main/resources/application-local.yaml +++ b/yudao-server/src/main/resources/application-local.yaml @@ -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 From 9f1fad0096757989556576adeca16c7ff11ad70e Mon Sep 17 00:00:00 2001 From: chenbowen Date: Thu, 24 Jul 2025 14:03:52 +0800 Subject: [PATCH 5/5] =?UTF-8?q?1.=20=E9=87=8D=E6=9E=84=E9=99=84=E4=BB=B6?= =?UTF-8?q?=E5=8A=A0=E5=AF=86=E4=B8=8B=E8=BD=BD=E9=AA=8C=E8=AF=81=E7=A0=81?= =?UTF-8?q?=E8=8E=B7=E5=8F=96=E6=B5=81=E7=A8=8B=202.=20=E6=96=B0=E5=A2=9E?= =?UTF-8?q?=E7=AE=80=E5=86=99=E5=91=BD=E5=90=8D=E5=AD=97=E5=85=B8=E5=8A=9F?= =?UTF-8?q?=E8=83=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../controller/admin/file/FileController.java | 54 ++++++++++++++----- .../dal/dataobject/stdnms/StdNmsDO.java | 15 +++--- .../system/dal/mysql/stdnms/StdNmsMapper.java | 25 +++++---- .../src/main/resources/application-local.yaml | 2 +- .../resources/mapper/stdnms/StdNmsMapper.xml | 24 +++++++++ 5 files changed, 87 insertions(+), 33 deletions(-) create mode 100644 yudao-module-system/yudao-module-system-server/src/main/resources/mapper/stdnms/StdNmsMapper.xml 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 dcb81546..2711f784 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 @@ -31,6 +31,7 @@ import org.springframework.web.multipart.MultipartFile; import java.io.IOException; import java.net.URLEncoder; import java.nio.charset.StandardCharsets; +import java.util.Base64; import static cn.iocoder.yudao.framework.common.pojo.CommonResult.success; import static cn.iocoder.yudao.framework.security.core.util.SecurityFrameworkUtils.getLoginUserId; @@ -51,6 +52,38 @@ public class FileController { @Value("${yudao.kkfile:}") private String onlinePreview; + @GetMapping("/download-url") + @Operation(summary = "获取文件下载地址", description = "根据 fileId 返回文件下载 url") + public CommonResult getDownloadUrl(@RequestParam("fileId") Long fileId) { + FileDO fileDO = fileService.getActiveFileById(fileId); + if (fileDO == null) { + return CommonResult.error(HttpStatus.NOT_FOUND.value(), "文件不存在"); + } + // FileDO 转换为 FileRespVO + FileRespVO fileRespVO = BeanUtils.toBean(fileDO, FileRespVO.class); + if (StrUtil.isEmpty(onlinePreview) || StrUtil.isEmpty(fileRespVO.getUrl())) { + return CommonResult.error(HttpStatus.BAD_REQUEST.value(), "文件 URL 为空"); + } + return success(fileRespVO); + } + + @GetMapping("/preview-url") + @Operation(summary = "获取文件预览地址", description = "根据 fileId 返回文件预览 url(kkfile)") + public CommonResult getPreviewUrl(@RequestParam("fileId") Long fileId) { + FileDO fileDO = fileService.getActiveFileById(fileId); + if (fileDO == null) { + return CommonResult.error(HttpStatus.NOT_FOUND.value(), "文件不存在"); + } + // FileDO 转换为 FileRespVO + FileRespVO fileRespVO = BeanUtils.toBean(fileDO, FileRespVO.class); + if (StrUtil.isEmpty(onlinePreview) || StrUtil.isEmpty(fileRespVO.getUrl())) { + return CommonResult.error(HttpStatus.BAD_REQUEST.value(), "在线预览地址未配置或文件 URL 为空"); + } + String previewUrl = onlinePreview + URLEncoder.encode(Base64.getEncoder().encodeToString(fileRespVO.getUrl().getBytes(StandardCharsets.UTF_8)), StandardCharsets.UTF_8); + fileRespVO.setPreviewUrl(previewUrl); + return success(fileRespVO); + } + @PostMapping("/upload") @Operation(summary = "上传文件", description = "模式一:后端上传文件") @@ -135,14 +168,6 @@ 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 { @@ -157,15 +182,18 @@ public class FileController { } @GetMapping("/generate-download-code") @Operation(summary = "获取下载验证码") - public CommonResult preDownloadEncrypt(@RequestParam("fileId") Long fileId) { + public CommonResult preDownloadEncrypt(@RequestParam("fileId") Long fileId) { Long userId = getLoginUserId(); - FileDO activeFileById = new FileDO(); + FileDO activeFileById = fileService.getActiveFileById(fileId); + if (activeFileById == null) { + return CommonResult.error(HttpStatus.NOT_FOUND.value(), "文件不存在"); + } + FileRespVO fileRespVO = BeanUtils.toBean(activeFileById, FileRespVO.class); try { - activeFileById = fileService.getActiveFileById(fileId); fileService.generateFileVerificationCode(fileId, userId); - return CommonResult.customize(activeFileById.getName(), HttpStatus.OK.value(), "验证码已生成,请使用验证码下载文件"); + return CommonResult.customize(fileRespVO, HttpStatus.OK.value(), "验证码已生成,请使用验证码下载文件"); } catch (ServiceException e) { - return CommonResult.customize(activeFileById.getName(), HttpStatus.BAD_REQUEST.value(), e.getMessage()); + return CommonResult.customize(fileRespVO, HttpStatus.OK.value(), e.getMessage()); } } } diff --git a/yudao-module-system/yudao-module-system-server/src/main/java/cn/iocoder/yudao/module/system/dal/dataobject/stdnms/StdNmsDO.java b/yudao-module-system/yudao-module-system-server/src/main/java/cn/iocoder/yudao/module/system/dal/dataobject/stdnms/StdNmsDO.java index e1ef563a..71832e93 100644 --- a/yudao-module-system/yudao-module-system-server/src/main/java/cn/iocoder/yudao/module/system/dal/dataobject/stdnms/StdNmsDO.java +++ b/yudao-module-system/yudao-module-system-server/src/main/java/cn/iocoder/yudao/module/system/dal/dataobject/stdnms/StdNmsDO.java @@ -1,11 +1,11 @@ package cn.iocoder.yudao.module.system.dal.dataobject.stdnms; -import lombok.*; -import java.util.*; -import java.time.LocalDateTime; -import java.time.LocalDateTime; -import com.baomidou.mybatisplus.annotation.*; import cn.iocoder.yudao.framework.mybatis.core.dataobject.BaseDO; +import com.baomidou.mybatisplus.annotation.IdType; +import com.baomidou.mybatisplus.annotation.KeySequence; +import com.baomidou.mybatisplus.annotation.TableId; +import com.baomidou.mybatisplus.annotation.TableName; +import lombok.*; /** * 数据命名与简写标准 DO * @@ -42,6 +42,9 @@ public class StdNmsDO extends BaseDO { * 中文意思 */ private String info; - + /** + * 是否删除(取消逻辑删除) + */ + private Boolean deleted; } \ No newline at end of file diff --git a/yudao-module-system/yudao-module-system-server/src/main/java/cn/iocoder/yudao/module/system/dal/mysql/stdnms/StdNmsMapper.java b/yudao-module-system/yudao-module-system-server/src/main/java/cn/iocoder/yudao/module/system/dal/mysql/stdnms/StdNmsMapper.java index 5bd448ed..e21cb124 100644 --- a/yudao-module-system/yudao-module-system-server/src/main/java/cn/iocoder/yudao/module/system/dal/mysql/stdnms/StdNmsMapper.java +++ b/yudao-module-system/yudao-module-system-server/src/main/java/cn/iocoder/yudao/module/system/dal/mysql/stdnms/StdNmsMapper.java @@ -1,13 +1,12 @@ package cn.iocoder.yudao.module.system.dal.mysql.stdnms; -import java.util.*; - import cn.iocoder.yudao.framework.common.pojo.PageResult; -import cn.iocoder.yudao.framework.mybatis.core.query.LambdaQueryWrapperX; import cn.iocoder.yudao.framework.mybatis.core.mapper.BaseMapperX; +import cn.iocoder.yudao.module.system.controller.admin.stdnms.vo.StdNmsPageReqVO; import cn.iocoder.yudao.module.system.dal.dataobject.stdnms.StdNmsDO; import org.apache.ibatis.annotations.Mapper; -import cn.iocoder.yudao.module.system.controller.admin.stdnms.vo.*; + +import java.util.List; /** * 数据命名与简写标准 Mapper @@ -17,13 +16,13 @@ import cn.iocoder.yudao.module.system.controller.admin.stdnms.vo.*; @Mapper public interface StdNmsMapper extends BaseMapperX { - default PageResult selectPage(StdNmsPageReqVO reqVO) { - return selectPage(reqVO, new LambdaQueryWrapperX() - .eqIfPresent(StdNmsDO::getWord, reqVO.getWord()) - .eqIfPresent(StdNmsDO::getAbbr, reqVO.getAbbr()) - .eqIfPresent(StdNmsDO::getInfo, reqVO.getInfo()) - .betweenIfPresent(StdNmsDO::getCreateTime, reqVO.getCreateTime()) - .orderByDesc(StdNmsDO::getId)); - } + // 使用自定义 XML SQL 分页查询,word 不区分大小写 + List selectPageCustom(StdNmsPageReqVO reqVO); + + default PageResult selectPage(StdNmsPageReqVO reqVO) { + List records = selectPageCustom(reqVO); + // 这里只做简单封装,如需 total 可自定义 count SQL + return new PageResult<>(records, (long) records.size()); + } +} -} \ No newline at end of file diff --git a/yudao-module-system/yudao-module-system-server/src/main/resources/application-local.yaml b/yudao-module-system/yudao-module-system-server/src/main/resources/application-local.yaml index 689136b3..0c4a6a4c 100644 --- a/yudao-module-system/yudao-module-system-server/src/main/resources/application-local.yaml +++ b/yudao-module-system/yudao-module-system-server/src/main/resources/application-local.yaml @@ -84,7 +84,7 @@ rocketmq: xxl: job: - enabled: false # 是否开启调度中心,默认为 true 开启 + enabled: true # 是否开启调度中心,默认为 true 开启 admin: addresses: http://172.16.46.63:30082/xxl-job-admin # 调度中心部署跟地址 diff --git a/yudao-module-system/yudao-module-system-server/src/main/resources/mapper/stdnms/StdNmsMapper.xml b/yudao-module-system/yudao-module-system-server/src/main/resources/mapper/stdnms/StdNmsMapper.xml new file mode 100644 index 00000000..cd8d25ab --- /dev/null +++ b/yudao-module-system/yudao-module-system-server/src/main/resources/mapper/stdnms/StdNmsMapper.xml @@ -0,0 +1,24 @@ + + + + + + +