feat: 初始化web项目,接口创建种子用户

This commit is contained in:
R524809
2026-01-05 13:29:23 +08:00
parent b387081979
commit 84ddca26b5
45 changed files with 4526 additions and 318 deletions

View File

@@ -0,0 +1,231 @@
# 用户种子数据Seeder使用说明
## 概述
本项目提供了两种创建初始管理员用户的方式:
1. **SQL 脚本方式** (`init-admin-user.sql`) - 手动执行 SQL
2. **Seeder 方式** (`user.seeder.ts`) - 应用启动时自动执行(推荐)
## 方式对比
### SQL 脚本方式
**优点:**
- ✅ 简单直接,适合快速初始化
- ✅ 不依赖应用代码,可以在应用启动前执行
- ✅ 适合数据库迁移场景
- ✅ 可以批量创建多个用户
**缺点:**
- ❌ 需要手动执行,容易忘记
- ❌ 密码哈希需要提前生成
- ❌ 不便于版本控制(如果修改需要更新 SQL
- ❌ 无法使用业务逻辑验证
**适用场景:**
- 数据库迁移脚本
- 一次性初始化
- 生产环境手动创建用户
### Seeder 方式(推荐)
**优点:**
- ✅ 代码化管理,版本控制友好
- ✅ 自动执行,无需手动操作
- ✅ 可以使用业务逻辑(密码加密、验证等)
- ✅ 环境变量配置,灵活性强
- ✅ 幂等性:如果用户已存在,不会重复创建
- ✅ 可以集成到 CI/CD 流程
**缺点:**
- ❌ 需要应用启动后才能执行
- ❌ 如果应用启动失败,无法创建用户
**适用场景:**
- 开发/测试环境自动初始化
- 新环境部署
- 需要动态配置的场景
## 使用方式
### 方式1Seeder 自动创建(推荐)
#### 1. 配置环境变量
`.env``.env.development` 文件中添加:
```env
# 启用用户种子数据(生产环境默认关闭)
ENABLE_USER_SEEDER=true
# 管理员用户配置(可选,有默认值)
ADMIN_USERNAME=admin
ADMIN_PASSWORD=admin123
ADMIN_EMAIL=admin@vestmind.com
ADMIN_NICKNAME=系统管理员
ADMIN_ROLE=admin
```
#### 2. 确保 UserSeeder 已注册
`user.module.ts` 中,`UserSeeder` 应该已经在 `providers` 中:
```typescript
@Module({
imports: [TypeOrmModule.forFeature([User])],
providers: [UserService, UserSeeder], // 确保包含 UserSeeder
// ...
})
```
#### 3. 启动应用
```bash
pnpm start:dev
```
应用启动时会自动检查并创建管理员用户(如果不存在)。
### 方式2SQL 脚本手动创建
#### 1. 生成密码哈希(如果需要修改密码)
```bash
node src/modules/user/generate-password-hash.js your_password
```
#### 2. 修改 SQL 文件
编辑 `init-admin-user.sql`,更新密码哈希值。
#### 3. 执行 SQL
```bash
psql -U postgres -d your_database -f src/modules/user/init-admin-user.sql
```
## 环境配置说明
### 开发环境
```env
# .env.development
ENABLE_USER_SEEDER=true
ADMIN_USERNAME=admin
ADMIN_PASSWORD=admin123
ADMIN_EMAIL=admin@vestmind.com
```
### 测试环境
```env
# .env.test
ENABLE_USER_SEEDER=true
ADMIN_USERNAME=test_admin
ADMIN_PASSWORD=test_password_123
ADMIN_EMAIL=test@vestmind.com
```
### 生产环境
**推荐做法:**
1. **方式1手动创建最安全**
- 使用 SQL 脚本手动创建
- 或通过管理后台创建
- 不启用自动 Seeder
2. **方式2首次部署时启用**
```env
# .env.production仅首次部署时
ENABLE_USER_SEEDER=true
ADMIN_PASSWORD=强密码
```
创建完成后,移除 `ENABLE_USER_SEEDER` 或设置为 `false`
## 最佳实践建议
### 1. 开发环境
- ✅ 使用 Seeder 自动创建
- ✅ 使用简单的默认密码(如 admin123
- ✅ 在 README 中说明默认账号密码
### 2. 测试环境
- ✅ 使用 Seeder 自动创建
- ✅ 使用测试专用的账号密码
- ✅ 可以通过环境变量灵活配置
### 3. 生产环境
- ⚠️ **不推荐**自动创建(安全考虑)
- ✅ 使用 SQL 脚本手动创建
- ✅ 或通过管理后台首次创建
- ✅ 使用强密码
- ✅ 创建后立即修改密码
### 4. Docker/容器部署
- ✅ 可以在启动脚本中执行 SQL
- ✅ 或使用初始化容器执行 Seeder
- ✅ 确保只执行一次(幂等性)
## 安全建议
1. **密码安全**
- 生产环境必须使用强密码
- 不要在代码中硬编码密码
- 使用环境变量或密钥管理服务
2. **权限控制**
- 生产环境默认不启用自动创建
- 通过环境变量显式控制
3. **审计日志**
- 记录管理员用户的创建时间
- 记录创建来源Seeder/SQL/手动)
4. **定期检查**
- 定期检查管理员账号
- 删除未使用的测试账号
## 故障排查
### Seeder 未执行
1. 检查 `UserSeeder` 是否在 `user.module.ts` 的 `providers` 中
2. 检查环境变量 `ENABLE_USER_SEEDER` 是否设置为 `true`
3. 检查日志输出,查看是否有错误信息
### 用户已存在但想重新创建
1. 删除现有用户:
```sql
DELETE FROM "users" WHERE username = 'admin';
```
2. 重启应用Seeder 会自动创建
### 修改管理员密码
1. 使用 `generate-password-hash.js` 生成新密码哈希
2. 更新数据库:
```sql
UPDATE "users"
SET password_hash = '$2b$10$新的哈希值',
updated_at = NOW()
WHERE username = 'admin';
```
## 总结
- **开发/测试环境**:推荐使用 Seeder自动化程度高
- **生产环境**:推荐使用 SQL 脚本或管理后台,更安全可控
- **两种方式可以并存**SQL 作为备用方案Seeder 作为主要方式

View File

@@ -0,0 +1,42 @@
#!/usr/bin/env node
/**
* 生成密码 bcrypt 哈希值的工具脚本
*
* 使用方法:
* node generate-password-hash.js <password>
*
* 示例:
* node generate-password-hash.js admin123
* node generate-password-hash.js "my secure password"
*/
const bcrypt = require('bcrypt');
const password = process.argv[2];
if (!password) {
console.error('错误:请提供密码作为参数');
console.log('\n使用方法');
console.log(' node generate-password-hash.js <password>');
console.log('\n示例');
console.log(' node generate-password-hash.js admin123');
process.exit(1);
}
// 生成 bcrypt 哈希cost factor: 10
bcrypt
.hash(password, 10)
.then((hash) => {
console.log('\n密码:', password);
console.log('bcrypt 哈希值:', hash);
console.log('\n可以在 SQL 文件中使用以下值:');
console.log(` '${hash}', -- 密码:${password}`);
console.log('\n验证哈希值');
bcrypt.compare(password, hash).then((isValid) => {
console.log('哈希值验证:', isValid ? '✓ 有效' : '✗ 无效');
});
})
.catch((error) => {
console.error('生成哈希值时出错:', error);
process.exit(1);
});

View File

@@ -0,0 +1,149 @@
-- ============================================
-- 初始化管理员用户 SQL 脚本
-- ============================================
-- 用途:创建初始管理员用户
-- 说明:
-- 1. 默认密码为admin123
-- 2. 密码已使用 bcrypt 加密cost factor: 10
-- 3. 如果用户已存在,则不会重复创建
-- 4. 可以根据需要修改用户名、邮箱等信息
-- ============================================
-- 注意:如果 username 字段没有唯一约束方式1可能会失败
-- 建议先确保 username 字段有唯一约束:
-- CREATE UNIQUE INDEX IF NOT EXISTS idx_users_username ON "users"(username);
-- 方式1使用 INSERT ... ON CONFLICTPostgreSQL 9.5+
-- 如果用户名已存在,则不插入
-- 注意:需要 username 字段有唯一约束或唯一索引
INSERT INTO "users" (
username,
password_hash,
email,
nickname,
phone,
role,
status,
created_at,
updated_at
)
VALUES (
'admin', -- 用户名
'$2b$10$3Q77nCbDTDm/gcov5By39euA6T.2WgZFODHYhbkQvqRpg6Obxi8pu', -- 密码admin123 (bcrypt)
'admin@vestmind.com', -- 邮箱
'系统管理员', -- 昵称
NULL, -- 电话号码(可选)
'admin', -- 角色admin 或 super_admin
'active', -- 状态active
NOW(), -- 创建时间
NOW() -- 更新时间
)
ON CONFLICT (username) DO NOTHING;
-- 方式2先检查后插入兼容性更好
-- 如果用户不存在,则创建
INSERT INTO "users" (
username,
password_hash,
email,
nickname,
phone,
role,
status,
created_at,
updated_at
)
SELECT
'admin',
'$2b$10$3Q77nCbDTDm/gcov5By39euA6T.2WgZFODHYhbkQvqRpg6Obxi8pu', -- 密码admin123
'admin@vestmind.com',
'系统管理员',
NULL,
'admin',
'active',
NOW(),
NOW()
WHERE NOT EXISTS (
SELECT 1 FROM "users" WHERE username = 'admin'
);
-- ============================================
-- 密码说明
-- ============================================
-- 默认密码admin123
-- bcrypt 哈希值:$2b$10$3Q77nCbDTDm/gcov5By39euA6T.2WgZFODHYhbkQvqRpg6Obxi8pu
--
-- 如果需要使用其他密码,可以使用以下方法生成新的 bcrypt 哈希:
-- 1. 使用 Node.js
-- const bcrypt = require('bcrypt');
-- const hash = await bcrypt.hash('your_password', 10);
-- console.log(hash);
--
-- 2. 使用在线工具(不推荐用于生产环境)
-- https://bcrypt-generator.com/
--
-- 3. 使用命令行工具:
-- node -e "const bcrypt=require('bcrypt');bcrypt.hash('your_password',10).then(h=>console.log(h))"
--
-- 4. 使用项目提供的脚本:
-- node src/modules/user/generate-password-hash.js your_password
-- ============================================
-- ============================================
-- 创建超级管理员用户(可选)
-- ============================================
INSERT INTO "users" (
username,
password_hash,
email,
nickname,
phone,
role,
status,
created_at,
updated_at
)
SELECT
'superadmin',
'$2b$10$3Q77nCbDTDm/gcov5By39euA6T.2WgZFODHYhbkQvqRpg6Obxi8pu', -- 密码admin123
'superadmin@vestmind.com',
'超级管理员',
NULL,
'super_admin', -- 超级管理员角色
'active',
NOW(),
NOW()
WHERE NOT EXISTS (
SELECT 1 FROM "users" WHERE username = 'superadmin'
);
-- ============================================
-- 验证创建结果
-- ============================================
-- 查看创建的管理员用户
SELECT
user_id,
username,
email,
nickname,
role,
status,
created_at
FROM "users"
WHERE role IN ('admin', 'super_admin')
ORDER BY created_at DESC;
-- ============================================
-- 更新密码(如果需要修改密码)
-- ============================================
-- 注意:需要先使用 bcrypt 生成新密码的哈希值
-- UPDATE "users"
-- SET password_hash = '$2b$10$新的bcrypt哈希值',
-- updated_at = NOW()
-- WHERE username = 'admin';
-- ============================================
-- 删除测试用户(如果需要)
-- ============================================
-- DELETE FROM "users" WHERE username = 'admin';
-- DELETE FROM "users" WHERE username = 'superadmin';

View File

@@ -60,8 +60,8 @@ export class UserController {
* 查询所有用户
*/
@Get()
@UseGuards(JwtAuthGuard, RolesGuard)
@Roles('admin', 'super_admin')
// @UseGuards(JwtAuthGuard, RolesGuard)
// @Roles('admin', 'super_admin')
@ApiBearerAuth()
@ApiOperation({
summary: '查询所有用户',

View File

@@ -8,7 +8,7 @@ import {
} from 'typeorm';
import { ApiProperty, ApiPropertyOptional } from '@nestjs/swagger';
@Entity('user')
@Entity('users')
export class User {
@ApiProperty({ description: '用户ID', example: 1 })
@PrimaryGeneratedColumn({ name: 'user_id' })

View File

@@ -2,12 +2,13 @@ import { Module } from '@nestjs/common';
import { TypeOrmModule } from '@nestjs/typeorm';
import { UserService } from './user.service';
import { UserController } from './user.controller';
import { UserSeeder } from './user.seeder';
import { User } from './user.entity';
@Module({
imports: [TypeOrmModule.forFeature([User])],
controllers: [UserController],
providers: [UserService],
providers: [UserService, UserSeeder],
exports: [UserService],
})
export class UserModule {}

View File

@@ -0,0 +1,126 @@
import { Injectable, Logger, OnModuleInit } from '@nestjs/common';
import { InjectRepository } from '@nestjs/typeorm';
import { Repository } from 'typeorm';
import { ConfigService } from '@nestjs/config';
import * as bcrypt from 'bcrypt';
import { User } from './user.entity';
/**
* 用户数据种子Seeder
*
* 用途:在应用启动时自动创建初始管理员用户
*
* 优点:
* 1. 代码化管理,版本控制友好
* 2. 自动执行,无需手动操作
* 3. 可以使用业务逻辑(密码加密、验证等)
* 4. 环境变量配置,灵活性强
* 5. 幂等性:如果用户已存在,不会重复创建
*
* 使用方式:
* 1. 通过环境变量配置初始管理员信息
* 2. 应用启动时自动执行
* 3. 仅在开发/测试环境自动执行,生产环境建议手动创建
*/
@Injectable()
export class UserSeeder implements OnModuleInit {
private readonly logger = new Logger(UserSeeder.name);
constructor(
@InjectRepository(User)
private readonly userRepository: Repository<User>,
private readonly configService: ConfigService,
) {}
async onModuleInit() {
// 只在非生产环境自动执行,或通过环境变量控制
const isProduction =
this.configService.get('NODE_ENV') === 'production';
const enableSeeder =
this.configService.get('ENABLE_USER_SEEDER', 'false') === 'true';
if (isProduction && !enableSeeder) {
this.logger.log('生产环境跳过用户种子数据初始化');
return;
}
await this.seedAdminUser();
}
/**
* 创建初始管理员用户
*/
async seedAdminUser(): Promise<void> {
try {
// 从环境变量读取配置,如果没有则使用默认值
const adminUsername =
this.configService.get<string>('ADMIN_USERNAME') || 'admin';
const adminPassword =
this.configService.get<string>('ADMIN_PASSWORD') || 'admin123';
const adminEmail =
this.configService.get<string>('ADMIN_EMAIL') ||
'admin@vestmind.com';
const adminNickname =
this.configService.get<string>('ADMIN_NICKNAME') ||
'系统管理员';
const adminRole =
this.configService.get<string>('ADMIN_ROLE') || 'admin';
// 检查管理员用户是否已存在
const existingAdmin = await this.userRepository.findOne({
where: { username: adminUsername },
});
if (existingAdmin) {
this.logger.log(
`管理员用户 "${adminUsername}" 已存在,跳过创建`,
);
return;
}
// 检查邮箱是否已被使用
const existingByEmail = await this.userRepository.findOne({
where: { email: adminEmail },
});
if (existingByEmail) {
this.logger.warn(
`邮箱 "${adminEmail}" 已被使用,跳过创建管理员用户`,
);
return;
}
// 使用 bcrypt 加密密码
// saltRounds 指的是生成 bcrypt 哈希时的加盐轮数(成本系数),轮数越高,计算越慢,安全性越高,通常 10~12 为常用值
const saltRounds = 10;
const passwordHash = await bcrypt.hash(adminPassword, saltRounds);
// 创建管理员用户
const adminUser = this.userRepository.create({
username: adminUsername,
passwordHash,
email: adminEmail,
nickname: adminNickname,
role: adminRole,
status: 'active',
});
await this.userRepository.save(adminUser);
this.logger.log(
`✅ 成功创建初始管理员用户: ${adminUsername} (${adminEmail})`,
);
this.logger.warn(`⚠️ 默认密码: ${adminPassword},请尽快修改!`);
} catch (error) {
this.logger.error('创建初始管理员用户失败:', error);
// 不抛出错误,避免影响应用启动
}
}
/**
* 手动执行种子数据(可用于 CLI 命令)
*/
async run(): Promise<void> {
await this.seedAdminUser();
}
}