package user import ( "context" "strings" "hyapp/pkg/appcode" "hyapp/pkg/xerr" userdomain "hyapp/services/user-service/internal/domain/user" ) const ( maxPushDeviceIDLen = 128 maxPushTokenLen = 512 maxPushProviderLen = 32 maxPushPlatformLen = 16 maxPushAppVersionLen = 64 maxPushLanguageLen = 32 maxPushTimezoneLen = 64 ) // BindPushToken 绑定当前登录用户的系统推送 token。 // 该能力不影响 App 进入主界面;失败只影响离线消息触达。 func (s *Service) BindPushToken(ctx context.Context, userID int64, deviceID string, pushToken string, provider string, platform string, appVersion string, language string, timezone string, requestID string) (int64, error) { if s.deviceRepository == nil { return 0, xerr.New(xerr.Unavailable, "device repository is not configured") } if err := s.requireActiveCompletedUser(ctx, userID); err != nil { return 0, err } command, err := normalizePushTokenCommand(ctx, userID, deviceID, pushToken, provider, platform, appVersion, language, timezone, requestID, s.now().UnixMilli()) if err != nil { return 0, err } if err := s.deviceRepository.BindPushToken(ctx, command); err != nil { return 0, err } return command.UpdatedAtMs, nil } // DeletePushToken 失效当前登录用户某个设备的推送 token。 func (s *Service) DeletePushToken(ctx context.Context, userID int64, deviceID string, pushToken string, requestID string) (bool, int64, error) { if s.deviceRepository == nil { return false, 0, xerr.New(xerr.Unavailable, "device repository is not configured") } if userID <= 0 { return false, 0, xerr.New(xerr.InvalidArgument, "user_id is required") } deviceID = strings.TrimSpace(deviceID) pushToken = strings.TrimSpace(pushToken) if deviceID == "" || len(deviceID) > maxPushDeviceIDLen { return false, 0, xerr.New(xerr.InvalidArgument, "device_id is invalid") } if len(pushToken) > maxPushTokenLen { return false, 0, xerr.New(xerr.InvalidArgument, "push_token is invalid") } nowMs := s.now().UnixMilli() deleted, err := s.deviceRepository.DeletePushToken(ctx, userdomain.DeletePushTokenCommand{ AppCode: appcode.FromContext(ctx), UserID: userID, DeviceID: deviceID, PushToken: pushToken, RequestID: strings.TrimSpace(requestID), UpdatedAtMs: nowMs, }) if err != nil { return false, 0, err } return deleted, nowMs, nil } func (s *Service) requireActiveCompletedUser(ctx context.Context, userID int64) error { if userID <= 0 { return xerr.New(xerr.InvalidArgument, "user_id is required") } user, err := s.GetUser(ctx, userID) if err != nil { return err } if !user.ProfileCompleted { return xerr.New(xerr.ProfileRequired, "profile required") } if user.Status != userdomain.StatusActive { return xerr.New(xerr.UserDisabled, "user disabled") } return nil } func normalizePushTokenCommand(ctx context.Context, userID int64, deviceID string, pushToken string, provider string, platform string, appVersion string, language string, timezone string, requestID string, nowMs int64) (userdomain.PushTokenCommand, error) { deviceID = strings.TrimSpace(deviceID) pushToken = strings.TrimSpace(pushToken) platform = strings.ToLower(strings.TrimSpace(platform)) provider = strings.ToLower(strings.TrimSpace(provider)) if provider == "" { provider = defaultPushProvider(platform) } if deviceID == "" || len(deviceID) > maxPushDeviceIDLen { return userdomain.PushTokenCommand{}, xerr.New(xerr.InvalidArgument, "device_id is invalid") } if pushToken == "" || len(pushToken) > maxPushTokenLen { return userdomain.PushTokenCommand{}, xerr.New(xerr.InvalidArgument, "push_token is invalid") } if provider == "" || len(provider) > maxPushProviderLen { return userdomain.PushTokenCommand{}, xerr.New(xerr.InvalidArgument, "provider is invalid") } if platform != "android" && platform != "ios" { return userdomain.PushTokenCommand{}, xerr.New(xerr.InvalidArgument, "platform is invalid") } appVersion = strings.TrimSpace(appVersion) language = strings.TrimSpace(language) timezone = strings.TrimSpace(timezone) if len(appVersion) > maxPushAppVersionLen || len(language) > maxPushLanguageLen || len(timezone) > maxPushTimezoneLen { return userdomain.PushTokenCommand{}, xerr.New(xerr.InvalidArgument, "push token metadata is invalid") } return userdomain.PushTokenCommand{ AppCode: appcode.FromContext(ctx), UserID: userID, DeviceID: deviceID, PushToken: pushToken, Provider: provider, Platform: platform, AppVersion: appVersion, Language: language, Timezone: timezone, RequestID: strings.TrimSpace(requestID), UpdatedAtMs: nowMs, }, nil } func defaultPushProvider(platform string) string { switch platform { case "android": return "fcm" case "ios": return "apns" default: return "" } }