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
19 KiB
新旧系统集成方案
版本: v4.0
创建日期: 2026-02-27
最后更新: 2026-02-27(Wrapper 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 反代) |
| 部署 | 阿里云 SAE(Serverless) | 阿里云 ECS(8.154.22.149) |
| 技术栈 | Node.js + React 19 + PostgreSQL | Spring Boot 1.4.1 + Java 8 + MySQL |
| 前端部署 | SAE 静态资源 | Nginx 直接 serve 静态文件 |
| 认证方式 | JWT(Access 2h + Refresh 7d) | 自定义 MD5 Token(MySQL 存储,3.5 天有效期) |
| 数据库 | PostgreSQL 15(14 个 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 运行(非 Docker),PID 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 1(ECS 侦察),最终确认采用 方案 B+E 组合:
- 方案 E(Token 注入):新系统后端直接写 Token 到老系统 MySQL,实现自动登录
- 方案 B(iframe 嵌入):在新系统内用 iframe 加载老系统页面,隐藏老系统导航
3.1 为什么选 Token 注入而非调用登录 API
| 对比项 | 调用登录 API(方案 C) | 直接写 Token(方案 E) |
|---|---|---|
| 需要知道用户密码 | 是 | 否 |
| 用户改了老系统密码 | 失败 | 不受影响 |
| 依赖老系统 API 可用 | 是 | 否(只依赖 MySQL) |
| 可同时创建账号 | 否 | 是 |
| 安全性 | 密码在网络传输 | Token 仅写入 DB |
3.2 整体流程
用户在新系统点击"研究管理"或"统计分析工具"
│
▼
新系统前端 → POST /api/legacy/auth(携带 JWT)
│
▼
新系统后端:
1. 从 JWT 解出当前用户手机号
2. 连接老系统 MySQL(xzyx_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 URL(token/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 + footer(bridge 的 CSS 注入已覆盖,可选回退) |
/home/work/www/xunzhengyixue/tool/js/appajax.js |
iframe 检测:隐藏 menu + footer(bridge 的 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.html(www.xunzhengyixue.com,设 Cookie + 注入 CSS)
└─ inner iframe(www.xunzhengyixue.com/index.html 或 /tool.html)
↑ 同源,bridge 可直接操作其 DOM
优势:
- 本地开发环境(localhost)可正常测试
- 所有样式定制集中在 bridge 页面一处
- 不再需要修改老系统的
common.js/appajax.js(之前的改动可选回退) - 未来可随时调整老系统的外观,无需登录 ECS 改文件
token-bridge.html 工作流程:
- 检查 Storage Access API(
document.hasStorageAccess())- 同站(生产环境):自动授权 → 直接进入步骤 2
- 跨站(本地开发)首次:显示"点击授权并继续"按钮 → 用户点击 →
requestStorageAccess()→ 浏览器授权(缓存 30 天) - 跨站(本地开发)后续:已缓存授权 → 直接进入步骤 2
- 读取 URL 参数,在
.xunzhengyixue.com域下设置 Cookie(SameSite=None; Secure) - 创建内部 iframe 加载目标页面
- 每次内部 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
需要知道用户在老系统的密码。如果用户改了密码就会失败。已被方案 E(Token 注入)取代。
方案 D:Nginx 反向代理统一入口
在新系统 Nginx 配置 /legacy/* 反代到老系统。路径改写复杂,流量多一次中转,SAE 环境配置不便。
附录 A: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 加载目标 |