Files
AIclinicalresearch/docs/09-架构实施/旧版系统集成/01-新旧系统集成方案.md
HaHafeng c3f7d54fdf feat(platform): Implement legacy system integration with Wrapper Bridge architecture
Complete integration of the old clinical research platform (www.xunzhengyixue.com)
into the new AI platform via Token injection + iframe embedding:

Backend:
- Add legacy-bridge module (MySQL pool, auth service, routes)
- POST /api/v1/legacy/auth: JWT -> phone lookup -> Token injection into old MySQL
- Auto-create user in old system if not found (matched by phone number)

Frontend:
- LegacySystemPage: iframe container with Bridge URL construction
- ResearchManagement + StatisticalTools entry components
- Module registry updated from external links to iframe embed mode

ECS (token-bridge.html deployed to www.xunzhengyixue.com):
- Wrapper Bridge: sets cookies within same-origin context
- Storage Access API for cross-site dev environments
- CSS injection: hide old system nav/footer, remove padding gaps
- Inner iframe loads target page with full DOM access (same-origin)

Key technical decisions:
- Token injection (direct MySQL write) instead of calling login API
- Wrapper Bridge instead of parent-page cookie setting (cross-origin fix)
- Storage Access API + SameSite=None;Secure for third-party cookie handling
- User isolation guaranteed by phone number matching

Documentation:
- Integration plan v4.0 with full implementation record
- Implementation summary with 6 pitfalls documented
- System status guide updated (ST module now integrated)

Tested: Local E2E verified - auto login, research management, 126 statistical
tools, report generation, download, UI layout all working correctly

Made-with: Cursor
2026-02-27 21:54:38 +08:00

