package service import ( "errors" "affiliate_dash/internal/model" "golang.org/x/crypto/bcrypt" "gorm.io/gorm" "gorm.io/gorm/clause" ) type UserService struct { db *gorm.DB tenant *TenantService } func NewUserService(db *gorm.DB, tenant *TenantService) *UserService { return &UserService{db: db, tenant: tenant} } type UserListQuery struct { MerchantID uint Page int Size int Keyword string Role string Status *int } // MerchantMemberGroup presents merchant accounts in their actual ownership // hierarchy: one merchant and all of its employee accounts. type MerchantMemberGroup struct { MerchantID uint `json:"merchant_id"` Merchant *model.Merchant `json:"merchant,omitempty"` Members []model.MerchantMember `json:"members"` } func (s *UserService) List(q UserListQuery) ([]model.User, int64, error) { if q.Page < 1 { q.Page = 1 } if q.Size < 1 || q.Size > 100 { q.Size = 20 } tx := s.db.Model(&model.User{}) if q.MerchantID != 0 { tx = tx.Joins("JOIN merchant_members ON merchant_members.user_id = users.id"). Where("merchant_members.merchant_id = ?", q.MerchantID) } if q.Keyword != "" { like := "%" + q.Keyword + "%" tx = tx.Where("users.username LIKE ? OR users.nickname LIKE ?", like, like) } if q.Role != "" { tx = tx.Where("users.role = ?", q.Role) } if q.Status != nil { tx = tx.Where("users.status = ?", *q.Status) } var total int64 if err := tx.Count(&total).Error; err != nil { return nil, 0, err } var list []model.User err := tx.Order("users.id DESC").Offset((q.Page - 1) * q.Size).Limit(q.Size).Find(&list).Error return list, total, err } func (s *UserService) Create(username, password, nickname, role string, merchantID uint) (*model.User, error) { var count int64 s.db.Model(&model.User{}).Where("username = ?", username).Count(&count) if count > 0 { return nil, errors.New("用户名已存在") } if role == "" { role = model.RoleMerchant } if role != model.RoleAdmin && role != model.RoleMerchant { return nil, errors.New("无效的账号角色") } hash, err := bcrypt.GenerateFromPassword([]byte(password), bcrypt.DefaultCost) if err != nil { return nil, err } user := &model.User{ Username: username, PasswordHash: string(hash), Nickname: nickname, Role: role, Status: 1, } if user.Nickname == "" { user.Nickname = username } err = s.db.Transaction(func(tx *gorm.DB) error { if err := tx.Create(user).Error; err != nil { return err } if role == model.RoleAdmin && s.tenant != nil { var selfMerchant model.Merchant if err := tx.Where("code = ?", model.MerchantCodeSelfOperated).First(&selfMerchant).Error; err != nil { return err } member := model.MerchantMember{MerchantID: selfMerchant.ID, UserID: user.ID, Role: model.MemberRoleOwner, Status: user.Status, IsDefault: true} if err := tx.Clauses(clause.OnConflict{ Columns: []clause.Column{{Name: "merchant_id"}, {Name: "user_id"}}, DoUpdates: clause.Assignments(map[string]interface{}{ "role": model.MemberRoleOwner, "status": user.Status, "is_default": true, }), }).Create(&member).Error; err != nil { return err } return nil } if s.tenant != nil && merchantID != 0 { memberRole := model.MemberRoleOperator member := model.MerchantMember{ MerchantID: merchantID, UserID: user.ID, Role: memberRole, Status: user.Status, IsDefault: false, } if err := tx.Create(&member).Error; err != nil { return err } } return nil }) if err != nil { return nil, err } return user, nil } type UpdatePlatformAdminInput struct { Username string Nickname string Password string Status int } func (s *UserService) ListPlatformAdmins(page, size int) ([]model.User, int64, error) { return s.List(UserListQuery{Page: page, Size: size, Role: model.RoleAdmin}) } // ListMerchantAccountGroups returns merchant employee accounts grouped by // merchant. Platform administrators are excluded from this independent view. func (s *UserService) ListMerchantAccountGroups(page, size int) ([]MerchantMemberGroup, int64, error) { page, size = normalizePage(page, size) tx := s.db.Model(&model.MerchantMember{}). Joins("JOIN users ON users.id = merchant_members.user_id AND users.deleted_at IS NULL"). Where("users.role = ?", model.RoleMerchant) var total int64 if err := tx.Distinct("merchant_members.merchant_id").Count(&total).Error; err != nil { return nil, 0, err } var merchantIDs []uint if err := tx.Distinct("merchant_members.merchant_id"). Order("merchant_members.merchant_id ASC"). Offset((page-1)*size).Limit(size). Pluck("merchant_members.merchant_id", &merchantIDs).Error; err != nil { return nil, 0, err } if len(merchantIDs) == 0 { return []MerchantMemberGroup{}, total, nil } var members []model.MerchantMember if err := s.db.Model(&model.MerchantMember{}). Joins("JOIN users ON users.id = merchant_members.user_id AND users.deleted_at IS NULL"). Where("users.role = ? AND merchant_members.merchant_id IN ?", model.RoleMerchant, merchantIDs). Preload("Merchant").Preload("User"). Order("merchant_members.merchant_id ASC, merchant_members.id ASC"). Find(&members).Error; err != nil { return nil, 0, err } groups := make([]MerchantMemberGroup, 0, len(merchantIDs)) byMerchantID := make(map[uint]*MerchantMemberGroup, len(merchantIDs)) for _, merchantID := range merchantIDs { groups = append(groups, MerchantMemberGroup{MerchantID: merchantID, Members: []model.MerchantMember{}}) byMerchantID[merchantID] = &groups[len(groups)-1] } for _, member := range members { group := byMerchantID[member.MerchantID] if group.Merchant == nil { group.Merchant = member.Merchant } group.Members = append(group.Members, member) } return groups, total, nil } // CreatePlatformAdmin creates an account with platform privileges and the // hidden self-operated merchant context required by merchant-side routes. func (s *UserService) CreatePlatformAdmin(username, password, nickname string) (*model.User, error) { return s.Create(username, password, nickname, model.RoleAdmin, 0) } func (s *UserService) UpdatePlatformAdmin(id uint, in UpdatePlatformAdminInput, actorUserID uint) (*model.User, error) { if id == 0 { return nil, errors.New("用户不存在") } if id == actorUserID { return nil, errors.New("当前账号请通过右上角账户菜单修改密码") } if in.Status != 0 && in.Status != 1 { return nil, errors.New("用户状态无效") } returnUser := &model.User{} err := s.db.Transaction(func(tx *gorm.DB) error { var user model.User if err := tx.First(&user, id).Error; err != nil { if errors.Is(err, gorm.ErrRecordNotFound) { return errors.New("用户不存在") } return err } if user.Role != model.RoleAdmin { return errors.New("只能管理平台管理员账号") } if user.Status == 1 && in.Status == 0 { var admins int64 if err := tx.Model(&model.User{}).Where("role = ? AND status = ?", model.RoleAdmin, 1).Count(&admins).Error; err != nil { return err } if admins <= 1 { return errors.New("至少保留一个启用的平台管理员") } } if in.Username != user.Username { var count int64 if err := tx.Model(&model.User{}).Where("username = ? AND id <> ?", in.Username, user.ID).Count(&count).Error; err != nil { return err } if count > 0 { return errors.New("用户名已存在") } } updates := map[string]interface{}{"username": in.Username, "nickname": in.Nickname, "status": in.Status} if in.Password != "" { hash, err := bcrypt.GenerateFromPassword([]byte(in.Password), bcrypt.DefaultCost) if err != nil { return err } updates["password_hash"] = string(hash) } if err := tx.Model(&user).Updates(updates).Error; err != nil { return err } return tx.First(returnUser, user.ID).Error }) if err != nil { return nil, err } return returnUser, nil } func (s *UserService) UpdateStatus(id uint, status int) error { if status != 0 && status != 1 { return errors.New("用户状态无效") } return s.db.Transaction(func(tx *gorm.DB) error { var user model.User if err := tx.First(&user, id).Error; err != nil { if errors.Is(err, gorm.ErrRecordNotFound) { return errors.New("用户不存在") } return err } if user.Role == model.RoleAdmin && user.Status == 1 && status == 0 { var admins int64 if err := tx.Model(&model.User{}).Where("role = ? AND status = ?", model.RoleAdmin, 1).Count(&admins).Error; err != nil { return err } if admins <= 1 { return errors.New("至少保留一个启用的平台管理员") } } return tx.Model(&user).Update("status", status).Error }) } // DeletePlatformAdmin removes a platform-only account. It protects the current // account and the final enabled platform administrator so the platform cannot be locked out. func (s *UserService) DeletePlatformAdmin(id, actorUserID uint) error { if id == 0 { return errors.New("用户不存在") } if id == actorUserID { return errors.New("不能删除当前登录账号") } return s.db.Transaction(func(tx *gorm.DB) error { var user model.User if err := tx.First(&user, id).Error; err != nil { if errors.Is(err, gorm.ErrRecordNotFound) { return errors.New("用户不存在") } return err } if user.Role != model.RoleAdmin { return errors.New("只能删除平台管理员账号") } if user.Role == model.RoleAdmin && user.Status == 1 { var admins int64 if err := tx.Model(&model.User{}).Where("role = ? AND status = ?", model.RoleAdmin, 1).Count(&admins).Error; err != nil { return err } if admins <= 1 { return errors.New("至少保留一个启用的平台管理员") } } return tx.Delete(&user).Error }) } // DeleteMerchantAccount permanently removes a merchant login account and all // of its merchant memberships. Platform administrators must be managed from // the dedicated platform administrator view instead. func (s *UserService) DeleteMerchantAccount(id, actorUserID uint) error { if id == 0 { return errors.New("用户不存在") } if id == actorUserID { return errors.New("不能删除当前登录账号") } return s.db.Transaction(func(tx *gorm.DB) error { var user model.User if err := tx.First(&user, id).Error; err != nil { if errors.Is(err, gorm.ErrRecordNotFound) { return errors.New("用户不存在") } return err } if user.Role != model.RoleMerchant { return errors.New("平台管理员请在平台管理员列表中管理") } if err := tx.Where("user_id = ?", user.ID).Delete(&model.MerchantMember{}).Error; err != nil { return err } return tx.Delete(&user).Error }) }