# CRA Agent V3.0 — 用户权限与多租户三阶段实施计划 > **版本:** v1.0 > **日期:** 2026-02-28 > **状态:** 待实施 > **前置依赖:** P0 + P1 已完成(质控流水线 + ChatOrchestrator) > **关联文档:** > - [V3.0 全新开发计划](./V3.0全新开发计划.md) > - [统一数字 CRA 质控平台 PRD](./统一数字%20CRA%20质控平台产品需求文档(PRD).md) --- ## 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 增强 ```prisma 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 预留) ```prisma model IitProject { // ... 现有字段 ... // 新增: tenantId String? @map("tenant_id") // 租户归属(Phase 3 正式启用) } ``` ### 4.3 API 改动 #### 4.3.1 新增接口 ``` GET /api/v1/iit/my-projects ``` 逻辑: 1. 从 JWT Token 获取当前用户 `userId` 2. 查询 `IitUserMapping WHERE userId = :userId` 3. 返回关联的项目列表 + 用户在每个项目中的角色 响应示例: ```json { "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` 字段,设为必填: ```prisma model IitProject { tenantId String @map("tenant_id") tenant Tenant @relation(fields: [tenantId], references: [id]) } ``` ### 5.3 API 改动 所有项目查询接口添加租户过滤: ```sql -- 非 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` | 修改 | 创建时选择租户 |