From e352488298c98a69fa809cede55c29d17eb29691 Mon Sep 17 00:00:00 2001 From: hongawen <83944980@qq.com> Date: Thu, 12 Mar 2026 20:08:58 +0800 Subject: [PATCH] =?UTF-8?q?=E5=BE=AE=E8=B0=83?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .gitignore | 3 + .../rdms-spring-boot-starter-biz-ip/README.md | 118 ---- .../rdms-spring-boot-starter-env/readme.md | 24 - .../rdms-spring-boot-starter-excel/README.md | 89 --- .../MQ_REFACTOR_PLAN.md | 217 ------ .../rdms-spring-boot-starter-mq/README.md | 241 ------- .../README.md | 349 ---------- .../README.md | 373 ---------- .../rdms-spring-boot-starter-redis/README.md | 389 ----------- .../rdms-spring-boot-starter-rpc/README.md | 159 ----- .../README.md | 646 ------------------ .../rdms-spring-boot-starter-test/README.md | 373 ---------- .../rdms-spring-boot-starter-web/README.md | 606 ---------------- .../README.md | 142 ---- rdms-gateway/README.md | 143 ---- rdms-system/rdms-system-api/README.md | 57 -- .../controller/admin/auth/AuthController.http | 45 -- .../admin/auth/vo/AuthRegisterReqVO.java | 2 +- .../admin/dict/DictDataController.http | 3 - .../admin/file/FileConfigController.http | 42 -- .../controller/admin/ip/AreaController.http | 3 - .../admin/logger/OperateLogController.http | 3 - .../vo/operatelog/OperateLogRespVO.java | 2 +- .../vo/message/NotifyMessageRespVO.java | 2 +- .../admin/oauth2/OAuth2ClientController.http | 22 - .../admin/oauth2/OAuth2OpenController.http | 53 -- .../admin/oauth2/OAuth2UserController.http | 12 - .../oauth2/vo/user/OAuth2UserInfoRespVO.java | 2 +- .../oauth2/vo/user/OAuth2UserUpdateReqVO.java | 2 +- .../admin/permission/MenuController.http | 3 - .../admin/permission/RoleController.http | 37 - .../admin/redis/RedisController.http | 4 - .../controller/admin/user/UserController.http | 8 - .../admin/user/UserProfileController.http | 3 - .../user/vo/profile/UserProfileRespVO.java | 2 +- .../vo/profile/UserProfileUpdateReqVO.java | 2 +- .../admin/user/vo/user/UserRespVO.java | 2 +- .../admin/user/vo/user/UserSaveReqVO.java | 2 +- 38 files changed, 12 insertions(+), 4173 deletions(-) delete mode 100644 rdms-framework/rdms-spring-boot-starter-biz-ip/README.md delete mode 100644 rdms-framework/rdms-spring-boot-starter-env/readme.md delete mode 100644 rdms-framework/rdms-spring-boot-starter-excel/README.md delete mode 100644 rdms-framework/rdms-spring-boot-starter-mq/MQ_REFACTOR_PLAN.md delete mode 100644 rdms-framework/rdms-spring-boot-starter-mq/README.md delete mode 100644 rdms-framework/rdms-spring-boot-starter-mybatis/README.md delete mode 100644 rdms-framework/rdms-spring-boot-starter-protection/README.md delete mode 100644 rdms-framework/rdms-spring-boot-starter-redis/README.md delete mode 100644 rdms-framework/rdms-spring-boot-starter-rpc/README.md delete mode 100644 rdms-framework/rdms-spring-boot-starter-security/README.md delete mode 100644 rdms-framework/rdms-spring-boot-starter-test/README.md delete mode 100644 rdms-framework/rdms-spring-boot-starter-web/README.md delete mode 100644 rdms-framework/rdms-spring-boot-starter-websocket/README.md delete mode 100644 rdms-gateway/README.md delete mode 100644 rdms-system/rdms-system-api/README.md delete mode 100644 rdms-system/rdms-system-boot/src/main/java/com/njcn/rdms/module/system/controller/admin/auth/AuthController.http delete mode 100644 rdms-system/rdms-system-boot/src/main/java/com/njcn/rdms/module/system/controller/admin/dict/DictDataController.http delete mode 100644 rdms-system/rdms-system-boot/src/main/java/com/njcn/rdms/module/system/controller/admin/file/FileConfigController.http delete mode 100644 rdms-system/rdms-system-boot/src/main/java/com/njcn/rdms/module/system/controller/admin/ip/AreaController.http delete mode 100644 rdms-system/rdms-system-boot/src/main/java/com/njcn/rdms/module/system/controller/admin/logger/OperateLogController.http delete mode 100644 rdms-system/rdms-system-boot/src/main/java/com/njcn/rdms/module/system/controller/admin/oauth2/OAuth2ClientController.http delete mode 100644 rdms-system/rdms-system-boot/src/main/java/com/njcn/rdms/module/system/controller/admin/oauth2/OAuth2OpenController.http delete mode 100644 rdms-system/rdms-system-boot/src/main/java/com/njcn/rdms/module/system/controller/admin/oauth2/OAuth2UserController.http delete mode 100644 rdms-system/rdms-system-boot/src/main/java/com/njcn/rdms/module/system/controller/admin/permission/MenuController.http delete mode 100644 rdms-system/rdms-system-boot/src/main/java/com/njcn/rdms/module/system/controller/admin/permission/RoleController.http delete mode 100644 rdms-system/rdms-system-boot/src/main/java/com/njcn/rdms/module/system/controller/admin/redis/RedisController.http delete mode 100644 rdms-system/rdms-system-boot/src/main/java/com/njcn/rdms/module/system/controller/admin/user/UserController.http delete mode 100644 rdms-system/rdms-system-boot/src/main/java/com/njcn/rdms/module/system/controller/admin/user/UserProfileController.http diff --git a/.gitignore b/.gitignore index e55eb64..2e5268d 100644 --- a/.gitignore +++ b/.gitignore @@ -73,3 +73,6 @@ functions/mock screenshot .firebase sessionStore + +# local docs +/docs/ diff --git a/rdms-framework/rdms-spring-boot-starter-biz-ip/README.md b/rdms-framework/rdms-spring-boot-starter-biz-ip/README.md deleted file mode 100644 index ff68af7..0000000 --- a/rdms-framework/rdms-spring-boot-starter-biz-ip/README.md +++ /dev/null @@ -1,118 +0,0 @@ -# rdms-spring-boot-starter-biz-ip - -## 模块定位 -这是一个本地静态工具模块,用于: - -1. IP -> 地区编码查询 -2. 地区树查询与地区名称格式化 - -不包含远程调用,不包含自动配置,不包含智能识别能力。 - -## 它做什么 - -1. 基于 `ip2region.xdb` 做 IP 归属地编码查询(离线、本地内存查询) -2. 基于 `area.csv` 提供地区树、地区路径、父级区域定位等工具方法 - -主要入口类: - -1. `com.njcn.rdms.framework.ip.core.utils.IPUtils` -2. `com.njcn.rdms.framework.ip.core.utils.AreaUtils` - -## 它不做什么 - -1. 不访问外部 IP 服务 -2. 不保证行政区数据实时更新(数据随资源文件版本) -3. 不负责业务策略(如风控、推荐、画像) - -## 资源与代价 - -1. 内置资源文件: -2. `src/main/resources/ip2region.xdb`(约 4MB) -3. `src/main/resources/area.csv` -4. 类加载时会预加载资源到内存,换取查询速度 - -## 前端地区树返回(重点) - -本模块非常适合做省市区级联选择器的后端数据源。 - -典型接口: - -1. 管理端:`GET /system/area/tree` -2. App 端:`GET /system/area/tree` - -节点字段: - -1. `id`:区域编码 -2. `name`:区域名称 -3. `children`:子节点列表 - -## 后端示例(获取“江苏区域树”) - -### 1. 获取江苏节点 - -```java -import com.njcn.rdms.framework.ip.core.Area; -import com.njcn.rdms.framework.ip.core.utils.AreaUtils; - -Area jiangsu = AreaUtils.parseArea("中国/江苏省"); -if (jiangsu == null) { - return; -} -``` - -### 2. 直接返回江苏及其下级区域树 - -`Area` 本身就是树节点(包含 `children`),拿到江苏节点后即可把它作为一棵子树返回。 - -```java -import com.njcn.rdms.framework.ip.core.Area; -import com.njcn.rdms.framework.ip.core.utils.AreaUtils; - -Area jiangsu = AreaUtils.parseArea("中国/江苏省"); -return jiangsu; // children 中包含南京、苏州等下级节点 -``` - -### 3. 转成前端常用结构(id/name/children) - -```java -import com.njcn.rdms.framework.ip.core.Area; -import com.njcn.rdms.framework.ip.core.utils.AreaUtils; -import java.util.Collections; -import java.util.List; -import java.util.stream.Collectors; - -record AreaNode(Integer id, String name, List children) {} - -private static AreaNode toNode(Area a) { - List children = a.getChildren() == null - ? Collections.emptyList() - : a.getChildren().stream().map(child -> toNode(child)).collect(Collectors.toList()); - return new AreaNode(a.getId(), a.getName(), children); -} - -Area jiangsu = AreaUtils.parseArea("中国/江苏省"); -AreaNode jiangsuTree = jiangsu == null ? null : toNode(jiangsu); -``` - -返回示例: - -```json -{ - "id": 320000, - "name": "江苏省", - "children": [ - { "id": 320100, "name": "南京市", "children": [] }, - { "id": 320500, "name": "苏州市", "children": [] } - ] -} -``` - -## 适用场景 - -1. 后端需要快速把 IP 转成地区信息 -2. 需要返回地区树给前端做省市区联动 - -## 不需要它的场景 - -1. 项目没有 IP 归属地需求 -2. 项目不需要地区树能力 diff --git a/rdms-framework/rdms-spring-boot-starter-env/readme.md b/rdms-framework/rdms-spring-boot-starter-env/readme.md deleted file mode 100644 index f287478..0000000 --- a/rdms-framework/rdms-spring-boot-starter-env/readme.md +++ /dev/null @@ -1,24 +0,0 @@ -这个模块的核心作用是“环境标签(tag)透传 + 按标签路由”。 - -它具体做了什么 - -1. 启动早期把 rdms.env.tag 映射到 Nacos 实例 metadata (spring.cloud.nacos.discovery.metadata.tag)。 - 文件: EnvEnvironmentPostProcessor.java -2. Web 请求进来时读取请求头 tag,放到线程上下文。 - 文件: EnvWebFilter.java -3. Feign 调用时把上下文里的 tag 继续写到下游请求头。 - 文件: EnvLoadBalancerClient.java - -rdms.env.tag 在这个项目里是可选的,不配也能跑。 -代码里明确是“有就生效、没有就跳过”: -- 读取 rdms.env.tag,为空就直接返回,不改路由标签 - EnvEnvironmentPostProcessor.java -- application-local.yaml 里配了 rdms.env.tag: ${HOSTNAME},而 dev 没配 - application-local.yaml - 它的目的就是你说的这种场景之一: - 多人共用一个 Nacos namespace/集群时,用 tag 做流量隔离(比如“我发起的请求尽量打到我这套实例”)。 - 再具体点,作用有三层: -1. 给本实例打 Nacos metadata tag。 -2. 接口收到 tag 请求头后放到上下文。 -3. Feign/LB 优先选同 tag 实例。 - 所以 dev 不配 tag,通常代表该环境不需要“按人/按版本隔离流量”,走默认路由即可。 \ No newline at end of file diff --git a/rdms-framework/rdms-spring-boot-starter-excel/README.md b/rdms-framework/rdms-spring-boot-starter-excel/README.md deleted file mode 100644 index 63d936d..0000000 --- a/rdms-framework/rdms-spring-boot-starter-excel/README.md +++ /dev/null @@ -1,89 +0,0 @@ -# rdms-spring-boot-starter-excel - -## 模块定位 -这是一个面向业务的 Excel 能力模块,核心目标是: - -1. 降低导入导出开发成本(统一工具类) -2. 让业务字段和 Excel 展示语义解耦(注解 + Converter) -3. 把字典能力接入 Excel 场景(值与标签互转、校验、下拉选项) - -## 设计思路 - -1. 统一入口:通过 `ExcelUtils` 封装读写,Controller 只关心 VO 和数据。 -2. 注解驱动:通过 `@DictFormat`、`@ExcelColumnSelect` 把“字段语义”放在 VO 上,而不是散落在业务代码里。 -3. 转换器分层:`DictConvert`、`AreaConvert`、`JsonConvert`、`MoneyConvert` 分别处理不同类型转换。 -4. 字典缓存:`DictFrameworkUtils` 基于缓存读取字典,减少重复远程调用。 -5. 导出体验:自动列宽、下拉选项、Long 防精度丢失等细节统一在模块内处理。 - -## 核心类 - -1. `com.njcn.rdms.framework.excel.core.util.ExcelUtils` -2. `com.njcn.rdms.framework.excel.core.convert.DictConvert` -3. `com.njcn.rdms.framework.excel.core.handler.SelectSheetWriteHandler` -4. `com.njcn.rdms.framework.dict.core.DictFrameworkUtils` -5. `com.njcn.rdms.framework.dict.validation.InDict` - -## 示例:用户导入导出(含字典转换) - -### 1. 定义 Excel VO - -```java -import cn.idev.excel.annotation.ExcelProperty; -import com.njcn.rdms.framework.excel.core.annotations.DictFormat; -import com.njcn.rdms.framework.excel.core.convert.DictConvert; -import lombok.Data; - -@Data -public class UserImportExcelVO { - - @ExcelProperty("登录名称") - private String username; - - @ExcelProperty(value = "用户性别", converter = DictConvert.class) - @DictFormat("USER_SEX") - private Integer sex; - - @ExcelProperty(value = "账号状态", converter = DictConvert.class) - @DictFormat("COMMON_STATUS") - private Integer status; -} -``` - -### 2. 导出模板 - -```java -import com.njcn.rdms.framework.excel.core.util.ExcelUtils; -import jakarta.servlet.http.HttpServletResponse; - -public void exportTemplate(HttpServletResponse response) throws Exception { - ExcelUtils.write(response, "用户导入模板.xls", "用户列表", UserImportExcelVO.class, List.of()); -} -``` - -### 3. 导入解析 - -```java -import com.njcn.rdms.framework.excel.core.util.ExcelUtils; -import org.springframework.web.multipart.MultipartFile; - -public List importExcel(MultipartFile file) throws Exception { - return ExcelUtils.read(file, UserImportExcelVO.class); -} -``` - -说明: - -1. 导入时 `DictConvert` 会把 Excel 标签值转回字典 value(如“男”->`1`)。 -2. 导出时 `DictConvert` 会把字典 value 转成可读标签(如 `1`->“男”)。 - -## 适用场景 - -1. 后台管理常规 Excel 导入导出 -2. 字典字段较多、希望自动做值/标签转换 -3. 需要生成带下拉选项的导入模板 - -## 不适用场景 - -1. 超大规模离线数据处理(建议走专用批处理链路) -2. 复杂流式处理或多工作簿复杂编排(建议单独实现) - diff --git a/rdms-framework/rdms-spring-boot-starter-mq/MQ_REFACTOR_PLAN.md b/rdms-framework/rdms-spring-boot-starter-mq/MQ_REFACTOR_PLAN.md deleted file mode 100644 index 2cbc94e..0000000 --- a/rdms-framework/rdms-spring-boot-starter-mq/MQ_REFACTOR_PLAN.md +++ /dev/null @@ -1,217 +0,0 @@ -# MQ 改造方案(评审稿) - -## 1. 背景与目标 -你当前的业务策略是: -1. 单体部署优先使用 Redis 作为 MQ。 -2. 微服务部署优先使用 RocketMQ 作为 MQ。 -3. RabbitMQ、Kafka 暂时不作为主路径,仅保留包结构隔离,不参与核心链路。 - -本方案目标是: -1. 在不破坏现有系统的前提下,建立可切换的 MQ 入口。 -2. 把“切换成本”收敛到配置层,而不是业务代码层。 -3. 采用分阶段改造,先低风险,后统一抽象。 - -## 1.1 本期非目标(避免范围蔓延) -本期改造明确不包含: -1. 严格顺序语义保障(全链路有序消费)。 -2. 统一死信队列(DLQ)治理体系。 -3. 事务消息与最终一致性框架化封装。 -4. 多机房容灾级别的 MQ 治理。 - -说明: -1. 本期先完成“可切换、可回滚、可观测”的基础能力。 -2. 复杂语义能力后续在 RocketMQ 路线下单独立项。 - -## 2. 当前实现现状(简版) -当前 `rdms-spring-boot-starter-mq` 的真实情况: -1. Redis 能力最完整,包含模板、监听器抽象、Stream 补偿与清理任务。 -2. RabbitMQ 只有 `MessageConverter` 级别自动配置,未形成统一收发抽象。 - -当前 `rdms-system` 默认配置: - -## 3. 改造原则 -1. 先配置统一,再接口统一,最后再清理非主路径。 -2. 任何阶段都必须可回滚,且回滚只改配置不改代码。 -3. 保持 Rabbit/Kafka 包路径存在,避免一次性大删导致历史分支合并困难。 - -## 4. 目标架构(落地后) -统一引入配置: -1. `rdms.mq.type=redis|rocketmq` - -统一行为: -1. 单体环境配 `redis`。 -2. 微服务环境配 `rocketmq`。 - -建议方式: - -## 4.1 配置矩阵(最小可运行) - -### 单体(Redis) -```yaml -rdms: - mq: - type: redis - -spring: - data: - redis: - host: 127.0.0.1 - port: 6379 -``` - -### 微服务(RocketMQ) -```yaml -rdms: - mq: - type: rocketmq - -rocketmq: - name-server: 127.0.0.1:9876 - producer: - group: rdms-producer-group -``` - -备注: -1. 微服务切 Rocket 前,必须先补齐 topic、consumer-group 等业务配置。 -2. 建议在 dev/local 保留 Redis 配置,便于快速回滚。 - -## 4.2 命名规范(Topic / Channel / StreamKey) -建议统一命名规则,避免后续混乱: -1. Redis Channel / StreamKey:`{env}:{domain}:{event}` -2. Rocket Topic:`{env}_{domain}_{event}` -3. ConsumerGroup:`{app}-{domain}-cg` - -示例: -1. `dev:system:notify` -2. `prod_system_notify` -3. `rdms-system-notify-cg` - -## 5. 分阶段实施计划 - -## 阶段 A(低风险,建议先做) -目标:只做配置层统一,不动大规模代码。 - -实施项: -1. 新增配置项 `rdms.mq.type`,默认值 `redis`。 -3. 本阶段不删除 Rabbit/Kafka 代码,不变更包结构。 - -收益: -1. 切换 Redis/Rocket 只改配置。 -2. 不触碰核心业务逻辑,风险最低。 - -回滚: - -## 阶段 B(中风险,接口统一) -目标:抽象统一 MQ 发送接口,减少业务对具体中间件的耦合。 - -实施项: -1. 新增统一接口,例如 `UnifiedMqSender`。 -2. 提供 `RedisMqSender` 与 `RocketMqSender` 两个实现。 -3. 用 `@ConditionalOnProperty` 根据 `rdms.mq.type` 注入唯一实现。 -5. 统一消息契约,至少包含标准消息头: - `msgId`、`bizKey`、`timestamp`、`producer`、`traceId`、`version`。 - -收益: -1. 业务层不再直接依赖 `RedisMQTemplate` 或 `RocketMQTemplate`。 -2. 后续切换中间件时改动面可控。 -3. 消息协议可演进,减少多团队并行开发冲突。 - -回滚: -1. 切回原 sender Bean 注入方案,保留统一接口代码但不启用。 - -## 阶段 C(可选,结构收敛) -目标:让非主路径代码“隔离可见但不激活”。 - -实施项: -1. Rabbit/Kafka 相关自动配置入口默认关闭。 -2. 保留目录与类,增加注释标识 `reserved` 或 `deprecated`。 -3. 文档明确“当前生产主路径仅 Redis/Rocket”。 - -收益: -1. 新成员不会误判项目“全量支持四种 MQ”。 -2. 后续若要恢复 Rabbit/Kafka,可低成本重新开启。 - -回滚: -1. 打开对应配置开关即可恢复。 - -## 6. 影响面评估 -主要影响模块: -1. `rdms-framework/rdms-spring-boot-starter-mq` -3. `rdms-system/rdms-system-boot` 配置文件 - -主要风险点: -1. Redis 与 Rocket 的消费语义不完全一致,幂等要在业务侧兜底。 -2. 广播模型下,多实例重复消费属于预期,业务处理要避免副作用。 -3. 配置切换后,缺失 Rocket 连接配置会导致启动失败。 -4. 现有代码中 Kafka 消费类注解存在明显可疑项,改造时应单独复核。 -5. 缺少统一幂等策略时,Rocket 与 Redis 切换后可能放大重复消费副作用。 -6. 配置缺失时若做了自动降级,可能导致“误以为切到 Rocket,实际走本地/Redis”。 - -## 6.1 幂等策略(建议作为阶段 B 配套) -最小策略建议: -1. 每条消息必须带 `msgId` 与 `bizKey`。 -2. 以 `bizKey` 作为业务幂等键(例如订单号、任务号)。 -3. 消费前先做幂等检查,消费成功后写入幂等记录。 -4. 幂等记录建议放 Redis,设置合理 TTL(如 3~7 天,按业务回放窗口)。 - -说明: -1. `msgId` 解决“传输级去重定位”,`bizKey` 解决“业务级幂等”。 -2. 幂等是切换 MQ 前置条件,不应后补。 - -## 6.2 故障切换策略(明确 fail-fast) -建议默认策略: -1. 目标 MQ 不可用时 `fail-fast`,启动或发送直接失败并告警。 -2. 禁止隐式降级到 local,避免行为不透明。 - -可选策略: -1. 在开发环境允许显式降级(需开关控制,并打印告警日志)。 - -## 7. 验收清单(每阶段都要过) -1. 单体环境 `rdms.mq.type=redis` 可启动、可发送、可消费。 -2. 微服务环境 `rdms.mq.type=rocketmq` 可启动、可发送、可消费。 -4. 关键链路日志可定位发送端与消费端。 -5. 切换只改配置,不改代码。 -6. 回滚路径已验证。 -7. 幂等校验通过(重复消息不会造成业务副作用)。 -8. 关键指标可观测并有告警(发送失败、消费失败、积压、重试)。 - -## 7.1 观测与告警建议 -建议统一接入以下指标: -1. `mq_send_total` / `mq_send_fail_total` -2. `mq_consume_total` / `mq_consume_fail_total` -3. `mq_backlog_size` -4. `mq_retry_total` - -建议阈值(示例): -1. 5 分钟发送失败率 > 1% 告警。 -2. 连续 10 分钟消费失败率 > 0.5% 告警。 -3. 积压超过基线 3 倍且持续 10 分钟告警。 - -## 8. 推荐实施顺序与工时 -推荐顺序: -1. 先做阶段 A。 -2. 阶段 A 稳定后再做阶段 B。 -3. 阶段 C 按团队节奏处理。 - -粗略工时(含联调): -1. 阶段 A:0.5 天。 -2. 阶段 B:1 天到 1.5 天。 -3. 阶段 C:0.5 天。 - -## 8.1 上线与回滚步骤(建议) -上线顺序: -1. 开发环境完成阶段 A,验证双配置切换。 -2. 预发环境灰度 1 个实例切换目标 MQ。 -3. 小流量观察 1 天,确认无异常后全量。 - -回滚触发条件(任一满足即回滚): -1. 消费失败率持续超过阈值。 -2. 积压持续增长且无法在观察窗口内回落。 -3. 关键业务出现重复消费副作用。 - -回滚动作: -2. 不回滚代码,确保恢复路径最短。 - -## 9. 当前建议 -你现在对项目结构还在熟悉期,建议先做阶段 A 的评审与验证,不直接进入阶段 B。 -这样能先拿到“可切换能力”,同时把改造风险控制在最低范围。 diff --git a/rdms-framework/rdms-spring-boot-starter-mq/README.md b/rdms-framework/rdms-spring-boot-starter-mq/README.md deleted file mode 100644 index dfecea1..0000000 --- a/rdms-framework/rdms-spring-boot-starter-mq/README.md +++ /dev/null @@ -1,241 +0,0 @@ -# rdms-spring-boot-starter-mq - -## 模块定位 -`rdms-spring-boot-starter-mq` 是项目里的消息队列基础封装,目标是: -1. 对 Redis 消息模型做统一抽象,降低业务接入复杂度 -2. 通过自动配置按需启用消费者容器,不强制业务使用全部能力 -3. 为后续多 MQ 方案(Redis / RabbitMQ / RocketMQ / Kafka)提供统一入口 - -当前实现里,**核心能力在 Redis**,RabbitMQ 是轻量补充(消息转换器)。 - -## 设计思路 - -### 1. Redis 统一模型 -Redis 相关能力分成两类: -1. `Pub/Sub` 广播模型 -2. `Stream` 分组消费模型 - -两者的消息基类分别是: -1. `AbstractRedisChannelMessage` -2. `AbstractRedisStreamMessage` - -共同父类是 `AbstractRedisMessage`,内置 `headers`,方便扩展链路信息。 - -### 2. 统一发送模板 -发送端统一走 `RedisMQTemplate`: -1. `send(AbstractRedisChannelMessage)` -> Redis `convertAndSend` -2. `send(AbstractRedisStreamMessage)` -> Redis Stream `XADD` - -这样业务代码不需要关心底层命令细节。 - -### 3. 监听器抽象 + 惰性自动装配 -消费者由两个抽象监听器承接: -1. `AbstractRedisChannelMessageListener` -2. `AbstractRedisStreamMessageListener` - -自动配置采用 `@ConditionalOnBean(...)`: -1. 只有你定义了对应监听器 Bean,容器才会注册消费者 -2. 没有监听器时不会额外启动 MQ 消费组件 - -### 4. Stream 运维补偿 -针对 Redis Stream,框架额外提供两个定时任务: -1. `RedisPendingMessageResendJob` - 扫描 pending 超时消息并重投(默认超时 5 分钟) -2. `RedisStreamMessageCleanupJob` - 定时 `XTRIM`,默认仅保留最近 10000 条,防止内存膨胀 - -两者都使用 Redisson 分布式锁,避免多实例重复执行。 - -### 5. RabbitMQ 轻封装 -`RdmsRabbitMQAutoConfiguration` 只提供 `Jackson2JsonMessageConverter`,让 RabbitTemplate / @RabbitListener 默认按 JSON 处理消息。 - -### 6. 多 MQ 的真实边界 -虽然模块描述写了支持 Redis / RocketMQ / RabbitMQ / Kafka, -但本 starter 的自动配置主要是 Redis + Rabbit。 - -## 自动配置入口 -`META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports`: -1. `RdmsRedisMQProducerAutoConfiguration` -2. `RdmsRedisMQConsumerAutoConfiguration` -3. `RdmsRabbitMQAutoConfiguration` - -## 如何使用 - -## 1. 引入依赖 -一般由业务模块直接依赖: - -```xml - - com.njcn - rdms-spring-boot-starter-mq - -``` - -## 1.1 项目现有 Redis 作为 MQ 的常规用法(推荐) -推荐按这个顺序接入: -1. 配好 `spring.data.redis.*`。 -2. 先按第 2 节使用 `Pub/Sub`(这是项目里最常见路径)。 -3. 需要可恢复消费时,再按第 3 节接入 `Stream`。 - -## 2. 使用 Redis Pub/Sub(广播) -适用场景: 通知广播、在线会话广播、对可靠性要求不高但强调实时性。 - -### 2.1 定义消息 -```java -import com.njcn.rdms.framework.mq.redis.core.pubsub.AbstractRedisChannelMessage; -import lombok.Data; - -@Data -public class DemoNotifyMessage extends AbstractRedisChannelMessage { - - private Long userId; - private String content; - - // 可选: 自定义 channel;不重写时默认是类名 - @Override - public String getChannel() { - return "demo:notify"; - } -} -``` - -### 2.2 定义消费者 -```java -import com.njcn.rdms.framework.mq.redis.core.pubsub.AbstractRedisChannelMessageListener; -import lombok.extern.slf4j.Slf4j; -import org.springframework.stereotype.Component; - -@Slf4j -@Component -public class DemoNotifyListener extends AbstractRedisChannelMessageListener { - - @Override - public void onMessage(DemoNotifyMessage message) { - log.info("收到广播消息 userId={}, content={}", message.getUserId(), message.getContent()); - } -} -``` - -### 2.3 发送消息 -```java -import com.njcn.rdms.framework.mq.redis.core.RedisMQTemplate; -import lombok.RequiredArgsConstructor; -import org.springframework.stereotype.Service; - -@Service -@RequiredArgsConstructor -public class DemoNotifyProducer { - - private final RedisMQTemplate redisMQTemplate; - - public void send(Long userId, String content) { - redisMQTemplate.send(new DemoNotifyMessage() - .setUserId(userId) - .setContent(content)); - } -} -``` - -## 3. 使用 Redis Stream(分组消费 + ACK) -适用场景: 异步任务、可恢复消费、希望具备重投和清理机制。 - -### 3.1 定义消息 -```java -import com.njcn.rdms.framework.mq.redis.core.stream.AbstractRedisStreamMessage; -import lombok.Data; - -@Data -public class DemoTaskMessage extends AbstractRedisStreamMessage { - - private Long taskId; - private String bizType; - - // 可选: 自定义 stream key;不重写时默认是类名 - @Override - public String getStreamKey() { - return "demo:task:stream"; - } -} -``` - -### 3.2 定义消费者 -```java -import com.njcn.rdms.framework.mq.redis.core.stream.AbstractRedisStreamMessageListener; -import lombok.extern.slf4j.Slf4j; -import org.springframework.stereotype.Component; - -@Slf4j -@Component -public class DemoTaskListener extends AbstractRedisStreamMessageListener { - - @Override - public void onMessage(DemoTaskMessage message) { - log.info("消费任务 taskId={}, bizType={}", message.getTaskId(), message.getBizType()); - // 这里抛异常会导致本次消费失败,不会执行 ACK - } -} -``` - -### 3.3 发送消息 -```java -import com.njcn.rdms.framework.mq.redis.core.RedisMQTemplate; -import lombok.RequiredArgsConstructor; -import org.springframework.data.redis.connection.stream.RecordId; -import org.springframework.stereotype.Service; - -@Service -@RequiredArgsConstructor -public class DemoTaskProducer { - - private final RedisMQTemplate redisMQTemplate; - - public RecordId send(Long taskId, String bizType) { - return redisMQTemplate.send(new DemoTaskMessage() - .setTaskId(taskId) - .setBizType(bizType)); - } -} -``` - -## 4. 消息拦截器扩展(可选) -可通过实现 `RedisMessageInterceptor`,对发送/消费前后做统一处理(比如租户透传、审计埋点)。 - -```java -import com.njcn.rdms.framework.mq.redis.core.interceptor.RedisMessageInterceptor; -import com.njcn.rdms.framework.mq.redis.core.message.AbstractRedisMessage; -import org.springframework.stereotype.Component; - -@Component -public class DemoRedisMessageInterceptor implements RedisMessageInterceptor { - - @Override - public void sendMessageBefore(AbstractRedisMessage message) { - message.addHeader("source", "rdms-system"); - } -} -``` - -## 配置说明 -本模块本身几乎没有 `rdms.mq.*` 配置项,主要依赖: -1. `spring.data.redis.*`(Redis 连接) -2. `spring.application.name`(Stream consumer group 默认值) - -另外: -1. Redis Stream 要求 Redis 版本 >= 5.0 -2. 只有存在 Stream 监听器时,重投/清理定时任务才会生效 - -## 当前已知注意点 -1. `AbstractRedisStreamMessageListener` 有一个 `(streamKey, group)` 构造器,内部把 `messageType` 置空;而消费反序列化仍依赖 `messageType`。 - 建议优先使用无参构造路径(即泛型推断消息类型的默认方式)。 -2. Stream 的重投机制本质是“至少一次投递”语义,业务侧应自行保证幂等。 - -## 适用与不适用 - -适用: -1. 需要快速接入 Redis 广播/异步任务队列 -2. 希望保留基础的失败补偿和消息清理能力 -3. 希望通过抽象基类统一消息定义风格 - -不适用: -1. 对事务一致性、延迟、吞吐有强约束且需要完整 MQ 运维体系 -2. 需要复杂顺序语义、死信重试拓扑、跨机房高可用治理 diff --git a/rdms-framework/rdms-spring-boot-starter-mybatis/README.md b/rdms-framework/rdms-spring-boot-starter-mybatis/README.md deleted file mode 100644 index ebe4127..0000000 --- a/rdms-framework/rdms-spring-boot-starter-mybatis/README.md +++ /dev/null @@ -1,349 +0,0 @@ -# rdms-spring-boot-starter-mybatis - -## 模块定位 -`rdms-spring-boot-starter-mybatis` 不是一个“只把 MyBatis-Plus 引进来”的轻量 starter,而是项目里的数据库访问基础设施模块。它把下面几类能力统一收口到一个地方: - -1. 数据源与事务管理 -2. MyBatis-Plus 与 MyBatis-Plus-Join 的统一装配 -3. 多数据库兼容策略 -4. DO 基类、审计字段自动填充、逻辑删除 -5. Mapper / Wrapper 的统一编码风格 -6. 常用 TypeHandler -7. Easy-Trans 的 VO 翻译接入 - -从仓库里的实际用法看,这个模块倾向于让业务模块在 DAL 层遵循一套固定约定,而不是每个模块自己重复配置 MyBatis、分页、排序、主键策略和字段填充。 - -## 设计思路 - -## 1. 用一个 starter 统一数据库层约定 -这个模块直接依赖了: - -1. `druid-spring-boot-3-starter` -2. `mybatis-plus-spring-boot3-starter` -3. `dynamic-datasource-spring-boot3-starter` -4. `mybatis-plus-join-boot-starter` -5. `easy-trans-spring-boot-starter` - -这说明模块不是“最小封装”路线,而是把项目默认采用的数据库栈整体打包,业务模块只需要依赖这一个 starter,就能得到一致的运行时行为。 - -## 2. 优先解决“约定不一致”问题,而不是只提供工具类 -模块里最核心的不是某一个工具类,而是整套约定: - -1. `@MapperScan` 统一扫描 `${rdms.info.base-package}` -2. `BaseDO` 统一审计字段和逻辑删除字段 -3. `BaseMapperX` 统一分页、批量操作、便捷查询 -4. `LambdaQueryWrapperX` / `QueryWrapperX` 统一动态拼条件写法 -5. `IdTypeEnvironmentPostProcessor` 统一不同数据库下的主键策略 - -这套设计可以让代码生成器、业务模块和框架模块都围绕同一套 DAL 模型工作。 - -## 3. 用“自动推断”减少多数据库切换成本 -模块针对不同数据库运行场景做了几层兼容: - -1. `pom.xml` 直接预留了 MySQL、Oracle、PostgreSQL、SQL Server、达梦、人大金仓、openGauss 等驱动 -2. `IdTypeEnvironmentPostProcessor` 会根据主数据源 URL 自动判断 `DbType` -3. 如果 `mybatis-plus.global-config.db-config.id-type=NONE`,则自动改写为更适合当前数据库的 `AUTO` 或 `INPUT` -4. 同一个后处理器顺手补齐 Quartz 的 `driverDelegateClass` -5. `QueryWrapperX.limitN` 和 `MyBatisUtils.findInSet` 也在做跨数据库差异适配 - -也就是说,这个模块尽量不让业务代码关心“当前连接的是哪一种数据库”,而是把差异尽量前移到框架层。 - -## 4. 把“业务里高频重复的 Mapper 写法”沉到基类里 -`BaseMapperX` 是这个模块最重要的抽象之一。它在 `BaseMapper` 之上继续补了几类默认能力: - -1. `selectPage(...)`:直接返回项目统一的 `PageResult` -2. `selectJoinPage(...)`:把 Join 查询也纳入同一分页模型 -3. `selectOne` / `selectList` / `selectCount` 的便捷重载 -4. `insertBatch` / `updateBatch` 等批量操作 -5. 针对 SQL Server 的批量插入特殊处理 -6. `PAGE_SIZE_NONE` 时的“不分页查询”约定 - -这背后的设计取向很明确:让 Mapper 接口本身承载一部分“轻业务语义”的默认实现,减少 XML 和 Service 层重复代码。 - -## 5. 动态查询要“少写 if”,而不是把 if 搬到 Service 层 -`LambdaQueryWrapperX`、`QueryWrapperX`、`MPJLambdaWrapperX` 都提供了 `xxxIfPresent` 系列方法,例如: - -1. `likeIfPresent` -2. `eqIfPresent` -3. `inIfPresent` -4. `betweenIfPresent` - -这样 Service 或 Mapper 默认方法可以直接链式拼接条件,空值自动跳过。这样可以把“查询条件存在才拼 SQL”这件事收口到 Wrapper,而不是散落在业务代码里做大量 `if (param != null)`。 - -## 6. 基础字段治理优先于业务字段治理 -`BaseDO` 和 `DefaultDBFieldHandler` 体现了这个模块在数据治理上的几个固定要求: - -1. 所有 DO 默认带 `createTime`、`updateTime`、`creator`、`updater`、`deleted` -2. 插入和更新时自动补时间 -3. 已登录用户存在时自动补创建人和更新人 -4. 逻辑删除统一使用 `deleted`,其未删除/已删除值由 yaml 中的 `logic-not-delete-value: 0` 和 `logic-delete-value: 1` 配置 -5. `BaseDO.clean()` 用于清空前端可能误传回来的审计字段 - -这说明“审计字段一致性”被视为框架职责,而不是每个表、每个 Service 自己维护。 - -## 7. 常见字段存储形式做成透明 TypeHandler -模块内置了几类 `TypeHandler`: - -1. `EncryptTypeHandler`:字符串字段透明加解密 -2. `StringListTypeHandler` -3. `LongListTypeHandler` -4. `LongSetTypeHandler` -5. `IntegerListTypeHandler` -6. `JacksonTypeHandler` 的 `ObjectMapper` 统一注入 - -这里的取向是:数据库里允许保留“逗号分隔字符串”“JSON”“密文”等存储形式,但业务对象层尽量继续使用自然的数据结构。 - -例如: - -```java -@TableName(value = "infra_mail_account", autoResultMap = true) -public class MailAccountDO extends BaseDO { - - @TableField(typeHandler = EncryptTypeHandler.class) - private String password; - - @TableField(typeHandler = StringListTypeHandler.class) - private List toMails; -} -``` - -如果数据库里的 `password` 存的是密文、`to_mails` 存的是 `a@xx.com,b@xx.com` 这样的逗号分隔字符串, -那么业务代码里仍然可以分别按普通 `String` 和 `List` 来使用它们,字段转换交给 `TypeHandler` 处理。 - -## 8. 把“查询之后的展示翻译”也并入数据库 starter -`RdmsTranslateAutoConfiguration` 和 `TranslateUtils` 说明这个模块并不只关心“查出来”,还关心“查出来后如何转成面向前端的 VO”。 -这是一种比较明显的项目式封装方式:把 DAL 和 VO 翻译放在同一个 starter,方便后台管理类系统直接复用。 - -如果开发中需要这种能力,可以按下面的方式使用: - -1. 在 VO 里同时定义“原始值字段”和“展示字段”,例如 `userId` 和 `userName` -2. 在原始值字段上增加 `@Trans`,声明要根据哪个对象、取哪个字段、回填到哪个展示字段 -3. 查询完成后,先把 DO 转成 VO -4. 在返回前触发翻译。适合注解方式的接口可使用 `@TransMethodResult`,不适合注解方式的场景可手动调用 `TranslateUtils.translate(...)` - -示例: - -```java -public class OperateLogRespVO implements VO { - - @Trans(type = TransType.SIMPLE, target = AdminUserDO.class, fields = "nickname", ref = "userName") - private Long userId; - - private String userName; -} -``` - -```java -@TransMethodResult -public CommonResult> pageOperateLog(...) { - PageResult pageResult = operateLogService.getOperateLogPage(pageReqVO); - return success(BeanUtils.toBean(pageResult, OperateLogRespVO.class)); -} -``` - -上面的含义是:返回结果里先保留 `userId`,然后在翻译阶段根据 `userId` 查到对应用户的 `nickname`,再回填到 `userName`。 -如果不是常规接口返回场景,例如导出 Excel,可以在转换为 VO 后手动调用: - -```java -TranslateUtils.translate(BeanUtils.toBean(list, OperateLogRespVO.class)); -``` - -## 自动装配链路 - -## 1. Spring Boot 自动配置入口 -`META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports` - -1. `com.njcn.rdms.framework.datasource.config.RdmsDataSourceAutoConfiguration` -2. `com.njcn.rdms.framework.mybatis.config.RdmsMybatisAutoConfiguration` -3. `com.njcn.rdms.framework.translate.config.RdmsTranslateAutoConfiguration` - -## 2. EnvironmentPostProcessor 入口 -`META-INF/spring.factories` - -1. `com.njcn.rdms.framework.mybatis.config.IdTypeEnvironmentPostProcessor` - -这意味着模块启动时会先做环境预处理,再进入正常的自动配置流程。 - -## 核心组件 - -| 组件 | 作用 | -| --- | --- | -| `RdmsDataSourceAutoConfiguration` | 开启事务管理,并在启用 Druid 监控页时注册过滤器去掉广告脚本 | -| `RdmsMybatisAutoConfiguration` | 提前于 MyBatis-Plus 完成装配,统一 `@MapperScan`、分页插件、字段填充、主键生成器、`JacksonTypeHandler` | -| `IdTypeEnvironmentPostProcessor` | 根据主数据源 URL 推断数据库类型,自动设置 `id-type` 和 Quartz Delegate | -| `BaseDO` | 统一 DO 基类,内置审计字段、逻辑删除和 Easy-Trans 兼容处理 | -| `DefaultDBFieldHandler` | 自动填充创建人、更新人、创建时间、更新时间 | -| `BaseMapperX` | 统一分页、Join 分页、批量操作、便捷查询 | -| `LambdaQueryWrapperX` / `QueryWrapperX` / `MPJLambdaWrapperX` | 提供 `IfPresent` 风格的条件拼装能力 | -| `JdbcUtils` / `MyBatisUtils` | 统一数据库类型探测、分页构造、排序拼装、跨库 SQL 片段 | -| `EncryptTypeHandler` 等 | 处理密文、列表、集合等特殊字段映射 | -| `TranslateUtils` | 在不能用注解自动翻译时,手动触发 Easy-Trans 翻译 | - -## 模块特征 -从实现细节和业务模块的用法看,这个 starter 有几个比较明确的特征: - -1. 它承担“项目默认 DAL 规范”的角色,而不是一个可随意裁剪的通用组件。 -2. 它偏向“约定优于配置”,例如主键策略、Mapper 扫描、分页结果、审计字段都由框架先定好。 -3. 它存在一定程度的强绑定,例如绑定动态数据源、Druid、Easy-Trans、Security 上下文,以换取更少的样板代码。 -4. 它在多数据库兼容上投入较多,适合数据库切换、国产库适配或私有化部署场景。 -5. 它优先抽象后台管理系统里高频出现的分页、筛选、列表页、字典翻译、逻辑删除、审计字段等能力。 - -## 如何使用 - -## 1. 引入依赖 -通常业务模块直接依赖: - -```xml - - com.njcn - rdms-spring-boot-starter-mybatis - -``` - -如果项目需要自动填充 `creator`、`updater`,运行时还应提供安全模块里的登录上下文能力。 - -## 2. 基础配置 -最小可工作的配置重点有四个: - -1. `rdms.info.base-package` -2. `spring.datasource.dynamic.primary` -3. `spring.datasource.dynamic.datasource..url` -4. `mybatis-plus.global-config.db-config.id-type` - -示例: - -```yaml -rdms: - info: - base-package: com.njcn.rdms.module.system - -spring: - datasource: - dynamic: - primary: master - datasource: - master: - url: jdbc:mysql://127.0.0.1:13306/rdms?useSSL=false&serverTimezone=Asia/Shanghai - username: root - password: 123456 - -mybatis-plus: - global-config: - db-config: - id-type: NONE - logic-delete-value: 1 - logic-not-delete-value: 0 - type-aliases-package: ${rdms.info.base-package}.dal.dataobject - encryptor: - password: your-16-bytes-key - -mybatis-plus-join: - banner: false - -easy-trans: - is-enable-global: false -``` - -说明: - -1. `id-type: NONE` 是这里推荐的“自动判断模式” -2. 如果没有配置主数据源 URL,`IdTypeEnvironmentPostProcessor` 就无法自动判断数据库类型 -3. `encryptor.password` 只在使用 `EncryptTypeHandler` 时需要 - -## 3. DO 写法 -推荐让 DO 继承 `BaseDO`,并在需要时启用 `autoResultMap = true`: - -```java -@TableName(value = "infra_data_source_config", autoResultMap = true) -@KeySequence("infra_data_source_config_seq") -public class DataSourceConfigDO extends BaseDO { - - private Long id; - private String name; - private String url; - private String username; - - @TableField(typeHandler = EncryptTypeHandler.class) - private String password; -} -``` - -如果字段在库里按逗号分隔字符串保存,也可以直接挂列表型 `TypeHandler`: - -```java -@TableField(typeHandler = StringListTypeHandler.class) -private List toMails; -``` - -## 4. Mapper 写法 -推荐所有 Mapper 继承 `BaseMapperX`,把分页和条件拼装直接写在默认方法里: - -```java -@Mapper -public interface AdminUserMapper extends BaseMapperX { - - default PageResult selectPage(UserPageReqVO reqVO, - Collection deptIds, - Collection userIds) { - return selectPage(reqVO, new LambdaQueryWrapperX() - .likeIfPresent(AdminUserDO::getUsername, reqVO.getUsername()) - .likeIfPresent(AdminUserDO::getMobile, reqVO.getMobile()) - .eqIfPresent(AdminUserDO::getStatus, reqVO.getStatus()) - .betweenIfPresent(AdminUserDO::getCreateTime, reqVO.getCreateTime()) - .inIfPresent(AdminUserDO::getDeptId, deptIds) - .inIfPresent(AdminUserDO::getId, userIds) - .orderByDesc(AdminUserDO::getId)); - } -} -``` - -如果需要跨库兼容的 `limit n`,当前实现建议使用 `QueryWrapperX`: - -```java -return selectList(new QueryWrapperX() - .eq("user_id", userId) - .eq("user_type", userType) - .eq("read_status", false) - .orderByDesc("id") - .limitN(size)); -``` - -## 5. 手动触发 VO 翻译 -当场景不适合用注解自动翻译时,可以手动调用: - -```java -List result = TranslateUtils.translate(list); -``` - -## 典型开发流程 -在这个 starter 的设计下,比较顺畅的开发流程基本是: - -1. 配置好多数据源和 `rdms.info.base-package` -2. DO 继承 `BaseDO` -3. Mapper 继承 `BaseMapperX` -4. 查询条件优先使用 `WrapperX` 系列拼装 -5. 分页统一返回 `PageResult` -6. 特殊字段优先通过 `TypeHandler` 做透明映射 -7. VO 展示转换需要字典或名称翻译时再接 Easy-Trans - -## 注意事项 - -1. `IdTypeEnvironmentPostProcessor` 依赖 `spring.datasource.dynamic.primary` 和对应数据源的 `url`,否则无法自动识别数据库类型。 -2. 当 `id-type` 被识别为 `INPUT` 时,自动配置还会注册对应数据库的 `IKeyGenerator`。这类场景下实体通常还需要正确声明 `@KeySequence`。 -3. `DefaultDBFieldHandler` 会调用安全上下文工具获取登录用户。如果运行时没有对应安全能力,创建人和更新人自动填充就不能按预期工作。 -4. `EncryptTypeHandler` 依赖 `mybatis-plus.encryptor.password`。密钥一旦更换,历史密文可能无法解密,生产环境不应硬编码。 -5. `easy-trans.is-enable-global` 在示例配置里默认关闭,说明默认没有把全局响应翻译作为高优先级能力。 -6. `QueryWrapperX.limitN` 是跨数据库兼容封装,但它仍然是基于数据库方言分支处理,不适合无限扩展到所有数据库。 -7. 这个模块的职责边界偏“大而全”,适合本项目统一使用;如果要抽成通用开源组件,需要先拆掉对动态数据源、安全上下文、Easy-Trans 的强耦合。 - -## 总结 -这个模块的核心价值,不是简单提供几个 MyBatis 工具类,而是把数据库层的共性问题一次性沉到框架层: - -1. 启动时怎么自动装配 -2. 多数据库怎么少改代码 -3. 分页、排序、条件查询怎么统一写 -4. 审计字段和逻辑删除怎么保持一致 -5. 特殊字段怎么透明映射 -6. 查询结果怎么更顺滑地进入 VO 展示层 - -如果把它当成一个“带强约束的项目级 DAL 基座”去理解,这个模块的设计就会非常清晰。 diff --git a/rdms-framework/rdms-spring-boot-starter-protection/README.md b/rdms-framework/rdms-spring-boot-starter-protection/README.md deleted file mode 100644 index 9c486b2..0000000 --- a/rdms-framework/rdms-spring-boot-starter-protection/README.md +++ /dev/null @@ -1,373 +0,0 @@ -# rdms-spring-boot-starter-protection - -## 模块定位 -`rdms-spring-boot-starter-protection` 用于提供一组服务保护能力,减少业务代码里重复编写“防重复提交、限流、分布式锁、接口签名校验”这类横切逻辑。 - -当前模块实际包含四块能力: - -1. 幂等:防止同一请求在短时间内被重复执行,例如重复提交、重复点击 -2. 分布式锁:在并发场景下保证同一业务动作同一时间只有一个线程或节点执行 -3. 限流:限制单位时间内的访问次数,避免接口被刷爆或高频调用 -4. HTTP API 签名:校验调用方身份、请求时效和随机数,防止伪造请求和重放攻击 - -它的目标不是提供复杂的治理平台,而是把常见保护能力做成可声明、可复用、可分布式生效的基础设施。 - -## 设计思路 - -## 1. 用注解声明保护规则,用 AOP 统一执行 -模块里的幂等、限流、签名校验都不是要求业务手写 Redis 判断逻辑,而是通过注解声明规则,再由 AOP 统一拦截执行: - -1. `@Idempotent` -2. `@RateLimiter` -3. `@ApiSignature` - -这样业务代码只负责说明“这个方法需要什么保护”,具体的 Redis Key 生成、重复请求判断、限流判断、签名验证由框架层统一处理。 - -## 2. 把状态统一放到 Redis,面向分布式部署 -这个模块的保护能力不是单机内存级别,而是默认面向多实例部署场景: - -1. 幂等使用 `StringRedisTemplate` -2. API 签名防重放使用 `StringRedisTemplate` -3. 限流使用 `Redisson` 的 `RRateLimiter` -4. 分布式锁接入 `lock4j-redisson-spring-boot-starter` - -这意味着它的核心价值不是“本机防抖”,而是“多个节点共享保护状态”。 - -## 3. 单一能力单独建模,避免职责混杂 -从实现上看,模块有一个比较明确的边界划分: - -1. 幂等用于“同一请求短时间内只允许执行一次” -2. 分布式锁用于“并发竞争下只允许一个线程/节点进入” -3. 限流用于“单位时间窗口内限制请求次数” -4. API 签名用于“校验调用方身份、请求时效和防重放” - -例如幂等组件没有扩展成“成功后立即删 Key”的锁语义,而是把这类能力交给 Lock4j 处理。 - -区分这两类能力时,可以抓住一个核心点: - -1. 幂等控制的是“短时间内不要重复执行同一类请求” -2. 分布式锁控制的是“同一时间谁有资格进入临界区执行” - -例如: - -1. 用户连续点击两次“提交订单”,更适合用幂等 -2. 两个线程同时刷新同一份缓存,只允许一个线程进入执行,更适合用分布式锁 - -一个常见误区是把长耗时任务也交给幂等处理。假设幂等窗口配置为 3 秒,但某个任务实际执行了 20 秒: - -1. 第一个请求在第 0 秒进入 -2. 幂等 Key 在第 3 秒过期 -3. 第二个请求在第 5 秒再次进入 -4. 此时第一个任务还没执行完,但第二个请求已经有机会再次执行 - -所以幂等更偏“防重复请求”,而不是“在整个执行期间控制线程执行权”。如果真正关心的是执行过程中的互斥,应优先使用分布式锁。 - -## 4. 用 KeyResolver 抽象不同粒度的保护范围 -幂等和限流都没有把 Key 生成规则写死,而是抽成了 `KeyResolver`: - -1. 默认级别:方法名 + 参数 -2. 用户级别:方法名 + 参数 + 当前用户 -3. IP 级别:方法名 + 参数 + 客户端 IP -4. 节点级别:方法名 + 参数 + 当前服务节点 -5. 表达式级别:通过 SpEL 自定义 Key - -这样模块能在“全局、按用户、按 IP、按节点、按业务字段”之间切换,而不是只能支持一种固定粒度。 - -## 5. 自动装配按功能块拆分 -自动配置入口见: -`META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports` - -当前注册了四个自动配置类: - -1. `RdmsIdempotentConfiguration` -2. `RdmsLock4jConfiguration` -3. `RdmsRateLimiterConfiguration` -4. `RdmsApiSignatureAutoConfiguration` - -这说明模块是按能力块拆分装配的,而不是堆在一个总配置类里。 - -## 功能说明 - -## 1. 幂等 -核心类: - -1. `Idempotent` -2. `IdempotentAspect` -3. `IdempotentRedisDAO` - -实现方式: - -1. AOP 拦截带 `@Idempotent` 的方法 -2. 通过 `IdempotentKeyResolver` 解析 Redis Key -3. 调用 `SETNX + EXPIRE` 语义的 `setIfAbsent` -4. 锁定失败时抛出“重复请求”异常 -5. 业务异常时可按配置删除 Key,允许后续重试 - -适合场景: - -1. 防止用户双击按钮 -2. 防止表单重复提交 -3. 防止短时间内重复触发同一个业务动作 - -典型案例: - -1. 创建工单时,前端连续点击两次“提交”,只允许创建一次 -2. 导入任务提交后,用户刷新页面再次点击“开始导入”,短时间内不允许重复发起 -3. 审批流提交节点时,浏览器因为网络抖动自动重发请求,后端只执行一次 - -边界说明: - -1. 这里的幂等更偏“短时间窗口防重复请求”,不适合作为支付、退款这类长期业务幂等的唯一方案 -2. 对于支付回调、订单状态推进这类场景,更可靠的做法仍然是基于业务唯一号和持久化状态做幂等判断,必要时再配合分布式锁 - -可选 KeyResolver: - -1. `DefaultIdempotentKeyResolver` -2. `UserIdempotentKeyResolver` -3. `ExpressionIdempotentKeyResolver` - -示例: - -```java -@Idempotent(timeout = 3, message = "请勿重复提交") -public Long createOrder(OrderCreateReqVO reqVO) { - return orderService.createOrder(reqVO); -} -``` - -如果需要按某个参数做幂等,可以使用表达式: - -```java -@Idempotent( - keyResolver = ExpressionIdempotentKeyResolver.class, - keyArg = "#reqVO.no", - timeout = 10 -) -public void submit(OrderReqVO reqVO) { -} -``` - -如果接口已经完成登录鉴权,也可以按“当前登录用户 + 接口参数”做幂等: - -```java -@Idempotent( - keyResolver = UserIdempotentKeyResolver.class, - timeout = 3, - message = "请勿重复提交" -) -public CommonResult createOrder(OrderCreateReqVO reqVO) { - return success(orderService.createOrder(reqVO)); -} -``` - -这种方式不要求把 `userId` 显式放在请求参数里,而是从当前登录上下文中获取用户信息,适合“同一个用户短时间内不能重复提交同一类请求”的场景。 - -## 2. 限流 -核心类: - -1. `RateLimiter` -2. `RateLimiterAspect` -3. `RateLimiterRedisDAO` - -实现方式: - -1. AOP 在方法执行前拦截 -2. 通过 `RateLimiterKeyResolver` 解析限流 Key -3. 使用 Redisson 的 `RRateLimiter` 设置速率 -4. 超过限制时抛出“请求过于频繁”异常 - -适合场景: - -1. 接口防刷 -2. 高频查询保护 -3. 短时间内限制短信、验证码、导出等高成本操作 -4. 对开放接口或公网接口做基础流量保护 - -典型案例: - -1. 短信验证码接口限制“1 分钟内最多发送 1 次” -2. 导出 Excel 接口限制“10 分钟内最多导出 3 次” -3. 登录接口按 IP 限制访问频率,防止暴力尝试 -4. 某个开放查询接口按调用方或用户维度限制 QPS,避免被刷爆 - -可选 KeyResolver: - -1. `DefaultRateLimiterKeyResolver` -2. `UserRateLimiterKeyResolver` -3. `ClientIpRateLimiterKeyResolver` -4. `ServerNodeRateLimiterKeyResolver` -5. `ExpressionRateLimiterKeyResolver` - -示例: - -```java -@RateLimiter(count = 5, time = 1, timeUnit = TimeUnit.MINUTES, message = "请求过于频繁") -public CommonResult sendSmsCode(String mobile) { - return success(true); -} -``` - -如果希望按用户限流: - -```java -@RateLimiter( - count = 10, - time = 1, - timeUnit = TimeUnit.MINUTES, - keyResolver = UserRateLimiterKeyResolver.class -) -public PageResult pageMyOrders(OrderPageReqVO reqVO) { - return orderService.pageMyOrders(reqVO); -} -``` - -## 3. HTTP API 签名 -核心类: - -1. `ApiSignature` -2. `ApiSignatureAspect` -3. `ApiSignatureRedisDAO` - -实现方式: - -1. 从请求头中读取 `appId`、`timestamp`、`nonce`、`sign` -2. 校验时间戳是否过期 -3. 校验 `nonce` 是否已使用,防止重放 -4. 根据 `appId` 从 Redis 中获取 `appSecret` -5. 按“请求参数 + 请求体 + 请求头 + 密钥”构建签名字符串 -6. 使用 `SHA-256` 计算服务端签名并比对 - -适合场景: - -1. 开放接口 -2. 系统间调用 -3. 对请求来源和重放攻击有要求的 HTTP 接口 - -典型案例: - -1. 第三方系统调用内部开放 API 时,使用 `appId + appSecret` 做签名校验 -2. 支付平台、供应商平台、合作方系统调用回调接口时,先校验签名再进入业务处理 -3. 对外提供的 B2B 接口要求请求在 60 秒内有效,并且 `nonce` 只能使用一次 - -示例: - -```java -@ApiSignature(timeout = 60) -public CommonResult callback() { - return success("ok"); -} -``` - -当前切面是按 `@annotation(signature)` 拦截的,因此实操上应优先加在方法上。 - -## 4. 分布式锁 -核心类: - -1. `RdmsLock4jConfiguration` -2. `DefaultLockFailureStrategy` - -这部分没有重复实现分布式锁,而是接入 Lock4j,并补了一层默认失败策略: - -1. 如果类路径存在 `Lock4j`,则启用配置 -2. 在获取锁失败时,统一抛出项目内的 `ServiceException` -3. 保持锁失败时的错误处理风格一致 - -适合场景: - -1. 防止同一业务动作并发执行 -2. 串行化关键资源操作 -3. 抢占式处理任务 - -典型案例: - -1. 同一个用户同时触发两次“刷新缓存”,只允许一个线程进入 -2. 定时任务集群部署时,同一时间只允许一个节点执行清理任务 -3. 库存扣减、状态迁移、批量结算这类关键动作,在并发场景下需要串行处理 - -示例: - -```java -@Lock4j(keys = "#userId", expire = 30000, acquireTimeout = 1000) -public void refreshUserCache(Long userId) { -} -``` - -## 自动装配链路 - -## 1. Spring Boot 自动配置入口 -`META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports` - -1. `com.njcn.rdms.framework.idempotent.config.RdmsIdempotentConfiguration` -2. `com.njcn.rdms.framework.lock4j.config.RdmsLock4jConfiguration` -3. `com.njcn.rdms.framework.ratelimiter.config.RdmsRateLimiterConfiguration` -4. `com.njcn.rdms.framework.signature.config.RdmsApiSignatureAutoConfiguration` - -## 2. 各配置类负责的事情 - -| 配置类 | 作用 | -| --- | --- | -| `RdmsIdempotentConfiguration` | 注册幂等切面、幂等 Redis DAO、幂等 KeyResolver | -| `RdmsLock4jConfiguration` | 在 Lock4j 存在时注册默认加锁失败策略 | -| `RdmsRateLimiterConfiguration` | 注册限流切面、限流 Redis DAO、限流 KeyResolver | -| `RdmsApiSignatureAutoConfiguration` | 注册 API 签名切面、签名 Redis DAO | - -## 如何使用 - -## 1. 引入依赖 -业务模块通常直接依赖: - -```xml - - com.njcn - rdms-spring-boot-starter-protection - -``` - -## 2. 基础前提 -这个模块依赖 Redis,因此至少需要可用的 Redis 配置。 - -其中: - -1. 幂等依赖 `StringRedisTemplate` -2. API 签名依赖 `StringRedisTemplate` -3. 限流依赖 `RedissonClient` -4. 分布式锁依赖 Lock4j + Redisson - -## 3. 选择合适的保护手段 - -1. 防重复提交,优先使用 `@Idempotent` -2. 控制访问频率,优先使用 `@RateLimiter` -3. 控制并发进入,优先使用 Lock4j -4. 保护开放接口,优先使用 `@ApiSignature` - -不要把它们混成一类能力: - -1. 幂等不等于分布式锁 -2. 限流不等于幂等 -3. API 签名不等于权限校验 - -## 4. KeyResolver 的选择建议 - -1. 默认对全局请求做保护,使用默认 Resolver -2. 需要按登录用户隔离保护,使用 User Resolver -3. 需要按 IP 控制访问,使用 ClientIp Resolver -4. 需要按业务字段控制粒度,使用 Expression Resolver - -## 注意事项 - -1. 幂等的 `timeout` 只是保护窗口,不是业务执行超时时间。如果方法执行时间长于这个窗口,后续请求仍可能进入。 -2. 幂等在异常时默认删除 Key,这是为了允许业务失败后重新提交;如果追求的是“执行期间只允许一个线程进入”,更适合使用分布式锁。 -3. 限流底层使用 Redisson 的 `RRateLimiter`,运行环境需要有 `RedissonClient`。 -4. API 签名依赖 Redis 中预先存在 `appId -> appSecret` 的映射,否则签名校验无法通过。 -5. API 签名当前实操上应加在方法上,避免把类级注解误认为一定会生效。 -6. 这个模块的保护能力都是方法级横切逻辑,适合放在 Controller 或 Service 的明确边界上使用,不适合到处滥加。 - -## 总结 -这个模块的核心价值,是把服务保护相关的高频横切问题统一沉到框架层: - -1. 如何防止重复提交 -2. 如何限制单位时间的访问频率 -3. 如何在分布式环境下串行化关键操作 -4. 如何校验开放接口的请求签名和防重放 - -如果把它理解成一个“请求保护与并发保护能力集合”的 starter,这个模块的设计会比较清晰。 diff --git a/rdms-framework/rdms-spring-boot-starter-redis/README.md b/rdms-framework/rdms-spring-boot-starter-redis/README.md deleted file mode 100644 index a795085..0000000 --- a/rdms-framework/rdms-spring-boot-starter-redis/README.md +++ /dev/null @@ -1,389 +0,0 @@ -# rdms-spring-boot-starter-redis - -## 模块定位 -`rdms-spring-boot-starter-redis` 不是单纯把 Redis 依赖引进来,而是把项目里常用的 Redis 使用约定统一收口。 - -当前模块主要承担三类职责: - -1. 统一 `RedisTemplate` 的序列化策略 -2. 统一 Spring Cache 基于 Redis 的实现方式 -3. 引入 Redisson,为分布式锁、限流等上层模块提供基础能力 - -它的重点不是封装大量 Redis 工具类,而是先把“Redis 怎么接入、怎么序列化、缓存怎么配置”这些基础设施统一好。 - -## 设计思路 - -## 1. 先统一 RedisTemplate,再让上层模块复用 -模块通过 `RdmsRedisAutoConfiguration` 注册了统一的 `RedisTemplate`,核心约定是: - -1. Key 使用字符串序列化 -2. Hash Key 使用字符串序列化 -3. Value 使用 JSON 序列化 -4. Hash Value 使用 JSON 序列化 - -这样可以避免各模块各自定义一套 `RedisTemplate`,导致同一个 Redis 里出现不同的序列化格式。 - -## 2. 优先解决“可读性和兼容性”问题 -这个模块没有采用 JDK 默认序列化,而是直接使用 JSON 序列化。这样做的直接收益是: - -1. Redis 中的数据结构更容易观察和排查 -2. 避免 JDK 序列化带来的可读性差和兼容性问题 -3. 方便不同模块围绕同一种序列化格式工作 - -同时,它还额外处理了 `LocalDateTime` 的序列化问题,避免 Java 时间类型在 Redis 中读写异常。 - -## 3. 把 Cache 也纳入统一约定 -模块不只提供 `RedisTemplate`,还把 Spring Cache 一起接入 Redis: - -1. 开启 `@EnableCaching` -2. 统一 `RedisCacheConfiguration` -3. 统一 `RedisCacheManager` -4. 扩展支持按缓存名声明过期时间 - -这说明模块的目标不是“给你 Redis 客户端自己玩”,而是先把项目里高频使用的缓存场景标准化。 - -## 4. 通过小扩展解决 Spring Cache 默认能力不够用的问题 -模块里的 `TimeoutRedisCacheManager` 做了一个很实用的扩展: - -1. 如果缓存名是普通格式,例如 `user` -2. 则按全局默认 TTL 工作 -3. 如果缓存名写成 `user#10m` -4. 则这个缓存自动按 10 分钟过期 - -这相当于把“单个缓存项的过期时间”从配置文件和自定义代码里,收口到了缓存名约定里。 - -## 5. 给上层能力模块提供 Redis 和 Redisson 基座 -这个模块本身不直接实现分布式锁、限流、幂等,但它提供了这些上层能力所依赖的基础能力: - -1. `StringRedisTemplate` -2. `RedisTemplate` -3. `RedissonClient` -4. 基于 Redis 的 Spring Cache - -因此像 `rdms-spring-boot-starter-protection`、`rdms-spring-boot-starter-mq` 这样的模块,都可以把 Redis 能力建立在这个 starter 之上。 - -## 自动装配链路 - -## 1. Spring Boot 自动配置入口 -`META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports` - -1. `com.njcn.rdms.framework.redis.config.RdmsRedisAutoConfiguration` -2. `com.njcn.rdms.framework.redis.config.RdmsCacheAutoConfiguration` - -## 2. 各配置类负责的事情 - -| 配置类 | 作用 | -| --- | --- | -| `RdmsRedisAutoConfiguration` | 注册统一序列化规则的 `RedisTemplate` | -| `RdmsCacheAutoConfiguration` | 开启 Spring Cache,注册 `RedisCacheConfiguration` 和自定义 `RedisCacheManager` | - -## 核心组件 - -| 组件 | 作用 | -| --- | --- | -| `RdmsRedisAutoConfiguration` | 提供统一 JSON 序列化的 RedisTemplate,并处理 `LocalDateTime` 序列化 | -| `RdmsCacheAutoConfiguration` | 统一 Redis Cache 前缀、TTL、空值缓存策略和 CacheManager | -| `RdmsCacheProperties` | 提供 `rdms.cache.*` 配置项,目前包含 `redis-scan-batch-size` | -| `TimeoutRedisCacheManager` | 支持通过 `cacheName#ttl` 的形式为单个缓存声明过期时间 | - -## 功能说明 - -## 1. 统一 RedisTemplate 序列化 -核心类: - -1. `RdmsRedisAutoConfiguration` - -实现方式: - -1. `RedisTemplate` 统一由框架注册 -2. Key/Hash Key 使用字符串序列化 -3. Value/Hash Value 使用 JSON 序列化 -4. 通过 `JavaTimeModule` 兼容 `LocalDateTime` - -适合场景: - -1. 业务模块直接注入 `RedisTemplate` 使用 -2. 需要缓存对象、列表、Map 等结构 -3. 希望 Redis 中的数据具有一定可读性 - -示例: - -```java -@Resource -private RedisTemplate redisTemplate; - -public void saveUser(Long userId, UserRespVO user) { - redisTemplate.opsForValue().set("user:" + userId, user); -} -``` - -## 2. 统一 Spring Cache 基于 Redis 的实现 -核心类: - -1. `RdmsCacheAutoConfiguration` -2. `RdmsCacheProperties` - -实现方式: - -1. 开启 `@EnableCaching` -2. 创建统一的 `RedisCacheConfiguration` -3. 按 `spring.cache.redis.*` 读取默认 TTL、前缀、空值缓存等配置 -4. 使用 JSON 序列化缓存值 -5. 使用自定义 `RedisCacheManager` - -这个模块还额外做了一个约定: - -1. Cache Key 前缀使用单个 `:`,而不是默认的 `::` -2. 这样 Redis 中的 Key 更紧凑,也更方便在可视化工具中查看 - -Spring Cache 在这个模块里的作用,可以理解为: - -1. 让“方法查询结果缓存”变成注解式能力,而不是每次都手写 `RedisTemplate` -2. 让缓存的写入、读取、失效围绕业务方法本身声明 -3. 让“查库 -> 放缓存 -> 下次命中缓存”这类标准流程交给框架处理 - -它更适合下面这类场景: - -1. 根据主键查询角色、菜单、模板、配置 -2. 根据某个唯一业务字段查询对象,例如 `clientId`、`code` -3. 根据某个参数查询相对稳定的数据,例如部门子节点列表、权限对应菜单列表 - -这类场景通常有几个共同点: - -1. 输入参数比较明确 -2. 返回结果可以直接缓存 -3. 数据更新时可以找到明确的缓存失效点 - -典型流程是: - -1. 在查询方法上加 `@Cacheable` -2. 第一次执行时,方法正常查数据库或远程接口 -3. 返回结果自动写入 Redis -4. 后续相同参数调用时,直接命中缓存,不再进入方法体 -5. 在更新、删除方法上通过 `@CacheEvict` 清理对应缓存 - -和直接使用 `RedisTemplate` 相比,Spring Cache 的优势主要是: - -1. 不需要手写 `get -> 判空 -> set -> expire` 这类模板代码 -2. 缓存逻辑和业务方法绑定更紧,读代码时更容易看出哪里有缓存 -3. 适合“查询结果缓存”这种标准模式 - -当前项目里,权限、角色、菜单、模板这类数据就大量采用这种方式;而像 Token、验证码这类更像“直接把业务对象存到 Redis”的场景,则更适合单独写 RedisDAO。 - -示例: - -```java -@Cacheable(cacheNames = "user", key = "#id") -public UserRespVO getUser(Long id) { - return userApi.getUser(id); -} -``` - -如果某个写操作会影响缓存,需要配套清理缓存: - -```java -@CacheEvict(cacheNames = "user", key = "#id") -public void deleteUser(Long id) { - userMapper.deleteById(id); -} -``` - -在当前项目语境下,更贴近实际的使用案例包括: - -1. 根据角色 ID 查询角色信息,并在角色更新或删除时清理缓存 -2. 根据权限标识查询菜单 ID 列表,并在菜单新增、修改、删除时清理缓存 -3. 根据模板编码查询通知模板、邮件模板,并在模板变更时清理缓存 - -## 3. 支持按缓存名自定义过期时间 -核心类: - -1. `TimeoutRedisCacheManager` - -实现方式: - -1. 解析 `@Cacheable(cacheNames = "...")` 中的缓存名 -2. 如果命中 `cacheName#ttl` 格式,则为该缓存单独设置 TTL -3. 支持的单位有: - - `d` 天 - - `h` 小时 - - `m` 分钟 - - `s` 秒 -4. 如果不带单位,默认按秒处理 - -示例: - -```java -@Cacheable(cacheNames = "user#10m", key = "#id") -public UserRespVO getUser(Long id) { - return userApi.getUser(id); -} -``` - -说明: - -1. `#10m` 只用于声明这个缓存的过期时间是 10 分钟 -2. 它不会作为最终 Redis key 的一部分长期保留 -3. 如果 `id = 1` -4. 且 `spring.cache.redis.key-prefix = rdms` -5. 那么实际 Redis key 一般形如 `rdms:user:1` -6. 其中 `user` 是缓存名,`1` 是 `key = "#id"` 计算出的缓存 Key - -```java -@Cacheable(cacheNames = "config#30s", key = "#key") -public ConfigRespVO getConfig(String key) { - return configService.getConfig(key); -} -``` - -说明: - -1. 如果 `key = "sms"` -2. 且 `spring.cache.redis.key-prefix = rdms` -3. 那么实际 Redis key 一般形如 `rdms:config:sms` -4. `#30s` 同样只影响 TTL,不直接体现在最终 key 名上 - -这种方式适合“绝大多数缓存共用默认 TTL,少数缓存按场景单独缩短或延长过期时间”的场景。 - -补充说明: - -1. `cacheNames = "user#10m"` 中的 `user` 是缓存名,`10m` 是 TTL 描述 -2. `key = "#id"` 或 `key = "#key"` 才决定具体缓存项的业务 Key -3. 如果配置了 `spring.cache.redis.key-prefix = rdms`,最终 Redis key 通常是 `rdms:user:1`、`rdms:config:sms` 这种形式 -4. `#10m`、`#30s` 这类 TTL 后缀用于创建缓存时解析过期时间,不应理解成最终 key 名的一部分 - -## 4. 提供 Redisson 基础能力 -从依赖上看,模块直接引入了 `redisson-spring-boot-starter`,因此除了 Spring Data Redis 以外,也会把 `RedissonClient` 这类能力带入项目。 - -这部分的直接收益是: - -1. 分布式锁能力可被上层模块直接复用 -2. 限流等基于 Redisson 的能力可直接建立在此模块之上 -3. 项目不需要在多个模块里重复引入 Redisson - -## 如何使用 - -## 1. 引入依赖 -业务模块通常直接依赖: - -```xml - - com.njcn - rdms-spring-boot-starter-redis - -``` - -## 2. 基础配置 -最常见的配置重点包括: - -1. `spring.data.redis.*` -2. `spring.cache.redis.*` -3. `rdms.cache.redis-scan-batch-size` - -示例: - -```yaml -spring: - data: - redis: - host: 127.0.0.1 # Redis 地址 - port: 6379 # Redis 端口 - database: 0 # 使用的 Redis 库编号 - - cache: - type: REDIS # Spring Cache 底层使用 Redis - redis: - time-to-live: 1h # 全局默认缓存过期时间 - cache-null-values: false # 是否缓存空值,通常建议关闭 - use-key-prefix: true # 是否启用缓存 Key 前缀 - key-prefix: rdms # 缓存 Key 的统一前缀 - -rdms: - cache: - redis-scan-batch-size: 100 # Redis CacheWriter 使用 SCAN 批量处理时的单次扫描数量 -``` - -说明: - -1. `spring.cache.redis.time-to-live` 是全局默认缓存过期时间 -2. `cacheName#ttl` 可以覆盖单个缓存的 TTL -3. `rdms.cache.redis-scan-batch-size` 用于控制 Redis CacheWriter 使用 `SCAN` 批量处理时的单次返回数量 - -## 3. 直接使用 RedisTemplate -如果场景不适合 Spring Cache,例如: - -1. 需要操作复杂数据结构 -2. 需要精细控制过期时间 -3. 需要手动删除、递增、集合操作 - -可以直接注入 `RedisTemplate` 或 `StringRedisTemplate` 使用。 - -## 4. 优先使用 Spring Cache 的场景 -如果场景是: - -1. 查询结果缓存 -2. 参数到结果的简单映射 -3. 希望通过注解快速声明缓存 - -则优先使用 `@Cacheable`、`@CacheEvict`、`@CachePut`,并利用统一的 Redis Cache 约定。 - -这三个注解的典型使用场景可以这样区分: - -1. `@Cacheable` - 用于“先查缓存,缓存没有再执行方法,并把结果写入缓存”。 - 适合读操作,例如“根据 ID 查询角色”“根据编码查询模板”。 - -```java -@Cacheable(cacheNames = "role", key = "#id", unless = "#result == null") -public RoleDO getRoleFromCache(Long id) { - return roleMapper.selectById(id); -} -``` - -2. `@CacheEvict` - 用于“执行方法后删除缓存”,避免更新或删除数据后缓存还是旧值。 - 适合写操作,例如“更新角色后清理角色缓存”“删除模板后清理模板缓存”。 - -```java -@CacheEvict(cacheNames = "role", key = "#updateReqVO.id") -public void updateRole(RoleSaveReqVO updateReqVO) { - roleMapper.updateById(BeanUtils.toBean(updateReqVO, RoleDO.class)); -} -``` - -3. `@CachePut` - 用于“执行方法后,把方法返回值直接写回缓存”。 - 适合更新后希望立即刷新缓存,而不是简单删除缓存的场景。 - -```java -@CachePut(cacheNames = "role", key = "#result.id") -public RoleDO updateRoleAndReturn(RoleSaveReqVO updateReqVO) { - RoleDO role = BeanUtils.toBean(updateReqVO, RoleDO.class); - roleMapper.updateById(role); - return role; -} -``` - -可以把它们简单记成: - -1. `@Cacheable`:查的时候用,没有缓存才执行方法 -2. `@CacheEvict`:改或删的时候用,执行后清缓存 -3. `@CachePut`:改的时候用,执行后直接刷新缓存 - -## 注意事项 - -1. `RedisTemplate` 使用 JSON 序列化,适合对象缓存,但跨版本变更字段时仍应关注兼容性。 -2. `LocalDateTime` 已做序列化兼容处理,但复杂对象结构仍应关注 Jackson 序列化结果。 -3. `cacheName#ttl` 是这个模块扩展出来的约定,不是 Spring Cache 默认语法。 -4. 通过 `cacheName#ttl` 设置过期时间时,TTL 是针对这个缓存名生效的,不是针对单条 Key 动态计算。 -5. `RdmsCacheAutoConfiguration` 将 Cache Key 前缀改成了单冒号 `:` 风格,如果已有依赖默认 `::` 的脚本或习惯,需要注意。 -6. 这个模块本身主要解决“接入和约定”问题,不提供大量 Redis 业务工具方法;复杂 Redis 场景通常还是由业务模块或上层 starter 自己封装。 - -## 总结 -这个模块的核心价值,不是简单引入 Redis 依赖,而是统一项目里的 Redis 使用方式: - -1. 统一 `RedisTemplate` 的序列化规则 -2. 统一 Spring Cache 的 Redis 落地方式 -3. 提供按缓存名声明 TTL 的扩展能力 -4. 为 Redisson 相关的上层能力提供基础环境 - -如果把它理解成一个“Redis 接入基座 + Cache 约定基座”的 starter,这个模块的设计会比较清晰。 diff --git a/rdms-framework/rdms-spring-boot-starter-rpc/README.md b/rdms-framework/rdms-spring-boot-starter-rpc/README.md deleted file mode 100644 index c2c9d31..0000000 --- a/rdms-framework/rdms-spring-boot-starter-rpc/README.md +++ /dev/null @@ -1,159 +0,0 @@ -# rdms-spring-boot-starter-rpc - -## 1. 模块现状 - -当前模块本体基本是空的。 - -从代码来看,这个模块目前只有: - -- `pom.xml` 中的依赖声明 -- `config`、`core` 两个包下的占位 `package-info.java` - -当前没有实际落地的 RPC 代码。 - -## 2. 当前实际作用 - -这个模块目前的作用,主要是统一引入 RPC 相关基础依赖,包括: - -- `spring-cloud-starter-openfeign` -- `spring-cloud-starter-loadbalancer` -- `feign-okhttp` -- `jakarta.validation-api` - -所以更准确地说,它现在是一个“RPC 依赖聚合模块”,而不是一个已经实现完整功能的 RPC starter。 - -## 3. 当前不包含的能力 - -这个模块当前没有提供以下能力: - -- 没有自动配置类 -- 没有 `@EnableFeignClients` 封装 -- 没有请求拦截器 -- 没有统一异常处理 -- 没有统一重试策略 -- 没有统一降级处理 -- 没有自定义编码器、解码器 -- 没有任何业务侧 RPC 工具类 - -也就是说,单看这个模块本身,当前还谈不上“功能实现”。 - -## 4. 如何理解这个模块 - -如果结合当前仓库结构来理解,这个模块更适合看作: - -- 先把 RPC 相关依赖收进来 -- 给后续扩展预留模块位置 -- 暂时还没有继续往里面建设实际能力 - -因此,这个模块当前的关键词不是“功能”,而是“占位”和“依赖聚合”。 - -## 5. 这个模块后续适合承接的内容 - -如果后续要继续建设,这个模块比较适合承接“各业务模块都可能共用的 RPC 通用能力”。 - -### 5.1 Feign 统一配置 - -适合放在这里的内容包括: - -- 统一开启 Feign 所需的基础配置 -- 统一配置超时、连接池、日志级别 -- 统一切换底层 HTTP 客户端 - -这类内容属于基础设施层,通常不应该散落在各业务模块中。 - -### 5.2 请求拦截 - -这部分正是比较典型、也比较适合放在本模块中的能力。 - -例如可以统一实现: - -- 请求头透传 -- Token 透传 -- 用户信息透传 -- TraceId / 请求链路标识透传 - -常见做法是基于 `RequestInterceptor`,在所有 Feign 请求发出前统一补充请求头。 - -### 5.3 返回结果统一处理 - -如果希望对所有 OpenFeign 调用结果做统一拦截和异常处理,也适合放在这个模块。 - -常见做法包括: - -- 通过 `ErrorDecoder` 统一处理非 2xx 响应 -- 通过自定义 `Decoder` 统一解析返回体 -- 对返回结果做统一业务异常转换 -- 对远程服务错误码做统一包装 - -例如: - -- 对方服务返回 404、500 时,统一转成项目内部异常 -- 对方服务返回统一响应结构时,统一判断是否成功 -- 对远程调用超时、连接失败做统一异常封装 - -### 5.4 调用日志与问题排查 - -这类通用能力也适合放在这里: - -- 统一打印 RPC 请求日志 -- 统一打印 RPC 响应日志 -- 记录调用耗时 -- 记录失败原因 - -这样做的好处是,排查跨服务问题时不需要每个模块各写一套。 - -### 5.5 重试、降级与容错 - -如果项目后续需要增强 RPC 稳定性,也可以在这里统一建设: - -- 重试策略 -- 熔断策略 -- 降级处理 -- 超时兜底 - -不过这部分是否要做,需要结合项目实际复杂度决定;如果当前系统调用链不复杂,也可以先不引入。 - -### 5.6 统一 RPC 规范 - -这个模块也适合沉淀一些统一约定,例如: - -- 统一返回结构 -- 统一异常码映射 -- 统一 Header 命名 -- 统一调用方与被调用方的接口规范 - -这样可以避免不同模块各自定义一套 RPC 风格。 - -## 6. 是否适合把拦截器和异常处理放在这里 - -如果这些能力是“针对所有或大多数 OpenFeign 调用都生效”的公共能力,那么放在这个模块是合适的。 - -原因很直接: - -- 它属于 RPC 基础设施,不属于某个单独业务模块 -- 放在这里可以统一维护,避免重复实现 -- 后续任何模块接入 Feign 时,都可以复用同一套规则 - -例如你提到的这类需求,就很适合落在这里: - -- 所有 Feign 请求统一加请求头 -- 所有 Feign 返回统一做异常判断 -- 所有远程调用失败统一转换成项目内部异常 - -如果某个拦截逻辑只服务于单一业务模块,那更适合放在该业务模块自己的 RPC 配置中;如果是全局通用规则,则更适合放在本模块。 - -## 7. 结论 - -`rdms-spring-boot-starter-rpc` 当前没有真正的功能代码。 - -如果后续要把它做成一个真正的 RPC starter,通常才会逐步补这些内容: - -- 自动装配 -- Feign 统一启用 -- 请求头透传 -- 调用日志 -- 异常处理 -- 超时与重试配置 -- 统一 RPC 规范 - -但以当前仓库代码为准,这些能力都还没有在本模块中实现。 diff --git a/rdms-framework/rdms-spring-boot-starter-security/README.md b/rdms-framework/rdms-spring-boot-starter-security/README.md deleted file mode 100644 index 4263e6d..0000000 --- a/rdms-framework/rdms-spring-boot-starter-security/README.md +++ /dev/null @@ -1,646 +0,0 @@ -# rdms-spring-boot-starter-security - -## 1. 模块定位 - -`rdms-spring-boot-starter-security` 是项目中的安全基础模块,当前实际包含两块能力: - -- 安全认证与权限校验 -- 操作日志记录 - -这个模块不是单纯引入 Spring Security 依赖,而是已经在项目里落了完整的自动装配链路,包括: - -- 基于 Token 的无状态认证 -- 登录用户上下文维护 -- URL 与方法级权限控制 -- 跨服务 `LoginUser` 透传 -- 401 / 403 统一返回 -- 操作日志采集与异步上报 - -## 2. 设计思路 - -### 2.1 认证与权限分层 - -这个模块把安全相关能力拆成了几层: - -1. 配置层 - 通过 `SecurityProperties` 统一管理 token 请求头、token 参数名、白名单、mock 登录、密码加密强度等配置。 - -2. 过滤器层 - 通过 `TokenAuthenticationFilter` 解析请求中的登录信息,并把 `LoginUser` 放入 Spring Security 上下文。 - -3. Spring Security 配置层 - 通过 `SecurityFilterChain` 统一配置无状态认证、放行规则、异常处理和过滤器顺序。 - -4. 权限服务层 - 通过 `SecurityFrameworkService` 对外提供权限、角色、scope 判断能力,供 `@PreAuthorize` 等表达式直接使用。 - -5. RPC 透传层 - 通过 Feign `RequestInterceptor` 把当前 `LoginUser` 继续透传给下游服务。 - -### 2.2 当前认证链路 - -当前项目的认证链路可以理解为: - -1. 请求进入服务 -2. 优先尝试从 `login-user` 请求头恢复登录用户 - 适用于网关或上游服务已经完成认证并透传用户信息的场景 -3. 如果没有 `login-user`,再尝试从 `Authorization` 或请求参数中获取 token -4. 通过 `OAuth2TokenCommonApi` 校验 token -5. 构造 `LoginUser` -6. 放入 Spring Security 上下文,供后续权限判断、业务代码、日志记录使用 - -这意味着当前项目跨服务调用时,主要透传的是 `LoginUser`,而不是原始 `Authorization`。 - -### 2.3 权限判断思路 - -权限判断没有把权限数据直接塞进本地配置,而是通过远程接口获取: - -- 权限校验走 `PermissionCommonApi` -- token 校验走 `OAuth2TokenCommonApi` - -也就是说,这个模块负责“接入和执行安全规则”,权限与 token 数据本身仍然由系统服务提供。 - -### 2.4 操作日志思路 - -操作日志能力不是自己从零实现注解解析,而是集成 `bizlog-sdk`: - -- 业务代码通过 `@LogRecord` 声明日志 -- 本模块提供 `ILogRecordService` 实现 -- 最终通过 `OperateLogCommonApi` 异步上报操作日志 - -这样做的特点是: - -- 业务代码侧书写简单 -- 日志记录逻辑统一 -- 日志存储仍集中在系统服务 - -## 3. 自动装配入口 - -当前自动装配入口在: - -- `RdmsSecurityRpcAutoConfiguration` -- `RdmsSecurityAutoConfiguration` -- `RdmsWebSecurityConfigurerAdapter` -- `RdmsOperateLogConfiguration` -- `RdmsOperateLogRpcAutoConfiguration` - -说明这个 starter 当前已经不是占位模块,而是有完整自动配置入口的基础模块。 - -## 4. 核心功能点 - -### 4.1 基于 Token 的无状态认证 - -通过 `SecurityFilterChain` 配置为无状态模式: - -- 禁用 Session -- 禁用表单登录 -- 禁用 httpBasic -- 通过自定义 `TokenAuthenticationFilter` 完成登录态识别 - -这意味着服务本身不维护 Session,认证主要依赖 token 或上游透传的 `login-user`。 - -### 4.2 登录用户上下文维护 - -模块内定义了 `LoginUser`,当前主要包含这些信息: - -- `id` -- `userType` -- `info` -- `scopes` -- `expiresTime` - -认证成功后,`LoginUser` 会进入 Spring Security 上下文,后续可以通过 `SecurityFrameworkUtils` 获取,例如: - -- 当前用户 ID -- 当前用户昵称 -- 当前用户部门 ID - -### 4.3 支持两种登录信息来源 - -`TokenAuthenticationFilter` 当前支持两种方式恢复登录用户: - -1. 从 `login-user` 请求头恢复 - 适合网关转发、服务间调用、Feign 透传场景 - -2. 从 token 恢复 - 适合直接请求服务、没有上游透传 `login-user` 的场景 - -这也是为什么当前服务间调用时,即使不继续透传原始 token,下游仍然可以识别当前登录用户。 - -### 4.4 跨服务 LoginUser 透传 - -模块内提供了 `LoginUserRequestInterceptor`。 - -作用是: - -- 发起 Feign 请求时,读取当前线程中的 `LoginUser` -- 将其序列化后放到 `login-user` 请求头 -- 下游服务再通过 `TokenAuthenticationFilter` 还原为登录用户上下文 - -当前项目跨服务透传的重点是“登录用户信息透传”,不是“原始 token 透传”。 - -### 4.5 URL 级访问控制 - -`RdmsWebSecurityConfigurerAdapter` 里统一配置了请求访问规则,当前支持: - -- 静态资源默认放行 -- `@PermitAll` 标注的方法或类自动放行 -- `rdms.security.permit-all-urls` 配置的 URL 放行 -- 各业务模块通过 `AuthorizeRequestsCustomizer` 继续追加自定义规则 -- 其余请求默认需要认证 - -其中 `AuthorizeRequestsCustomizer` 是一个扩展点,适合各模块自己补充 URL 安全规则。 - -例如 WebSocket 模块就是通过继承这个类,把自己的连接地址加入放行规则。 - -### 4.6 方法级权限校验 - -模块开启了 `@EnableMethodSecurity`,并注册了名为 `ss` 的权限服务 Bean。 - -因此业务代码可以直接写: - -```java -@PreAuthorize("@ss.hasPermission('system:user:create')") -public Long createUser(UserSaveReqVO reqVO) { - ... -} -``` - -当前支持的能力包括: - -- `hasPermission` -- `hasAnyPermissions` -- `hasRole` -- `hasAnyRoles` -- `hasScope` -- `hasAnyScopes` - -### 4.7 权限与角色远程校验 - -权限、角色判断不是本地硬编码,而是通过 `PermissionCommonApi` 远程校验。 - -为了减少频繁 RPC 调用,当前还做了本地 Guava 缓存: - -- 权限判断缓存 1 分钟 -- 角色判断缓存 1 分钟 - -`scope` 判断则直接基于当前 `LoginUser.scopes` 完成。 - -### 4.8 401 / 403 统一处理 - -模块内提供了统一异常处理器: - -- `AuthenticationEntryPointImpl`:未登录访问需要认证的资源时返回 401 -- `AccessDeniedHandlerImpl`:已登录但权限不足时返回 403 - -这样前端收到的是统一 JSON 结构,而不是默认的 Spring Security 页面或跳转行为。 - -### 4.9 SecurityContext 跨线程传递 - -模块把 `SecurityContextHolder` 的策略切换成了 `TransmittableThreadLocalSecurityContextHolderStrategy`。 - -作用是: - -- 在 `@Async` 等异步线程场景下,尽量保留当前安全上下文 -- 减少使用普通 `ThreadLocal` 时上下文丢失的问题 - -### 4.10 支持开发期 mock 登录 - -当配置开启后,可以使用 mock token 构造登录用户,便于本地调试。 - -这块能力由 `SecurityProperties` 中的以下配置控制: - -- `mock-enable` -- `mock-secret` - -这类能力只适合开发调试环境,不适合生产环境开启。 - -### 4.11 操作日志能力 - -这个模块还同时集成了操作日志能力。 - -当前实现方式是: - -1. 通过 `@EnableLogRecord` 开启日志能力 -2. 业务方法上使用 `@LogRecord` -3. 本模块的 `LogRecordServiceImpl` 收集日志内容 -4. 自动补充用户信息、请求信息、链路追踪信息 -5. 通过 `OperateLogCommonApi` 异步上报 - -日志中会补充的典型信息包括: - -- 用户 ID -- 用户类型 -- 请求方法 -- 请求 URL -- 请求 IP -- User-Agent -- TraceId - -## 5. 配置项 - -当前可见的核心配置在 `rdms.security` 下: - -```yaml -rdms: - security: - token-header: Authorization - token-parameter: token - mock-enable: false - mock-secret: test - permit-all-urls: - - /admin-api/system/auth/login - password-encoder-length: 4 -``` - -说明: - -- `token-header`:请求头里 token 的字段名,默认是 `Authorization` -- `token-parameter`:请求参数里 token 的字段名,主要兼容不能方便传 header 的场景 -- `mock-enable` / `mock-secret`:开发期 mock 登录控制 -- `permit-all-urls`:额外白名单 URL -- `password-encoder-length`:BCrypt 加密强度 - -## 6. 快速上手 - -如果从“一个开发者接手后该怎么用”来理解,这个模块最常见的上手路径如下。 - -### 6.1 引入依赖后,会自动得到什么 - -业务模块引入这个 starter 后,默认会自动得到这些能力: - -- Spring Security 过滤链 -- Token 认证过滤器 -- `@EnableMethodSecurity` -- `@ss` 权限表达式 Bean -- 401 / 403 统一返回 -- Feign 的 `login-user` 透传 -- `@LogRecord` 操作日志能力 - -也就是说,通常不需要你再手写一套 Security 配置类,除非业务模块有额外的特殊规则。 - -### 6.2 最小配置怎么写 - -最常见的基础配置如下: - -```yaml -rdms: - security: - token-header: Authorization - token-parameter: token - mock-enable: false - permit-all-urls: - - /admin-api/system/auth/login - - /admin-api/system/auth/refresh-token -``` - -如果没有特殊需求,通常只需要确认: - -- 哪些接口要放行 -- token 从哪个 header 取 - -### 6.3 一个新接口,默认是什么状态 - -默认情况下: - -- 没有显式放行的接口,都会要求登录 -- 已登录后,如果方法上写了 `@PreAuthorize`,还会继续做权限校验 - -也就是说,一个普通接口最常见的开发方式是: - -1. 默认需要登录 -2. 再按需要加权限注解 - -例如: - -```java -@PreAuthorize("@ss.hasPermission('system:user:query')") -@GetMapping("/page") -public CommonResult> getUserPage(UserPageReqVO reqVO) { - ... -} -``` - -### 6.4 哪些接口不需要登录,怎么做 - -有两种常用方式。 - -方式一:在接口或类上标 `@PermitAll` - -```java -@PermitAll -@GetMapping("/public-info") -public CommonResult getPublicInfo() { - ... -} -``` - -方式二:通过配置白名单放行 - -```yaml -rdms: - security: - permit-all-urls: - - /admin-api/system/auth/login - - /admin-api/system/auth/refresh-token -``` - -实践建议: - -- 固定的公共接口、登录接口、刷新 token 接口,更适合放配置白名单 -- 个别无需登录的业务接口,更适合直接写 `@PermitAll` - -### 6.5 需要权限控制,怎么做 - -最常用的是在 Controller 或 Service 方法上写 `@PreAuthorize`: - -```java -@PreAuthorize("@ss.hasPermission('system:user:create')") -@PostMapping("/create") -public CommonResult createUser(@Valid @RequestBody UserSaveReqVO reqVO) { - ... -} -``` - -当前可直接使用的表达式包括: - -- `@ss.hasPermission('xxx')` -- `@ss.hasAnyPermissions('a', 'b')` -- `@ss.hasRole('admin')` -- `@ss.hasAnyRoles('admin', 'manager')` -- `@ss.hasScope('user.read')` -- `@ss.hasAnyScopes('a', 'b')` - -最常见的是 `hasPermission`。 - -### 6.5.1 接口可以按三类理解 - -从实际开发角度,可以把接口简单分成三类: - -1. 匿名可访问 - 这类接口不要求登录。 - 常见做法: - - 使用 `@PermitAll` - - 或加入 `rdms.security.permit-all-urls` 白名单 - -2. 登录即可访问 - 这类接口要求登录,但不校验具体 permission。 - 常见做法: - - 不加 `@PermitAll` - - 不加白名单 - - 也不加 `@PreAuthorize` - - 在当前配置下,这类接口会被默认的 `authenticated()` 规则保护: - - 未登录无法访问 - - 已登录可以访问 - -3. 登录后还要校验具体权限 - 这类接口除了要求登录,还要求具备具体权限点。 - 常见做法: - - 使用 `@PreAuthorize("@ss.hasPermission('xxx')")` - -一个比较实用的开发策略是: - -- 大部分普通接口只要求登录 -- 删除、导出、审批、分配等敏感动作再加具体权限校验 - -### 6.6 权限校验是怎么触发的 - -很多时候容易疑惑:`@PreAuthorize("@ss.hasPermission('system:dept:update')")` 这行代码本身没有手动调用 `PermissionCommonApi`,那远程权限校验是什么时候发生的? - -当前链路可以直接理解成下面这几步: - -```text -请求进入服务 --> TokenAuthenticationFilter 完成登录态恢复 --> 请求到达 Controller / Service 方法前 --> Spring Security 拦截 @PreAuthorize --> 解析表达式 @ss.hasPermission('system:dept:update') --> 调用 Spring 容器里的 ss Bean --> 进入 SecurityFrameworkServiceImpl.hasPermission(...) --> 转到 hasAnyPermissions(...) --> 先查本地 1 分钟缓存 --> 缓存未命中时,调用 PermissionCommonApi 远程判断权限 --> 返回 true:允许进入方法 --> 返回 false:拒绝访问,返回 403 -``` - -可以把它理解为: - -- `@PreAuthorize` 负责“触发权限判断” -- `@ss` 负责“找到权限判断 Bean” -- `SecurityFrameworkServiceImpl` 负责“执行权限判断逻辑” -- `PermissionCommonApi` 负责“向权限服务查询结果” - -所以真正触发 `PermissionCommonApi` 的,不是 Controller 代码手动调用,而是 Spring Security 在执行 `@PreAuthorize` 表达式时自动触发。 - -### 6.7 业务代码里怎么拿当前登录用户 - -最常用的是通过 `SecurityFrameworkUtils` 获取: - -```java -Long userId = SecurityFrameworkUtils.getLoginUserId(); -String nickname = SecurityFrameworkUtils.getLoginUserNickname(); -Long deptId = SecurityFrameworkUtils.getLoginUserDeptId(); -``` - -如果需要更完整的信息: - -```java -LoginUser loginUser = SecurityFrameworkUtils.getLoginUser(); -``` - -适用场景: - -- 新增、修改数据时记录操作人 -- 查询当前用户自己的数据 -- 记录审计字段 - -### 6.8 为什么跨服务还能拿到当前用户 - -当前项目的链路是这样的: - -1. 前端把 token 传给网关 -2. 网关校验 token 后,把用户信息转成 `login-user` 请求头 -3. 下游服务读取 `login-user`,恢复 `LoginUser` -4. 如果下游服务再通过 Feign 调别的服务,`LoginUserRequestInterceptor` 会继续透传 `login-user` - -所以开发时可以这样理解: - -- Web 入口主要靠 token 建立登录态 -- 服务间调用主要靠 `login-user` 继续传递登录态 - -### 6.9 什么时候需要自己补 URL 规则 - -如果某个模块有特殊放行需求,可以继承 `AuthorizeRequestsCustomizer`。 - -例如: - -```java -public class XxxAuthorizeRequestsCustomizer extends AuthorizeRequestsCustomizer { - - @Override - public void customize(AuthorizeHttpRequestsConfigurer.AuthorizationManagerRequestMatcherRegistry registry) { - registry.requestMatchers("/xxx/**").permitAll(); - } -} -``` - -常见场景: - -- WebSocket 握手地址 -- 第三方回调接口 -- 某些模块自己的公共入口 - -### 6.10 需要记录操作日志,怎么做 - -在业务方法上直接加 `@LogRecord`: - -```java -@LogRecord(type = "SYSTEM_USER", subType = "CREATE", bizNo = "{{#user.id}}", - success = "创建用户成功") -public Long createUser(UserSaveReqVO reqVO) { - ... -} -``` - -这个注解更适合加在“有明确业务动作”的方法上,例如: - -- 新增用户 -- 修改角色 -- 删除字典 -- 分配权限 - -### 6.11 开发时最容易混淆的几个点 - -1. 这个模块负责“认证接入和权限执行”,不负责 token 签发 - token 的签发和存储仍在系统服务等其他模块。 - -2. 当前跨服务透传的是 `login-user`,不是原始 token - 所以下游服务能识别当前用户,不等于它一直拿着原始 `Authorization`。 -3. 单体部署(不经过网关)建议在入口层剥离 `login-user` 请求头,避免外部伪造登录态 - - Nginx 示例: - ```nginx - # 去除客户端伪造的 login-user 头 - proxy_set_header login-user ""; - ``` - -3. 不加 `@PreAuthorize` 不代表匿名可访问 - 默认仍然需要登录,只是不会进一步做权限点校验。 - -4. `@PermitAll` 和 `permit-all-urls` 都是放行登录校验 - 放行后通常也不会再走权限控制。 - -## 7. 常见使用方式 - -### 7.1 方法权限控制 - -```java -@PreAuthorize("@ss.hasPermission('system:user:query')") -@GetMapping("/page") -public CommonResult> getUserPage(UserPageReqVO reqVO) { - ... -} -``` - -适用场景: - -- 控制某个接口必须具备某个权限点 -- 细粒度控制新增、修改、删除、导出等操作 - -### 7.2 放行无需登录的接口 - -方式一:直接标注 `@PermitAll` - -```java -@PermitAll -@GetMapping("/public-info") -public CommonResult getPublicInfo() { - ... -} -``` - -方式二:通过配置加入白名单 - -```yaml -rdms: - security: - permit-all-urls: - - /admin-api/system/auth/login - - /admin-api/system/auth/refresh-token -``` - -### 7.3 为某个模块补充 URL 安全规则 - -如果某个模块有自己的特殊放行需求,可以继承 `AuthorizeRequestsCustomizer`: - -```java -public class XxxAuthorizeRequestsCustomizer extends AuthorizeRequestsCustomizer { - - @Override - public void customize(AuthorizeHttpRequestsConfigurer.AuthorizationManagerRequestMatcherRegistry registry) { - registry.requestMatchers("/xxx/**").permitAll(); - } -} -``` - -适用场景: - -- WebSocket 握手地址放行 -- 回调接口放行 -- 某些特殊模块追加自己的 URL 规则 - -### 7.4 记录操作日志 - -```java -@LogRecord(type = "SYSTEM_USER", subType = "CREATE", bizNo = "{{#user.id}}", - success = "创建用户成功") -public Long createUser(UserSaveReqVO reqVO) { - ... -} -``` - -适用场景: - -- 新增、修改、删除等需要审计的核心业务动作 -- 希望把操作人、请求来源、业务对象编号一起记录下来 - -## 8. 模块边界 - -阅读这个模块时,需要把“它负责什么”和“它不负责什么”分开看。 - -当前它负责: - -- 接入 Spring Security -- 解析 token 或 `login-user` -- 构建登录用户上下文 -- 提供权限判断能力 -- 统一未登录 / 未授权响应 -- 透传 `LoginUser` -- 集成操作日志记录 - -当前它不负责: - -- token 的签发 -- 权限数据本身的维护 -- 用户、角色、菜单、权限模型的存储 -- 原始 `Authorization` 的 Feign 透传 - -这些数据来源和业务模型,当前仍由系统服务等其他模块提供。 - -## 9. 总结 - -`rdms-spring-boot-starter-security` 当前已经是一个实际落地的基础模块,不是占位模块。 - -从当前代码看,它的核心价值在于: - -- 统一接入认证与权限体系 -- 把登录用户上下文在 Web、Spring Security、Feign 调用之间串起来 -- 给业务层提供简单直接的权限表达式入口 -- 把操作日志能力收进统一基础设施 - -如果后续继续扩展,这个模块仍然适合承接更多安全通用能力,例如: - -- 原始 token 透传 -- 更细的权限表达式 -- 更完整的审计日志字段 diff --git a/rdms-framework/rdms-spring-boot-starter-test/README.md b/rdms-framework/rdms-spring-boot-starter-test/README.md deleted file mode 100644 index 4f0e01a..0000000 --- a/rdms-framework/rdms-spring-boot-starter-test/README.md +++ /dev/null @@ -1,373 +0,0 @@ -# rdms-spring-boot-starter-test - -## 1. 模块定位 - -`rdms-spring-boot-starter-test` 是项目中的测试基础模块,作用不是提供业务测试用例,而是为测试提供统一的基础设施和基类。 - -当前模块主要解决的是: - -- 纯 Mockito 单元测试怎么写 -- 依赖数据库的单元测试怎么快速启动 -- 依赖 Redis 的单元测试怎么快速启动 -- 同时依赖 DB 和 Redis 的测试怎么快速启动 -- 测试里怎么更方便地造随机数据和做断言 - -所以这个模块更适合理解为“测试脚手架模块”。 - -## 2. 设计思路 - -当前模块的设计思路比较明确:把常见测试场景抽成几种固定模板,让开发人员通过继承基类快速开始写测试,而不是每个模块都自己从零搭测试环境。 - -整体上分成三层: - -1. 依赖层 - 统一引入测试常用依赖,例如 Mockito、Spring Boot Test、H2、内存 Redis、随机对象生成工具。 - -2. 配置层 - 提供针对测试场景的补充配置,例如: - - 内存 Redis 启动配置 - - SQL 初始化配置 - -3. 基类层 - 把测试场景收口成几类基类,开发时按场景继承即可。 - -这种设计的重点不是“测试能力多复杂”,而是“让测试环境标准化”。 - -## 3. 当前提供的能力 - -### 3.1 纯 Mockito 单元测试基类 - -基类: - -- `BaseMockitoUnitTest` - -作用: - -- 适合不需要 Spring 容器、不需要 DB、不需要 Redis 的纯单元测试 -- 基于 `MockitoExtension` - -适用场景: - -- 测试工具类 -- 测试纯业务逻辑类 -- 依赖全部可以通过 Mock 替代的 Service - -### 3.2 内存 DB 单元测试基类 - -基类: - -- `BaseDbUnitTest` - -作用: - -- 启动 Spring 测试上下文 -- 引入数据源、事务、MyBatis 相关配置 -- 使用 H2 作为内存数据库 -- 支持 SQL 初始化 -- 每个测试方法结束后自动执行 `/sql/clean.sql` 清理数据库 - -适用场景: - -- Mapper 测试 -- 依赖本模块数据库访问的 Service 测试 -- 需要真实执行 MyBatis SQL 的测试 - -### 3.3 内存 Redis 单元测试基类 - -基类: - -- `BaseRedisUnitTest` - -作用: - -- 启动 Spring 测试上下文 -- 启动内存 Redis -- 引入 Redis 与 Redisson 相关配置 - -适用场景: - -- Redis DAO 测试 -- 缓存逻辑测试 -- 分布式锁、限流、缓存等依赖 Redis 的逻辑测试 - -### 3.4 内存 DB + Redis 组合测试基类 - -基类: - -- `BaseDbAndRedisUnitTest` - -作用: - -- 同时启动内存数据库和内存 Redis -- 一次性引入 DB、MyBatis、Redis、Redisson 所需配置 -- 每个测试方法结束后自动清理数据库 - -适用场景: - -- 同时依赖数据库和缓存的 Service 测试 -- 需要验证“写库 + 删缓存 / 刷缓存”这类联动逻辑的测试 - -### 3.5 内存 Redis 启动配置 - -配置类: - -- `RedisTestConfiguration` - -作用: - -- 基于 `jedis-mock` 启动一个内存 Redis Server -- 供 Redis 相关测试基类复用 - -这个配置的重点是:让测试不依赖外部真实 Redis 服务。 - -### 3.6 SQL 初始化配置 - -配置类: - -- `SqlInitializationTestConfiguration` - -作用: - -- 在测试场景下补充 DataSource SQL 初始化能力 -- 解决延迟加载场景下默认 SQL 初始化配置不生效的问题 - -这个配置的核心价值是:保证 H2 测试数据库能按测试配置正常初始化 schema 和 data。 - -### 3.7 随机测试数据工具 - -工具类: - -- `RandomUtils` - -作用: - -- 随机生成字符串、数字、时间、邮箱、手机号 -- 随机生成 POJO -- 随机生成对象列表、对象集合 - -它内部基于 `podam` 做随机对象填充,并对部分字段做了定制处理,例如: - -- `status` 字段优先生成常见状态值 -- `deleted` 字段默认生成 `false` -- `LocalDateTime` 的纳秒位归零,避免 MySQL / H2 时间精度差异 - -### 3.8 测试断言工具 - -工具类: - -- `AssertUtils` - -作用: - -- 对比两个 POJO 的字段是否一致 -- 断言是否抛出指定 `ServiceException` - -适用场景: - -- 校验 DTO / DO / VO 转换结果 -- 校验业务异常是否符合预期 - -## 4. 开发人员怎么上手 - -这个模块最重要的不是“看懂代码”,而是先判断当前测试属于哪一类,然后继承对应基类。 - -### 4.1 如果只是纯逻辑测试 - -直接继承: - -```java -class XxxServiceTest extends BaseMockitoUnitTest { -} -``` - -适合: - -- 不需要 Spring 容器 -- 不访问数据库 -- 不访问 Redis -- 依赖都可以 Mock - -这是成本最低、执行最快的一类测试。 - -### 4.2 如果要测 Mapper 或真实 SQL - -直接继承: - -```java -class XxxMapperTest extends BaseDbUnitTest { -} -``` - -适合: - -- Mapper 层测试 -- 依赖 H2 跑 SQL -- Service 里要真实查库、写库 - -开发时通常要准备: - -- `application-unit-test.yaml` -- 初始化 SQL -- `clean.sql` - -### 4.3 如果要测 Redis 逻辑 - -直接继承: - -```java -class XxxRedisDaoTest extends BaseRedisUnitTest { -} -``` - -适合: - -- Redis Key 操作测试 -- 缓存逻辑测试 -- 基于 Redis 的基础设施测试 - -这样就不需要自己额外准备 Redis 环境。 - -### 4.4 如果业务同时依赖 DB 和 Redis - -直接继承: - -```java -class XxxServiceTest extends BaseDbAndRedisUnitTest { -} -``` - -适合: - -- 先写库再删缓存 -- 先查库再写缓存 -- 依赖数据库和 Redis 共同完成的业务逻辑 - -### 4.5 如果想快速造测试对象 - -可以直接使用 `RandomUtils`: - -```java -UserSaveReqVO reqVO = RandomUtils.randomPojo(UserSaveReqVO.class); -List list = RandomUtils.randomPojoList(UserSaveReqVO.class, 3); -String email = RandomUtils.randomEmail(); -String mobile = RandomUtils.randomMobile(); -``` - -适合: - -- 减少手写测试数据 -- 快速生成大量测试输入 -- 配合局部字段覆盖提高测试效率 - -### 4.6 如果要断言业务异常 - -可以直接使用 `AssertUtils.assertServiceException(...)`: - -```java -AssertUtils.assertServiceException( - () -> userService.createUser(reqVO), - USER_USERNAME_EXISTS -); -``` - -适合: - -- 校验业务校验逻辑 -- 校验异常码和异常信息 - -### 4.7 如果要断言对象字段一致 - -可以使用: - -```java -AssertUtils.assertPojoEquals(expected, actual, "createTime", "updateTime"); -``` - -适合: - -- 校验转换逻辑 -- 校验查询结果 -- 忽略少数字段差异后做整体对比 - -## 5. 常见使用建议 - -### 5.1 优先选最轻的测试基类 - -建议顺序是: - -1. 能用 `BaseMockitoUnitTest` 就不要上 Spring 容器 -2. 需要真实 DB 再用 `BaseDbUnitTest` -3. 需要真实 Redis 再用 `BaseRedisUnitTest` -4. 两者都需要时再用 `BaseDbAndRedisUnitTest` - -原因很简单: - -- 测试越轻,执行越快 -- 环境越少,问题越少 - -### 5.2 本模块更偏“单元测试基础设施” - -虽然这里用到了 Spring Boot Test、H2、内存 Redis,但当前命名和设计仍然偏“单元测试 / 轻量集成测试”。 - -也就是说,它更适合: - -- 在模块内部验证逻辑正确性 -- 快速验证 Mapper / Redis / Service 行为 - -而不是: - -- 完整端到端测试 -- 完整微服务联调测试 -- 依赖真实外部中间件的系统测试 - -### 5.3 自己模块真实依赖走内存实现,外部模块依赖走 Mock - -从 `BaseDbUnitTest` 的注释就能看出当前设计思路: - -- 自己模块的 Mapper 走 H2 -- 别的模块的 Service 走 Mock - -这是一种比较务实的测试策略: - -- 保留自己模块的真实数据访问能力 -- 避免跨模块依赖把测试拖重 - -## 6. 模块边界 - -当前模块负责的是: - -- 提供测试基类 -- 提供测试配置 -- 提供随机数据和断言工具 - -当前模块不负责的是: - -- 自动生成业务测试用例 -- 自动替你写 Mock 行为 -- 真实中间件联调 -- 完整集成测试平台 - -所以它的职责很明确:让测试环境更容易搭,不是替业务写测试。 - -## 7. 总结 - -`rdms-spring-boot-starter-test` 当前已经把项目里最常见的测试场景做成了统一模板。 - -对于开发人员来说,真正重要的上手方式只有两步: - -1. 先判断当前测试依赖什么环境 -2. 再继承对应的基类 - -如果只是想快速理解这个模块,可以直接记住下面这张映射: - -- 纯逻辑测试:`BaseMockitoUnitTest` -- 测 DB:`BaseDbUnitTest` -- 测 Redis:`BaseRedisUnitTest` -- DB + Redis 一起测:`BaseDbAndRedisUnitTest` - -再配合: - -- `RandomUtils` 造数据 -- `AssertUtils` 做断言 - -基本就能覆盖大部分日常测试开发场景。 diff --git a/rdms-framework/rdms-spring-boot-starter-web/README.md b/rdms-framework/rdms-spring-boot-starter-web/README.md deleted file mode 100644 index e0ee4c5..0000000 --- a/rdms-framework/rdms-spring-boot-starter-web/README.md +++ /dev/null @@ -1,606 +0,0 @@ -# rdms-spring-boot-starter-web - -## 1. 模块定位 - -`rdms-spring-boot-starter-web` 是 Web 层基础设施模块,用来统一处理 HTTP 接口开发中的通用问题,而不是承载具体业务逻辑。 - -模块当前聚合了以下能力: - -- Web 基础自动配置 -- 全局异常处理 -- 接口返回结果暂存 -- 管理端 / 应用端接口前缀约定 -- Swagger / Knife4j 文档 -- API 访问日志 -- XSS 防护 -- API 加解密 -- 返回字段脱敏 -- Jackson JSON 定制 -- Banner 输出 - -## 2. 设计思路 - -这个模块的设计重点,是把 Web 层横切能力收敛到 starter 中,业务模块只关注 Controller、参数对象和业务逻辑本身。 - -核心思路如下: - -- 通过自动装配统一注册 `Filter`、`ControllerAdvice`、`RestTemplate`、Swagger 等基础组件。 -- 通过包路径约定,自动给 Controller 增加统一前缀,减少每个模块重复写公共路径。 -- 通过全局异常处理,把常见异常统一翻译成 `CommonResult`。 -- 通过过滤器和拦截器处理访问日志、XSS、防重复读取请求体、演示环境保护等横切逻辑。 -- 通过注解方式扩展局部能力,例如 API 访问日志增强、接口加解密、字段脱敏。 - -自动装配入口如下: - -- `com.njcn.rdms.framework.apilog.config.RdmsApiLogAutoConfiguration` -- `com.njcn.rdms.framework.jackson.config.RdmsJacksonAutoConfiguration` -- `com.njcn.rdms.framework.swagger.config.RdmsSwaggerAutoConfiguration` -- `com.njcn.rdms.framework.web.config.RdmsWebAutoConfiguration` -- `com.njcn.rdms.framework.apilog.config.RdmsApiLogRpcAutoConfiguration` -- `com.njcn.rdms.framework.xss.config.RdmsXssAutoConfiguration` -- `com.njcn.rdms.framework.banner.config.RdmsBannerAutoConfiguration` -- `com.njcn.rdms.framework.encrypt.config.RdmsApiEncryptAutoConfiguration` - -## 3. 功能模块 - -### 3.1 Web 基础自动配置 - -`RdmsWebAutoConfiguration` 是本模块的核心入口,主要提供以下能力: - -- 注册全局异常处理器 `GlobalExceptionHandler` -- 注册返回结果处理器 `GlobalResponseBodyHandler` -- 注册 `WebFrameworkUtils` -- 注册跨域过滤器 `CorsFilter` -- 注册请求体缓存过滤器 `CacheRequestBodyFilter` -- 提供普通 `RestTemplate` -- 提供带 `@LoadBalanced` 的 `RestTemplate` - -其中有两个基础能力需要重点关注: - -#### 3.1.1 按包路径自动增加接口前缀 - -模块会根据 Controller 所在包路径,自动追加统一前缀: - -- `**.controller.admin.**` 默认追加 `/admin-api` -- `**.controller.app.**` 默认追加 `/app-api` - -默认配置来自 `rdms.web`: - -```yaml -rdms: - web: - admin-api: - prefix: /admin-api - controller: "**.controller.admin.**" - app-api: - prefix: /app-api - controller: "**.controller.app.**" - admin-ui: - url: http://127.0.0.1:80 - -# 说明: -# - admin-api/app-api 已有默认值(如上所示),不改可省略 -# - admin-ui.url 无默认值,需要显式配置 -``` - -例如: - -```java -package com.njcn.rdms.module.system.controller.admin.user; - -@RestController -@RequestMapping("/user") -public class UserController { - - @GetMapping("/get") - public CommonResult get() { - return CommonResult.success("ok"); - } -} -``` - -实际访问路径是: - -```text -/admin-api/user/get -``` - -如果 Controller 放在: - -```text -com.xxx.xxx.controller.app.xxx -``` - -则会自动挂到 `/app-api` 下。 - -#### 3.1.2 `CommonResult` 不是自动包装 - -这个模块不会把任意 Controller 返回值自动包成 `CommonResult`。 - -`GlobalResponseBodyHandler` 的作用,是在返回前记录已经构造好的 `CommonResult`,供访问日志等能力读取,而不是替开发者自动改写返回结构。 - -因此 Controller 仍然需要显式返回: - -```java -return CommonResult.success(data); -``` - -而不是依赖框架自动包装。 - -### 3.2 全局异常处理 - -`GlobalExceptionHandler` 会把常见异常统一转换成 `CommonResult`,包括: - -- 参数缺失 -- 参数类型错误 -- `@Validated` / `@Valid` 校验失败 -- 请求方式不匹配 -- Content-Type 不匹配 -- 无权限访问 -- 业务异常 `ServiceException` -- 系统异常 - -对于系统异常,除了统一返回错误结果外,还会通过 `ApiErrorLogCommonApi` 异步记录异常日志。 - -这意味着业务代码中通常只需要: - -- 正常场景返回 `CommonResult.success(...)` -- 业务错误抛出 `ServiceException` - -其余异常由框架统一兜底。 - -### 3.3 接口返回结果暂存 - -这里的“接口返回结果记录”,更准确地说是“接口返回结果暂存”。 - -对应实现是 `GlobalResponseBodyHandler`,它只会在 Controller 返回值为 `CommonResult` 时,把该结果放到当前请求上下文中,供后续组件读取。 - -它本身不负责: - -- 打印日志 -- 持久化日志 -- 改写返回结构 - -它主要服务于访问日志能力。因为 `ApiAccessLogFilter` 执行时,需要拿到本次请求最终返回的 `CommonResult`,才能把返回码、返回消息、响应体等内容写入访问日志。 - -可以这样理解两者关系: - -- 接口返回结果暂存:把本次返回结果放到 request 上,供后续读取 -- API 访问日志:读取请求信息和返回结果,组装后异步上报日志 - -因此,这两个能力不是并列重复关系,而是前者为后者提供支撑。 - -### 3.4 请求体缓存 - -`CacheRequestBodyFilter` 会把请求体包装成可重复读取的形式。 - -这个能力主要解决以下问题: - -- 过滤器里读过一次请求体后,后续代码还能继续读取 -- 访问日志、XSS、防护类过滤器可以提前读取请求内容 -- 异常日志记录时可以再次拿到请求参数 - -这类能力对 `application/json` 请求尤其重要。 - -### 3.5 跨域处理 - -模块默认注册了全局 `CorsFilter`,允许: - -- 任意来源 -- 任意请求头 -- 任意请求方法 -- 携带凭证 - -适合前后端分离场景的统一跨域处理。 - -如果项目已经有更严格的网关级跨域策略,需要注意是否与这里的配置重复。 - -### 3.6 RestTemplate 支持 - -模块提供两个 `RestTemplate` Bean: - -- 普通 `RestTemplate` -- 支持服务名负载均衡的 `loadBalancedRestTemplate` - -使用示例: - -```java -@Resource -private RestTemplate restTemplate; - -@Resource(name = "loadBalancedRestTemplate") -private RestTemplate loadBalancedRestTemplate; -``` - -适用场景: - -- `restTemplate`:直接调用固定地址 -- `loadBalancedRestTemplate`:通过服务名访问注册中心中的其他服务 - -### 3.7 Swagger / Knife4j 文档 - -模块会自动装配 OpenAPI,并按管理端、应用端拆分分组: - -- `/admin-api/**` -- `/app-api/**` - -同时会在文档中预置常见请求头,例如认证头,便于在 Swagger 页面直接调试接口。 - -Swagger 配置示例: - -```yaml -rdms: - swagger: - title: RDMS API # 文档标题 - description: RDMS 接口文档 # 文档描述 - author: RDMS # 作者/团队 - version: 1.0.0 # 版本号 - url: https://example.com # 项目或团队主页 - email: dev@example.com # 联系邮箱 - license: Apache 2.0 # 协议名称 - license-url: https://www.apache.org/licenses/LICENSE-2.0.html # 协议地址 -``` - -开发时通常只需要: - -- 引入本模块 -- 补齐 `rdms.swagger` 配置 -- 在 Controller 上使用 `@Tag` -- 在接口上使用 `@Operation` - -这样访问日志模块还能直接复用这些注解信息,自动推断操作模块、操作名称。 - -### 3.8 API 访问日志 - -模块通过 `ApiAccessLogFilter + ApiAccessLogInterceptor` 组合处理接口访问日志。 - -这里的访问日志,才是真正意义上的“日志记录能力”。 - -处理逻辑分成两层: - -- 拦截器层:把 `HandlerMethod` 放到 request 中,供过滤器层读取 -- 过滤器层:在请求结束后读取请求信息、异常信息、返回结果,并异步上报访问日志 - -访问日志内容包括: - -- 用户信息 -- 请求路径、方法、IP、User-Agent -- 请求参数 -- 返回码、返回信息 -- 执行耗时 -- 操作模块、操作名称、操作类型 - -其中“返回码、返回信息、响应体”这部分数据,正是从前面的 `GlobalResponseBodyHandler` 暂存结果中读取出来的。 - -默认会记录访问日志;如需关闭,可配置: - -```yaml -rdms: - access-log: - enable: false -``` - -如果需要对某个接口单独调整日志行为,可使用 `@ApiAccessLog`。 - -常见用法示例: - -```java -@Tag(name = "用户管理") -@RestController -@RequestMapping("/user") -public class UserController { - - @GetMapping("/page") - @Operation(summary = "查询用户分页") - @ApiAccessLog(responseEnable = true, sanitizeKeys = {"mobile"}) - public CommonResult> page(UserPageReqVO reqVO) { - return CommonResult.success(userService.page(reqVO)); - } -} -``` - -说明: - -- `responseEnable = true`:额外记录响应体 -- `sanitizeKeys`:从日志中移除敏感字段 -- 如果没有显式指定操作名称,默认优先从 `@Operation` 中获取 - -访问日志的异步落库依赖 `ApiAccessLogCommonApi`,因此它本质上是“记录请求轨迹”,不是直接把日志写在本模块内部。 - -### 3.9 XSS 防护 - -模块内提供了 XSS 过滤能力,核心组成包括: - -- `XssFilter` -- `XssRequestWrapper` -- `XssStringJsonDeserializer` -- `JsoupXssCleaner` - -设计方式是: - -- 对进入系统的请求内容做统一清洗 -- 对 JSON 中的字符串字段做清洗 -- 底层使用 `jsoup` 处理潜在的恶意脚本片段 - -适用场景: - -- 富文本之外的大多数普通文本输入 -- 表单提交 -- JSON 请求体中的字符串字段 - -如果某些接口需要保留原始 HTML 内容,通常需要结合该模块的 XSS 配置做排除,而不是在业务层手工规避。 - -URL 白名单说明: - -- 支持通过 `rdms.xss.exclude-urls` 配置 URL 白名单,命中的 URL 会跳过 XSS 过滤(包括 Filter 与 JSON 字符串反序列化)。 -- 适合富文本、HTML 片段等需要保留原始内容的接口。 - -示例: - -```yaml -rdms: - xss: - enable: true - exclude-urls: - - /admin-api/rich-text/** - - /app-api/article/save -``` - -### 3.10 API 加解密 - -模块提供了接口级加解密能力,核心组成包括: - -- `@ApiEncrypt` -- `ApiEncryptFilter` -- `ApiDecryptRequestWrapper` -- `ApiEncryptResponseWrapper` - -适合理解为: - -- 请求进入时先解密 -- Controller / Service 内部仍然处理明文 -- 响应返回前再加密 - -开发使用上,一般是在需要加解密的接口上增加 `@ApiEncrypt`,其余请求不受影响。 - -示例: - -```java -@RestController -@RequestMapping("/secure/demo") -public class SecureDemoController { - - @PostMapping("/submit") - @ApiEncrypt - public CommonResult submit(@RequestBody DemoReqVO reqVO) { - return CommonResult.success(reqVO.getContent()); - } -} -``` - -这类能力适合: - -- 对外开放接口 -- 对传输内容有额外保护要求的场景 - -不适合把它当成通用权限控制手段。它解决的是“传输内容保护”,不是“访问授权”。 - -### 3.11 返回字段脱敏 - -模块内置了一组字段脱敏注解和序列化器,常见注解包括: - -- `@MobileDesensitize` -- `@EmailDesensitize` -- `@IdCardDesensitize` -- `@BankCardDesensitize` -- `@ChineseNameDesensitize` -- `@PasswordDesensitize` - -适用方式是:在返回对象字段上直接加注解,序列化为 JSON 时自动脱敏。 - -示例: - -```java -@Data -public class UserRespVO { - - private Long id; - - @MobileDesensitize - private String mobile; - - @EmailDesensitize - private String email; - - @ChineseNameDesensitize - private String nickname; -} -``` - -这样接口内部仍可使用原始值,真正对外输出时才变成脱敏后的内容。 - -### 3.12 Jackson JSON 定制 - -模块包含 `RdmsJacksonAutoConfiguration`,用于统一注册 JSON 序列化 / 反序列化相关定制能力。 - -从模块结构上看,它主要承担两类职责: - -- 承接本模块的 XSS、脱敏等 JSON 处理能力 -- 把 JSON 相关行为统一收口到 starter 中,避免各业务模块重复配置 `ObjectMapper` - -因此业务模块通常不需要自己再手动声明一套新的全局 Jackson 配置,除非有明确的覆盖需求。 - -### 3.13 Banner - -模块还包含 `RdmsBannerAutoConfiguration` 和 `BannerApplicationRunner`,用于在应用启动时输出框架 Banner 信息。 - -这部分属于展示型能力,不影响具体业务逻辑。 - -## 4. 开发人员上手 - -### 4.1 引入依赖 - -```xml - - com.njcn - rdms-spring-boot-starter-web - -``` - -本模块已经聚合: - -- `spring-boot-starter-web` -- `spring-boot-starter-validation` -- `knife4j-openapi3-jakarta-spring-boot-starter` -- `springdoc-openapi-starter-webmvc-ui` - -如果项目已经单独引入了这些依赖,需要留意是否存在重复配置。 - -### 4.2 准备基础配置 - -建议至少配置以下内容: - -```yaml -spring: - application: - name: rdms-system-server - -rdms: - web: - admin-api: - prefix: /admin-api - controller: "**.controller.admin.**" - app-api: - prefix: /app-api - controller: "**.controller.app.**" - admin-ui: - url: http://127.0.0.1:80 - - swagger: - title: RDMS API # 文档标题 - description: RDMS 接口文档 # 文档描述 - author: RDMS # 作者/团队 - version: 1.0.0 # 版本号 - url: https://example.com # 项目或团队主页 - email: dev@example.com # 联系邮箱 - license: Apache 2.0 # 协议名称 - license-url: https://www.apache.org/licenses/LICENSE-2.0.html # 协议地址 - - access-log: - enable: true -``` - -### 4.3 按约定放置 Controller - -如果希望自动带上前缀,需要把 Controller 放到约定包下: - -- 管理端:`xx.controller.admin.xx` -- 应用端:`xx.controller.app.xx` - -不符合这个包路径规则的 Controller,不会自动追加 `/admin-api` 或 `/app-api`。 - -### 4.4 统一返回 `CommonResult` - -Controller 写法建议保持统一: - -```java -@Tag(name = "部门管理") -@RestController -@RequestMapping("/dept") -public class DeptController { - - @GetMapping("/get") - @Operation(summary = "获得部门详情") - public CommonResult get(@RequestParam("id") Long id) { - return CommonResult.success(deptService.get(id)); - } -} -``` - -建议遵循: - -- 成功场景返回 `CommonResult.success(...)` -- 业务异常抛出 `ServiceException` -- 参数对象使用 `@Validated` / `@Valid` - -### 4.5 使用访问日志 - -多数情况下,不需要额外写代码,访问日志会自动生效。 - -如果需要补充操作名、操作类型、响应体记录等信息,可以加: - -```java -@ApiAccessLog(responseEnable = true) -``` - -建议同时补齐: - -- `@Tag` -- `@Operation` - -这样日志中的操作模块和操作名称会更完整;建议将 `@Tag` / `@Operation` 作为接口开发的必填规范。 - -### 4.6 使用加解密 - -只在需要的接口上加: - -```java -@ApiEncrypt -``` - -然后让调用方按约定传输密文即可。接口内部仍按明文对象开发,不需要在 Controller 内手工做解密逻辑。 - -### 4.7 使用字段脱敏 - -在返回对象字段上直接加注解即可: - -```java -@MobileDesensitize -private String mobile; -``` - -适合用户信息、联系方式、证件信息等对外展示场景。 - -### 4.8 使用 RestTemplate - -固定地址调用: - -```java -@Resource -private RestTemplate restTemplate; -``` - -服务名调用: - -```java -@Resource(name = "loadBalancedRestTemplate") -private RestTemplate loadBalancedRestTemplate; -``` - -### 4.9 开发时需要注意的几个点 - -- 这个模块不会自动把任意返回值包装成 `CommonResult`,需要显式返回。 -- 接口前缀是按包路径匹配出来的,不是看 Controller 名称。 -- 访问日志默认开启,但是否真正异步落库,还取决于日志相关 RPC 接口是否可用。 -- XSS、防脱敏、加解密都属于横切能力,原则上应该通过统一配置或注解使用,不建议在业务代码里重复造轮子。 -- 如果项目已经在网关层处理跨域、日志、安全头等逻辑,需要评估是否与本模块职责重叠。 - -## 5. 适合承载什么,不适合承载什么 - -适合放在这个模块里的能力: - -- Web 层公共配置 -- HTTP 请求/响应横切处理 -- 接口文档 -- 访问日志 -- 安全性输入输出处理 -- Web 基础工具注册 - -不适合放在这个模块里的能力: - -- 具体业务规则 -- 菜单、权限、数据权限等业务权限判断 -- 某个业务模块专属的 Controller 逻辑 -- 与单一业务强耦合的转换规则 - -从职责上看,这个模块更接近“Web 基础设施层”,而不是“业务功能层”。 diff --git a/rdms-framework/rdms-spring-boot-starter-websocket/README.md b/rdms-framework/rdms-spring-boot-starter-websocket/README.md deleted file mode 100644 index 66c1153..0000000 --- a/rdms-framework/rdms-spring-boot-starter-websocket/README.md +++ /dev/null @@ -1,142 +0,0 @@ -# rdms-spring-boot-starter-websocket - -## 1. 模块定位 - -`rdms-spring-boot-starter-websocket` 是 WebSocket 基础设施模块,用于统一处理连接、会话管理、消息分发与消息发送。 - -模块聚合的核心能力: - -- WebSocket 自动装配与路径注册 -- 登录用户绑定与会话管理 -- JSON 消息协议与监听器分发 -- 消息发送 - -## 2. 设计思路 - -- 统一 JSON 消息协议(`type` + `content`),通过 `type` 分发到对应监听器,降低业务耦合。 -- 通过 `WebSocketSessionManager` 统一管理会话,支持按用户类型/用户编号/会话 ID 进行推送。 -- 通过本地发送器支持单机消息推送。 -- 与安全体系结合,在握手阶段写入 `LoginUser`,便于后续鉴权与定向发送。 - -## 3. 功能模块 - -### 3.1 自动装配 - -`RdmsWebSocketAutoConfiguration` 会完成: - -- 注册 WebSocket 路径与处理器 -- 注册握手拦截器(默认 `LoginUserHandshakeInterceptor`) -- 注册 `WebSocketSessionManager` -- 放行 WebSocket 路径的安全校验 -- 注册消息发送器 - -### 3.2 JSON 消息协议 - -内置统一消息结构: - -```json -{ - "type": "notice", - "content": "{...}" -} -``` - -处理流程: - -- `JsonWebSocketMessageHandler` 解析 JSON -- 根据 `type` 选择对应 `WebSocketMessageListener` -- 将 `content` 反序列化为监听器泛型类型并交给业务处理 - -内置 `ping/pong`:收到 `ping` 会直接返回 `pong`。 - -### 3.3 会话管理 - -`WebSocketSessionManager` 支持: - -- 按 `sessionId` 获取会话 -- 按 `userType` 获取会话列表 -- 按 `userType + userId` 获取会话列表 - -用于定向推送和广播推送。 - -### 3.4 消息发送 - -统一使用 `WebSocketMessageSender` 发送消息: - -- 按用户推送 -- 按用户类型广播 -- 按会话 ID 推送 - -消息会被封装为统一的 `JsonWebSocketMessage` 后发送。 - -### 3.5 登录用户绑定 - -握手阶段会读取当前登录用户并写入 WebSocket Session: - -- 需要前端通过 `?token={token}` 形式携带令牌 -- `LoginUserHandshakeInterceptor` 会将 `LoginUser` 写入 Session -- `WebSocketFrameworkUtils` 可获取 `userId/userType` - -## 4. 开发人员上手 - -### 4.1 引入依赖 - -```xml - - com.njcn - rdms-spring-boot-starter-websocket - -``` - -### 4.2 基础配置 - -```yaml -rdms: - websocket: - enable: true - path: /ws -``` - -说明: - -- `path` 默认 `/ws` -- `enable` 默认 `true` - -### 4.3 连接方式 - -前端连接示例: - -``` -ws://{host}:{port}/ws?token={token} -``` - -### 4.4 编写消息监听器 - -```java -@Component -public class NoticeMessageListener implements WebSocketMessageListener { - - @Override - public void onMessage(WebSocketSession session, NoticeDTO message) { - // 处理消息 - } - - @Override - public String getType() { - return "notice"; - } -} -``` - -### 4.5 服务端推送 - -```java -@Resource -private WebSocketMessageSender webSocketMessageSender; - -public void push(Long userId, NoticeDTO notice) { - webSocketMessageSender.sendObject(UserTypeEnum.ADMIN.getValue(), userId, "notice", notice); -} -``` - - diff --git a/rdms-gateway/README.md b/rdms-gateway/README.md deleted file mode 100644 index cd6beb8..0000000 --- a/rdms-gateway/README.md +++ /dev/null @@ -1,143 +0,0 @@ -# rdms-gateway - -## 1. 模块定位 - -`rdms-gateway` 是 API 服务网关模块,基于 Spring Cloud Gateway(WebFlux)实现,负责统一入口的路由转发、鉴权透传、跨域处理、访问日志、灰度/标签负载与全局异常处理。 - -## 2. 设计思路 - -- 通过网关统一处理横切能力,业务服务只关注业务逻辑。 -- 鉴权只做“解析 + 透传”,是否需要登录交给后端服务判定。 -- 灰度/标签负载通过自定义 `grayLb://` 方案实现。 -- 访问日志默认打印到日志,后续可扩展为落库。 - -## 3. 功能模块 - -### 3.1 路由与文档聚合 - -- 路由配置集中在 `application.yaml` 的 `spring.cloud.gateway.server.webflux.routes`。 -- Knife4j Gateway 负责聚合各服务的 OpenAPI 文档。 - -### 3.2 Token 鉴权透传 - -`TokenAuthenticationFilter` 负责: - -- 从 `Authorization` 解析 Token -- 远程校验 Token 有效性 -- 校验通过后将 `login-user` 头透传给后端 -- 不阻断请求,是否需要登录由后端服务决定 - -### 3.3 跨域处理 - -`CorsFilter` 统一放行跨域请求,并处理 OPTIONS 预检请求。 - -### 3.4 访问日志 - -`AccessLogFilter` 记录: - -- 请求路径、方法、参数、Body、Headers -- 响应体、响应码 -- 耗时、用户信息 - -当前实现是打印到日志(控制台/文件),可扩展为落库。 - -### 3.5 灰度/标签负载 - -灰度发布(Gray/Canary)是指新旧版本并行运行,只让一小部分请求先进入新版本验证,稳定后再逐步扩大流量,最终全量切换。 - -在本项目中,灰度不是按“百分比随机分流”,而是**按请求头定向路由**: - -- 请求头 `version` 用于匹配实例 `metadata.version` -- 请求头 `tag` 用于匹配实例 `metadata.tag` -- 如果匹配不到目标实例,会回退到全量实例 -- 最终按 Nacos 权重随机选择实例 - -`grayLb://` + `GrayLoadBalancer` 规则: - -- header `version` 匹配实例 metadata `version` -- header `tag` 匹配实例 metadata `tag` -- 无匹配时回落到全量实例 - -注意: - -- 不带 `tag` 的请求会优先过滤掉带 `tag` 的实例,避免测试/灰度实例被默认流量命中 -- 灰度发布依赖请求头控制路由范围,需要由网关或内部调用方注入 `version/tag`,避免外部随意绕路 - -### 3.6 全局异常 - -`GlobalExceptionHandler` 将异常统一翻译为 `CommonResult` 返回。 - -### 3.7 Jackson 一致性 - -`GatewayJacksonAutoConfiguration` 统一 JSON 序列化策略(时间戳、Long -> Number 等)。 - -## 4. 开发人员上手 - -### 4.1 路由配置示例 - -```yaml -spring: - cloud: - gateway: - server: - webflux: - routes: - - id: system-admin-api - uri: grayLb://rdms-system-server - predicates: - - Path=/admin-api/system/** - filters: - - RewritePath=/admin-api/system/v3/api-docs, /v3/api-docs -``` - -### 4.2 鉴权透传 - -前端携带: - -``` -Authorization: Bearer -``` - -网关校验成功后,会透传 `login-user` 给后端服务。 - -### 4.3 灰度/标签路由 - -请求头示例: - -``` -version: 1.0.0 -tag: dev -``` - -服务实例需要在注册中心 metadata 中配置对应 `version/tag`。 - -### 4.4 WebSocket 路由 - -```yaml -- id: system-websocket - uri: grayLb://rdms-system-server - predicates: - - Path=/system/ws/** -``` - -### 4.5 文档聚合示例 - -```yaml -knife4j: - gateway: - enabled: true - routes: - - name: system-server - service-name: rdms-system-server - url: /admin-api/system/v3/api-docs -``` - -## 5. 关键类索引 - -- 入口:`src/main/java/com/njcn/rdms/gateway/GatewayServerApplication.java` -- 路由配置:`src/main/resources/application.yaml` -- 鉴权:`src/main/java/com/njcn/rdms/gateway/filter/security/TokenAuthenticationFilter.java` -- 灰度:`src/main/java/com/njcn/rdms/gateway/filter/grey/GrayLoadBalancer.java` -- 访问日志:`src/main/java/com/njcn/rdms/gateway/filter/logging/AccessLogFilter.java` -- 跨域:`src/main/java/com/njcn/rdms/gateway/filter/cors/CorsFilter.java` -- 全局异常:`src/main/java/com/njcn/rdms/gateway/handler/GlobalExceptionHandler.java` diff --git a/rdms-system/rdms-system-api/README.md b/rdms-system/rdms-system-api/README.md deleted file mode 100644 index b0d1005..0000000 --- a/rdms-system/rdms-system-api/README.md +++ /dev/null @@ -1,57 +0,0 @@ -# rdms-system-api - -**概述** -本模块定义了 System(系统)模块对其他模块开放的公共 RPC API 面。它是一个纯 API 的 jar 包:包含 Feign 客户端接口、请求/响应 DTO,以及枚举/常量。不包含 controller 或 service 的实现。 - -**这里包含什么** -- 位于 `com.njcn.rdms.module.system.api.*` 下的 Feign 客户端接口 -- 位于 `com.njcn.rdms.module.system.api.*.dto` 下的 DTO -- 位于 `com.njcn.rdms.module.system.enums.*` 下的系统枚举和常量 - -**API 分组** -- `config`:`ConfigApi`,用于根据 key 读取配置值 -- `dept`:`DeptApi` 和 `PostApi`,用于部门与岗位数据及校验 -- `dict`:`DictDataApi`,用于字典数据校验(继承 `DictDataCommonApi`) -- `file`:`FileApi`,用于文件创建与预签名 URL -- `logger`:`LoginLogApi` 和 `OperateLogApi`(继承 `OperateLogCommonApi`) -- `notify`:`NotifyMessageSendApi`,用于站内消息发送 -- `permission`:`PermissionApi`(继承 `PermissionCommonApi`)以及 `RoleApi` -- `user`:`AdminUserApi`,用于管理员用户查询与校验 -- `websocket`:`WebSocketSenderApi`,用于推送 WebSocket 消息 - -**关键常量** -- `ApiConstants.NAME = "system-server"`(必须与 system 服务的 `spring.application.name` 保持一致) -- `ApiConstants.PREFIX = "/rpc-api/system"` -- `ApiConstants.VERSION = "1.0.0"` - -**用法** -1. 在调用方模块中添加依赖。 - -```xml - - com.njcn - rdms-system-api - -``` - -2. 在调用方服务中启用 Feign 客户端扫描。 - -```java -@EnableFeignClients(basePackages = "com.njcn.rdms.module.system.api") -``` - -3. 注入并调用 API 接口。 - -```java -@Resource -private AdminUserApi adminUserApi; - -public AdminUserRespDTO loadUser(Long id) { - return adminUserApi.getUser(id).getCheckedData(); -} -``` - -**备注** -- 所有方法都返回 `CommonResult`。按需使用 `getCheckedData()` 或 `checkError()`。 -- 有些 API 提供了默认的辅助方法(例如 `FileApi.createFile(...)`、`WebSocketSenderApi.send(...)`)。 -- `DictDataApi`、`OperateLogApi`、`PermissionApi` 继承了来自 `rdms-framework` 的通用 RPC 接口。 diff --git a/rdms-system/rdms-system-boot/src/main/java/com/njcn/rdms/module/system/controller/admin/auth/AuthController.http b/rdms-system/rdms-system-boot/src/main/java/com/njcn/rdms/module/system/controller/admin/auth/AuthController.http deleted file mode 100644 index a274ec8..0000000 --- a/rdms-system/rdms-system-boot/src/main/java/com/njcn/rdms/module/system/controller/admin/auth/AuthController.http +++ /dev/null @@ -1,45 +0,0 @@ -### 请求 /login 接口 => 成功 -POST {{baseUrl}}/system/auth/login -Content-Type: application/json -tag: Yunai.local - -{ - "username": "admin", - "password": "admin123", - "uuid": "3acd87a09a4f48fb9118333780e94883", - "code": "1024" -} - -### 请求 /login 接口【加密 AES】 => 成功 -POST {{baseUrl}}/system/auth/login -Content-Type: application/json -tag: Yunai.local -X-API-ENCRYPT: true - -WvSX9MOrenyGfBhEM0g1/hHgq8ocktMZ9OwAJ6MOG5FUrzYF/rG5JF1eMptQM1wT73VgDS05l/37WeRtad+JrqChAul/sR/SdOsUKqjBhvvQx1JVhzxr6s8uUP67aKTSZ6Psv7O32ELxXrzSaQvG5CInzz3w6sLtbNNLd1kXe6Q= - -### 请求 /login 接口【加密 RSA】 => 成功 -POST {{baseUrl}}/system/auth/login -Content-Type: application/json -tag: Yunai.local -X-API-ENCRYPT: true - -e7QZTork9ZV5CmgZvSd+cHZk3xdUxKtowLM02kOha+gxHK2H/daU8nVBYS3+bwuDRy5abf+Pz1QJJGVAEd27wwrXBmupOOA/bhpuzzDwcRuJRD+z+YgiNoEXFDRHERxPYlPqAe9zAHtihD0ceub1AjybQsEsROew4C3Q602XYW0= - -### 请求 /login 接口 => 成功(无验证码) -POST {{baseUrl}}/system/auth/login -Content-Type: application/json - -{ - "username": "admin", - "password": "admin123" -} - -### 请求 /get-permission-info 接口 => 成功 -GET {{baseUrl}}/system/auth/get-permission-info -Authorization: Bearer {{token}} - -### 请求 /list-menus 接口 => 成功 -GET {{baseUrl}}/system/list-menus -Authorization: Bearer {{token}} -#Authorization: Bearer a6aa7714a2e44c95aaa8a2c5adc2a67a diff --git a/rdms-system/rdms-system-boot/src/main/java/com/njcn/rdms/module/system/controller/admin/auth/vo/AuthRegisterReqVO.java b/rdms-system/rdms-system-boot/src/main/java/com/njcn/rdms/module/system/controller/admin/auth/vo/AuthRegisterReqVO.java index 0ffd2fc..a9661c9 100644 --- a/rdms-system/rdms-system-boot/src/main/java/com/njcn/rdms/module/system/controller/admin/auth/vo/AuthRegisterReqVO.java +++ b/rdms-system/rdms-system-boot/src/main/java/com/njcn/rdms/module/system/controller/admin/auth/vo/AuthRegisterReqVO.java @@ -19,7 +19,7 @@ public class AuthRegisterReqVO extends CaptchaVerificationReqVO { @Size(min = 4, max = 30, message = "用户账号长度为 4-30 个字符") private String username; - @Schema(description = "用户昵称", requiredMode = Schema.RequiredMode.REQUIRED, example = "芋艿") + @Schema(description = "用户昵称", requiredMode = Schema.RequiredMode.REQUIRED, example = "awen") @NotBlank(message = "用户昵称不能为空") @Size(max = 30, message = "用户昵称长度不能超过 30 个字符") private String nickname; diff --git a/rdms-system/rdms-system-boot/src/main/java/com/njcn/rdms/module/system/controller/admin/dict/DictDataController.http b/rdms-system/rdms-system-boot/src/main/java/com/njcn/rdms/module/system/controller/admin/dict/DictDataController.http deleted file mode 100644 index f7890dc..0000000 --- a/rdms-system/rdms-system-boot/src/main/java/com/njcn/rdms/module/system/controller/admin/dict/DictDataController.http +++ /dev/null @@ -1,3 +0,0 @@ -### 请求 /menu/list 接口 => 成功 -GET {{baseUrl}}/system/dict-data/list-all-simple -Authorization: Bearer {{token}} diff --git a/rdms-system/rdms-system-boot/src/main/java/com/njcn/rdms/module/system/controller/admin/file/FileConfigController.http b/rdms-system/rdms-system-boot/src/main/java/com/njcn/rdms/module/system/controller/admin/file/FileConfigController.http deleted file mode 100644 index 1275a3d..0000000 --- a/rdms-system/rdms-system-boot/src/main/java/com/njcn/rdms/module/system/controller/admin/file/FileConfigController.http +++ /dev/null @@ -1,42 +0,0 @@ -### 请求 /system/file-config/create 接口 => 成功 -POST {{baseUrl}}/system/file-config/create -Content-Type: application/json -Authorization: Bearer {{token}} - -{ - "name": "S3 - 七牛云", - "remark": "", - "storage": 20, - "config": { - "accessKey": "b7yvuhBSAGjmtPhMFcn9iMOxUOY_I06cA_p0ZUx8", - "accessSecret": "kXM1l5ia1RvSX3QaOEcwI3RLz3Y2rmNszWonKZtP", - "bucket": "ruoyi-vue-pro", - "endpoint": "s3-cn-south-1.qiniucs.com", - "domain": "http://test.rdms.iocoder.cn", - "region": "oss-cn-beijing" - } -} - -### 请求 /system/file-config/update 接口 => 成功 -PUT {{baseUrl}}/system/file-config/update -Content-Type: application/json -Authorization: Bearer {{token}} - -{ - "id": 2, - "name": "S3 - 七牛云", - "remark": "", - "config": { - "accessKey": "b7yvuhBSAGjmtPhMFcn9iMOxUOY_I06cA_p0ZUx8", - "accessSecret": "kXM1l5ia1RvSX3QaOEcwI3RLz3Y2rmNszWonKZtP", - "bucket": "ruoyi-vue-pro", - "endpoint": "s3-cn-south-1.qiniucs.com", - "domain": "http://test.rdms.iocoder.cn", - "region": "oss-cn-beijing" - } -} - -### 请求 /system/file-config/test 接口 => 成功 -GET {{baseUrl}}/system/file-config/test?id=2 -Content-Type: application/json -Authorization: Bearer {{token}} diff --git a/rdms-system/rdms-system-boot/src/main/java/com/njcn/rdms/module/system/controller/admin/ip/AreaController.http b/rdms-system/rdms-system-boot/src/main/java/com/njcn/rdms/module/system/controller/admin/ip/AreaController.http deleted file mode 100644 index f6a9cc6..0000000 --- a/rdms-system/rdms-system-boot/src/main/java/com/njcn/rdms/module/system/controller/admin/ip/AreaController.http +++ /dev/null @@ -1,3 +0,0 @@ -### 获得地区树 -GET {{baseUrl}}/system/area/tree -Authorization: Bearer {{token}} diff --git a/rdms-system/rdms-system-boot/src/main/java/com/njcn/rdms/module/system/controller/admin/logger/OperateLogController.http b/rdms-system/rdms-system-boot/src/main/java/com/njcn/rdms/module/system/controller/admin/logger/OperateLogController.http deleted file mode 100644 index 9fdccac..0000000 --- a/rdms-system/rdms-system-boot/src/main/java/com/njcn/rdms/module/system/controller/admin/logger/OperateLogController.http +++ /dev/null @@ -1,3 +0,0 @@ -### 请求 /system/operate-log/page 接口 => 成功 -GET {{baseUrl}}/system/operate-log/page -Authorization: Bearer {{token}} diff --git a/rdms-system/rdms-system-boot/src/main/java/com/njcn/rdms/module/system/controller/admin/logger/vo/operatelog/OperateLogRespVO.java b/rdms-system/rdms-system-boot/src/main/java/com/njcn/rdms/module/system/controller/admin/logger/vo/operatelog/OperateLogRespVO.java index 0ee524a..8cb6f0c 100644 --- a/rdms-system/rdms-system-boot/src/main/java/com/njcn/rdms/module/system/controller/admin/logger/vo/operatelog/OperateLogRespVO.java +++ b/rdms-system/rdms-system-boot/src/main/java/com/njcn/rdms/module/system/controller/admin/logger/vo/operatelog/OperateLogRespVO.java @@ -29,7 +29,7 @@ public class OperateLogRespVO implements VO { @Schema(description = "用户编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "1024") @Trans(type = TransType.SIMPLE, target = AdminUserDO.class, fields = "nickname", ref = "userName") private Long userId; - @Schema(description = "用户昵称", requiredMode = Schema.RequiredMode.REQUIRED, example = "芋艿") + @Schema(description = "用户昵称", requiredMode = Schema.RequiredMode.REQUIRED, example = "awen") @ExcelProperty("操作人") private String userName; diff --git a/rdms-system/rdms-system-boot/src/main/java/com/njcn/rdms/module/system/controller/admin/notify/vo/message/NotifyMessageRespVO.java b/rdms-system/rdms-system-boot/src/main/java/com/njcn/rdms/module/system/controller/admin/notify/vo/message/NotifyMessageRespVO.java index 63e97c1..99a2c96 100644 --- a/rdms-system/rdms-system-boot/src/main/java/com/njcn/rdms/module/system/controller/admin/notify/vo/message/NotifyMessageRespVO.java +++ b/rdms-system/rdms-system-boot/src/main/java/com/njcn/rdms/module/system/controller/admin/notify/vo/message/NotifyMessageRespVO.java @@ -25,7 +25,7 @@ public class NotifyMessageRespVO { @Schema(description = "模板编码", requiredMode = Schema.RequiredMode.REQUIRED, example = "test_01") private String templateCode; - @Schema(description = "模版发送人名称", requiredMode = Schema.RequiredMode.REQUIRED, example = "芋艿") + @Schema(description = "模版发送人名称", requiredMode = Schema.RequiredMode.REQUIRED, example = "awen") private String templateNickname; @Schema(description = "模版内容", requiredMode = Schema.RequiredMode.REQUIRED, example = "测试内容") diff --git a/rdms-system/rdms-system-boot/src/main/java/com/njcn/rdms/module/system/controller/admin/oauth2/OAuth2ClientController.http b/rdms-system/rdms-system-boot/src/main/java/com/njcn/rdms/module/system/controller/admin/oauth2/OAuth2ClientController.http deleted file mode 100644 index 3ffe945..0000000 --- a/rdms-system/rdms-system-boot/src/main/java/com/njcn/rdms/module/system/controller/admin/oauth2/OAuth2ClientController.http +++ /dev/null @@ -1,22 +0,0 @@ -### 请求 /login 接口 => 成功 -POST {{baseUrl}}/system/oauth2-client/create -Content-Type: application/json -Authorization: Bearer {{token}} - -{ - "id": "1", - "secret": "admin123", - "name": "灿能源码", - "logo": "https://www.iocoder.cn/images/favicon.ico", - "description": "我是描述", - "status": 0, - "accessTokenValiditySeconds": 180, - "refreshTokenValiditySeconds": 8640, - "redirectUris": ["https://www.iocoder.cn"], - "autoApprove": true, - "authorizedGrantTypes": ["password"], - "scopes": ["user_info"], - "authorities": ["system:user:query"], - "resource_ids": ["1024"], - "additionalInformation": "{}" -} diff --git a/rdms-system/rdms-system-boot/src/main/java/com/njcn/rdms/module/system/controller/admin/oauth2/OAuth2OpenController.http b/rdms-system/rdms-system-boot/src/main/java/com/njcn/rdms/module/system/controller/admin/oauth2/OAuth2OpenController.http deleted file mode 100644 index b6564bc..0000000 --- a/rdms-system/rdms-system-boot/src/main/java/com/njcn/rdms/module/system/controller/admin/oauth2/OAuth2OpenController.http +++ /dev/null @@ -1,53 +0,0 @@ -### 请求 /system/oauth2/authorize 接口 => 成功 -GET {{baseUrl}}/system/oauth2/authorize?clientId=default -Authorization: Bearer {{token}} - -### 请求 /system/oauth2/authorize + token 接口 => 成功 -POST {{baseUrl}}/system/oauth2/authorize -Content-Type: application/x-www-form-urlencoded -Authorization: Bearer {{token}} - -response_type=token&client_id=default&scope={"user.read": true}&redirect_uri=https://www.iocoder.cn&auto_approve=true - -### 请求 /system/oauth2/authorize + code 接口 => 成功 -POST {{baseUrl}}/system/oauth2/authorize -Content-Type: application/x-www-form-urlencoded -Authorization: Bearer {{token}} - -response_type=code&client_id=default&scope={"user.read": true}&redirect_uri=https://www.iocoder.cn&auto_approve=false - -### 请求 /system/oauth2/token + code 接口 => 成功 -POST {{baseUrl}}/system/oauth2/token -Content-Type: application/x-www-form-urlencoded -Authorization: Basic ZGVmYXVsdDphZG1pbjEyMw== - -grant_type=authorization_code&redirect_uri=https://www.iocoder.cn&code=189956c07a174588a97157eabef2f93a - -### 请求 /system/oauth2/token + password 接口 => 成功 -POST {{baseUrl}}/system/oauth2/token -Content-Type: application/x-www-form-urlencoded -Authorization: Basic ZGVmYXVsdDphZG1pbjEyMw== - -grant_type=password&username=admin&password=admin123&scope=user.read - -### 请求 /system/oauth2/token + client_credentials 接口 => 成功 -POST {{baseUrl}}/system/oauth2/token -Content-Type: application/x-www-form-urlencoded -Authorization: Basic ZGVmYXVsdDphZG1pbjEyMw== - -grant_type=client_credentials&scope=user.read - -### 请求 /system/oauth2/token + refresh_token 接口 => 成功 -POST {{baseUrl}}/system/oauth2/token -Content-Type: application/x-www-form-urlencoded -Authorization: Basic ZGVmYXVsdDphZG1pbjEyMw== - -grant_type=refresh_token&refresh_token=00895465d6994f72a9d926ceeed0f588 - -### 请求 /system/oauth2/token + DELETE 接口 => 成功 -DELETE {{baseUrl}}/system/oauth2/token?token=ca8a188f464441d6949c51493a2b7596 -Authorization: Basic ZGVmYXVsdDphZG1pbjEyMw== - -### 请求 /system/oauth2/check-token 接口 => 成功 -POST {{baseUrl}}/system/oauth2/check-token?token=620d307c5b4148df8a98dd6c6c547106 -Authorization: Basic ZGVmYXVsdDphZG1pbjEyMw== diff --git a/rdms-system/rdms-system-boot/src/main/java/com/njcn/rdms/module/system/controller/admin/oauth2/OAuth2UserController.http b/rdms-system/rdms-system-boot/src/main/java/com/njcn/rdms/module/system/controller/admin/oauth2/OAuth2UserController.http deleted file mode 100644 index 60c705d..0000000 --- a/rdms-system/rdms-system-boot/src/main/java/com/njcn/rdms/module/system/controller/admin/oauth2/OAuth2UserController.http +++ /dev/null @@ -1,12 +0,0 @@ -### 请求 /system/oauth2/user/get 接口 => 成功 -GET {{baseUrl}}/system/oauth2/user/get -Authorization: Bearer 47f9c74ec11041f193b777ebb95c3b0d - -### 请求 /system/oauth2/user/update 接口 => 成功 -PUT {{baseUrl}}/system/oauth2/user/update -Content-Type: application/json -Authorization: Bearer 47f9c74ec11041f193b777ebb95c3b0d - -{ - "nickname": "灿能源码" -} diff --git a/rdms-system/rdms-system-boot/src/main/java/com/njcn/rdms/module/system/controller/admin/oauth2/vo/user/OAuth2UserInfoRespVO.java b/rdms-system/rdms-system-boot/src/main/java/com/njcn/rdms/module/system/controller/admin/oauth2/vo/user/OAuth2UserInfoRespVO.java index 0f096b7..1d36ea5 100644 --- a/rdms-system/rdms-system-boot/src/main/java/com/njcn/rdms/module/system/controller/admin/oauth2/vo/user/OAuth2UserInfoRespVO.java +++ b/rdms-system/rdms-system-boot/src/main/java/com/njcn/rdms/module/system/controller/admin/oauth2/vo/user/OAuth2UserInfoRespVO.java @@ -16,7 +16,7 @@ public class OAuth2UserInfoRespVO { @Schema(description = "用户编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "1") private Long id; - @Schema(description = "用户账号", requiredMode = Schema.RequiredMode.REQUIRED, example = "芋艿") + @Schema(description = "用户账号", requiredMode = Schema.RequiredMode.REQUIRED, example = "awen") private String username; @Schema(description = "用户昵称", requiredMode = Schema.RequiredMode.REQUIRED, example = "灿能") diff --git a/rdms-system/rdms-system-boot/src/main/java/com/njcn/rdms/module/system/controller/admin/oauth2/vo/user/OAuth2UserUpdateReqVO.java b/rdms-system/rdms-system-boot/src/main/java/com/njcn/rdms/module/system/controller/admin/oauth2/vo/user/OAuth2UserUpdateReqVO.java index bc141a0..38c13ba 100644 --- a/rdms-system/rdms-system-boot/src/main/java/com/njcn/rdms/module/system/controller/admin/oauth2/vo/user/OAuth2UserUpdateReqVO.java +++ b/rdms-system/rdms-system-boot/src/main/java/com/njcn/rdms/module/system/controller/admin/oauth2/vo/user/OAuth2UserUpdateReqVO.java @@ -15,7 +15,7 @@ import jakarta.validation.constraints.Size; @AllArgsConstructor public class OAuth2UserUpdateReqVO { - @Schema(description = "用户昵称", requiredMode = Schema.RequiredMode.REQUIRED, example = "芋艿") + @Schema(description = "用户昵称", requiredMode = Schema.RequiredMode.REQUIRED, example = "awen") @Size(max = 30, message = "用户昵称长度不能超过 30 个字符") private String nickname; diff --git a/rdms-system/rdms-system-boot/src/main/java/com/njcn/rdms/module/system/controller/admin/permission/MenuController.http b/rdms-system/rdms-system-boot/src/main/java/com/njcn/rdms/module/system/controller/admin/permission/MenuController.http deleted file mode 100644 index c073f7b..0000000 --- a/rdms-system/rdms-system-boot/src/main/java/com/njcn/rdms/module/system/controller/admin/permission/MenuController.http +++ /dev/null @@ -1,3 +0,0 @@ -### 请求 /menu/list 接口 => 成功 -GET {{baseUrl}}/system/menu/list -Authorization: Bearer {{token}} diff --git a/rdms-system/rdms-system-boot/src/main/java/com/njcn/rdms/module/system/controller/admin/permission/RoleController.http b/rdms-system/rdms-system-boot/src/main/java/com/njcn/rdms/module/system/controller/admin/permission/RoleController.http deleted file mode 100644 index 1ab4a75..0000000 --- a/rdms-system/rdms-system-boot/src/main/java/com/njcn/rdms/module/system/controller/admin/permission/RoleController.http +++ /dev/null @@ -1,37 +0,0 @@ -### /role/create 成功 -POST {{baseUrl}}/system/role/create -Authorization: Bearer {{token}} -Content-Type: application/json - -{ - "name": "测试角色", - "code": "test", - "sort": 0 -} - -### /role/update 成功 -POST {{baseUrl}}/system/role/update -Authorization: Bearer {{token}} -Content-Type: application/json - -{ - "id": 100, - "name": "测试角色", - "code": "test", - "sort": 10 -} -### /resource/delete 成功 -POST {{baseUrl}}/system/role/delete -Content-Type: application/x-www-form-urlencoded -Authorization: Bearer {{token}} - -roleId=14 - -### /role/get 成功 -GET {{baseUrl}}/system/role/get?id=100 -Content-Type: application/x-www-form-urlencoded -Authorization: Bearer {{token}} - -### /role/page 成功 -GET {{baseUrl}}/system/role/page?pageNo=1&pageSize=10 -Authorization: Bearer {{token}} diff --git a/rdms-system/rdms-system-boot/src/main/java/com/njcn/rdms/module/system/controller/admin/redis/RedisController.http b/rdms-system/rdms-system-boot/src/main/java/com/njcn/rdms/module/system/controller/admin/redis/RedisController.http deleted file mode 100644 index 1af3452..0000000 --- a/rdms-system/rdms-system-boot/src/main/java/com/njcn/rdms/module/system/controller/admin/redis/RedisController.http +++ /dev/null @@ -1,4 +0,0 @@ -### 请求 /system/redis/get-monitor-info 接口 => 成功 -GET {{baseUrl}}/system/redis/get-monitor-info -Authorization: Bearer {{token}} - diff --git a/rdms-system/rdms-system-boot/src/main/java/com/njcn/rdms/module/system/controller/admin/user/UserController.http b/rdms-system/rdms-system-boot/src/main/java/com/njcn/rdms/module/system/controller/admin/user/UserController.http deleted file mode 100644 index 1175c77..0000000 --- a/rdms-system/rdms-system-boot/src/main/java/com/njcn/rdms/module/system/controller/admin/user/UserController.http +++ /dev/null @@ -1,8 +0,0 @@ -### 请求 /system/user/page 接口 => 没有权限 -GET {{baseUrl}}/system/user/page?pageNo=1&pageSize=10 -Authorization: Bearer {{token}} -#Authorization: Bearer test100 - -### 请求 /system/user/page 接口(测试访问别的租户) -GET {{baseUrl}}/system/user/page?pageNo=1&pageSize=10 -Authorization: Bearer {{token}} diff --git a/rdms-system/rdms-system-boot/src/main/java/com/njcn/rdms/module/system/controller/admin/user/UserProfileController.http b/rdms-system/rdms-system-boot/src/main/java/com/njcn/rdms/module/system/controller/admin/user/UserProfileController.http deleted file mode 100644 index fe4dd7c..0000000 --- a/rdms-system/rdms-system-boot/src/main/java/com/njcn/rdms/module/system/controller/admin/user/UserProfileController.http +++ /dev/null @@ -1,3 +0,0 @@ -### 请求 /system/user/profile/get 接口 => 没有权限 -GET {{baseUrl}}/system/user/profile/get -Authorization: Bearer {{token}} diff --git a/rdms-system/rdms-system-boot/src/main/java/com/njcn/rdms/module/system/controller/admin/user/vo/profile/UserProfileRespVO.java b/rdms-system/rdms-system-boot/src/main/java/com/njcn/rdms/module/system/controller/admin/user/vo/profile/UserProfileRespVO.java index 554b377..ba1975b 100644 --- a/rdms-system/rdms-system-boot/src/main/java/com/njcn/rdms/module/system/controller/admin/user/vo/profile/UserProfileRespVO.java +++ b/rdms-system/rdms-system-boot/src/main/java/com/njcn/rdms/module/system/controller/admin/user/vo/profile/UserProfileRespVO.java @@ -19,7 +19,7 @@ public class UserProfileRespVO { @Schema(description = "用户账号", requiredMode = Schema.RequiredMode.REQUIRED, example = "rdms") private String username; - @Schema(description = "用户昵称", requiredMode = Schema.RequiredMode.REQUIRED, example = "芋艿") + @Schema(description = "用户昵称", requiredMode = Schema.RequiredMode.REQUIRED, example = "awen") private String nickname; @Schema(description = "用户邮箱", example = "rdms@iocoder.cn") diff --git a/rdms-system/rdms-system-boot/src/main/java/com/njcn/rdms/module/system/controller/admin/user/vo/profile/UserProfileUpdateReqVO.java b/rdms-system/rdms-system-boot/src/main/java/com/njcn/rdms/module/system/controller/admin/user/vo/profile/UserProfileUpdateReqVO.java index ff282d8..9c12611 100644 --- a/rdms-system/rdms-system-boot/src/main/java/com/njcn/rdms/module/system/controller/admin/user/vo/profile/UserProfileUpdateReqVO.java +++ b/rdms-system/rdms-system-boot/src/main/java/com/njcn/rdms/module/system/controller/admin/user/vo/profile/UserProfileUpdateReqVO.java @@ -13,7 +13,7 @@ import org.hibernate.validator.constraints.URL; @Data public class UserProfileUpdateReqVO { - @Schema(description = "用户昵称", example = "芋艿") + @Schema(description = "用户昵称", example = "awen") @Size(max = 30, message = "用户昵称长度不能超过 30 个字符") private String nickname; diff --git a/rdms-system/rdms-system-boot/src/main/java/com/njcn/rdms/module/system/controller/admin/user/vo/user/UserRespVO.java b/rdms-system/rdms-system-boot/src/main/java/com/njcn/rdms/module/system/controller/admin/user/vo/user/UserRespVO.java index 7382ad4..06bba51 100644 --- a/rdms-system/rdms-system-boot/src/main/java/com/njcn/rdms/module/system/controller/admin/user/vo/user/UserRespVO.java +++ b/rdms-system/rdms-system-boot/src/main/java/com/njcn/rdms/module/system/controller/admin/user/vo/user/UserRespVO.java @@ -24,7 +24,7 @@ public class UserRespVO{ @ExcelProperty("用户名称") private String username; - @Schema(description = "用户昵称", requiredMode = Schema.RequiredMode.REQUIRED, example = "芋艿") + @Schema(description = "用户昵称", requiredMode = Schema.RequiredMode.REQUIRED, example = "awen") @ExcelProperty("用户昵称") private String nickname; diff --git a/rdms-system/rdms-system-boot/src/main/java/com/njcn/rdms/module/system/controller/admin/user/vo/user/UserSaveReqVO.java b/rdms-system/rdms-system-boot/src/main/java/com/njcn/rdms/module/system/controller/admin/user/vo/user/UserSaveReqVO.java index 4508732..ed757d8 100644 --- a/rdms-system/rdms-system-boot/src/main/java/com/njcn/rdms/module/system/controller/admin/user/vo/user/UserSaveReqVO.java +++ b/rdms-system/rdms-system-boot/src/main/java/com/njcn/rdms/module/system/controller/admin/user/vo/user/UserSaveReqVO.java @@ -28,7 +28,7 @@ public class UserSaveReqVO { @DiffLogField(name = "用户账号") private String username; - @Schema(description = "用户昵称", requiredMode = Schema.RequiredMode.REQUIRED, example = "芋艿") + @Schema(description = "用户昵称", requiredMode = Schema.RequiredMode.REQUIRED, example = "awen") @Size(max = 30, message = "用户昵称长度不能超过30个字符") @DiffLogField(name = "用户昵称") private String nickname;