# 默认超级管理员自动初始化功能 ## 功能概述 系统在 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. 尝试使用默认账号登录管理后台