119 lines
3.7 KiB
Go
119 lines
3.7 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 使用默认页容量搜索并分页。
|
|
func ListUsers(db *sql.DB, actor *model.User, keyword string, status model.UserStatus, requestedPage int) (*UserListResult, error) {
|
|
return ListUsersWithPageSize(db, actor, keyword, status, requestedPage, DefaultPageSize)
|
|
}
|
|
|
|
// ListUsersWithPageSize 按白名单页容量搜索并分页。
|
|
func ListUsersWithPageSize(db *sql.DB, actor *model.User, keyword string, status model.UserStatus, requestedPage, pageSize int) (*UserListResult, error) {
|
|
pageSize = NormalizePageSize(pageSize)
|
|
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, pageSize)
|
|
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))
|
|
}
|