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:
2026-01-13 07:34:30 +08:00
parent 5523ef36ea
commit d595037316
51 changed files with 3550 additions and 287 deletions

View File

@@ -0,0 +1,206 @@
// 查询数据库用户信息
import { PrismaClient } from '@prisma/client';
const prisma = new PrismaClient();
async function main() {
console.log('\n========== 平台用户 (platform_schema.users) ==========\n');
const users = await prisma.user.findMany({
select: {
id: true,
phone: true,
name: true,
role: true,
status: true,
tenant_id: true,
},
orderBy: { role: 'asc' }
});
console.log('用户列表:');
console.table(users.map(u => ({
ID: u.id.substring(0, 8) + '...',
手机号: u.phone,
姓名: u.name,
角色: u.role,
状态: u.status,
})));
console.log('\n默认密码: 123456');
console.log('\n========== 角色权限 (platform_schema.role_permissions) ==========\n');
const rolePerms = await prisma.role_permissions.findMany({
include: {
permissions: true
},
orderBy: { role: 'asc' }
});
const permsByRole = {};
rolePerms.forEach(rp => {
if (!permsByRole[rp.role]) {
permsByRole[rp.role] = [];
}
permsByRole[rp.role].push(rp.permissions.code);
});
console.log('角色权限:');
Object.entries(permsByRole).forEach(([role, perms]) => {
console.log(`\n${role}:`);
perms.forEach(p => console.log(` - ${p}`));
});
console.log('\n========== 租户模块配置 (platform_schema.tenant_modules) ==========\n');
const tenantModules = await prisma.tenant_modules.findMany({
orderBy: [{ tenant_id: 'asc' }, { module_code: 'asc' }]
});
if (tenantModules.length === 0) {
console.log('⚠️ 尚未配置任何租户模块(所有用户可能默认访问所有模块)');
} else {
const modulesByTenant = {};
tenantModules.forEach(tm => {
if (!modulesByTenant[tm.tenant_id]) {
modulesByTenant[tm.tenant_id] = [];
}
modulesByTenant[tm.tenant_id].push({
module: tm.module_code,
enabled: tm.is_enabled,
expires: tm.expires_at
});
});
Object.entries(modulesByTenant).forEach(([tenantId, modules]) => {
console.log(`\n租户 ${tenantId.substring(0, 8)}...:`);
modules.forEach(m => {
const status = m.enabled ? '✅' : '❌';
const expiry = m.expires ? ` (到期: ${m.expires})` : '';
console.log(` ${status} ${m.module}${expiry}`);
});
});
}
console.log('\n========== 租户列表 (platform_schema.tenants) ==========\n');
const tenants = await prisma.tenants.findMany({
select: {
id: true,
name: true,
code: true,
type: true,
status: true,
}
});
console.table(tenants.map(t => ({
ID: t.id.substring(0, 8) + '...',
名称: t.name,
代码: t.code,
类型: t.type,
状态: t.status,
})));
console.log('\n========== 用户-租户关系 ==========\n');
const usersWithTenant = await prisma.user.findMany({
select: {
phone: true,
name: true,
role: true,
tenant_id: true,
tenants: {
select: {
name: true,
code: true
}
}
},
orderBy: { role: 'asc' }
});
console.table(usersWithTenant.map(u => ({
手机号: u.phone,
姓名: u.name,
角色: u.role,
租户: u.tenants?.name || 'N/A',
租户代码: u.tenants?.code || 'N/A',
})));
}
console.log('\n========== 示范医院详情 ==========\n');
const demoHospital = await prisma.tenants.findFirst({
where: { code: 'demo-hospital' },
include: {
tenant_modules: true,
users: {
select: { id: true, phone: true, name: true, role: true }
}
}
});
if (demoHospital) {
console.log('租户ID:', demoHospital.id);
console.log('名称:', demoHospital.name);
console.log('联系人:', demoHospital.contact_name);
console.log('联系电话:', demoHospital.contact_phone);
console.log('\n已开通模块:');
demoHospital.tenant_modules.forEach(m => {
console.log(` ${m.is_enabled ? '✅' : '❌'} ${m.module_code}`);
});
console.log('\n租户下的用户:');
demoHospital.users.forEach(u => {
console.log(` - ${u.phone} ${u.name} (${u.role})`);
});
}
console.log('\n========== 张主任用户信息 ==========\n');
const zhangUser = await prisma.user.findFirst({
where: { phone: '13800138001' },
include: {
tenants: {
include: {
tenant_modules: true
}
},
tenant_members: {
include: {
tenants: {
include: {
tenant_modules: true
}
}
}
}
}
});
if (zhangUser) {
console.log('用户ID:', zhangUser.id);
console.log('手机号:', zhangUser.phone);
console.log('姓名:', zhangUser.name);
console.log('角色:', zhangUser.role);
console.log('主租户ID:', zhangUser.tenant_id);
console.log('主租户名称:', zhangUser.tenants?.name);
console.log('\n主租户模块配置:');
zhangUser.tenants?.tenant_modules?.forEach(m => {
console.log(` ${m.is_enabled ? '✅' : '❌'} ${m.module_code}`);
});
console.log('\n额外加入的租户:');
zhangUser.tenant_members?.forEach(tm => {
console.log(` - ${tm.tenants.name}`);
tm.tenants.tenant_modules?.forEach(m => {
console.log(` ${m.is_enabled ? '✅' : '❌'} ${m.module_code}`);
});
});
}
}
main()
.catch(console.error)
.finally(() => prisma.$disconnect());

