Files
AIclinicalresearch/backend/prisma/seed.ts
HaHafeng 971e903acf chore(deploy): finalize 0309 SAE rollout updates
Sync deployment documentation to the final successful SAE state and clear pending deployment checklist items. Include backend/frontend/R hardening and diagnostics improvements required for stable production behavior.

Made-with: Cursor
2026-03-09 22:27:11 +08:00

495 lines
18 KiB
TypeScript
Raw Blame History

This file contains invisible Unicode characters
This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
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.
import { PrismaClient, UserRole, TenantType, TenantStatus } from '@prisma/client';
import bcrypt from 'bcryptjs';
import { v4 as uuidv4 } from 'uuid';
const prisma = new PrismaClient();
// 默认密码
const DEFAULT_PASSWORD = '123456';
async function main() {
console.log('🌱 开始创建种子数据...\n');
// 加密默认密码
const hashedDefaultPassword = await bcrypt.hash(DEFAULT_PASSWORD, 10);
// ============================================
// 1. 创建内部租户(运营团队专用)
// ============================================
console.log('📌 创建内部租户...');
const internalTenant = await prisma.tenants.upsert({
where: { code: 'yizhengxun' },
update: {},
create: {
id: uuidv4(),
code: 'yizhengxun',
name: '壹证循科技',
type: TenantType.INTERNAL,
status: TenantStatus.ACTIVE,
config: {
logo: null,
backgroundImage: null,
primaryColor: '#1890ff',
systemName: 'AI临床研究平台 - 运营管理端',
},
total_quota: BigInt(999999999),
updated_at: new Date(),
},
});
console.log(` ✅ 内部租户创建成功: ${internalTenant.name}`);
// 为内部租户开放所有模块(超级管理员完整权限)
const internalModules = ['AIA', 'ASL', 'PKB', 'DC', 'SSA', 'ST', 'RVW', 'IIT', 'RM', 'AIA_PROTOCOL'];
for (const moduleCode of internalModules) {
await prisma.tenant_modules.upsert({
where: { tenant_id_module_code: { tenant_id: internalTenant.id, module_code: moduleCode } },
update: {},
create: {
id: uuidv4(),
tenant_id: internalTenant.id,
module_code: moduleCode,
is_enabled: true,
},
});
}
console.log(` ✅ 内部租户模块订阅: ${internalModules.join(', ')}`);
// ============================================
// 1.5 创建公共租户(个人用户池)
// ============================================
console.log('📌 创建公共租户(个人用户)...');
const publicTenant = await prisma.tenants.upsert({
where: { code: 'public' },
update: {},
create: {
id: uuidv4(),
code: 'public',
name: '个人用户',
type: TenantType.PUBLIC,
status: TenantStatus.ACTIVE,
config: {
logo: null,
backgroundImage: null,
primaryColor: '#1890ff',
systemName: 'AI临床研究平台',
},
total_quota: BigInt(100000),
updated_at: new Date(),
},
});
console.log(` ✅ 公共租户创建成功: ${publicTenant.name}`);
// 为公共租户开放部分模块
const publicModules = ['PKB', 'RVW'];
for (const moduleCode of publicModules) {
await prisma.tenant_modules.upsert({
where: { tenant_id_module_code: { tenant_id: publicTenant.id, module_code: moduleCode } },
update: {},
create: {
id: uuidv4(),
tenant_id: publicTenant.id,
module_code: moduleCode,
is_enabled: true,
},
});
}
console.log(` ✅ 公共租户模块订阅: ${publicModules.join(', ')}`);
// ============================================
// 2. 创建超级管理员
// ============================================
console.log('📌 创建超级管理员...');
const superAdmin = await prisma.user.upsert({
where: { phone: '13800000001' },
update: {},
create: {
phone: '13800000001',
password: hashedDefaultPassword,
is_default_password: false,
name: '超级管理员',
tenant_id: internalTenant.id,
role: UserRole.SUPER_ADMIN,
status: 'active',
isTrial: false,
},
});
console.log(` ✅ 超级管理员创建成功: ${superAdmin.phone}`);
// ============================================
// 3. 创建Prompt工程师账号
// ============================================
console.log('📌 创建Prompt工程师账号...');
const promptEngineer = await prisma.user.upsert({
where: { phone: '13800000002' },
update: {},
create: {
phone: '13800000002',
password: hashedDefaultPassword,
is_default_password: false,
name: 'Prompt工程师',
tenant_id: internalTenant.id,
role: UserRole.PROMPT_ENGINEER,
status: 'active',
isTrial: false,
},
});
console.log(` ✅ Prompt工程师创建成功: ${promptEngineer.phone}`);
// ============================================
// 4. 创建示例租户(医院)
// ============================================
console.log('📌 创建示例租户(医院)...');
const hospitalTenant = await prisma.tenants.upsert({
where: { code: 'demo-hospital' },
update: {},
create: {
id: uuidv4(),
code: 'demo-hospital',
name: '示范医院',
type: TenantType.HOSPITAL,
status: TenantStatus.ACTIVE,
config: {
logo: null,
backgroundImage: null,
primaryColor: '#1890ff',
systemName: '示范医院临床研究平台',
},
total_quota: BigInt(1000000),
contact_name: '张主任',
contact_phone: '13800138000',
contact_email: 'zhang@demo-hospital.com',
updated_at: new Date(),
},
});
console.log(` ✅ 示例医院租户创建成功: ${hospitalTenant.name}`);
// ============================================
// 5. 创建医院科室
// ============================================
console.log('📌 创建医院科室...');
const cardiology = await prisma.departments.upsert({
where: { id: 'dept-cardiology' },
update: {},
create: {
id: 'dept-cardiology',
tenant_id: hospitalTenant.id,
name: '心内科',
description: '心血管内科',
updated_at: new Date(),
},
});
const neurology = await prisma.departments.upsert({
where: { id: 'dept-neurology' },
update: {},
create: {
id: 'dept-neurology',
tenant_id: hospitalTenant.id,
name: '神经内科',
description: '神经内科',
updated_at: new Date(),
},
});
console.log(` ✅ 科室创建成功: 心内科、神经内科`);
// ============================================
// 6. 创建示例租户(药企)
// ============================================
console.log('📌 创建示例租户(药企)...');
const pharmaTenant = await prisma.tenants.upsert({
where: { code: 'demo-pharma' },
update: {},
create: {
id: uuidv4(),
code: 'demo-pharma',
name: '示范药企',
type: TenantType.PHARMA,
status: TenantStatus.ACTIVE,
config: {
logo: null,
backgroundImage: null,
primaryColor: '#52c41a',
systemName: '示范药企IIT管理平台',
},
total_quota: BigInt(2000000),
contact_name: '李经理',
contact_phone: '13900139000',
contact_email: 'li@demo-pharma.com',
updated_at: new Date(),
},
});
console.log(` ✅ 示例药企租户创建成功: ${pharmaTenant.name}`);
// ============================================
// 7. 创建医院管理员
// ============================================
console.log('📌 创建医院管理员...');
const hospitalAdmin = await prisma.user.upsert({
where: { phone: '13800138001' },
update: {},
create: {
phone: '13800138001',
password: hashedDefaultPassword,
is_default_password: true,
name: '张主任',
tenant_id: hospitalTenant.id,
department_id: cardiology.id,
role: UserRole.HOSPITAL_ADMIN,
status: 'active',
isTrial: false,
},
});
console.log(` ✅ 医院管理员创建成功: ${hospitalAdmin.phone} (${hospitalAdmin.name})`);
// 创建租户成员关系
await prisma.tenant_members.upsert({
where: { tenant_id_user_id: { tenant_id: hospitalTenant.id, user_id: hospitalAdmin.id } },
update: {},
create: {
id: uuidv4(),
tenant_id: hospitalTenant.id,
user_id: hospitalAdmin.id,
role: UserRole.HOSPITAL_ADMIN,
},
});
// ============================================
// 8. 创建普通医生用户
// ============================================
console.log('📌 创建普通医生用户...');
const doctor1 = await prisma.user.upsert({
where: { phone: '13800138002' },
update: {},
create: {
phone: '13800138002',
password: hashedDefaultPassword,
is_default_password: true,
name: '李医生',
tenant_id: hospitalTenant.id,
department_id: cardiology.id,
role: UserRole.USER,
status: 'active',
isTrial: false,
},
});
const doctor2 = await prisma.user.upsert({
where: { phone: '13800138003' },
update: {},
create: {
phone: '13800138003',
password: hashedDefaultPassword,
is_default_password: true,
name: '王医生',
tenant_id: hospitalTenant.id,
department_id: neurology.id,
role: UserRole.USER,
status: 'active',
isTrial: false,
},
});
console.log(` ✅ 普通用户创建成功: ${doctor1.name}${doctor2.name}`);
// 创建租户成员关系
for (const user of [doctor1, doctor2]) {
await prisma.tenant_members.upsert({
where: { tenant_id_user_id: { tenant_id: hospitalTenant.id, user_id: user.id } },
update: {},
create: {
id: uuidv4(),
tenant_id: hospitalTenant.id,
user_id: user.id,
role: UserRole.USER,
},
});
}
// ============================================
// 9. 创建权限数据
// ============================================
console.log('📌 创建权限数据...');
const permissionsData = [
// Prompt管理权限
{ code: 'prompt:view', name: '查看Prompt', description: '查看Prompt列表和历史版本', module: 'prompt' },
{ code: 'prompt:edit', name: '编辑Prompt', description: '创建/修改DRAFT版本', module: 'prompt' },
{ code: 'prompt:debug', name: '调试Prompt', description: '开启调试模式(核心权限)', module: 'prompt' },
{ code: 'prompt:publish', name: '发布Prompt', description: '发布DRAFT为ACTIVE', module: 'prompt' },
// 租户管理权限
{ code: 'tenant:view', name: '查看租户', description: '查看租户列表', module: 'tenant' },
{ code: 'tenant:create', name: '创建租户', description: '创建新租户', module: 'tenant' },
{ code: 'tenant:edit', name: '编辑租户', description: '编辑租户信息', module: 'tenant' },
{ code: 'tenant:delete', name: '删除租户', description: '删除租户', module: 'tenant' },
// 用户管理权限
{ code: 'user:view', name: '查看用户', description: '查看用户列表', module: 'user' },
{ code: 'user:create', name: '创建用户', description: '创建新用户', module: 'user' },
{ code: 'user:edit', name: '编辑用户', description: '编辑用户信息', module: 'user' },
{ code: 'user:delete', name: '删除用户', description: '删除用户', module: 'user' },
// 配额管理权限
{ code: 'quota:view', name: '查看配额', description: '查看配额使用情况', module: 'quota' },
{ code: 'quota:allocate', name: '分配配额', description: '分配配额给科室/用户', module: 'quota' },
// 审计日志权限
{ code: 'audit:view', name: '查看审计日志', description: '查看操作审计日志', module: 'audit' },
// 用户运营权限(可访问租户管理/用户管理/运营日志)
{ code: 'ops:user-ops', name: '用户运营', description: '运营管理端用户运营视图权限', module: 'ops' },
];
for (const perm of permissionsData) {
await prisma.permissions.upsert({
where: { code: perm.code },
update: {},
create: perm,
});
}
console.log(`${permissionsData.length} 个权限创建成功`);
// ============================================
// 10. 创建角色-权限关联
// ============================================
console.log('📌 创建角色-权限关联...');
const allPermissions = await prisma.permissions.findMany();
const permissionMap = new Map(allPermissions.map(p => [p.code, p.id]));
// SUPER_ADMIN 拥有所有权限
for (const perm of allPermissions) {
await prisma.role_permissions.upsert({
where: { role_permission_id: { role: UserRole.SUPER_ADMIN, permission_id: perm.id } },
update: {},
create: {
role: UserRole.SUPER_ADMIN,
permission_id: perm.id,
},
});
}
// PROMPT_ENGINEER 拥有Prompt相关权限
const promptPermCodes = ['prompt:view', 'prompt:edit', 'prompt:debug', 'prompt:publish'];
for (const code of promptPermCodes) {
const permId = permissionMap.get(code);
if (permId) {
await prisma.role_permissions.upsert({
where: { role_permission_id: { role: UserRole.PROMPT_ENGINEER, permission_id: permId } },
update: {},
create: {
role: UserRole.PROMPT_ENGINEER,
permission_id: permId,
},
});
}
}
// HOSPITAL_ADMIN 拥有用户、配额、审计权限
const hospitalAdminPermCodes = ['user:view', 'user:create', 'user:edit', 'quota:view', 'quota:allocate', 'audit:view'];
for (const code of hospitalAdminPermCodes) {
const permId = permissionMap.get(code);
if (permId) {
await prisma.role_permissions.upsert({
where: { role_permission_id: { role: UserRole.HOSPITAL_ADMIN, permission_id: permId } },
update: {},
create: {
role: UserRole.HOSPITAL_ADMIN,
permission_id: permId,
},
});
}
}
console.log(' ✅ 角色-权限关联创建成功');
// ============================================
// 11. 跳过Prompt模板表尚未创建
// ============================================
console.log('📌 跳过Prompt模板创建capability_schema.prompt_templates 尚未创建)');
console.log('💡 提示Protocol Agent 配置请运行独立脚本: npx tsx prisma/seed-protocol-agent.ts');
// ============================================
// 12. 创建租户模块订阅
// ============================================
console.log('📌 创建租户模块订阅...');
const hospitalModules = ['ASL', 'DC', 'PKB', 'AIA', 'RVW'];
for (const moduleCode of hospitalModules) {
await prisma.tenant_modules.upsert({
where: { tenant_id_module_code: { tenant_id: hospitalTenant.id, module_code: moduleCode } },
update: {},
create: {
id: uuidv4(),
tenant_id: hospitalTenant.id,
module_code: moduleCode,
is_enabled: true,
},
});
}
// 药企只订阅IIT和DC
const pharmaModules = ['IIT', 'DC'];
for (const moduleCode of pharmaModules) {
await prisma.tenant_modules.upsert({
where: { tenant_id_module_code: { tenant_id: pharmaTenant.id, module_code: moduleCode } },
update: {},
create: {
id: uuidv4(),
tenant_id: pharmaTenant.id,
module_code: moduleCode,
is_enabled: true,
},
});
}
console.log(' ✅ 租户模块订阅创建成功');
// ============================================
// 完成
// ============================================
console.log('\n🎉 种子数据创建完成!\n');
console.log('╔════════════════════════════════════════════════════════════╗');
console.log('║ 📝 测试账号信息 ║');
console.log('╠════════════════════════════════════════════════════════════╣');
console.log('║ 🔐 登录方式1手机号 + 验证码 ║');
console.log('║ 🔐 登录方式2手机号 + 密码 ║');
console.log('╠════════════════════════════════════════════════════════════╣');
console.log('║ 【运营管理员】 ║');
console.log('║ 超级管理员: 13800000001 / 123456 ║');
console.log('║ Prompt工程师: 13800000002 / 123456 ║');
console.log('╠════════════════════════════════════════════════════════════╣');
console.log('║ 【医院端 - demo-hospital】 ║');
console.log('║ 医院管理员: 13800138001 / 123456 (张主任) ║');
console.log('║ 普通医生: 13800138002 / 123456 (李医生·心内科) ║');
console.log('║ 普通医生: 13800138003 / 123456 (王医生·神内科) ║');
console.log('╠════════════════════════════════════════════════════════════╣');
console.log('║ 【药企端 - demo-pharma】 ║');
console.log('║ (暂无用户,可通过管理端添加) ║');
console.log('╠════════════════════════════════════════════════════════════╣');
console.log('║ 【个人用户 - public】 ║');
console.log('║ 通用登录入口: /login ║');
console.log('║ 可用模块: PKB, RVW ║');
console.log('╚════════════════════════════════════════════════════════════╝');
console.log('\n📌 租户专属登录URL');
console.log(' - 通用登录: /login');
console.log(' - 医院端: /t/demo-hospital/login');
console.log(' - 药企端: /t/demo-pharma/login');
console.log('\n⚠ 提示:普通用户首次登录会提示修改默认密码');
}
main()
.then(async () => {
await prisma.$disconnect();
})
.catch(async (e) => {
console.error('❌ 种子数据创建失败:', e);
await prisma.$disconnect();
process.exit(1);
});