2
0
yourname 8 месяцев назад
Родитель
Сommit
928a58182c
2 измененных файлов с 230 добавлено и 0 удалено
  1. 192 0
      src/server/api/init.ts
  2. 38 0
      src/server/utils/env-init.ts

+ 192 - 0
src/server/api/init.ts

@@ -0,0 +1,192 @@
+import { OpenAPIHono, createRoute, z } from '@hono/zod-openapi';
+import { ErrorSchema } from '../middleware/errorHandler';
+import { AppDataSource } from '../data-source';
+import { User } from '../modules/users/user.entity';
+import { Role } from '../modules/users/role.entity';
+import * as bcrypt from 'bcrypt';
+import { generateJwtSecret } from '../utils/env-init';
+import { writeFileSync } from 'fs';
+
+const initRouter = new OpenAPIHono();
+
+// Zod 验证模式
+const DatabaseConfigSchema = z.object({
+  host: z.string(),
+  port: z.number(),
+  username: z.string(),
+  password: z.string(),
+  database: z.string()
+});
+
+const AdminUserSchema = z.object({
+  username: z.string().min(3),
+  password: z.string().min(8)
+});
+
+const InitConfigSchema = z.object({
+  dbConfig: DatabaseConfigSchema,
+  adminUser: AdminUserSchema
+});
+
+// 检查初始化状态路由
+const statusRoute = createRoute({
+  method: 'get',
+  path: '/status',
+  responses: {
+    200: {
+      content: {
+        'application/json': {
+          schema: z.object({
+            initialized: z.boolean()
+          })
+        }
+      },
+      description: '返回系统初始化状态'
+    },
+    500: {
+      description: '初始化状态检查失败',
+      content: {
+        'application/json': {
+          schema: ErrorSchema
+        }
+      }
+    }
+  }
+});
+
+initRouter.openapi(statusRoute, async (c) => {
+  try {
+    const isInitialized = await checkInitialization();
+    return c.json({ initialized: isInitialized }, 200);
+  } catch (error) {
+    return c.json({
+      code: 500,
+      message: '初始化状态检查失败'
+    }, 500);
+  }
+});
+
+// 提交配置路由
+const configRoute = createRoute({
+  method: 'post',
+  path: '/config',
+  request: {
+    body: {
+      content: {
+        'application/json': {
+          schema: InitConfigSchema
+        }
+      }
+    }
+  },
+  responses: {
+    200: {
+      description: '系统初始化成功',
+      content: {
+        'application/json': {
+          schema: z.object({
+            success: z.boolean(),
+            jwtSecret: z.string().optional()
+          })
+        }
+      }
+    },
+    400: {
+      description: '初始化失败',
+      content: {
+        'application/json': {
+          schema: ErrorSchema
+        }
+      }
+    },
+    500: {
+      description: '服务器内部错误',
+      content: {
+        'application/json': {
+          schema: ErrorSchema
+        }
+      }
+    }
+  }
+});
+
+initRouter.openapi(configRoute, async (c) => {
+  const { dbConfig, adminUser } = c.req.valid('json');
+  
+  if (await checkInitialization()) {
+    return c.json({
+      code: 400,
+      message: '系统已初始化'
+    }, 400);
+  }
+
+  try {
+    await validateDatabase(dbConfig);
+    await generateEnvFile(dbConfig);
+    const jwtSecret = generateJwtSecret();
+    await createAdminUser(adminUser);
+    
+    return c.json({
+      success: true,
+      jwtSecret
+    }, 200);
+  } catch (error) {
+    return c.json({
+      code: 400,
+      message: error instanceof Error ? error.message : '未知错误'
+    }, 400);
+  }
+});
+
+export { initRouter };
+
+
+async function checkInitialization(): Promise<boolean> {
+  // 检查数据库是否已初始化
+  return AppDataSource.isInitialized;
+}
+
+async function validateDatabase(config: any) {
+  // 使用TypeORM验证数据库连接
+  const testDataSource = AppDataSource.setOptions(config);
+  await testDataSource.initialize();
+  await testDataSource.destroy();
+}
+
+async function generateEnvFile(config: any) {
+  const envContent = `DB_HOST=${config.host}
+DB_PORT=${config.port}
+DB_USERNAME=${config.username}
+DB_PASSWORD=${config.password}
+DB_DATABASE=${config.database}
+`;
+  writeFileSync('.env', envContent);
+}
+
+async function createAdminUser(userData: any) {
+  const userRepo = AppDataSource.getRepository(User);
+  const roleRepo = AppDataSource.getRepository(Role);
+  
+  // 检查是否已存在管理员
+  const existingAdmin = await userRepo.findOne({ where: { username: userData.username } });
+  if (existingAdmin) {
+    throw new Error('管理员用户已存在');
+  }
+  
+  // 创建管理员角色
+  let adminRole = await roleRepo.findOne({ where: { name: 'admin' } });
+  if (!adminRole) {
+    adminRole = roleRepo.create({ name: 'admin', permissions: ['*'] });
+    await roleRepo.save(adminRole);
+  }
+  
+  // 创建管理员用户
+  const hashedPassword = await bcrypt.hash(userData.password, 10);
+  const adminUser = userRepo.create({
+    username: userData.username,
+    password: hashedPassword,
+    roles: [adminRole]
+  });
+  
+  await userRepo.save(adminUser);
+}

+ 38 - 0
src/server/utils/env-init.ts

@@ -0,0 +1,38 @@
+import { randomBytes } from 'crypto';
+import { writeFileSync, existsSync } from 'fs';
+
+export function generateJwtSecret(): string {
+  if (existsSync('.env')) {
+    const envContent = require('fs').readFileSync('.env', 'utf-8');
+    if (envContent.includes('JWT_SECRET')) {
+      return 'JWT_SECRET已存在';
+    }
+  }
+  
+  const secret = randomBytes(64).toString('hex');
+  const envLine = `JWT_SECRET=${secret}\n`;
+  
+  if (existsSync('.env')) {
+    require('fs').appendFileSync('.env', envLine);
+  } else {
+    writeFileSync('.env', envLine);
+  }
+  
+  return secret;
+}
+
+export function checkRequiredEnvVars(): boolean {
+  const requiredVars = [
+    'DB_HOST',
+    'DB_PORT', 
+    'DB_USERNAME',
+    'DB_PASSWORD',
+    'DB_DATABASE',
+    'JWT_SECRET'
+  ];
+  
+  if (!existsSync('.env')) return false;
+  
+  const envContent = require('fs').readFileSync('.env', 'utf-8');
+  return requiredVars.every(varName => envContent.includes(varName));
+}