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:
@@ -1,9 +1,9 @@
|
||||
# 字段映射问题修复报告
|
||||
|
||||
**日期**: 2025-11-21
|
||||
**问题**: 真实LLMç›é€‰å¤±è´¥ï¼ˆæˆ<EFBFBD>功ï¼?/20ï¼?
|
||||
**问题**: 真实LLM筛选失败(成功:0/20)
|
||||
**原因**: 字段名不匹配
|
||||
**状�*: �已修�
|
||||
**状态**: ✅ 已修复
|
||||
|
||||
---
|
||||
|
||||
@@ -11,16 +11,16 @@
|
||||
|
||||
### 症状
|
||||
```
|
||||
任务状� completed
|
||||
任务状态: completed
|
||||
进度: 20/20
|
||||
æˆ<EFBFBD>功: 0 â<EFBFBD>?
|
||||
成功: 0 ❌
|
||||
筛选结果数: 0
|
||||
```
|
||||
|
||||
**表现**�
|
||||
- 任务瞬间完æˆ<EFBFBD>ï¼?秒)
|
||||
- 所有文献处ç<EFBFBD>†å¤±è´?
|
||||
- 没有ä¿<EFBFBD>å˜ä»»ä½•ç›é€‰ç»“æž?
|
||||
**表现**:
|
||||
- 任务瞬间完成(1秒)
|
||||
- 所有文献处理失败
|
||||
- 没有保存任何筛选结果
|
||||
|
||||
---
|
||||
|
||||
@@ -28,11 +28,11 @@
|
||||
|
||||
### 问题1: PICOS字段名不匹配
|
||||
|
||||
**å‰<EFBFBD>端/æ•°æ<C2B0>®åº“æ ¼å¼?* (`TitleScreeningSettings.tsx`):
|
||||
**前端/数据库格式** (`TitleScreeningSettings.tsx`):
|
||||
```typescript
|
||||
picoCriteria: {
|
||||
P: '2型糖尿病患�..',
|
||||
I: 'SGLT2抑制�..',
|
||||
P: '2型糖尿病患者...',
|
||||
I: 'SGLT2抑制剂...',
|
||||
C: '安慰剂或常规治疗...',
|
||||
O: '心血管结局...',
|
||||
S: 'RCT'
|
||||
@@ -43,15 +43,15 @@ picoCriteria: {
|
||||
```typescript
|
||||
// 实际上支持两种格式,但优先使用短格式
|
||||
picoCriteria: {
|
||||
P: '...', // �
|
||||
I: '...', // �
|
||||
C: '...', // �
|
||||
O: '...', // �
|
||||
S: '...' // �
|
||||
P: '...', // ✅
|
||||
I: '...', // ✅
|
||||
C: '...', // ✅
|
||||
O: '...', // ✅
|
||||
S: '...' // ✅
|
||||
}
|
||||
```
|
||||
|
||||
**诊æ–**:å‰<C3A5>端使ç”?P/I/C/O/S æ ¼å¼<C3A5>,但 `screeningService.ts` ç›´æŽ¥ä¼ é€’äº†æ•°æ<C2B0>®åº“çš„åŽŸå§‹æ ¼å¼<C3A5>,未å<C2AA>šæ˜ å°„ã€?
|
||||
**诊断**:前端使用 P/I/C/O/S 格式,但 `screeningService.ts` 直接传递了数据库的原始格式,未做映射。
|
||||
|
||||
---
|
||||
|
||||
@@ -67,24 +67,24 @@ models: ['DeepSeek-V3', 'Qwen-Max']
|
||||
models: ['deepseek-chat', 'qwen-max']
|
||||
```
|
||||
|
||||
**åŽŸå› **:å‰<C3A5>端使用展示å<C2BA><C3A5>称,å<C592>Žç«¯éœ€è¦<C3A8>APIå<49><C3A5>ç§°ã€?
|
||||
**原因**:前端使用展示名称,后端需要API名称。
|
||||
|
||||
---
|
||||
|
||||
### 问题3: 缺少字段验证
|
||||
|
||||
文献å<EFBFBD>¯èƒ½ç¼ºå°‘ `title` æˆ?`abstract`,导致LLM调用失败ã€?
|
||||
文献可能缺少 `title` 或 `abstract`,导致LLM调用失败。
|
||||
|
||||
---
|
||||
|
||||
## âœ?ä¿®å¤<C3A5>方案
|
||||
## ✅ 修复方案
|
||||
|
||||
### 修复1: 添加PICOS字段映射
|
||||
|
||||
**文件**: `backend/src/modules/asl/services/screeningService.ts`
|
||||
|
||||
```typescript
|
||||
// 🔧 ä¿®å¤<C3A5>ï¼šå—æ®µå<C2B5><C3A5>æ˜ å°„ï¼ˆæ•°æ<C2B0>®åº“æ ¼å¼<C3A5> â†?LLMæœ<C3A6>åŠ¡æ ¼å¼<C3A5>ï¼?
|
||||
// 🔧 修复:字段名映射(数据库格式 → LLM服务格式)
|
||||
const rawPicoCriteria = project.picoCriteria as any;
|
||||
const picoCriteria = {
|
||||
P: rawPicoCriteria?.P || rawPicoCriteria?.population || '',
|
||||
@@ -95,22 +95,22 @@ const picoCriteria = {
|
||||
};
|
||||
```
|
||||
|
||||
**优势**�
|
||||
- âœ?兼容两ç§<C3A7>æ ¼å¼<C3A5>(P/I/C/O/S æˆ?population/intervention/...ï¼?
|
||||
- âœ?防御性编程,é<C592>¿å…<C3A5>undefined
|
||||
**优势**:
|
||||
- ✅ 兼容两种格式(P/I/C/O/S 或 population/intervention/...)
|
||||
- ✅ 防御性编程,避免undefined
|
||||
|
||||
---
|
||||
|
||||
### ä¿®å¤<EFBFBD>2: æ·»åŠ æ¨¡åž‹å<E280B9><C3A5>æ˜ å°?
|
||||
### 修复2: 添加模型名映射
|
||||
|
||||
```typescript
|
||||
// 🔧 ä¿®å¤<C3A5>:模型å<E280B9><C3A5>æ˜ å°„ï¼ˆå‰<C3A5>ç«¯æ ¼å¼?â†?APIæ ¼å¼<C3A5>ï¼?
|
||||
// 🔧 修复:模型名映射(前端格式 → API格式)
|
||||
const MODEL_NAME_MAP: Record<string, string> = {
|
||||
'DeepSeek-V3': 'deepseek-chat',
|
||||
'Qwen-Max': 'qwen-max',
|
||||
'GPT-4o': 'gpt-4o',
|
||||
'Claude-4.5': 'claude-sonnet-4.5',
|
||||
'deepseek-chat': 'deepseek-chat', // 兼容直接使用APIå<EFBFBD>?
|
||||
'deepseek-chat': 'deepseek-chat', // 兼容直接使用API名
|
||||
'qwen-max': 'qwen-max',
|
||||
// ... 更多映射
|
||||
};
|
||||
@@ -119,8 +119,8 @@ const rawModels = screeningConfig?.models || ['deepseek-chat', 'qwen-max'];
|
||||
const models = rawModels.map((m: string) => MODEL_NAME_MAP[m] || m);
|
||||
```
|
||||
|
||||
**æ˜ å°„è¡?*ï¼?
|
||||
| å‰<EFBFBD>端展示å<EFBFBD>?| APIå<49><C3A5>ç§° |
|
||||
**映射表**:
|
||||
| 前端展示名 | API名称 |
|
||||
|-----------|---------|
|
||||
| DeepSeek-V3 | deepseek-chat |
|
||||
| Qwen-Max | qwen-max |
|
||||
@@ -132,7 +132,7 @@ const models = rawModels.map((m: string) => MODEL_NAME_MAP[m] || m);
|
||||
### 修复3: 添加文献验证
|
||||
|
||||
```typescript
|
||||
// 🔧 验è¯<C3A8>ï¼šå¿…é¡»æœ‰æ ‡é¢˜å’Œæ‘˜è¦?
|
||||
// 🔧 验证:必须有标题和摘要
|
||||
if (!literature.title || !literature.abstract) {
|
||||
logger.warn('Skipping literature without title or abstract', {
|
||||
literatureId: literature.id,
|
||||
@@ -150,62 +150,62 @@ if (!literature.title || !literature.abstract) {
|
||||
### 修复4: 增强调试日志
|
||||
|
||||
```typescript
|
||||
console.log('\n🚀 开始真实LLMç›é€?');
|
||||
console.log('\n🚀 开始真实LLM筛选:');
|
||||
console.log(' 任务ID:', taskId);
|
||||
console.log(' 项目ID:', projectId);
|
||||
console.log(' 文献�', literatures.length);
|
||||
console.log(' æ¨¡åž‹ï¼ˆæ˜ å°„å<EFBFBD>Žï¼?', models); // â?æ˜¾ç¤ºæ˜ å°„å<E2809E>Žçš„å€?
|
||||
console.log(' PICOS-P:', picoCriteria.P?.substring(0, 50) || '(ç©?');
|
||||
console.log(' PICOS-I:', picoCriteria.I?.substring(0, 50) || '(ç©?');
|
||||
console.log(' PICOS-C:', picoCriteria.C?.substring(0, 50) || '(ç©?');
|
||||
console.log(' çº³å…¥æ ‡å‡†:', inclusionCriteria?.substring(0, 50) || '(ç©?');
|
||||
console.log(' æŽ’é™¤æ ‡å‡†:', exclusionCriteria?.substring(0, 50) || '(ç©?');
|
||||
console.log(' 文献数:', literatures.length);
|
||||
console.log(' 模型(映射后):', models); // ⭐ 显示映射后的值
|
||||
console.log(' PICOS-P:', picoCriteria.P?.substring(0, 50) || '(空)');
|
||||
console.log(' PICOS-I:', picoCriteria.I?.substring(0, 50) || '(空)');
|
||||
console.log(' PICOS-C:', picoCriteria.C?.substring(0, 50) || '(空)');
|
||||
console.log(' 纳入标准:', inclusionCriteria?.substring(0, 50) || '(空)');
|
||||
console.log(' 排除标准:', exclusionCriteria?.substring(0, 50) || '(空)');
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 🧪 测试步骤
|
||||
|
||||
### 1. é‡<EFBFBD>å<EFBFBD>¯å<EFBFBD>Žç«¯ï¼ˆå¿…é¡»ï¼<EFBFBD>ï¼?
|
||||
### 1. 重启后端(必须!)
|
||||
|
||||
```bash
|
||||
# å<EFBFBD>œæ¢å½“å‰<EFBFBD>å<EFBFBD>Žç«¯ï¼ˆCtrl+Cï¼?
|
||||
# 停止当前后端(Ctrl+C)
|
||||
cd D:\MyCursor\AIclinicalresearch\backend
|
||||
npm run dev
|
||||
```
|
||||
|
||||
### 2. 测试(å°<EFBFBD>规模ï¼?
|
||||
### 2. 测试(小规模)
|
||||
|
||||
1. 访问前端
|
||||
2. 填写PICOS
|
||||
3. **ä¸Šä¼ 5篇文çŒ?*(先测试å°<C3A5>规模)
|
||||
3. **上传5篇文献**(先测试小规模)
|
||||
4. 点击"开始AI初筛"
|
||||
|
||||
### 3. 查看å<EFBFBD>Žç«¯æŽ§åˆ¶å<EFBFBD>?
|
||||
### 3. 查看后端控制台
|
||||
|
||||
**应该看到**�
|
||||
**应该看到**:
|
||||
```
|
||||
🚀 开始真实LLMç›é€?
|
||||
🚀 开始真实LLM筛选:
|
||||
任务ID: xxx
|
||||
文献� 5
|
||||
æ¨¡åž‹ï¼ˆæ˜ å°„å<EFBFBD>Žï¼? [ 'deepseek-chat', 'qwen-max' ]
|
||||
PICOS-P: 2型糖尿病患�..
|
||||
PICOS-I: SGLT2抑制�..
|
||||
PICOS-C: 安慰�..
|
||||
文献数: 5
|
||||
模型(映射后): [ 'deepseek-chat', 'qwen-max' ]
|
||||
PICOS-P: 2型糖尿病患者...
|
||||
PICOS-I: SGLT2抑制剂...
|
||||
PICOS-C: 安慰剂...
|
||||
纳入标准: 成人2型糖尿病...
|
||||
æŽ’é™¤æ ‡å‡†: 综述ã€<C3A3>系统评ä»?..
|
||||
排除标准: 综述、系统评价...
|
||||
|
||||
[等待10-20秒]
|
||||
|
||||
âœ?文献 1/5 处ç<E2809E>†æˆ<C3A6>功
|
||||
✅ 文献 1/5 处理成功
|
||||
DS: include / Qwen: include
|
||||
冲çª<EFBFBD>: å<>?
|
||||
冲突: 否
|
||||
|
||||
[等待10-20秒]
|
||||
|
||||
âœ?文献 2/5 处ç<E2809E>†æˆ<C3A6>功
|
||||
✅ 文献 2/5 处理成功
|
||||
DS: exclude / Qwen: exclude
|
||||
冲çª<EFBFBD>: å<>?
|
||||
冲突: 否
|
||||
...
|
||||
```
|
||||
|
||||
@@ -213,25 +213,25 @@ npm run dev
|
||||
|
||||
## 📊 预期效果
|
||||
|
||||
### ä¿®å¤<EFBFBD>å‰?
|
||||
- â<EFBFBD>±ï¸<EFBFBD> 1秒完æˆ?0ç¯?
|
||||
- â<EFBFBD>?æˆ<C3A6>功ï¼?
|
||||
- â<EFBFBD>?ç›é€‰ç»“果数ï¼?
|
||||
### 修复前
|
||||
- ⏱️ 1秒完成20篇
|
||||
- ❌ 成功:0
|
||||
- ❌ 筛选结果数:0
|
||||
|
||||
### ä¿®å¤<EFBFBD>å<EFBFBD>?
|
||||
- â<EFBFBD>±ï¸<EFBFBD> 50-100秒完æˆ?篇(æ¯<C3A6>篇10-20秒)
|
||||
- âœ?æˆ<C3A6>功ï¼?
|
||||
- âœ?ç›é€‰ç»“果数ï¼?
|
||||
- âœ?è¯<C3A8>æ<EFBFBD>®åŒ…å<E280A6>«çœŸå®žçš„AI分æž<C3A6>
|
||||
- âœ?è¯<C3A8>æ<EFBFBD>®ä¸<C3A4>包å<E280A6>?模拟è¯<C3A8>æ<EFBFBD>®"
|
||||
### 修复后
|
||||
- ⏱️ 50-100秒完成5篇(每篇10-20秒)
|
||||
- ✅ 成功:5
|
||||
- ✅ 筛选结果数:5
|
||||
- ✅ 证据包含真实的AI分析
|
||||
- ✅ 证据不包含"模拟证据"
|
||||
|
||||
---
|
||||
|
||||
## 🔧 修改文件
|
||||
|
||||
- �`backend/src/modules/asl/services/screeningService.ts`
|
||||
- ✅ `backend/src/modules/asl/services/screeningService.ts`
|
||||
- 添加PICOS字段映射
|
||||
- æ·»åŠ æ¨¡åž‹å<EFBFBD><EFBFBD>æ˜ å°?
|
||||
- 添加模型名映射
|
||||
- 添加文献验证
|
||||
- 增强调试日志
|
||||
|
||||
@@ -239,42 +239,42 @@ npm run dev
|
||||
|
||||
## 💡 经验教训
|
||||
|
||||
### 1. å‰<EFBFBD>å<EFBFBD>Žç«¯æ•°æ<EFBFBD>®æ ¼å¼<EFBFBD>一致æ€?
|
||||
- å‰<EFBFBD>ç«¯ä½¿ç”¨çš„å±•ç¤ºæ ¼å¼?â‰?å<>Žç«¯APIæ ¼å¼<C3A5>
|
||||
### 1. 前后端数据格式一致性
|
||||
- 前端使用的展示格式 ≠ 后端API格式
|
||||
- 需要在集成层做映射
|
||||
|
||||
### 2. 防御性编�
|
||||
- 使用 `||` æ<EFBFBD><EFBFBD>供默认å€?
|
||||
### 2. 防御性编程
|
||||
- 使用 `||` 提供默认值
|
||||
- 验证必需字段
|
||||
- 兼容多种格式
|
||||
|
||||
### 3. 调试日志的é‡<EFBFBD>è¦<EFBFBD>æ€?
|
||||
### 3. 调试日志的重要性
|
||||
- 显示映射后的值(不是原始值)
|
||||
- 输出所有关键å<EFBFBD>‚æ•?
|
||||
- 帮助快速定ä½<EFBFBD>é—®é¢?
|
||||
- 输出所有关键参数
|
||||
- 帮助快速定位问题
|
||||
|
||||
---
|
||||
|
||||
## 🎯 后续优化
|
||||
|
||||
### 短期
|
||||
1. âœ?å—æ®µæ˜ 射(已完æˆ<C3A6>ï¼?
|
||||
2. âœ?模型å<E280B9><C3A5>æ˜ å°„ï¼ˆå·²å®Œæˆ<C3A6>)
|
||||
3. âœ?验è¯<C3A8>å¿…éœ€å—æ®µï¼ˆå·²å®Œæˆ<C3A6>ï¼?
|
||||
1. ✅ 字段映射(已完成)
|
||||
2. ✅ 模型名映射(已完成)
|
||||
3. ✅ 验证必需字段(已完成)
|
||||
|
||||
### 中期
|
||||
1. 统一å‰<EFBFBD>å<EFBFBD>Žç«¯æ•°æ<EFBFBD>®æ ¼å¼<EFBFBD>(使用 TypeScript 接å<EFBFBD>£ï¼?
|
||||
2. æ·»åŠ æ•°æ<EFBFBD>®æ ¼å¼<EFBFBD>验è¯<EFBFBD>ä¸é—´ä»?
|
||||
1. 统一前后端数据格式(使用 TypeScript 接口)
|
||||
2. 添加数据格式验证中间件
|
||||
3. 改进错误提示
|
||||
|
||||
### 长期
|
||||
1. 使用 tRPC æˆ?GraphQL ç¡®ä¿<EFBFBD>类型安全
|
||||
2. 自动化测试覆�
|
||||
1. 使用 tRPC 或 GraphQL 确保类型安全
|
||||
2. 自动化测试覆盖
|
||||
3. Schema验证
|
||||
|
||||
---
|
||||
|
||||
**报告�*: AI Assistant
|
||||
**报告人**: AI Assistant
|
||||
**日期**: 2025-11-21
|
||||
**版本**: v1.0.0
|
||||
|
||||
|
||||
Reference in New Issue
Block a user