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: 完整功能文档
This commit is contained in:
328
docs/add-default-admin-init/功能说明.md
Normal file
328
docs/add-default-admin-init/功能说明.md
Normal file
@@ -0,0 +1,328 @@
|
||||
# 默认超级管理员自动初始化功能
|
||||
|
||||
## 功能概述
|
||||
|
||||
系统在 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. 尝试使用默认账号登录管理后台
|
||||
Reference in New Issue
Block a user