View File

@@ -0,0 +1,116 @@
/**
* 初始化 modules 表数据
*
* 运行: node scripts/seed-modules.js
*/
import { PrismaClient } from '@prisma/client';
const prisma = new PrismaClient();
const MODULES = [
{
code: 'RVW',
name: '智能审稿',
description: '基于AI的稿件自动审查系统支持稿约规范性评估和方法学评估',
icon: 'FileTextOutlined',
is_active: true,
sort_order: 1,
},
{
code: 'PKB',
name: '个人知识库',
description: '个人文档管理与智能检索系统,支持多格式文档上传和语义搜索',
icon: 'BookOutlined',
is_active: true,
sort_order: 2,
},
{
code: 'ASL',
name: '智能文献',
description: '文献筛选与管理系统支持批量导入、AI辅助筛选和全文复筛',
icon: 'ReadOutlined',
is_active: true,
sort_order: 3,
},
{
code: 'DC',
name: '数据清洗',
description: '智能数据清洗与整理工具支持双模型提取和AI辅助数据处理',
icon: 'DatabaseOutlined',
is_active: true,
sort_order: 4,
},
{
code: 'IIT',
name: 'IIT管理',
description: 'IIT项目管理系统支持REDCap集成和项目协作',
icon: 'ProjectOutlined',
is_active: true,
sort_order: 5,
},
{
code: 'AIA',
name: '智能问答',
description: 'AI智能问答助手提供临床研究相关问题的智能解答',
icon: 'RobotOutlined',
is_active: true,
sort_order: 6,
},
{
code: 'SSA',
name: '智能统计分析',
description: 'AI驱动的智能统计分析系统提供高级统计方法和自动化分析',
icon: 'BarChartOutlined',
is_active: true,
sort_order: 7,
},
{
code: 'ST',
name: '统计工具',
description: '统计分析工具集,提供常用统计方法和数据可视化功能',
icon: 'LineChartOutlined',
is_active: true,
sort_order: 8,
},
];
async function main() {
console.log('🚀 开始初始化 modules 表...\n');
for (const module of MODULES) {
const result = await prisma.modules.upsert({
where: { code: module.code },
update: {
name: module.name,
description: module.description,
icon: module.icon,
is_active: module.is_active,
sort_order: module.sort_order,
},
create: module,
});
console.log(`${result.code} - ${result.name}`);
}
console.log('\n========== 当前 modules 表数据 ==========\n');
const allModules = await prisma.modules.findMany({
orderBy: { sort_order: 'asc' },
});
console.table(allModules.map(m => ({
代码: m.code,
名称: m.name,
描述: m.description?.substring(0, 30) + '...',
状态: m.is_active ? '✅ 上线' : '❌ 下线',
排序: m.sort_order,
})));
console.log('\n✨ 初始化完成!');
}
main()
.catch(console.error)
.finally(() => prisma.$disconnect());