Files
cmautobuy/admin/service/user.go
T

113 lines
3.4 KiB
Go

// Admin 用户管理业务:列表、新增采购员、启停和重置密码。
package service
import (
"database/sql"
"errors"
"strings"
"time"
"cmautobuy/admin/model"
"cmautobuy/admin/repository"
)
var ErrAdminRequired = errors.New("只有管理员可以执行此管理操作")
// UserListResult 是用户管理页的分页结果。
type UserListResult struct {
Rows []model.User
Keyword string
Status model.UserStatus
Page int
Total int
TotalPages int
}
// ListUsers 搜索并分页。用户页沿用全站统一的每页 20 条规则。
func ListUsers(db *sql.DB, actor *model.User, keyword string, status model.UserStatus, requestedPage int) (*UserListResult, error) {
if actor == nil || !actor.IsAdmin() {
return nil, ErrAdminRequired
}
keyword = strings.TrimSpace(keyword)
if status != model.UserActive && status != model.UserDisabled {
status = ""
}
page := requestedPage
if page < 1 {
page = 1
}
rows, total, err := repository.ListUsers(db, keyword, status, PageSize, (page-1)*PageSize)
if err != nil {
return nil, err
}
totalPages := TotalPages(total)
clampedPage := ClampPage(page, totalPages)
if clampedPage != page {
page = clampedPage
rows, _, err = repository.ListUsers(db, keyword, status, PageSize, (page-1)*PageSize)
if err != nil {
return nil, err
}
}
return &UserListResult{
Rows: rows, Keyword: keyword, Status: status,
Page: page, Total: total, TotalPages: totalPages,
}, nil
}
// CreatePurchaser 创建固定 purchaser 角色的账号,不提供第二管理员入口。
func CreatePurchaser(db *sql.DB, actor *model.User, username, password, confirmation string, now time.Time) error {
if actor == nil || !actor.IsAdmin() {
return ErrAdminRequired
}
username = strings.TrimSpace(username)
if err := validateUsername(username); err != nil {
return err
}
hash, err := hashConfirmedPassword(password, confirmation)
if err != nil {
return err
}
userID, err := randomID("USR-", 16)
if err != nil {
return err
}
at := now.UTC().Format(model.TimeLayout)
return repository.CreateUser(db, model.User{
UserID: userID, Username: username, PasswordHash: string(hash),
Role: model.RolePurchaser, Status: model.UserActive,
PasswordChangedAt: at, CreatedAt: at, UpdatedAt: at,
})
}
// ChangeUserStatus 启用或禁用账号。数据库事务负责最后管理员保护和 Session 撤销。
func ChangeUserStatus(db *sql.DB, actor *model.User, userID string, status model.UserStatus, now time.Time) error {
if actor == nil || !actor.IsAdmin() {
return ErrAdminRequired
}
if status != model.UserActive && status != model.UserDisabled {
return invalidInput("账号状态无效")
}
if strings.TrimSpace(userID) == "" {
return invalidInput("用户编号不能为空")
}
return repository.SetUserStatusAndRevokeSessions(
db, userID, status, now.UTC().Format(model.TimeLayout))
}
// ResetUserPassword 更新密码哈希,并在同一事务中撤销目标用户全部 Session。
func ResetUserPassword(db *sql.DB, actor *model.User, userID, password, confirmation string, now time.Time) error {
if actor == nil || !actor.IsAdmin() {
return ErrAdminRequired
}
if strings.TrimSpace(userID) == "" {
return invalidInput("用户编号不能为空")
}
hash, err := hashConfirmedPassword(password, confirmation)
if err != nil {
return err
}
return repository.ResetUserPasswordAndRevokeSessions(
db, userID, string(hash), now.UTC().Format(model.TimeLayout))
}