feat(admin): Complete tenant management and module access control system
Major Features: - Tenant management CRUD (list, create, edit, delete, module configuration) - Dynamic module management system (modules table with 8 modules) - Multi-tenant module permission merging (ModuleService) - Module access control middleware (requireModule) - User module permission API (GET /api/v1/auth/me/modules) - Frontend module permission filtering (HomePage + TopNavigation) Module Integration: - RVW module integrated with PromptService (editorial + methodology) - All modules (RVW/PKB/ASL/DC) added authenticate + requireModule middleware - Fixed ReviewTask foreign key constraint (cross-schema issue) - Removed all MOCK_USER_ID, unified to request.user?.userId Prompt Management Enhancements: - Module names displayed in Chinese (RVW -> 智能审稿) - Enhanced version history with view content and rollback features - List page shows both activeVersion and draftVersion columns Database Changes: - Added platform_schema.modules table - Modified tenant_modules table (added index and UUID) - Removed ReviewTask foreign key to public.users (cross-schema fix) - Seeded 8 modules: RVW, PKB, ASL, DC, IIT, AIA, SSA, ST Documentation Updates: - Updated ADMIN module development status - Updated TODO checklist (89% progress) - Updated Prompt management plan (Phase 3.5.5 completed) - Added module authentication specification Files Changed: 80+ Status: All features tested and verified locally Next: User management module development
This commit is contained in:
190
docs/04-开发规范/10-模块认证规范.md
Normal file
190
docs/04-开发规范/10-模块认证规范.md
Normal file
@@ -0,0 +1,190 @@
|
||||
# 模块认证规范
|
||||
|
||||
> 本文档定义了业务模块如何正确使用平台认证能力,确保所有 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`
|
||||
|
||||
|
||||
Reference in New Issue
Block a user