package statistic import ( "context" "sync" "time" dubboclient "dubbo.apache.org/dubbo-go/v3/client" "github.com/google/uuid" pb "github.com/topfans/backend/pkg/proto/event" statisticPb "github.com/topfans/backend/pkg/proto/statistic" ) var ( instance *Client once sync.Once ) // Client 业务侧统一 SDK(fire-and-forget 调用 statisticService) type Client struct { service statisticPb.StatisticService } // Init 用 Dubbo client 初始化 SDK(在业务服务 main.go 启动时调用一次) func Init(dubboClient *dubboclient.Client) error { var err error once.Do(func() { svc, e := statisticPb.NewStatisticService(dubboClient) if e != nil { err = e return } instance = &Client{service: svc} }) return err } // Get 返回全局 SDK 实例(Init 之后才能用) func Get() *Client { return instance } // TrackEvent fire-and-forget 上报单个事件 // - 自动填充 event_id(若为空)和 occurred_at // - 不阻塞业务方(独立 goroutine + background context) func (c *Client) TrackEvent(ctx context.Context, e *pb.Event) { if c == nil || c.service == nil { return } if e.EventId == "" { e.EventId = uuid.New().String() } if e.OccurredAt == 0 { e.OccurredAt = time.Now().UnixMilli() } bgCtx, cancel := context.WithTimeout(context.Background(), 5*time.Second) go func() { defer cancel() _, _ = c.service.TrackEvent(bgCtx, e) }() } // SetMockForTest 测试钩子:注入 mock 客户端 var mockForTest Capturer // Capturer 测试用 capture 接口 type Capturer interface { Capture(e *pb.Event) } // SetMockForTest 注入测试 mock func SetMockForTest(c Capturer) { mockForTest = c } // ResetMockForTest 重置 mock func ResetMockForTest() { mockForTest = nil } // TrackEventSync 同步版本(测试用) func (c *Client) TrackEventSync(ctx context.Context, e *pb.Event) (*statisticPb.TrackEventResponse, error) { if e.EventId == "" { e.EventId = uuid.New().String() } if e.OccurredAt == 0 { e.OccurredAt = time.Now().UnixMilli() } if mockForTest != nil { mockForTest.Capture(e) return &statisticPb.TrackEventResponse{Accepted: 1, Rejected: 0}, nil } if c == nil || c.service == nil { return &statisticPb.TrackEventResponse{Accepted: 0, Rejected: 1}, nil } return c.service.TrackEvent(ctx, e) }