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:
34
frontend-v2/src/framework/auth/moduleApi.ts
Normal file
34
frontend-v2/src/framework/auth/moduleApi.ts
Normal file
@@ -0,0 +1,34 @@
|
||||
/**
|
||||
* 用户模块权限 API
|
||||
*/
|
||||
|
||||
import { getAccessToken } from './api';
|
||||
|
||||
const API_BASE = '/api/v1/auth';
|
||||
|
||||
/**
|
||||
* 获取当前用户可访问的模块
|
||||
*/
|
||||
export async function fetchUserModules(): Promise<string[]> {
|
||||
const token = getAccessToken();
|
||||
const headers: HeadersInit = {
|
||||
'Content-Type': 'application/json',
|
||||
};
|
||||
if (token) {
|
||||
(headers as Record<string, string>)['Authorization'] = `Bearer ${token}`;
|
||||
}
|
||||
|
||||
const response = await fetch(`${API_BASE}/me/modules`, {
|
||||
method: 'GET',
|
||||
headers,
|
||||
});
|
||||
|
||||
if (!response.ok) {
|
||||
const error = await response.json();
|
||||
throw new Error(error.message || '获取模块权限失败');
|
||||
}
|
||||
|
||||
const result = await response.json();
|
||||
return result.data || [];
|
||||
}
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
import { useState, useEffect } from 'react'
|
||||
import { useNavigate, useLocation } from 'react-router-dom'
|
||||
import { Dropdown, Avatar, Tooltip } from 'antd'
|
||||
import {
|
||||
@@ -9,9 +10,11 @@ import {
|
||||
BankOutlined,
|
||||
} from '@ant-design/icons'
|
||||
import type { MenuProps } from 'antd'
|
||||
import { getAvailableModules } from '../modules/moduleRegistry'
|
||||
import { getAvailableModulesByCode } from '../modules/moduleRegistry'
|
||||
import { fetchUserModules } from '../auth/moduleApi'
|
||||
import { usePermission } from '../permission'
|
||||
import { useAuth } from '../auth'
|
||||
import type { ModuleDefinition } from '../modules/types'
|
||||
|
||||
/**
|
||||
* 顶部导航栏组件
|
||||
@@ -28,9 +31,22 @@ const TopNavigation = () => {
|
||||
const location = useLocation()
|
||||
const { user: authUser, logout: authLogout } = useAuth()
|
||||
const { user, checkModulePermission, logout } = usePermission()
|
||||
const [availableModules, setAvailableModules] = useState<ModuleDefinition[]>([])
|
||||
|
||||
// 获取用户有权访问的模块列表(权限过滤)⭐ 新增
|
||||
const availableModules = getAvailableModules(user?.version || 'basic')
|
||||
// 加载用户可访问的模块
|
||||
useEffect(() => {
|
||||
const loadModules = async () => {
|
||||
try {
|
||||
const moduleCodes = await fetchUserModules()
|
||||
const modules = getAvailableModulesByCode(moduleCodes)
|
||||
setAvailableModules(modules)
|
||||
} catch (error) {
|
||||
console.error('加载模块权限失败', error)
|
||||
setAvailableModules([])
|
||||
}
|
||||
}
|
||||
loadModules()
|
||||
}, [authUser])
|
||||
|
||||
// 获取当前激活的模块
|
||||
const activeModule = availableModules.find(module =>
|
||||
|
||||
@@ -10,6 +10,19 @@ import {
|
||||
AuditOutlined
|
||||
} from '@ant-design/icons'
|
||||
|
||||
/**
|
||||
* 前端模块ID与后端模块代码的映射
|
||||
*/
|
||||
export const MODULE_CODE_MAP: Record<string, string> = {
|
||||
'ai-qa': 'AIA',
|
||||
'literature-platform': 'ASL',
|
||||
'knowledge-base': 'PKB',
|
||||
'data-cleaning': 'DC',
|
||||
'statistical-analysis': 'SSA', // 暂未实现
|
||||
'statistical-tools': 'ST', // 暂未实现
|
||||
'review-system': 'RVW',
|
||||
};
|
||||
|
||||
/**
|
||||
* 模块注册中心
|
||||
* 按照平台架构文档顺序注册所有业务模块
|
||||
@@ -25,6 +38,7 @@ export const MODULES: ModuleDefinition[] = [
|
||||
placeholder: true, // 后续重写
|
||||
requiredVersion: 'basic',
|
||||
description: '基于LLM的智能问答系统',
|
||||
moduleCode: 'AIA', // 后端模块代码
|
||||
},
|
||||
{
|
||||
id: 'literature-platform',
|
||||
@@ -36,6 +50,7 @@ export const MODULES: ModuleDefinition[] = [
|
||||
requiredVersion: 'advanced',
|
||||
description: 'AI驱动的文献筛选和分析系统',
|
||||
standalone: true, // 支持独立运行
|
||||
moduleCode: 'ASL', // 后端模块代码
|
||||
},
|
||||
{
|
||||
id: 'knowledge-base',
|
||||
@@ -46,6 +61,7 @@ export const MODULES: ModuleDefinition[] = [
|
||||
placeholder: false, // V5.0设计已完成实现 ✅
|
||||
requiredVersion: 'basic',
|
||||
description: '个人知识库管理系统(支持全文阅读、逐篇精读、批处理)',
|
||||
moduleCode: 'PKB', // 后端模块代码
|
||||
},
|
||||
{
|
||||
id: 'data-cleaning',
|
||||
@@ -56,6 +72,7 @@ export const MODULES: ModuleDefinition[] = [
|
||||
placeholder: true, // 占位
|
||||
requiredVersion: 'advanced',
|
||||
description: '智能数据清洗整理工具',
|
||||
moduleCode: 'DC', // 后端模块代码
|
||||
},
|
||||
{
|
||||
id: 'statistical-analysis',
|
||||
@@ -67,6 +84,7 @@ export const MODULES: ModuleDefinition[] = [
|
||||
requiredVersion: 'premium',
|
||||
description: '智能统计分析系统(Java团队开发)',
|
||||
isExternal: true, // 外部模块
|
||||
moduleCode: 'SSA', // 后端模块代码
|
||||
},
|
||||
{
|
||||
id: 'statistical-tools',
|
||||
@@ -78,6 +96,7 @@ export const MODULES: ModuleDefinition[] = [
|
||||
requiredVersion: 'premium',
|
||||
description: '统计分析工具集(Java团队开发)',
|
||||
isExternal: true, // 外部模块
|
||||
moduleCode: 'ST', // 后端模块代码
|
||||
},
|
||||
{
|
||||
id: 'review-system',
|
||||
@@ -88,6 +107,7 @@ export const MODULES: ModuleDefinition[] = [
|
||||
placeholder: false, // RVW模块已开发
|
||||
requiredVersion: 'basic',
|
||||
description: '智能期刊审稿系统(稿约评审+方法学评审)',
|
||||
moduleCode: 'RVW', // 后端模块代码
|
||||
},
|
||||
]
|
||||
|
||||
@@ -112,6 +132,7 @@ export const getModuleByPath = (path: string): ModuleDefinition | undefined => {
|
||||
* @returns 用户有权访问的模块列表
|
||||
*
|
||||
* @version Week 2 Day 7 - 任务17:实现权限过滤逻辑
|
||||
* @deprecated 使用 getAvailableModulesByCode 替代
|
||||
*/
|
||||
export const getAvailableModules = (userVersion: string = 'premium'): ModuleDefinition[] => {
|
||||
// 权限等级映射
|
||||
@@ -134,3 +155,19 @@ export const getAvailableModules = (userVersion: string = 'premium'): ModuleDefi
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
* 根据用户模块权限过滤可访问的模块
|
||||
*
|
||||
* @param userModuleCodes 用户可访问的模块代码列表 (如 ['RVW', 'PKB'])
|
||||
* @returns 用户有权访问的模块列表
|
||||
*/
|
||||
export const getAvailableModulesByCode = (userModuleCodes: string[]): ModuleDefinition[] => {
|
||||
return MODULES.filter(module => {
|
||||
// 如果模块没有 moduleCode,保持兼容(外部模块或占位模块)
|
||||
if (!module.moduleCode) return false;
|
||||
|
||||
// 检查用户是否有该模块的访问权限
|
||||
return userModuleCodes.includes(module.moduleCode);
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
@@ -60,5 +60,8 @@ export interface ModuleDefinition {
|
||||
|
||||
/** 模块描述 */
|
||||
description?: string
|
||||
|
||||
/** 后端模块代码(用于权限检查) */
|
||||
moduleCode?: string
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user