diff --git a/.gitignore b/.gitignore
index a349a276..9a577d61 100644
--- a/.gitignore
+++ b/.gitignore
@@ -75,3 +75,4 @@ backend/app/uploads/
# Git worktrees
.worktrees/
+.install-logs/
diff --git a/IMPLEMENTATION_SUMMARY.md b/IMPLEMENTATION_SUMMARY.md
deleted file mode 100644
index 0be9f50a..00000000
--- a/IMPLEMENTATION_SUMMARY.md
+++ /dev/null
@@ -1,222 +0,0 @@
-# 权限系统重构实现总结
-
-## 实现状态
-
-### ✅ 已完成的阶段
-
-#### 第1阶段:数据库设计
-- **ApiEndpointPermission 模型** (`/backend/app/models/api_endpoint_permission.py`)
- - 存储接口级权限配置
- - 支持按项目、角色、接口端点的权限管理
-
-- **ApiEndpointRegistry 模型** (`/backend/app/models/api_endpoint_registry.py`)
- - 存储系统中所有已注册的API端点
- - 包含端点元数据(方法、路径、模块、操作、默认角色)
-
-- **数据库迁移** (`/backend/alembic/versions/20250513_01_add_api_endpoint_permissions.py`)
- - 创建两个新表
- - 包含幂等性检查
-
-#### 第2阶段:权限配置系统
-- **API_ENDPOINT_PERMISSIONS** (`/backend/app/core/api_permissions.py`)
- - 定义了所有关键业务模块的接口级权限
- - 包含 subjects, risk_issues, fees, finance_contracts, project_members, sites 等模块
-
-- **MODULE_TO_ENDPOINTS** 映射
- - 支持向后兼容,允许回退到模块级权限
-
-#### 第3阶段:权限检查依赖注入
-- **require_api_permission()** (`/backend/app/core/deps.py`)
- - FastAPI 依赖注入函数
- - 支持接口级权限检查
-
-- **role_has_api_permission()** (`/backend/app/core/project_permissions.py`)
- - 核心权限检查逻辑
- - 支持接口级权限优先,模块级权限回退
-
-- **get_api_endpoint_permissions()** 和 **replace_api_endpoint_permissions()**
- - 权限矩阵查询和更新函数
-
-#### 第4阶段:API端点迁移
-已迁移的模块:
-
-1. **subjects.py** - 参与者管理
- - POST /subjects → POST:/subjects
- - GET /subjects → GET:/subjects
- - GET /subjects/{id} → GET:/subjects/{id}
- - PATCH /subjects/{id} → PATCH:/subjects/{id}
- - DELETE /subjects/{id} → DELETE:/subjects/{id}
-
-2. **aes.py** - 不良事件管理
- - POST /aes → POST:/risk-issues
- - GET /aes → GET:/risk-issues
- - GET /aes/{id} → GET:/risk-issues/{id}
- - PATCH /aes/{id} → PATCH:/risk-issues/{id}
- - DELETE /aes/{id} → DELETE:/risk-issues/{id}
-
-3. **fees_contracts.py** - 费用合同管理
- - GET /fees/contracts → GET:/fees/contracts
- - GET /fees/contracts/{id} → GET:/fees/contracts/{id}
- - POST /fees/contracts → POST:/fees/contracts
- - PATCH /fees/contracts/{id} → PATCH:/fees/contracts/{id}
- - DELETE /fees/contracts/{id} → DELETE:/fees/contracts/{id}
- - POST /fees/contracts/{id}/payments → POST:/fees/contracts/{id}/payments
- - PATCH /fees/payments/{id} → PATCH:/fees/payments/{id}
- - DELETE /fees/payments/{id} → DELETE:/fees/payments/{id}
-
-4. **finance_contracts.py** - 财务合同管理
- - POST /finance/contracts → POST:/finance/contracts
- - GET /finance/contracts → GET:/finance/contracts
- - GET /finance/contracts/{id} → GET:/finance/contracts/{id}
- - PATCH /finance/contracts/{id} → PATCH:/finance/contracts/{id}
- - DELETE /finance/contracts/{id} → DELETE:/finance/contracts/{id}
-
-#### 第5阶段:权限管理API
-- **api_permissions.py** (`/backend/app/api/v1/api_permissions.py`)
- - GET /api-permissions/endpoints - 获取所有已注册的API端点
- - GET /studies/{study_id}/api-permissions - 获取项目的权限矩阵
- - PUT /studies/{study_id}/api-permissions - 更新项目的权限矩阵
-
-### 📋 待完成的工作(第6阶段)
-
-#### 单元测试
-- [ ] 权限检查函数测试
- - 接口级权限检查
- - 向后兼容(模块级权限回退)
- - 权限优先级验证
-
-- [ ] 权限配置测试
- - 权限配置的创建、读取、更新
- - 权限配置的验证
-
-#### 集成测试
-- [ ] 跨模块数据访问测试
- - 验证 risk_issues 模块可以读取 subjects 数据
- - 验证权限检查正确
-
-- [ ] 权限管理API测试
- - 权限查询、更新、删除
-
-- [ ] 向后兼容测试
- - 验证旧的模块级权限仍然有效
- - 验证迁移期间两套权限系统并行运行
-
-#### 端到端测试
-- [ ] 权限变更流程
- - 管理员修改权限
- - 用户权限立即生效
- - 审计日志记录
-
-- [ ] 跨模块场景
- - PV 查看参与者信息
- - PV 创建不良事件(需要参与者信息)
- - 权限检查正确
-
-#### 文档更新
-- [ ] API文档更新
- - 新增权限管理API文档
- - 更新已迁移模块的权限说明
-
-- [ ] 开发指南
- - 如何添加新的接口级权限
- - 如何迁移现有模块到接口级权限
-
-- [ ] 权限矩阵文档
- - 各角色的默认权限
- - 权限配置示例
-
-## 关键特性
-
-### 1. 细粒度权限控制
-- 从模块级(subjects.read/write)升级到接口级(POST:/subjects, GET:/subjects/{id})
-- 支持跨模块数据访问权限管理
-
-### 2. 向后兼容
-- 接口级权限优先级高于模块级权限
-- 如果接口级权限未配置,自动回退到模块级权限
-- 现有代码无需立即迁移
-
-### 3. 灵活的权限配置
-- 权限配置存储在数据库中
-- 支持动态调整,无需重启应用
-- 提供REST API进行权限管理
-
-### 4. 权限检查流程
-```
-请求到达
- ↓
-FastAPI依赖注入 → require_api_permission("POST:/subjects")
- ↓
-role_has_api_permission(db, study_id, role, "POST:/subjects")
- ↓
- ├─ 查询 ApiEndpointPermission 表
- │ ├─ 找到 → 返回 allowed 值
- │ └─ 未找到 → 继续
- │
- └─ 回退到模块级权限
- └─ role_has_project_permission(db, study_id, role, "subjects", "write")
- ├─ 查询 StudyRolePermission 表
- └─ 返回权限结果
- ↓
-权限检查通过 → 执行业务逻辑
-权限检查失败 → 返回 403 Forbidden
-```
-
-## 文件清单
-
-### 新增文件
-- `/backend/app/models/api_endpoint_permission.py` - 接口级权限模型
-- `/backend/app/models/api_endpoint_registry.py` - 接口注册表模型
-- `/backend/app/core/decorators.py` - 装饰器辅助函数
-- `/backend/app/api/v1/api_permissions.py` - 权限管理API
-- `/backend/alembic/versions/20250513_01_add_api_endpoint_permissions.py` - 数据库迁移
-
-### 修改文件
-- `/backend/app/core/api_permissions.py` - 接口权限配置
-- `/backend/app/core/project_permissions.py` - 权限检查函数
-- `/backend/app/core/deps.py` - 依赖注入函数
-- `/backend/app/main.py` - 应用启动初始化
-- `/backend/app/api/v1/router.py` - 路由注册
-- `/backend/app/api/v1/subjects.py` - 参与者管理API
-- `/backend/app/api/v1/aes.py` - 不良事件API
-- `/backend/app/api/v1/fees_contracts.py` - 费用合同API
-- `/backend/app/api/v1/finance_contracts.py` - 财务合同API
-
-## 下一步
-
-1. **运行数据库迁移**
- ```bash
- alembic upgrade head
- ```
-
-2. **启动应用并验证**
- ```bash
- uvicorn app.main:app --reload
- ```
-
-3. **编写和运行测试**
- - 单元测试
- - 集成测试
- - 端到端测试
-
-4. **迁移第二批模块**
- - members (project_members)
- - sites
-
-5. **更新文档**
- - API文档
- - 开发指南
- - 权限矩阵文档
-
-## 验证清单
-
-- [x] 代码语法检查通过
-- [x] 数据库模型可以导入
-- [x] 权限配置系统完整
-- [x] 关键业务模块已迁移
-- [ ] 单元测试编写
-- [ ] 集成测试编写
-- [ ] 端到端测试编写
-- [ ] 文档更新
-- [ ] 性能测试
-- [ ] 安全审计
diff --git a/MODULE_LEVEL_PERMISSIONS_ASSESSMENT.md b/MODULE_LEVEL_PERMISSIONS_ASSESSMENT.md
deleted file mode 100644
index 4c2af560..00000000
--- a/MODULE_LEVEL_PERMISSIONS_ASSESSMENT.md
+++ /dev/null
@@ -1,258 +0,0 @@
-# 模块级权限必要性评估
-
-**评估日期**: 2026-05-14
-**评估结论**: ✅ **模块级权限仍有必要保留**
-
----
-
-## 1. 当前权限系统现状
-
-### 权限层级结构
-```
-系统级权限 (ADMIN)
- ↓
-项目级权限
- ├─ 模块级权限 (Module-level) - 87处使用
- └─ 接口级权限 (API-level) - 32处使用
-```
-
-### 使用统计
-- **模块级权限检查**: 87处(占比 73%)
-- **接口级权限检查**: 32处(占比 27%)
-- **权限模块数**: 16个
-- **权限操作数**: 97个
-
----
-
-## 2. 模块级权限的价值
-
-### 2.1 向后兼容性
-**现状**: 接口级权限检查会自动回退到模块级权限
-```python
-# 权限检查优先级
-1. 接口级权限(如果已配置)
-2. 模块级权限(向后兼容)
-```
-
-**意义**:
-- 允许渐进式迁移,无需一次性改造所有接口
-- 新增接口可直接使用接口级权限
-- 旧接口可保持模块级权限
-
-### 2.2 粗粒度权限管理
-**应用场景**:
-- 项目管理员快速配置整个模块的权限
-- 不需要逐个配置每个操作
-- 适合权限配置简单的项目
-
-**示例**:
-```
-PM 角色权限配置:
-- 项目成员: 读写
-- 中心管理: 读写
-- 合同费用: 读写
-- 参与者管理: 读写
-```
-
-### 2.3 前端路由权限控制
-**现状**: 前端使用模块级权限控制路由访问
-```typescript
-// projectRoutePermissions.ts
-{
- prefixes: ["/project/overview"],
- permission: { module: "project_overview", action: "read" }
-}
-```
-
-**意义**:
-- 前端路由与后端权限保持一致
-- 用户界面权限检查更简洁
-- 避免用户访问无权限的页面
-
-### 2.4 权限管理 UI 的两层结构
-**现状**: 权限管理页面提供两个标签页
-```
-权限管理
-├─ 模块级权限 (ProjectPermissionsModule)
-└─ 接口级权限 (ApiEndpointPermissions)
-```
-
-**意义**:
-- 模块级权限: 快速配置,适合大多数场景
-- 接口级权限: 细粒度控制,适合复杂场景
-- 两者互补,满足不同需求
-
----
-
-## 3. 接口级权限的价值
-
-### 3.1 细粒度权限控制
-**解决的问题**: 跨模块数据访问权限混乱
-
-**示例**:
-```
-问题: risk_issues 模块需要读取 subjects 数据
-- 模块级权限: 无法表达"只能查看自己创建的"
-- 接口级权限: 可以精确控制 GET /subjects 的访问权限
-```
-
-### 3.2 业务语言权限名称
-**改进**: 从技术性改为业务语言
-```
-旧: "POST:/subjects", "GET:/subjects/{id}"
-新: "subjects:create", "subjects:read"
-```
-
-**优势**:
-- 权限名称更易理解
-- 与业务流程对应
-- 便于权限审计和合规
-
-### 3.3 权限操作数量
-- **模块级**: 16个模块 × 2个操作 = 32个权限
-- **接口级**: 97个权限操作
-- **覆盖范围**: 接口级权限更全面
-
----
-
-## 4. 迁移成本分析
-
-### 4.1 完全移除模块级权限的成本
-
-| 项目 | 工作量 | 风险 |
-|------|--------|------|
-| 后端接口迁移 | 87处代码改造 | 高 |
-| 前端路由权限 | 20+个路由 | 中 |
-| 权限管理 UI | 简化为单标签页 | 低 |
-| 数据库迁移 | 权限数据转换 | 中 |
-| 测试覆盖 | 完整回归测试 | 高 |
-| **总计** | **3-5天** | **中高** |
-
-### 4.2 保留模块级权限的成本
-- **维护成本**: 极低(已稳定运行)
-- **学习成本**: 低(文档完善)
-- **扩展成本**: 低(两套系统并行)
-
----
-
-## 5. 权限系统对比
-
-| 维度 | 模块级权限 | 接口级权限 |
-|------|-----------|-----------|
-| **粒度** | 粗(模块+操作) | 细(具体操作) |
-| **易用性** | 高(配置简单) | 中(配置复杂) |
-| **灵活性** | 低(无法精确控制) | 高(精确到操作) |
-| **性能** | 高(缓存友好) | 中(查询更多) |
-| **覆盖范围** | 32个权限 | 97个权限 |
-| **跨模块支持** | 不支持 | 支持 |
-| **维护成本** | 低 | 中 |
-
----
-
-## 6. 建议方案
-
-### 6.1 短期(现在)
-✅ **保留两套权限系统**
-- 模块级权限: 继续用于快速配置和前端路由
-- 接口级权限: 用于细粒度控制和新增接口
-- 优先级: 接口级 > 模块级(自动回退)
-
-### 6.2 中期(3-6个月)
-📋 **逐步迁移**
-- 新增接口直接使用接口级权限
-- 不改造现有接口(保持稳定)
-- 收集用户反馈,优化权限模型
-
-### 6.3 长期(6-12个月)
-🎯 **可选完全迁移**
-- 如果接口级权限完全满足需求
-- 可考虑移除模块级权限
-- 但成本较高,收益有限
-
----
-
-## 7. 风险评估
-
-### 7.1 保留模块级权限的风险
-- **权限混乱**: 两套权限系统可能产生不一致
- - *缓解*: 接口级权限优先级更高
-- **维护复杂**: 需要维护两套权限逻辑
- - *缓解*: 代码已清晰分离,维护成本低
-
-### 7.2 移除模块级权限的风险
-- **回归风险**: 87处代码改造可能引入 bug
- - *影响*: 高,涉及所有业务接口
-- **用户影响**: 权限配置方式改变
- - *影响*: 中,需要重新培训
-- **数据迁移**: 现有权限数据转换
- - *影响*: 中,需要数据验证
-
----
-
-## 8. 结论
-
-### ✅ 模块级权限应该保留,原因:
-
-1. **成本效益差**: 移除成本高(3-5天),收益有限
-2. **向后兼容**: 现有系统运行稳定,无需改造
-3. **用户友好**: 模块级权限配置更简单直观
-4. **风险可控**: 两套系统并行,相互补充
-5. **前端依赖**: 路由权限控制依赖模块级权限
-
-### 📌 最佳实践:
-
-```
-权限检查策略:
-├─ 前端路由: 使用模块级权限(快速检查)
-├─ 后端接口: 优先使用接口级权限
-│ └─ 如果未配置,自动回退到模块级权限
-└─ 权限管理: 提供两个标签页
- ├─ 模块级权限: 快速配置
- └─ 接口级权限: 细粒度控制
-```
-
-### 🎯 建议行动:
-
-1. **保持现状** - 两套权限系统并行运行
-2. **新增接口** - 直接使用接口级权限
-3. **监控效果** - 收集用户反馈
-4. **定期评估** - 每季度评估一次迁移必要性
-
----
-
-## 9. 附录:权限系统架构图
-
-```
-┌─────────────────────────────────────────────────┐
-│ 权限检查请求 │
-└────────────────┬────────────────────────────────┘
- │
- ┌───────▼────────┐
- │ ADMIN 角色? │
- │ 是 → 允许所有 │
- └───────┬────────┘
- │ 否
- ┌───────▼──────────────┐
- │ 查询接口级权限 │
- │ (ApiEndpointPerm) │
- └───────┬──────────────┘
- │
- ┌───────▼────────────┐
- │ 找到配置? │
- │ 是 → 返回结果 │
- │ 否 → 继续 │
- └───────┬────────────┘
- │
- ┌───────▼──────────────┐
- │ 回退到模块级权限 │
- │ (StudyRolePermission)│
- └───────┬──────────────┘
- │
- ┌───────▼────────────┐
- │ 返回权限检查结果 │
- └────────────────────┘
-```
-
----
-
-**评估完成**: ✅ 2026-05-14
diff --git a/MODULE_LEVEL_PERMISSIONS_DEPENDENCY_ANALYSIS.md b/MODULE_LEVEL_PERMISSIONS_DEPENDENCY_ANALYSIS.md
deleted file mode 100644
index 04361191..00000000
--- a/MODULE_LEVEL_PERMISSIONS_DEPENDENCY_ANALYSIS.md
+++ /dev/null
@@ -1,488 +0,0 @@
-# 模块级权限依赖详细分析
-
-## 执行摘要
-
-分析发现,虽然 68 个 API 端点已迁移到接口级权限,但系统中仍有多个地方在使用模块级权限函数。**不能立即移除模块级权限**,需要先迁移这些依赖。
-
-**评估结论**: ⚠️ **暂不可移除**(需要先迁移其他依赖)
-
----
-
-## 1. 模块级权限使用情况
-
-### 1.1 role_has_project_permission 函数的使用
-
-**使用位置**: 11 个文件,15 处调用
-
-#### 1. app/core/deps.py (1 处)
-```python
-# 行号: ~180
-allowed = await role_has_project_permission(
- db, study_id, membership.role_in_study, module, action
-)
-```
-**用途**: 通用权限检查依赖(require_study_permission)
-**状态**: ⚠️ 需要迁移
-
-#### 2. app/core/project_permissions.py (1 处)
-```python
-# 行号: ~300
-has_main_permission = await role_has_project_permission(
- db, study_id, role, module, action
-)
-```
-**用途**: 接口级权限的回退机制
-**状态**: ✓ 这是回退逻辑,移除时删除
-
-#### 3. app/api/v1/attachments.py (4 处)
-```python
-# 行号: ~45, ~75, ~105, ~135
-allowed = await role_has_project_permission(
- db, study_id, membership.role_in_study, "attachments", action
-)
-```
-**用途**: 附件管理权限检查
-**状态**: ⚠️ 需要迁移到接口级权限
-
-#### 4. app/api/v1/dashboard.py (2 处)
-```python
-# 行号: ~50, ~60
-can_read_sites = await role_has_project_permission(
- db, study_id, membership.role_in_study, "sites", "read"
-)
-can_read_subjects = await role_has_project_permission(
- db, study_id, membership.role_in_study, "subjects", "read"
-)
-```
-**用途**: 仪表板权限检查
-**状态**: ⚠️ 需要迁移到接口级权限
-
-#### 5. app/api/v1/faqs.py (2 处)
-```python
-# 行号: ~40, ~80
-allowed = await role_has_project_permission(
- db, study_id, member.role_in_study, "faq", action
-)
-```
-**用途**: FAQ 权限检查
-**状态**: ⚠️ 需要迁移到接口级权限
-
-#### 6. app/api/v1/fees_attachments.py (1 处)
-```python
-# 行号: ~50
-allowed = await role_has_project_permission(
- db, project_id, membership.role_in_study, "fees", action
-)
-```
-**用途**: 费用附件权限检查
-**状态**: ⚠️ 需要迁移到接口级权限
-
-#### 7. app/api/v1/faq_categories.py (1 处)
-```python
-# 行号: ~40
-allowed = await role_has_project_permission(
- db, study_id, member.role_in_study, "faq", action
-)
-```
-**用途**: FAQ 分类权限检查
-**状态**: ⚠️ 需要迁移到接口级权限
-
-#### 8. app/services/document_service.py (1 处)
-```python
-# 行号: ~100
-allowed = await role_has_project_permission(
- db, trial_id, membership.role_in_study, module, permission_action
-)
-```
-**用途**: 文档服务权限检查
-**状态**: ⚠️ 需要迁移到接口级权限
-
-#### 9. app/api/v1/project_permissions.py (2 处)
-```python
-# 行号: ~50, ~100
-# 用于权限管理 UI
-```
-**用途**: 权限管理 API
-**状态**: ⚠️ 需要更新为接口级权限
-
-### 1.2 使用统计
-
-| 类别 | 数量 | 状态 |
-|------|------|------|
-| 回退逻辑 | 1 | ✓ 可删除 |
-| 需要迁移 | 14 | ⚠️ 需要迁移 |
-| **总计** | **15** | |
-
----
-
-## 2. 未迁移的 API 端点
-
-### 2.1 未迁移端点列表
-
-| 模块 | 端点 | 文件 | 状态 |
-|------|------|------|------|
-| attachments | 创建/读取/更新/删除 | attachments.py | ⚠️ 未迁移 |
-| dashboard | 获取仪表板 | dashboard.py | ⚠️ 未迁移 |
-| faqs | 创建/读取/更新/删除 | faqs.py | ⚠️ 未迁移 |
-| faq_categories | 创建/读取/更新/删除 | faq_categories.py | ⚠️ 未迁移 |
-| fees_attachments | 创建/读取/删除 | fees_attachments.py | ⚠️ 未迁移 |
-| documents | 创建/读取/更新/删除 | documents.py | ⚠️ 未迁移 |
-| knowledge_notes | 创建/读取/更新/删除 | knowledge_notes.py | ⚠️ 未迁移 |
-| subject_histories | 读取 | subject_histories.py | ⚠️ 未迁移 |
-| study_subject_pds | 创建/读取/更新/删除 | study_subject_pds.py | ⚠️ 未迁移 |
-
-**未迁移端点总数**: ~40+ 个
-
-### 2.2 迁移优先级
-
-**高优先级** (核心业务):
-- attachments (4 个端点)
-- dashboard (1 个端点)
-- faqs (4 个端点)
-- faq_categories (4 个端点)
-
-**中优先级** (重要功能):
-- fees_attachments (3 个端点)
-- documents (4 个端点)
-- knowledge_notes (4 个端点)
-
-**低优先级** (辅助功能):
-- subject_histories (1 个端点)
-- study_subject_pds (4 个端点)
-
----
-
-## 3. 修订后的移除计划
-
-### 3.1 新的阶段划分
-
-#### 第1阶段:迁移剩余端点 (2-3 周)
-
-**任务**:
-1. 迁移 attachments 端点 (4 个)
-2. 迁移 dashboard 端点 (1 个)
-3. 迁移 faqs 端点 (4 个)
-4. 迁移 faq_categories 端点 (4 个)
-5. 迁移 fees_attachments 端点 (3 个)
-6. 迁移 documents 端点 (4 个)
-7. 迁移 knowledge_notes 端点 (4 个)
-8. 迁移 subject_histories 端点 (1 个)
-9. 迁移 study_subject_pds 端点 (4 个)
-
-**总计**: ~40 个端点
-
-**预期工作量**: 2-3 周
-
-#### 第2阶段:更新权限管理 API (1 周)
-
-**任务**:
-1. 更新 project_permissions.py 中的权限管理 API
-2. 添加接口级权限的管理端点
-3. 更新权限矩阵 UI
-
-**预期工作量**: 1 周
-
-#### 第3阶段:代码清理 (1-2 周)
-
-**任务**:
-1. 删除 role_has_project_permission() 函数
-2. 删除 PROJECT_PERMISSION_MODULES 常量
-3. 删除 StudyRolePermission 模型
-4. 删除相关测试
-
-**预期工作量**: 1-2 周
-
-#### 第4阶段:数据库迁移 (1-2 周)
-
-**任务**:
-1. 创建迁移脚本
-2. 备份数据
-3. 执行迁移
-4. 验证
-
-**预期工作量**: 1-2 周
-
-### 3.2 修订的时间表
-
-```
-2026年5月14日: 接口级权限迁移完成 (68 个端点)
-2026年5月-6月: 迁移剩余端点 (2-3 周)
-2026年6月-7月: 更新权限管理 API (1 周)
-2026年7月-8月: 代码清理 (1-2 周)
-2026年8月-9月: 数据库迁移 (1-2 周)
-2026年9月-10月: 验证和监控 (2-4 周)
-
-建议完全移除日期: 2026年10月
-```
-
----
-
-## 4. 需要迁移的权限配置
-
-### 4.1 新增权限操作
-
-需要在 `api_permissions.py` 中添加以下权限操作:
-
-```python
-# 附件管理
-"attachments:create": {
- "module": "attachments",
- "action": "write",
- "description": "创建附件",
- "default_roles": ["PM", "CRA", "PV"],
- "prerequisite_permissions": [],
-},
-"attachments:read": {
- "module": "attachments",
- "action": "read",
- "description": "查询附件",
- "default_roles": ["PM", "CRA", "PV", "MEDICAL_REVIEW", "IMP", "QA"],
- "prerequisite_permissions": [],
-},
-"attachments:update": {
- "module": "attachments",
- "action": "write",
- "description": "更新附件",
- "default_roles": ["PM", "CRA"],
- "prerequisite_permissions": [],
-},
-"attachments:delete": {
- "module": "attachments",
- "action": "write",
- "description": "删除附件",
- "default_roles": ["PM", "CRA"],
- "prerequisite_permissions": [],
-},
-
-# 仪表板
-"dashboard:read": {
- "module": "dashboard",
- "action": "read",
- "description": "查询仪表板",
- "default_roles": ["PM", "CRA", "PV", "MEDICAL_REVIEW", "IMP", "QA"],
- "prerequisite_permissions": ["subjects:read", "sites:read"],
-},
-
-# FAQ
-"faq:create": {
- "module": "faq",
- "action": "write",
- "description": "创建FAQ",
- "default_roles": ["PM"],
- "prerequisite_permissions": [],
-},
-"faq:read": {
- "module": "faq",
- "action": "read",
- "description": "查询FAQ",
- "default_roles": ["PM", "CRA", "PV", "MEDICAL_REVIEW", "IMP", "QA"],
- "prerequisite_permissions": [],
-},
-"faq:update": {
- "module": "faq",
- "action": "write",
- "description": "更新FAQ",
- "default_roles": ["PM"],
- "prerequisite_permissions": [],
-},
-"faq:delete": {
- "module": "faq",
- "action": "write",
- "description": "删除FAQ",
- "default_roles": ["PM"],
- "prerequisite_permissions": [],
-},
-
-# 文档
-"documents:create": {
- "module": "documents",
- "action": "write",
- "description": "创建文档",
- "default_roles": ["PM"],
- "prerequisite_permissions": [],
-},
-"documents:read": {
- "module": "documents",
- "action": "read",
- "description": "查询文档",
- "default_roles": ["PM", "CRA", "PV", "MEDICAL_REVIEW", "IMP", "QA"],
- "prerequisite_permissions": [],
-},
-"documents:update": {
- "module": "documents",
- "action": "write",
- "description": "更新文档",
- "default_roles": ["PM"],
- "prerequisite_permissions": [],
-},
-"documents:delete": {
- "module": "documents",
- "action": "write",
- "description": "删除文档",
- "default_roles": ["PM"],
- "prerequisite_permissions": [],
-},
-
-# 知识库笔记
-"knowledge_notes:create": {
- "module": "knowledge",
- "action": "write",
- "description": "创建知识库笔记",
- "default_roles": ["PM", "CRA"],
- "prerequisite_permissions": [],
-},
-"knowledge_notes:read": {
- "module": "knowledge",
- "action": "read",
- "description": "查询知识库笔记",
- "default_roles": ["PM", "CRA", "PV", "MEDICAL_REVIEW", "IMP", "QA"],
- "prerequisite_permissions": [],
-},
-"knowledge_notes:update": {
- "module": "knowledge",
- "action": "write",
- "description": "更新知识库笔记",
- "default_roles": ["PM", "CRA"],
- "prerequisite_permissions": [],
-},
-"knowledge_notes:delete": {
- "module": "knowledge",
- "action": "write",
- "description": "删除知识库笔记",
- "default_roles": ["PM"],
- "prerequisite_permissions": [],
-},
-
-# 参与者历史
-"subject_histories:read": {
- "module": "subjects",
- "action": "read",
- "description": "查询参与者历史",
- "default_roles": ["PM", "CRA", "PV", "MEDICAL_REVIEW", "IMP", "QA"],
- "prerequisite_permissions": ["subjects:read"],
-},
-
-# 参与者PDS
-"study_subject_pds:create": {
- "module": "subjects",
- "action": "write",
- "description": "创建参与者PDS",
- "default_roles": ["PM", "CRA"],
- "prerequisite_permissions": ["subjects:read", "sites:read"],
-},
-"study_subject_pds:read": {
- "module": "subjects",
- "action": "read",
- "description": "查询参与者PDS",
- "default_roles": ["PM", "CRA", "PV", "MEDICAL_REVIEW", "IMP", "QA"],
- "prerequisite_permissions": ["subjects:read"],
-},
-"study_subject_pds:update": {
- "module": "subjects",
- "action": "write",
- "description": "更新参与者PDS",
- "default_roles": ["PM", "CRA"],
- "prerequisite_permissions": ["subjects:read", "sites:read"],
-},
-"study_subject_pds:delete": {
- "module": "subjects",
- "action": "write",
- "description": "删除参与者PDS",
- "default_roles": ["PM"],
- "prerequisite_permissions": ["subjects:read"],
-},
-
-# 费用附件
-"fees_attachments:create": {
- "module": "fees",
- "action": "write",
- "description": "创建费用附件",
- "default_roles": ["PM", "CRA"],
- "prerequisite_permissions": [],
-},
-"fees_attachments:read": {
- "module": "fees",
- "action": "read",
- "description": "查询费用附件",
- "default_roles": ["PM", "CRA", "PV"],
- "prerequisite_permissions": [],
-},
-"fees_attachments:delete": {
- "module": "fees",
- "action": "write",
- "description": "删除费用附件",
- "default_roles": ["PM", "CRA"],
- "prerequisite_permissions": [],
-},
-```
-
----
-
-## 5. 修订后的结论
-
-### 5.1 评估结论
-
-**模块级权限暂不可移除**: ⚠️ **需要先迁移其他依赖**
-
-**理由**:
-1. ⚠️ 仍有 ~40 个 API 端点未迁移
-2. ⚠️ 仍有 15 处代码调用 role_has_project_permission()
-3. ⚠️ 权限管理 API 仍依赖模块级权限
-4. ⚠️ 无法在不破坏现有功能的情况下移除
-
-### 5.2 建议
-
-**立即行动** (本周):
-1. ✓ 制定剩余端点迁移计划
-2. ✓ 分配迁移任务
-3. ✓ 准备权限配置
-
-**短期行动** (2-3 周):
-1. ✓ 迁移所有剩余端点
-2. ✓ 更新权限管理 API
-3. ✓ 进行充分测试
-
-**中期行动** (4-6 周):
-1. ✓ 执行代码清理
-2. ✓ 执行数据库迁移
-3. ✓ 进行充分验证
-
-### 5.3 修订的优先级
-
-**优先级**: **高** (需要在下一个发布周期完成)
-
-**理由**:
-- 影响系统的权限管理完整性
-- 需要充分的时间进行迁移和测试
-- 可以与其他功能开发并行进行
-
----
-
-## 6. 附录
-
-### 6.1 需要迁移的文件
-
-```
-backend/app/api/v1/attachments.py
-backend/app/api/v1/dashboard.py
-backend/app/api/v1/faqs.py
-backend/app/api/v1/faq_categories.py
-backend/app/api/v1/fees_attachments.py
-backend/app/api/v1/documents.py
-backend/app/api/v1/knowledge_notes.py
-backend/app/api/v1/subject_histories.py
-backend/app/api/v1/study_subject_pds.py
-backend/app/api/v1/project_permissions.py
-backend/app/core/deps.py
-backend/app/services/document_service.py
-```
-
-### 6.2 参考文档
-
-- [权限系统迁移完成报告](./backend/PERMISSION_MIGRATION_TEST_REPORT.md)
-- [模块级权限移除评估](./REMOVE_MODULE_LEVEL_PERMISSIONS_ASSESSMENT.md)
-
----
-
-**分析完成日期**: 2026-05-14
-**建议完全移除日期**: 2026-10-01
diff --git a/README.md b/README.md
index ed77cecb..caf34ebf 100644
--- a/README.md
+++ b/README.md
@@ -45,7 +45,7 @@
- 前端环境变量请使用 `frontend/.env`,可从 `frontend/.env.example` 复制。
- 根目录 `.env` 不作为当前默认启动流程的提交配置文件。
- Postman 本地环境请基于 `docs/postman/local.postman_environment.example.json` 自行复制,不提交个人环境文件。
-- 文档入口见 `docs/README.md`;当前操作手册集中在 `docs/guides/`,审计与治理文档集中在 `docs/audits/`。
+- 文档入口见 `docs/README.md`;当前约束集中在分支治理、发布清单和治理审计文档,操作手册集中在 `docs/guides/`,历史交付记录集中在 `docs/reports/` 与 `docs/plans/`。
## 常用流程
1. 用管理员账号登录前端(默认 `admin@example.com / admin123`)。
diff --git a/REMOVE_MODULE_LEVEL_PERMISSIONS_ASSESSMENT.md b/REMOVE_MODULE_LEVEL_PERMISSIONS_ASSESSMENT.md
deleted file mode 100644
index c47a60a9..00000000
--- a/REMOVE_MODULE_LEVEL_PERMISSIONS_ASSESSMENT.md
+++ /dev/null
@@ -1,475 +0,0 @@
-# 模块级权限移除评估
-
-## 执行摘要
-
-经过完整的接口级权限迁移和测试,模块级权限系统已不再被使用。建议在 6-12 个月的观察期后完全移除。
-
-**评估结论**: ✓ **可以移除**(需要过渡期)
-
----
-
-## 1. 当前状态分析
-
-### 1.1 模块级权限系统概况
-
-**表结构**:
-- `study_role_permissions` - 存储项目角色的模块级权限
-
-**字段**:
-- `study_id` - 项目ID
-- `role` - 角色名称
-- `module` - 模块名称 (subjects, visits, aes, etc.)
-- `can_read` - 读权限
-- `can_write` - 写权限
-
-**覆盖的模块**:
-```
-subjects, visits, aes, risk_issues, finance, fees, materials,
-startup_auth, ethics, monitoring_audit, subject_pds, audit_export, etc.
-```
-
-### 1.2 接口级权限迁移完成度
-
-**迁移状态**:
-- ✓ 68 个 API 端点已迁移到接口级权限
-- ✓ 所有迁移端点已测试通过
-- ✓ 前置权限检查机制已实现
-- ✓ 权限管理 API 已完成
-
-**迁移覆盖范围**:
-```
-第1批 (23个): subjects, visits, aes, monitoring_visit_issues
-第2批 (11个): members, sites, project_milestones
-第3批 (15个): finance_contracts, fees_contracts, drug_shipments
-第4批 (19个): startup endpoints
-```
-
-### 1.3 向后兼容性实现
-
-**当前回退机制**:
-```python
-# 在 role_has_api_permission() 中
-if perm is not None:
- has_main_permission = perm.allowed
-else:
- # 回退到模块级权限
- endpoint_config = API_ENDPOINT_PERMISSIONS.get(endpoint_key)
- module = endpoint_config["module"]
- action = endpoint_config["action"]
- has_main_permission = await role_has_project_permission(
- db, study_id, role, module, action
- )
-```
-
-**回退使用情况**:
-- 所有迁移的端点都配置了对应的模块映射
-- 如果接口级权限未配置,自动使用模块级权限
-- 确保现有权限配置继续有效
-
----
-
-## 2. 移除可行性分析
-
-### 2.1 依赖关系检查
-
-**代码中对模块级权限的引用**:
-
-```bash
-# 搜索 role_has_project_permission 的使用
-grep -r "role_has_project_permission" backend/app --include="*.py"
-```
-
-**结果**:
-- ✓ 仅在 `project_permissions.py` 中定义
-- ✓ 仅在 `role_has_api_permission()` 的回退逻辑中使用
-- ✓ 没有其他地方直接调用
-
-**代码中对 StudyRolePermission 的引用**:
-
-```bash
-# 搜索 StudyRolePermission 的使用
-grep -r "StudyRolePermission" backend/app --include="*.py"
-```
-
-**结果**:
-- ✓ 仅在 `project_permissions.py` 中导入
-- ✓ 仅在 `role_has_project_permission()` 中使用
-- ✓ 没有其他业务逻辑依赖
-
-### 2.2 前端依赖检查
-
-**前端权限检查**:
-- 前端使用接口级权限进行路由守卫
-- 前端权限矩阵已更新为接口级权限
-- 模块级权限在前端已不使用
-
-### 2.3 数据库迁移影响
-
-**表的使用情况**:
-- `study_role_permissions` 表中可能存储了历史数据
-- 移除前需要确认数据迁移策略
-
-**数据迁移方案**:
-1. **保留数据**: 保留表用于审计和历史查询
-2. **归档数据**: 将数据导出到历史表后删除
-3. **直接删除**: 如果确认无需保留历史数据
-
----
-
-## 3. 移除步骤规划
-
-### 3.1 第1阶段:准备期 (1-2 周)
-
-**任务**:
-1. 备份 `study_role_permissions` 表数据
-2. 生成数据迁移脚本
-3. 更新文档,说明权限系统变更
-4. 通知所有相关人员
-
-**验证**:
-- ✓ 所有接口级权限已配置
-- ✓ 所有迁移端点已测试
-- ✓ 备份数据已验证
-
-### 3.2 第2阶段:代码清理 (1-2 周)
-
-**删除项目**:
-
-1. **删除回退逻辑**
- ```python
- # 从 role_has_api_permission() 中删除
- else:
- # 2. 如果没有接口级权限,回退到模块级权限(向后兼容)
- endpoint_config = API_ENDPOINT_PERMISSIONS.get(endpoint_key)
- if not endpoint_config:
- return False
- module = endpoint_config["module"]
- action = endpoint_config["action"]
- has_main_permission = await role_has_project_permission(db, study_id, role, module, action)
- ```
-
-2. **删除函数**
- - `role_has_project_permission()` 函数
- - `PROJECT_PERMISSION_MODULES` 常量
-
-3. **删除导入**
- - `from app.models.study_role_permission import StudyRolePermission`
- - `from app.core.project_permissions import role_has_project_permission`
-
-4. **删除模型**
- - `app/models/study_role_permission.py` 文件
-
-5. **删除测试**
- - 删除模块级权限相关的测试用例
-
-**文件修改清单**:
-```
-backend/app/core/project_permissions.py
- - 删除 role_has_project_permission() 函数
- - 删除 PROJECT_PERMISSION_MODULES 常量
- - 删除 StudyRolePermission 导入
-
-backend/app/core/api_permissions.py
- - 删除 module 字段映射(可选,保留用于文档)
-
-backend/app/models/study_role_permission.py
- - 删除整个文件
-
-backend/tests/
- - 删除模块级权限相关测试
- - 更新现有测试中的模块级权限引用
-```
-
-### 3.3 第3阶段:数据库迁移 (1-2 周)
-
-**创建迁移文件**:
-```python
-# alembic/versions/20260601_remove_study_role_permissions.py
-
-def upgrade() -> None:
- # 选项1: 删除表
- op.drop_table("study_role_permissions")
-
- # 选项2: 重命名为历史表
- # op.rename_table("study_role_permissions", "study_role_permissions_history")
-
-def downgrade() -> None:
- # 恢复表结构
- op.create_table(...)
-```
-
-**执行步骤**:
-1. 在测试环境运行迁移
-2. 验证迁移成功
-3. 备份生产数据
-4. 在生产环境运行迁移
-
-### 3.4 第4阶段:验证和监控 (2-4 周)
-
-**验证项**:
-- ✓ 所有权限检查正常工作
-- ✓ 没有权限相关的错误日志
-- ✓ 前端权限守卫正常工作
-- ✓ 审计日志记录正确
-
-**监控指标**:
-- 权限检查错误率
-- 权限拒绝次数
-- API 响应时间
-- 数据库查询性能
-
----
-
-## 4. 风险评估
-
-### 4.1 高风险项
-
-| 风险 | 影响 | 缓解措施 |
-|------|------|---------|
-| 遗漏的模块级权限使用 | 权限检查失败 | 充分的代码审查和测试 |
-| 数据丢失 | 无法恢复历史权限配置 | 完整备份和验证 |
-| 性能下降 | 权限检查变慢 | 性能测试和优化 |
-
-### 4.2 中风险项
-
-| 风险 | 影响 | 缓解措施 |
-|------|------|---------|
-| 迁移期间权限不一致 | 用户权限混乱 | 充分的过渡期和通知 |
-| 回滚困难 | 无法快速恢复 | 完整的迁移脚本和文档 |
-
-### 4.3 低风险项
-
-| 风险 | 影响 | 缓解措施 |
-|------|------|---------|
-| 代码库混乱 | 维护成本增加 | 代码清理和文档更新 |
-| 文档过时 | 新人上手困难 | 及时更新文档 |
-
----
-
-## 5. 移除前检查清单
-
-### 5.1 代码检查
-
-- [ ] 所有 API 端点都配置了接口级权限
-- [ ] 所有接口级权限都有对应的模块映射
-- [ ] 没有直接调用 `role_has_project_permission()` 的代码
-- [ ] 没有直接访问 `StudyRolePermission` 的代码
-- [ ] 所有权限相关的测试都通过
-
-### 5.2 数据检查
-
-- [ ] `study_role_permissions` 表数据已备份
-- [ ] 数据迁移脚本已准备
-- [ ] 数据迁移已在测试环境验证
-- [ ] 数据恢复计划已制定
-
-### 5.3 文档检查
-
-- [ ] 权限系统文档已更新
-- [ ] API 文档已更新
-- [ ] 迁移指南已编写
-- [ ] 回滚计划已文档化
-
-### 5.4 测试检查
-
-- [ ] 单元测试覆盖率 > 90%
-- [ ] 集成测试全部通过
-- [ ] 性能测试通过
-- [ ] 安全性测试通过
-
-### 5.5 部署检查
-
-- [ ] 部署计划已制定
-- [ ] 回滚计划已准备
-- [ ] 监控告警已配置
-- [ ] 团队培训已完成
-
----
-
-## 6. 建议时间表
-
-### 6.1 推荐移除时间
-
-**最早移除时间**: 2026年7月(3个月后)
-- 充分的观察期
-- 足够的数据积累
-- 足够的问题发现和修复时间
-
-**推荐移除时间**: 2026年8月-9月(4-5个月后)
-- 更长的观察期
-- 更充分的准备时间
-- 更低的风险
-
-### 6.2 阶段时间表
-
-```
-2026年5月14日: 接口级权限迁移完成
-2026年5月-6月: 观察期(1-2个月)
-2026年6月-7月: 准备期(1-2个月)
-2026年7月-8月: 代码清理(1-2个月)
-2026年8月-9月: 数据库迁移(1-2个月)
-2026年9月-10月: 验证和监控(2-4个月)
-```
-
----
-
-## 7. 移除后的改进
-
-### 7.1 代码简化
-
-**删除的代码**:
-- ~100 行权限检查逻辑
-- ~50 行常量定义
-- ~200 行测试代码
-
-**简化的流程**:
-```
-权限检查流程简化为:
-API请求 → 获取当前用户 → 检查接口级权限 → 检查前置权限 → 执行业务逻辑
-```
-
-### 7.2 性能改进
-
-**预期改进**:
-- 权限检查减少一次数据库查询(不再需要查询模块级权限)
-- 权限检查响应时间减少 ~5-10%
-- 数据库查询减少 ~5%
-
-### 7.3 维护成本降低
-
-**预期降低**:
-- 代码行数减少 ~350 行
-- 测试用例减少 ~50 个
-- 文档维护工作减少 ~30%
-
----
-
-## 8. 备选方案
-
-### 8.1 方案A: 完全移除(推荐)
-
-**优点**:
-- ✓ 代码最简洁
-- ✓ 维护成本最低
-- ✓ 性能最优
-
-**缺点**:
-- ✗ 无法恢复历史权限配置
-- ✗ 需要完整的迁移计划
-
-**实施时间**: 3-4 个月
-
-### 8.2 方案B: 保留表但不使用
-
-**优点**:
-- ✓ 可以保留历史数据
-- ✓ 可以快速回滚
-- ✓ 风险较低
-
-**缺点**:
-- ✗ 代码中仍有回退逻辑
-- ✗ 维护成本仍然存在
-- ✗ 容易造成混淆
-
-**实施时间**: 1-2 个月
-
-### 8.3 方案C: 延迟移除
-
-**优点**:
-- ✓ 更长的观察期
-- ✓ 更充分的准备时间
-- ✓ 风险最低
-
-**缺点**:
-- ✗ 维护成本持续
-- ✗ 代码库混乱
-- ✗ 新人容易混淆
-
-**实施时间**: 6-12 个月
-
----
-
-## 9. 结论和建议
-
-### 9.1 评估结论
-
-**模块级权限可以移除**: ✓ **是**
-
-**理由**:
-1. ✓ 接口级权限迁移已完成 (68 个端点)
-2. ✓ 所有迁移端点已测试通过 (46 个测试)
-3. ✓ 前置权限检查机制已实现
-4. ✓ 向后兼容性已保证
-5. ✓ 没有其他地方依赖模块级权限
-
-### 9.2 建议
-
-**立即行动**:
-1. ✓ 备份 `study_role_permissions` 表数据
-2. ✓ 编写数据迁移脚本
-3. ✓ 更新文档和培训材料
-
-**短期行动** (1-2 个月):
-1. ✓ 进行充分的观察和监控
-2. ✓ 收集用户反馈
-3. ✓ 修复发现的问题
-
-**中期行动** (2-4 个月):
-1. ✓ 执行代码清理
-2. ✓ 执行数据库迁移
-3. ✓ 进行充分的验证
-
-### 9.3 优先级
-
-**优先级**: **中等** (可以在下一个发布周期执行)
-
-**理由**:
-- 不是紧急任务
-- 需要充分的准备时间
-- 可以与其他功能开发并行进行
-
----
-
-## 10. 附录
-
-### 10.1 相关文件清单
-
-**需要修改的文件**:
-```
-backend/app/core/project_permissions.py
-backend/app/core/api_permissions.py
-backend/app/models/study_role_permission.py (删除)
-backend/tests/test_*.py (多个文件)
-```
-
-**需要创建的文件**:
-```
-alembic/versions/20260601_remove_study_role_permissions.py
-docs/MIGRATION_GUIDE.md
-```
-
-### 10.2 参考文档
-
-- [权限系统迁移完成报告](./backend/PERMISSION_MIGRATION_TEST_REPORT.md)
-- [模块级权限评估](./MODULE_LEVEL_PERMISSIONS_ASSESSMENT.md)
-- [权限系统设计文档](./PERMISSION_SYSTEM_DESIGN.md)
-
-### 10.3 联系方式
-
-**权限系统负责人**:
-- 技术负责人: [待填]
-- 产品负责人: [待填]
-
----
-
-## 版本历史
-
-| 版本 | 日期 | 作者 | 变更 |
-|------|------|------|------|
-| 1.0 | 2026-05-14 | Claude | 初始评估 |
-
----
-
-**评估完成日期**: 2026-05-14
-**下次评估日期**: 2026-08-14
-**建议移除日期**: 2026-08-01 - 2026-09-30
diff --git a/backend/IMPLEMENTATION_SUMMARY.md b/backend/IMPLEMENTATION_SUMMARY.md
deleted file mode 100644
index 4f7c5996..00000000
--- a/backend/IMPLEMENTATION_SUMMARY.md
+++ /dev/null
@@ -1,283 +0,0 @@
-# 接口级权限系统 - 实现总结
-
-## 项目概述
-
-本项目实现了从模块级权限到接口级权限的系统迁移,支持更细粒度的API端点级权限控制,同时保持向后兼容性。
-
-## 实现阶段
-
-### 第1-6阶段:核心基础设施(已完成)
-
-#### 1. 数据库模型
-- **ApiEndpointPermission**:存储接口级权限配置
-- **ApiEndpointRegistry**:注册系统中所有API端点
-
-#### 2. 权限配置系统
-- **api_permissions.py**:中央权限配置注册表
-- **MODULE_TO_ENDPOINTS**:向后兼容映射表
-
-#### 3. 权限检查函数
-- **role_has_api_permission()**:检查角色是否有权访问特定接口
-- **get_api_endpoint_permissions()**:获取项目权限矩阵
-- **replace_api_endpoint_permissions()**:更新项目权限矩阵
-
-#### 4. FastAPI 集成
-- **require_api_permission()**:依赖注入函数
-- **@register_api_endpoint**:装饰器注册端点
-
-#### 5. 权限管理API
-- **GET /studies/{study_id}/api-permissions**:获取权限矩阵
-- **PUT /studies/{study_id}/api-permissions**:更新权限矩阵
-
-### 第8阶段:迁移第3批模块(已完成)
-
-#### 迁移的模块
-
-**第一优先级(关键业务)**
-- startup.py:19个端点(伦理审批、可行性评估、预算、时间表)
-- project_permissions.py:2个端点(项目权限查询、更新)
-- overview.py:1个端点(项目概览)
-
-**第二优先级(重要业务)**
-- monitoring_visit_issues.py:7个端点(监查问题管理)
-- drug_shipments.py:5个端点(药物发货管理)
-- material_equipments.py:5个端点(物资管理)
-- subject_pds.py:4个端点(参与者PDS)
-- audit_logs.py:3个端点(审计日志)
-
-**第三优先级(辅助功能)**
-- visits.py:5个端点(访视管理)
-- knowledge_notes.py:5个端点(知识库笔记)
-- subject_histories.py:5个端点(参与者历史)
-- project_milestones.py:2个端点(项目里程碑)
-
-#### 迁移统计
-- **迁移端点数**:63 个
-- **涉及文件**:12 个
-- **新增测试**:34 个
-- **总测试数**:109 个(包括前7阶段的75个)
-
-## 权限配置示例
-
-### Members 模块权限矩阵
-
-| 角色 | 添加成员 | 查询成员 | 更新成员 | 删除成员 | 查询候选人 |
-|------|---------|---------|---------|---------|-----------|
-| ADMIN | ✅ | ✅ | ✅ | ✅ | ✅ |
-| PM | ✅ | ✅ | ✅ | ✅ | ✅ |
-| CRA | ❌ | ❌ | ❌ | ❌ | ❌ |
-| PV | ❌ | ❌ | ❌ | ❌ | ❌ |
-| MEDICAL_REVIEW | ❌ | ❌ | ❌ | ❌ | ❌ |
-| IMP | ❌ | ❌ | ❌ | ❌ | ❌ |
-| QA | ❌ | ❌ | ❌ | ❌ | ❌ |
-
-### Sites 模块权限矩阵
-
-| 角色 | 创建中心 | 查询中心 | 更新中心 | 删除中心 |
-|------|---------|---------|---------|---------|
-| ADMIN | ✅ | ✅ | ✅ | ✅ |
-| PM | ✅ | ✅ | ✅ | ✅ |
-| CRA | ❌ | ❌ | ❌ | ❌ |
-| PV | ❌ | ❌ | ❌ | ❌ |
-| MEDICAL_REVIEW | ❌ | ❌ | ❌ | ❌ |
-| IMP | ❌ | ❌ | ❌ | ❌ |
-| QA | ❌ | ❌ | ❌ | ❌ |
-
-## 迁移代码示例
-
-### 迁移前(模块级权限)
-```python
-@router.post(
- "/",
- response_model=StudyMemberRead,
- dependencies=[
- Depends(require_study_permission("project_members", "write")),
- ],
-)
-async def add_member(...):
- pass
-```
-
-### 迁移后(接口级权限)
-```python
-@router.post(
- "/",
- response_model=StudyMemberRead,
- dependencies=[
- Depends(require_api_permission("POST:/studies/{study_id}/members")),
- Depends(require_study_not_locked())
- ],
-)
-@register_api_endpoint(
- endpoint_key="POST:/studies/{study_id}/members",
- module="project_members",
- action="write",
- description="添加项目成员",
- default_roles=["PM"],
-)
-async def add_member(...):
- pass
-```
-
-## 权限检查流程
-
-```
-请求到达
- ↓
-FastAPI依赖注入 → require_api_permission("POST:/studies/{study_id}/members")
- ↓
-role_has_api_permission(db, study_id, role, "POST:/studies/{study_id}/members")
- ↓
- ├─ 查询 ApiEndpointPermission 表
- │ ├─ 找到 → 返回 allowed 值
- │ └─ 未找到 → 继续
- │
- └─ 回退到模块级权限
- └─ role_has_project_permission(db, study_id, role, "project_members", "write")
- ├─ 查询 StudyRolePermission 表
- └─ 返回权限结果
- ↓
-权限检查通过 → 执行业务逻辑
-权限检查失败 → 返回 403 Forbidden
-```
-
-## 已迁移端点总览
-
-### 第1批(22个端点)
-- **subjects**:5 个端点
-- **risk_issues**:3 个端点
-- **fees**:8 个端点
-- **finance_contracts**:5 个端点
-
-### 第2批(9个端点)
-- **members**:5 个端点
-- **sites**:4 个端点
-
-### 第3批(63个端点)
-- **startup**:19 个端点
-- **project_permissions**:2 个端点
-- **overview**:1 个端点
-- **monitoring_visit_issues**:7 个端点
-- **drug_shipments**:5 个端点
-- **material_equipments**:5 个端点
-- **subject_pds**:4 个端点
-- **audit_logs**:3 个端点
-- **visits**:5 个端点
-- **knowledge_notes**:5 个端点
-- **subject_histories**:5 个端点
-- **project_milestones**:2 个端点
-
-### 总计:94 个端点
-
-## 向后兼容性
-
-系统支持两种权限检查方式的并行运行:
-
-1. **接口级权限**(优先级高)
- - 存储在 `ApiEndpointPermission` 表
- - 支持细粒度的端点级控制
-
-2. **模块级权限**(优先级低)
- - 存储在 `StudyRolePermission` 表
- - 用于未迁移的端点和向后兼容
-
-**优先级规则**:
-- 如果存在接口级权限配置,使用接口级权限
-- 如果不存在接口级权限配置,回退到模块级权限
-- 如果两者都不存在,拒绝访问
-
-## 测试覆盖
-
-### 测试文件
-- `test_api_permissions.py`:12 个测试
-- `test_api_permissions_endpoints.py`:11 个测试
-- `test_api_permissions_config.py`:13 个测试
-- `test_migrated_endpoints.py`:22 个测试(第1批)
-- `test_migrated_endpoints_batch2.py`:17 个测试(第2批)
-- `test_migrated_endpoints_batch3.py`:34 个测试(第3批)
-
-### 测试场景
-- ✅ 接口级权限允许/拒绝
-- ✅ 模块级权限回退
-- ✅ 权限优先级验证
-- ✅ 权限矩阵操作
-- ✅ 向后兼容性验证
-- ✅ 权限隔离验证
-- ✅ 权限配置验证
-
-### 覆盖率
-- **代码覆盖率**:85%
-- **测试通过率**:100%(109/109)
-
-## 关键文件清单
-
-### 新增文件
-| 文件 | 用途 |
-|------|------|
-| `app/models/api_endpoint_permission.py` | 接口级权限模型 |
-| `app/models/api_endpoint_registry.py` | 接口注册表模型 |
-| `app/core/api_permissions.py` | 接口权限配置 |
-| `app/core/decorators.py` | 装饰器辅助函数 |
-| `app/api/v1/api_permissions.py` | 权限管理API |
-| `tests/test_api_permissions.py` | 权限检查测试 |
-| `tests/test_api_permissions_endpoints.py` | 权限管理API测试 |
-| `tests/test_migrated_endpoints.py` | 第1批端点测试 |
-| `tests/test_migrated_endpoints_batch2.py` | 第2批端点测试 |
-
-### 修改文件
-| 文件 | 修改内容 |
-|------|---------|
-| `app/core/project_permissions.py` | 新增接口级权限检查函数 |
-| `app/core/deps.py` | 新增 `require_api_permission()` 函数 |
-| `app/api/v1/subjects.py` | 迁移到接口级权限 |
-| `app/api/v1/aes.py` | 迁移到接口级权限 |
-| `app/api/v1/fees_contracts.py` | 迁移到接口级权限 |
-| `app/api/v1/members.py` | 迁移到接口级权限(第2批) |
-| `app/api/v1/sites.py` | 迁移到接口级权限(第2批) |
-
-## 性能指标
-
-- **测试执行时间**:0.40 秒
-- **平均单个测试时间**:3.7 毫秒
-- **代码覆盖率**:85%
-- **总测试数**:109 个
-
-## 下一步工作
-
-### 第9阶段:安全审计与性能优化(已完成)
-**目标模块**:
-- ✅ 安全审计:检查权限系统的安全性
-- ✅ 性能优化:优化权限检查的性能
-- ✅ 缓存策略:实现权限缓存
-
-**完成情况**:
-- ✅ 权限检查覆盖率 100%(94个端点全部受保护)
-- ✅ 权限配置完整性优秀(101个端点完整配置)
-- ✅ ADMIN角色处理一致且安全
-- ✅ 实现权限矩阵缓存和成员身份缓存
-- ✅ 性能提升 50%+,缓存命中率 > 80%
-- ✅ 新增47个测试用例(性能测试12个 + 安全测试20个 + 缓存测试15个)
-
-**生成文档**:
-- ✅ SECURITY_AUDIT.md - 安全审计报告
-- ✅ PERFORMANCE_OPTIMIZATION.md - 性能优化报告
-
-### 第10-11阶段
-- 性能测试
-- 文档更新
-
-## 总结
-
-接口级权限系统已成功实现,包括:
-- ✅ 核心基础设施(数据库、配置、检查函数)
-- ✅ FastAPI 集成(依赖注入、装饰器)
-- ✅ 权限管理API
-- ✅ 第1批模块迁移(22 个端点)
-- ✅ 第2批模块迁移(9 个端点)
-- ✅ 第3批模块迁移(63 个端点)
-- ✅ 全面的测试覆盖(109 个测试)
-- ✅ 向后兼容性保证
-
-**已迁移端点总数:94 个**
-
-系统已准备好进行第9阶段的安全审计和性能优化。
diff --git a/backend/MONITORING_DASHBOARD.md b/backend/MONITORING_DASHBOARD.md
deleted file mode 100644
index c1d794f8..00000000
--- a/backend/MONITORING_DASHBOARD.md
+++ /dev/null
@@ -1,410 +0,0 @@
-# 权限系统监控仪表板
-
-## 概述
-
-权限系统监控仪表板提供了权限系统运行状态的实时可视化,包括权限检查性能、缓存效率、告警信息和系统健康状态。
-
----
-
-## API 端点
-
-### 1. 获取权限系统指标
-
-**端点**: `GET /api/v1/permission-monitoring/metrics`
-
-**描述**: 获取权限检查和缓存的运行指标
-
-**响应示例**:
-```json
-{
- "check_metrics": {
- "total_checks": 1000,
- "allowed_checks": 950,
- "denied_checks": 50,
- "total_time": 5.234,
- "min_time": 0.001,
- "max_time": 0.05,
- "avg_time": 0.005,
- "allow_rate": 95.0,
- "deny_rate": 5.0,
- "error_rate": 0.1,
- "errors": 1
- },
- "cache_metrics": {
- "total_accesses": 1000,
- "cache_hits": 850,
- "cache_misses": 150,
- "cache_invalidations": 5,
- "hit_rate": 85.0,
- "miss_rate": 15.0
- },
- "uptime_seconds": 3600
-}
-```
-
-**关键指标**:
-- `total_checks`: 总权限检查次数
-- `allowed_checks`: 允许的检查次数
-- `denied_checks`: 拒绝的检查次数
-- `avg_time`: 平均权限检查耗时(秒)
-- `allow_rate`: 允许率(百分比)
-- `cache_hits`: 缓存命中次数
-- `hit_rate`: 缓存命中率(百分比)
-
----
-
-### 2. 获取缓存统计
-
-**端点**: `GET /api/v1/permission-monitoring/cache-stats`
-
-**描述**: 获取缓存的详细统计信息
-
-**响应示例**:
-```json
-{
- "cache_items": {
- "project_permissions_count": 10,
- "member_role_count": 50,
- "total_count": 60
- },
- "cache_metrics": {
- "total_accesses": 1000,
- "cache_hits": 850,
- "cache_misses": 150,
- "cache_invalidations": 5,
- "hit_rate": 85.0,
- "miss_rate": 15.0
- }
-}
-```
-
-**关键指标**:
-- `project_permissions_count`: 项目权限缓存项目数
-- `member_role_count`: 成员角色缓存项目数
-- `hit_rate`: 缓存命中率
-
----
-
-### 3. 获取告警列表
-
-**端点**: `GET /api/v1/permission-monitoring/alerts`
-
-**参数**:
-- `level` (可选): 告警级别过滤 (info, warning, error)
-- `limit` (可选): 返回的最大告警数,默认 100
-
-**响应示例**:
-```json
-{
- "total": 5,
- "alerts": [
- {
- "timestamp": 1715692800.123,
- "level": "warning",
- "type": "slow_permission_check",
- "message": "权限检查耗时过长: 52.34ms",
- "data": {
- "elapsed_time": 0.05234
- }
- },
- {
- "timestamp": 1715692799.456,
- "level": "error",
- "type": "permission_check_error",
- "message": "权限检查出错: database connection timeout",
- "data": {
- "error": "database connection timeout"
- }
- }
- ]
-}
-```
-
-**告警类型**:
-- `slow_permission_check`: 权限检查耗时过长(>50ms)
-- `permission_check_error`: 权限检查出错
-
----
-
-### 4. 权限系统健康检查
-
-**端点**: `GET /api/v1/permission-monitoring/health`
-
-**描述**: 获取权限系统的健康状态
-
-**响应示例**:
-```json
-{
- "status": "healthy",
- "health_score": 95,
- "issues": [],
- "metrics": {
- "check_metrics": { ... },
- "cache_metrics": { ... },
- "uptime_seconds": 3600
- },
- "cache_stats": { ... }
-}
-```
-
-**健康状态**:
-- `healthy`: 健康(分数 >= 80)
-- `degraded`: 降级(分数 50-80)
-- `unhealthy`: 不健康(分数 < 50)
-
-**健康评分规则**:
-- 初始分数: 100
-- 错误率 > 1%: -20
-- 缓存命中率 < 50%: -10
-- 平均响应时间 > 10ms: -10
-- 权限拒绝率 > 50%: -5
-
----
-
-### 5. 重置指标
-
-**端点**: `POST /api/v1/permission-monitoring/reset-metrics`
-
-**描述**: 重置所有累积的指标数据
-
-**响应示例**:
-```json
-{
- "message": "指标已重置"
-}
-```
-
----
-
-### 6. 清除告警
-
-**端点**: `POST /api/v1/permission-monitoring/clear-alerts`
-
-**描述**: 删除所有累积的告警记录
-
-**响应示例**:
-```json
-{
- "message": "告警已清除"
-}
-```
-
----
-
-## 监控指标详解
-
-### 权限检查指标
-
-| 指标 | 说明 | 目标值 |
-|------|------|--------|
-| `total_checks` | 总权限检查次数 | - |
-| `allowed_checks` | 允许的检查次数 | - |
-| `denied_checks` | 拒绝的检查次数 | - |
-| `allow_rate` | 允许率(百分比) | > 90% |
-| `deny_rate` | 拒绝率(百分比) | < 10% |
-| `avg_time` | 平均权限检查耗时 | < 10ms |
-| `min_time` | 最小权限检查耗时 | - |
-| `max_time` | 最大权限检查耗时 | < 100ms |
-| `error_rate` | 错误率(百分比) | < 1% |
-| `errors` | 错误次数 | 0 |
-
-### 缓存指标
-
-| 指标 | 说明 | 目标值 |
-|------|------|--------|
-| `total_accesses` | 总缓存访问次数 | - |
-| `cache_hits` | 缓存命中次数 | - |
-| `cache_misses` | 缓存未命中次数 | - |
-| `hit_rate` | 缓存命中率(百分比) | > 80% |
-| `miss_rate` | 缓存未命中率(百分比) | < 20% |
-| `cache_invalidations` | 缓存失效次数 | - |
-
----
-
-## 告警规则
-
-### 性能告警
-
-**慢速权限检查**
-- 触发条件: 权限检查耗时 > 50ms
-- 级别: warning
-- 建议: 检查数据库性能或缓存配置
-
-### 错误告警
-
-**权限检查错误**
-- 触发条件: 权限检查抛出异常
-- 级别: error
-- 建议: 检查错误日志,排查问题
-
----
-
-## 监控最佳实践
-
-### 1. 定期检查健康状态
-
-```bash
-# 每5分钟检查一次健康状态
-curl -H "Authorization: Bearer $TOKEN" \
- http://localhost:8000/api/v1/permission-monitoring/health
-```
-
-### 2. 监控缓存命中率
-
-```bash
-# 检查缓存统计
-curl -H "Authorization: Bearer $TOKEN" \
- http://localhost:8000/api/v1/permission-monitoring/cache-stats
-```
-
-**目标**: 缓存命中率 > 80%
-
-### 3. 监控权限检查性能
-
-```bash
-# 获取权限系统指标
-curl -H "Authorization: Bearer $TOKEN" \
- http://localhost:8000/api/v1/permission-monitoring/metrics
-```
-
-**目标**: 平均响应时间 < 10ms
-
-### 4. 监控告警
-
-```bash
-# 获取最近的告警
-curl -H "Authorization: Bearer $TOKEN" \
- "http://localhost:8000/api/v1/permission-monitoring/alerts?limit=20"
-```
-
-### 5. 定期重置指标
-
-```bash
-# 每天重置一次指标,用于日报
-curl -X POST -H "Authorization: Bearer $TOKEN" \
- http://localhost:8000/api/v1/permission-monitoring/reset-metrics
-```
-
----
-
-## 故障排除
-
-### 问题1: 缓存命中率低(< 50%)
-
-**可能原因**:
-1. 缓存 TTL 过短
-2. 缓存失效频率过高
-3. 权限变更频繁
-
-**解决方案**:
-1. 增加缓存 TTL(默认 5 分钟)
-2. 检查权限变更频率
-3. 优化权限更新逻辑
-
-### 问题2: 权限检查响应时间长(> 50ms)
-
-**可能原因**:
-1. 数据库查询慢
-2. 缓存未命中
-3. 并发请求过多
-
-**解决方案**:
-1. 检查数据库性能
-2. 增加缓存 TTL
-3. 添加数据库索引
-
-### 问题3: 权限检查错误率高(> 1%)
-
-**可能原因**:
-1. 数据库连接问题
-2. 权限配置错误
-3. 并发修改问题
-
-**解决方案**:
-1. 检查数据库连接
-2. 验证权限配置
-3. 检查并发修改逻辑
-
----
-
-## 集成示例
-
-### Python 客户端
-
-```python
-import requests
-
-# 获取权限系统指标
-response = requests.get(
- "http://localhost:8000/api/v1/permission-monitoring/metrics",
- headers={"Authorization": f"Bearer {token}"}
-)
-metrics = response.json()
-
-# 检查缓存命中率
-cache_hit_rate = metrics["cache_metrics"]["hit_rate"]
-if cache_hit_rate < 80:
- print(f"警告: 缓存命中率低 ({cache_hit_rate}%)")
-
-# 检查平均响应时间
-avg_time = metrics["check_metrics"]["avg_time"]
-if avg_time > 0.01: # 10ms
- print(f"警告: 权限检查响应时间长 ({avg_time*1000:.2f}ms)")
-```
-
-### JavaScript 客户端
-
-```javascript
-// 获取权限系统健康状态
-async function checkPermissionHealth() {
- const response = await fetch(
- 'http://localhost:8000/api/v1/permission-monitoring/health',
- {
- headers: {
- 'Authorization': `Bearer ${token}`
- }
- }
- );
- const health = await response.json();
-
- console.log(`健康状态: ${health.status}`);
- console.log(`健康分数: ${health.health_score}`);
-
- if (health.issues.length > 0) {
- console.warn('发现问题:', health.issues);
- }
-}
-```
-
----
-
-## 仪表板建议
-
-### 实时监控仪表板
-
-建议使用 Grafana 或类似的可视化工具创建实时监控仪表板,包括:
-
-1. **权限检查性能**
- - 平均响应时间趋势
- - 允许/拒绝率
- - 错误率
-
-2. **缓存效率**
- - 缓存命中率趋势
- - 缓存项目数
- - 缓存失效频率
-
-3. **系统健康**
- - 健康分数
- - 告警数量
- - 系统状态
-
-4. **告警面板**
- - 最近的告警
- - 告警趋势
- - 告警分布
-
----
-
-**文档版本**: 1.0
-**最后更新**: 2026-05-14
diff --git a/backend/PERFORMANCE_OPTIMIZATION.md b/backend/PERFORMANCE_OPTIMIZATION.md
deleted file mode 100644
index c9eac86d..00000000
--- a/backend/PERFORMANCE_OPTIMIZATION.md
+++ /dev/null
@@ -1,448 +0,0 @@
-# 接口级权限系统 - 性能优化报告
-
-**优化日期**: 2026-05-14
-**优化范围**: 接口级权限系统(第9阶段)
-**优化结果**: ✅ **成功** - 性能提升 50%+,缓存命中率 > 80%
-
----
-
-## 执行摘要
-
-本次性能优化针对权限系统的N+1查询问题进行了全面改进,通过实现权限矩阵缓存和成员身份缓存,显著提升了权限检查的性能。
-
-**关键成果**:
-- ✅ 实现权限缓存机制,减少数据库查询
-- ✅ 权限检查性能提升 **50%+**
-- ✅ 缓存命中率 **> 80%**
-- ✅ 列表操作性能提升 **30%+**
-- ✅ 自动缓存失效机制,确保权限一致性
-
----
-
-## 1. 性能瓶颈分析
-
-### 1.1 N+1查询问题
-
-**问题描述**:
-- `get_project_role_permissions()` 函数每次调用都查询整个权限矩阵
-- 在列表操作中,每个项目都会触发权限检查
-- 高并发场景下产生大量重复查询
-
-**影响范围**:
-- 权限检查函数:`role_has_project_permission()`, `role_has_api_permission()`
-- 列表操作:查询项目列表时需要逐个检查权限
-- 并发场景:多个用户同时访问时,数据库查询激增
-
-**性能指标**:
-- 单次权限检查:~5-10ms(包含数据库查询)
-- 100次权限检查:~500-1000ms
-- 列表操作(50个项目):~250-500ms
-
-### 1.2 缺失的缓存机制
-
-**问题描述**:
-- 无权限矩阵缓存
-- 无成员身份缓存
-- 无API端点权限缓存
-- 每次请求都重新查询数据库
-
-**影响范围**:
-- 重复查询相同的权限数据
-- 数据库负载增加
-- 响应时间变长
-
----
-
-## 2. 优化方案
-
-### 2.1 缓存架构
-
-```
-权限检查请求
- ↓
-检查缓存(内存)
- ├─ 缓存命中 → 返回缓存数据(<1ms)
- └─ 缓存未命中 → 查询数据库 → 存储到缓存 → 返回数据
-```
-
-### 2.2 缓存层设计
-
-**文件**: `/backend/app/core/permission_cache.py`
-
-**核心功能**:
-1. **权限矩阵缓存**
- - 缓存项目的完整权限矩阵
- - TTL: 5分钟(可配置)
- - 缓存键:`project_permissions:{study_id}`
-
-2. **成员身份缓存**
- - 缓存成员在项目中的角色
- - TTL: 5分钟(可配置)
- - 缓存键:`member_role:{study_id}:{user_id}`
-
-3. **缓存失效机制**
- - 权限更新时自动失效相关缓存
- - 成员角色变更时自动失效缓存
- - 支持单项失效和批量失效
-
-### 2.3 缓存集成
-
-**修改文件**: `/backend/app/core/project_permissions.py`
-
-**修改内容**:
-1. 在 `replace_project_role_permissions()` 后失效缓存
-2. 在 `replace_api_endpoint_permissions()` 后失效缓存
-3. 导入缓存模块,集成缓存到权限检查
-
----
-
-## 3. 性能测试结果
-
-### 3.1 基准测试
-
-**测试场景**: 权限检查性能(无缓存)
-
-```
-执行次数: 100
-总耗时: ~500-1000ms
-平均耗时: ~5-10ms/次
-```
-
-### 3.2 缓存性能测试
-
-**测试场景**: 权限检查性能(有缓存)
-
-```
-第一次调用: ~5-10ms(数据库查询)
-后续调用: <1ms(缓存命中)
-性能提升: 5-10倍
-```
-
-### 3.3 缓存命中率
-
-**测试场景**: 实际使用场景模拟
-
-```
-缓存命中率: > 80%
-缓存失效率: < 20%
-缓存有效期: 5分钟
-```
-
-### 3.4 列表操作性能
-
-**测试场景**: 查询50个项目的权限
-
-```
-无缓存: ~250-500ms
-有缓存: ~50-100ms
-性能提升: 50%+
-```
-
-### 3.5 并发性能
-
-**测试场景**: 100个并发权限检查
-
-```
-无缓存: ~1000-2000ms
-有缓存: ~100-200ms
-性能提升: 10倍+
-```
-
----
-
-## 4. 缓存策略
-
-### 4.1 TTL策略
-
-**权限矩阵缓存**: 5分钟
-- 权限变更不频繁
-- 5分钟内的数据一致性可接受
-- 可根据需要调整
-
-**成员身份缓存**: 5分钟
-- 成员角色变更不频繁
-- 5分钟内的数据一致性可接受
-- 可根据需要调整
-
-### 4.2 失效策略
-
-**主动失效**:
-- 权限更新时立即失效
-- 成员角色变更时立即失效
-- 项目权限矩阵更新时失效所有成员缓存
-
-**被动失效**:
-- 缓存过期时自动失效(TTL)
-- 系统重启时清除所有缓存
-
-### 4.3 缓存监控
-
-**缓存统计**:
-- 缓存项目数
-- 缓存命中率
-- 缓存失效频率
-
-**监控指标**:
-```python
-stats = cache.get_cache_stats()
-# {
-# 'project_permissions_count': 10,
-# 'member_role_count': 50,
-# 'total_count': 60,
-# }
-```
-
----
-
-## 5. 性能改进总结
-
-### 5.1 性能指标对比
-
-| 指标 | 优化前 | 优化后 | 改进 |
-|------|--------|--------|------|
-| 单次权限检查 | 5-10ms | <1ms | 5-10倍 |
-| 100次权限检查 | 500-1000ms | 50-100ms | 5-10倍 |
-| 列表操作(50项) | 250-500ms | 50-100ms | 50%+ |
-| 并发检查(100个) | 1000-2000ms | 100-200ms | 10倍+ |
-| 缓存命中率 | 0% | >80% | - |
-
-### 5.2 数据库查询减少
-
-**优化前**:
-- 每次权限检查都查询数据库
-- 列表操作:N个项目 = N次数据库查询
-
-**优化后**:
-- 缓存命中时无数据库查询
-- 缓存失效时才查询数据库
-- 数据库查询减少 **80%+**
-
-### 5.3 响应时间改进
-
-**优化前**:
-- 权限检查:5-10ms
-- 列表操作:250-500ms
-
-**优化后**:
-- 权限检查:<1ms(缓存命中)
-- 列表操作:50-100ms
-- 改进:**50%+**
-
----
-
-## 6. 实现细节
-
-### 6.1 缓存初始化
-
-```python
-from app.core.permission_cache import get_permission_cache
-
-cache = get_permission_cache()
-```
-
-### 6.2 权限检查集成
-
-```python
-# 自动使用缓存
-allowed = await role_has_project_permission(
- db, study_id, role, module, action
-)
-```
-
-### 6.3 缓存失效
-
-```python
-# 权限更新时自动失效
-await replace_project_role_permissions(db, study_id, permissions)
-# 缓存已自动失效
-```
-
-### 6.4 缓存监控
-
-```python
-# 获取缓存统计
-stats = cache.get_cache_stats()
-print(f"缓存项目数: {stats['total_count']}")
-```
-
----
-
-## 7. 测试覆盖
-
-### 7.1 性能测试
-
-**文件**: `/backend/tests/test_permission_performance.py`
-
-**测试用例**:
-- ✅ 基准测试(无缓存)
-- ✅ 缓存性能测试
-- ✅ 缓存命中率测试
-- ✅ 并发权限检查测试
-- ✅ 列表操作性能测试
-- ✅ API权限检查性能测试
-- ✅ 权限矩阵缓存测试
-- ✅ 成员角色缓存测试
-
-**测试数量**: 12个
-
-### 7.2 安全测试
-
-**文件**: `/backend/tests/test_permission_security.py`
-
-**测试用例**:
-- ✅ 权限检查遗漏检测
-- ✅ 权限配置完整性
-- ✅ 缓存失效场景
-- ✅ 权限更新立即生效
-- ✅ 并发权限检查一致性
-- ✅ ADMIN角色权限
-- ✅ None角色拒绝
-- ✅ 未知端点拒绝
-
-**测试数量**: 20个
-
-### 7.3 缓存测试
-
-**文件**: `/backend/tests/test_permission_cache.py`
-
-**测试用例**:
-- ✅ 缓存命中
-- ✅ 缓存未命中
-- ✅ 缓存过期
-- ✅ 缓存失效
-- ✅ 并发缓存访问
-- ✅ 缓存键生成
-- ✅ 缓存统计
-- ✅ 清除所有缓存
-- ✅ 不同TTL缓存
-- ✅ 全局缓存实例
-- ✅ 缓存性能改进
-
-**测试数量**: 15个
-
-**总测试数**: 47个新增测试
-
----
-
-## 8. 建议和最佳实践
-
-### 8.1 缓存配置建议
-
-**开发环境**:
-```python
-cache = PermissionCache(default_ttl=60) # 1分钟
-```
-
-**生产环境**:
-```python
-cache = PermissionCache(default_ttl=300) # 5分钟
-```
-
-### 8.2 监控建议
-
-**定期检查缓存统计**:
-```python
-stats = cache.get_cache_stats()
-if stats['total_count'] > 1000:
- # 缓存项目过多,考虑清除
- cache.clear_all()
-```
-
-### 8.3 故障排除
-
-**缓存不生效**:
-1. 检查缓存是否被正确初始化
-2. 检查缓存是否被意外失效
-3. 检查缓存TTL是否过短
-
-**缓存数据不一致**:
-1. 检查缓存失效机制是否正确
-2. 检查是否有绕过缓存的直接数据库查询
-3. 检查是否有并发修改问题
-
----
-
-## 9. 后续优化方向
-
-### 9.1 短期优化(第10阶段)
-
-1. **实现分布式缓存**
- - 使用 Redis 替代内存缓存
- - 支持多进程/多服务器场景
-
-2. **添加缓存预热**
- - 系统启动时预加载常用权限
- - 减少冷启动时的缓存未命中
-
-3. **实现缓存统计和监控**
- - 记录缓存命中率
- - 监控缓存大小
- - 告警缓存异常
-
-### 9.2 中期优化(第11阶段)
-
-1. **实现权限预加载**
- - 用户登录时预加载权限
- - 减少权限检查时的缓存未命中
-
-2. **实现权限变更通知**
- - 权限变更时通知相关用户
- - 实时更新客户端权限信息
-
-3. **实现权限审计日志**
- - 记录权限变更历史
- - 支持权限变更追溯
-
-### 9.3 长期优化
-
-1. **实现权限预测**
- - 基于用户行为预测权限需求
- - 提前加载可能需要的权限
-
-2. **实现权限优化**
- - 分析权限使用模式
- - 优化权限配置
-
----
-
-## 10. 性能优化总结
-
-### 10.1 关键成果
-
-✅ **性能提升 50%+**
-- 权限检查:5-10倍
-- 列表操作:50%+
-- 并发场景:10倍+
-
-✅ **缓存命中率 > 80%**
-- 大多数权限检查都命中缓存
-- 数据库查询减少 80%+
-
-✅ **自动缓存失效**
-- 权限更新时自动失效
-- 确保数据一致性
-
-✅ **完整的测试覆盖**
-- 47个新增测试
-- 性能、安全、缓存全覆盖
-
-### 10.2 实施建议
-
-1. **立即应用**:
- - 部署缓存机制到生产环境
- - 监控缓存性能
-
-2. **短期改进**:
- - 实现分布式缓存(Redis)
- - 添加缓存监控告警
-
-3. **长期规划**:
- - 实现权限预加载
- - 实现权限变更通知
- - 实现权限审计日志
-
----
-
-**优化完成日期**: 2026-05-14
-**优化员**: Claude Haiku 4.5
-**优化状态**: ✅ 完成
diff --git a/backend/PERMISSION_MIGRATION_TEST_REPORT.md b/backend/PERMISSION_MIGRATION_TEST_REPORT.md
deleted file mode 100644
index 9fdf819c..00000000
--- a/backend/PERMISSION_MIGRATION_TEST_REPORT.md
+++ /dev/null
@@ -1,198 +0,0 @@
-# 权限系统迁移 - 测试验证报告
-
-## 执行摘要
-
-权限系统从模块级权限完全迁移到接口级权限,并实现了前置权限检查机制。所有测试均已通过,系统稳定性得到验证。
-
-## 测试覆盖范围
-
-### 1. 单元测试 - 接口级权限检查 (12 个测试)
-
-**文件**: `tests/test_api_permissions.py`
-
-| 测试用例 | 目的 | 状态 |
-|---------|------|------|
-| test_api_permission_check_allowed | 验证接口级权限允许 | ✓ PASS |
-| test_api_permission_check_denied | 验证接口级权限拒绝 | ✓ PASS |
-| test_api_permission_fallback_to_module_level | 验证回退到模块级权限 | ✓ PASS |
-| test_api_permission_fallback_denied | 验证模块级权限拒绝 | ✓ PASS |
-| test_admin_always_allowed | 验证ADMIN角色总是被允许 | ✓ PASS |
-| test_api_permission_priority_over_module | 验证接口级权限优先于模块级 | ✓ PASS |
-| test_api_permission_read_endpoint | 验证读取端点权限 | ✓ PASS |
-| test_api_permission_different_endpoints | 验证不同端点权限独立 | ✓ PASS |
-| test_api_permission_different_roles | 验证不同角色权限独立 | ✓ PASS |
-| test_api_permission_different_studies | 验证不同项目权限独立 | ✓ PASS |
-| test_api_permission_none_role | 验证None角色权限检查 | ✓ PASS |
-| test_api_permission_unknown_endpoint | 验证未知端点权限检查 | ✓ PASS |
-
-### 2. 单元测试 - 前置权限检查 (12 个测试)
-
-**文件**: `tests/test_prerequisite_permissions.py`
-
-| 测试用例 | 目的 | 状态 |
-|---------|------|------|
-| test_prerequisite_permission_satisfied | 验证前置权限满足 | ✓ PASS |
-| test_prerequisite_permission_missing | 验证前置权限缺失 | ✓ PASS |
-| test_prerequisite_permission_not_configured | 验证前置权限未配置 | ✓ PASS |
-| test_multiple_prerequisites_all_satisfied | 验证多个前置权限都满足 | ✓ PASS |
-| test_multiple_prerequisites_one_missing | 验证多个前置权限中有一个缺失 | ✓ PASS |
-| test_get_missing_prerequisites_empty | 验证获取缺失权限 - 无缺失 | ✓ PASS |
-| test_get_missing_prerequisites_single | 验证获取缺失权限 - 单个缺失 | ✓ PASS |
-| test_get_missing_prerequisites_multiple | 验证获取缺失权限 - 多个缺失 | ✓ PASS |
-| test_prerequisite_check_disabled | 验证禁用前置权限检查 | ✓ PASS |
-| test_admin_bypasses_prerequisites | 验证ADMIN角色绕过前置权限 | ✓ PASS |
-| test_prerequisite_with_no_prerequisites_operation | 验证无前置权限的操作 | ✓ PASS |
-| test_prerequisite_missing_not_configured | 验证前置权限未配置时的缺失 | ✓ PASS |
-
-### 3. 集成测试 - 迁移的API端点 (22 个测试)
-
-**文件**: `tests/test_migrated_endpoints.py`
-
-覆盖的端点:
-- **Subjects**: create, list, read, update, delete (5 个)
-- **Visits**: create, list, read, update, delete (5 个)
-- **Risk Issues**: create, list, read, update, delete (5 个)
-- **Fee Contracts**: create, list, read, update, delete (5 个)
-- **Finance Contracts**: create, list, read, update, delete (5 个)
-- **Fee Payments**: create, update, delete (3 个)
-
-所有端点测试均通过,验证了接口级权限的正确实现。
-
-## 测试结果统计
-
-```
-总测试数: 46
-通过: 46 ✓
-失败: 0
-覆盖率: 100%
-```
-
-## 关键验证点
-
-### 1. 权限检查优先级
-
-✓ 接口级权限优先于模块级权限
-✓ 接口级权限未配置时正确回退到模块级权限
-✓ ADMIN角色总是被允许
-
-### 2. 前置权限机制
-
-✓ 单个前置权限检查正确
-✓ 多个前置权限检查正确
-✓ 缺失前置权限被正确识别
-✓ 前置权限可被禁用(用于递归检查)
-✓ ADMIN角色绕过前置权限检查
-
-### 3. 权限隔离
-
-✓ 不同端点的权限独立
-✓ 不同角色的权限独立
-✓ 不同项目的权限独立
-✓ 权限配置不会相互影响
-
-### 4. 边界情况
-
-✓ None角色被正确拒绝
-✓ 未知端点被正确拒绝
-✓ 无前置权限的操作正常工作
-
-## 前置权限配置验证
-
-系统中已配置的前置权限依赖关系:
-
-```
-subjects:create → sites:read
-subjects:update → sites:read
-subjects:delete → sites:read
-
-visits:create → subjects:read, sites:read
-visits:update → subjects:read, sites:read
-visits:delete → subjects:read, sites:read
-
-risk_issues:create → subjects:read, sites:read
-risk_issues:update → subjects:read, sites:read
-risk_issues:delete → subjects:read, sites:read
-
-finance_contracts:* → sites:read
-fees_contracts:* → sites:read
-drug_shipments:* → sites:read
-
-subject_pds:create → subjects:read, sites:read
-subject_pds:update → subjects:read, sites:read
-
-monitoring_audit:* → sites:read
-```
-
-所有前置权限配置均已验证正确。
-
-## API端点迁移验证
-
-### 第1批 (23 个端点)
-- subjects: 5 个端点 ✓
-- visits: 5 个端点 ✓
-- aes (risk_issues): 5 个端点 ✓
-- monitoring_visit_issues: 7 个端点 ✓
-
-### 第2批 (11 个端点)
-- members: 5 个端点 ✓
-- sites: 4 个端点 ✓
-- project_milestones: 2 个端点 ✓
-
-### 第3批 (15 个端点)
-- finance_contracts: 5 个端点 ✓
-- fees_contracts: 5 个端点 ✓
-- drug_shipments: 5 个端点 ✓
-
-### 第4批 (19 个端点)
-- startup endpoints: 19 个端点 ✓
-
-**总计**: 68 个API端点已成功迁移到接口级权限
-
-## 权限管理API验证
-
-新增的权限管理端点:
-
-| 端点 | 功能 | 状态 |
-|------|------|------|
-| GET /api-permissions/operations | 获取所有权限操作及前置权限 | ✓ 实现 |
-| GET /api-permissions/operations/prerequisites | 获取所有操作的前置权限依赖 | ✓ 实现 |
-| GET /api-permissions/{endpoint_key}/prerequisites | 检查特定操作的缺失前置权限 | ✓ 实现 |
-| GET /api-permissions | 获取项目权限矩阵 | ✓ 实现 |
-| PUT /api-permissions | 更新项目权限矩阵 | ✓ 实现 |
-
-## 性能验证
-
-- 权限检查响应时间: < 10ms (单个权限)
-- 前置权限检查响应时间: < 50ms (多个前置权限)
-- 数据库查询优化: 使用索引,避免N+1查询
-
-## 向后兼容性
-
-✓ 模块级权限表保留,用于历史数据
-✓ 接口级权限未配置时自动回退到模块级权限
-✓ 现有的权限配置继续有效
-✓ 迁移过程中无需修改数据库数据
-
-## 安全性验证
-
-✓ ADMIN角色权限检查正确
-✓ 权限隔离完整
-✓ 前置权限检查防止权限泄露
-✓ 缺失权限被正确识别和报告
-
-## 建议
-
-1. **监控**: 在生产环境中监控权限检查的性能
-2. **审计**: 记录所有权限变更操作
-3. **文档**: 更新用户文档,说明新的权限系统
-4. **培训**: 对管理员进行权限管理培训
-
-## 结论
-
-权限系统迁移已完成,所有测试均通过。系统已准备好用于生产环境。
-
-- ✓ 接口级权限系统完全实现
-- ✓ 前置权限检查机制正常工作
-- ✓ 68个API端点已迁移
-- ✓ 向后兼容性保证
-- ✓ 所有测试通过 (46/46)
diff --git a/backend/SECURITY_AUDIT.md b/backend/SECURITY_AUDIT.md
deleted file mode 100644
index e26b89ee..00000000
--- a/backend/SECURITY_AUDIT.md
+++ /dev/null
@@ -1,363 +0,0 @@
-# 接口级权限系统 - 安全审计报告
-
-**审计日期**: 2026-05-14
-**审计范围**: 接口级权限系统(第8阶段完成后)
-**审计结论**: ✅ **安全** - 权限系统设计合理,未发现严重安全漏洞
-
----
-
-## 执行摘要
-
-本次安全审计对接口级权限系统进行了全面评估,包括:
-- 权限检查覆盖范围
-- 权限配置完整性
-- ADMIN角色处理一致性
-- 潜在安全风险
-
-**关键发现**:
-- ✅ 权限检查覆盖率 **100%**(94个已迁移端点全部受保护)
-- ✅ 权限配置完整性 **优秀**(101个端点完整配置)
-- ✅ ADMIN角色处理 **一致**(所有权限检查函数处理一致)
-- ⚠️ MODULE_TO_ENDPOINTS 映射 **不完整**(缺失19个端点,但不影响权限检查)
-
----
-
-## 1. 权限检查覆盖范围审计
-
-### 1.1 审计结果
-
-| 指标 | 数值 | 状态 |
-|------|------|------|
-| 已迁移端点总数 | 94 | ✅ |
-| 已覆盖权限检查的端点 | 94 | ✅ |
-| 未覆盖权限检查的端点 | 0 | ✅ |
-| **权限检查覆盖率** | **100%** | ✅ |
-
-### 1.2 权限装饰器分布
-
-系统采用了多层防御策略,使用了4种权限装饰器:
-
-| 装饰器类型 | 端点数 | 用途 |
-|-----------|--------|------|
-| `require_api_permission()` | 25 | API级别权限控制 |
-| `require_study_permission()` | 62 | 项目级别权限控制 |
-| `require_study_member()` | 4 | 项目成员验证 |
-| `require_roles()` | 7 | 系统级别角色控制 |
-| **总计** | **98** | - |
-
-### 1.3 按模块的权限检查覆盖
-
-**第1批模块**(22个端点)
-- ✅ subjects:5个端点,100%覆盖
-- ✅ risk_issues:3个端点,100%覆盖
-- ✅ fees:8个端点,100%覆盖
-- ✅ finance_contracts:5个端点,100%覆盖
-
-**第2批模块**(9个端点)
-- ✅ members:5个端点,100%覆盖
-- ✅ sites:4个端点,100%覆盖
-
-**第3批模块**(63个端点)
-- ✅ startup:19个端点,100%覆盖
-- ✅ project_permissions:2个端点,100%覆盖
-- ✅ overview:1个端点,100%覆盖
-- ✅ monitoring_visit_issues:7个端点,100%覆盖
-- ✅ drug_shipments:5个端点,100%覆盖
-- ✅ material_equipments:5个端点,100%覆盖
-- ✅ subject_pds:4个端点,100%覆盖
-- ✅ audit_logs:3个端点,100%覆盖
-- ✅ visits:5个端点,100%覆盖
-- ✅ knowledge_notes:5个端点,100%覆盖
-- ✅ subject_histories:5个端点,100%覆盖
-- ✅ project_milestones:2个端点,100%覆盖
-
-### 1.4 关键发现
-
-**✅ 所有94个已迁移的端点都已正确配置权限装饰器**
-
-- 没有发现任何未受保护的端点
-- 所有端点都使用了适当的权限检查机制
-- 权限装饰器配置与端点功能相匹配
-- 采用了多层防御策略(API权限 + 项目权限 + 角色权限)
-
----
-
-## 2. 权限配置完整性审计
-
-### 2.1 API_ENDPOINT_PERMISSIONS 配置质量
-
-| 检查项 | 结果 | 说明 |
-|--------|------|------|
-| 总端点数 | 101 | 包含94个已迁移 + 7个额外端点 |
-| 完整配置的端点 | 101 | 100% |
-| 缺失字段的端点 | 0 | 0% |
-| **配置质量** | **优秀** | ✅ |
-
-**配置字段检查**:
-- ✅ 所有端点都有 `module` 字段
-- ✅ 所有端点都有 `action` 字段(仅包含有效值:read 或 write)
-- ✅ 所有端点都有 `description` 字段
-- ✅ 所有端点都有非空的 `default_roles` 字段
-- ✅ 所有角色都是有效的(PM, CRA, PV, MEDICAL_REVIEW, IMP, QA)
-
-### 2.2 MODULE_TO_ENDPOINTS 映射完整性
-
-| 指标 | 数值 | 状态 |
-|------|------|------|
-| API_ENDPOINT_PERMISSIONS 中的端点 | 101 | - |
-| MODULE_TO_ENDPOINTS 中的端点 | 82 | ⚠️ |
-| 缺失的端点 | 19 | ⚠️ |
-| 多余的端点 | 0 | ✅ |
-
-**缺失的19个端点分布**:
-- project_members:5个(POST、GET、GET/candidates、PATCH、DELETE)
-- subjects:14个(访视、PDS、历史记录相关)
-
-**影响评估**:
-- ⚠️ MODULE_TO_ENDPOINTS 映射不完整
-- ✅ 但不影响权限检查,因为系统优先使用 API_ENDPOINT_PERMISSIONS
-- ✅ 这可能是向后兼容性的故意设计
-
-### 2.3 数据一致性检查
-
-| 检查项 | 结果 |
-|--------|------|
-| 所有 MODULE_TO_ENDPOINTS 端点都在 API_ENDPOINT_PERMISSIONS 中 | ✅ |
-| 所有端点的 action 在两个数据结构中一致 | ✅ |
-| 没有重复的模块定义 | ✅ |
-| **数据一致性** | **完全一致** |
-
-### 2.4 关键发现
-
-**✅ API_ENDPOINT_PERMISSIONS 配置完整且一致**
-
-- 所有101个端点都有完整的权限配置
-- 所有必需字段都已填充
-- 数据一致性良好,没有冲突
-
-**⚠️ MODULE_TO_ENDPOINTS 映射不完整**
-
-- 缺失19个端点的映射
-- 但不影响权限检查,因为系统优先使用 API_ENDPOINT_PERMISSIONS
-- 建议在后续维护中补充完整的映射
-
----
-
-## 3. ADMIN角色处理一致性审计
-
-### 3.1 ADMIN角色处理的一致性评估
-
-| 检查项 | 状态 | 说明 |
-|--------|------|------|
-| ADMIN在 role_has_project_permission() 中的处理 | ✅ 一致 | 直接返回True |
-| ADMIN在 role_has_api_permission() 中的处理 | ✅ 一致 | 直接返回True |
-| ADMIN在 require_study_roles() 中的处理 | ✅ 一致 | 通过allow_system_admin参数绕过 |
-| ADMIN在 require_study_permission() 中的处理 | ✅ 一致 | 通过allow_system_admin参数绕过 |
-| ADMIN在 require_api_permission() 中的处理 | ✅ 一致 | 通过allow_system_admin参数绕过 |
-| ADMIN在 require_study_member() 中的处理 | ✅ 一致 | 直接返回当前用户 |
-| ADMIN在权限矩阵初始化中的处理 | ✅ 一致 | 始终返回完全权限 |
-| ADMIN在权限持久化中的处理 | ✅ 一致 | 不存储到数据库 |
-| ADMIN在权限查询中的处理 | ✅ 一致 | 不需要查询 |
-| **总体一致性** | **✅ 一致** | - |
-
-### 3.2 ADMIN角色的设计特点
-
-**优点**:
-1. ✅ ADMIN权限不存储在数据库中,避免了权限配置错误
-2. ✅ 非ADMIN用户不能修改ADMIN用户的权限
-3. ✅ 所有权限检查都在依赖注入层进行,难以绕过
-4. ✅ 权限检查的顺序正确:先检查ADMIN,再检查其他权限
-5. ✅ ADMIN角色检查在权限检查的最早阶段进行,避免了不必要的数据库查询
-
-**缺点**:
-- ⚠️ 没有明确的文档说明ADMIN角色的行为
-- ⚠️ 没有审计日志记录ADMIN用户的权限相关操作
-
-### 3.3 发现的问题
-
-#### 问题1:API权限端点中的ADMIN角色处理不一致
-
-**位置**: `/backend/app/api/v1/api_permissions.py` (第56-85行)
-
-**问题描述**:
-- 虽然ADMIN用户可以通过 `require_study_roles(["PM"])` 的 `allow_system_admin=True` 默认参数访问权限管理端点
-- 但返回结果中明确排除了ADMIN角色的权限信息
-- 这在逻辑上是一致的(因为ADMIN权限不需要配置),但可能会让用户困惑
-
-**风险等级**: 🟡 **低** - 这是设计特性,不是安全漏洞
-
-#### 问题2:allow_system_admin 参数未被充分利用
-
-**位置**: `/backend/app/core/deps.py`
-
-**问题描述**:
-- 所有权限检查函数都有 `allow_system_admin: bool = True` 参数
-- 但没有任何端点显式设置 `allow_system_admin=False`
-- 这意味着所有端点都允许ADMIN用户绕过权限检查
-
-**风险等级**: 🟢 **无** - 这是预期的设计,ADMIN用户应该有完全访问权限
-
-### 3.4 关键发现
-
-**✅ ADMIN角色处理一致且安全**
-
-- 所有权限检查函数都正确处理ADMIN角色
-- ADMIN权限不存储在数据库中,避免了配置错误
-- 非ADMIN用户不能修改ADMIN用户的权限
-- 权限检查的顺序和逻辑都是正确的
-
----
-
-## 4. 潜在安全风险评估
-
-### 4.1 已识别的风险
-
-| 风险 | 概率 | 影响 | 缓解措施 | 优先级 |
-|------|------|------|---------|--------|
-| 权限检查遗漏 | 低 | 高 | 100%覆盖率已验证 | ✅ 已解决 |
-| 权限配置错误 | 低 | 中 | 配置验证已实现 | ✅ 已解决 |
-| ADMIN角色滥用 | 低 | 高 | 缺少审计日志 | 🟡 需要改进 |
-| 权限缓存不一致 | 中 | 中 | 缓存机制未实现 | 🟡 第9阶段实现 |
-| N+1查询问题 | 中 | 中 | 缓存机制未实现 | 🟡 第9阶段实现 |
-
-### 4.2 建议的改进措施
-
-#### 建议1:添加ADMIN操作审计日志(优先级:高)
-
-在ADMIN用户执行权限相关操作时添加审计日志:
-- ADMIN用户访问权限管理端点
-- ADMIN用户修改其他用户的项目权限
-- ADMIN用户修改项目权限矩阵
-
-**实现位置**:
-- `/backend/app/api/v1/api_permissions.py`
-- `/backend/app/api/v1/members.py`
-
-#### 建议2:实现权限缓存机制(优先级:高)
-
-在第9阶段实现权限缓存,解决N+1查询问题:
-- 权限矩阵缓存(TTL: 5分钟)
-- 成员身份缓存(TTL: 5分钟)
-- 缓存失效机制
-
-**实现位置**:
-- `/backend/app/core/permission_cache.py`(新建)
-
-#### 建议3:明确文档化ADMIN角色的行为(优先级:中)
-
-在代码中添加详细的文档说明ADMIN角色的处理方式:
-- ADMIN用户在所有权限检查中都被视为拥有完全权限
-- ADMIN权限不存储在数据库中
-- ADMIN用户可以修改任何其他用户的项目权限
-
-**实现位置**:
-- `/backend/app/core/deps.py` - 模块级文档
-- `/backend/app/core/project_permissions.py` - 模块级文档
-
-#### 建议4:补充MODULE_TO_ENDPOINTS映射(优先级:低)
-
-补充缺失的19个端点到MODULE_TO_ENDPOINTS映射中:
-- project_members:5个端点
-- subjects:14个端点
-
-**实现位置**:
-- `/backend/app/core/api_permissions.py`
-
----
-
-## 5. 安全性评估总结
-
-### 5.1 总体评估
-
-| 维度 | 评分 | 说明 |
-|------|------|------|
-| 权限检查覆盖 | ⭐⭐⭐⭐⭐ | 100%覆盖,无遗漏 |
-| 权限配置完整性 | ⭐⭐⭐⭐⭐ | 101个端点完整配置 |
-| ADMIN角色处理 | ⭐⭐⭐⭐⭐ | 一致且安全 |
-| 权限隔离 | ⭐⭐⭐⭐⭐ | 多层防御策略 |
-| 审计日志 | ⭐⭐⭐⭐☆ | 缺少ADMIN操作审计 |
-| **总体安全性** | ⭐⭐⭐⭐⭐ | **优秀** |
-
-### 5.2 安全结论
-
-**✅ 权限系统设计合理,安全性良好**
-
-**关键安全点**:
-1. ✅ 权限检查覆盖率100%,所有端点都受保护
-2. ✅ 权限配置完整且一致,没有配置错误
-3. ✅ ADMIN角色处理一致,没有权限绕过漏洞
-4. ✅ 采用多层防御策略,提高了安全性
-5. ✅ 权限检查在依赖注入层进行,难以绕过
-
-**需要改进的地方**:
-1. ⚠️ 缺少ADMIN操作审计日志
-2. ⚠️ 缺少权限缓存机制(性能问题)
-3. ⚠️ MODULE_TO_ENDPOINTS映射不完整(向后兼容性)
-
----
-
-## 6. 验证清单
-
-### 6.1 权限检查覆盖验证
-- ✅ 所有API端点都使用权限装饰器
-- ✅ 所有端点都在 `API_ENDPOINT_PERMISSIONS` 中定义
-- ✅ 所有端点都有 `default_roles` 配置
-- ✅ 权限配置映射完整
-
-### 6.2 安全性验证
-- ✅ ADMIN角色处理一致
-- ✅ 权限检查优先级正确
-- ✅ 权限隔离有效
-- ✅ 没有发现权限绕过漏洞
-
-### 6.3 配置完整性验证
-- ✅ API_ENDPOINT_PERMISSIONS 完整
-- ⚠️ MODULE_TO_ENDPOINTS 不完整(但不影响权限检查)
-- ✅ 所有必需字段都已填充
-- ✅ 数据一致性良好
-
----
-
-## 7. 后续行动
-
-### 立即行动(第9阶段)
-1. 实现权限缓存机制(性能优化)
-2. 添加ADMIN操作审计日志(安全增强)
-3. 补充MODULE_TO_ENDPOINTS映射(完整性)
-
-### 中期行动(第10阶段)
-1. 实现权限系统监控和告警
-2. 添加权限变更通知机制
-3. 实现权限审计报告功能
-
-### 长期行动
-1. 实现资源级权限控制
-2. 实现权限继承机制
-3. 实现权限模板功能
-
----
-
-## 附录:审计工具和方法
-
-### 使用的审计工具
-- 代码静态分析:Grep、Glob
-- 代码审查:手工代码阅读
-- 配置验证:配置文件检查
-
-### 审计方法
-1. 扫描所有API端点文件,检查权限装饰器覆盖
-2. 验证权限配置的完整性和一致性
-3. 检查ADMIN角色在所有权限检查函数中的处理方式
-4. 评估潜在的安全风险
-
-### 审计范围
-- `/backend/app/api/v1/` - 所有API端点文件
-- `/backend/app/core/project_permissions.py` - 权限检查函数
-- `/backend/app/core/deps.py` - 依赖注入函数
-- `/backend/app/core/api_permissions.py` - 权限配置
-
----
-
-**审计完成日期**: 2026-05-14
-**审计员**: Claude Haiku 4.5
-**审计状态**: ✅ 完成
diff --git a/backend/TESTING_SUMMARY.md b/backend/TESTING_SUMMARY.md
deleted file mode 100644
index 86cfb747..00000000
--- a/backend/TESTING_SUMMARY.md
+++ /dev/null
@@ -1,265 +0,0 @@
-# 接口级权限系统 - 测试总结
-
-## 测试执行结果
-
-### 测试覆盖范围
-
-| 测试文件 | 测试数量 | 状态 | 覆盖内容 |
-|---------|--------|------|---------|
-| `test_api_permissions.py` | 12 | ✅ 全部通过 | 权限检查函数、优先级、回退机制 |
-| `test_api_permissions_endpoints.py` | 11 | ✅ 全部通过 | 权限管理API、权限矩阵操作 |
-| `test_api_permissions_config.py` | 13 | ✅ 全部通过 | 权限配置验证、端点注册 |
-| `test_migrated_endpoints.py` | 22 | ✅ 全部通过 | 已迁移端点的权限验证(第1批) |
-| `test_migrated_endpoints_batch2.py` | 17 | ✅ 全部通过 | 已迁移端点的权限验证(第2批) |
-| `test_migrated_endpoints_batch3.py` | 34 | ✅ 全部通过 | 已迁移端点的权限验证(第3批) |
-| **总计** | **109** | ✅ **全部通过** | - |
-
-### 代码覆盖率
-
-```
-Name Stmts Miss Cover
--------------------------------------------------------------
-app/core/api_permissions.py 3 0 100%
-app/core/project_permissions.py 114 17 85%
--------------------------------------------------------------
-TOTAL 117 17 85%
-```
-
-**覆盖率达到 85%,满足 80% 的目标要求。**
-
-## 测试详情
-
-### 1. 权限检查函数测试 (test_api_permissions.py)
-
-**测试场景:**
-- ✅ 接口级权限允许
-- ✅ 接口级权限拒绝
-- ✅ 回退到模块级权限(允许)
-- ✅ 回退到模块级权限(拒绝)
-- ✅ ADMIN 角色总是被允许
-- ✅ 接口级权限优先于模块级权限
-- ✅ 读取端点权限检查
-- ✅ 不同端点的权限检查
-- ✅ 不同角色的权限检查
-- ✅ 不同项目的权限隔离
-- ✅ None 角色处理
-- ✅ 未知端点处理
-
-**关键验证:**
-- 权限检查优先级正确(接口级 > 模块级)
-- 向后兼容性保证(模块级权限回退)
-- 角色隔离和项目隔离正确
-
-### 2. 权限管理API测试 (test_api_permissions_endpoints.py)
-
-**测试场景:**
-- ✅ 获取空权限矩阵(返回默认权限)
-- ✅ 获取自定义权限矩阵
-- ✅ 替换单个角色权限
-- ✅ 替换多个角色权限
-- ✅ 拒绝权限设置
-- ✅ 覆盖现有权限
-- ✅ 多端点权限设置
-- ✅ 不同项目权限隔离
-- ✅ 空负载处理
-- ✅ 部分权限更新
-- ✅ 权限矩阵结构验证
-
-**关键验证:**
-- 权限矩阵格式正确:`{role: {endpoint_key: {allowed: bool}}}`
-- 默认权限正确应用
-- 权限覆盖和更新正确
-- 项目隔离正确
-
-### 3. 已迁移端点测试 (test_migrated_endpoints.py)
-
-**测试端点:**
-
-**参与者管理 (Subjects)**
-- ✅ POST /subjects - 创建参与者
-- ✅ GET /subjects - 查询参与者列表
-- ✅ GET /subjects/{id} - 查询参与者详情
-- ✅ PATCH /subjects/{id} - 更新参与者
-- ✅ DELETE /subjects/{id} - 删除参与者
-
-**不良事件 (Risk Issues)**
-- ✅ POST /risk-issues - 创建不良事件
-- ✅ GET /risk-issues - 查询不良事件列表
-- ✅ GET /risk-issues/{id} - 查询不良事件详情
-
-**费用管理 (Fees)**
-- ✅ POST /fees/contracts - 创建费用合同
-- ✅ GET /fees/contracts - 查询费用合同列表
-- ✅ GET /fees/contracts/{id} - 查询费用合同详情
-- ✅ PATCH /fees/contracts/{id} - 更新费用合同
-- ✅ DELETE /fees/contracts/{id} - 删除费用合同
-- ✅ POST /fees/contracts/{id}/payments - 创建费用分期
-- ✅ PATCH /fees/payments/{id} - 更新费用分期
-- ✅ DELETE /fees/payments/{id} - 删除费用分期
-
-**财务合同 (Finance Contracts)**
-- ✅ POST /finance/contracts - 创建财务合同
-- ✅ GET /finance/contracts - 查询财务合同列表
-- ✅ GET /finance/contracts/{id} - 查询财务合同详情
-- ✅ PATCH /finance/contracts/{id} - 更新财务合同
-- ✅ DELETE /finance/contracts/{id} - 删除财务合同
-
-**关键验证:**
-- 所有端点权限检查正确
-- 权限拒绝时返回 403
-- 权限允许时正常执行
-
-### 4. 第3批已迁移端点测试 (test_migrated_endpoints_batch3.py)
-
-**测试端点:**
-
-**启动管理 (Startup)**
-- ✅ POST /studies/{study_id}/startup/ethics - 创建伦理审批
-- ✅ GET /studies/{study_id}/startup/ethics - 查询伦理审批列表
-- ✅ POST /studies/{study_id}/startup/feasibility - 创建可行性评估
-- ✅ POST /studies/{study_id}/startup/budget - 创建预算
-- ✅ POST /studies/{study_id}/startup/timeline - 创建时间表
-
-**项目权限管理 (Project Permissions)**
-- ✅ GET /studies/{study_id}/project-permissions - 查询项目权限
-- ✅ PUT /studies/{study_id}/project-permissions - 更新项目权限
-
-**项目概览 (Overview)**
-- ✅ GET /studies/{study_id}/overview - 查询项目概览
-
-**监查问题 (Monitoring Issues)**
-- ✅ POST /studies/{study_id}/monitoring-issues - 创建监查问题
-- ✅ GET /studies/{study_id}/monitoring-issues - 查询监查问题列表
-
-**药物发货 (Drug Shipments)**
-- ✅ POST /studies/{study_id}/drug-shipments - 创建药物发货
-- ✅ GET /studies/{study_id}/drug-shipments - 查询药物发货列表
-
-**物资管理 (Materials)**
-- ✅ POST /studies/{study_id}/materials - 创建物资
-- ✅ GET /studies/{study_id}/materials - 查询物资列表
-
-**参与者PDS (Subject PDS)**
-- ✅ POST /studies/{study_id}/subject-pds - 创建参与者PDS
-- ✅ GET /studies/{study_id}/subject-pds - 查询参与者PDS列表
-
-**审计日志 (Audit Logs)**
-- ✅ GET /studies/{study_id}/audit-logs - 查询审计日志列表
-- ✅ POST /studies/{study_id}/audit-logs/export - 导出审计日志
-
-**访视管理 (Visits)**
-- ✅ POST /studies/{study_id}/visits - 创建访视
-- ✅ GET /studies/{study_id}/visits - 查询访视列表
-
-**知识库笔记 (Knowledge Notes)**
-- ✅ POST /studies/{study_id}/knowledge-notes - 创建知识库笔记
-- ✅ GET /studies/{study_id}/knowledge-notes - 查询知识库笔记列表
-
-**参与者历史 (Subject Histories)**
-- ✅ GET /studies/{study_id}/subject-histories - 查询参与者历史列表
-- ✅ POST /studies/{study_id}/subject-histories/export - 导出参与者历史
-
-**项目里程碑 (Milestones)**
-- ✅ GET /studies/{study_id}/milestones - 查询项目里程碑列表
-- ✅ PATCH /studies/{study_id}/milestones/{id} - 更新项目里程碑
-
-**权限拒绝场景**
-- ✅ CRA 无法执行启动管理写操作
-- ✅ CRA 无法执行项目权限管理操作
-
-**向后兼容性验证**
-- ✅ startup 模块的模块级权限回退仍然有效
-- ✅ drug_shipments 模块的模块级权限回退仍然有效
-- ✅ startup 模块的接口级权限优先于模块级权限
-- ✅ materials 模块的接口级权限优先于模块级权限
-
-**权限矩阵一致性**
-- ✅ 第3批模块的权限矩阵一致性验证
-- ✅ ADMIN 角色总是被允许
-
-**关键验证:**
-- 所有端点权限检查正确
-- 权限拒绝时返回 403
-- 向后兼容性保证(模块级权限回退)
-- 接口级权限优先级正确
-
-## 修复的问题
-
-### 1. StudyRolePermission 模型参数错误
-**问题:** 测试使用了不存在的 `action` 和 `allowed` 参数
-**解决:** 更新测试使用正确的 `can_read` 和 `can_write` 参数
-
-### 2. 权限矩阵返回格式不匹配
-**问题:** `get_api_endpoint_permissions` 返回 `{role: {endpoint_key: bool}}`,但测试期望 `{role: {endpoint_key: {allowed: bool}}}`
-**解决:** 更新函数返回正确的嵌套字典格式
-
-### 3. 默认权限未返回
-**问题:** `get_api_endpoint_permissions` 在没有自定义权限时返回空字典
-**解决:** 更新函数初始化所有角色和端点的默认权限
-
-## 测试基础设施
-
-### 数据库配置
-- **类型:** SQLite 内存数据库
-- **UUID 处理:** 自定义 GUID TypeDecorator 支持 SQLite
-- **隔离:** 每个测试使用唯一的 study_code
-
-### 测试框架
-- **框架:** pytest + pytest-asyncio
-- **异步支持:** AsyncSession 和 async/await
-- **Fixtures:** event_loop, test_engine, db_session
-
-## 下一步工作
-
-### 已完成
-- ✅ 第1阶段:数据库设计
-- ✅ 第2阶段:权限配置系统
-- ✅ 第3阶段:权限检查依赖注入
-- ✅ 第4阶段:API端点迁移(第1批)
-- ✅ 第5阶段:权限管理API
-- ✅ 第6阶段:测试和文档
-- ✅ 第7阶段:迁移第2批模块(members, sites)
-- ✅ 第8阶段:迁移第3批模块(12个模块,63个端点)
-
-## 下一步工作
-
-### 已完成
-- ✅ 第1阶段:数据库设计
-- ✅ 第2阶段:权限配置系统
-- ✅ 第3阶段:权限检查依赖注入
-- ✅ 第4阶段:API端点迁移(第1批)
-- ✅ 第5阶段:权限管理API
-- ✅ 第6阶段:测试和文档
-- ✅ 第7阶段:迁移第2批模块(members, sites)
-- ✅ 第8阶段:迁移第3批模块(12个模块,63个端点)
-- ✅ 第9阶段:安全审计与性能优化
-
-### 待完成
-- [ ] 第10阶段:监控与告警
-- [ ] 第11阶段:文档完善
-
-## 性能指标
-
-- **测试执行时间:** 0.40 秒
-- **平均单个测试时间:** 3.7 毫秒
-- **代码覆盖率:** 85%
-- **总测试数:** 109 个
-
-## 结论
-
-接口级权限系统的核心功能已完全实现并通过全面测试。第3批模块(12个模块,63个端点)已成功迁移。系统具有:
-- ✅ 细粒度的接口级权限控制
-- ✅ 向后兼容的模块级权限回退
-- ✅ 清晰的权限优先级
-- ✅ 完整的权限管理API
-- ✅ 高代码覆盖率(85%)
-- ✅ 109 个测试用例全部通过
-
-**已迁移模块:**
-- 第1批:subjects, risk_issues, fees, finance_contracts(22 个端点)
-- 第2批:members, sites(9 个端点)
-- 第3批:audit_logs, drug_shipments, knowledge_notes, material_equipments, monitoring_visit_issues, overview, project_milestones, project_permissions, startup, subject_histories, subject_pds, visits(63 个端点)
-
-**总计:94 个端点已迁移**
-
-系统已准备好进行第9阶段的安全审计和性能优化。
diff --git a/backend/app/api/v1/permission_monitoring.py b/backend/app/api/v1/permission_monitoring.py
index 5a59535b..04b89736 100644
--- a/backend/app/api/v1/permission_monitoring.py
+++ b/backend/app/api/v1/permission_monitoring.py
@@ -15,7 +15,7 @@ from sqlalchemy import func, select, desc
from sqlalchemy.ext.asyncio import AsyncSession
from app.core.deps import get_current_user, get_db_session
-from app.core.permission_monitor import evaluate_permission_system_health, get_permission_monitor
+from app.core.permission_monitor import get_permission_monitor
from app.models.permission_access_log import PermissionAccessLog
from app.models.permission_metric_snapshot import PermissionMetricSnapshot
from app.models.security_access_log import SecurityAccessLog
@@ -31,10 +31,45 @@ router = APIRouter(prefix="/permission-monitoring", tags=["permission-monitoring
@router.get("/metrics", status_code=status.HTTP_200_OK)
async def get_permission_metrics(
+ db: AsyncSession = Depends(get_db_session),
_=Depends(get_current_user),
+ hours: int = Query(24, ge=1, le=720),
) -> dict:
+ """从 permission_access_logs 实时聚合权限检查指标"""
+ start_time = datetime.now(timezone.utc) - timedelta(hours=hours)
+ result = await db.execute(
+ select(
+ func.count().label("total_checks"),
+ func.count().filter(PermissionAccessLog.allowed.is_(True)).label("allowed_checks"),
+ func.count().filter(PermissionAccessLog.allowed.is_(False)).label("denied_checks"),
+ func.coalesce(func.sum(PermissionAccessLog.elapsed_ms), 0).label("total_time_ms"),
+ func.coalesce(func.min(PermissionAccessLog.elapsed_ms), 0).label("min_time_ms"),
+ func.coalesce(func.max(PermissionAccessLog.elapsed_ms), 0).label("max_time_ms"),
+ func.coalesce(func.avg(PermissionAccessLog.elapsed_ms), 0).label("avg_time_ms"),
+ ).where(PermissionAccessLog.created_at >= start_time)
+ )
+ row = result.one()
+ total = row.total_checks or 0
monitor = get_permission_monitor()
- return monitor.get_metrics()
+ cache_metrics = monitor.metrics.cache_metrics.to_dict()
+ return {
+ "window_hours": hours,
+ "check_metrics": {
+ "total_checks": total,
+ "allowed_checks": row.allowed_checks or 0,
+ "denied_checks": row.denied_checks or 0,
+ "total_time": round(float(row.total_time_ms) / 1000, 3),
+ "min_time": round(float(row.min_time_ms) / 1000, 3),
+ "max_time": round(float(row.max_time_ms) / 1000, 3),
+ "avg_time": round(float(row.avg_time_ms) / 1000, 3),
+ "allow_rate": round(row.allowed_checks / total * 100, 2) if total else 0,
+ "deny_rate": round(row.denied_checks / total * 100, 2) if total else 0,
+ "error_rate": 0,
+ "errors": 0,
+ },
+ "cache_metrics": cache_metrics,
+ "uptime_seconds": monitor.metrics.uptime_seconds,
+ }
@router.get("/cache-stats", status_code=status.HTTP_200_OK)
@@ -78,12 +113,54 @@ async def clear_alerts(
@router.get("/health", status_code=status.HTTP_200_OK)
async def permission_system_health(
+ db: AsyncSession = Depends(get_db_session),
_=Depends(get_current_user),
) -> dict:
+ """从 DB 聚合最近 1 小时数据评估权限系统健康状态"""
+ start_time = datetime.now(timezone.utc) - timedelta(hours=1)
+ result = await db.execute(
+ select(
+ func.count().label("total"),
+ func.count().filter(PermissionAccessLog.allowed.is_(False)).label("denied"),
+ func.coalesce(func.avg(PermissionAccessLog.elapsed_ms), 0).label("avg_ms"),
+ ).where(PermissionAccessLog.created_at >= start_time)
+ )
+ row = result.one()
+ total = row.total or 0
+ avg_ms = float(row.avg_ms)
+ deny_rate = round(row.denied / total * 100, 2) if total else 0
+
monitor = get_permission_monitor()
- metrics = monitor.get_metrics()
- cache_stats = monitor.get_cache_stats()
- return evaluate_permission_system_health(metrics, cache_stats)
+ cache_metrics = monitor.metrics.cache_metrics
+
+ health_score = 100
+ issues = []
+
+ if avg_ms > 10:
+ health_score -= 10
+ issues.append("权限检查响应时间过长")
+ if deny_rate > 50:
+ health_score -= 5
+ issues.append("权限拒绝率过高")
+ if (
+ cache_metrics.total_accesses >= 10
+ and cache_metrics.hit_rate < 50
+ ):
+ health_score -= 10
+ issues.append("缓存命中率过低")
+
+ return {
+ "status": "healthy" if health_score >= 80 else "degraded" if health_score >= 50 else "unhealthy",
+ "health_score": max(0, health_score),
+ "issues": issues,
+ "last_hour": {
+ "total_checks": total,
+ "denied_checks": row.denied or 0,
+ "deny_rate": deny_rate,
+ "avg_elapsed_ms": round(avg_ms, 2),
+ },
+ "cache_stats": monitor.get_cache_stats(),
+ }
# ═══════════════════════════════════════════
diff --git a/backend/app/core/deps.py b/backend/app/core/deps.py
index c86e5ce4..2344861f 100644
--- a/backend/app/core/deps.py
+++ b/backend/app/core/deps.py
@@ -162,10 +162,13 @@ def require_api_permission(endpoint_key: str, *, allow_system_admin: bool = True
raise
finally:
elapsed_ms = (time.perf_counter() - start_time) * 1000
- monitor = get_permission_monitor()
- monitor.record_permission_check(
- allowed=allowed, elapsed_time=elapsed_ms / 1000, error=error
- )
+ if error is not None or elapsed_ms > 50:
+ from app.core.permission_monitor import get_permission_monitor
+ monitor = get_permission_monitor()
+ if error is not None:
+ monitor.record_error_alert(error)
+ else:
+ monitor.record_slow_check_alert(elapsed_ms)
_enqueue_permission_log(
study_id, current_user.id, endpoint_key,
membership.role_in_study, allowed, elapsed_ms, request
diff --git a/backend/app/core/permission_monitor.py b/backend/app/core/permission_monitor.py
index b6e8f9be..547dfc41 100644
--- a/backend/app/core/permission_monitor.py
+++ b/backend/app/core/permission_monitor.py
@@ -1,16 +1,13 @@
"""权限系统监控
-实现权限系统的监控功能,包括:
-- 权限检查统计
-- 缓存性能监控
-- 异常检测
-- 性能指标收集
+收集缓存性能指标和告警信息。
+权限检查的统计数据(总次数、允许/拒绝、耗时)已持久化在
+permission_access_logs 表中,通过 /metrics 端点实时聚合查询。
"""
from __future__ import annotations
import time
-import uuid
from dataclasses import dataclass, field
from typing import Any
@@ -20,88 +17,28 @@ from app.core.permission_cache import get_permission_cache
CACHE_HIT_RATE_HEALTH_MIN_ACCESSES = 10
-@dataclass
-class PermissionCheckMetrics:
- """权限检查指标"""
-
- total_checks: int = 0 # 总检查次数
- allowed_checks: int = 0 # 允许的检查次数
- denied_checks: int = 0 # 拒绝的检查次数
- total_time: float = 0.0 # 总耗时(秒)
- min_time: float = float("inf") # 最小耗时(秒)
- max_time: float = 0.0 # 最大耗时(秒)
- errors: int = 0 # 错误次数
-
- @property
- def avg_time(self) -> float:
- """平均耗时(秒)"""
- if self.total_checks == 0:
- return 0.0
- return self.total_time / self.total_checks
-
- @property
- def allow_rate(self) -> float:
- """允许率(百分比)"""
- if self.total_checks == 0:
- return 0.0
- return (self.allowed_checks / self.total_checks) * 100
-
- @property
- def deny_rate(self) -> float:
- """拒绝率(百分比)"""
- if self.total_checks == 0:
- return 0.0
- return (self.denied_checks / self.total_checks) * 100
-
- @property
- def error_rate(self) -> float:
- """错误率(百分比)"""
- if self.total_checks == 0:
- return 0.0
- return (self.errors / self.total_checks) * 100
-
- def to_dict(self) -> dict[str, Any]:
- """转换为字典"""
- return {
- "total_checks": self.total_checks,
- "allowed_checks": self.allowed_checks,
- "denied_checks": self.denied_checks,
- "total_time": round(self.total_time, 3),
- "min_time": round(self.min_time, 3) if self.min_time != float("inf") else 0,
- "max_time": round(self.max_time, 3),
- "avg_time": round(self.avg_time, 3),
- "allow_rate": round(self.allow_rate, 2),
- "deny_rate": round(self.deny_rate, 2),
- "error_rate": round(self.error_rate, 2),
- "errors": self.errors,
- }
-
-
@dataclass
class CacheMetrics:
"""缓存指标"""
- total_accesses: int = 0 # 总访问次数
- cache_hits: int = 0 # 缓存命中次数
- cache_misses: int = 0 # 缓存未命中次数
- cache_invalidations: int = 0 # 缓存失效次数
+ total_accesses: int = 0
+ cache_hits: int = 0
+ cache_misses: int = 0
+ cache_invalidations: int = 0
@property
def hit_rate(self) -> float:
- """缓存命中率(百分比)"""
if self.total_accesses == 0:
return 0.0
return (self.cache_hits / self.total_accesses) * 100
@property
def miss_rate(self) -> float:
- """缓存未命中率(百分比)"""
if self.total_accesses == 0:
return 0.0
return (self.cache_misses / self.total_accesses) * 100
def to_dict(self) -> dict[str, Any]:
- """转换为字典"""
return {
"total_accesses": self.total_accesses,
"cache_hits": self.cache_hits,
@@ -114,101 +51,37 @@ class CacheMetrics:
@dataclass
class PermissionSystemMetrics:
- """权限系统指标"""
-
- check_metrics: PermissionCheckMetrics = field(default_factory=PermissionCheckMetrics)
cache_metrics: CacheMetrics = field(default_factory=CacheMetrics)
- last_reset_time: float = field(default_factory=time.time)
+ start_time: float = field(default_factory=time.time)
+
+ @property
+ def uptime_seconds(self) -> float:
+ return time.time() - self.start_time
def reset(self) -> None:
- """重置所有指标"""
- self.check_metrics = PermissionCheckMetrics()
self.cache_metrics = CacheMetrics()
- self.last_reset_time = time.time()
-
- def to_dict(self) -> dict[str, Any]:
- """转换为字典"""
- return {
- "check_metrics": self.check_metrics.to_dict(),
- "cache_metrics": self.cache_metrics.to_dict(),
- "uptime_seconds": time.time() - self.last_reset_time,
- }
+ self.start_time = time.time()
class PermissionMonitor:
- """权限系统监控器
-
- 收集权限系统的运行指标,用于监控和告警。
- """
+ """权限系统监控器(仅缓存指标 + 告警)"""
def __init__(self):
- """初始化监控器"""
self.metrics = PermissionSystemMetrics()
self._alerts: list[dict[str, Any]] = []
- def record_permission_check(
- self,
- allowed: bool,
- elapsed_time: float,
- error: Exception | None = None,
- ) -> None:
- """记录权限检查
-
- Args:
- allowed: 是否允许
- elapsed_time: 耗时(秒)
- error: 错误对象(如果有)
- """
- metrics = self.metrics.check_metrics
- metrics.total_checks += 1
-
- if allowed:
- metrics.allowed_checks += 1
- else:
- metrics.denied_checks += 1
-
- metrics.total_time += elapsed_time
- metrics.min_time = min(metrics.min_time, elapsed_time)
- metrics.max_time = max(metrics.max_time, elapsed_time)
-
- if error:
- metrics.errors += 1
- self._check_error_alert(error)
-
- # 检查性能告警
- self._check_performance_alert(elapsed_time)
-
def record_cache_hit(self) -> None:
- """记录缓存命中"""
- metrics = self.metrics.cache_metrics
- metrics.total_accesses += 1
- metrics.cache_hits += 1
+ self.metrics.cache_metrics.total_accesses += 1
+ self.metrics.cache_metrics.cache_hits += 1
def record_cache_miss(self) -> None:
- """记录缓存未命中"""
- metrics = self.metrics.cache_metrics
- metrics.total_accesses += 1
- metrics.cache_misses += 1
+ self.metrics.cache_metrics.total_accesses += 1
+ self.metrics.cache_metrics.cache_misses += 1
def record_cache_invalidation(self) -> None:
- """记录缓存失效"""
self.metrics.cache_metrics.cache_invalidations += 1
- def _check_performance_alert(self, elapsed_time: float) -> None:
- """检查性能告警
-
- 如果权限检查耗时过长,发出告警。
- """
- if elapsed_time > 0.05: # 50ms
- self._add_alert(
- level="warning",
- type="slow_permission_check",
- message=f"权限检查耗时过长: {elapsed_time*1000:.2f}ms",
- data={"elapsed_time": elapsed_time},
- )
-
- def _check_error_alert(self, error: Exception) -> None:
- """检查错误告警"""
+ def record_error_alert(self, error: Exception) -> None:
self._add_alert(
level="error",
type="permission_check_error",
@@ -216,55 +89,33 @@ class PermissionMonitor:
data={"error": str(error)},
)
- def _add_alert(
- self,
- level: str,
- type: str,
- message: str,
- data: dict[str, Any] | None = None,
- ) -> None:
- """添加告警
+ def record_slow_check_alert(self, elapsed_ms: float) -> None:
+ if elapsed_ms > 50:
+ self._add_alert(
+ level="warning",
+ type="slow_permission_check",
+ message=f"权限检查耗时过长: {elapsed_ms:.2f}ms",
+ data={"elapsed_ms": elapsed_ms},
+ )
- Args:
- level: 告警级别 (info, warning, error)
- type: 告警类型
- message: 告警消息
- data: 额外数据
- """
- alert = {
+ def _add_alert(self, level: str, type: str, message: str, data: dict[str, Any] | None = None) -> None:
+ self._alerts.append({
"timestamp": time.time(),
"level": level,
"type": type,
"message": message,
"data": data or {},
- }
- self._alerts.append(alert)
-
- # 只保留最近1000条告警
+ })
if len(self._alerts) > 1000:
self._alerts = self._alerts[-1000:]
def get_alerts(self, level: str | None = None, limit: int = 100) -> list[dict[str, Any]]:
- """获取告警列表
-
- Args:
- level: 告警级别过滤(可选)
- limit: 返回的最大告警数
-
- Returns:
- 告警列表
- """
alerts = self._alerts
if level:
alerts = [a for a in alerts if a["level"] == level]
return alerts[-limit:]
- def get_metrics(self) -> dict[str, Any]:
- """获取指标"""
- return self.metrics.to_dict()
-
def get_cache_stats(self) -> dict[str, Any]:
- """获取缓存统计"""
cache = get_permission_cache()
return {
"cache_items": cache.get_cache_stats(),
@@ -272,20 +123,16 @@ class PermissionMonitor:
}
def reset_metrics(self) -> None:
- """重置指标"""
self.metrics.reset()
def clear_alerts(self) -> None:
- """清除所有告警"""
self._alerts.clear()
-# 全局监控器实例
_permission_monitor: PermissionMonitor | None = None
def get_permission_monitor() -> PermissionMonitor:
- """获取全局权限监控器实例"""
global _permission_monitor
if _permission_monitor is None:
_permission_monitor = PermissionMonitor()
@@ -293,42 +140,5 @@ def get_permission_monitor() -> PermissionMonitor:
def set_permission_monitor(monitor: PermissionMonitor) -> None:
- """设置全局权限监控器实例(用于测试)"""
global _permission_monitor
_permission_monitor = monitor
-
-
-def evaluate_permission_system_health(metrics: dict[str, Any], cache_stats: dict[str, Any]) -> dict[str, Any]:
- """根据权限系统指标评估健康状态"""
- check_metrics = metrics["check_metrics"]
- cache_metrics = metrics["cache_metrics"]
-
- health_score = 100
- issues = []
-
- if check_metrics["error_rate"] > 1:
- health_score -= 20
- issues.append("权限检查错误率过高")
-
- if (
- cache_metrics["total_accesses"] >= CACHE_HIT_RATE_HEALTH_MIN_ACCESSES
- and cache_metrics["hit_rate"] < 50
- ):
- health_score -= 10
- issues.append("缓存命中率过低")
-
- if check_metrics["avg_time"] > 0.01:
- health_score -= 10
- issues.append("权限检查响应时间过长")
-
- if check_metrics["deny_rate"] > 50:
- health_score -= 5
- issues.append("权限拒绝率过高")
-
- return {
- "status": "healthy" if health_score >= 80 else "degraded" if health_score >= 50 else "unhealthy",
- "health_score": max(0, health_score),
- "issues": issues,
- "metrics": metrics,
- "cache_stats": cache_stats,
- }
diff --git a/backend/app/core/permission_monitoring_middleware.py b/backend/app/core/permission_monitoring_middleware.py
deleted file mode 100644
index 2aca1d08..00000000
--- a/backend/app/core/permission_monitoring_middleware.py
+++ /dev/null
@@ -1,80 +0,0 @@
-"""权限系统监控中间件
-
-自动收集权限检查的性能指标和告警信息。
-"""
-
-from __future__ import annotations
-
-import time
-from typing import Callable
-
-from app.core.permission_monitor import get_permission_monitor
-
-
-class PermissionMonitoringMiddleware:
- """权限监控中间件
-
- 在权限检查前后记录指标。
- """
-
- def __init__(self):
- """初始化中间件"""
- self.monitor = get_permission_monitor()
-
- def record_check(
- self,
- func: Callable,
- ) -> Callable:
- """装饰器:记录权限检查指标
-
- Args:
- func: 权限检查函数
-
- Returns:
- 装饰后的函数
- """
-
- async def wrapper(*args, **kwargs):
- start_time = time.time()
- try:
- result = await func(*args, **kwargs)
- elapsed_time = time.time() - start_time
- self.monitor.record_permission_check(
- allowed=result,
- elapsed_time=elapsed_time,
- )
- return result
- except Exception as e:
- elapsed_time = time.time() - start_time
- self.monitor.record_permission_check(
- allowed=False,
- elapsed_time=elapsed_time,
- error=e,
- )
- raise
-
- return wrapper
-
- def record_cache_hit(self) -> None:
- """记录缓存命中"""
- self.monitor.record_cache_hit()
-
- def record_cache_miss(self) -> None:
- """记录缓存未命中"""
- self.monitor.record_cache_miss()
-
- def record_cache_invalidation(self) -> None:
- """记录缓存失效"""
- self.monitor.record_cache_invalidation()
-
-
-# 全局中间件实例
-_middleware: PermissionMonitoringMiddleware | None = None
-
-
-def get_monitoring_middleware() -> PermissionMonitoringMiddleware:
- """获取全局监控中间件实例"""
- global _middleware
- if _middleware is None:
- _middleware = PermissionMonitoringMiddleware()
- return _middleware
diff --git a/docs/README.md b/docs/README.md
index 0c7cb5c4..c389c695 100644
--- a/docs/README.md
+++ b/docs/README.md
@@ -1,12 +1,20 @@
# Docs Index
-CTMS 文档按用途分为三类:当前操作手册、审计与治理记录、历史实现计划。
+CTMS 文档入口只展示当前仍会影响开发、发布和运维决策的内容。历史报告和历史计划保留在目录中,但不作为默认操作入口。
-## 当前常用
+## 当前约束
+- [`branch-governance.md`](branch-governance.md): 长期分支治理规则
- [`guides/release-checklist.md`](guides/release-checklist.md): 发布前检查项与回归门禁
+- [`audits/storage-persistence-governance.md`](audits/storage-persistence-governance.md): 重要数据落库治理基线
+- [`audits/module-level-permissions-transition.md`](audits/module-level-permissions-transition.md): 模块级权限迁移状态与约束
+
+## 当前操作手册
+
- [`guides/branch-environment-installation.md`](guides/branch-environment-installation.md): `dev`、`main`、`release` 分支环境安装配置
- [`guides/setup-config-api.md`](guides/setup-config-api.md): 立项配置接口、联调与冒烟说明
+- [`guides/frontend-permission-integration.md`](guides/frontend-permission-integration.md): 前端权限管理集成说明
+- [`guides/permission-system-testing.md`](guides/permission-system-testing.md): 权限系统测试指南
- [`setup-config-curl-smoke.sh`](setup-config-curl-smoke.sh): 立项配置 curl 冒烟脚本
- [`postman/setup-config.postman_collection.json`](postman/setup-config.postman_collection.json): Postman 联调集合
- [`postman/local.postman_environment.example.json`](postman/local.postman_environment.example.json): Postman 本地环境模板
@@ -14,16 +22,17 @@ CTMS 文档按用途分为三类:当前操作手册、审计与治理记录、
## 审计与治理
- [`audits/auth-session-acceptance.md`](audits/auth-session-acceptance.md): 鉴权会话验收清单
-- [`audits/enterprise-ui-acceptance-checklist.md`](audits/enterprise-ui-acceptance-checklist.md): Enterprise UI 验收结果
-- [`audits/setup-config-code-audit.md`](audits/setup-config-code-audit.md): 立项配置代码审计记录
-- [`audits/storage-persistence-audit.md`](audits/storage-persistence-audit.md): 存储落库扫描快照
- [`audits/storage-persistence-governance.md`](audits/storage-persistence-governance.md): 存储落库治理基线与流程
+- [`audits/module-level-permissions-transition.md`](audits/module-level-permissions-transition.md): 模块级权限迁移状态与约束
+- [`audits/`](audits): 验收记录、归档审计和可再生成扫描快照
-## 历史计划
+## 历史归档
-- [`plans/`](plans): 设计稿、实施计划与历史交付记录
+- [`reports/`](reports): 阶段总结、修复报告、交付说明和已降级的历史评估
+- [`plans/`](plans): 设计稿、实施计划和已完成/已移除方案记录
约定:
- `guides/` 放当前仍会被执行、查阅或复制命令的操作文档
-- `audits/` 放验收、治理、审计、扫描结果
+- `audits/` 放当前仍有效的验收、治理、审计、扫描结果
+- `reports/` 只做历史追溯,不作为当前操作依据
- `plans/` 只作为历史记录,不作为当前操作入口
diff --git a/docs/audits/module-level-permissions-transition.md b/docs/audits/module-level-permissions-transition.md
new file mode 100644
index 00000000..3d6311d9
--- /dev/null
+++ b/docs/audits/module-level-permissions-transition.md
@@ -0,0 +1,35 @@
+# 模块级权限迁移状态
+
+状态: `active`
+适用范围: `permissions`
+最后更新: `2026-05-21`
+
+## 当前结论
+
+模块级权限不再作为当前业务权限控制的主路径。当前运行时代码以接口级权限为主,`backend/app` 和 `backend/tests` 中未检出 `role_has_project_permission`、`require_study_permission` 或 `StudyRolePermission` 的直接使用。
+
+`study_role_permissions` 仍出现在历史 Alembic 迁移中。这表示数据库结构存在历史来源,不等同于当前运行时代码仍依赖模块级权限。
+
+## 当前约束
+
+- 新功能不得新增模块级权限调用。
+- 新权限控制应接入接口级权限配置与前置权限机制。
+- 删除 `study_role_permissions` 表前,必须先完成数据库数据检查、回滚方案和迁移脚本评审。
+- 历史评估文档仅用于追溯,不作为当前执行依据。
+
+## 复评条件
+
+满足以下条件后,才可以进入表结构移除设计:
+
+- `rg -n "role_has_project_permission|require_study_permission|StudyRolePermission" backend/app backend/tests` 无运行时代码或测试依赖。
+- 生产数据确认 `study_role_permissions` 无仍需迁移的有效配置。
+- 已定义删除迁移、回滚迁移和发布窗口。
+- 权限系统测试覆盖接口级权限矩阵、前置权限、权限模板和监控接口。
+
+## 历史结论演进
+
+- 早期评估认为模块级权限仍需保留,用于兼容旧接口和简化前端配置。
+- 迁移依赖分析认为模块级权限暂不可立即移除,需要先完成接口级权限覆盖。
+- 后续移除评估认为运行时代码完成迁移后,可以在观察期和数据检查后移除遗留表结构。
+
+上述细节已作为历史报告清理出工作树。当前执行依据以本文档为准。
diff --git a/docs/branch-governance.md b/docs/branch-governance.md
index a98b599e..78b2a386 100644
--- a/docs/branch-governance.md
+++ b/docs/branch-governance.md
@@ -280,7 +280,7 @@ Before promoting `main` to `release`, confirm:
Reference:
-- [docs/release-checklist.md](/Users/zcc/MyCTMS/ctms-dev/docs/release-checklist.md)
+- [docs/guides/release-checklist.md](guides/release-checklist.md)
## 10. Standard Workflow Examples
diff --git a/FRONTEND_PERMISSION_INTEGRATION.md b/docs/guides/frontend-permission-integration.md
similarity index 100%
rename from FRONTEND_PERMISSION_INTEGRATION.md
rename to docs/guides/frontend-permission-integration.md
diff --git a/TESTING_GUIDE.md b/docs/guides/permission-system-testing.md
similarity index 100%
rename from TESTING_GUIDE.md
rename to docs/guides/permission-system-testing.md
diff --git a/docs/plans/2026-03-30-remove-nginx-design.md b/docs/plans/2026-03-30-remove-nginx-design.md
deleted file mode 100644
index f63b8555..00000000
--- a/docs/plans/2026-03-30-remove-nginx-design.md
+++ /dev/null
@@ -1,20 +0,0 @@
-# Remove In-Repo Nginx Design
-
-**Goal:** Remove all in-repository Nginx runtime, build, publish, and documentation responsibilities while keeping the frontend as an independently deployable image behind an external reverse proxy.
-
-**Decisions**
-- Remove the `nginx/` directory and every repository-owned Nginx configuration reference.
-- Keep `docker-compose.yaml` as the deployment entrypoint, but change the production topology to `frontend + backend + db`.
-- Keep `frontend` as a standalone runtime image that serves the built SPA directly.
-- Keep `backend` as the only API container and let the external reverse proxy route `/api` and `/health` to it.
-- Update historical docs so they describe the current topology instead of preserving obsolete Nginx-based guidance.
-
-**Architecture**
-- `db` remains the persistent PostgreSQL service.
-- `backend` remains the FastAPI service exposed on port `8000`.
-- `frontend` becomes a standalone container image built from `frontend/Dockerfile` and serves the compiled SPA on its own port.
-- The external reverse proxy is now the only public entrypoint and is responsible for routing browser traffic to `frontend` and API traffic to `backend`.
-
-**Operational Notes**
-- The frontend still assumes same-origin API access, so the external proxy must route `/api/*` and `/health` to `backend`.
-- Verification should prove that no runtime, CI, or document references to repository-managed Nginx remain.
diff --git a/docs/plans/2026-03-30-remove-nginx-implementation.md b/docs/plans/2026-03-30-remove-nginx-implementation.md
deleted file mode 100644
index c78d57d1..00000000
--- a/docs/plans/2026-03-30-remove-nginx-implementation.md
+++ /dev/null
@@ -1,92 +0,0 @@
-# Remove In-Repo Nginx Implementation Plan
-
-> **For Claude:** REQUIRED SUB-SKILL: Use superpowers:executing-plans to implement this plan task-by-task.
-
-**Goal:** Remove repository-managed Nginx and replace it with a standalone frontend image plus backend image that are intended to sit behind an external reverse proxy.
-
-**Architecture:** The compose stack runs `db`, `backend`, and `frontend`. The frontend image builds the Vite SPA and serves it directly from its own container, while the backend continues serving the API on port `8000`. External infrastructure performs the only reverse proxy routing.
-
-**Tech Stack:** Docker Compose, FastAPI, Vue/Vite, Node.js, PostgreSQL, GitHub Actions
-
----
-
-### Task 1: Define the frontend runtime image
-
-**Files:**
-- Modify: `frontend/Dockerfile`
-
-**Step 1: Keep the multi-stage frontend build**
-
-Retain the existing build stage so `npm ci` and `npm run build` still produce `dist`.
-
-**Step 2: Turn the final image into a runnable frontend container**
-
-Use a lightweight runtime image that copies `dist`, exposes a stable frontend port, and starts a static file server suitable for SPA delivery.
-
-**Step 3: Verify Dockerfile shape**
-
-Run: `sed -n '1,200p' frontend/Dockerfile`
-Expected: final stage exposes a frontend port and contains a runnable `CMD`.
-
-### Task 2: Replace nginx topology with frontend topology
-
-**Files:**
-- Modify: `docker-compose.yaml`
-
-**Step 1: Remove the `nginx` service**
-
-Delete the Nginx image, build, ports, and volume mounts.
-
-**Step 2: Add a `frontend` service**
-
-Build from `frontend/Dockerfile`, define a private-registry-backed `FRONTEND_IMAGE` default, and expose the frontend runtime port.
-
-**Step 3: Verify rendered compose**
-
-Run: `docker compose config`
-Expected: only `db`, `backend`, `backend-init`, and `frontend` render.
-
-### Task 3: Rewrite docs to match the new topology
-
-**Files:**
-- Modify: `README.md`
-- Modify: `docs/guides/release-checklist.md`
-- Modify: `docs/plans/2026-03-27-production-compose-design.md`
-- Modify: `docs/plans/2026-03-27-production-compose-implementation.md`
-- Modify: `docs/plans/2026-03-27-production-init-design.md`
-- Modify: `docs/plans/2026-03-27-production-init-implementation.md`
-- Modify: `docs/plans/2026-03-30-tcloud-private-registry-design.md`
-- Modify: `docs/plans/2026-03-30-tcloud-private-registry.md`
-
-**Step 1: Remove repository-managed Nginx language**
-
-Replace every statement that says Nginx is the public entrypoint, serves the SPA, or is a published runtime image.
-
-**Step 2: Document the external reverse proxy expectation**
-
-Describe the runtime as frontend and backend containers behind an external proxy.
-
-**Step 3: Verify search results**
-
-Run: `rg -n --hidden -g '!/.git' -S "nginx|ctms-nginx|NGINX_IMAGE|Nginx" README.md docker-compose.yaml .github scripts docs`
-Expected: no active repository-managed Nginx references remain.
-
-### Task 4: Remove obsolete nginx files and verify end-to-end cleanup
-
-**Files:**
-- Delete: `nginx/Dockerfile`
-- Delete: `nginx/nginx.conf`
-
-**Step 1: Delete the obsolete files**
-
-Remove the Nginx directory because nothing in the repository should depend on it anymore.
-
-**Step 2: Validate compose and script syntax**
-
-Run: `docker compose config`
-Expected: PASS
-
-**Step 3: Validate frontend and backend image references**
-
-Run: `sed -n '1,160p' README.md`
-Expected: deployment instructions mention `frontend` and `backend`, not `nginx`.
diff --git a/docs/plans/2026-03-30-remove-tcloud-publish-implementation.md b/docs/plans/2026-03-30-remove-tcloud-publish-implementation.md
index abfeb14e..57e89816 100644
--- a/docs/plans/2026-03-30-remove-tcloud-publish-implementation.md
+++ b/docs/plans/2026-03-30-remove-tcloud-publish-implementation.md
@@ -58,17 +58,15 @@ Document the repository as local-build-first and external-deploy-managed.
### Task 4: Rewrite historical design records
-**Files:**
-- Modify: `docs/plans/2026-03-30-tcloud-private-registry-design.md`
-- Modify: `docs/plans/2026-03-30-tcloud-private-registry.md`
-- Modify: `docs/plans/2026-03-30-remove-nginx-design.md`
-- Modify: `docs/plans/2026-03-30-remove-nginx-implementation.md`
+**Step 1: Remove superseded Tencent Cloud/private registry records**
-**Step 1: Mark the Tencent Cloud/private registry path as removed**
+Remove the standalone Tencent Cloud private registry design and implementation records after the active publish path has been deleted. Keep only this cleanup plan as the historical trace.
-Keep the documents but rewrite them so they no longer read as active implementation guidance.
+**Step 2: Remove superseded Nginx removal records**
-**Step 2: Remove references to active publish files**
+Remove the standalone Nginx removal design and implementation records because the repository-managed Nginx entrypoint was later restored. Keep `2026-03-31-restore-nginx-design.md` as the current historical record for that reversal.
+
+**Step 3: Remove references to active publish files**
Update any references to `.github/workflows/publish-images.yml`, `scripts/build-and-push-registry.sh`, or private-registry defaults if those files/configs no longer exist.
diff --git a/docs/plans/2026-03-30-tcloud-private-registry-design.md b/docs/plans/2026-03-30-tcloud-private-registry-design.md
deleted file mode 100644
index ede105cb..00000000
--- a/docs/plans/2026-03-30-tcloud-private-registry-design.md
+++ /dev/null
@@ -1,15 +0,0 @@
-# Tencent Cloud Private Registry Design (Removed)
-
-**Status:** Removed on 2026-03-30.
-
-**Goal:** This document describes a publish path that has since been removed from the repository.
-
-**Removal Summary**
-- The GitHub Actions publish workflow has been deleted.
-- The repository-owned remote build-and-push script has been deleted.
-- `docker-compose.yaml` no longer defaults to private registry image names.
-- Tencent Cloud and private registry deployment are no longer active repository features.
-
-**Current Replacement**
-- Use local `docker compose build` workflows inside this repository.
-- If deployment needs a registry in the future, reintroduce it as a fresh design rather than relying on the removed Tencent Cloud path.
diff --git a/docs/plans/2026-03-30-tcloud-private-registry.md b/docs/plans/2026-03-30-tcloud-private-registry.md
deleted file mode 100644
index ed9971bf..00000000
--- a/docs/plans/2026-03-30-tcloud-private-registry.md
+++ /dev/null
@@ -1,12 +0,0 @@
-# Tencent Cloud Private Registry Implementation Plan (Removed)
-
-**Status:** Removed on 2026-03-30.
-
-**Goal:** This implementation plan is retained only as historical record. The Tencent Cloud publish path and private registry defaults have been removed from the repository.
-
-**Removal Outcome:** The workflow file, remote publish script, and private-registry-based compose defaults have been deleted. Local build-based workflows are now the only repository-owned path.
-
-**Tech Stack:** GitHub Actions, SSH, Docker, self-hosted `registry:2`, Docker Compose, FastAPI, Node.js
-
----
-This plan is no longer executable as written because the referenced workflow, script, and compose defaults have been removed.
diff --git a/DOCKER_FIX_REPORT.md b/docs/reports/docker-fix-report.md
similarity index 100%
rename from DOCKER_FIX_REPORT.md
rename to docs/reports/docker-fix-report.md
diff --git a/backend/PROJECT_COMPLETION_SUMMARY.md b/docs/reports/permission-system-project-completion-summary.md
similarity index 95%
rename from backend/PROJECT_COMPLETION_SUMMARY.md
rename to docs/reports/permission-system-project-completion-summary.md
index ca92fe1b..7a236798 100644
--- a/backend/PROJECT_COMPLETION_SUMMARY.md
+++ b/docs/reports/permission-system-project-completion-summary.md
@@ -169,11 +169,11 @@ FastAPI依赖注入 → require_api_permission()
| 文档 | 内容 |
|------|------|
-| `SECURITY_AUDIT.md` | 安全审计报告 |
-| `PERFORMANCE_OPTIMIZATION.md` | 性能优化报告 |
-| `MONITORING_DASHBOARD.md` | 监控仪表板文档 |
-| `IMPLEMENTATION_SUMMARY.md` | 实现总结 |
-| `TESTING_SUMMARY.md` | 测试总结 |
+| 安全审计 | 已合并为本总结的安全审计与风险控制章节 |
+| 性能优化 | 已合并为本总结的性能优化章节 |
+| 监控仪表板 | 已合并为本总结的监控与告警章节 |
+| 后端实现 | 已合并为本总结的核心基础设施与端点迁移章节 |
+| 测试总结 | 已合并为本总结的测试覆盖章节 |
### 文档特点
@@ -481,11 +481,7 @@ curl -H "Authorization: Bearer $TOKEN" \
- `tests/test_permission_monitoring_api.py` - 监控 API 测试
**文档**:
-- `SECURITY_AUDIT.md` - 安全审计报告
-- `PERFORMANCE_OPTIMIZATION.md` - 性能优化报告
-- `MONITORING_DASHBOARD.md` - 监控仪表板文档
-- `IMPLEMENTATION_SUMMARY.md` - 实现总结
-- `TESTING_SUMMARY.md` - 测试总结
+- 安全审计、性能优化、监控仪表板、后端实现和测试总结已合并保留在本文档中。
---
diff --git a/PHASE_11_COMPLETION_SUMMARY.md b/docs/reports/phase-11-permission-frontend-summary.md
similarity index 98%
rename from PHASE_11_COMPLETION_SUMMARY.md
rename to docs/reports/phase-11-permission-frontend-summary.md
index c58fccdf..3d4b6923 100644
--- a/PHASE_11_COMPLETION_SUMMARY.md
+++ b/docs/reports/phase-11-permission-frontend-summary.md
@@ -147,7 +147,7 @@
#### 10. 集成指南文档 ✅
-**文件**: `FRONTEND_PERMISSION_INTEGRATION.md`
+**文件**: `../guides/frontend-permission-integration.md`
**内容**:
- 功能特性说明
@@ -233,7 +233,7 @@
| `/frontend/src/views/admin/ApiPermissions.test.ts` | 主页面测试 |
| `/frontend/src/components/ApiEndpointPermissions.test.ts` | 接口级权限测试 |
| `/frontend/src/components/PermissionMonitoring.test.ts` | 监控仪表板测试 |
-| `FRONTEND_PERMISSION_INTEGRATION.md` | 集成指南文档 |
+| `../guides/frontend-permission-integration.md` | 集成指南文档 |
### 修改文件
diff --git a/frontend/src/components/PermissionIpLocations.vue b/frontend/src/components/PermissionIpLocations.vue
index 90028589..62686944 100644
--- a/frontend/src/components/PermissionIpLocations.vue
+++ b/frontend/src/components/PermissionIpLocations.vue
@@ -72,13 +72,13 @@ import VChart from "vue-echarts";
import { registerMap, use } from "echarts/core";
import { CanvasRenderer } from "echarts/renderers";
import { MapChart } from "echarts/charts";
-import { TooltipComponent } from "echarts/components";
+import { TooltipComponent, VisualMapComponent } from "echarts/components";
import chinaMapGeoJson from "../assets/china.json";
import { fetchIpLocations } from "../api/projectPermissions";
import type { IpLocationsResponse, IpLocationStatItem } from "../types/api";
import { normalizeProvinceName } from "./chinaProvinceMap";
-use([CanvasRenderer, MapChart, TooltipComponent]);
+use([CanvasRenderer, MapChart, TooltipComponent, VisualMapComponent]);
registerMap("ctms-china", chinaMapGeoJson as any);
const IconGlobe = () => h("svg", { viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", "stroke-width": "2" }, [
@@ -185,6 +185,11 @@ const provinceData = computed(() => {
return Array.from(provinceMap.values());
});
+const maxValue = computed(() => {
+ const values = provinceData.value.map((item) => item.total_count);
+ return values.length ? Math.max(...values) : 100;
+});
+
const chinaMapOption = computed(() => ({
tooltip: {
trigger: "item",
@@ -200,6 +205,18 @@ const chinaMapOption = computed(() => ({
].join("
");
},
},
+ visualMap: {
+ min: 0,
+ max: maxValue.value,
+ left: "left",
+ bottom: "20",
+ text: ["高", "低"],
+ inRange: {
+ color: ["#edf3ff", "#a5c4fd", "#6399f7", "#3b82f6", "#1d4ed8"],
+ },
+ show: true,
+ calculable: false,
+ },
series: [
{
name: "用户分布图",
diff --git a/scripts/install-ctms.sh b/scripts/install-ctms.sh
index 962985fe..43e95562 100755
--- a/scripts/install-ctms.sh
+++ b/scripts/install-ctms.sh
@@ -1,51 +1,171 @@
#!/usr/bin/env bash
set -euo pipefail
+# ─────────────────────────────────────────────
+# CTMS 一键安装脚本
+# ─────────────────────────────────────────────
+
TARGET_ENV=""
BASE_URL=""
ASSUME_YES=0
SKIP_BUILD=0
SKIP_MIGRATE=0
+VERBOSE=0
ROOT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)"
ENV_FILE="$ROOT_DIR/.env"
+LOG_DIR="$ROOT_DIR/.install-logs"
-usage() {
- cat <<'EOF'
-用法:
- bash scripts/install-ctms.sh dev|main|release [选项]
+# ── 颜色 & 样式 ──────────────────────────────
+_has_color() { [[ -t 1 ]] && command -v tput >/dev/null 2>&1 && [[ "$(tput colors 2>/dev/null)" -ge 8 ]]; }
-选项:
- --base-url 健康检查使用的访问地址。dev/main 默认 http://127.0.0.1:8888,release 必填或交互输入。
- --yes 跳过交互确认,适合自动化执行。
- --skip-build 跳过 docker compose up -d --build。
- --skip-migrate 跳过 alembic upgrade head。
- -h, --help 显示帮助。
+if _has_color; then
+ C_RESET="$(tput sgr0)"
+ C_BOLD="$(tput bold)"
+ C_DIM="$(tput dim 2>/dev/null || printf '')"
+ C_BLUE="$(tput setaf 4)"
+ C_CYAN="$(tput setaf 6)"
+ C_GREEN="$(tput setaf 2)"
+ C_YELLOW="$(tput setaf 3)"
+ C_RED="$(tput setaf 1)"
+ C_WHITE="$(tput setaf 7)"
+else
+ C_RESET="" C_BOLD="" C_DIM="" C_BLUE="" C_CYAN=""
+ C_GREEN="" C_YELLOW="" C_RED="" C_WHITE=""
+fi
-示例:
- bash scripts/install-ctms.sh dev
- bash scripts/install-ctms.sh main --base-url http://127.0.0.1:8888
- bash scripts/install-ctms.sh release --base-url https://ctms.example.com
-EOF
+# ── 输出函数 ─────────────────────────────────
+
+_banner() {
+ printf '\n'
+ printf '%s╔══════════════════════════════════════════════╗%s\n' "${C_BLUE}${C_BOLD}" "${C_RESET}"
+ printf '%s║%s ██████╗████████╗███╗ ███╗███████╗ %s║%s\n' "${C_BLUE}${C_BOLD}" "${C_CYAN}" "${C_BLUE}" "${C_RESET}"
+ printf '%s║%s ██╔════╝╚══██╔══╝████╗ ████║██╔════╝ %s║%s\n' "${C_BLUE}${C_BOLD}" "${C_CYAN}" "${C_BLUE}" "${C_RESET}"
+ printf '%s║%s ██║ ██║ ██╔████╔██║███████╗ %s║%s\n' "${C_BLUE}${C_BOLD}" "${C_CYAN}" "${C_BLUE}" "${C_RESET}"
+ printf '%s║%s ██║ ██║ ██║╚██╔╝██║╚════██║ %s║%s\n' "${C_BLUE}${C_BOLD}" "${C_CYAN}" "${C_BLUE}" "${C_RESET}"
+ printf '%s║%s ╚██████╗ ██║ ██║ ╚═╝ ██║███████║ %s║%s\n' "${C_BLUE}${C_BOLD}" "${C_CYAN}" "${C_BLUE}" "${C_RESET}"
+ printf '%s║%s ╚═════╝ ╚═╝ ╚═╝ ╚═╝╚══════╝ %s║%s\n' "${C_BLUE}${C_BOLD}" "${C_CYAN}" "${C_BLUE}" "${C_RESET}"
+ printf '%s║%s 临床试验管理系统 安装程序 %s║%s\n' "${C_BLUE}${C_BOLD}" "${C_DIM}${C_WHITE}" "${C_BLUE}" "${C_RESET}"
+ printf '%s╚══════════════════════════════════════════════╝%s\n' "${C_BLUE}${C_BOLD}" "${C_RESET}"
+ printf '\n'
}
-log() {
- printf '[CTMS] %s\n' "$*"
-}
+log() { printf ' %s·%s %s\n' "${C_DIM}" "${C_RESET}" "$*"; }
+ok() { printf ' %s✔%s %s\n' "${C_GREEN}${C_BOLD}" "${C_RESET}" "$*"; }
+warn() { printf ' %s⚠%s %s\n' "${C_YELLOW}${C_BOLD}" "${C_RESET}" "$*" >&2; }
step() {
- printf '\n[CTMS] >>> %s\n' "$*"
+ local idx="${_STEP_IDX:-0}"
+ _STEP_IDX=$(( idx + 1 ))
+ printf '\n%s┌─ 步骤 %s · %s%s\n' \
+ "${C_BLUE}${C_BOLD}" "$_STEP_IDX" "$*" "${C_RESET}"
}
fail() {
- printf '\n[CTMS] 安装中止: %s\n' "$*" >&2
+ printf '\n %s✖ 安装中止%s\n' "${C_RED}${C_BOLD}" "${C_RESET}" >&2
+ printf ' %s%s%s\n\n' "${C_RED}" "$*" "${C_RESET}" >&2
exit 1
}
run() {
- log "执行: $*"
+ printf ' %s$%s %s%s%s\n' "${C_DIM}" "${C_RESET}" "${C_WHITE}" "$*" "${C_RESET}"
"$@"
}
+# 运行长命令,默认折叠输出。失败或 --verbose 时回放完整日志。
+# 用法: run_quiet <提示语> -- <命令...>
+run_quiet() {
+ local label="$1"; shift
+ [[ "${1:-}" == "--" ]] && shift
+
+ if [[ "$VERBOSE" -eq 1 ]]; then
+ printf ' %s$%s %s%s%s\n' "${C_DIM}" "${C_RESET}" "${C_WHITE}" "$*" "${C_RESET}"
+ "$@"
+ return $?
+ fi
+
+ mkdir -p "$LOG_DIR"
+ local log_file
+ log_file="$(mktemp "$LOG_DIR/cmd_XXXXXXXX")"
+ local start_ts elapsed exit_code
+ start_ts="$(date +%s)"
+
+ set +e
+ if [[ -t 1 ]]; then
+ "$@" >"$log_file" 2>&1 &
+ local cmd_pid=$!
+ _spinner_for "$cmd_pid" "$label" "$start_ts" &
+ local spin_pid=$!
+ wait "$cmd_pid"
+ exit_code=$?
+ kill "$spin_pid" 2>/dev/null || true
+ wait "$spin_pid" 2>/dev/null || true
+ printf '\r\033[K'
+ else
+ printf ' · %s …\n' "$label"
+ "$@" >"$log_file" 2>&1
+ exit_code=$?
+ fi
+ set -e
+
+ elapsed=$(( $(date +%s) - start_ts ))
+ if [[ "$exit_code" -eq 0 ]]; then
+ printf ' %s✔%s %s %s(%ss)%s\n' \
+ "${C_GREEN}${C_BOLD}" "${C_RESET}" "$label" "${C_DIM}" "$elapsed" "${C_RESET}"
+ return 0
+ fi
+
+ printf '\n %s✖%s %s 失败(耗时 %ss),完整输出:\n\n' \
+ "${C_RED}${C_BOLD}" "${C_RESET}" "$label" "$elapsed" >&2
+ sed 's/^/ /' "$log_file" >&2
+ printf '\n' >&2
+ fail "命令执行失败,详细日志保留于:${log_file#$ROOT_DIR/}(追加 --verbose 可看实时输出)"
+}
+
+_spinner_for() {
+ local cmd_pid="$1" label="$2" start_ts="$3"
+ local frames=('⠋' '⠙' '⠹' '⠸' '⠼' '⠴' '⠦' '⠧' '⠇' '⠏')
+ local i=0
+ while kill -0 "$cmd_pid" 2>/dev/null; do
+ local elapsed=$(( $(date +%s) - start_ts ))
+ printf '\r\033[K %s%s%s %s %s(%ss)%s' \
+ "${C_CYAN}${C_BOLD}" "${frames[i]}" "${C_RESET}" \
+ "$label" "${C_DIM}" "$elapsed" "${C_RESET}"
+ i=$(( (i + 1) % ${#frames[@]} ))
+ sleep 0.1
+ done
+}
+
+# ── 帮助 ─────────────────────────────────────
+
+usage() {
+ cat <${C_RESET} [选项]
+
+${C_BOLD}环境:${C_RESET}
+ ${C_CYAN}dev${C_RESET} 本地开发环境(使用默认密钥,不生成 RSA 密钥对)
+ ${C_CYAN}main${C_RESET} 内网测试 / 预发布环境
+ ${C_CYAN}release${C_RESET} 生产环境(强制 HTTPS,自动生成密钥对)
+
+${C_BOLD}选项:${C_RESET}
+ ${C_WHITE}--base-url ${C_RESET} 健康检查使用的访问地址
+ dev/main 默认 http://127.0.0.1:8888
+ release 必须通过此参数或交互输入提供
+ ${C_WHITE}--yes${C_RESET} 跳过所有交互确认,适合 CI/CD 自动化执行
+ ${C_WHITE}--skip-build${C_RESET} 跳过镜像重新构建(docker compose up -d)
+ ${C_WHITE}--skip-migrate${C_RESET} 跳过数据库迁移(alembic upgrade head)
+ ${C_WHITE}--verbose${C_RESET} 展开所有命令的实时输出(默认折叠为 spinner)
+ ${C_WHITE}-h, --help${C_RESET} 显示此帮助信息
+
+${C_BOLD}示例:${C_RESET}
+ bash scripts/install-ctms.sh dev
+ bash scripts/install-ctms.sh main --base-url http://127.0.0.1:8888
+ bash scripts/install-ctms.sh release --base-url https://ctms.example.com --yes
+EOF
+}
+
+# ── 参数解析 ─────────────────────────────────
+
parse_args() {
if [[ $# -eq 0 ]]; then
usage
@@ -57,39 +177,25 @@ parse_args() {
case "$TARGET_ENV" in
dev|main|release) ;;
- -h|--help)
- usage
- exit 0
- ;;
+ -h|--help) usage; exit 0 ;;
*)
usage
- fail "目标环境必须是 dev、main 或 release"
+ fail "未知环境 \"$TARGET_ENV\",有效值为 dev、main 或 release"
;;
esac
while [[ $# -gt 0 ]]; do
case "$1" in
--base-url)
- [[ $# -ge 2 ]] || fail "--base-url 需要提供 URL"
+ [[ $# -ge 2 ]] || fail "--base-url 需要提供一个 URL 参数"
BASE_URL="$2"
shift 2
;;
- --yes)
- ASSUME_YES=1
- shift
- ;;
- --skip-build)
- SKIP_BUILD=1
- shift
- ;;
- --skip-migrate)
- SKIP_MIGRATE=1
- shift
- ;;
- -h|--help)
- usage
- exit 0
- ;;
+ --yes) ASSUME_YES=1; shift ;;
+ --skip-build) SKIP_BUILD=1; shift ;;
+ --skip-migrate) SKIP_MIGRATE=1; shift ;;
+ --verbose) VERBOSE=1; shift ;;
+ -h|--help) usage; exit 0 ;;
*)
usage
fail "未知参数: $1"
@@ -98,12 +204,14 @@ parse_args() {
done
}
-require_command() {
- local command_name="$1"
- local install_hint="$2"
- if ! command -v "$command_name" >/dev/null 2>&1; then
- printf '[CTMS] 缺少依赖: %s\n' "$command_name" >&2
- printf '[CTMS] 安装建议: %s\n' "$install_hint" >&2
+# ── 依赖检查 ─────────────────────────────────
+
+_require_command() {
+ local cmd="$1"
+ local hint="$2"
+ if ! command -v "$cmd" >/dev/null 2>&1; then
+ warn "缺少依赖: ${C_BOLD}${cmd}${C_RESET}"
+ warn "安装方式: $hint"
return 1
fi
}
@@ -111,40 +219,37 @@ require_command() {
check_dependencies() {
step "检查系统依赖"
local missing=0
- require_command docker "安装 Docker Engine 或 Docker Desktop,并确认 docker 命令可用。" || missing=1
- require_command openssl "Linux: sudo apt-get install -y openssl;macOS: brew install openssl。" || missing=1
- require_command curl "Linux: sudo apt-get install -y curl;macOS: brew install curl。" || missing=1
- if [[ "$missing" -ne 0 ]]; then
- fail "系统依赖不完整"
- fi
+ _require_command docker "安装 Docker Engine 或 Docker Desktop,并确认 docker 命令可用" || missing=1
+ _require_command openssl "Linux: sudo apt-get install -y openssl | macOS: brew install openssl" || missing=1
+ _require_command curl "Linux: sudo apt-get install -y curl | macOS: brew install curl" || missing=1
+ [[ "$missing" -eq 0 ]] || fail "系统依赖不完整,请按上方提示安装后重试"
+
if ! docker compose version >/dev/null 2>&1; then
- fail "缺少 Docker Compose v2,请确认 docker compose version 可正常执行"
+ fail "未检测到 Docker Compose v2,请确认 docker compose version 可正常执行"
fi
- log "依赖检查通过"
+ ok "系统依赖检查通过"
}
+# ── 环境参数计算 ──────────────────────────────
+
compose_project_name() {
case "$TARGET_ENV" in
- dev) printf 'ctms_dev' ;;
- main) printf 'ctms_main' ;;
+ dev) printf 'ctms_dev' ;;
+ main) printf 'ctms_main' ;;
release) printf 'ctms_release' ;;
esac
}
app_env() {
- if [[ "$TARGET_ENV" == "dev" ]]; then
- printf 'development'
- else
- printf 'production'
- fi
+ [[ "$TARGET_ENV" == "dev" ]] && printf 'development' || printf 'production'
}
key_id() {
local today
today="$(date +%Y%m%d)"
case "$TARGET_ENV" in
- dev) printf 'default' ;;
- main) printf 'main-%s' "$today" ;;
+ dev) printf 'default' ;;
+ main) printf 'main-%s' "$today" ;;
release) printf 'release-%s' "$today" ;;
esac
}
@@ -152,10 +257,12 @@ key_id() {
default_base_url() {
case "$TARGET_ENV" in
dev|main) printf 'http://127.0.0.1:8888' ;;
- release) printf '' ;;
+ release) printf '' ;;
esac
}
+# ── .env 读写 ─────────────────────────────────
+
read_env_value() {
local key="$1"
[[ -f "$ENV_FILE" ]] || return 0
@@ -173,17 +280,12 @@ read_env_value() {
}
normalize_private_key_to_stdout() {
- local escaped_key="$1"
- printf '%b' "$escaped_key"
+ printf '%b' "$1"
}
private_key_is_valid() {
- local escaped_key="$1"
- [[ -n "$escaped_key" ]] || return 1
- if ! normalize_private_key_to_stdout "$escaped_key" | openssl rsa -check -noout >/dev/null 2>&1; then
- return 1
- fi
- return 0
+ [[ -n "$1" ]] || return 1
+ normalize_private_key_to_stdout "$1" | openssl rsa -check -noout >/dev/null 2>&1
}
generate_escaped_private_key() {
@@ -195,64 +297,76 @@ generate_jwt_secret() {
openssl rand -hex 32
}
+# ── 地址解析 ─────────────────────────────────
+
resolve_base_url() {
- if [[ -z "$BASE_URL" ]]; then
- BASE_URL="$(default_base_url)"
- fi
+ [[ -z "$BASE_URL" ]] && BASE_URL="$(default_base_url)"
if [[ "$TARGET_ENV" == "release" && -z "$BASE_URL" && "$ASSUME_YES" -eq 0 ]]; then
- read -r -p "请输入 release 健康检查域名,例如 https://ctms.example.com: " BASE_URL
+ printf ' %s▸%s 请输入 release 健康检查域名(例如 https://ctms.example.com): ' \
+ "${C_YELLOW}${C_BOLD}" "${C_RESET}"
+ read -r BASE_URL
fi
- if [[ -z "$BASE_URL" ]]; then
- fail "release 环境必须通过 --base-url 或交互输入提供 HTTPS 地址"
- fi
+ [[ -n "$BASE_URL" ]] || fail "release 环境必须通过 --base-url 或交互输入提供 HTTPS 地址"
if [[ "$TARGET_ENV" == "release" && "$BASE_URL" != https://* ]]; then
- fail "release 的 --base-url 必须使用 HTTPS"
+ fail "release 环境的访问地址必须使用 HTTPS"
fi
}
+# ── 安装确认 ─────────────────────────────────
+
confirm_install() {
local project_name="$1"
local runtime_env="$2"
step "确认安装参数"
- log "目标环境: $TARGET_ENV"
- log "运行 ENV: $runtime_env"
- log "Compose 项目名: $project_name"
- log "健康检查地址: $BASE_URL"
- log "已有 .env: $([[ -f "$ENV_FILE" ]] && printf '会先备份再重写' || printf '不存在,将创建')"
- log "已有 pg_data: $([[ -d "$ROOT_DIR/pg_data" ]] && printf '保留' || printf '不存在')"
- log "构建镜像: $([[ "$SKIP_BUILD" -eq 1 ]] && printf '跳过' || printf '执行')"
- log "数据库迁移: $([[ "$SKIP_MIGRATE" -eq 1 ]] && printf '跳过' || printf '执行')"
+
+ local env_status build_status migrate_status
+ env_status="$([[ -f "$ENV_FILE" ]] && printf '已存在(将备份后重写)' || printf '不存在(将新建)')"
+ build_status="$([[ "$SKIP_BUILD" -eq 1 ]] && printf '跳过' || printf '执行')"
+ migrate_status="$([[ "$SKIP_MIGRATE" -eq 1 ]] && printf '跳过' || printf '执行')"
+
+ printf '\n'
+ printf ' %s%-18s%s %s\n' "${C_BOLD}" "目标环境" "${C_RESET}" "$TARGET_ENV"
+ printf ' %s%-18s%s %s\n' "${C_BOLD}" "运行模式" "${C_RESET}" "$runtime_env"
+ printf ' %s%-18s%s %s\n' "${C_BOLD}" "Compose 项目" "${C_RESET}" "$project_name"
+ printf ' %s%-18s%s %s\n' "${C_BOLD}" "健康检查地址" "${C_RESET}" "$BASE_URL"
+ printf ' %s%-18s%s %s\n' "${C_BOLD}" ".env 文件" "${C_RESET}" "$env_status"
+ printf ' %s%-18s%s %s\n' "${C_BOLD}" "pg_data 目录" "${C_RESET}" "$([[ -d "$ROOT_DIR/pg_data" ]] && printf '已存在(保留)' || printf '不存在')"
+ printf ' %s%-18s%s %s\n' "${C_BOLD}" "镜像构建" "${C_RESET}" "$build_status"
+ printf ' %s%-18s%s %s\n' "${C_BOLD}" "数据库迁移" "${C_RESET}" "$migrate_status"
+ printf '\n'
if [[ "$ASSUME_YES" -eq 1 ]]; then
+ log "已启用 --yes,跳过确认"
return
fi
+ printf ' %s▸%s 确认以上参数并继续安装?输入 %syes%s 继续: ' \
+ "${C_YELLOW}${C_BOLD}" "${C_RESET}" "${C_BOLD}" "${C_RESET}"
local answer
- read -r -p "确认继续安装?输入 yes 继续: " answer
- [[ "$answer" == "yes" ]] || fail "用户取消"
+ read -r answer
+ [[ "$answer" == "yes" ]] || fail "用户取消安装"
}
+# ── .env 写入 ─────────────────────────────────
+
backup_env_file() {
if [[ -f "$ENV_FILE" ]]; then
local backup_file
backup_file="$ENV_FILE.bak.$(date +%Y%m%d%H%M%S)"
cp "$ENV_FILE" "$backup_file"
- log "已备份现有 .env: $backup_file"
+ ok "已备份现有 .env → $(basename "$backup_file")"
fi
}
write_env_file() {
- local project_name="$1"
- local runtime_env="$2"
- local login_key_id="$3"
- local jwt_secret="$4"
- local rsa_private_key="$5"
+ local project_name="$1" runtime_env="$2" login_key_id="$3"
+ local jwt_secret="$4" rsa_private_key="$5"
local temp_file
-
temp_file="$(mktemp "$ROOT_DIR/.env.tmp.XXXXXX")"
+
if [[ -f "$ENV_FILE" ]]; then
awk '
BEGIN {
@@ -265,33 +379,29 @@ write_env_file() {
{
drop = 0
for (i = 1; i <= 5; i++) {
- if (index($0, prefixes[i]) == 1) {
- drop = 1
- break
- }
+ if (index($0, prefixes[i]) == 1) { drop = 1; break }
}
if (!drop) print
}
' "$ENV_FILE" > "$temp_file"
fi
+
{
printf 'COMPOSE_PROJECT_NAME=%s\n' "$project_name"
- printf 'ENV=%s\n' "$runtime_env"
- printf 'JWT_SECRET_KEY=%s\n' "$jwt_secret"
- printf 'LOGIN_RSA_KEY_ID=%s\n' "$login_key_id"
+ printf 'ENV=%s\n' "$runtime_env"
+ printf 'JWT_SECRET_KEY=%s\n' "$jwt_secret"
+ printf 'LOGIN_RSA_KEY_ID=%s\n' "$login_key_id"
printf 'LOGIN_RSA_PRIVATE_KEY=%s\n' "$rsa_private_key"
} >> "$temp_file"
+
mv "$temp_file" "$ENV_FILE"
- log "已写入 .env"
+ ok ".env 写入完成"
}
prepare_env_file() {
- step "准备 .env"
- local project_name="$1"
- local runtime_env="$2"
- local login_key_id="$3"
- local jwt_secret=""
- local rsa_private_key=""
+ step "准备环境配置文件"
+ local project_name="$1" runtime_env="$2" login_key_id="$3"
+ local jwt_secret="" rsa_private_key=""
if [[ "$TARGET_ENV" == "dev" ]]; then
jwt_secret="dev-secret"
@@ -300,19 +410,20 @@ prepare_env_file() {
jwt_secret="$(read_env_value JWT_SECRET_KEY || true)"
if [[ -z "$jwt_secret" || "$jwt_secret" == "dev-secret" ]]; then
jwt_secret="$(generate_jwt_secret)"
- log "已生成新的 JWT_SECRET_KEY"
+ ok "已生成新的 JWT_SECRET_KEY"
else
- log "保留已有 JWT_SECRET_KEY"
+ ok "保留现有 JWT_SECRET_KEY"
fi
rsa_private_key="$(read_env_value LOGIN_RSA_PRIVATE_KEY || true)"
if [[ -z "$rsa_private_key" ]]; then
+ log "正在生成 RSA 2048 密钥对,请稍候…"
rsa_private_key="$(generate_escaped_private_key)"
- log "已生成新的 LOGIN_RSA_PRIVATE_KEY"
+ ok "已生成新的 LOGIN_RSA_PRIVATE_KEY"
elif private_key_is_valid "$rsa_private_key"; then
- log "保留已有有效 LOGIN_RSA_PRIVATE_KEY"
+ ok "保留现有有效 LOGIN_RSA_PRIVATE_KEY"
else
- fail "现有 LOGIN_RSA_PRIVATE_KEY 不是有效 PEM 私钥。请修复 .env 后重试,脚本不会自动覆盖已有无效密钥。"
+ fail "现有 LOGIN_RSA_PRIVATE_KEY 不是合法的 PEM 私钥,请修复 .env 后重试(脚本不会自动覆盖已有密钥)"
fi
fi
@@ -320,162 +431,159 @@ prepare_env_file() {
write_env_file "$project_name" "$runtime_env" "$login_key_id" "$jwt_secret" "$rsa_private_key"
}
+# ── Docker 流程 ───────────────────────────────
+
run_compose_config() {
- step "检查 Docker Compose 配置"
- run docker compose config >/dev/null
- log "Docker Compose 配置检查通过"
+ step "校验 Docker Compose 配置"
+ run_quiet "Docker Compose 配置语法校验" -- docker compose config
}
run_backend_init() {
- if [[ "$TARGET_ENV" == "dev" ]]; then
- return
- fi
- step "初始化生产数据库和管理员"
- run docker compose run --rm backend-init
+ [[ "$TARGET_ENV" != "dev" ]] || return 0
+ step "初始化生产数据库与管理员账号"
+ run_quiet "运行 backend-init 容器" -- docker compose run --rm backend-init
}
run_build_and_start() {
if [[ "$SKIP_BUILD" -eq 1 ]]; then
step "启动服务(跳过镜像重新构建)"
- run docker compose up -d
- return
+ run_quiet "启动容器" -- docker compose up -d
+ else
+ step "构建镜像并启动服务"
+ run_quiet "构建镜像并启动容器(首次较慢)" -- docker compose up -d --build
fi
- step "构建镜像并启动服务"
- run docker compose up -d --build
}
run_migrations() {
if [[ "$SKIP_MIGRATE" -eq 1 ]]; then
- step "跳过数据库迁移"
+ log "已跳过数据库迁移(--skip-migrate)"
return
fi
step "执行数据库迁移"
- run docker compose run --rm backend python -m alembic upgrade head
+ run_quiet "Alembic upgrade head" -- docker compose run --rm backend python -m alembic upgrade head
}
+# ── 健康检查 ─────────────────────────────────
+
check_container_status() {
- step "检查容器状态"
- run docker compose ps
- local ps_output
- ps_output="$(docker compose ps --services --filter status=running 2>/dev/null || true)"
- printf '%s\n' "$ps_output" | grep -qx 'db' || fail "db 容器未运行"
- printf '%s\n' "$ps_output" | grep -qx 'backend' || fail "backend 容器未运行"
- printf '%s\n' "$ps_output" | grep -qx 'nginx' || fail "nginx 容器未运行"
- log "容器状态检查通过"
+ step "检查容器运行状态"
+ local running
+ running="$(docker compose ps --services --filter status=running 2>/dev/null || true)"
+ for svc in db backend nginx; do
+ if printf '%s\n' "$running" | grep -qx "$svc"; then
+ printf ' %s✔%s %-8s 运行中\n' "${C_GREEN}${C_BOLD}" "${C_RESET}" "$svc"
+ else
+ printf ' %s✖%s %-8s %s未运行%s\n' "${C_RED}${C_BOLD}" "${C_RESET}" "$svc" "${C_RED}" "${C_RESET}"
+ fail "$svc 容器未正常运行,请执行 'docker compose ps' 排查"
+ fi
+ done
}
check_backend_environment() {
- step "检查后端环境变量"
- local expected_env="$1"
- local expected_key_id="$2"
- local expect_rsa="$3"
+ step "校验后端运行时环境变量"
+ local expected_env="$1" expected_key_id="$2" expect_rsa="$3"
local check_code
-
check_code=$(cat <<'PY'
+import os, sys
from app.core.config import settings
from app.core.login_crypto import _normalize_pem
-expected_env = "__EXPECTED_ENV__"
-expected_key_id = "__EXPECTED_KEY_ID__"
-expect_rsa = "__EXPECT_RSA__" == "1"
-
-print("ENV=" + settings.ENV)
-print("JWT_SECRET_IS_DEV=" + str(settings.JWT_SECRET_KEY == "dev-secret"))
-print("LOGIN_RSA_PRIVATE_KEY_SET=" + str(bool(settings.LOGIN_RSA_PRIVATE_KEY)))
-print("LOGIN_RSA_KEY_ID=" + settings.LOGIN_RSA_KEY_ID)
+expected_env = os.environ["EXPECTED_ENV"]
+expected_key_id = os.environ["EXPECTED_KEY_ID"]
+expect_rsa = os.environ["EXPECT_RSA"] == "1"
if settings.ENV != expected_env:
- raise SystemExit(f"ENV mismatch: {settings.ENV} != {expected_env}")
+ sys.exit(f"ENV 不匹配: {settings.ENV} != {expected_env}")
if settings.LOGIN_RSA_KEY_ID != expected_key_id:
- raise SystemExit("LOGIN_RSA_KEY_ID mismatch")
+ sys.exit("LOGIN_RSA_KEY_ID 不匹配")
if expected_env == "production" and settings.JWT_SECRET_KEY == "dev-secret":
- raise SystemExit("production JWT_SECRET_KEY must not be dev-secret")
+ sys.exit("生产环境不允许使用默认 JWT_SECRET_KEY")
if expect_rsa:
if not settings.LOGIN_RSA_PRIVATE_KEY:
- raise SystemExit("LOGIN_RSA_PRIVATE_KEY is required")
+ sys.exit("LOGIN_RSA_PRIVATE_KEY 未配置")
from cryptography.hazmat.primitives import serialization
-
serialization.load_pem_private_key(
_normalize_pem(settings.LOGIN_RSA_PRIVATE_KEY).encode("utf-8"),
password=None,
)
PY
)
- check_code="${check_code//__EXPECTED_ENV__/$expected_env}"
- check_code="${check_code//__EXPECTED_KEY_ID__/$expected_key_id}"
- check_code="${check_code//__EXPECT_RSA__/$expect_rsa}"
- run docker compose exec -T backend python -c "$check_code"
- log "后端环境变量检查通过"
+ run_quiet "执行后端配置自检" -- docker compose exec -T \
+ -e EXPECTED_ENV="$expected_env" \
+ -e EXPECTED_KEY_ID="$expected_key_id" \
+ -e EXPECT_RSA="$expect_rsa" \
+ backend python -c "$check_code"
}
check_http_endpoint() {
local path="$1"
local url="${BASE_URL%/}$path"
- log "检查接口: $url"
- local attempt=1
- local max_attempts=10
- local delay_seconds=2
+ log "探测接口: $url"
+ local attempt=1 max_attempts=10 delay=2
while [[ "$attempt" -le "$max_attempts" ]]; do
if curl -fsS "$url" >/dev/null; then
+ ok "接口就绪: $path"
return 0
fi
if [[ "$attempt" -lt "$max_attempts" ]]; then
- log "接口未就绪,${delay_seconds}s 后重试 (${attempt}/${max_attempts})"
- sleep "$delay_seconds"
+ log "接口尚未就绪,${delay}s 后重试(${attempt}/${max_attempts})…"
+ sleep "$delay"
fi
- attempt=$((attempt + 1))
+ attempt=$(( attempt + 1 ))
done
if [[ "$path" == "/api/v1/auth/login-key" ]]; then
- printf '[CTMS] 登录公钥接口失败。请检查 .env 中 LOGIN_RSA_PRIVATE_KEY 是否是完整 PEM 单行转义文本。\n' >&2
+ warn "登录公钥接口无响应,请检查 .env 中 LOGIN_RSA_PRIVATE_KEY 是否为完整的单行转义 PEM 文本"
fi
- fail "接口检查失败: $url"
-}
-
-show_progress_note() {
- log "当前阶段: $1"
+ fail "接口探测失败: $url"
}
run_health_checks() {
- local runtime_env="$1"
- local login_key_id="$2"
+ local runtime_env="$1" login_key_id="$2"
local expect_rsa=0
- if [[ "$runtime_env" == "production" ]]; then
- expect_rsa=1
- fi
+ [[ "$runtime_env" == "production" ]] && expect_rsa=1
check_container_status
check_backend_environment "$runtime_env" "$login_key_id" "$expect_rsa"
- step "检查 HTTP 接口"
+ step "探测 HTTP 接口可用性"
check_http_endpoint "/health"
check_http_endpoint "/api/v1/auth/login-key"
- log "HTTP 接口检查通过"
+ ok "HTTP 接口全部就绪"
}
+# ── 完成提示 ─────────────────────────────────
+
+show_success() {
+ printf '\n'
+ printf '%s┌──────────────────────────────────────────────┐%s\n' "${C_GREEN}${C_BOLD}" "${C_RESET}"
+ printf '%s│%s ✔ CTMS 安装成功 %s│%s\n' "${C_GREEN}${C_BOLD}" "${C_GREEN}" "${C_GREEN}${C_BOLD}" "${C_RESET}"
+ printf '%s└──────────────────────────────────────────────┘%s\n' "${C_GREEN}${C_BOLD}" "${C_RESET}"
+ printf '\n'
+ printf ' %s访问地址%s %s%s%s\n' "${C_BOLD}" "${C_RESET}" "${C_CYAN}" "$BASE_URL" "${C_RESET}"
+ printf ' %s环境模式%s %s\n' "${C_BOLD}" "${C_RESET}" "$TARGET_ENV"
+ printf '\n'
+}
+
+# ── 入口 ─────────────────────────────────────
+
main() {
+ _banner
parse_args "$@"
cd "$ROOT_DIR"
- local project_name
- local runtime_env
- local login_key_id
+ local project_name runtime_env login_key_id
project_name="$(compose_project_name)"
runtime_env="$(app_env)"
login_key_id="$(key_id)"
resolve_base_url
check_dependencies
- show_progress_note "准备写入环境配置"
confirm_install "$project_name" "$runtime_env"
prepare_env_file "$project_name" "$runtime_env" "$login_key_id"
- show_progress_note "校验并启动 Docker 流程"
run_compose_config
run_backend_init
run_build_and_start
run_migrations
- show_progress_note "执行健康检查"
run_health_checks "$runtime_env" "$login_key_id"
-
- step "安装完成"
- log "访问地址: $BASE_URL"
+ show_success
}
main "$@"