419 lines
19 KiB
Markdown
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# 新旧系统集成方案
> **版本:** v4.0
> **创建日期:** 2026-02-27
> **最后更新:** 2026-02-27Wrapper Bridge 架构 + Storage Access API + 本地 E2E 验证通过)
> **维护者:** 开发团队
> **状态:** ✅ Phase 0-2 全部完成,本地开发环境 E2E 验证通过,待部署生产环境
---
## 1. 背景
### 1.1 两套系统概览
| 维度 | 新系统AI临床研究平台 | 老系统(循证医学平台) |
|------|------------------------|----------------------|
| 域名 | `iit.xunzhengyixue.com` | `www.xunzhengyixue.com` |
| API 域名 | `iit.xunzhengyixue.com/api` (SAE) | `api.xunzhengyixue.com` (ECS Nginx 反代) |
| 部署 | 阿里云 SAEServerless | 阿里云 ECS`8.154.22.149` |
| 技术栈 | Node.js + React 19 + PostgreSQL | Spring Boot 1.4.1 + Java 8 + MySQL |
| 前端部署 | SAE 静态资源 | Nginx 直接 serve 静态文件 |
| 认证方式 | JWTAccess 2h + Refresh 7d | 自定义 MD5 TokenMySQL 存储3.5 天有效期) |
| 数据库 | PostgreSQL 1514 个 Schema | MariaDB/MySQL`xzyx_online` |
### 1.2 老系统模块
| 模块 | 入口地址 | 集成状态 |
|------|---------|---------|
| 研究管理 | `https://www.xunzhengyixue.com/index.html` | Phase 0 外链已完成 → Phase 2 将改为 iframe 内嵌 |
| 统计分析工具 | `https://www.xunzhengyixue.com/tool.html` | Phase 0 外链已完成 → Phase 2 将改为 iframe 内嵌 |
| AI 问答 | 老系统内 | 已在新系统完全重写AIA 模块),无需集成 |
### 1.3 约束条件
- Java 团队已离职,未正式交接源码
- 后找到一份源码,经 ECS 侦察确认 **与生产环境完全一致**
- 线上系统可通过 ECS SSH 访问MySQL 数据库可完全读写
- 两系统同属 `xunzhengyixue.com` 根域Cookie 可共享)
### 1.4 用户关系
- 两套系统的用户是 **同一批人**,由运营人员统一开账号
- 老系统按用户隔离数据(每个用户有自己的研究项目)
- 自动登录必须以用户本人身份进行,不能使用公共账号
---
## 2. ECS 侦察结果Phase 1 - 已完成)
> 2026-02-27 通过 SSH 登录 `8.154.22.149` 实地验证
### 2.1 部署架构
```
┌─────────────────────────────────────────┐
│ ECS 8.154.22.149 │
│ │
www.xunzhengyixue.com ──► Nginx:443 │
│ │ │
│ ├─ / → 静态文件 │
│ │ /home/work/www/xunzhengyixue/ │
│ │ │
│ └─ /api-proxy/ → rag.xunzhengyixue│
│ │
api.xunzhengyixue.com ──► Nginx:443 │
│ └─ / → proxy_pass 127.0.0.1:8899 │
│ │ │
│ Java JAR (Spring Boot) │
│ XZYXServer-0.0.1-SNAPSHOT.jar │
│ -Xms800m -Xmx4000m │
│ 运行自 2025 年至今 │
│ │ │
│ MariaDB/MySQL :3306 │
│ xzyx_online │
└─────────────────────────────────────────┘
```
### 2.2 关键确认事项
| 项目 | 侦察结果 |
|------|---------|
| 源码一致性 | **生产 `common.js` 与本地源码 100% 一致** |
| 前端静态文件路径 | `/home/work/www/xunzhengyixue/` |
| 要修改的文件 | `/home/work/www/xunzhengyixue/static/js/common.js` |
| Java 部署方式 | 直接 JAR 运行(非 DockerPID 66548 |
| API 地址 | `https://api.xunzhengyixue.com``config.js` 确认) |
| Cookie 域名 | **`xunzhengyixue.com`**`config.js` 退出登录逻辑确认) |
| Cookie 名称 | `token`, `nickname`, `id`3 个) |
| 登录检查逻辑 | 仅检查 `$.cookie("nickname")` 是否存在 |
| API 认证逻辑 | `$.cookie("token")``Authorization` 请求头 |
| X-Frame-Options | **未设置** → iframe 嵌入可行 |
| Nginx 改 common.js | **无需重启 Java**,静态文件改完即生效 |
### 2.3 认证机制详解
```
Token 生成公式MD5("KyKz1.0:" + userId + ":" + timestamp)
Token 存储位置MySQL u_user_token 表
Token 有效期:可配置(生产 5000 分钟 ≈ 3.5 天)
Token 传递方式Cookie → JS 读取 → Authorization 请求头
老系统 AuthInterceptor 验证流程:
1. 从 Authorization header 取 Token
2. 查 u_user_token 表是否存在该 Token
3. 检查是否过期gen_time + timeout
4. 不涉及密码验证 ← 这是直接写 Token 方案可行的根本原因
```
### 2.4 对集成友好的 6 个关键发现
| 发现 | 对集成的意义 |
|------|-------------|
| **无 CSRF 保护** | 可以从新系统直接 POST 调用 API |
| **CORS 全开**`Access-Control-Allow-Origin: *` | 新系统前端可直接 fetch 调用老系统 API |
| **无 X-Frame-Options** | iframe 嵌入完全可行 |
| **Token 仅查表验证,不校验密码** | 直接写 Token 到 DB 即可,绕过密码 |
| **同根域名 `.xunzhengyixue.com`** | 新系统 `iit.*` 可设置 Cookie 供 `www.*` 读取 |
| **前端纯静态 + 后端分离** | 改 `common.js` 无需碰 Java 代码或重启服务 |
---
## 3. 最终方案Token 注入 + iframe 嵌入
经过 Phase 0外链验证和 Phase 1ECS 侦察),最终确认采用 **方案 B+E 组合**
- **方案 EToken 注入)**:新系统后端直接写 Token 到老系统 MySQL实现自动登录
- **方案 Biframe 嵌入)**:在新系统内用 iframe 加载老系统页面,隐藏老系统导航
### 3.1 为什么选 Token 注入而非调用登录 API
| 对比项 | 调用登录 API方案 C | 直接写 Token方案 E |
|--------|----------------------|----------------------|
| 需要知道用户密码 | 是 | **否** |
| 用户改了老系统密码 | 失败 | **不受影响** |
| 依赖老系统 API 可用 | 是 | **否**(只依赖 MySQL |
| 可同时创建账号 | 否 | **是** |
| 安全性 | 密码在网络传输 | Token 仅写入 DB |
### 3.2 整体流程
```
用户在新系统点击"研究管理"或"统计分析工具"
新系统前端 → POST /api/legacy/auth携带 JWT
新系统后端:
1. 从 JWT 解出当前用户手机号
2. 连接老系统 MySQLxzyx_online
3. SELECT id FROM u_user_info WHERE phone = 手机号
├─ 不存在 → INSERT 创建账号(默认密码 MD5
└─ 存在 → 获取 userId
4. 生成 Token = MD5("KyKz1.0:" + userId + ":" + Date.now())
5. INSERT INTO u_user_token (user_id, token, gen_time, user_role)
6. 返回 { token, nickname, id, userRole }
新系统前端:
7. 构建 Bridge URLtoken/nickname/id/userRole/redirect 作为参数)
8. iframe src = https://www.xunzhengyixue.com/token-bridge.html?...
Bridge 页面www.xunzhengyixue.com 域内执行):
9. Storage Access API 检查(同站自动授权,跨站需一次性用户点击)
10. document.cookie = "token=xxx; domain=.xunzhengyixue.com"(同域设置)
11. 创建内部 iframe 加载目标页面(/index.html 或 /tool.html
12. 每次内部页面加载后,注入自定义 CSS隐藏导航栏、页脚等
老系统页面Bridge 内部 iframe同源
13. 读取 Cookie nickname → 存在 → 正常渲染
14. AJAX 请求带 Cookie 中的 token → AuthInterceptor 验证通过
用户看到自己的研究项目iframe 内),新系统导航栏保持在顶部
```
### 3.3 账号同步策略
| 场景 | 处理方式 |
|------|---------|
| 用户在老系统已有账号 | 直接注入 Token使用原有账号 |
| 用户在老系统无账号 | 自动创建INSERT u_user_info默认密码 `123456` 的 MD5 |
| 用户改了老系统密码 | **不影响**Token 注入不依赖密码 |
| 用户在老系统被删除 | 自动重新创建 |
匹配规则:以 **手机号phone** 作为两套系统的用户关联键。
---
## 4. 实施计划
### Phase 0 — 外链跳转(已完成)
顶部导航加外链按钮,点击后 `window.open()` 打开老系统新标签页。
**已改动文件:**
| 文件 | 改动 |
|------|------|
| `frontend-v2/src/framework/modules/types.ts` | 新增 `externalUrl?: string` 字段 |
| `frontend-v2/src/framework/modules/moduleRegistry.ts` | 新增「研究管理」模块 + 「统计分析工具」加 `externalUrl` |
| `frontend-v2/src/framework/layout/TopNavigation.tsx` | 外部模块 `window.open` + 外链图标 + 跳过权限检查 |
| `frontend-v2/src/App.tsx` | 外部模块跳过内部路由注册 |
### Phase 1 — ECS 侦察(已完成)
SSH 登录 ECS验证部署架构、Nginx 配置、Cookie 机制、源码一致性。
**结论:** 全部确认,方案可行。详见第 2 节。
### Phase 2 — 新系统代码改动清单
**后端新增文件:**
| 文件 | 说明 |
|------|------|
| `backend/src/modules/legacy-bridge/legacy-auth.service.ts` | 连接老系统 MySQL查询用户、写入 token |
| `backend/src/modules/legacy-bridge/routes.ts` | `POST /api/v1/legacy/auth` 接口 |
**后端改动文件:**
| 文件 | 改动 |
|------|------|
| `backend/.env` | 新增 `LEGACY_MYSQL_*` 连接配置 |
| `backend/src/index.ts` | 注册 `/api/v1/legacy` 路由 |
**前端新增文件:**
| 文件 | 说明 |
|------|------|
| `frontend-v2/src/modules/legacy/LegacySystemPage.tsx` | iframe 容器页面(调后端注入 token → 加载 iframe |
| `frontend-v2/src/modules/legacy/ResearchManagement.tsx` | 研究管理入口(指向 index.html |
| `frontend-v2/src/modules/legacy/StatisticalTools.tsx` | 统计分析工具入口(指向 tool.html |
**前端改动文件:**
| 文件 | 改动 |
|------|------|
| `frontend-v2/src/framework/modules/types.ts` | 新增 `legacyUrl` 字段 |
| `frontend-v2/src/framework/modules/moduleRegistry.ts` | 研究管理/统计分析模块改为 iframe 内嵌模式 |
| `frontend-v2/src/App.tsx` | legacy 模块路由注册 |
**ECS 改动文件:**
| 文件 | 改动 |
|------|------|
| `/home/work/www/xunzhengyixue/token-bridge.html` | **新增**Wrapper Bridge 页面(设 Cookie + 内嵌 iframe + 注入 CSS |
| `/home/work/www/xunzhengyixue/static/js/common.js` | iframe 检测:隐藏 header + footerbridge 的 CSS 注入已覆盖,可选回退) |
| `/home/work/www/xunzhengyixue/tool/js/appajax.js` | iframe 检测:隐藏 menu + footerbridge 的 CSS 注入已覆盖,可选回退) |
### Phase 2 — Token 注入 + iframe 嵌入(已完成)
| 步骤 | 内容 | 位置 | 状态 |
|------|------|------|------|
| 2.1 | 新系统后端添加 MySQL 连接(老系统数据库) | `backend/` | ✅ 已完成 |
| 2.2 | 新增 `POST /api/v1/legacy/auth` 接口 | `backend/` | ✅ 已完成 |
| 2.3 | 前端创建 iframe 容器页面组件 | `frontend-v2/` | ✅ 已完成 |
| 2.4 | 修改模块注册:从外链改为内嵌 iframe 页面 | `frontend-v2/` | ✅ 已完成 |
| 2.5 | ECS 部署 Token Bridge 页面 | ECS 服务器 | ✅ 已部署 |
| 2.6 | Token 注入测试 + iframe 显示测试 | 浏览器 | ✅ 已验证 |
### Phase 2.5 — Wrapper Bridge 架构升级
**动机:** 原方案由父页面(新系统)设置 Cookie但在 localhost 开发环境无法跨域设置
`.xunzhengyixue.com` 的 Cookie。改为 Wrapper Bridge 方案后Cookie 在老系统域名内
由 bridge 页面自己设置,同时利用同源 DOM 访问注入自定义 CSS。
**架构:**
```
新系统 iframe
└─ token-bridge.htmlwww.xunzhengyixue.com设 Cookie + 注入 CSS
└─ inner iframewww.xunzhengyixue.com/index.html 或 /tool.html
↑ 同源bridge 可直接操作其 DOM
```
**优势:**
- 本地开发环境localhost可正常测试
- 所有样式定制集中在 bridge 页面一处
- 不再需要修改老系统的 `common.js` / `appajax.js`(之前的改动可选回退)
- 未来可随时调整老系统的外观,无需登录 ECS 改文件
**`token-bridge.html` 工作流程:**
1. 检查 Storage Access API`document.hasStorageAccess()`
- 同站(生产环境):自动授权 → 直接进入步骤 2
- 跨站(本地开发)首次:显示"点击授权并继续"按钮 → 用户点击 → `requestStorageAccess()` → 浏览器授权(缓存 30 天)
- 跨站(本地开发)后续:已缓存授权 → 直接进入步骤 2
2. 读取 URL 参数,在 `.xunzhengyixue.com` 域下设置 Cookie`SameSite=None; Secure`
3. 创建内部 iframe 加载目标页面
4. 每次内部 iframe 导航后,注入自定义 CSS
- 隐藏 `#header-navbar``#footer-bar`(主页面)
- 隐藏 `#menu``#footer`(工具详情页)
- 清除 `body``#page-wrapper` 的顶部间距
**源码位置:** `backend/src/modules/legacy-bridge/token-bridge.html`
**浏览器兼容性:**
| 环境 | 结果 | 说明 |
|------|------|------|
| 生产环境(同站) | ✅ 自动工作 | `iit.*``www.*` 是 same-site |
| 本地开发普通模式 | ✅ 自动工作 | Storage Access API 自动授权 |
| 本地开发无痕模式 | ❌ 不可用 | Chrome 无痕模式禁止所有第三方 Cookie |
### Phase 2 — ECS 历史操作记录
> 以下 `common.js` 和 `appajax.js` 的改动在 Bridge 升级后**已冗余**bridge 的 CSS 注入覆盖了相同功能)。
> 保留这些改动作为双重保障;如需回退,用 `.bak` 文件恢复即可。
**文件 1`/home/work/www/xunzhengyixue/static/js/common.js`**
`navigation:function () {` 开头加入的 iframe 检测代码。
**文件 2`/home/work/www/xunzhengyixue/tool/js/appajax.js`**
在文件开头加入的 iframe 检测代码。
### Phase 2 — 踩坑记录
| 问题 | 原因 | 修复 |
|------|------|------|
| Token 注入后 tool.html 正常,但 index.html 跳转登录页 | `gen_time` 字段用了 MySQL `NOW()`datetime 格式),老系统期望 Java `System.currentTimeMillis()`(毫秒时间戳) | 改为 `Date.now()`,同时先 DELETE 旧 token 再 INSERT |
| 研究管理没有左侧导航 | iframe 检测代码隐藏了 `#nav-col``return` 导致侧边栏内容未生成 | 只隐藏 header + footer不 return |
| 工具详情页仍显示顶部导航 | 工具详情页是独立前端,不加载 `common.js`,使用 `tool/js/appajax.js` | 在 `appajax.js` 开头也加 iframe 检测 |
| localhost 无法测试 iframe 嵌入 | 父页面(localhost)无法为 `.xunzhengyixue.com` 设 Cookie | 升级为 Wrapper Bridge由 bridge 页面在同域内设 Cookie |
| Bridge 设了 Cookie 但老系统仍跳登录页 | 浏览器第三方 Cookie 隔离cross-site iframe 的 `document.cookie` 被静默拒绝 | 添加 `SameSite=None; Secure` + Storage Access API 双重保障 |
| 隐藏导航栏后页面顶部多出空白条 | `#header-navbar` 隐藏后 body 的 `padding-top` 仍在 | Bridge CSS 注入额外清除 `body``#page-wrapper` 的 padding-top/margin-top |
---
## 5. 验证结果2026-02-27 本地 E2E 测试)
| 测试项 | 结果 |
|------|------|
| Token 注入 + 自动登录 | ✅ 通过 |
| 研究管理index.html— 左侧导航正常 | ✅ 通过 |
| 统计分析工具tool.html— 126 个工具 | ✅ 通过 |
| 工具详情页tool/isttest1.html 等) | ✅ 通过 |
| 统计分析 + 出报告 + 下载报告 | ✅ 通过 |
| 导航栏/页脚隐藏 + 顶部间距消除 | ✅ 通过 |
| 用户隔离(不同用户看不到彼此项目) | ✅ 设计保证(按手机号绑定) |
| 普通模式 Storage Access API | ✅ 自动授权 |
| 无痕模式 | ❌ 预期不可用(浏览器限制) |
## 6. 后续步骤
| 事项 | 说明 |
|------|------|
| 部署新系统后端到 SAE | 确保 `.env``LEGACY_MYSQL_*` 配置指向 ECS MySQL |
| 部署新系统前端到 SAE | 包含 `LegacySystemPage` 等新组件 |
| 生产端到端验证 | 同站环境(`iit.*``www.*`),应自动工作无需 Storage Access API |
| 可选:回退 ECS JS 改动 | `common.js``appajax.js` 的 iframe 检测代码已被 bridge CSS 覆盖,可用 `.bak` 恢复 |
| 监控 | 关注 `/api/v1/legacy/auth` 接口调用量和失败率 |
---
## 7. 曾考虑但未选用的方案
### 方案 A直接外链跳转
作为 Phase 0 已实施。用户需手动登录老系统,两个标签页切换,体验不佳。
### 方案 C前端直调登录 API
需要知道用户在老系统的密码。如果用户改了密码就会失败。已被方案 EToken 注入)取代。
### 方案 DNginx 反向代理统一入口
在新系统 Nginx 配置 `/legacy/*` 反代到老系统。路径改写复杂流量多一次中转SAE 环境配置不便。
---
## 附录 ANginx 配置摘要(生产环境)
```nginx
# api.xunzhengyixue.com → Java 后端
server {
listen 443 ssl;
server_name api.xunzhengyixue.com;
location / {
proxy_pass http://127.0.0.1:8899;
proxy_cookie_path / /;
}
}
# www.xunzhengyixue.com → 静态文件
server {
listen 443 ssl;
server_name xunzhengyixue.com www.xunzhengyixue.com;
root /home/work/www/xunzhengyixue;
index index.html;
location /api-proxy/ {
proxy_pass https://rag.xunzhengyixue.com/api/v1/;
}
}
```
## 附录 B老系统源码关键文件索引
| 文件 | 位置(相对于 Java_old_system/XZYXServer | 说明 |
|------|----------------------------------------|------|
| 登录 Controller | `src/main/java/com/xzyx/controller/UserController.java` | 3 个登录端点 |
| Token 生成 | `src/main/java/com/xzyx/biz/service/impl/MysqlTokenValidator.java` | MD5 Token 公式 |
| 认证拦截器 | `src/main/java/com/xzyx/framework/AuthInterceptor.java` | 仅查表验证 Token |
| CORS 配置 | `src/main/java/com/xzyx/framework/CorsFilter.java` | 全开(`*` |
| MVC 配置 | `src/main/java/com/xzyx/framework/MvcConfig.java` | 拦截路径注册 |
| 前端导航+登录检查 | ECS `/home/work/www/xunzhengyixue/static/js/common.js` | **已修改**iframe 检测 |
| 工具详情页 JS | ECS `/home/work/www/xunzhengyixue/tool/js/appajax.js` | **已修改**iframe 检测 |
| 前端 API 配置 | ECS `/home/work/www/xunzhengyixue/static/js/config.js` | Cookie 域名逻辑 |
| 前端 HTTP 工具 | ECS `/home/work/www/xunzhengyixue/static/js/request.js` | Token → Authorization |
| 前端入口页 | ECS `/home/work/www/xunzhengyixue/index.html`, `tool.html` | iframe 加载目标 |
## 附录 C老系统数据库表结构
详见 [02-服务器与数据库配置说明.md](./02-服务器与数据库配置说明.md)