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

120 lines
3.2 KiB
Go

// Package device implements user-service push token persistence.
package device
import (
"context"
"database/sql"
"strings"
"hyapp/pkg/appcode"
userdomain "hyapp/services/user-service/internal/domain/user"
)
// Repository stores App push tokens in MySQL.
type Repository struct {
db *sql.DB
}
// New creates a device repository over the shared user-service connection pool.
func New(db *sql.DB) *Repository {
return &Repository{db: db}
}
// BindPushToken binds a push token to the current user/device and removes stale ownership of the same token.
func (r *Repository) BindPushToken(ctx context.Context, command userdomain.PushTokenCommand) error {
appCode := normalizedAppCode(ctx, command.AppCode)
tx, err := r.db.BeginTx(ctx, nil)
if err != nil {
return err
}
// 同一个系统 push token 只能投递一次;重新登录其他账号时删除旧归属,避免离线消息双发。
if _, err := tx.ExecContext(ctx,
`DELETE FROM user_push_tokens
WHERE app_code = ? AND provider = ? AND push_token = ?
AND NOT (user_id = ? AND device_id = ?)`,
appCode,
command.Provider,
command.PushToken,
command.UserID,
command.DeviceID,
); err != nil {
_ = tx.Rollback()
return err
}
if _, err := tx.ExecContext(ctx,
`INSERT INTO user_push_tokens (
app_code, user_id, device_id, push_token, provider, platform, app_version, language, timezone,
status, bound_at_ms, updated_at_ms, last_request_id
) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
ON DUPLICATE KEY UPDATE
push_token = VALUES(push_token),
provider = VALUES(provider),
platform = VALUES(platform),
app_version = VALUES(app_version),
language = VALUES(language),
timezone = VALUES(timezone),
status = VALUES(status),
updated_at_ms = VALUES(updated_at_ms),
last_request_id = VALUES(last_request_id)`,
appCode,
command.UserID,
command.DeviceID,
command.PushToken,
command.Provider,
command.Platform,
command.AppVersion,
command.Language,
command.Timezone,
userdomain.PushTokenStatusActive,
command.UpdatedAtMs,
command.UpdatedAtMs,
command.RequestID,
); err != nil {
_ = tx.Rollback()
return err
}
return tx.Commit()
}
// DeletePushToken marks one user's device token inactive.
func (r *Repository) DeletePushToken(ctx context.Context, command userdomain.DeletePushTokenCommand) (bool, error) {
appCode := normalizedAppCode(ctx, command.AppCode)
query := `UPDATE user_push_tokens
SET status = ?, updated_at_ms = ?, last_request_id = ?
WHERE app_code = ? AND user_id = ? AND device_id = ? AND status = ?`
args := []any{
userdomain.PushTokenStatusInactive,
command.UpdatedAtMs,
command.RequestID,
appCode,
command.UserID,
command.DeviceID,
userdomain.PushTokenStatusActive,
}
if strings.TrimSpace(command.PushToken) != "" {
query += ` AND push_token = ?`
args = append(args, command.PushToken)
}
result, err := r.db.ExecContext(ctx, query, args...)
if err != nil {
return false, err
}
affected, err := result.RowsAffected()
if err != nil {
return false, err
}
return affected > 0, nil
}
func normalizedAppCode(ctx context.Context, value string) string {
value = strings.TrimSpace(value)
if value == "" {
return appcode.FromContext(ctx)
}
return appcode.Normalize(value)
}