Features - User Management (Phase 4.1): - Database: Add user_modules table for fine-grained module permissions - Database: Add 4 user permissions (view/create/edit/delete) to role_permissions - Backend: UserService (780 lines) - CRUD with tenant isolation - Backend: UserController + UserRoutes (648 lines) - 13 API endpoints - Backend: Batch import users from Excel - Frontend: UserListPage (412 lines) - list/filter/search/pagination - Frontend: UserFormPage (341 lines) - create/edit with module config - Frontend: UserDetailPage (393 lines) - details/tenant/module management - Frontend: 3 modal components (592 lines) - import/assign/configure - API: GET/POST/PUT/DELETE /api/admin/users/* endpoints Architecture Upgrade - Module Permission System: - Backend: Add getUserModules() method in auth.service - Backend: Login API returns modules array in user object - Frontend: AuthContext adds hasModule() method - Frontend: Navigation filters modules based on user.modules - Frontend: RouteGuard checks requiredModule instead of requiredVersion - Frontend: Remove deprecated version-based permission system - UX: Only show accessible modules in navigation (clean UI) - UX: Smart redirect after login (avoid 403 for regular users) Fixes: - Fix UTF-8 encoding corruption in ~100 docs files - Fix pageSize type conversion in userService (String to Number) - Fix authUser undefined error in TopNavigation - Fix login redirect logic with role-based access check - Update Git commit guidelines v1.2 with UTF-8 safety rules Database Changes: - CREATE TABLE user_modules (user_id, tenant_id, module_code, is_enabled) - ADD UNIQUE CONSTRAINT (user_id, tenant_id, module_code) - INSERT 4 permissions + role assignments - UPDATE PUBLIC tenant with 8 module subscriptions Technical: - Backend: 5 new files (~2400 lines) - Frontend: 10 new files (~2500 lines) - Docs: 1 development record + 2 status updates + 1 guideline update - Total: ~4900 lines of code Status: User management 100% complete, module permission system operational
436 lines
13 KiB
Markdown
436 lines
13 KiB
Markdown
# 数据库迁移状态说明
|
||
|
||
> **文档版本:** v1.0
|
||
> **创建日期:** 2025-11-23
|
||
> **维护者:** ASL开发团队
|
||
> **文档目的:** 记录ASL模块数据库迁移状态,为未来开发人员提供清晰的上下文
|
||
|
||
---
|
||
|
||
## 📋 当前数据库状态总览
|
||
|
||
### ✅ ASL模块(asl_schema)- 完全正确
|
||
|
||
| 表名 | 状态 | 用途 | 记录数 |
|
||
|-----|------|------|--------|
|
||
| `literatures` | ✅ 已更新 | 文献基础信息(含全文字段) | - |
|
||
| `screening_projects` | ✅ 正常 | 筛选项目 | - |
|
||
| `screening_tasks` | ✅ 正常 | 标题摘要初筛任务 | - |
|
||
| `screening_results` | ✅ 正常 | 标题摘要初筛结果 | - |
|
||
| `fulltext_screening_tasks` | ✅ 新建 | 全文复筛任务 | 0 |
|
||
| `fulltext_screening_results` | ✅ 新建 | 全文复筛结果 | 0 |
|
||
|
||
**核心结论**:
|
||
- ✅ ASL模块所有数据完全位于 `asl_schema`
|
||
- ✅ 没有数据泄漏到 `public` schema
|
||
- ✅ Schema隔离策略执行正确
|
||
- ✅ 代码访问路径正确(`prisma.aslLiterature`, `prisma.aslScreeningProject` 等)
|
||
|
||
---
|
||
|
||
## 🔴 Public Schema历史遗留问题(与ASL无关)
|
||
|
||
### 问题描述
|
||
|
||
在项目早期开发中,部分模块的表被错误地创建在 `public` schema 中,违反了Schema隔离策略:
|
||
|
||
| 错误表名 | 应在Schema | 当前状态 |
|
||
|---------|-----------|---------|
|
||
| `public.users` | `platform_schema` | ⚠️ 重复存在 |
|
||
| `public.projects` | `aia_schema` | ⚠️ 重复存在 |
|
||
| `public.conversations` | `aia_schema` | ⚠️ 重复存在 |
|
||
| `public.messages` | `aia_schema` | ⚠️ 重复存在 |
|
||
| `public.knowledge_bases` | `pkb_schema` | ⚠️ 重复存在 |
|
||
| `public.documents` | `pkb_schema` | ⚠️ 重复存在 |
|
||
| `public.batch_tasks` | `pkb_schema` | ⚠️ 重复存在 |
|
||
| `public.batch_results` | `pkb_schema` | ⚠️ 重复存在 |
|
||
|
||
**数据对比(2025-11-23快照)**:
|
||
|
||
```
|
||
platform_schema.users: 3条记录
|
||
public.users: 2条记录
|
||
|
||
aia_schema.projects: 2条记录
|
||
public.projects: 2条记录
|
||
|
||
pkb_schema.knowledge_bases: 2条记录
|
||
public.knowledge_bases: 2条记录
|
||
```
|
||
|
||
**影响范围**:
|
||
- 🟢 **不影响ASL模块**(ASL完全隔离在asl_schema)
|
||
- ⚠️ 影响AIA模块(AI助手)
|
||
- ⚠️ 影响PKB模块(知识库)
|
||
- ⚠️ 影响Platform模块(用户系统)
|
||
|
||
**责任归属**:
|
||
- 🔵 ASL团队:无责任,数据管理完全正确
|
||
- 🟡 其他模块团队:需自行清理public schema数据
|
||
|
||
---
|
||
|
||
## 🛠️ 2025-11-23迁移操作记录
|
||
|
||
### 迁移目标
|
||
|
||
为全文复筛功能(Day 4开发)添加数据库支持:
|
||
1. 修改 `literatures` 表(添加全文相关字段)
|
||
2. 创建 `fulltext_screening_tasks` 表
|
||
3. 创建 `fulltext_screening_results` 表
|
||
|
||
### 迁移策略选择
|
||
|
||
**❌ 方案A:Prisma Migrate(被拒绝)**
|
||
|
||
```bash
|
||
npx prisma migrate dev --name add_fulltext_screening
|
||
```
|
||
|
||
**拒绝原因**:
|
||
- Prisma会尝试删除 `public` schema中的重复表
|
||
- 可能影响其他模块的数据
|
||
- 违反"管好自己"的原则
|
||
|
||
**✅ 方案B:手动SQL脚本(已采用)**
|
||
|
||
```bash
|
||
# 创建手动迁移脚本
|
||
backend/prisma/migrations/manual_fulltext_screening.sql
|
||
|
||
# 执行迁移(仅操作asl_schema)
|
||
Get-Content manual_fulltext_screening.sql | docker exec -i ai-clinical-postgres psql ...
|
||
```
|
||
|
||
**优势**:
|
||
- ✅ 只操作 `asl_schema`,不动其他schema
|
||
- ✅ 不删除任何 `public` 数据
|
||
- ✅ 安全、可控、可审计
|
||
- ✅ 符合"管好自己"原则
|
||
|
||
### 迁移内容详情
|
||
|
||
#### 1. 修改 `literatures` 表
|
||
|
||
新增字段(13个):
|
||
|
||
**文献生命周期**:
|
||
- `stage TEXT DEFAULT 'imported'` - 阶段标记(imported → title_screened → fulltext_pending → fulltext_screened)
|
||
|
||
**PDF管理**:
|
||
- `has_pdf BOOLEAN DEFAULT false` - 是否有PDF
|
||
- `pdf_storage_type TEXT` - 存储类型(oss/dify/local)
|
||
- `pdf_storage_ref TEXT` - 存储引用(key或ID)
|
||
- `pdf_status TEXT DEFAULT 'pending'` - 状态(pending/extracting/completed/failed)
|
||
- `pdf_uploaded_at TIMESTAMP(3)` - 上传时间
|
||
|
||
**全文管理(云原生)**:
|
||
- `full_text_storage_type TEXT` - 存储类型(oss/dify)
|
||
- `full_text_storage_ref TEXT` - 存储引用
|
||
- `full_text_url TEXT` - 访问URL
|
||
|
||
**全文元数据**:
|
||
- `full_text_format TEXT` - 格式(markdown/plaintext)
|
||
- `full_text_source TEXT` - 提取方式(nougat/pymupdf)
|
||
- `full_text_token_count INTEGER` - Token数量
|
||
- `full_text_extracted_at TIMESTAMP(3)` - 提取时间
|
||
|
||
**新增索引**:
|
||
- `idx_literatures_stage`
|
||
- `idx_literatures_has_pdf`
|
||
- `idx_literatures_pdf_status`
|
||
|
||
#### 2. 创建 `fulltext_screening_tasks` 表
|
||
|
||
任务管理表,字段包括:
|
||
- 基础信息:`id`, `project_id`
|
||
- 模型配置:`model_a`, `model_b`, `prompt_version`
|
||
- 进度跟踪:`total_count`, `processed_count`, `success_count`, `failed_count`, `degraded_count`
|
||
- 成本统计:`total_tokens`, `total_cost`
|
||
- 状态管理:`status`, `started_at`, `completed_at`, `estimated_end_at`
|
||
- 错误记录:`error_message`, `error_stack`
|
||
|
||
**索引**:
|
||
- `idx_fulltext_tasks_project_id`
|
||
- `idx_fulltext_tasks_status`
|
||
- `idx_fulltext_tasks_created_at`
|
||
|
||
**外键约束**:
|
||
- `project_id` → `screening_projects(id)` ON DELETE CASCADE
|
||
|
||
#### 3. 创建 `fulltext_screening_results` 表
|
||
|
||
结果存储表(12字段模板),字段包括:
|
||
- 关联信息:`task_id`, `project_id`, `literature_id`
|
||
- Model A结果:`model_a_name`, `model_a_fields` (JSONB), `model_a_tokens`, `model_a_cost` 等
|
||
- Model B结果:`model_b_name`, `model_b_fields` (JSONB), `model_b_tokens`, `model_b_cost` 等
|
||
- 验证结果:`medical_logic_issues` (JSONB), `evidence_chain_issues` (JSONB)
|
||
- 冲突检测:`is_conflict`, `conflict_severity`, `conflict_fields`, `review_priority`
|
||
- 人工复核:`final_decision`, `final_decision_by`, `exclusion_reason`, `review_notes`
|
||
- 处理状态:`processing_status`, `is_degraded`, `degraded_model`
|
||
- 可追溯性:`raw_output_a` (JSONB), `raw_output_b` (JSONB), `prompt_version`
|
||
|
||
**索引**:
|
||
- `idx_fulltext_results_task_id`
|
||
- `idx_fulltext_results_project_id`
|
||
- `idx_fulltext_results_literature_id`
|
||
- `idx_fulltext_results_is_conflict`
|
||
- `idx_fulltext_results_final_decision`
|
||
- `idx_fulltext_results_review_priority`
|
||
|
||
**唯一约束**:
|
||
- `unique_project_literature_fulltext (project_id, literature_id)`
|
||
|
||
**外键约束**:
|
||
- `task_id` → `fulltext_screening_tasks(id)` ON DELETE CASCADE
|
||
- `project_id` → `screening_projects(id)` ON DELETE CASCADE
|
||
- `literature_id` → `literatures(id)` ON DELETE CASCADE
|
||
|
||
### 迁移结果验证
|
||
|
||
```sql
|
||
-- 验证表创建
|
||
\dt asl_schema.*
|
||
|
||
-- 结果:6个表
|
||
-- ✅ literatures (已更新)
|
||
-- ✅ screening_projects
|
||
-- ✅ screening_tasks
|
||
-- ✅ screening_results
|
||
-- ✅ fulltext_screening_tasks (新建)
|
||
-- ✅ fulltext_screening_results (新建)
|
||
|
||
-- 验证新字段
|
||
\d asl_schema.literatures
|
||
|
||
-- 结果:
|
||
-- ✅ stage
|
||
-- ✅ has_pdf
|
||
-- ✅ full_text_storage_type
|
||
-- ✅ full_text_storage_ref
|
||
-- ✅ full_text_url
|
||
-- ✅ full_text_format
|
||
-- ... 等13个新字段
|
||
```
|
||
|
||
**Prisma Client生成**:
|
||
|
||
```bash
|
||
cd backend
|
||
npx prisma generate
|
||
|
||
# 结果:✅ 生成成功
|
||
# 代码可访问:
|
||
# - prisma.aslLiterature
|
||
# - prisma.aslFulltextScreeningTask
|
||
# - prisma.aslFulltextScreeningResult
|
||
```
|
||
|
||
---
|
||
|
||
## 📐 Schema隔离策略执行情况
|
||
|
||
### 设计原则(来自系统架构文档)
|
||
|
||
```
|
||
各模块数据逻辑隔离:
|
||
├── admin_schema (系统管理)
|
||
├── platform_schema (用户系统)
|
||
├── aia_schema (AI助手)
|
||
├── asl_schema (AI智能文献) ✅ 执行正确
|
||
├── pkb_schema (知识库)
|
||
├── rvw_schema (审阅协作)
|
||
├── st_schema (统计分析)
|
||
├── dc_schema (数据采集)
|
||
├── ssa_schema (样本量分析)
|
||
└── common_schema (公共数据)
|
||
```
|
||
|
||
### ASL模块执行情况 ✅
|
||
|
||
| 检查项 | 状态 | 说明 |
|
||
|-------|------|------|
|
||
| Schema命名 | ✅ 正确 | `asl_schema` |
|
||
| 所有表都在正确Schema | ✅ 正确 | 6个表全部在 `asl_schema` |
|
||
| 没有表在public | ✅ 正确 | 无泄漏 |
|
||
| Prisma Model映射正确 | ✅ 正确 | `@@schema("asl_schema")` |
|
||
| 代码访问路径正确 | ✅ 正确 | `prisma.aslXxx` |
|
||
| 外键约束内部化 | ✅ 正确 | 所有FK指向同schema表 |
|
||
|
||
**代码示例**(正确访问方式):
|
||
|
||
```typescript
|
||
// ✅ 正确:通过Prisma Client访问asl_schema
|
||
const project = await prisma.aslScreeningProject.findUnique({
|
||
where: { id: projectId },
|
||
});
|
||
|
||
const literatures = await prisma.aslLiterature.findMany({
|
||
where: { projectId },
|
||
});
|
||
|
||
const task = await prisma.aslFulltextScreeningTask.create({
|
||
data: { ... },
|
||
});
|
||
|
||
// ❌ 错误:直接SQL访问public(不会发生,因为表不在public)
|
||
await prisma.$queryRaw`SELECT * FROM public.literatures`;
|
||
```
|
||
|
||
---
|
||
|
||
## 🔮 未来迁移策略
|
||
|
||
### 对于ASL模块
|
||
|
||
**推荐策略**:继续使用手动SQL脚本
|
||
|
||
**原因**:
|
||
1. ✅ Public schema的历史遗留问题短期无法解决
|
||
2. ✅ 手动脚本更安全、可控
|
||
3. ✅ 避免意外影响其他模块
|
||
4. ✅ 便于代码审查和审计
|
||
|
||
**操作流程**:
|
||
|
||
```bash
|
||
# 1. 修改 Prisma Schema
|
||
# backend/prisma/schema.prisma
|
||
|
||
# 2. 编写手动SQL脚本
|
||
# backend/prisma/migrations/manual_xxx.sql
|
||
|
||
# 3. 执行脚本(只操作asl_schema)
|
||
Get-Content manual_xxx.sql | docker exec -i ai-clinical-postgres psql ...
|
||
|
||
# 4. 验证结果
|
||
docker exec ai-clinical-postgres psql ... -c "\dt asl_schema.*"
|
||
|
||
# 5. 生成Prisma Client
|
||
npx prisma generate
|
||
|
||
# 6. 提交Git
|
||
git add .
|
||
git commit -m "feat(asl): add xxx tables for xxx feature"
|
||
```
|
||
|
||
**SQL脚本模板**:
|
||
|
||
```sql
|
||
-- 只操作asl_schema,不影响其他schema
|
||
ALTER TABLE asl_schema.xxx ADD COLUMN IF NOT EXISTS ...;
|
||
CREATE TABLE IF NOT EXISTS asl_schema.xxx (...);
|
||
CREATE INDEX IF NOT EXISTS idx_xxx ON asl_schema.xxx(...);
|
||
```
|
||
|
||
### 对于其他模块
|
||
|
||
**问题所有者**:各模块开发团队
|
||
|
||
**建议操作**(由各模块团队自行决定):
|
||
1. 检查 `public` schema中是否有本模块的表
|
||
2. 对比数据差异(`public` vs 正确schema)
|
||
3. 决策是否需要数据迁移或清理
|
||
4. 执行清理操作(风险自负)
|
||
|
||
**ASL团队立场**:
|
||
- 🔵 不主动清理其他模块的public表
|
||
- 🔵 不对其他模块数据安全负责
|
||
- 🔵 专注于asl_schema的质量和稳定性
|
||
|
||
---
|
||
|
||
## 📊 数据完整性验证
|
||
|
||
### ASL模块数据关系图
|
||
|
||
```
|
||
asl_schema.screening_projects (项目)
|
||
↓ 1:N
|
||
asl_schema.literatures (文献)
|
||
↓ 1:1 ↓ 1:1
|
||
asl_schema.screening_results asl_schema.fulltext_screening_results
|
||
(标题摘要初筛结果) (全文复筛结果)
|
||
↑ N:1 ↑ N:1
|
||
asl_schema.screening_tasks asl_schema.fulltext_screening_tasks
|
||
(标题摘要初筛任务) (全文复筛任务)
|
||
```
|
||
|
||
### 外键约束验证
|
||
|
||
```sql
|
||
-- 验证所有外键都指向asl_schema内部
|
||
SELECT
|
||
tc.constraint_name,
|
||
tc.table_name,
|
||
kcu.column_name,
|
||
ccu.table_name AS foreign_table_name,
|
||
ccu.column_name AS foreign_column_name
|
||
FROM information_schema.table_constraints AS tc
|
||
JOIN information_schema.key_column_usage AS kcu
|
||
ON tc.constraint_name = kcu.constraint_name
|
||
JOIN information_schema.constraint_column_usage AS ccu
|
||
ON ccu.constraint_name = tc.constraint_name
|
||
WHERE tc.constraint_type = 'FOREIGN KEY'
|
||
AND tc.table_schema = 'asl_schema'
|
||
ORDER BY tc.table_name;
|
||
|
||
-- 预期结果:
|
||
-- ✅ 所有FK的 foreign_table_name 都在 asl_schema 中
|
||
-- ✅ 没有跨schema引用
|
||
```
|
||
|
||
---
|
||
|
||
## 🎯 关键结论
|
||
|
||
### ✅ ASL模块:完全健康
|
||
|
||
1. **Schema隔离**:100%正确,所有表都在 `asl_schema`
|
||
2. **数据管理**:无数据泄漏到 `public`
|
||
3. **代码规范**:所有访问路径正确
|
||
4. **迁移策略**:手动SQL脚本,安全可控
|
||
|
||
### ⚠️ 系统级问题:Public Schema污染
|
||
|
||
1. **问题性质**:历史遗留,与ASL无关
|
||
2. **影响范围**:AIA、PKB、Platform模块
|
||
3. **解决责任**:各模块团队自行处理
|
||
4. **ASL策略**:不动public,管好自己
|
||
|
||
### 📋 开发人员指南
|
||
|
||
**如果你是ASL模块开发者**:
|
||
- ✅ 继续保持当前的Schema隔离实践
|
||
- ✅ 使用手动SQL脚本进行数据库迁移
|
||
- ✅ 所有表都创建在 `asl_schema`
|
||
- ✅ 不要尝试清理 `public` schema
|
||
|
||
**如果你是其他模块开发者**:
|
||
- 🟡 检查自己模块的Schema隔离状况
|
||
- 🟡 决定是否需要清理 `public` 中的重复表
|
||
- 🟡 参考ASL的迁移策略(手动SQL)
|
||
- 🟡 不要依赖ASL团队清理public
|
||
|
||
---
|
||
|
||
## 📚 相关文档
|
||
|
||
- [系统总体设计 - 数据库架构说明](../../../../00-系统总体设计/03-数据库架构说明.md)
|
||
- [ASL模块 - 数据库设计](../../02-技术设计/01-数据库设计.md)
|
||
- [云原生开发规范](../../../../04-开发规范/08-云原生开发规范.md)
|
||
- [Day 2-3开发记录](./2025-11-22_Day2-Day3_LLM服务与验证系统开发.md)
|
||
|
||
---
|
||
|
||
**文档维护**:
|
||
- 数据库结构变更时更新
|
||
- 发现新问题时记录
|
||
- 定期审查Schema隔离状况
|
||
|
||
**最后更新**:2025-11-23
|
||
**更新人**:ASL开发团队
|
||
**下次审查**:下次数据库迁移时
|
||
|
||
|