Files
huang 9c399df6bc feat(auth): 新增系统启动时自动初始化默认超级管理员功能
- 新增默认管理员自动初始化逻辑,系统启动时检查并创建超级管理员账号
- 支持通过配置文件自定义账号信息(优先级:配置文件 > 代码默认值)
- 新增 CreateSystemAccount 方法用于系统内部账号创建
- 新增默认管理员配置项和常量定义
- 更新 README.md 添加默认账号使用说明
- 归档 OpenSpec 变更提案及完整文档

相关文件:
- internal/bootstrap/admin.go: 管理员初始化逻辑
- internal/service/account/service.go: 系统账号创建方法
- pkg/config/config.go: 默认管理员配置结构
- pkg/constants/constants.go: 默认值常量定义
- docs/add-default-admin-init/功能说明.md: 完整功能文档
2026-01-14 10:53:42 +08:00

329 lines
8.8 KiB
Markdown
Raw Permalink Blame History

This file contains ambiguous Unicode characters
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.
# 默认超级管理员自动初始化功能
## 功能概述
系统在 API 服务启动时,自动检查数据库中是否存在超级管理员账号。如果不存在,则自动创建一个默认的超级管理员账号,确保系统首次部署后可以立即登录使用。
## 业务背景
**问题**
- 首次部署新环境(开发、测试、生产)时,数据库中没有任何管理员账号
- 无法登录管理后台,需要手动执行 SQL 或脚本创建管理员
- 人为操作容易出错,不同环境的初始账号可能不一致
**解决方案**
- 系统启动时自动检测并创建默认超级管理员
- 支持配置文件自定义账号信息
- 幂等操作,多次启动不会重复创建
## 技术实现
### 核心逻辑
#### 1. 初始化时机
```go
// internal/bootstrap/bootstrap.go
func Bootstrap(deps *Dependencies) (*BootstrapResult, error) {
// ... 其他初始化 ...
// 4. 初始化默认超级管理员(降级处理:失败不中断启动)
if err := initDefaultAdmin(deps, services); err != nil {
deps.Logger.Error("初始化默认超级管理员失败", zap.Error(err))
}
// ... 继续初始化 ...
}
```
**初始化顺序**
1. Store 层初始化
2. GORM Callbacks 注册
3. Service 层初始化
4. **默认管理员初始化** ← 在此执行
5. Middleware 层初始化
6. Handler 层初始化
#### 2. 检查逻辑
```go
// 检查是否已存在超级管理员user_type = 1
var count int64
err := db.Model(&Account{}).Where("user_type = ?", constants.UserTypeSuperAdmin).Count(&count).Error
if count > 0 {
// 已存在,跳过创建
logger.Info("超级管理员账号已存在,跳过初始化", zap.Int64("count", count))
return nil
}
// count == 0创建默认管理员
```
**确保唯一性**
- ✅ 检查条件:`user_type = 1`(精确匹配超级管理员类型)
- ✅ 创建条件:只有 `count == 0` 时才创建
- ✅ 幂等性:多次启动不会重复创建
- ✅ 并发安全:启动时单进程执行,无并发问题
#### 3. 账号信息来源
优先级:**配置文件 > 代码默认值**
```go
// 代码默认值pkg/constants/constants.go
const (
DefaultAdminUsername = "admin"
DefaultAdminPassword = "Admin@123456"
DefaultAdminPhone = "13800000000"
)
// 读取配置文件(可选)
username := constants.DefaultAdminUsername
password := constants.DefaultAdminPassword
phone := constants.DefaultAdminPhone
if cfg.DefaultAdmin.Username != "" {
username = cfg.DefaultAdmin.Username // 使用配置文件值
}
if cfg.DefaultAdmin.Password != "" {
password = cfg.DefaultAdmin.Password
}
if cfg.DefaultAdmin.Phone != "" {
phone = cfg.DefaultAdmin.Phone
}
```
#### 4. 创建账号
```go
account := &model.Account{
Username: username,
Phone: phone,
Password: password, // 原始密码CreateSystemAccount 内部会进行 bcrypt 哈希
UserType: constants.UserTypeSuperAdmin, // 1=超级管理员
Status: constants.StatusEnabled, // 1=启用
}
// 调用 Service 层的系统创建方法(绕过当前用户检查)
err := services.Account.CreateSystemAccount(ctx, account)
```
**CreateSystemAccount 方法特点**
- 绕过 `currentUserID` 检查(因为是系统初始化,没有当前用户)
- 仍然保留用户名和手机号唯一性检查
- 密码自动进行 bcrypt 哈希
- 跳过数据权限过滤(使用 `SkipDataPermission(ctx)`
### 降级处理
```go
if err := initDefaultAdmin(deps, services); err != nil {
deps.Logger.Error("初始化默认超级管理员失败", zap.Error(err))
// 不返回错误,继续启动服务
}
```
**设计理由**
- 默认管理员创建失败不应导致整个服务无法启动
- 可能的失败原因:数据库连接问题、用户名/手机号冲突等
- 管理员可以通过日志查看失败原因,手动处理
## 配置说明
### 使用代码默认值(无需配置)
直接启动服务,系统使用内置默认值:
- 用户名:`admin`
- 密码:`Admin@123456`
- 手机号:`13800000000`
### 自定义配置
`configs/config.yaml` 中添加:
```yaml
default_admin:
username: "自定义用户名"
password: "自定义密码"
phone: "自定义手机号"
```
**注意**
- 配置项为可选,不参与 `Validate()` 验证
- 任何字段留空则使用代码默认值
- 密码必须足够复杂(建议包含大小写字母、数字、特殊字符)
## 使用示例
### 场景1首次部署空数据库
```bash
# 启动服务
go run cmd/api/main.go
```
**日志输出**
```
{"level":"info","msg":"默认超级管理员创建成功","username":"admin","phone":"13800000000"}
```
**结果**
- 数据库 `tb_account` 表新增一条记录
- `user_type = 1`(超级管理员)
- `username = "admin"`
- `phone = "13800000000"`
- `password` 为 bcrypt 哈希值
### 场景2已有管理员再次启动
```bash
# 再次启动服务
go run cmd/api/main.go
```
**日志输出**
```
{"level":"info","msg":"超级管理员账号已存在,跳过初始化","count":1}
```
**结果**
- 跳过创建,不修改现有数据
### 场景3使用自定义配置
**配置文件** (`configs/config.yaml`)
```yaml
default_admin:
username: "myadmin"
password: "MySecurePass@2024"
phone: "13900000000"
```
**启动服务**
```bash
go run cmd/api/main.go
```
**日志输出**
```
{"level":"info","msg":"默认超级管理员创建成功","username":"myadmin","phone":"13900000000"}
```
## 安全注意事项
### 1. 密码安全
- ✅ 密码使用 bcrypt 哈希存储,不可逆
- ⚠️ 默认密码相对简单,建议首次登录后立即修改
- ⚠️ 生产环境建议通过配置文件使用更复杂的密码
### 2. 权限控制
- ✅ 超级管理员拥有所有权限,跳过数据权限过滤
- ⚠️ 不要将超级管理员账号用于日常操作
- ⚠️ 建议为日常管理员创建独立的平台用户账号
### 3. 审计日志
- ✅ 创建成功/跳过都记录在 `logs/app.log`
- ✅ 包含时间戳、用户名、手机号
- ⚠️ 日志中不会记录明文密码
### 4. 配置文件安全
- ⚠️ `config.yaml` 中的密码是明文存储
- ⚠️ 确保配置文件访问权限受限(不要提交到公开仓库)
- ⚠️ 生产环境建议使用环境变量或密钥管理服务
## 手动创建管理员(备用方案)
如果自动初始化失败,可以手动执行以下 SQL
```sql
-- 生成 bcrypt 哈希密码(使用 Go 代码或在线工具)
-- bcrypt.GenerateFromPassword([]byte("Admin@123456"), bcrypt.DefaultCost)
-- 示例哈希值(实际使用时需重新生成):
-- $2a$10$abcdefghijklmnopqrstuvwxyz...
INSERT INTO tb_account (
username,
phone,
password,
user_type,
status,
created_at,
updated_at
) VALUES (
'admin',
'13800000000',
'$2a$10$...your-bcrypt-hash...', -- 替换为实际的 bcrypt 哈希
1, -- 超级管理员
1, -- 启用
NOW(),
NOW()
);
```
**生成 bcrypt 哈希工具**
```go
package main
import (
"fmt"
"golang.org/x/crypto/bcrypt"
)
func main() {
password := "Admin@123456"
hash, _ := bcrypt.GenerateFromPassword([]byte(password), bcrypt.DefaultCost)
fmt.Println(string(hash))
}
```
## 相关文件
- `pkg/constants/constants.go` - 默认值常量定义
- `pkg/config/config.go` - 配置结构定义
- `configs/config.yaml` - 配置示例
- `internal/service/account/service.go` - CreateSystemAccount 方法
- `internal/bootstrap/admin.go` - initDefaultAdmin 函数
- `internal/bootstrap/bootstrap.go` - Bootstrap 主流程
## 常见问题
### Q1: 为什么要用系统创建方法而不是直接插入数据库?
**A**: 保持业务逻辑统一性和数据一致性:
- 复用用户名/手机号唯一性检查逻辑
- 自动进行密码哈希处理
- 遵循分层架构原则(通过 Service 层操作)
- 未来扩展更容易(如添加审计日志、事件通知等)
### Q2: 如果数据库有多个超级管理员怎么办?
**A**: 系统检查 `user_type = 1` 的账号数量:
- `count == 0`:创建默认管理员
- `count > 0`:跳过创建(不管有几个)
- 系统不会删除或修改现有的超级管理员
### Q3: 可以通过配置禁用这个功能吗?
**A**: 目前不支持配置禁用,因为这是核心初始化逻辑。如果需要禁用:
1. 手动创建一个超级管理员账号(系统会自动跳过)
2. 或者修改代码注释掉 `initDefaultAdmin()` 调用
### Q4: 创建失败会影响服务启动吗?
**A**: 不会。采用降级处理策略:
- 创建失败只记录错误日志
- 服务继续正常启动
- 管理员可通过日志排查原因并手动创建
### Q5: 如何验证管理员创建成功?
**A**: 三种方式:
1. 查看 `logs/app.log`,搜索 "默认超级管理员创建成功"
2. 查询数据库:`SELECT * FROM tb_account WHERE user_type = 1;`
3. 尝试使用默认账号登录管理后台