2026-06-08 14:12:23 +08:00

137 lines
5.3 KiB
Go
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

// Package cp 承载 CP/兄弟/姐妹关系的用例编排。
package cp
import (
"context"
"strings"
"time"
"hyapp/pkg/xerr"
cpdomain "hyapp/services/user-service/internal/domain/cp"
)
// Repository 描述 CP 关系 service 需要的持久化能力,便于后续单元测试替换 MySQL 实现。
type Repository interface {
ListApplications(ctx context.Context, userID int64, direction string, status string, page int32, pageSize int32, nowMs int64) ([]cpdomain.Application, int64, error)
AcceptApplication(ctx context.Context, userID int64, applicationID string, nowMs int64) (cpdomain.Application, cpdomain.Relationship, error)
RejectApplication(ctx context.Context, userID int64, applicationID string, reason string, nowMs int64) (cpdomain.Application, error)
ListRelationships(ctx context.Context, userID int64, relationType string, page int32, pageSize int32) ([]cpdomain.Relationship, int64, error)
ConsumeGiftEvent(ctx context.Context, event cpdomain.GiftEvent, nowMs int64) (cpdomain.ConsumeResult, error)
}
// Service 只编排 CP 用例,不直接投递 IMIM 由 user_outbox 下游消费者按事实事件处理。
type Service struct {
repo Repository
clock func() time.Time
}
// New 创建 CP 关系 service。
func New(repo Repository) *Service {
return &Service{
repo: repo,
clock: func() time.Time { return time.Now().UTC() },
}
}
// ListApplications 返回用户收到或发出的 CP 申请;过期固化在 repository 中完成。
func (s *Service) ListApplications(ctx context.Context, userID int64, direction string, status string, page int32, pageSize int32) ([]cpdomain.Application, int64, int64, error) {
if err := requireUserID(userID); err != nil {
return nil, 0, 0, err
}
nowMs := s.nowMs()
applications, total, err := s.repo.ListApplications(ctx, userID, normalizeDirection(direction), normalizeStatus(status), page, pageSize, nowMs)
return applications, total, nowMs, err
}
// AcceptApplication 同意申请并建立关系;同一用户只能有一个 active 关系的互斥由 repository 事务保证。
func (s *Service) AcceptApplication(ctx context.Context, userID int64, applicationID string) (cpdomain.Application, cpdomain.Relationship, error) {
if err := requireUserID(userID); err != nil {
return cpdomain.Application{}, cpdomain.Relationship{}, err
}
if strings.TrimSpace(applicationID) == "" {
return cpdomain.Application{}, cpdomain.Relationship{}, xerr.New(xerr.InvalidArgument, "application_id is required")
}
return s.repo.AcceptApplication(ctx, userID, strings.TrimSpace(applicationID), s.nowMs())
}
// RejectApplication 拒绝申请;拒绝不阻断同一对用户其他类型的 pending 申请。
func (s *Service) RejectApplication(ctx context.Context, userID int64, applicationID string, reason string) (cpdomain.Application, error) {
if err := requireUserID(userID); err != nil {
return cpdomain.Application{}, err
}
if strings.TrimSpace(applicationID) == "" {
return cpdomain.Application{}, xerr.New(xerr.InvalidArgument, "application_id is required")
}
return s.repo.RejectApplication(ctx, userID, strings.TrimSpace(applicationID), reason, s.nowMs())
}
// ListRelationships 返回用户当前 active 关系;当前产品固定每个用户同时只能有一条关系。
func (s *Service) ListRelationships(ctx context.Context, userID int64, relationType string, page int32, pageSize int32) ([]cpdomain.Relationship, int64, error) {
if err := requireUserID(userID); err != nil {
return nil, 0, err
}
return s.repo.ListRelationships(ctx, userID, normalizeRelationType(relationType), page, pageSize)
}
// ConsumeGiftEvent 消费 room-service 已扣费并落房间状态的送礼事实。
func (s *Service) ConsumeGiftEvent(ctx context.Context, event cpdomain.GiftEvent) (cpdomain.ConsumeResult, error) {
event.CPRelationType = normalizeRelationType(event.CPRelationType)
if event.CPRelationType == "" {
return cpdomain.ConsumeResult{Consumed: false}, nil
}
// gift_value 是亲密值增量,沿用 room 热度结算后的值0 值仍可创建申请,但不会增加已有关联亲密值。
if event.GiftValue < 0 {
event.GiftValue = 0
}
return s.repo.ConsumeGiftEvent(ctx, event, s.nowMs())
}
func (s *Service) nowMs() int64 {
if s == nil || s.clock == nil {
return time.Now().UTC().UnixMilli()
}
return s.clock().UTC().UnixMilli()
}
func requireUserID(userID int64) error {
if userID <= 0 {
return xerr.New(xerr.InvalidArgument, "user_id is required")
}
return nil
}
func normalizeDirection(value string) string {
switch strings.ToLower(strings.TrimSpace(value)) {
case "outgoing":
return "outgoing"
default:
return "incoming"
}
}
func normalizeStatus(value string) string {
switch strings.ToLower(strings.TrimSpace(value)) {
case cpdomain.ApplicationStatusPending,
cpdomain.ApplicationStatusAccepted,
cpdomain.ApplicationStatusRejected,
cpdomain.ApplicationStatusExpired,
cpdomain.ApplicationStatusBlocked:
return strings.ToLower(strings.TrimSpace(value))
default:
return ""
}
}
func normalizeRelationType(value string) string {
switch strings.ToLower(strings.TrimSpace(value)) {
case cpdomain.RelationTypeCP:
return cpdomain.RelationTypeCP
case cpdomain.RelationTypeBrother:
return cpdomain.RelationTypeBrother
case cpdomain.RelationTypeSister:
return cpdomain.RelationTypeSister
default:
return ""
}
}