feat(account): 实现平台账号管理功能
- 新增平台账号列表查询接口(自动筛选超级管理员和平台用户) - 新增密码修改和状态切换专用接口 - 增强角色分配功能,支持空数组清空所有角色 - 新增超级管理员保护机制,禁止分配角色 - 新增完整的集成测试和OpenSpec规范文档
This commit is contained in:
@@ -210,15 +210,13 @@ func (s *Service) List(ctx context.Context, req *model.AccountListRequest) ([]*m
|
||||
return s.accountStore.List(ctx, opts, filters)
|
||||
}
|
||||
|
||||
// AssignRoles 为账号分配角色
|
||||
// AssignRoles 为账号分配角色(支持空数组清空所有角色,超级管理员禁止分配)
|
||||
func (s *Service) AssignRoles(ctx context.Context, accountID uint, roleIDs []uint) ([]*model.AccountRole, error) {
|
||||
// 获取当前用户 ID
|
||||
currentUserID := middleware.GetUserIDFromContext(ctx)
|
||||
if currentUserID == 0 {
|
||||
return nil, errors.New(errors.CodeUnauthorized, "未授权访问")
|
||||
}
|
||||
|
||||
// 检查账号存在
|
||||
account, err := s.accountStore.GetByID(ctx, accountID)
|
||||
if err != nil {
|
||||
if err == gorm.ErrRecordNotFound {
|
||||
@@ -227,19 +225,29 @@ func (s *Service) AssignRoles(ctx context.Context, accountID uint, roleIDs []uin
|
||||
return nil, fmt.Errorf("获取账号失败: %w", err)
|
||||
}
|
||||
|
||||
// 检查用户类型是否允许分配角色
|
||||
// 超级管理员禁止分配角色
|
||||
if account.UserType == constants.UserTypeSuperAdmin {
|
||||
return nil, errors.New(errors.CodeInvalidParam, "超级管理员不允许分配角色")
|
||||
}
|
||||
|
||||
// 空数组:清空所有角色
|
||||
if len(roleIDs) == 0 {
|
||||
if err := s.accountRoleStore.DeleteByAccountID(ctx, accountID); err != nil {
|
||||
return nil, fmt.Errorf("清空账号角色失败: %w", err)
|
||||
}
|
||||
return []*model.AccountRole{}, nil
|
||||
}
|
||||
|
||||
maxRoles := constants.GetMaxRolesForUserType(account.UserType)
|
||||
if maxRoles == 0 {
|
||||
return nil, errors.New(errors.CodeInvalidParam, "该用户类型不需要分配角色")
|
||||
}
|
||||
|
||||
// 检查角色数量限制
|
||||
existingCount, err := s.accountRoleStore.CountByAccountID(ctx, accountID)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("统计现有角色数量失败: %w", err)
|
||||
}
|
||||
|
||||
// 计算将要分配的新角色数量(排除已存在的)
|
||||
newRoleCount := 0
|
||||
for _, roleID := range roleIDs {
|
||||
exists, _ := s.accountRoleStore.Exists(ctx, accountID, roleID)
|
||||
@@ -248,12 +256,10 @@ func (s *Service) AssignRoles(ctx context.Context, accountID uint, roleIDs []uin
|
||||
}
|
||||
}
|
||||
|
||||
// 检查角色数量限制(-1 表示无限制)
|
||||
if maxRoles != -1 && int(existingCount)+newRoleCount > maxRoles {
|
||||
return nil, errors.New(errors.CodeInvalidParam, fmt.Sprintf("该用户类型最多只能分配 %d 个角色", maxRoles))
|
||||
}
|
||||
|
||||
// 验证所有角色存在并检查角色类型是否匹配
|
||||
for _, roleID := range roleIDs {
|
||||
role, err := s.roleStore.GetByID(ctx, roleID)
|
||||
if err != nil {
|
||||
@@ -263,19 +269,16 @@ func (s *Service) AssignRoles(ctx context.Context, accountID uint, roleIDs []uin
|
||||
return nil, fmt.Errorf("获取角色失败: %w", err)
|
||||
}
|
||||
|
||||
// 检查角色类型与用户类型是否匹配
|
||||
if !constants.IsRoleTypeMatchUserType(role.RoleType, account.UserType) {
|
||||
return nil, errors.New(errors.CodeInvalidParam, "角色类型与账号类型不匹配")
|
||||
}
|
||||
}
|
||||
|
||||
// 创建关联
|
||||
var ars []*model.AccountRole
|
||||
for _, roleID := range roleIDs {
|
||||
// 检查是否已分配
|
||||
exists, _ := s.accountRoleStore.Exists(ctx, accountID, roleID)
|
||||
if exists {
|
||||
continue // 跳过已存在的关联
|
||||
continue
|
||||
}
|
||||
|
||||
ar := &model.AccountRole{
|
||||
@@ -344,6 +347,83 @@ func (s *Service) ValidatePassword(plainPassword, hashedPassword string) bool {
|
||||
return err == nil
|
||||
}
|
||||
|
||||
// UpdatePassword 修改账号密码(管理员重置场景,无需旧密码)
|
||||
func (s *Service) UpdatePassword(ctx context.Context, accountID uint, newPassword string) error {
|
||||
currentUserID := middleware.GetUserIDFromContext(ctx)
|
||||
if currentUserID == 0 {
|
||||
return errors.New(errors.CodeUnauthorized, "未授权访问")
|
||||
}
|
||||
|
||||
_, err := s.accountStore.GetByID(ctx, accountID)
|
||||
if err != nil {
|
||||
if err == gorm.ErrRecordNotFound {
|
||||
return errors.New(errors.CodeAccountNotFound, "账号不存在")
|
||||
}
|
||||
return fmt.Errorf("获取账号失败: %w", err)
|
||||
}
|
||||
|
||||
hashedPassword, err := bcrypt.GenerateFromPassword([]byte(newPassword), bcrypt.DefaultCost)
|
||||
if err != nil {
|
||||
return fmt.Errorf("密码哈希失败: %w", err)
|
||||
}
|
||||
|
||||
if err := s.accountStore.UpdatePassword(ctx, accountID, string(hashedPassword), currentUserID); err != nil {
|
||||
return fmt.Errorf("更新密码失败: %w", err)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// UpdateStatus 修改账号状态(启用/禁用)
|
||||
func (s *Service) UpdateStatus(ctx context.Context, accountID uint, status int) error {
|
||||
currentUserID := middleware.GetUserIDFromContext(ctx)
|
||||
if currentUserID == 0 {
|
||||
return errors.New(errors.CodeUnauthorized, "未授权访问")
|
||||
}
|
||||
|
||||
_, err := s.accountStore.GetByID(ctx, accountID)
|
||||
if err != nil {
|
||||
if err == gorm.ErrRecordNotFound {
|
||||
return errors.New(errors.CodeAccountNotFound, "账号不存在")
|
||||
}
|
||||
return fmt.Errorf("获取账号失败: %w", err)
|
||||
}
|
||||
|
||||
if err := s.accountStore.UpdateStatus(ctx, accountID, status, currentUserID); err != nil {
|
||||
return fmt.Errorf("更新状态失败: %w", err)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// ListPlatformAccounts 查询平台账号列表(自动筛选 user_type IN (1, 2))
|
||||
func (s *Service) ListPlatformAccounts(ctx context.Context, req *model.PlatformAccountListRequest) ([]*model.Account, int64, error) {
|
||||
opts := &store.QueryOptions{
|
||||
Page: req.Page,
|
||||
PageSize: req.PageSize,
|
||||
OrderBy: "id DESC",
|
||||
}
|
||||
if opts.Page == 0 {
|
||||
opts.Page = 1
|
||||
}
|
||||
if opts.PageSize == 0 {
|
||||
opts.PageSize = constants.DefaultPageSize
|
||||
}
|
||||
|
||||
filters := make(map[string]interface{})
|
||||
if req.Username != "" {
|
||||
filters["username"] = req.Username
|
||||
}
|
||||
if req.Phone != "" {
|
||||
filters["phone"] = req.Phone
|
||||
}
|
||||
if req.Status != nil {
|
||||
filters["status"] = *req.Status
|
||||
}
|
||||
|
||||
return s.accountStore.ListPlatformAccounts(ctx, opts, filters)
|
||||
}
|
||||
|
||||
// CreateSystemAccount 系统内部创建账号方法,用于系统初始化场景(绕过当前用户检查)
|
||||
func (s *Service) CreateSystemAccount(ctx context.Context, account *model.Account) error {
|
||||
if account.Username == "" {
|
||||
|
||||
Reference in New Issue
Block a user