2026-05-06 21:22:39 +08:00

148 lines
4.7 KiB
Go

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 ""
}
}