feat: 完成数据中心转发网关核心功能(真实联调通过)
核心功能(已连接真实源服务器验证):
- /sys/watchUserLogin 本地处理:RSA解密验证设备密码(防伪造) + 单飞换token + Redis缓存
- 业务接口经本地通配Controller手动HttpUtils透传至源服务器 + 401检测清缓存
- token失效三重自愈:10秒探活 + 预过期刷新 + 被动401兜底
配置隔离(dotenv风格)+ 安全:
- .env 存敏感信息不入库;app.yml 用 ${KEY} 占位符引用;EnvLoader启动前加载
- 移除fastjson(规避0day),JSON改用Solon内置snack4
联调修复多个Solon实际API问题(配置文件名/YAML插件/打包/redisx/注解路径等)
Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,6 @@
|
||||
# 项目记忆索引
|
||||
|
||||
- [架构变更-改用本地透传](architecture-local-passthrough.md) — 为何不用 Cloud Gateway 路由透传
|
||||
- [Solon 技术栈坑点](solon-pitfalls.md) — redisx/定时任务/HTTP API 的正确用法
|
||||
- [jeecg token 机制](jeecg-token-mechanism.md) — 源服务器 token 滑动续期原理
|
||||
- [源服务器位置](source-server-location.md) — 源代码与部署地址
|
||||
@@ -0,0 +1,12 @@
|
||||
---
|
||||
name: architecture-local-passthrough
|
||||
description: 为何用本地通配Controller+HttpUtils手动透传,而非solon.cloud.gateway.routes路由透传
|
||||
metadata:
|
||||
type: project
|
||||
---
|
||||
|
||||
本项目业务接口透传采用「本地通配 Controller(`@Mapping("/**")`)+ HttpUtils 手动透传」,而非 `solon.cloud.gateway.routes` 路由透传。
|
||||
|
||||
**Why:** 调研发现 Solon Cloud Gateway 的路由匹配中,`Path=/**` 兜底路由会**覆盖本地 Controller**,导致 `/sys/watchUserLogin` 被透传到源服务器而非本地处理——这会使规避风控的核心需求失效。而用 CloudGatewayFilter 短路拦截 watchUserLogin 时,获取 reactive 请求体(password)的 API 复杂不确定。
|
||||
|
||||
**How to apply:** 保持 TokenController(`@Mapping("/sys/watchUserLogin")` 具体路径)与 GatewayController(`@Mapping("/**")` 通配)并存,依赖 Solon 本地路由器「具体路径优先于通配」的特性。若未来要换回流式透传,需先确认本地 Controller 能优先于路由匹配。相关坑点见 [[solon-pitfalls]]。
|
||||
@@ -0,0 +1,17 @@
|
||||
---
|
||||
name: jeecg-token-mechanism
|
||||
description: 源服务器jeecg的token机制(JWT+Redis双层、滑动续期),决定中转刷新策略
|
||||
metadata:
|
||||
type: reference
|
||||
---
|
||||
|
||||
源服务器 jeecg 的 token 机制(调研 `data-center-boot-spring3` 代码得出):
|
||||
|
||||
- **JWT 带 exp**(`JwtUtil.sign` 用 `withExpiresAt`,exp = 生成时间 + 3.5天),但 **exp 不是硬性过期**。
|
||||
- **真正失效条件**:Redis key `prefix_user_token:<token>` 被删除(主动 logout 或 7天不活跃)。Redis TTL = 604800秒(7天)。
|
||||
- **滑动续期**:`ShiroRealm.jwtTokenRefresh()` 每次请求(Redis key 存在时)重置 TTL 为 7 天,且 JWT exp 到期时静默重签。
|
||||
- **失效返回**:HTTP 401,body `"Token失效,请重新登录!"`,token 从 `X-Access-Token` 请求头读取。
|
||||
|
||||
**对中转的影响**:中转每 10 秒探活会刷新源服务器 Redis TTL,token 实际不会自然过期(除非被主动 logout)。预过期刷新基于 JWT exp,因滑动续期很少触发,作为双保险保留。探活失效/被动401 后必须 `forceRefresh`(先清缓存再换新),否则命中失效 token 死循环。
|
||||
|
||||
**RSA**:源服务器 `RSAEncryptUtils.decrypt1` 用 `Cipher.getInstance("RSA")` = `RSA/ECB/PKCS1Padding`,公钥 X.509、私钥 PKCS#8、明文 UTF-8、密文 Base64,1024位。中转 `RsaEncryptUtil` 已对齐。
|
||||
@@ -0,0 +1,22 @@
|
||||
---
|
||||
name: solon-pitfalls
|
||||
description: Solon框架的正确API用法(redisx/定时任务/HTTP/注解/配置文件/打包),踩坑后实测确认
|
||||
metadata:
|
||||
type: reference
|
||||
---
|
||||
|
||||
联调实测确认的 Solon 4.0.4 正确用法(多次踩坑后验证,务必牢记):
|
||||
|
||||
- **配置文件名**:Solon 默认读 `app.yml`,**不是** `application.yml`。配置文件名错会导致所有配置读不到(null)。
|
||||
- **YAML 插件**:默认只读 .properties,需加 `org.noear:solon-config-yaml` 依赖才能读 .yml,否则报 `profile is not supported`。
|
||||
- **打包插件**:用 `org.noear:solon-maven-plugin`(repackage 目标),**不要用** maven-assembly-plugin(版本号易错,且非 Solon 标配)。插件版本用 3.10.5(与 solon 主框架 4.0.4 不同版本线)。
|
||||
- **日志**:需加 `slf4j-simple` 实现,否则 SLF4J 退化为 NOP 无日志。
|
||||
- **Redis(redisx)**:是独立库,无自动注入。手动 `new RedisClient(Properties)`,Properties key 为 `server`/`db`/`password`/`user`/`maxTotal`。**不要**用 `@Inject("${redis}") RedisClient`。API:`getBucket().store/get`、`open(session -> session.key(k).delete())`。
|
||||
- **定时任务注解**:`@Scheduled`/`@EnableScheduling` 在 `org.noear.solon.scheduling.annotation`(来自 `solon-scheduling` 依赖,非 `solon-scheduling-simple`)。
|
||||
- **Context**:路径 `org.noear.solon.core.handle.Context`(非 `org.noear.solon.core.Context`)。方法:`path()`、`queryString()`、`method()`、`body()`、`headerNames()`、`header(name)`、`status(int)`、`headerSet(k,v)`、`output(String)`。
|
||||
- **HTTP 客户端**:`solon-net-httputils`。`HttpUtils.http(url).exec(method)` 返回 HttpResponse。状态码 `resp.code()`(非 status)。body 用 `bodyOfJson(String)`(无 `body(String)` 单参重载,只有 `body(byte[])`/`body(content,contentType)`)。响应体 `resp.bodyAsString()`。
|
||||
- **占位符**:`${KEY}` 能解析 `System.setProperty` 注入的值(联调验证)。
|
||||
- **JSON 返回**:Controller 返回 HashMap 会被 snack4 序列化为 `{key=value}` 非标准 JSON。返回标准 JSON 用 snack4 ONode(`new ONode().asObject()` + `.set(k,v)`)+ `@Produces("application/json")`。**不使用 fastjson**(规避 0day 漏洞),JSON 处理统一用 Solon 内置 snack4(`org.noear.snack4.ONode`)。
|
||||
- **snack4 API**:解析 `ONode.ofJson(str)`;取值 `node.getOrNull(key).getString()`/`.getLong()`;构造对象 `new ONode().asObject().set(k,v)`;序列化 `ONode.serialize(obj)` 或 `node.toJson()`。
|
||||
|
||||
关联 [[architecture-local-passthrough]]、[[jeecg-token-mechanism]]。
|
||||
@@ -0,0 +1,16 @@
|
||||
---
|
||||
name: source-server-location
|
||||
description: 源服务器jeecg代码位置、部署地址、接口账号等环境信息
|
||||
metadata:
|
||||
type: reference
|
||||
---
|
||||
|
||||
- **源服务器部署地址**(测试):`http://10.10.10.228:29999`(业务接口 + 换token + 探活目标)
|
||||
- **源服务器代码**:`E:\giteaspace\data-center-boot-spring3`(jeecg,Spring3)
|
||||
- 登录/token:`jeecg-module-system/jeecg-system-biz/.../controller/LoginController.java`(`/sys/watchUserLogin`、`/sys/getToken`、`/sys/getThirdToken`)
|
||||
- RSA 工具:`jeecg-boot-base-core/.../util/RSAEncryptUtils.java`
|
||||
- token 失效:`jeecg-boot-base-core/.../config/shiro/`(JwtFilter/ShiroRealm)
|
||||
- **Redis**(复用 228 服务器):`10.10.10.228:6379`,密码 `Aa135790!123`,中转用 db11(与源服务器 db 隔离)
|
||||
- **接口账号**(所有设备共用):username=`watchUser`,password=`aECrgTES@@q32`
|
||||
- **探活接口**:`/watch/dcWatchEmeMobile/list`(带 `X-Access-Token` 头,200=有效/401=失效)
|
||||
- **方案文档**:`E:\data\ob_data\myob\11 健康长庆\数据中心转发网关优化\数据中心转发网关优化.md`
|
||||
Reference in New Issue
Block a user