添加新接口
This commit is contained in:
parent
13f07d800f
commit
9b4447e158
File diff suppressed because it is too large
Load Diff
@ -2256,6 +2256,7 @@ message GetCPWeeklyRankStatusResponse {
|
||||
int64 period_start_ms = 3;
|
||||
int64 period_end_ms = 4;
|
||||
int64 server_time_ms = 5;
|
||||
repeated CPWeeklyRankEntry previous_entries = 6;
|
||||
}
|
||||
|
||||
message GetCPWeeklyRankConfigRequest {
|
||||
|
||||
@ -77,9 +77,10 @@ type Settlement struct {
|
||||
|
||||
// Status is the H5 read model for current week leaderboard and configured rewards.
|
||||
type Status struct {
|
||||
Config Config
|
||||
Entries []Entry
|
||||
PeriodStartMS int64
|
||||
PeriodEndMS int64
|
||||
ServerTimeMS int64
|
||||
Config Config
|
||||
Entries []Entry
|
||||
PreviousEntries []Entry
|
||||
PeriodStartMS int64
|
||||
PeriodEndMS int64
|
||||
ServerTimeMS int64
|
||||
}
|
||||
|
||||
@ -120,11 +120,19 @@ func (s *Service) GetStatus(ctx context.Context, limit int32, nowMS int64) (doma
|
||||
if limit <= 0 {
|
||||
limit = 3
|
||||
}
|
||||
// H5 同时需要当前周期榜单和上一完整 UTC 周期的前三名;两个窗口都从 user-service 的周期新增 gift_value 聚合读取,避免 gateway/H5 误用累计亲密榜。
|
||||
entries, err := s.rankSource.ListCPWeeklyRankEntries(ctx, config.RelationType, result.PeriodStartMS, result.PeriodEndMS, limit)
|
||||
if err != nil {
|
||||
return domain.Status{}, err
|
||||
}
|
||||
result.Entries = entries
|
||||
previousStart, previousEnd := PreviousWeekWindow(now)
|
||||
previousLimit := int32(3)
|
||||
previousEntries, err := s.rankSource.ListCPWeeklyRankEntries(ctx, config.RelationType, previousStart.UnixMilli(), previousEnd.UnixMilli(), previousLimit)
|
||||
if err != nil {
|
||||
return domain.Status{}, err
|
||||
}
|
||||
result.PreviousEntries = previousEntries
|
||||
return result, nil
|
||||
}
|
||||
|
||||
@ -136,6 +144,7 @@ func (s *Service) ListSettlements(ctx context.Context, periodStartMS int64, peri
|
||||
}
|
||||
|
||||
// ProcessSettlementBatch freezes the previous complete UTC week and grants each Top relationship reward to both users.
|
||||
// The rank source already returns weekly delta score, so settlement only persists that frozen score and never re-reads cumulative intimacy.
|
||||
func (s *Service) ProcessSettlementBatch(ctx context.Context, runID string, workerID string, batchSize int32) (claimed int32, processed int32, success int32, failure int32, hasMore bool, err error) {
|
||||
if err := s.requireRepository(); err != nil {
|
||||
return 0, 0, 0, 0, false, err
|
||||
|
||||
@ -0,0 +1,142 @@
|
||||
package cpweeklyrank_test
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"strconv"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"hyapp/pkg/appcode"
|
||||
domain "hyapp/services/activity-service/internal/domain/cpweeklyrank"
|
||||
service "hyapp/services/activity-service/internal/service/cpweeklyrank"
|
||||
)
|
||||
|
||||
func TestGetStatusReturnsPreviousEntriesFromPreviousWeek(t *testing.T) {
|
||||
now := time.Date(2026, 6, 24, 12, 0, 0, 0, time.UTC)
|
||||
currentStart, currentEnd := service.WeekWindow(now)
|
||||
previousStart, previousEnd := service.PreviousWeekWindow(now)
|
||||
rankSource := &fakeCPWeeklyRankSource{
|
||||
windows: map[[2]int64][]domain.Entry{
|
||||
{currentStart.UnixMilli(), currentEnd.UnixMilli()}: cpWeeklyRankEntries(2, 1000),
|
||||
{previousStart.UnixMilli(), previousEnd.UnixMilli()}: cpWeeklyRankEntries(5, 5000),
|
||||
},
|
||||
}
|
||||
svc := service.New(&fakeCPWeeklyRankRepository{config: domain.Config{
|
||||
AppCode: "lalu",
|
||||
ActivityCode: domain.ActivityCode,
|
||||
Enabled: true,
|
||||
RelationType: "cp",
|
||||
TopCount: 10,
|
||||
}}, rankSource, nil)
|
||||
svc.SetClock(func() time.Time { return now })
|
||||
|
||||
status, err := svc.GetStatus(appcode.WithContext(context.Background(), "lalu"), 1, 0)
|
||||
if err != nil {
|
||||
t.Fatalf("GetStatus failed: %v", err)
|
||||
}
|
||||
if len(status.Entries) != 1 {
|
||||
t.Fatalf("current entries mismatch: %+v", status.Entries)
|
||||
}
|
||||
if len(status.PreviousEntries) != 3 {
|
||||
t.Fatalf("previous entries should be limited to 3, got %+v", status.PreviousEntries)
|
||||
}
|
||||
if status.PreviousEntries[0].UserA.Username != "left-1" || status.PreviousEntries[0].UserA.Avatar != "https://avatar/left-1.png" ||
|
||||
status.PreviousEntries[0].UserB.Username != "right-1" || status.PreviousEntries[0].UserB.Avatar != "https://avatar/right-1.png" ||
|
||||
status.PreviousEntries[0].Score != 5001 {
|
||||
t.Fatalf("previous entry should keep user snapshots and intimacy score: %+v", status.PreviousEntries[0])
|
||||
}
|
||||
if len(rankSource.calls) != 2 {
|
||||
t.Fatalf("rank source call count mismatch: %+v", rankSource.calls)
|
||||
}
|
||||
if rankSource.calls[0].startMS != currentStart.UnixMilli() || rankSource.calls[0].endMS != currentEnd.UnixMilli() || rankSource.calls[0].limit != 1 {
|
||||
t.Fatalf("current window call mismatch: %+v", rankSource.calls[0])
|
||||
}
|
||||
if rankSource.calls[1].startMS != previousStart.UnixMilli() || rankSource.calls[1].endMS != previousEnd.UnixMilli() || rankSource.calls[1].limit != 3 {
|
||||
t.Fatalf("previous window call mismatch: %+v", rankSource.calls[1])
|
||||
}
|
||||
}
|
||||
|
||||
type fakeCPWeeklyRankRepository struct {
|
||||
config domain.Config
|
||||
}
|
||||
|
||||
func (r *fakeCPWeeklyRankRepository) GetCPWeeklyRankConfig(context.Context) (domain.Config, bool, error) {
|
||||
return r.config, true, nil
|
||||
}
|
||||
|
||||
func (r *fakeCPWeeklyRankRepository) UpdateCPWeeklyRankConfig(context.Context, domain.Config, int64) (domain.Config, error) {
|
||||
return domain.Config{}, errors.New("not implemented")
|
||||
}
|
||||
|
||||
func (r *fakeCPWeeklyRankRepository) ListCPWeeklyRankSettlements(context.Context, int64, int64) ([]domain.Settlement, error) {
|
||||
return nil, errors.New("not implemented")
|
||||
}
|
||||
|
||||
func (r *fakeCPWeeklyRankRepository) PrepareCPWeeklyRankSettlements(context.Context, domain.Config, []domain.Entry, int64, int64, int64) ([]domain.Settlement, error) {
|
||||
return nil, errors.New("not implemented")
|
||||
}
|
||||
|
||||
func (r *fakeCPWeeklyRankRepository) ListPendingCPWeeklyRankSettlements(context.Context, int64, int64, int) ([]domain.Settlement, error) {
|
||||
return nil, errors.New("not implemented")
|
||||
}
|
||||
|
||||
func (r *fakeCPWeeklyRankRepository) MarkCPWeeklyRankSettlementGranted(context.Context, string, string, int64) error {
|
||||
return errors.New("not implemented")
|
||||
}
|
||||
|
||||
func (r *fakeCPWeeklyRankRepository) MarkCPWeeklyRankSettlementFailed(context.Context, string, string, int64) error {
|
||||
return errors.New("not implemented")
|
||||
}
|
||||
|
||||
type fakeCPWeeklyRankSource struct {
|
||||
calls []fakeCPWeeklyRankSourceCall
|
||||
windows map[[2]int64][]domain.Entry
|
||||
}
|
||||
|
||||
type fakeCPWeeklyRankSourceCall struct {
|
||||
relationType string
|
||||
startMS int64
|
||||
endMS int64
|
||||
limit int32
|
||||
}
|
||||
|
||||
func (s *fakeCPWeeklyRankSource) ListCPWeeklyRankEntries(_ context.Context, relationType string, startMS int64, endMS int64, limit int32) ([]domain.Entry, error) {
|
||||
s.calls = append(s.calls, fakeCPWeeklyRankSourceCall{
|
||||
relationType: relationType,
|
||||
startMS: startMS,
|
||||
endMS: endMS,
|
||||
limit: limit,
|
||||
})
|
||||
entries := append([]domain.Entry(nil), s.windows[[2]int64{startMS, endMS}]...)
|
||||
if limit > 0 && len(entries) > int(limit) {
|
||||
entries = entries[:limit]
|
||||
}
|
||||
return entries, nil
|
||||
}
|
||||
|
||||
func cpWeeklyRankEntries(count int, baseScore int64) []domain.Entry {
|
||||
entries := make([]domain.Entry, 0, count)
|
||||
for index := 1; index <= count; index++ {
|
||||
suffix := strconv.Itoa(index)
|
||||
entries = append(entries, domain.Entry{
|
||||
Rank: int64(index),
|
||||
RelationshipID: "cp-rel-" + suffix,
|
||||
RelationType: "cp",
|
||||
Score: baseScore + int64(index),
|
||||
UserA: domain.User{
|
||||
UserID: int64(1000 + index),
|
||||
DisplayUserID: "L" + suffix,
|
||||
Username: "left-" + suffix,
|
||||
Avatar: "https://avatar/left-" + suffix + ".png",
|
||||
},
|
||||
UserB: domain.User{
|
||||
UserID: int64(2000 + index),
|
||||
DisplayUserID: "R" + suffix,
|
||||
Username: "right-" + suffix,
|
||||
Avatar: "https://avatar/right-" + suffix + ".png",
|
||||
},
|
||||
})
|
||||
}
|
||||
return entries
|
||||
}
|
||||
@ -30,11 +30,12 @@ func (s *CPWeeklyRankServer) GetCPWeeklyRankStatus(ctx context.Context, req *act
|
||||
return nil, xerr.ToGRPCError(err)
|
||||
}
|
||||
return &activityv1.GetCPWeeklyRankStatusResponse{
|
||||
Config: cpWeeklyRankConfigToProto(status.Config),
|
||||
Entries: cpWeeklyRankEntriesToProto(status.Entries),
|
||||
PeriodStartMs: status.PeriodStartMS,
|
||||
PeriodEndMs: status.PeriodEndMS,
|
||||
ServerTimeMs: status.ServerTimeMS,
|
||||
Config: cpWeeklyRankConfigToProto(status.Config),
|
||||
Entries: cpWeeklyRankEntriesToProto(status.Entries),
|
||||
PreviousEntries: cpWeeklyRankEntriesToProto(status.PreviousEntries),
|
||||
PeriodStartMs: status.PeriodStartMS,
|
||||
PeriodEndMs: status.PeriodEndMS,
|
||||
ServerTimeMs: status.ServerTimeMS,
|
||||
}, nil
|
||||
}
|
||||
|
||||
|
||||
@ -9,12 +9,13 @@ import (
|
||||
)
|
||||
|
||||
type cpWeeklyRankStatusData struct {
|
||||
Enabled bool `json:"enabled"`
|
||||
Period cpWeeklyRankPeriodData `json:"period"`
|
||||
Rewards []cpWeeklyRankRewardData `json:"rewards"`
|
||||
Entries []cpWeeklyRankEntryData `json:"entries"`
|
||||
ServerTimeMS int64 `json:"server_time_ms"`
|
||||
RelationType string `json:"relation_type"`
|
||||
Enabled bool `json:"enabled"`
|
||||
Period cpWeeklyRankPeriodData `json:"period"`
|
||||
Rewards []cpWeeklyRankRewardData `json:"rewards"`
|
||||
Entries []cpWeeklyRankEntryData `json:"entries"`
|
||||
PreviousEntries []cpWeeklyRankEntryData `json:"previous_entries"`
|
||||
ServerTimeMS int64 `json:"server_time_ms"`
|
||||
RelationType string `json:"relation_type"`
|
||||
}
|
||||
|
||||
type cpWeeklyRankPeriodData struct {
|
||||
@ -81,12 +82,13 @@ func (h *Handler) getCPWeeklyRankStatus(writer http.ResponseWriter, request *htt
|
||||
func cpWeeklyRankStatusFromProto(resp *activityv1.GetCPWeeklyRankStatusResponse, groups map[int64]checkinResourceGroupData) cpWeeklyRankStatusData {
|
||||
config := resp.GetConfig()
|
||||
return cpWeeklyRankStatusData{
|
||||
Enabled: config.GetEnabled(),
|
||||
RelationType: config.GetRelationType(),
|
||||
Period: cpWeeklyRankPeriodData{StartMS: resp.GetPeriodStartMs(), EndMS: resp.GetPeriodEndMs()},
|
||||
Rewards: cpWeeklyRankRewardsFromProto(config.GetRewards(), groups),
|
||||
Entries: cpWeeklyRankEntriesFromProto(resp.GetEntries()),
|
||||
ServerTimeMS: resp.GetServerTimeMs(),
|
||||
Enabled: config.GetEnabled(),
|
||||
RelationType: config.GetRelationType(),
|
||||
Period: cpWeeklyRankPeriodData{StartMS: resp.GetPeriodStartMs(), EndMS: resp.GetPeriodEndMs()},
|
||||
Rewards: cpWeeklyRankRewardsFromProto(config.GetRewards(), groups),
|
||||
Entries: cpWeeklyRankEntriesFromProto(resp.GetEntries()),
|
||||
PreviousEntries: cpWeeklyRankEntriesFromProto(resp.GetPreviousEntries()),
|
||||
ServerTimeMS: resp.GetServerTimeMs(),
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ -618,6 +618,16 @@ type fakeWeeklyStarClient struct {
|
||||
err error
|
||||
}
|
||||
|
||||
type fakeCPWeeklyRankClient struct {
|
||||
lastStatus *activityv1.GetCPWeeklyRankStatusRequest
|
||||
statusResp *activityv1.GetCPWeeklyRankStatusResponse
|
||||
err error
|
||||
}
|
||||
|
||||
type fakeUserCPClient struct {
|
||||
listIntimacyLeaderboardCalls int
|
||||
}
|
||||
|
||||
type fakeBroadcastClient struct {
|
||||
lastRemove *activityv1.RemoveRegionBroadcastMemberRequest
|
||||
err error
|
||||
@ -1305,6 +1315,50 @@ func (f *fakeWeeklyStarClient) ListWeeklyStarHistory(_ context.Context, req *act
|
||||
return &activityv1.ListWeeklyStarHistoryResponse{}, nil
|
||||
}
|
||||
|
||||
func (f *fakeCPWeeklyRankClient) GetCPWeeklyRankStatus(_ context.Context, req *activityv1.GetCPWeeklyRankStatusRequest) (*activityv1.GetCPWeeklyRankStatusResponse, error) {
|
||||
f.lastStatus = req
|
||||
if f.err != nil {
|
||||
return nil, f.err
|
||||
}
|
||||
if f.statusResp != nil {
|
||||
return f.statusResp, nil
|
||||
}
|
||||
return &activityv1.GetCPWeeklyRankStatusResponse{}, nil
|
||||
}
|
||||
|
||||
func (f *fakeUserCPClient) ListCPApplications(context.Context, *userv1.ListCPApplicationsRequest) (*userv1.ListCPApplicationsResponse, error) {
|
||||
return &userv1.ListCPApplicationsResponse{}, nil
|
||||
}
|
||||
|
||||
func (f *fakeUserCPClient) AcceptCPApplication(context.Context, *userv1.AcceptCPApplicationRequest) (*userv1.AcceptCPApplicationResponse, error) {
|
||||
return &userv1.AcceptCPApplicationResponse{}, nil
|
||||
}
|
||||
|
||||
func (f *fakeUserCPClient) RejectCPApplication(context.Context, *userv1.RejectCPApplicationRequest) (*userv1.RejectCPApplicationResponse, error) {
|
||||
return &userv1.RejectCPApplicationResponse{}, nil
|
||||
}
|
||||
|
||||
func (f *fakeUserCPClient) ListCPRelationships(context.Context, *userv1.ListCPRelationshipsRequest) (*userv1.ListCPRelationshipsResponse, error) {
|
||||
return &userv1.ListCPRelationshipsResponse{}, nil
|
||||
}
|
||||
|
||||
func (f *fakeUserCPClient) ListCPIntimacyLeaderboard(context.Context, *userv1.ListCPIntimacyLeaderboardRequest) (*userv1.ListCPIntimacyLeaderboardResponse, error) {
|
||||
f.listIntimacyLeaderboardCalls++
|
||||
return nil, errors.New("ordinary cp intimacy leaderboard must not serve cp weekly activity")
|
||||
}
|
||||
|
||||
func (f *fakeUserCPClient) PrepareBreakCPRelationship(context.Context, *userv1.PrepareBreakCPRelationshipRequest) (*userv1.PrepareBreakCPRelationshipResponse, error) {
|
||||
return &userv1.PrepareBreakCPRelationshipResponse{}, nil
|
||||
}
|
||||
|
||||
func (f *fakeUserCPClient) ConfirmBreakCPRelationship(context.Context, *userv1.ConfirmBreakCPRelationshipRequest) (*userv1.ConfirmBreakCPRelationshipResponse, error) {
|
||||
return &userv1.ConfirmBreakCPRelationshipResponse{}, nil
|
||||
}
|
||||
|
||||
func (f *fakeUserCPClient) CancelBreakCPRelationship(context.Context, *userv1.CancelBreakCPRelationshipRequest) (*userv1.CancelBreakCPRelationshipResponse, error) {
|
||||
return &userv1.CancelBreakCPRelationshipResponse{}, nil
|
||||
}
|
||||
|
||||
func (f *fakeBroadcastClient) RemoveRegionBroadcastMember(_ context.Context, req *activityv1.RemoveRegionBroadcastMemberRequest) (*activityv1.RemoveRegionBroadcastMemberResponse, error) {
|
||||
f.lastRemove = req
|
||||
if f.err != nil {
|
||||
@ -6069,6 +6123,94 @@ func TestAchievementAndBadgeRoutesEmbedResourceMaterials(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func TestCPWeeklyRankActivityUsesWeeklyScoreAndActivityClient(t *testing.T) {
|
||||
cpWeeklyClient := &fakeCPWeeklyRankClient{
|
||||
statusResp: &activityv1.GetCPWeeklyRankStatusResponse{
|
||||
Config: &activityv1.CPWeeklyRankConfig{
|
||||
ActivityCode: "cp_weekly_rank",
|
||||
Enabled: true,
|
||||
RelationType: "cp",
|
||||
TopCount: 3,
|
||||
},
|
||||
Entries: []*activityv1.CPWeeklyRankEntry{
|
||||
{
|
||||
Rank: 1,
|
||||
RelationshipId: "weekly_delta_high",
|
||||
RelationType: "cp",
|
||||
Score: 100,
|
||||
UserA: &activityv1.CPWeeklyRankUser{UserId: 7003, DisplayUserId: "7003", Username: "Weekly A", Avatar: "a.png"},
|
||||
UserB: &activityv1.CPWeeklyRankUser{UserId: 7004, DisplayUserId: "7004", Username: "Weekly B", Avatar: "b.png"},
|
||||
FormedAtMs: 1780000000000,
|
||||
UpdatedAtMs: 1780876800000,
|
||||
FirstScoredAtMs: 1780876800000,
|
||||
LastScoredAtMs: 1781481599999,
|
||||
},
|
||||
{
|
||||
Rank: 2,
|
||||
RelationshipId: "weekly_total_high",
|
||||
RelationType: "cp",
|
||||
Score: 10,
|
||||
UserA: &activityv1.CPWeeklyRankUser{UserId: 7001, DisplayUserId: "7001", Username: "Total A", Avatar: "c.png"},
|
||||
UserB: &activityv1.CPWeeklyRankUser{UserId: 7002, DisplayUserId: "7002", Username: "Total B", Avatar: "d.png"},
|
||||
FormedAtMs: 1780000000000,
|
||||
UpdatedAtMs: 1780876800000,
|
||||
FirstScoredAtMs: 1780876800000,
|
||||
LastScoredAtMs: 1780876800000,
|
||||
},
|
||||
},
|
||||
PeriodStartMs: 1780876800000,
|
||||
PeriodEndMs: 1781481600000,
|
||||
ServerTimeMs: 1780880000000,
|
||||
},
|
||||
}
|
||||
userCPClient := &fakeUserCPClient{}
|
||||
handler := NewHandlerWithClients(&fakeRoomClient{}, nil, nil, &fakeUserProfileClient{})
|
||||
handler.SetCPWeeklyRankClient(cpWeeklyClient)
|
||||
handler.SetUserCPClient(userCPClient)
|
||||
router := handler.Routes(auth.NewVerifier("secret"))
|
||||
|
||||
request := httptest.NewRequest(http.MethodGet, "/api/v1/activities/cp-weekly-rank?limit=2", nil)
|
||||
request.Header.Set("Authorization", "Bearer "+signGatewayToken(t, "secret", 42))
|
||||
recorder := httptest.NewRecorder()
|
||||
router.ServeHTTP(recorder, request)
|
||||
if recorder.Code != http.StatusOK {
|
||||
t.Fatalf("cp weekly rank status mismatch: got %d body=%s", recorder.Code, recorder.Body.String())
|
||||
}
|
||||
|
||||
var envelope struct {
|
||||
Code string `json:"code"`
|
||||
Data struct {
|
||||
Entries []struct {
|
||||
Rank int64 `json:"rank"`
|
||||
RelationshipID string `json:"relationship_id"`
|
||||
Score int64 `json:"score"`
|
||||
} `json:"entries"`
|
||||
Period struct {
|
||||
StartMS int64 `json:"start_ms"`
|
||||
EndMS int64 `json:"end_ms"`
|
||||
} `json:"period"`
|
||||
} `json:"data"`
|
||||
}
|
||||
if err := json.NewDecoder(recorder.Body).Decode(&envelope); err != nil {
|
||||
t.Fatalf("decode cp weekly rank response failed: %v", err)
|
||||
}
|
||||
if cpWeeklyClient.lastStatus == nil || cpWeeklyClient.lastStatus.GetLimit() != 2 || cpWeeklyClient.lastStatus.GetMeta().GetAppCode() != "lalu" {
|
||||
t.Fatalf("cp weekly rank must call activity-service with request meta: %+v", cpWeeklyClient.lastStatus)
|
||||
}
|
||||
if userCPClient.listIntimacyLeaderboardCalls != 0 {
|
||||
t.Fatalf("cp weekly activity must not read ordinary cumulative intimacy leaderboard, calls=%d", userCPClient.listIntimacyLeaderboardCalls)
|
||||
}
|
||||
if envelope.Code != httpkit.CodeOK || len(envelope.Data.Entries) != 2 {
|
||||
t.Fatalf("cp weekly rank envelope mismatch: %+v", envelope)
|
||||
}
|
||||
if envelope.Data.Entries[0].RelationshipID != "weekly_delta_high" || envelope.Data.Entries[0].Score != 100 || envelope.Data.Entries[1].Score != 10 {
|
||||
t.Fatalf("cp weekly rank must expose activity weekly score unchanged: %+v", envelope.Data.Entries)
|
||||
}
|
||||
if envelope.Data.Period.StartMS != 1780876800000 || envelope.Data.Period.EndMS != 1781481600000 {
|
||||
t.Fatalf("cp weekly rank must expose activity period: %+v", envelope.Data.Period)
|
||||
}
|
||||
}
|
||||
|
||||
func TestWeeklyStarCurrentEmbedsRewardResourceGroups(t *testing.T) {
|
||||
giftResource := &walletv1.Resource{
|
||||
ResourceId: 7308,
|
||||
|
||||
@ -72,7 +72,7 @@ type IntimacyLeaderboardEntry struct {
|
||||
UpdatedAtMS int64 `json:"updated_at_ms"`
|
||||
}
|
||||
|
||||
// WeeklyRankEntry 是按一个 UTC 周期聚合的 CP 关系送礼积分;activity-service 用它固化和发奖。
|
||||
// WeeklyRankEntry 是按一个 UTC 周期聚合的 CP 关系新增亲密值;activity-service 用它固化和发奖,不能回退到关系累计 intimacy_value。
|
||||
type WeeklyRankEntry struct {
|
||||
Rank int64
|
||||
RelationshipID string
|
||||
|
||||
@ -319,7 +319,8 @@ func (r *Repository) ListActiveIntimacyLeaderboardEntries(ctx context.Context, l
|
||||
return entries, rows.Err()
|
||||
}
|
||||
|
||||
// ListWeeklyRankEntries 按消费事实聚合一个 UTC 周期内的 CP 关系送礼分数。
|
||||
// ListWeeklyRankEntries 按消费事实聚合一个 UTC 周期内的 CP 关系新增亲密值。
|
||||
// score 只等于窗口内 relationship_intimacy_added gift_value 之和;关系表 intimacy_value 只服务普通亲密榜和等级累计态,不参与活动周榜排名。
|
||||
func (r *Repository) ListWeeklyRankEntries(ctx context.Context, relationType string, startMS int64, endMS int64, limit int) ([]cpdomain.WeeklyRankEntry, error) {
|
||||
if r == nil || r.db == nil {
|
||||
return nil, xerr.New(xerr.Unavailable, "mysql repository is not configured")
|
||||
@ -342,7 +343,7 @@ func (r *Repository) ListWeeklyRankEntries(ctx context.Context, relationType str
|
||||
args = append(args, relationType)
|
||||
}
|
||||
args = append(args, limit, appCode, appCode)
|
||||
// 周榜以礼物消费位点为事实源,relationship 表只提供双方用户和关系类型;即使关系后来解除,也不能让历史周榜丢失获奖对象。
|
||||
// 周榜以礼物消费位点为事实源,relationship 表只提供双方用户、关系类型和展示时间;即使累计亲密值更高,也不能覆盖本周期新增分数排序。
|
||||
// 同分时用首次得分时间和 relationship_id 稳定排序,保证 cron 重试、后台查询和 H5 展示看到同一批排名。
|
||||
rows, err := r.db.QueryContext(ctx, `
|
||||
SELECT
|
||||
|
||||
@ -176,6 +176,47 @@ func TestListActiveIntimacyLeaderboardEntriesOrdersAndIncludesBothUsers(t *testi
|
||||
}
|
||||
}
|
||||
|
||||
func TestListWeeklyRankEntriesUsesWeeklyGiftValueNotCumulativeIntimacy(t *testing.T) {
|
||||
schema := mysqlschema.New(t)
|
||||
repo := New(schema.DB)
|
||||
ctx := appcode.WithContext(context.Background(), appcode.Default)
|
||||
startMS := int64(1780876800000)
|
||||
endMS := startMS + 7*24*60*60*1000
|
||||
nowMS := startMS + 1234
|
||||
|
||||
for _, userID := range []int64{7001, 7002, 7003, 7004, 7005, 7006} {
|
||||
seedCPTestUser(t, schema.DB, userID, fmt.Sprintf("Weekly %d", userID), nowMS)
|
||||
}
|
||||
seedActiveCPRelationship(t, schema.DB, "weekly_total_high", cpdomain.RelationTypeCP, 7001, 7002, 9000, 9, nowMS)
|
||||
seedActiveCPRelationship(t, schema.DB, "weekly_delta_high", cpdomain.RelationTypeCP, 7003, 7004, 100, 1, nowMS+1)
|
||||
seedActiveCPRelationship(t, schema.DB, "weekly_brother", cpdomain.RelationTypeBrother, 7005, 7006, 1, 1, nowMS+2)
|
||||
|
||||
seedCPGiftConsumption(t, schema.DB, "evt_total_high_start", "relationship_intimacy_added", "weekly_total_high", 10, startMS, nowMS)
|
||||
seedCPGiftConsumption(t, schema.DB, "evt_delta_high_start", "relationship_intimacy_added", "weekly_delta_high", 70, startMS, nowMS)
|
||||
seedCPGiftConsumption(t, schema.DB, "evt_delta_high_end_minus_one", "relationship_intimacy_added", "weekly_delta_high", 30, endMS-1, nowMS)
|
||||
seedCPGiftConsumption(t, schema.DB, "evt_delta_high_end_excluded", "relationship_intimacy_added", "weekly_delta_high", 999, endMS, nowMS)
|
||||
seedCPGiftConsumption(t, schema.DB, "evt_delta_high_zero_ignored", "relationship_intimacy_added", "weekly_delta_high", 0, startMS+1, nowMS)
|
||||
seedCPGiftConsumption(t, schema.DB, "evt_delta_high_status_ignored", "skipped", "weekly_delta_high", 500, startMS+2, nowMS)
|
||||
seedCPGiftConsumption(t, schema.DB, "evt_brother_relation_ignored", "relationship_intimacy_added", "weekly_brother", 1000, startMS+3, nowMS)
|
||||
|
||||
entries, err := repo.ListWeeklyRankEntries(ctx, cpdomain.RelationTypeCP, startMS, endMS, 10)
|
||||
if err != nil {
|
||||
t.Fatalf("list weekly rank entries failed: %v", err)
|
||||
}
|
||||
if len(entries) != 2 {
|
||||
t.Fatalf("weekly entries len=%d, want 2: %+v", len(entries), entries)
|
||||
}
|
||||
if entries[0].RelationshipID != "weekly_delta_high" || entries[0].Score != 100 || entries[0].Rank != 1 {
|
||||
t.Fatalf("weekly rank must order by in-window gift_value delta, got first=%+v", entries[0])
|
||||
}
|
||||
if entries[0].FirstScoredAtMS != startMS || entries[0].LastScoredAtMS != endMS-1 {
|
||||
t.Fatalf("weekly rank must use [start,end) boundary times, got first=%d last=%d", entries[0].FirstScoredAtMS, entries[0].LastScoredAtMS)
|
||||
}
|
||||
if entries[1].RelationshipID != "weekly_total_high" || entries[1].Score != 10 || entries[1].Rank != 2 {
|
||||
t.Fatalf("cumulative intimacy must not outrank weekly delta score, got second=%+v", entries[1])
|
||||
}
|
||||
}
|
||||
|
||||
func TestConsumeGiftEventAddsIntimacyForNormalGiftWhenRelationshipExists(t *testing.T) {
|
||||
schema := mysqlschema.New(t)
|
||||
repo := New(schema.DB)
|
||||
@ -602,6 +643,21 @@ func seedActiveCPRelationship(t testing.TB, db *sql.DB, relationshipID string, r
|
||||
}
|
||||
}
|
||||
|
||||
func seedCPGiftConsumption(t testing.TB, db *sql.DB, eventID string, status string, relationshipID string, giftValue int64, occurredAtMS int64, nowMS int64) {
|
||||
t.Helper()
|
||||
|
||||
// CP 周榜只信任消费位点里的新增 gift_value;测试显式写多种状态和边界时间,防止查询退回关系累计值或闭区间统计。
|
||||
if _, err := db.ExecContext(context.Background(), `
|
||||
INSERT INTO user_cp_gift_event_consumption (
|
||||
app_code, room_event_id, status, relationship_id, gift_value,
|
||||
occurred_at_ms, consumed_at_ms, created_at_ms, updated_at_ms
|
||||
) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)`,
|
||||
appcode.Default, eventID, status, relationshipID, giftValue, occurredAtMS, nowMS, nowMS, nowMS,
|
||||
); err != nil {
|
||||
t.Fatalf("seed cp gift consumption %s failed: %v", eventID, err)
|
||||
}
|
||||
}
|
||||
|
||||
func seedPendingCPApplication(t testing.TB, db *sql.DB, applicationID string, relationType string, requesterUserID int64, targetUserID int64, nowMS int64) {
|
||||
t.Helper()
|
||||
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user