diff --git a/README.md b/README.md index a84c6f3..5a6782a 100644 --- a/README.md +++ b/README.md @@ -32,16 +32,15 @@ open http://localhost:3018 | 后端 API | 3019 | | PostgreSQL | 5432 | -## 创建首个系统管理员 +## 默认系统管理员 -系统不内置默认账号。启动后,先注册一个普通用户,再通过 SQL 将其提升为系统管理员: +系统启动时会自动创建一个系统管理员账号(仅当不存在时): -```bash -docker compose exec db psql -U stock -d stock -c " -UPDATE users SET role_id = (SELECT id FROM roles WHERE name = 'system_admin') -WHERE username = '你的用户名'; -" -``` +| 用户名 | 密码 | +|---|---| +| `system_admin` | `system_admin` | + +整个系统只能有一个系统管理员账号,无法通过管理后台再创建或提升其他系统管理员。 ## 角色说明 diff --git a/backend/cmd/api/main.go b/backend/cmd/api/main.go index 5f7b1e7..e1cc03f 100644 --- a/backend/cmd/api/main.go +++ b/backend/cmd/api/main.go @@ -30,6 +30,10 @@ func main() { log.Fatalf("seed roles: %v", err) } + if err := models.SeedSystemAdmin(database); err != nil { + log.Fatalf("seed system admin: %v", err) + } + r := routes.Setup(cfg, database) addr := fmt.Sprintf("0.0.0.0:%s", cfg.Port) diff --git a/backend/internal/handlers/admin.go b/backend/internal/handlers/admin.go index 3b66ec4..6f156ef 100644 --- a/backend/internal/handlers/admin.go +++ b/backend/internal/handlers/admin.go @@ -161,6 +161,10 @@ func (h *AdminHandler) UpdateUser(c *gin.Context) { } if req.Status != "" { + if target.Role.NameEnum() == models.RoleSystemAdmin && req.Status != "active" { + c.JSON(http.StatusForbidden, gin.H{"success": false, "error": "不能禁用系统管理员账号"}) + return + } updates["status"] = req.Status } @@ -170,6 +174,10 @@ func (h *AdminHandler) UpdateUser(c *gin.Context) { c.JSON(http.StatusBadRequest, gin.H{"success": false, "error": "无效的角色"}) return } + if target.Role.NameEnum() == models.RoleSystemAdmin && newRole != models.RoleSystemAdmin { + c.JSON(http.StatusForbidden, gin.H{"success": false, "error": "不能修改系统管理员账号的角色"}) + return + } if !containsRole(allowedRolesForCreation(current.Role), newRole) || !current.Role.CanManage(newRole) { c.JSON(http.StatusForbidden, gin.H{"success": false, "error": "权限不足"}) return @@ -211,6 +219,11 @@ func (h *AdminHandler) DeleteUser(c *gin.Context) { return } + if target.Role.NameEnum() == models.RoleSystemAdmin { + c.JSON(http.StatusForbidden, gin.H{"success": false, "error": "不能删除系统管理员账号"}) + return + } + if err := h.DB.Delete(&target).Error; err != nil { c.JSON(http.StatusInternalServerError, gin.H{"success": false, "error": "internal server error"}) return diff --git a/backend/internal/models/user.go b/backend/internal/models/user.go index ec037e9..feeb368 100644 --- a/backend/internal/models/user.go +++ b/backend/internal/models/user.go @@ -4,6 +4,7 @@ import ( "fmt" "time" + "golang.org/x/crypto/bcrypt" "gorm.io/gorm" ) @@ -118,6 +119,33 @@ func SeedRoles(db *gorm.DB) error { return nil } +// SeedSystemAdmin 在没有 system_admin 用户时创建默认系统管理员账号。 +func SeedSystemAdmin(db *gorm.DB) error { + var existing User + if err := db.Where("username = ?", "system_admin").First(&existing).Error; err == nil { + return nil + } else if err != gorm.ErrRecordNotFound { + return err + } + + var role Role + if err := db.Where("name = ?", RoleSystemAdmin).First(&role).Error; err != nil { + return err + } + + hash, err := bcrypt.GenerateFromPassword([]byte("system_admin"), bcrypt.DefaultCost) + if err != nil { + return err + } + + return db.Create(&User{ + Username: "system_admin", + PasswordHash: string(hash), + RoleID: role.ID, + Status: "active", + }).Error +} + func strPtr(s string) *string { return &s }