feat: 迁移 Admin Repository 到 MySQL 8 (#79)
This commit is contained in:
@@ -5,9 +5,9 @@ import (
|
||||
"database/sql"
|
||||
"errors"
|
||||
"fmt"
|
||||
"strings"
|
||||
|
||||
"cmautobuy/admin/model"
|
||||
"github.com/go-sql-driver/mysql"
|
||||
)
|
||||
|
||||
var (
|
||||
@@ -63,14 +63,18 @@ func ChangePasswordAndRevokeSessions(db *sql.DB, userID, expectedHash, passwordH
|
||||
return nil
|
||||
}
|
||||
|
||||
// CreateInitialAdmin 在写事务里再次确认 users 为空后创建首位管理员。
|
||||
// Open 的 _txlock=immediate 让并发事务从一开始就串行,最多一个能成功。
|
||||
// CreateInitialAdmin 锁住唯一的初始化哨兵行,再确认 users 为空并创建首位管理员。
|
||||
// 并发请求会在 FOR UPDATE 处串行,最多一个能成功。
|
||||
func CreateInitialAdmin(db *sql.DB, user model.User) error {
|
||||
tx, err := db.Begin()
|
||||
if err != nil {
|
||||
return fmt.Errorf("开始初始化管理员事务失败: %w", err)
|
||||
}
|
||||
defer tx.Rollback()
|
||||
var lockID int
|
||||
if err := tx.QueryRow(`SELECT id FROM admin_initialization_lock WHERE id = 1 FOR UPDATE`).Scan(&lockID); err != nil {
|
||||
return fmt.Errorf("锁定管理员初始化状态失败: %w", err)
|
||||
}
|
||||
|
||||
count, err := CountUsers(tx)
|
||||
if err != nil {
|
||||
@@ -189,7 +193,7 @@ func DeleteExpiredSessions(q Execer, nowISO string) error {
|
||||
// ListUsers 按用户名搜索并分页。列表和 COUNT 共用同一段筛选,避免页码漂移。
|
||||
func ListUsers(q Execer, keyword string, status model.UserStatus, limit, offset int) ([]model.User, int, error) {
|
||||
pattern := "%" + escapeLike(keyword) + "%"
|
||||
where := `WHERE username LIKE ? ESCAPE '\'`
|
||||
where := `WHERE username LIKE ? ESCAPE '!'`
|
||||
args := []any{pattern}
|
||||
if status == model.UserActive || status == model.UserDisabled {
|
||||
where += ` AND status = ?`
|
||||
@@ -239,7 +243,8 @@ func CreateUser(q Execer, user model.User) error {
|
||||
user.UserID, user.Username, user.PasswordHash, user.Role, user.Status,
|
||||
user.PasswordChangedAt, user.CreatedAt, user.UpdatedAt)
|
||||
if err != nil {
|
||||
if strings.Contains(err.Error(), "UNIQUE constraint failed: users.username") {
|
||||
var mysqlErr *mysql.MySQLError
|
||||
if errors.As(err, &mysqlErr) && mysqlErr.Number == 1062 {
|
||||
return ErrUsernameExists
|
||||
}
|
||||
return fmt.Errorf("创建 Admin 用户失败: %w", err)
|
||||
@@ -258,15 +263,27 @@ func SetUserStatusAndRevokeSessions(db *sql.DB, userID string, status model.User
|
||||
|
||||
var role model.UserRole
|
||||
var current model.UserStatus
|
||||
if err := tx.QueryRow(`SELECT role, status FROM users WHERE user_id = ?`, userID).Scan(&role, ¤t); errors.Is(err, sql.ErrNoRows) {
|
||||
if err := tx.QueryRow(`SELECT role, status FROM users WHERE user_id = ? FOR UPDATE`, userID).Scan(&role, ¤t); errors.Is(err, sql.ErrNoRows) {
|
||||
return ErrUserNotFound
|
||||
} else if err != nil {
|
||||
return fmt.Errorf("读取待修改用户失败: %w", err)
|
||||
}
|
||||
if status == model.UserDisabled && current == model.UserActive && role == model.RoleAdmin {
|
||||
var activeAdmins int
|
||||
if err := tx.QueryRow(`SELECT COUNT(*) FROM users WHERE role = 'admin' AND status = 'active'`).Scan(&activeAdmins); err != nil {
|
||||
return fmt.Errorf("统计有效管理员失败: %w", err)
|
||||
rows, err := tx.Query(`SELECT user_id FROM users WHERE role = 'admin' AND status = 'active' FOR UPDATE`)
|
||||
if err != nil {
|
||||
return fmt.Errorf("锁定有效管理员失败: %w", err)
|
||||
}
|
||||
activeAdmins := 0
|
||||
for rows.Next() {
|
||||
var activeID string
|
||||
if err := rows.Scan(&activeID); err != nil {
|
||||
rows.Close()
|
||||
return fmt.Errorf("读取有效管理员失败: %w", err)
|
||||
}
|
||||
activeAdmins++
|
||||
}
|
||||
if err := rows.Close(); err != nil {
|
||||
return fmt.Errorf("读取有效管理员失败: %w", err)
|
||||
}
|
||||
if activeAdmins <= 1 {
|
||||
return ErrLastActiveAdmin
|
||||
|
||||
Reference in New Issue
Block a user