QC System Deep Fix: - HardRuleEngine: add null tolerance + field availability pre-check (skipped status) - SkillRunner: baseline data merge for follow-up events + field availability check - QcReportService: record-level pass rate calculation + accurate LLM XML report - iitBatchController: legacy log cleanup (eventId=null) + upsert RecordSummary - seed-iit-qc-rules: null/empty string tolerance + applicableEvents config V3.1 Architecture Design (docs only, no code changes): - QC engine V3.1 plan: 5-level data structure (CDISC ODM) + D1-D7 dimensions - Three-batch implementation strategy (A: foundation, B: bubbling, C: new engines) - Architecture team review: 4 whitepapers reviewed + feedback doc + 4 critical suggestions - CRA Agent strategy roadmap + CRA 4-tool explanation doc for clinical experts Project Member Management: - Cross-tenant member search and assignment (remove tenant restriction) - IIT project detail page enhancement with tabbed layout (KB + members) - IitProjectContext for business-side project selection - System-KB route access control adjustment for project operators Frontend: - AdminLayout sidebar menu restructure - IitLayout with project context provider - IitMemberManagePage new component - Business-side pages adapt to project context Prisma: - 2 new migrations (user-project RBAC + is_demo flag) - Schema updates for project member management Made-with: Cursor
16 KiB
CRA Agent V3.0 — 用户权限与多租户三阶段实施计划
版本: v1.0
日期: 2026-02-28
状态: 待实施
前置依赖: P0 + P1 已完成(质控流水线 + ChatOrchestrator)
关联文档:
1. 问题背景
1.1 当前系统存在的三个核心问题
问题 A:业务端 CRA 质控平台无法运行
- 所有业务端页面(驾驶舱、eQuery、报告、AI 对话)从 URL 参数
?projectId=xxx获取项目 ID - 没有任何机制把登录用户与 IIT 项目关联,导致页面显示"请先选择一个项目"
- 侧边栏项目信息为硬编码假数据("IIT-2026-001")
问题 B:运营管理端 IIT 项目管理定位不当
- "IIT 项目管理"与"Prompt 管理""租户管理""用户管理"混在同一菜单中
- 普通项目运营人员不应看到平台级管理功能
- 药企/医院客户的项目管理人员不应看到其他企业的项目
问题 C:用户-项目关联机制缺失
IitUserMapping.systemUserId存储的是随意字符串(当前值为"FengZhiBo"),未关联平台User表IitProject没有tenantId字段,无法实现租户隔离- IIT API 路由没有认证中间件,无访问控制
1.2 当前数据库实际状态
-- iit_schema.projects(1 条记录)
id: test0102-pd-study | name: test0207 | status: active
-- iit_schema.user_mappings(1 条记录)
system_user_id: FengZhiBo | wecom_user_id: FengZhiBo | role: PI
-- public.users(1 条记录)
id: user-mock-001 | name: 测试用户 | role: user
(注:此用户为占位假数据,非真实用户)
两张表之间没有任何外键关联。
1.3 当前角色体系
平台级角色(public.users.role):
| 角色 | 说明 | 当前使用 |
|---|---|---|
| SUPER_ADMIN | 平台超级管理员 | 可访问运营管理端全部功能 |
| PROMPT_ENGINEER | Prompt 工程师 | 可访问运营管理端 |
| HOSPITAL_ADMIN | 医院管理员 | 已定义,未使用 |
| PHARMA_ADMIN | 药企管理员 | 已定义,未使用 |
| DEPARTMENT_ADMIN | 科室管理员 | 已定义,未使用 |
| USER | 普通用户 | 默认角色 |
IIT 项目级角色(iit_schema.user_mappings.role):
| 角色 | 说明 | 当前使用 |
|---|---|---|
| PI | 主要研究者 | 已使用 |
| Sub-I | 次要研究者 | 已定义 |
| CRC | 临床研究协调员 | 已定义 |
| CRA | 临床监查员 | 已定义 |
| DM | 数据管理员 | 已定义 |
| Statistician | 统计师 | 已定义 |
| Other | 其他 | 已定义 |
缺失的角色:
| 角色 | 层级 | 说明 |
|---|---|---|
| PM | IIT 项目级 | 项目管理员,负责项目配置和管理 |
| IIT_OPERATOR | 平台级 | IIT 项目运营,负责为客户创建和配置项目(Phase 2 实现) |
2. 三阶段实施计划
Phase 1(立即) Phase 2(近期) Phase 3(中期)
让业务端能跑 用户-项目关联 多租户隔离
───────────────── → ──────────────────── → ─────────────────
自动选中活跃项目 systemUserId 关联 User IitProject 加 tenantId
Provider 注入上下文 /my-projects API 项目按租户过滤
管理端侧边栏分组 项目角色权限矩阵 PHARMA_ADMIN 自助管理
配置功能补全 IIT 路由加认证
IIT_OPERATOR 平台角色
3. Phase 1:让业务端能跑(预估 0.5 天)
3.1 目标
- CRA 质控平台业务端页面能正常加载和显示数据
- 运营管理端 IIT 项目管理菜单位置合理
- 项目配置功能补全(定时质控、变量清单)
3.2 方案
3.2.1 创建 IitProjectContext Provider
设计原则: 通用方案,适用于用户关联 0 / 1 / N 个项目的所有场景。
核心逻辑:
用户进入 /iit → IitProjectProvider 初始化
→ 调用 GET /api/v1/admin/iit-projects 获取项目列表
→ 过滤 status = 'active'
→ 0 个 → 显示空状态页:"暂无关联的 IIT 项目"
→ 1 个 → 自动选中,不弹选择器
→ N 个 → 恢复 localStorage 上次选择;若无记录,默认选第一个
→ 用户可随时通过侧边栏顶部下拉选择器切换项目
→ 切换后写入 localStorage,子页面自动刷新
→ 通过 useIitProject() hook 暴露:
{ projectId, project, projects, loading, switchProject(id) }
项目选择器 UI:
放在 IitLayout 侧边栏顶部(替换当前硬编码的项目名称),具体表现:
- 1 个项目:显示项目名称(纯文本,无下拉箭头)
- N 个项目:显示当前项目名 + 下拉箭头,点击弹出 Select 选择器
- 选择器选项:项目名称 + 项目编号(如 "原发性痛经队列研究 / IIT-2026-001")
- 切换项目后,所有子页面通过 Context 自动获取新 projectId 并刷新数据
改动文件:
| 文件 | 改动 |
|---|---|
frontend-v2/src/modules/iit/context/IitProjectContext.tsx |
新建 Context + Provider + useIitProject hook |
frontend-v2/src/modules/iit/IitLayout.tsx |
集成 Provider,侧边栏顶部改为项目选择器 |
frontend-v2/src/modules/iit/pages/DashboardPage.tsx |
删除 URL 参数读取,改用 useIitProject() |
frontend-v2/src/modules/iit/pages/EQueryPage.tsx |
同上 |
frontend-v2/src/modules/iit/pages/ReportsPage.tsx |
同上 |
frontend-v2/src/modules/iit/pages/AiStreamPage.tsx |
同上 |
frontend-v2/src/modules/iit/pages/AiChatPage.tsx |
同上 |
3.2.2 管理端侧边栏三区重组(方案 B:逻辑拆分)
决策背景: 概念上存在三种管理职能(平台管理、项目运营、商务运营),但当前阶段团队规模小,同一人可能跨职能操作。采用方案 B(逻辑拆分):保留单一 /admin 入口,侧边栏按职能分为三个带标题的菜单组,通过 RBAC 控制每个角色看到哪些组。未来用户群体分化后可平滑升级为物理拆分(独立路由 + 独立 Layout)。
侧边栏结构(SUPER_ADMIN 视角,看到全部):
┌─ 平台管理 ─────────────┐
│ 运营概览 │
│ Prompt 管理 │
│ 系统知识库 │
│ LLM 配置 │
│ 系统设置 │
├─ 项目运营 ─────────────┤
│ IIT 项目管理 │
├─ 商务运营 ─────────────┤
│ 租户管理 │
│ 用户管理 │
└────────────────────────┘
各角色可见性:
| 菜单组 | SUPER_ADMIN | PROMPT_ENGINEER | IIT_OPERATOR | PHARMA_ADMIN |
|---|---|---|---|---|
| 平台管理 | 全部 | Prompt 管理 + 系统知识库 | - | - |
| 项目运营 | 全部项目 | - | 全部项目 | 本租户项目 |
| 商务运营 | 全部 | - | - | - |
改动文件: frontend-v2/src/framework/layout/AdminLayout.tsx
3.2.3 项目配置功能补全
A. 定时质控配置 UI
数据库 iit_schema.projects 已有 cron_enabled 和 cron_expression 字段,前端未暴露。
在 IitProjectDetailPage.tsx 的"REDCap 配置"Tab 中增加:
- 定时质控开关(Switch,绑定
cronEnabled) - Cron 表达式输入(Input + 预设选项:"每天 8:00" / "每周一 9:00" / 自定义)
B. 变量清单 Tab
后端 GET /:id/field-metadata API 已存在。在 IitProjectDetailPage.tsx 新增第 5 个 Tab"变量清单",展示 REDCap 变量表格。
C. PM 角色新增
在 iitUserMappingService.ts 的 getRoleOptions() 中添加:
{ value: 'PM', label: '项目管理员 (PM)' }
3.3 Phase 1 不做的事
- 不改数据库 Schema
- 不做用户-项目关联(systemUserId 改造)
- 不加认证中间件
- 不做租户隔离
4. Phase 2:用户-项目关联 + 角色权限(预估 2 天)
4.1 目标
- 登录用户自动看到自己关联的 IIT 项目
- IIT 项目角色对应明确的权限
- IIT API 路由有认证保护
- 运营管理端按角色控制菜单可见性
- 新增 IIT_OPERATOR 平台角色,使项目运营人员无需 SUPER_ADMIN 权限即可创建和配置 IIT 项目
4.2 数据模型改动
4.2.1 IitUserMapping 增强
model IitUserMapping {
// ... 现有字段 ...
systemUserId String @map("system_user_id") // 改为必须是 User.id
// 新增:
userId String? @map("user_id") // 平台 User 表外键(可选,渐进式关联)
// 新增关联
user User? @relation(fields: [userId], references: [id])
}
迁移策略:
- 新增
userId字段(nullable),不破坏现有数据 - 新建用户映射时要求填写真实
userId - 旧数据逐步补齐
4.2.2 IitProject 增强(为 Phase 3 预留)
model IitProject {
// ... 现有字段 ...
// 新增:
tenantId String? @map("tenant_id") // 租户归属(Phase 3 正式启用)
}
4.3 API 改动
4.3.1 新增接口
GET /api/v1/iit/my-projects
逻辑:
- 从 JWT Token 获取当前用户
userId - 查询
IitUserMapping WHERE userId = :userId - 返回关联的项目列表 + 用户在每个项目中的角色
响应示例:
{
"projects": [
{
"id": "test0102-pd-study",
"name": "test0207",
"status": "active",
"myRole": "PI",
"description": "原发性痛经队列研究"
}
]
}
4.3.2 IIT 路由加认证
所有 /api/v1/admin/iit-projects 路由添加 authenticate 中间件。
4.3.3 项目级权限检查
中间件:requireProjectRole(projectId, allowedRoles)
→ 查 IitUserMapping WHERE projectId AND userId
→ 检查 role 是否在 allowedRoles 中
4.4 项目角色权限矩阵
| 功能 | PM | PI | CRA | CRC | DM | Sub-I | Statistician |
|---|---|---|---|---|---|---|---|
| 查看驾驶舱 | 读 | 读 | 读 | 读 | 读 | 读 | 读 |
| 查看/处理 eQuery | 读写 | 读 | 读写 | 读写 | 读 | 读 | - |
| 查看报告 | 读 | 读 | 读 | 读 | 读 | 读 | 读 |
| AI 对话 | 是 | 是 | 是 | 是 | 是 | 是 | 是 |
| 项目配置(REDCap/规则/通知/知识库) | 读写 | - | - | - | - | - | - |
| 查看变量清单 | 读 | 读 | 读 | 读 | 读 | 读 | 读 |
| 管理质控规则 | 读写 | - | 读 | - | 读 | - | - |
4.5 管理端菜单权限(承接 Phase 1 三区分组)
Phase 1 已完成侧边栏三区分组的 UI 结构,Phase 2 加入真正的 RBAC 控制:
| 角色 | 可进入 /admin | 平台管理 | 项目运营 | 商务运营 |
|---|---|---|---|---|
| SUPER_ADMIN | 是 | 全部 | 全部项目 | 全部 |
| PROMPT_ENGINEER | 是 | Prompt + 知识库 | - | - |
| IIT_OPERATOR(本阶段新增) | 是 | - | 全部项目 | - |
| PHARMA_ADMIN | 是 | - | 本租户项目 | - |
| HOSPITAL_ADMIN | 是 | - | 本租户项目 | - |
| USER | 否 | - | - | - |
实现方式:
- 路由守卫:
/admin入口检查user.role是否在允许列表中 - 侧边栏渲染:根据
user.role过滤menuItems,角色看不到的组直接不渲染 - API 层:每个管理端 API 添加
authenticate+requireRole(allowedRoles)中间件
4.6 前端改动
IitProjectContext改为调用/api/v1/iit/my-projects- 根据
myRole控制业务端 Tab 可见性(PM 看到更多设置入口) AdminLayout.tsx按user.role过滤侧边栏菜单组可见性(Phase 1 已分组,此处加权限判断)/admin路由入口添加角色守卫
5. Phase 3:多租户隔离(预估 1.5 天)
5.1 目标
- IIT 项目按租户隔离,药企只能看到自己的项目
- PHARMA_ADMIN 可在运营管理端管理自己租户的 IIT 项目
- SUPER_ADMIN 可跨租户查看所有项目
5.2 数据模型
启用 Phase 2 预留的 tenantId 字段,设为必填:
model IitProject {
tenantId String @map("tenant_id")
tenant Tenant @relation(fields: [tenantId], references: [id])
}
5.3 API 改动
所有项目查询接口添加租户过滤:
-- 非 SUPER_ADMIN:
SELECT * FROM iit_schema.projects WHERE tenant_id = :currentUserTenantId
-- SUPER_ADMIN:
SELECT * FROM iit_schema.projects -- 无限制
5.4 项目创建流程
Phase 2 完成后:
SUPER_ADMIN 或 IIT_OPERATOR 创建项目 → 手动指定 tenantId(选择为哪个客户创建)
Phase 3 完成后(客户自助):
PHARMA_ADMIN 创建项目 → 自动绑定自己的 tenantId
5.5 前端改动
- 运营管理端项目列表:SUPER_ADMIN 看到全部 + 租户筛选器;PHARMA_ADMIN 只看到自己的
- 项目创建表单:SUPER_ADMIN 需选择租户;PHARMA_ADMIN 自动绑定
6. 实施优先级总结
| 阶段 | 核心内容 | 预估工时 | 数据库改动 | 前置条件 |
|---|---|---|---|---|
| Phase 1 | Provider + 管理端分组 + 配置补全 | 0.5 天 | 无 | 无 |
| Phase 2 | userId 关联 + /my-projects + 角色权限 + 认证 + IIT_OPERATOR 角色 | 2 天 | 新增 userId 列、tenantId 列、IIT_OPERATOR 角色枚举 | Phase 1 |
| Phase 3 | 租户隔离 + PHARMA_ADMIN 自助 | 1.5 天 | tenantId 改为必填 | Phase 2 |
Phase 1 立即可执行的原因
- 0 数据库改动
- 只改前端代码
- 当前只有 1 个活跃项目 + 1 个测试用户,自动选中即可
- 让业务端立即可演示
Phase 2 的触发条件
- 有真实用户需要登录系统
- 需要区分不同用户看到不同项目
- 需要保护 API 安全
Phase 3 的触发条件
- 有多个客户(药企/医院)同时使用系统
- 需要租户级别的数据隔离
7. 附录:改动文件清单
Phase 1
| 文件 | 类型 | 说明 |
|---|---|---|
frontend-v2/src/modules/iit/context/IitProjectContext.tsx |
新建 | 项目上下文 Provider |
frontend-v2/src/modules/iit/IitLayout.tsx |
修改 | 集成 Provider + 真实项目信息 |
frontend-v2/src/modules/iit/pages/DashboardPage.tsx |
修改 | 改用 useIitProject() |
frontend-v2/src/modules/iit/pages/EQueryPage.tsx |
修改 | 改用 useIitProject() |
frontend-v2/src/modules/iit/pages/ReportsPage.tsx |
修改 | 改用 useIitProject() |
frontend-v2/src/modules/iit/pages/AiStreamPage.tsx |
修改 | 改用 useIitProject() |
frontend-v2/src/modules/iit/pages/AiChatPage.tsx |
修改 | 改用 useIitProject() |
frontend-v2/src/framework/layout/AdminLayout.tsx |
修改 | 侧边栏分组 |
frontend-v2/src/modules/admin/pages/IitProjectDetailPage.tsx |
修改 | 定时质控配置 + 变量清单 Tab |
backend/src/modules/admin/iit-projects/iitUserMappingService.ts |
修改 | 添加 PM 角色 |
Phase 2
| 文件 | 类型 | 说明 |
|---|---|---|
backend/prisma/schema.prisma |
修改 | IitUserMapping 加 userId、IitProject 加 tenantId |
backend/src/modules/iit-manager/routes/index.ts |
修改 | 加 /my-projects 路由 + authenticate 中间件 |
backend/src/common/auth/auth.middleware.ts |
修改 | 新增 requireProjectRole 中间件 |
backend/prisma/schema.prisma (Role enum) |
修改 | 新增 IIT_OPERATOR 角色枚举值 |
frontend-v2/src/modules/iit/context/IitProjectContext.tsx |
修改 | 改调 /my-projects |
frontend-v2/src/framework/layout/AdminLayout.tsx |
修改 | 按角色控制菜单组可见性(IIT_OPERATOR 只看项目运营) |
Phase 3
| 文件 | 类型 | 说明 |
|---|---|---|
backend/prisma/schema.prisma |
修改 | tenantId 改为必填 |
backend/src/modules/admin/iit-projects/iitProjectService.ts |
修改 | 查询加租户过滤 |
frontend-v2/src/modules/admin/pages/IitProjectListPage.tsx |
修改 | 租户筛选器 |
frontend-v2/src/modules/admin/pages/IitProjectDetailPage.tsx |
修改 | 创建时选择租户 |