Files
AIclinicalresearch/docs/04-开发规范/10-模块认证规范.md
HaHafeng 66255368b7 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
2026-01-16 13:42:10 +08:00

194 lines
6.8 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# 模块认证规范
> 本文档定义了业务模块如何正确使用平台认证能力,确保所有 API 都正确携带和验证用户身份。
## 1. 架构概览
```
┌─────────────────────────────────────────────────────────────┐
│ 前端 │
│ ┌─────────────────────────────────────────────────────┐ │
│ │ common/api/axios.ts ← 带认证的 axios 实例 │ │
│ │ framework/auth/api.ts ← Token 管理 (getAccessToken)│ │
│ └─────────────────────────────────────────────────────┘ │
└─────────────────────────────────────────────────────────────┘
│ Authorization: Bearer <token>
┌─────────────────────────────────────────────────────────────┐
│ 后端 │
│ ┌─────────────────────────────────────────────────────┐ │
│ │ common/auth/auth.middleware.ts │ │
│ │ - authenticate: 验证 JWT Token │ │
│ │ - requirePermission: 权限检查 │ │
│ │ - requireRoles: 角色检查 │ │
│ └─────────────────────────────────────────────────────┘ │
└─────────────────────────────────────────────────────────────┘
```
## 2. 前端规范
### 2.1 使用带认证的 axios 实例(推荐)
```typescript
// 导入带认证的 apiClient
import apiClient from '../../../common/api/axios';
// 使用方式与 axios 完全相同,自动携带 JWT Token
const response = await apiClient.get('/api/v2/xxx');
const response = await apiClient.post('/api/v2/xxx', data);
```
### 2.2 使用原生 fetch需手动添加 Token
```typescript
import { getAccessToken } from '../../../framework/auth/api';
// 创建 getAuthHeaders 函数
function getAuthHeaders(): HeadersInit {
const headers: Record<string, string> = {
'Content-Type': 'application/json',
};
const token = getAccessToken();
if (token) {
headers['Authorization'] = `Bearer ${token}`;
}
return headers;
}
// 所有 fetch 请求使用 getAuthHeaders()
const response = await fetch(url, {
headers: getAuthHeaders(),
});
// 文件上传(不设置 Content-Type
const token = getAccessToken();
const headers: HeadersInit = {};
if (token) {
headers['Authorization'] = `Bearer ${token}`;
}
const response = await fetch(url, {
method: 'POST',
headers,
body: formData,
});
```
## 3. 后端规范
### 3.1 路由添加认证中间件
```typescript
// 导入认证中间件
import { authenticate, requirePermission } from '../../../common/auth/auth.middleware.js';
// 添加到路由
fastify.get('/xxx', { preHandler: [authenticate] }, handler);
// 需要特定权限
fastify.post('/xxx', {
preHandler: [authenticate, requirePermission('module:action')]
}, handler);
```
### 3.2 控制器获取用户 ID
```typescript
/**
* 获取用户ID从JWT Token中获取
*/
function getUserId(request: FastifyRequest): string {
const userId = (request as any).user?.userId;
if (!userId) {
throw new Error('User not authenticated');
}
return userId;
}
// 在控制器方法中使用
async function myHandler(request: FastifyRequest, reply: FastifyReply) {
const userId = getUserId(request);
// ... 使用 userId
}
```
### 3.3 JWT Token 结构
```typescript
interface DecodedToken {
userId: string; // 用户ID
phone: string; // 手机号
role: string; // 角色
tenantId: string; // 租户ID
tenantCode?: string; // 租户Code
iat: number; // 签发时间
exp: number; // 过期时间
}
```
## 4. 检查清单
### 4.1 新模块开发检查清单
- [ ] **前端 API 文件**
- [ ] 使用 `apiClient` 或添加 `getAuthHeaders()`
- [ ] 文件上传单独处理(不设置 Content-Type
- [ ] 导出函数不包含测试用 userId 参数
- [ ] **后端路由文件**
- [ ] 导入 `authenticate` 中间件
- [ ] 所有需要认证的路由添加 `preHandler: [authenticate]`
- [ ] 公开 API如模板列表可不添加认证
- [ ] **后端控制器文件**
- [ ] 添加 `getUserId()` 辅助函数
- [ ] 移除所有 `MOCK_USER_ID` 或硬编码默认值
- [ ] 使用 `getUserId(request)` 获取用户 ID
### 4.2 已完成模块状态
| 模块 | 前端 API | 后端路由 | 后端控制器 | 状态 |
|------|---------|---------|-----------|------|
| RVW | ✅ apiClient | ✅ authenticate | ✅ getUserId | ✅ |
| PKB | ✅ 拦截器 | ✅ authenticate | ✅ getUserId | ✅ |
| ASL | ✅ getAuthHeaders | ✅ authenticate | ✅ getUserId | ✅ |
| DC Tool B | ✅ getAuthHeaders | ✅ authenticate | ✅ getUserId | ✅ |
| DC Tool C | ✅ apiClient | ✅ authenticate | ✅ getUserId | ✅ |
| IIT | N/A (企业微信) | N/A | ✅ 企业微信userId | ✅ |
| Prompt管理 | ✅ getAuthHeaders | ✅ authenticate | ✅ getUserId | ✅ |
## 5. 常见错误和解决方案
### 5.1 401 Unauthorized
**原因**: 前端没有携带 JWT Token 或 Token 过期
**解决**:
1. 检查前端 API 是否使用 `apiClient``getAuthHeaders()`
2. 检查 localStorage 中是否有 `accessToken`
3. 如果 Token 过期,尝试刷新或重新登录
### 5.2 User not authenticated
**原因**: 后端路由没有添加 `authenticate` 中间件
**解决**: 在路由定义中添加 `preHandler: [authenticate]`
### 5.3 TypeError: Cannot read property 'userId' of undefined
**原因**: 使用了错误的属性名(`request.user.id` 而非 `request.user.userId`
**解决**: 使用 `(request as any).user?.userId`
## 6. 参考文件
- 前端 axios 实例: `frontend-v2/src/common/api/axios.ts`
- 前端 Token 管理: `frontend-v2/src/framework/auth/api.ts`
- 后端认证中间件: `backend/src/common/auth/auth.middleware.ts`
- 后端 JWT 服务: `backend/src/common/auth/jwt.service.ts`