Files
junhong_cmp_fiber/docs/add-default-admin-init/功能说明.md
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

8.8 KiB
Raw Blame History

默认超级管理员自动初始化功能

功能概述

系统在 API 服务启动时,自动检查数据库中是否存在超级管理员账号。如果不存在,则自动创建一个默认的超级管理员账号,确保系统首次部署后可以立即登录使用。

业务背景

问题

  • 首次部署新环境(开发、测试、生产)时,数据库中没有任何管理员账号
  • 无法登录管理后台,需要手动执行 SQL 或脚本创建管理员
  • 人为操作容易出错,不同环境的初始账号可能不一致

解决方案

  • 系统启动时自动检测并创建默认超级管理员
  • 支持配置文件自定义账号信息
  • 幂等操作,多次启动不会重复创建

技术实现

核心逻辑

1. 初始化时机

// 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. 检查逻辑

// 检查是否已存在超级管理员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. 账号信息来源

优先级:配置文件 > 代码默认值

// 代码默认值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. 创建账号

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)

降级处理

if err := initDefaultAdmin(deps, services); err != nil {
    deps.Logger.Error("初始化默认超级管理员失败", zap.Error(err))
    // 不返回错误,继续启动服务
}

设计理由

  • 默认管理员创建失败不应导致整个服务无法启动
  • 可能的失败原因:数据库连接问题、用户名/手机号冲突等
  • 管理员可以通过日志查看失败原因,手动处理

配置说明

使用代码默认值(无需配置)

直接启动服务,系统使用内置默认值:

  • 用户名:admin
  • 密码:Admin@123456
  • 手机号:13800000000

自定义配置

configs/config.yaml 中添加:

default_admin:
  username: "自定义用户名"
  password: "自定义密码"
  phone: "自定义手机号"

注意

  • 配置项为可选,不参与 Validate() 验证
  • 任何字段留空则使用代码默认值
  • 密码必须足够复杂(建议包含大小写字母、数字、特殊字符)

使用示例

场景1首次部署空数据库

# 启动服务
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已有管理员再次启动

# 再次启动服务
go run cmd/api/main.go

日志输出

{"level":"info","msg":"超级管理员账号已存在,跳过初始化","count":1}

结果

  • 跳过创建,不修改现有数据

场景3使用自定义配置

配置文件 (configs/config.yaml)

default_admin:
  username: "myadmin"
  password: "MySecurePass@2024"
  phone: "13900000000"

启动服务

go run cmd/api/main.go

日志输出

{"level":"info","msg":"默认超级管理员创建成功","username":"myadmin","phone":"13900000000"}

安全注意事项

1. 密码安全

  • 密码使用 bcrypt 哈希存储,不可逆
  • ⚠️ 默认密码相对简单,建议首次登录后立即修改
  • ⚠️ 生产环境建议通过配置文件使用更复杂的密码

2. 权限控制

  • 超级管理员拥有所有权限,跳过数据权限过滤
  • ⚠️ 不要将超级管理员账号用于日常操作
  • ⚠️ 建议为日常管理员创建独立的平台用户账号

3. 审计日志

  • 创建成功/跳过都记录在 logs/app.log
  • 包含时间戳、用户名、手机号
  • ⚠️ 日志中不会记录明文密码

4. 配置文件安全

  • ⚠️ config.yaml 中的密码是明文存储
  • ⚠️ 确保配置文件访问权限受限(不要提交到公开仓库)
  • ⚠️ 生产环境建议使用环境变量或密钥管理服务

手动创建管理员(备用方案)

如果自动初始化失败,可以手动执行以下 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 哈希工具

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. 尝试使用默认账号登录管理后台