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:
2026-08-05 15:32:17 +08:00
co-authored by Claude
parent 159d6362af
commit 020f112124
20 changed files with 1196 additions and 0 deletions
+6
View File
@@ -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]]。
+17
View File
@@ -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 401body `"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、密文 Base641024位。中转 `RsaEncryptUtil` 已对齐。
+22
View File
@@ -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 无日志。
- **Redisredisx)**:是独立库,无自动注入。手动 `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]]。
+16
View File
@@ -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`jeecgSpring3
- 登录/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`