package repository import ( "errors" "sort" "strings" "hyapp-admin-server/internal/appctx" "hyapp-admin-server/internal/model" "gorm.io/gorm" ) type AppAccess struct { Mode string AppCodes []string } func (access AppAccess) Allows(appCode string) bool { appCode = strings.ToLower(strings.TrimSpace(appCode)) if appCode == "" { return false } if access.Mode == model.UserAppScopeModeAll { return true } for _, allowed := range access.AppCodes { if allowed == appCode { return true } } return false } func (s *Store) AppAccessForUser(userID uint) (AppAccess, error) { if userID == 0 { return AppAccess{Mode: model.UserAppScopeModeNone}, nil } var user model.User if err := s.db.Select("id", "app_scope_mode").First(&user, userID).Error; err != nil { return AppAccess{}, err } mode := normalizeAppScopeMode(user.AppScopeMode) if mode != model.UserAppScopeModeSelected { return AppAccess{Mode: mode}, nil } var scopes []model.UserAppScope if err := s.db.Where("user_id = ?", userID).Order("app_code ASC").Find(&scopes).Error; err != nil { return AppAccess{}, err } appCodes := make([]string, 0, len(scopes)) for _, scope := range scopes { appCodes = append(appCodes, scope.AppCode) } return AppAccess{Mode: mode, AppCodes: appCodes}, nil } func (s *Store) ReplaceUserAppScopes(userID uint, mode string, appCodes []string) (AppAccess, error) { if userID == 0 { return AppAccess{}, errors.New("user id is required") } rawMode := strings.ToLower(strings.TrimSpace(mode)) if rawMode != model.UserAppScopeModeAll && rawMode != model.UserAppScopeModeSelected && rawMode != model.UserAppScopeModeNone { return AppAccess{}, errors.New("app scope mode must be all, selected, or none") } mode = rawMode normalizedCodes := normalizeAppCodes(appCodes) if mode == model.UserAppScopeModeSelected && len(normalizedCodes) == 0 { return AppAccess{}, errors.New("selected app scope requires app codes") } if mode != model.UserAppScopeModeSelected { normalizedCodes = nil } err := s.db.Transaction(func(tx *gorm.DB) error { var user model.User if err := tx.First(&user, userID).Error; err != nil { return err } // 先删除旧明细再更新模式,使 all/none 不残留会被未来代码误读的 selected 行。 if err := tx.Where("user_id = ?", userID).Delete(&model.UserAppScope{}).Error; err != nil { return err } if len(normalizedCodes) > 0 { scopes := make([]model.UserAppScope, 0, len(normalizedCodes)) for _, appCode := range normalizedCodes { scopes = append(scopes, model.UserAppScope{UserID: userID, AppCode: appCode}) } if err := tx.Create(&scopes).Error; err != nil { return err } } return tx.Model(&user).Update("app_scope_mode", mode).Error }) if err != nil { return AppAccess{}, err } return AppAccess{Mode: mode, AppCodes: normalizedCodes}, nil } func normalizeAppScopeMode(mode string) string { switch strings.ToLower(strings.TrimSpace(mode)) { case model.UserAppScopeModeAll: return model.UserAppScopeModeAll case model.UserAppScopeModeSelected: return model.UserAppScopeModeSelected default: return model.UserAppScopeModeNone } } func normalizeAppCodes(appCodes []string) []string { seen := make(map[string]struct{}, len(appCodes)) out := make([]string, 0, len(appCodes)) for _, value := range appCodes { // appctx.Normalize 会把空值回退 lalu;App scope 的空值代表无授权,必须先独立过滤。 value = strings.ToLower(strings.TrimSpace(value)) if value == "" { continue } value = appctx.Normalize(value) if _, ok := seen[value]; ok { continue } seen[value] = struct{}{} out = append(out, value) } sort.Strings(out) return out }