From 6428a264137e86641cfb7f05fc15e99630cca8a8 Mon Sep 17 00:00:00 2001 From: zhx Date: Wed, 22 Jul 2026 21:28:14 +0800 Subject: [PATCH] =?UTF-8?q?fix(fami):=20=E6=81=A2=E5=A4=8D=E5=85=AC?= =?UTF-8?q?=E4=BC=9A=E4=B8=AD=E5=BF=83=E7=BB=9F=E8=AE=A1=E6=95=B0=E6=8D=AE?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../http/userapi/bd_center_stats_handler.go | 11 ++++- .../userapi/host_center_stats_handler_test.go | 15 +++++++ .../internal/storage/mysql/host/stats.go | 41 +++++++++++++++---- .../internal/storage/mysql/host/stats_test.go | 6 +++ 4 files changed, 63 insertions(+), 10 deletions(-) diff --git a/services/gateway-service/internal/transport/http/userapi/bd_center_stats_handler.go b/services/gateway-service/internal/transport/http/userapi/bd_center_stats_handler.go index 2613f3df..2f65c5f4 100644 --- a/services/gateway-service/internal/transport/http/userapi/bd_center_stats_handler.go +++ b/services/gateway-service/internal/transport/http/userapi/bd_center_stats_handler.go @@ -91,7 +91,7 @@ func (h *Handler) getBDCenterStats(writer http.ResponseWriter, request *http.Req items[index] = bdCenterAgencyStatsData{ Agency: hostAgencyFromProto(agency), Owner: owner, Country: owner.Country, TotalHosts: len(hostIDs), GiftedHostCount: stats.GetStats().GetGiftedHostCount(), - DiamondEarnings: stats.GetStats().GetGiftIncome(), JoinedAtMS: agency.GetCreatedAtMs(), + DiamondEarnings: bdCenterAgencyHostIncome(stats.GetStats()), JoinedAtMS: agency.GetCreatedAtMs(), } } } @@ -115,3 +115,12 @@ func (h *Handler) getBDCenterStats(writer http.ResponseWriter, request *http.Req "items": items, "total": len(items), }) } + +// bdCenterAgencyHostIncome 合并工资型与永久积分型 Host 礼物投影,覆盖政策切换前后的查询区间; +// Agency 分成和已结算工资属于其他口径,不能混入 BD 页面展示的 Agency Host 总收入。 +func bdCenterAgencyHostIncome(stats *walletv1.AgencyHostGiftStats) int64 { + if stats == nil { + return 0 + } + return stats.GetGiftIncome() + stats.GetPointDiamondHostIncome() +} diff --git a/services/gateway-service/internal/transport/http/userapi/host_center_stats_handler_test.go b/services/gateway-service/internal/transport/http/userapi/host_center_stats_handler_test.go index e3fa98b0..2d0ec65d 100644 --- a/services/gateway-service/internal/transport/http/userapi/host_center_stats_handler_test.go +++ b/services/gateway-service/internal/transport/http/userapi/host_center_stats_handler_test.go @@ -33,6 +33,21 @@ func TestParseHostCenterStatsRangeUsesInclusiveNaturalDays(t *testing.T) { } } +func TestBDCenterAgencyHostIncomeIncludesBothPolicyProjections(t *testing.T) { + stats := &walletv1.AgencyHostGiftStats{ + GiftIncome: 8800, + PointDiamondHostIncome: 1200, + // Agency 分成不能计入 BD 表格里的直属 Host 总收入。 + PointDiamondAgencyShareIncome: 300, + } + if got := bdCenterAgencyHostIncome(stats); got != 10000 { + t.Fatalf("bd agency host income = %d, want 10000", got) + } + if got := bdCenterAgencyHostIncome(nil); got != 0 { + t.Fatalf("nil bd agency stats income = %d, want 0", got) + } +} + func TestParseHostCenterStatsRangeRejectsInvalidOrOversizedRanges(t *testing.T) { for _, rawURL := range []string{ "/api/v1/host-center/stats?start_date=2026-07-12&end_date=2026-07-01", diff --git a/services/user-service/internal/storage/mysql/host/stats.go b/services/user-service/internal/storage/mysql/host/stats.go index bfb83591..cb8e64a1 100644 --- a/services/user-service/internal/storage/mysql/host/stats.go +++ b/services/user-service/internal/storage/mysql/host/stats.go @@ -31,20 +31,43 @@ func (r *Repository) GetHostEngagementStats(ctx context.Context, query hostdomai return hostdomain.EngagementStats{}, err } - // 私聊事实严格按当前 App 的复合索引读取;Fami 尚未上线,不再跨租户回捞旧错误数据。 - // 这样所有 App 走同一查询路径,也避免默认 App 身份成为业务分支。 - if err := r.db.QueryRowContext(ctx, ` - SELECT COUNT(DISTINCT matched.sender_user_id) - FROM ( - SELECT sender_user_id + // Fami 上线前的 C2C 回调曾把事实写入默认租户;读取按两个精确 app_code 索引区间 + // 合并,并用发送者主数据校验真实租户,既恢复历史私信人数又不把 Lalu 用户串入 Fami。 + privateMessageSQL := ` + SELECT COUNT(DISTINCT matched.sender_user_id) + FROM ( + SELECT sender_user_id FROM user_private_message_events FORCE INDEX (idx_private_message_target_time) WHERE app_code = ? AND target_user_id = ? AND occurred_at_ms >= ? AND occurred_at_ms < ? + ) matched + INNER JOIN users sender + ON sender.user_id = matched.sender_user_id AND sender.app_code = ?` + privateMessageArgs := []any{appCode, query.HostUserID, query.StartAtMS, query.EndAtMS, appCode} + if appCode != appcode.Default { + // UNION ALL 保持两个复合索引范围独立可用;最终 DISTINCT 收敛迁移前后重复发送者。 + privateMessageSQL = ` + SELECT COUNT(DISTINCT matched.sender_user_id) + FROM ( + SELECT sender_user_id + FROM user_private_message_events FORCE INDEX (idx_private_message_target_time) + WHERE app_code = ? AND target_user_id = ? + AND occurred_at_ms >= ? AND occurred_at_ms < ? + UNION ALL + SELECT sender_user_id + FROM user_private_message_events FORCE INDEX (idx_private_message_target_time) + WHERE app_code = ? AND target_user_id = ? + AND occurred_at_ms >= ? AND occurred_at_ms < ? ) matched INNER JOIN users sender - ON sender.user_id = matched.sender_user_id AND sender.app_code = ?`, - appCode, query.HostUserID, query.StartAtMS, query.EndAtMS, appCode, - ).Scan(&stats.PrivateMessageSenders); err != nil { + ON sender.user_id = matched.sender_user_id AND sender.app_code = ?` + privateMessageArgs = []any{ + appCode, query.HostUserID, query.StartAtMS, query.EndAtMS, + appcode.Default, query.HostUserID, query.StartAtMS, query.EndAtMS, + appCode, + } + } + if err := r.db.QueryRowContext(ctx, privateMessageSQL, privateMessageArgs...).Scan(&stats.PrivateMessageSenders); err != nil { return hostdomain.EngagementStats{}, err } diff --git a/services/user-service/internal/storage/mysql/host/stats_test.go b/services/user-service/internal/storage/mysql/host/stats_test.go index a96cc234..c98eb8b0 100644 --- a/services/user-service/internal/storage/mysql/host/stats_test.go +++ b/services/user-service/internal/storage/mysql/host/stats_test.go @@ -27,11 +27,17 @@ func TestGetHostEngagementStatsAggregatesOwnedFacts(t *testing.T) { FROM user_private_message_events FORCE INDEX (idx_private_message_target_time) WHERE app_code = ? AND target_user_id = ? AND occurred_at_ms >= ? AND occurred_at_ms < ? + UNION ALL + SELECT sender_user_id + FROM user_private_message_events FORCE INDEX (idx_private_message_target_time) + WHERE app_code = ? AND target_user_id = ? + AND occurred_at_ms >= ? AND occurred_at_ms < ? ) matched INNER JOIN users sender ON sender.user_id = matched.sender_user_id AND sender.app_code = ?`)). WithArgs( "fami", int64(42), int64(1783296000000), int64(1783900800000), + "lalu", int64(42), int64(1783296000000), int64(1783900800000), "fami", ). WillReturnRows(sqlmock.NewRows([]string{"senders"}).AddRow(int64(6)))