feat(admin): Add user management and upgrade to module permission system

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
This commit is contained in:
2026-01-16 13:42:10 +08:00
parent 98d862dbd4
commit 66255368b7
560 changed files with 70424 additions and 52353 deletions

View File

@@ -2,24 +2,24 @@
**日期**: 2025-11-21
**任务**: 将Mock AI替换为真实LLM调用
**状æ€?*: âœ?完æˆ<C3A6>
**状态**: ✅ 完成
---
## 📋 背景
### ä¹å‰<EFBFBD>的状æ€?
- �已完�Prompt 设计(v1.0.0-MVP�
- �已实�`llmScreeningService.ts`(真实LLM调用�
- âœ?已完æˆ<C3A6>æµè¯•框架åŒè´¨é‡<C3A9>验è¯<C3A8>
- â<EFBFBD>?**问题**: `screeningService.ts` 中使ç”?`mockAIScreening` 生æˆ<EFBFBD>å<EFBFBD>‡æ•°æ<EFBFBD>?
### 之前的状态
- ✅ 已完成 Prompt 设计(v1.0.0-MVP
- ✅ 已实现 `llmScreeningService.ts`真实LLM调用
- ✅ 已完成测试框架和质量验证
- **问题**: `screeningService.ts` 中使用 `mockAIScreening` 生成假数据
### 用户需�
ä»?设置与å<C5BD>¯åŠ?页é<C2B5>¢ä¸Šä¼ çœŸå®žæ‡çŒ®æ•°æ<C2B0>®å<C2AE>Žï¼Œ**使用真实çš?DeepSeek å’?Qwen API 进行筛é€?*,而ä¸<C3A4>æ˜¯æ¨¡æŸæ•°æ<C2B0>®ã€?
### 用户需求
从"设置与启动"页面上传真实文献数据后,**使用真实的 DeepSeek Qwen API 进行筛选**,而不是模拟数据。
---
## âœ?完æˆ<C3A6>内容
## ✅ 完成内容
### 1. 修改 `screeningService.ts`
@@ -34,10 +34,10 @@ import { llmScreeningService } from './llmScreeningService.js';
**替换处理逻辑**:
```typescript
// â<EFBFBD>?旧代ç <C3A7>(Mockï¼?
// ❌ 旧代码(Mock
const result = await mockAIScreening(projectId, literature);
// âœ?æ°ä»£ç <C3A7>(真实LLMï¼?
// ✅ 新代码真实LLM
const screeningResult = await llmScreeningService.dualModelScreening(
literature.id,
literature.title,
@@ -87,7 +87,7 @@ const screeningResult = await llmScreeningService.dualModelScreening(
// DeepSeek结果
dsModelName: screeningResult.deepseekModel,
dsPJudgment: screeningResult.deepseek.judgment.P,
// ... 完整的字段映�
// ... 完整的字段映射
};
```
@@ -97,65 +97,65 @@ const screeningResult = await llmScreeningService.dualModelScreening(
### 用户操作流程
```
1. 访问"设置与å<C5BD>¯åŠ?页é<C2B5>¢
�
1. 访问"设置与启动"页面
2. 填写 PICOS 标准
�
3. 上传 Excel 文献列表例如199篇
�
4. 点击"开始AI初筛"
�
5. å<EFBFBD>Žç«¯è‡ªåЍ处ç<EFBFBD>†ï¼?
5. 后端自动处理:
a. 创建项目
b. 导入文献
c. å<EFBFBD>¯åЍç­é€‰ä»»åŠ?
�
c. 启动筛选任务
6. 真实LLM处理每篇约10-15秒
a. 调用 DeepSeek API
b. 调用 Qwen API
c. 对比结果,检测冲�
c. 对比结果,检测冲突
d. 保存到数据库
�
7. å‰<EFBFBD>端自动跳转åˆ?审核工作å<C593>?
�
8. 显示真实的AIç­é€‰ç»“æž?
7. 前端自动跳转到"审核工作台"
8. 显示真实的AI筛选结果
```
### 技术æµ<EFBFBD>ç¨?
### 技术流程
```
前端: TitleScreeningSettings.tsx
�POST /api/v1/asl/literatures/import
POST /api/v1/asl/literatures/import
后端: literatureController.ts
�importLiteratures()
�startScreeningTask()
importLiteratures()
startScreeningTask()
后端: screeningService.ts
�processLiteraturesInBackground()
�for each literature:
�llmScreeningService.dualModelScreening()
processLiteraturesInBackground()
for each literature:
llmScreeningService.dualModelScreening()
后端: llmScreeningService.ts
�Promise.all([
Promise.all([
screenWithModel('deepseek-chat', ...),
screenWithModel('qwen-max', ...),
])
后端: LLMFactory
�getAdapter('deepseek-v3')
�getAdapter('qwen3-72b')
getAdapter('deepseek-v3')
getAdapter('qwen3-72b')
真实API调用
�DeepSeek API
�Qwen API
DeepSeek API
Qwen API
结果保存
�AslScreeningResult �
AslScreeningResult
前端: ScreeningWorkbench.tsx
�GET /api/v1/asl/projects/:projectId/screening-results
�显示真实结果
GET /api/v1/asl/projects/:projectId/screening-results
↓ 显示真实结果
```
---
@@ -165,46 +165,46 @@ const screeningResult = await llmScreeningService.dualModelScreening(
### 单篇文献处理时间
| 步骤 | 耗时(串行) |
|-----|------------|
| DeepSeek API 调用 | 5-10�|
| Qwen API 调用 | 5-10�|
| 结果ä¿<EFBFBD>å­˜ | 0.1ç§?|
| **总计** | **10-20�* |
| DeepSeek API 调用 | 5-10|
| Qwen API 调用 | 5-10|
| 结果保存 | 0.1|
| **总计** | **10-20秒** |
### 批é‡<EFBFBD>处ç<EFBFBD>†æ—¶é—´ï¼?99篇)
### 批量处理时间199篇
| 模式 | 耗时 | 说明 |
|-----|------|-----|
| **串行处理** | 33-66分钟 | 当前实现避免API限流|
| å¹¶å<EFBFBD>处ç<EFBFBD>†ï¼?个) | 11-22åˆ†éŸ | å<>¯é€‰ä¼˜åŒï¼ˆéœ€æµè¯•ï¼?|
| å¹¶å<EFBFBD>处ç<EFBFBD>†ï¼?0个) | 3-7åˆ†éŸ | 风险:å<C5A1>¯èƒ½è§¦å<C2A6>APIé™<C3A9>é¢<C3A9> |
| 并发处理3个 | 11-22分钟 | 可选优化(需测试) |
| 并发处理10个 | 3-7分钟 | 风险可能触发API限额 |
**当前策略**: 串行处理(稳定优先)
---
## 🎯 与Mockæ•°æ<C2B0>®çš„对æ¯?
## 🎯 与Mock数据的对比
### Mock æ•°æ<EFBFBD>®ï¼ˆæ—§ï¼?
### Mock 数据(旧)
```javascript
// â<EFBFBD><>‡æ•°æ<C2B0>?
// ❌ 假数据
dsPEvidence: "模拟证据: 研究人群与PICO中的P标准匹配"
dsReason: "åŸºäºŽæ ‡é¢˜åŒæ˜è¦<EFBFBD>分æž<EFBFBD>,该æ‡çŒ®ç¬¦å<EFBFBD>ˆçº³å…¥æ ‡å‡†ã€?
dsConclusion: randomConclusion() // éš<EFBFBD>机ï¼?
dsReason: "基于标题和摘要分析,该文献符合纳入标准。"
dsConclusion: randomConclusion() // 随机!
// 特点�
- 1秒完�99�
// 特点:
- 1秒完成199篇
- 证据都是"模拟证据"
- 判断结果随机生成
```
### 真实LLM(æ°ï¼?
### 真实LLM
```javascript
// âœ?真实数æ<C2B0>®
// ✅ 真实数据
dsPEvidence: "This study included adult patients with type 2 diabetes mellitus aged 18 years or older, which matches the population criteria."
dsReason: "The study population consists of T2DM patients, the intervention is an SGLT2 inhibitor (empagliflozin), the comparator is placebo, and the study design is a randomized controlled trial. All PICO criteria are met. The study reports on cardiovascular outcomes including MACE, heart failure hospitalization, and cardiovascular death, which are the outcomes of interest."
dsConclusion: "include" // AI真实判断�
dsConclusion: "include" // AI真实判断!
// 特点�
- 33-66分éŸå®Œæˆ<EFBFBD>199ç¯?
// 特点:
- 33-66分钟完成199篇
- 证据引用文献原文
- 判断基于Prompt v1.0.0-MVP
- 准确率60%(首次测试)
@@ -222,12 +222,12 @@ node check-data.mjs
### 预期输出(真实数据)
```
🔬 筛选结果样�
🔬 筛选结果样本:
[1] 文献: Assessment of Thrombectomy versus Combined...
DeepSeek: include (P:match, I:partial, C:mismatch, S:match)
Qwen: exclude (P:mismatch, I:mismatch, C:partial, S:match)
冲çª<EFBFBD>状æ€? conflict
是å<EFBFBD>¦æœ‰è¯<EFBFBD>æ<EFBFBD>? DeepSeek=true, Qwen=true âœ?
冲突状态: conflict
是否有证据: DeepSeek=true, Qwen=true
证据示例:
- dsPEvidence: "The study population consists of..."
@@ -245,17 +245,17 @@ node check-data.mjs
- 不合格的输出会被拒绝
2. **错误处理**:
- å<EFBFBD>•篇æ‡çŒ®å¤±è´¥ä¸<EFBFBD>å½±å“<EFBFBD>整体任åŠ?
- 单篇文献失败不影响整体任务
- 详细错误日志记录
3. **进度追踪**:
- æ¯?0ç¯‡æ´æ°ä¸€æ¬¡è¿åº?
- 实时统计æˆ<EFBFBD>功/冲çª<C3A7>/失败æ•?
- 每10篇更新一次进度
- 实时统计成功/冲突/失败数
4. **å<EFBFBD>¯è¿½æº¯æ€?*:
- 记录原å§LLM输出(`rawOutput`ï¼?
- 记录Prompt版本(`promptVersion`�
- 记录处ç<EFBFBD>†æ—¶é—´ï¼ˆ`aiProcessedAt`ï¼?
4. **可追溯性**:
- 记录原始LLM输出`rawOutput`
- 记录Prompt版本`promptVersion`
- 记录处理时间(`aiProcessedAt`
---
@@ -264,8 +264,8 @@ node check-data.mjs
### Step 1: 准备测试数据
```
使用现有测试文件:
- PICOS: docs/.../æµè¯•案ä¾çš„PICOSã€<EFBFBD>纳入标准ã€<EFBFBD>æŽé™¤æ ‡å‡?txt
- Excel: docs/.../Test Cases.xlsx (199篇文�
- PICOS: docs/.../测试案例的PICOS、纳入标准、排除标准.txt
- Excel: docs/.../Test Cases.xlsx (199篇文献)
```
### Step 2: 执行测试
@@ -274,8 +274,8 @@ node check-data.mjs
3. 访问: `http://localhost:3001`
4. 填写PICOS + 上传Excel
5. 点击"开始AI初筛"
6. **等待30-60分éŸ**ï¼?99篇Ã?0ç§ï¼‰
7. 查çœå®¡æ ¸å·¥ä½œå<EFBFBD>?
6. **等待30-60分钟**199篇×20秒
7. 查看审核工作台
### Step 3: 验证结果
```bash
@@ -284,18 +284,18 @@ node check-data.mjs
```
**检查项**:
- [ ] 所有文献都有筛选结�
- [ ] è¯<EFBFBD>æ<EFBFBD>®ä¸<EFBFBD>å†<EFBFBD>æ˜?模æŸè¯<C3A8>æ<EFBFBD>®"
- [ ] 所有文献都有筛选结果
- [ ] 证据不再是"模拟证据"
- [ ] 证据包含文献原文引用
- [ ] 判断理由详细且符合逻辑
- [ ] 冲çª<EFBFBD>检æµå‡†ç¡®ï¼ˆconclusionä¸<EFBFBD>å<EFBFBD>Œï¼?
- [ ] 冲突检测准确(conclusion不同)
---
## ⚠️ 注意事项
### API密钥配置
ç¡®ä¿<EFBFBD>环境å<EFBFBD>˜é‡<EFBFBD>å·²é…<EFBFBD>ç½?
确保环境变量已配置:
```bash
# .env
DEEPSEEK_API_KEY=sk-xxxxx
@@ -306,33 +306,33 @@ QWEN_API_KEY=sk-xxxxx
- DeepSeek: 60 RPM每分钟请求数
- Qwen: 60 RPM
**当å‰<EFBFBD>ç­ç•¥**: 串行处ç<E2809E>†ï¼Œä¸<C3A4>会触å<C2A6>é™<C3A9>æµ?
**当前策略**: 串行处理,不会触发限流
### 成本估算
- DeepSeek: ~$0.001/�× 199 = **$0.20**
- Qwen: ~$0.001/�× 199 = **$0.20**
- **总计**: **$0.40** / 次完整测�
- DeepSeek: ~$0.001/× 199 = **$0.20**
- Qwen: ~$0.001/× 199 = **$0.20**
- **总计**: **$0.40** / 次完整测试
---
## 💡 优化建议
### 短期优化(Week 2 - Day 4-5�
1. **å¹¶å<EFBFBD>控制**: 改为3个并å<C2B6>(33åˆ†éŸ â†?11分éŸï¼?
2. **进度显示**: å‰<C3A5>端轮询显示è¿åº¦ç™¾åˆ†æ¯?
3. **错误é‡<EFBFBD>试**: 失败的æ‡çŒ®è‡ªåЍé‡<C3A9>è¯?æ¬?
### 短期优化(Week 2 - Day 4-5
1. **并发控制**: 改为3个并发33分钟 → 11分钟
2. **进度显示**: 前端轮询显示进度百分比
3. **错误重试**: 失败的文献自动重试1次
### 中期优化(Week 3�
### 中期优化(Week 3
1. **消息队列**: 使用Bull Queue异步处理
2. **批é‡<EFBFBD>优åŒ**: 使用批é‡<C3A9>API接å<C2A5>£ï¼ˆå¦æžœæœ‰ï¼?
3. **缓存机制**: ç¸å<C2B8>Œæ‡çŒ®ä¸<C3A4>é‡<C3A9>å¤<C3A5>ç­é€?
2. **批量优化**: 使用批量API接口如果有
3. **缓存机制**: 相同文献不重复筛选
---
## 📁 相关文件
### 修改的文�
- `backend/src/modules/asl/services/screeningService.ts` â­?
### 修改的文件
- `backend/src/modules/asl/services/screeningService.ts`
### 依赖的文件(已存在)
- `backend/src/modules/asl/services/llmScreeningService.ts`
@@ -348,30 +348,30 @@ QWEN_API_KEY=sk-xxxxx
## 🎉 成果总结
### 已实�
âœ?真实LLMè°ƒç”¨æ¿æ<C2BF>¢Mockæ•°æ<C2B0>®
âœ?从项ç®è¯»å<C2BB>PICOS标准
âœ?å<>Œæ¨¡åžå¹¶è¡Œç­é€?
âœ?冲çª<C3A7>检æµä¸Žæ ‡è®°
�完整的日志追�
âœ?错误处ç<E2809E>†æœºåˆ
### 已实现
✅ 真实LLM调用替换Mock数据
✅ 从项目读取PICOS标准
✅ 双模型并行筛选
✅ 冲突检测与标记
✅ 完整的日志追踪
✅ 错误处理机制
### 待优�
âš ï¸<EFBFBD> 处ç<E2809E>†æ—¶é—´è¾ƒé•¿ï¼?0-60分éŸï¼?
âš ï¸<EFBFBD> 串行处ç<E2809E>†ï¼ˆå<CB86>¯æ”¹ä¸ºå¹¶å<C2B6>ï¼?
âš ï¸<EFBFBD> å‰<C3A5>端è¿åº¦æ˜¾ç¤ºï¼ˆéœ€ä¼˜åŒè½®è¯¢é¢çއï¼?
### 待优化
⚠️ 处理时间较长30-60分钟
⚠️ 串行处理(可改为并发)
⚠️ 前端进度显示(需优化轮询频率)
---
## 🔗 å<>è€ƒæ‡æ¡?
## 🔗 参考文档
- [Prompt设计与æµè¯•完æˆ<EFBFBD>报åŠ](./2025-11-18-Prompt设计与æµè¯•完æˆ<C3A6>报å?md)
- [Prompt设计与测试完成报告](./2025-11-18-Prompt设计与测试完成报告.md)
- [卒中数据泛化测试报告](./2025-11-18-卒中数据泛化测试报告.md)
- [任务分解](../04-å¼€å<E282AC>计åˆ?03-任务分解.md)
- [任务分解](../04-开发计划/03-任务分解.md)
---
**报告�*: AI Assistant
**报告人**: AI Assistant
**日期**: 2025-11-21
**版本**: v1.0.0