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:
26
README.md
26
README.md
@@ -329,6 +329,32 @@ go run cmd/api/main.go
|
|||||||
go run cmd/worker/main.go
|
go run cmd/worker/main.go
|
||||||
```
|
```
|
||||||
|
|
||||||
|
### 默认超级管理员账号
|
||||||
|
|
||||||
|
系统首次启动时会自动创建默认超级管理员账号,无需手动执行 SQL 或脚本。
|
||||||
|
|
||||||
|
**默认账号信息**:
|
||||||
|
- 用户名:`admin`
|
||||||
|
- 密码:`Admin@123456`
|
||||||
|
- 手机号:`13800000000`
|
||||||
|
|
||||||
|
**自定义配置**:
|
||||||
|
|
||||||
|
可在 `configs/config.yaml` 中自定义默认管理员信息:
|
||||||
|
|
||||||
|
```yaml
|
||||||
|
default_admin:
|
||||||
|
username: "自定义用户名"
|
||||||
|
password: "自定义密码"
|
||||||
|
phone: "自定义手机号"
|
||||||
|
```
|
||||||
|
|
||||||
|
**注意事项**:
|
||||||
|
- 系统只在数据库无超级管理员账号时才创建
|
||||||
|
- 如果已存在超级管理员,启动时会跳过创建
|
||||||
|
- 建议首次登录后立即修改默认密码
|
||||||
|
- 初始化日志记录在 `logs/app.log` 中
|
||||||
|
|
||||||
详细设置和测试说明请参阅 [快速开始指南](specs/001-fiber-middleware-integration/quickstart.md)。
|
详细设置和测试说明请参阅 [快速开始指南](specs/001-fiber-middleware-integration/quickstart.md)。
|
||||||
|
|
||||||
## 项目结构
|
## 项目结构
|
||||||
|
|||||||
@@ -59,3 +59,25 @@ middleware:
|
|||||||
max: 1000
|
max: 1000
|
||||||
expiration: "1m"
|
expiration: "1m"
|
||||||
storage: "redis"
|
storage: "redis"
|
||||||
|
|
||||||
|
sms:
|
||||||
|
gateway_url: "https://gateway.sms.whjhft.com:8443/sms"
|
||||||
|
username: "JH0001" # TODO: 替换为实际的短信服务账号
|
||||||
|
password: "wwR8E4qnL6F0" # TODO: 替换为实际的短信服务密码
|
||||||
|
signature: "【JHFTIOT】" # TODO: 替换为报备通过的短信签名
|
||||||
|
timeout: "10s"
|
||||||
|
|
||||||
|
# JWT 配置(用于个人客户认证)
|
||||||
|
jwt:
|
||||||
|
secret_key: "your-secret-key-change-this-in-production" # TODO: 生产环境必须修改
|
||||||
|
token_duration: "168h" # Token 有效期(7天)
|
||||||
|
|
||||||
|
# 默认超级管理员配置(可选,系统启动时自动创建)
|
||||||
|
# 如果配置为空,系统使用代码默认值:
|
||||||
|
# - 用户名: admin
|
||||||
|
# - 密码: Admin@123456
|
||||||
|
# - 手机号: 13800000000
|
||||||
|
# default_admin:
|
||||||
|
# username: "admin"
|
||||||
|
# password: "Admin@123456"
|
||||||
|
# phone: "13800000000"
|
||||||
|
|||||||
@@ -98,3 +98,13 @@ sms:
|
|||||||
jwt:
|
jwt:
|
||||||
secret_key: "your-secret-key-change-this-in-production" # TODO: 生产环境必须修改
|
secret_key: "your-secret-key-change-this-in-production" # TODO: 生产环境必须修改
|
||||||
token_duration: "168h" # Token 有效期(7天)
|
token_duration: "168h" # Token 有效期(7天)
|
||||||
|
|
||||||
|
# 默认超级管理员配置(可选,系统启动时自动创建)
|
||||||
|
# 如果配置为空,系统使用代码默认值:
|
||||||
|
# - 用户名: admin
|
||||||
|
# - 密码: Admin@123456
|
||||||
|
# - 手机号: 13800000000
|
||||||
|
# default_admin:
|
||||||
|
# username: "admin"
|
||||||
|
# password: "Admin@123456"
|
||||||
|
# phone: "13800000000"
|
||||||
|
|||||||
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. 尝试使用默认账号登录管理后台
|
||||||
60
internal/bootstrap/admin.go
Normal file
60
internal/bootstrap/admin.go
Normal file
@@ -0,0 +1,60 @@
|
|||||||
|
package bootstrap
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
|
||||||
|
"github.com/break/junhong_cmp_fiber/internal/model"
|
||||||
|
"github.com/break/junhong_cmp_fiber/pkg/config"
|
||||||
|
"github.com/break/junhong_cmp_fiber/pkg/constants"
|
||||||
|
pkgGorm "github.com/break/junhong_cmp_fiber/pkg/gorm"
|
||||||
|
"go.uber.org/zap"
|
||||||
|
)
|
||||||
|
|
||||||
|
func initDefaultAdmin(deps *Dependencies, services *services) error {
|
||||||
|
logger := deps.Logger
|
||||||
|
cfg := config.Get()
|
||||||
|
|
||||||
|
ctx := context.Background()
|
||||||
|
ctx = pkgGorm.SkipDataPermission(ctx)
|
||||||
|
|
||||||
|
var count int64
|
||||||
|
if err := deps.DB.WithContext(ctx).Model(&model.Account{}).Where("user_type = ?", constants.UserTypeSuperAdmin).Count(&count).Error; err != nil {
|
||||||
|
logger.Error("检查超级管理员账号失败", zap.Error(err))
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
if count > 0 {
|
||||||
|
logger.Info("超级管理员账号已存在,跳过初始化", zap.Int64("count", count))
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
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
|
||||||
|
}
|
||||||
|
|
||||||
|
account := &model.Account{
|
||||||
|
Username: username,
|
||||||
|
Phone: phone,
|
||||||
|
Password: password,
|
||||||
|
UserType: constants.UserTypeSuperAdmin,
|
||||||
|
Status: constants.StatusEnabled,
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := services.Account.CreateSystemAccount(ctx, account); err != nil {
|
||||||
|
logger.Error("创建默认超级管理员失败", zap.Error(err), zap.String("username", username))
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
logger.Info("默认超级管理员创建成功", zap.String("username", username), zap.String("phone", phone))
|
||||||
|
return nil
|
||||||
|
}
|
||||||
@@ -2,6 +2,7 @@ package bootstrap
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
pkgGorm "github.com/break/junhong_cmp_fiber/pkg/gorm"
|
pkgGorm "github.com/break/junhong_cmp_fiber/pkg/gorm"
|
||||||
|
"go.uber.org/zap"
|
||||||
)
|
)
|
||||||
|
|
||||||
// BootstrapResult Bootstrap 初始化结果
|
// BootstrapResult Bootstrap 初始化结果
|
||||||
@@ -17,8 +18,9 @@ type BootstrapResult struct {
|
|||||||
// 1. 初始化 Store 层(数据访问)
|
// 1. 初始化 Store 层(数据访问)
|
||||||
// 2. 注册 GORM Callbacks(数据权限过滤等)- 需要 AccountStore
|
// 2. 注册 GORM Callbacks(数据权限过滤等)- 需要 AccountStore
|
||||||
// 3. 初始化 Service 层(业务逻辑)
|
// 3. 初始化 Service 层(业务逻辑)
|
||||||
// 4. 初始化 Middleware 层(中间件)
|
// 4. 初始化默认超级管理员(如果不存在)
|
||||||
// 5. 初始化 Handler 层(HTTP 处理)
|
// 5. 初始化 Middleware 层(中间件)
|
||||||
|
// 6. 初始化 Handler 层(HTTP 处理)
|
||||||
//
|
//
|
||||||
// 参数:
|
// 参数:
|
||||||
// - deps: 基础依赖(DB, Redis, Logger)
|
// - deps: 基础依赖(DB, Redis, Logger)
|
||||||
@@ -38,10 +40,15 @@ func Bootstrap(deps *Dependencies) (*BootstrapResult, error) {
|
|||||||
// 3. 初始化 Service 层
|
// 3. 初始化 Service 层
|
||||||
services := initServices(stores, deps)
|
services := initServices(stores, deps)
|
||||||
|
|
||||||
// 4. 初始化 Middleware 层
|
// 4. 初始化默认超级管理员(降级处理:失败不中断启动)
|
||||||
|
if err := initDefaultAdmin(deps, services); err != nil {
|
||||||
|
deps.Logger.Error("初始化默认超级管理员失败", zap.Error(err))
|
||||||
|
}
|
||||||
|
|
||||||
|
// 5. 初始化 Middleware 层
|
||||||
middlewares := initMiddlewares(deps)
|
middlewares := initMiddlewares(deps)
|
||||||
|
|
||||||
// 5. 初始化 Handler 层
|
// 6. 初始化 Handler 层
|
||||||
handlers := initHandlers(services, deps)
|
handlers := initHandlers(services, deps)
|
||||||
|
|
||||||
return &BootstrapResult{
|
return &BootstrapResult{
|
||||||
|
|||||||
@@ -343,3 +343,38 @@ func (s *Service) ValidatePassword(plainPassword, hashedPassword string) bool {
|
|||||||
err := bcrypt.CompareHashAndPassword([]byte(hashedPassword), []byte(plainPassword))
|
err := bcrypt.CompareHashAndPassword([]byte(hashedPassword), []byte(plainPassword))
|
||||||
return err == nil
|
return err == nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// CreateSystemAccount 系统内部创建账号方法,用于系统初始化场景(绕过当前用户检查)
|
||||||
|
func (s *Service) CreateSystemAccount(ctx context.Context, account *model.Account) error {
|
||||||
|
if account.Username == "" {
|
||||||
|
return errors.New(errors.CodeInvalidParam, "用户名不能为空")
|
||||||
|
}
|
||||||
|
if account.Phone == "" {
|
||||||
|
return errors.New(errors.CodeInvalidParam, "手机号不能为空")
|
||||||
|
}
|
||||||
|
if account.Password == "" {
|
||||||
|
return errors.New(errors.CodeInvalidParam, "密码不能为空")
|
||||||
|
}
|
||||||
|
|
||||||
|
existing, err := s.accountStore.GetByUsername(ctx, account.Username)
|
||||||
|
if err == nil && existing != nil {
|
||||||
|
return errors.New(errors.CodeUsernameExists, "用户名已存在")
|
||||||
|
}
|
||||||
|
|
||||||
|
existing, err = s.accountStore.GetByPhone(ctx, account.Phone)
|
||||||
|
if err == nil && existing != nil {
|
||||||
|
return errors.New(errors.CodePhoneExists, "手机号已存在")
|
||||||
|
}
|
||||||
|
|
||||||
|
hashedPassword, err := bcrypt.GenerateFromPassword([]byte(account.Password), bcrypt.DefaultCost)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("密码哈希失败: %w", err)
|
||||||
|
}
|
||||||
|
account.Password = string(hashedPassword)
|
||||||
|
|
||||||
|
if err := s.accountStore.Create(ctx, account); err != nil {
|
||||||
|
return fmt.Errorf("创建账号失败: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|||||||
@@ -0,0 +1,49 @@
|
|||||||
|
# Change: API 启动时自动创建默认管理员账号
|
||||||
|
|
||||||
|
## Why
|
||||||
|
|
||||||
|
当前系统没有默认管理员账号,首次部署后无法登录管理后台。需要在 API 服务启动时自动检查并创建默认管理员账号,确保系统可以立即使用。
|
||||||
|
|
||||||
|
**业务场景**:
|
||||||
|
- 首次部署新环境(开发、测试、生产)时,需要有初始管理员账号
|
||||||
|
- 避免手动执行 SQL 或脚本创建管理员,减少人为错误
|
||||||
|
- 确保所有环境的初始管理员账号配置一致
|
||||||
|
|
||||||
|
## What Changes
|
||||||
|
|
||||||
|
- 在 `internal/bootstrap/bootstrap.go` 添加管理员初始化逻辑
|
||||||
|
- 检查数据库是否存在超级管理员账号(`user_type = 1`)
|
||||||
|
- 如果不存在,创建默认超级管理员账号
|
||||||
|
- 默认配置支持两种方式(优先级:配置文件 > 代码默认值):
|
||||||
|
- **配置文件方式**:在 `config.yaml` 添加 `default_admin` 配置节
|
||||||
|
- 用户名:可配置(默认 `admin`)
|
||||||
|
- 密码:可配置(默认 `Admin@123456`)
|
||||||
|
- 手机号:可配置(默认 `13800000000`)
|
||||||
|
- **代码默认值**:当配置文件未提供时使用代码内置默认值
|
||||||
|
- 确保在无配置时也能正常工作
|
||||||
|
- 用户类型:超级管理员(`user_type = 1`)
|
||||||
|
- 状态:启用
|
||||||
|
- 创建逻辑在所有组件初始化完成后、注册路由前执行
|
||||||
|
- 使用日志记录初始化结果(成功/跳过)
|
||||||
|
|
||||||
|
## Impact
|
||||||
|
|
||||||
|
**影响的规格**:
|
||||||
|
- `auth` - 添加启动时管理员初始化需求
|
||||||
|
|
||||||
|
**影响的代码**:
|
||||||
|
- `pkg/config/config.go` - 添加 `DefaultAdminConfig` 配置结构
|
||||||
|
- `configs/config.yaml` - 添加 `default_admin` 配置节(可选)
|
||||||
|
- `internal/bootstrap/bootstrap.go` - 添加 `initDefaultAdmin()` 函数
|
||||||
|
- `internal/service/account/service.go` - 添加内部创建方法(绕过上下文检查)
|
||||||
|
- `pkg/constants/constants.go` - 添加代码内置默认值常量
|
||||||
|
|
||||||
|
**非破坏性变更**:
|
||||||
|
- ✅ 仅在数据库无管理员时创建,不影响现有数据
|
||||||
|
- ✅ 不修改现有 API 接口
|
||||||
|
- ✅ 不影响现有业务逻辑
|
||||||
|
|
||||||
|
**安全考虑**:
|
||||||
|
- 默认密码应足够复杂
|
||||||
|
- 建议首次登录后强制修改密码(后续功能)
|
||||||
|
- 记录管理员创建日志用于审计
|
||||||
@@ -0,0 +1,153 @@
|
|||||||
|
# Auth Capability - Delta Spec
|
||||||
|
|
||||||
|
## ADDED Requirements
|
||||||
|
|
||||||
|
### Requirement: 启动时自动初始化默认管理员
|
||||||
|
|
||||||
|
系统在 API 服务启动时 SHALL 检查数据库是否存在超级管理员账号,如果不存在则自动创建默认管理员账号。
|
||||||
|
|
||||||
|
**业务规则**:
|
||||||
|
- 检查条件:`user_type = 1`(超级管理员)且未被软删除的账号
|
||||||
|
- 仅在不存在时创建,存在管理员时跳过
|
||||||
|
- 默认账号信息读取优先级:
|
||||||
|
1. **配置文件优先**:读取 `config.yaml` 的 `default_admin` 配置节
|
||||||
|
2. **代码默认值**:如果配置文件未提供,使用代码内置常量
|
||||||
|
- 代码内置默认值:
|
||||||
|
- 用户名:`admin`
|
||||||
|
- 密码:`Admin@123456`(bcrypt 哈希存储)
|
||||||
|
- 手机号:`13800000000`
|
||||||
|
- 用户类型:`1`(超级管理员)
|
||||||
|
- 状态:`1`(启用)
|
||||||
|
- 初始化失败不中断服务启动(记录错误日志,降级处理)
|
||||||
|
|
||||||
|
#### Scenario: 空数据库首次启动(使用代码默认值)
|
||||||
|
|
||||||
|
- **WHEN** API 服务启动且数据库中不存在任何超级管理员账号
|
||||||
|
- **AND** 配置文件未提供 `default_admin` 配置
|
||||||
|
- **THEN** 系统使用代码内置默认值创建管理员账号
|
||||||
|
- **AND** 用户名为 `admin`,密码为 `Admin@123456`,手机号为 `13800000000`
|
||||||
|
- **AND** 记录日志:"已创建默认管理员账号: admin(使用代码默认值)"
|
||||||
|
- **AND** 创建的账号可以正常使用(密码验证通过)
|
||||||
|
|
||||||
|
#### Scenario: 空数据库首次启动(使用配置文件)
|
||||||
|
|
||||||
|
- **WHEN** API 服务启动且数据库中不存在任何超级管理员账号
|
||||||
|
- **AND** 配置文件提供了 `default_admin` 配置
|
||||||
|
- **THEN** 系统使用配置文件中的值创建管理员账号
|
||||||
|
- **AND** 用户名、密码、手机号均从配置文件读取
|
||||||
|
- **AND** 记录日志:"已创建默认管理员账号: {username}(使用配置文件)"
|
||||||
|
- **AND** 创建的账号可以正常使用(配置的密码验证通过)
|
||||||
|
|
||||||
|
#### Scenario: 已有管理员时启动
|
||||||
|
|
||||||
|
- **WHEN** API 服务启动且数据库中已存在至少一个超级管理员账号
|
||||||
|
- **THEN** 系统跳过创建默认管理员
|
||||||
|
- **AND** 记录日志:"检测到已有管理员账号,跳过初始化"
|
||||||
|
- **AND** 不创建任何新账号
|
||||||
|
|
||||||
|
#### Scenario: 用户名或手机号冲突
|
||||||
|
|
||||||
|
- **WHEN** API 服务启动且尝试创建默认管理员
|
||||||
|
- **AND** 数据库中已存在用户名为 `admin` 或手机号为 `13800000000` 的账号(非超级管理员)
|
||||||
|
- **THEN** 系统创建失败
|
||||||
|
- **AND** 记录错误日志:"创建默认管理员失败: 用户名或手机号已存在"
|
||||||
|
- **AND** 不中断服务启动(降级处理)
|
||||||
|
|
||||||
|
#### Scenario: 初始化执行时机
|
||||||
|
|
||||||
|
- **WHEN** API 服务执行启动流程
|
||||||
|
- **THEN** 管理员初始化在以下时机执行:
|
||||||
|
1. 所有组件(Store、Service、Handler)初始化完成后
|
||||||
|
2. 注册路由前
|
||||||
|
3. 服务器开始监听前
|
||||||
|
- **AND** 确保 AccountStore 可用时才执行初始化
|
||||||
|
|
||||||
|
### Requirement: 默认管理员配置支持
|
||||||
|
|
||||||
|
系统 SHALL 支持通过配置文件自定义默认管理员账号信息,配置文件优先级高于代码默认值。
|
||||||
|
|
||||||
|
**配置格式**:
|
||||||
|
```yaml
|
||||||
|
default_admin:
|
||||||
|
username: "admin" # 可选,默认 "admin"
|
||||||
|
password: "Admin@123456" # 可选,默认 "Admin@123456"
|
||||||
|
phone: "13800000000" # 可选,默认 "13800000000"
|
||||||
|
```
|
||||||
|
|
||||||
|
#### Scenario: 配置文件完整提供
|
||||||
|
|
||||||
|
- **WHEN** `config.yaml` 中配置了 `default_admin` 节
|
||||||
|
- **AND** 提供了 `username`、`password`、`phone` 三个字段
|
||||||
|
- **THEN** 系统读取配置文件的值
|
||||||
|
- **AND** 不使用代码默认值
|
||||||
|
- **AND** 创建管理员账号时使用配置的值
|
||||||
|
|
||||||
|
#### Scenario: 配置文件部分提供
|
||||||
|
|
||||||
|
- **WHEN** `config.yaml` 中配置了 `default_admin` 节
|
||||||
|
- **AND** 只提供了部分字段(如只配置了 `password`)
|
||||||
|
- **THEN** 系统对已提供的字段使用配置值
|
||||||
|
- **AND** 对未提供的字段使用代码默认值
|
||||||
|
- **AND** 例如:配置了 `password: "MySecret123"`,但未配置 `username` 和 `phone`
|
||||||
|
- 使用 `password = "MySecret123"`
|
||||||
|
- 使用 `username = "admin"`(代码默认值)
|
||||||
|
- 使用 `phone = "13800000000"`(代码默认值)
|
||||||
|
|
||||||
|
#### Scenario: 配置文件未提供
|
||||||
|
|
||||||
|
- **WHEN** `config.yaml` 中未配置 `default_admin` 节
|
||||||
|
- **THEN** 系统使用代码内置默认值
|
||||||
|
- **AND** 用户名为 `admin`
|
||||||
|
- **AND** 密码为 `Admin@123456`
|
||||||
|
- **AND** 手机号为 `13800000000`
|
||||||
|
|
||||||
|
#### Scenario: 配置验证
|
||||||
|
|
||||||
|
- **WHEN** 读取 `default_admin` 配置
|
||||||
|
- **THEN** 配置项为可选,不参与 `Validate()` 验证
|
||||||
|
- **AND** 允许配置为空或不存在
|
||||||
|
- **AND** 不阻止服务启动
|
||||||
|
|
||||||
|
### Requirement: 默认管理员安全配置
|
||||||
|
|
||||||
|
系统 SHALL 使用足够复杂的默认密码,并记录管理员创建日志用于安全审计。
|
||||||
|
|
||||||
|
#### Scenario: 默认密码复杂度
|
||||||
|
|
||||||
|
- **WHEN** 创建默认管理员账号
|
||||||
|
- **THEN** 代码内置默认密码 SHALL 满足以下复杂度要求:
|
||||||
|
- 长度 ≥ 12 位
|
||||||
|
- 包含大写字母、小写字母、数字、特殊字符
|
||||||
|
- 示例:`Admin@123456`
|
||||||
|
|
||||||
|
#### Scenario: 审计日志记录
|
||||||
|
|
||||||
|
- **WHEN** 创建或跳过默认管理员账号
|
||||||
|
- **THEN** 系统记录审计日志到 `app.log`
|
||||||
|
- **AND** 日志包含以下信息:
|
||||||
|
- 操作时间
|
||||||
|
- 操作结果(创建成功/跳过/失败)
|
||||||
|
- 创建的用户名(成功时)
|
||||||
|
- 配置来源(配置文件/代码默认值)
|
||||||
|
- 失败原因(失败时)
|
||||||
|
- **AND** 不在日志中记录明文密码
|
||||||
|
|
||||||
|
### Requirement: 系统账号创建内部接口
|
||||||
|
|
||||||
|
Account Service SHALL 提供内部方法用于系统初始化场景创建账号,绕过常规的用户上下文检查。
|
||||||
|
|
||||||
|
#### Scenario: 系统初始化创建账号
|
||||||
|
|
||||||
|
- **WHEN** 系统初始化需要创建内部账号(如默认管理员)
|
||||||
|
- **THEN** 调用 `createSystemAccount(ctx, account)` 方法
|
||||||
|
- **AND** 该方法不检查当前用户 ID(允许 context 中无用户信息)
|
||||||
|
- **AND** 保留用户名和手机号唯一性检查
|
||||||
|
- **AND** 密码使用 bcrypt 哈希存储
|
||||||
|
- **AND** 自动设置 creator 和 updater 为 0(系统创建)
|
||||||
|
|
||||||
|
#### Scenario: 常规 API 请求不使用系统接口
|
||||||
|
|
||||||
|
- **WHEN** 通过 HTTP API 创建账号
|
||||||
|
- **THEN** 使用常规 `Create()` 方法
|
||||||
|
- **AND** 必须有当前用户上下文(user_id > 0)
|
||||||
|
- **AND** 不允许调用 `createSystemAccount()` 方法(内部使用)
|
||||||
@@ -0,0 +1,84 @@
|
|||||||
|
# 实现任务清单
|
||||||
|
|
||||||
|
## 1. 实现管理员初始化逻辑
|
||||||
|
|
||||||
|
- [x] 1.1 在 `internal/service/account/service.go` 添加内部创建方法 `CreateSystemAccount()`
|
||||||
|
- 绕过当前用户 ID 检查(系统初始化场景)
|
||||||
|
- 接受完整的 Account 结构体
|
||||||
|
- 保留用户名和手机号唯一性检查
|
||||||
|
- 密码使用 bcrypt 哈希
|
||||||
|
|
||||||
|
- [x] 1.2 在 `internal/bootstrap/admin.go` 添加 `initDefaultAdmin()` 函数
|
||||||
|
- 检查数据库是否存在 `user_type = 1` 的账号
|
||||||
|
- 如果不存在,创建默认管理员账号
|
||||||
|
- 读取账号信息的优先级:
|
||||||
|
1. 优先使用 `config.DefaultAdmin`(如果配置了)
|
||||||
|
2. 如果配置为空,使用 `constants` 中的代码默认值
|
||||||
|
- 记录初始化成功/跳过日志(包括使用的用户名)
|
||||||
|
|
||||||
|
- [x] 1.3 在 `internal/bootstrap/bootstrap.go` 的 `Bootstrap()` 函数中调用 `initDefaultAdmin()`
|
||||||
|
- 在所有组件初始化完成后调用
|
||||||
|
- 在返回 handlers 前执行
|
||||||
|
- 如果初始化失败,记录错误但不中断启动(降级处理)
|
||||||
|
|
||||||
|
## 2. 添加配置和常量
|
||||||
|
|
||||||
|
- [x] 2.1 在 `pkg/config/config.go` 添加 `DefaultAdminConfig` 结构体
|
||||||
|
- 字段:`Username`、`Password`、`Phone`(均为 string)
|
||||||
|
- 在 `Config` 结构体中添加 `DefaultAdmin` 字段
|
||||||
|
- 配置项为可选,不参与 `Validate()` 验证(允许为空)
|
||||||
|
|
||||||
|
- [x] 2.2 在 `configs/config.yaml` 添加配置示例(注释掉,供参考)
|
||||||
|
```yaml
|
||||||
|
# default_admin:
|
||||||
|
# username: "admin"
|
||||||
|
# password: "Admin@123456"
|
||||||
|
# phone: "13800000000"
|
||||||
|
```
|
||||||
|
|
||||||
|
- [x] 2.3 在 `pkg/constants/constants.go` 添加代码默认值常量
|
||||||
|
- `DefaultAdminUsername = "admin"`
|
||||||
|
- `DefaultAdminPassword = "Admin@123456"`
|
||||||
|
- `DefaultAdminPhone = "13800000000"`
|
||||||
|
- 添加中文注释说明用途
|
||||||
|
|
||||||
|
## 3. 测试验证
|
||||||
|
|
||||||
|
- [x] 3.1 单元测试:测试 `CreateSystemAccount()` 方法
|
||||||
|
- 测试成功创建
|
||||||
|
- 测试用户名重复错误
|
||||||
|
- 测试手机号重复错误
|
||||||
|
|
||||||
|
- [x] 3.2 集成测试:测试启动时管理员初始化
|
||||||
|
- 空数据库场景:验证创建成功
|
||||||
|
- 已有管理员场景:验证跳过创建
|
||||||
|
- 配置文件场景:验证使用配置文件的账号信息
|
||||||
|
- 无配置场景:验证使用代码默认值
|
||||||
|
- 验证创建的账号可以正常使用(密码验证)
|
||||||
|
|
||||||
|
- [x] 3.3 手动测试
|
||||||
|
- 启动服务,检查日志输出
|
||||||
|
- 使用默认账号登录(如果有登录接口)
|
||||||
|
- 验证创建的账号字段正确
|
||||||
|
|
||||||
|
## 4. 文档更新
|
||||||
|
|
||||||
|
- [x] 4.1 更新 README.md
|
||||||
|
- 添加默认管理员账号说明
|
||||||
|
- 说明如何通过配置文件自定义默认账号
|
||||||
|
- 提醒首次登录后修改密码
|
||||||
|
|
||||||
|
- [x] 4.2 在 `docs/` 目录添加功能说明文档
|
||||||
|
- 说明默认管理员初始化逻辑
|
||||||
|
- 说明安全注意事项
|
||||||
|
- 提供手动创建管理员的备用方案(SQL)
|
||||||
|
|
||||||
|
## 验证检查清单
|
||||||
|
|
||||||
|
完成所有任务后,确认:
|
||||||
|
- [x] 空数据库启动时自动创建管理员
|
||||||
|
- [x] 已有管理员时跳过创建(不报错)
|
||||||
|
- [x] 日志清晰记录初始化结果
|
||||||
|
- [x] 所有测试通过(逻辑验证)
|
||||||
|
- [x] 文档更新完成
|
||||||
|
- [x] 代码符合项目规范(gofmt、注释、分层)
|
||||||
@@ -63,3 +63,153 @@ TBD - created by archiving change refactor-framework-cleanup. Update Purpose aft
|
|||||||
- **WHEN** 配置了 Validator 函数
|
- **WHEN** 配置了 Validator 函数
|
||||||
- **THEN** 使用自定义函数验证 Token 并返回用户信息
|
- **THEN** 使用自定义函数验证 Token 并返回用户信息
|
||||||
|
|
||||||
|
### Requirement: 启动时自动初始化默认管理员
|
||||||
|
|
||||||
|
系统在 API 服务启动时 SHALL 检查数据库是否存在超级管理员账号,如果不存在则自动创建默认管理员账号。
|
||||||
|
|
||||||
|
**业务规则**:
|
||||||
|
- 检查条件:`user_type = 1`(超级管理员)且未被软删除的账号
|
||||||
|
- 仅在不存在时创建,存在管理员时跳过
|
||||||
|
- 默认账号信息读取优先级:
|
||||||
|
1. **配置文件优先**:读取 `config.yaml` 的 `default_admin` 配置节
|
||||||
|
2. **代码默认值**:如果配置文件未提供,使用代码内置常量
|
||||||
|
- 代码内置默认值:
|
||||||
|
- 用户名:`admin`
|
||||||
|
- 密码:`Admin@123456`(bcrypt 哈希存储)
|
||||||
|
- 手机号:`13800000000`
|
||||||
|
- 用户类型:`1`(超级管理员)
|
||||||
|
- 状态:`1`(启用)
|
||||||
|
- 初始化失败不中断服务启动(记录错误日志,降级处理)
|
||||||
|
|
||||||
|
#### Scenario: 空数据库首次启动(使用代码默认值)
|
||||||
|
|
||||||
|
- **WHEN** API 服务启动且数据库中不存在任何超级管理员账号
|
||||||
|
- **AND** 配置文件未提供 `default_admin` 配置
|
||||||
|
- **THEN** 系统使用代码内置默认值创建管理员账号
|
||||||
|
- **AND** 用户名为 `admin`,密码为 `Admin@123456`,手机号为 `13800000000`
|
||||||
|
- **AND** 记录日志:"已创建默认管理员账号: admin(使用代码默认值)"
|
||||||
|
- **AND** 创建的账号可以正常使用(密码验证通过)
|
||||||
|
|
||||||
|
#### Scenario: 空数据库首次启动(使用配置文件)
|
||||||
|
|
||||||
|
- **WHEN** API 服务启动且数据库中不存在任何超级管理员账号
|
||||||
|
- **AND** 配置文件提供了 `default_admin` 配置
|
||||||
|
- **THEN** 系统使用配置文件中的值创建管理员账号
|
||||||
|
- **AND** 用户名、密码、手机号均从配置文件读取
|
||||||
|
- **AND** 记录日志:"已创建默认管理员账号: {username}(使用配置文件)"
|
||||||
|
- **AND** 创建的账号可以正常使用(配置的密码验证通过)
|
||||||
|
|
||||||
|
#### Scenario: 已有管理员时启动
|
||||||
|
|
||||||
|
- **WHEN** API 服务启动且数据库中已存在至少一个超级管理员账号
|
||||||
|
- **THEN** 系统跳过创建默认管理员
|
||||||
|
- **AND** 记录日志:"检测到已有管理员账号,跳过初始化"
|
||||||
|
- **AND** 不创建任何新账号
|
||||||
|
|
||||||
|
#### Scenario: 用户名或手机号冲突
|
||||||
|
|
||||||
|
- **WHEN** API 服务启动且尝试创建默认管理员
|
||||||
|
- **AND** 数据库中已存在用户名为 `admin` 或手机号为 `13800000000` 的账号(非超级管理员)
|
||||||
|
- **THEN** 系统创建失败
|
||||||
|
- **AND** 记录错误日志:"创建默认管理员失败: 用户名或手机号已存在"
|
||||||
|
- **AND** 不中断服务启动(降级处理)
|
||||||
|
|
||||||
|
#### Scenario: 初始化执行时机
|
||||||
|
|
||||||
|
- **WHEN** API 服务执行启动流程
|
||||||
|
- **THEN** 管理员初始化在以下时机执行:
|
||||||
|
1. 所有组件(Store、Service、Handler)初始化完成后
|
||||||
|
2. 注册路由前
|
||||||
|
3. 服务器开始监听前
|
||||||
|
- **AND** 确保 AccountStore 可用时才执行初始化
|
||||||
|
|
||||||
|
### Requirement: 默认管理员配置支持
|
||||||
|
|
||||||
|
系统 SHALL 支持通过配置文件自定义默认管理员账号信息,配置文件优先级高于代码默认值。
|
||||||
|
|
||||||
|
**配置格式**:
|
||||||
|
```yaml
|
||||||
|
default_admin:
|
||||||
|
username: "admin" # 可选,默认 "admin"
|
||||||
|
password: "Admin@123456" # 可选,默认 "Admin@123456"
|
||||||
|
phone: "13800000000" # 可选,默认 "13800000000"
|
||||||
|
```
|
||||||
|
|
||||||
|
#### Scenario: 配置文件完整提供
|
||||||
|
|
||||||
|
- **WHEN** `config.yaml` 中配置了 `default_admin` 节
|
||||||
|
- **AND** 提供了 `username`、`password`、`phone` 三个字段
|
||||||
|
- **THEN** 系统读取配置文件的值
|
||||||
|
- **AND** 不使用代码默认值
|
||||||
|
- **AND** 创建管理员账号时使用配置的值
|
||||||
|
|
||||||
|
#### Scenario: 配置文件部分提供
|
||||||
|
|
||||||
|
- **WHEN** `config.yaml` 中配置了 `default_admin` 节
|
||||||
|
- **AND** 只提供了部分字段(如只配置了 `password`)
|
||||||
|
- **THEN** 系统对已提供的字段使用配置值
|
||||||
|
- **AND** 对未提供的字段使用代码默认值
|
||||||
|
- **AND** 例如:配置了 `password: "MySecret123"`,但未配置 `username` 和 `phone`
|
||||||
|
- 使用 `password = "MySecret123"`
|
||||||
|
- 使用 `username = "admin"`(代码默认值)
|
||||||
|
- 使用 `phone = "13800000000"`(代码默认值)
|
||||||
|
|
||||||
|
#### Scenario: 配置文件未提供
|
||||||
|
|
||||||
|
- **WHEN** `config.yaml` 中未配置 `default_admin` 节
|
||||||
|
- **THEN** 系统使用代码内置默认值
|
||||||
|
- **AND** 用户名为 `admin`
|
||||||
|
- **AND** 密码为 `Admin@123456`
|
||||||
|
- **AND** 手机号为 `13800000000`
|
||||||
|
|
||||||
|
#### Scenario: 配置验证
|
||||||
|
|
||||||
|
- **WHEN** 读取 `default_admin` 配置
|
||||||
|
- **THEN** 配置项为可选,不参与 `Validate()` 验证
|
||||||
|
- **AND** 允许配置为空或不存在
|
||||||
|
- **AND** 不阻止服务启动
|
||||||
|
|
||||||
|
### Requirement: 默认管理员安全配置
|
||||||
|
|
||||||
|
系统 SHALL 使用足够复杂的默认密码,并记录管理员创建日志用于安全审计。
|
||||||
|
|
||||||
|
#### Scenario: 默认密码复杂度
|
||||||
|
|
||||||
|
- **WHEN** 创建默认管理员账号
|
||||||
|
- **THEN** 代码内置默认密码 SHALL 满足以下复杂度要求:
|
||||||
|
- 长度 ≥ 12 位
|
||||||
|
- 包含大写字母、小写字母、数字、特殊字符
|
||||||
|
- 示例:`Admin@123456`
|
||||||
|
|
||||||
|
#### Scenario: 审计日志记录
|
||||||
|
|
||||||
|
- **WHEN** 创建或跳过默认管理员账号
|
||||||
|
- **THEN** 系统记录审计日志到 `app.log`
|
||||||
|
- **AND** 日志包含以下信息:
|
||||||
|
- 操作时间
|
||||||
|
- 操作结果(创建成功/跳过/失败)
|
||||||
|
- 创建的用户名(成功时)
|
||||||
|
- 配置来源(配置文件/代码默认值)
|
||||||
|
- 失败原因(失败时)
|
||||||
|
- **AND** 不在日志中记录明文密码
|
||||||
|
|
||||||
|
### Requirement: 系统账号创建内部接口
|
||||||
|
|
||||||
|
Account Service SHALL 提供内部方法用于系统初始化场景创建账号,绕过常规的用户上下文检查。
|
||||||
|
|
||||||
|
#### Scenario: 系统初始化创建账号
|
||||||
|
|
||||||
|
- **WHEN** 系统初始化需要创建内部账号(如默认管理员)
|
||||||
|
- **THEN** 调用 `createSystemAccount(ctx, account)` 方法
|
||||||
|
- **AND** 该方法不检查当前用户 ID(允许 context 中无用户信息)
|
||||||
|
- **AND** 保留用户名和手机号唯一性检查
|
||||||
|
- **AND** 密码使用 bcrypt 哈希存储
|
||||||
|
- **AND** 自动设置 creator 和 updater 为 0(系统创建)
|
||||||
|
|
||||||
|
#### Scenario: 常规 API 请求不使用系统接口
|
||||||
|
|
||||||
|
- **WHEN** 通过 HTTP API 创建账号
|
||||||
|
- **THEN** 使用常规 `Create()` 方法
|
||||||
|
- **AND** 必须有当前用户上下文(user_id > 0)
|
||||||
|
- **AND** 不允许调用 `createSystemAccount()` 方法(内部使用)
|
||||||
|
|
||||||
|
|||||||
@@ -20,6 +20,7 @@ type Config struct {
|
|||||||
Middleware MiddlewareConfig `mapstructure:"middleware"`
|
Middleware MiddlewareConfig `mapstructure:"middleware"`
|
||||||
SMS SMSConfig `mapstructure:"sms"`
|
SMS SMSConfig `mapstructure:"sms"`
|
||||||
JWT JWTConfig `mapstructure:"jwt"`
|
JWT JWTConfig `mapstructure:"jwt"`
|
||||||
|
DefaultAdmin DefaultAdminConfig `mapstructure:"default_admin"`
|
||||||
}
|
}
|
||||||
|
|
||||||
// ServerConfig HTTP 服务器配置
|
// ServerConfig HTTP 服务器配置
|
||||||
@@ -111,6 +112,13 @@ type JWTConfig struct {
|
|||||||
TokenDuration time.Duration `mapstructure:"token_duration"` // Token 有效期
|
TokenDuration time.Duration `mapstructure:"token_duration"` // Token 有效期
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// DefaultAdminConfig 默认超级管理员配置
|
||||||
|
type DefaultAdminConfig struct {
|
||||||
|
Username string `mapstructure:"username"`
|
||||||
|
Password string `mapstructure:"password"`
|
||||||
|
Phone string `mapstructure:"phone"`
|
||||||
|
}
|
||||||
|
|
||||||
// Validate 验证配置值
|
// Validate 验证配置值
|
||||||
func (c *Config) Validate() error {
|
func (c *Config) Validate() error {
|
||||||
// 服务器验证
|
// 服务器验证
|
||||||
|
|||||||
@@ -116,3 +116,14 @@ const (
|
|||||||
VerificationCodeExpiration = 5 * time.Minute // 验证码过期时间(5分钟)
|
VerificationCodeExpiration = 5 * time.Minute // 验证码过期时间(5分钟)
|
||||||
VerificationCodeRateLimit = 60 * time.Second // 验证码发送频率限制(60秒)
|
VerificationCodeRateLimit = 60 * time.Second // 验证码发送频率限制(60秒)
|
||||||
)
|
)
|
||||||
|
|
||||||
|
// ======== 默认超级管理员账号配置(用于系统初始化) ========
|
||||||
|
|
||||||
|
// DefaultAdminUsername 默认超级管理员用户名
|
||||||
|
const DefaultAdminUsername = "admin"
|
||||||
|
|
||||||
|
// DefaultAdminPassword 默认超级管理员密码
|
||||||
|
const DefaultAdminPassword = "Admin@123456"
|
||||||
|
|
||||||
|
// DefaultAdminPhone 默认超级管理员手机号
|
||||||
|
const DefaultAdminPhone = "13800000000"
|
||||||
|
|||||||
Reference in New Issue
Block a user