/** * 数据库种子脚本 * * 用法: * npx ts-node scripts/seed.ts * * 或在 docker-compose 中启动时执行 * * 环境变量: * DATABASE_URL: 数据库连接字符串 * DB_HOST, DB_PORT, DB_USER, DB_PASSWORD, DB_NAME: 单独配置 */ import { Client } from 'pg'; import * as bcrypt from 'bcrypt'; // 默认租户 ID const DEFAULT_TENANT_ID = '00000000-0000-0000-0000-000000000001'; // 数据库连接配置 function getDbConfig() { if (process.env.DATABASE_URL) { return { connectionString: process.env.DATABASE_URL }; } return { host: process.env.DB_HOST || 'localhost', port: parseInt(process.env.DB_PORT || '5432', 10), user: process.env.DB_USER || 'postgres', password: process.env.DB_PASSWORD || 'postgres', database: process.env.DB_NAME || 'iconsulting', }; } async function seed() { const client = new Client(getDbConfig()); try { await client.connect(); console.log('Connected to database'); // 1. 检查并创建默认租户 await seedTenant(client); // 2. 检查并创建默认管理员 await seedAdmin(client); // 3. 检查并创建默认系统配置 await seedSystemConfigs(client); // 4. 检查并创建默认服务定价 await seedServicePricing(client); console.log('Seed completed successfully!'); } catch (error) { console.error('Seed failed:', error); process.exit(1); } finally { await client.end(); } } async function seedTenant(client: Client) { console.log('Checking tenants table...'); // 检查 tenants 表是否存在 const tableExists = await client.query(` SELECT EXISTS ( SELECT FROM information_schema.tables WHERE table_schema = 'public' AND table_name = 'tenants' ); `); if (!tableExists.rows[0].exists) { console.log(' tenants table does not exist, skipping'); return; } // 检查默认租户是否存在 const tenantExists = await client.query( 'SELECT id FROM tenants WHERE id = $1', [DEFAULT_TENANT_ID] ); if (tenantExists.rows.length === 0) { console.log(' Creating default tenant...'); await client.query(` INSERT INTO tenants (id, name, slug, status, plan) VALUES ($1, 'Default Tenant', 'default', 'ACTIVE', 'ENTERPRISE') `, [DEFAULT_TENANT_ID]); console.log(' Default tenant created'); } else { console.log(' Default tenant already exists'); } } async function seedAdmin(client: Client) { console.log('Checking admins table...'); // 检查 admins 表是否存在 const tableExists = await client.query(` SELECT EXISTS ( SELECT FROM information_schema.tables WHERE table_schema = 'public' AND table_name = 'admins' ); `); if (!tableExists.rows[0].exists) { console.log(' admins table does not exist, skipping'); return; } // 检查默认管理员是否存在 const adminExists = await client.query( "SELECT id FROM admins WHERE username = 'admin'" ); if (adminExists.rows.length === 0) { console.log(' Creating default admin...'); // bcrypt hash for 'admin123' with cost 10 const passwordHash = await bcrypt.hash('admin123', 10); // 检查表结构是否包含 tenant_id 和 is_super_admin const hasNewColumns = await client.query(` SELECT column_name FROM information_schema.columns WHERE table_name = 'admins' AND column_name IN ('tenant_id', 'is_super_admin') `); if (hasNewColumns.rows.length >= 2) { // 新表结构 await client.query(` INSERT INTO admins (tenant_id, is_super_admin, username, password_hash, name, role, permissions) VALUES ($1, TRUE, 'admin', $2, '系统管理员', 'SUPER_ADMIN', '["*"]') `, [DEFAULT_TENANT_ID, passwordHash]); } else { // 旧表结构 await client.query(` INSERT INTO admins (username, password_hash, name, role, permissions) VALUES ('admin', $1, '系统管理员', 'SUPER_ADMIN', '["*"]') `, [passwordHash]); } console.log(' Default admin created (username: admin, password: admin123)'); } else { console.log(' Default admin already exists'); } } async function seedSystemConfigs(client: Client) { console.log('Checking system_configs table...'); // 检查 system_configs 表是否存在 const tableExists = await client.query(` SELECT EXISTS ( SELECT FROM information_schema.tables WHERE table_schema = 'public' AND table_name = 'system_configs' ); `); if (!tableExists.rows[0].exists) { console.log(' system_configs table does not exist, skipping'); return; } const configs = [ { key: 'system_prompt_identity', value: '"专业、友善、耐心的香港移民顾问"', group: 'PROMPT', desc: '系统身份定位' }, { key: 'system_prompt_style', value: '"专业但不生硬,用简洁明了的语言解答"', group: 'PROMPT', desc: '对话风格' }, { key: 'system_prompt_rules', value: '["只回答移民相关问题", "复杂评估建议付费服务", "不做法律承诺"]', group: 'PROMPT', desc: '系统行为规则' }, { key: 'max_free_messages_per_day', value: '50', group: 'SYSTEM', desc: '每日免费消息数限制' }, { key: 'max_conversation_context_messages', value: '20', group: 'SYSTEM', desc: '对话上下文最大消息数' }, { key: 'assessment_price_default', value: '99', group: 'PAYMENT', desc: '默认评估价格(元)' }, { key: 'payment_timeout_minutes', value: '30', group: 'PAYMENT', desc: '支付超时时间(分钟)' }, { key: 'sms_rate_limit_per_hour', value: '5', group: 'SYSTEM', desc: '每小时短信发送限制' }, { key: 'enable_anonymous_chat', value: 'true', group: 'FEATURE', desc: '是否允许匿名用户聊天' }, { key: 'require_phone_for_payment', value: 'true', group: 'FEATURE', desc: '支付时是否要求手机验证' }, ]; for (const config of configs) { const exists = await client.query( 'SELECT key FROM system_configs WHERE key = $1', [config.key] ); if (exists.rows.length === 0) { await client.query(` INSERT INTO system_configs (key, value, config_group, description) VALUES ($1, $2, $3, $4) `, [config.key, config.value, config.group, config.desc]); console.log(` Created config: ${config.key}`); } } console.log(' System configs seeded'); } async function seedServicePricing(client: Client) { console.log('Checking service_pricing table...'); // 检查 service_pricing 表是否存在 const tableExists = await client.query(` SELECT EXISTS ( SELECT FROM information_schema.tables WHERE table_schema = 'public' AND table_name = 'service_pricing' ); `); if (!tableExists.rows[0].exists) { console.log(' service_pricing table does not exist, skipping'); return; } const pricing = [ { type: 'ASSESSMENT', category: 'QMAS', price: 99, originalPrice: 199, desc: '优才计划移民资格评估' }, { type: 'ASSESSMENT', category: 'GEP', price: 99, originalPrice: 199, desc: '专才计划移民资格评估' }, { type: 'ASSESSMENT', category: 'IANG', price: 79, originalPrice: 149, desc: '留学IANG移民资格评估' }, { type: 'ASSESSMENT', category: 'TTPS', price: 99, originalPrice: 199, desc: '高才通移民资格评估' }, { type: 'ASSESSMENT', category: 'CIES', price: 199, originalPrice: 399, desc: '投资移民资格评估' }, { type: 'ASSESSMENT', category: 'TECHTAS', price: 99, originalPrice: 199, desc: '科技人才移民资格评估' }, ]; for (const p of pricing) { const exists = await client.query( 'SELECT id FROM service_pricing WHERE service_type = $1 AND category = $2', [p.type, p.category] ); if (exists.rows.length === 0) { await client.query(` INSERT INTO service_pricing (service_type, category, price, original_price, currency, description) VALUES ($1, $2, $3, $4, 'CNY', $5) `, [p.type, p.category, p.price, p.originalPrice, p.desc]); console.log(` Created pricing: ${p.type} - ${p.category}`); } } console.log(' Service pricing seeded'); } // 运行种子脚本 seed();