fix(fami): 恢复公会中心统计数据

This commit is contained in:
zhx 2026-07-22 21:28:14 +08:00
parent 3dd58b2f9c
commit 6428a26413
4 changed files with 63 additions and 10 deletions

View File

@ -91,7 +91,7 @@ func (h *Handler) getBDCenterStats(writer http.ResponseWriter, request *http.Req
items[index] = bdCenterAgencyStatsData{ items[index] = bdCenterAgencyStatsData{
Agency: hostAgencyFromProto(agency), Owner: owner, Country: owner.Country, Agency: hostAgencyFromProto(agency), Owner: owner, Country: owner.Country,
TotalHosts: len(hostIDs), GiftedHostCount: stats.GetStats().GetGiftedHostCount(), 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), "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()
}

View File

@ -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) { func TestParseHostCenterStatsRangeRejectsInvalidOrOversizedRanges(t *testing.T) {
for _, rawURL := range []string{ for _, rawURL := range []string{
"/api/v1/host-center/stats?start_date=2026-07-12&end_date=2026-07-01", "/api/v1/host-center/stats?start_date=2026-07-12&end_date=2026-07-01",

View File

@ -31,20 +31,43 @@ func (r *Repository) GetHostEngagementStats(ctx context.Context, query hostdomai
return hostdomain.EngagementStats{}, err return hostdomain.EngagementStats{}, err
} }
// 私聊事实严格按当前 App 的复合索引读取Fami 尚未上线,不再跨租户回捞旧错误数据。 // Fami 上线前的 C2C 回调曾把事实写入默认租户;读取按两个精确 app_code 索引区间
// 这样所有 App 走同一查询路径,也避免默认 App 身份成为业务分支 // 合并,并用发送者主数据校验真实租户,既恢复历史私信人数又不把 Lalu 用户串入 Fami
if err := r.db.QueryRowContext(ctx, ` privateMessageSQL := `
SELECT COUNT(DISTINCT matched.sender_user_id) SELECT COUNT(DISTINCT matched.sender_user_id)
FROM ( FROM (
SELECT sender_user_id SELECT sender_user_id
FROM user_private_message_events FORCE INDEX (idx_private_message_target_time) FROM user_private_message_events FORCE INDEX (idx_private_message_target_time)
WHERE app_code = ? AND target_user_id = ? WHERE app_code = ? AND target_user_id = ?
AND occurred_at_ms >= ? AND occurred_at_ms < ? 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 ) matched
INNER JOIN users sender INNER JOIN users sender
ON sender.user_id = matched.sender_user_id AND sender.app_code = ?`, ON sender.user_id = matched.sender_user_id AND sender.app_code = ?`
appCode, query.HostUserID, query.StartAtMS, query.EndAtMS, appCode, privateMessageArgs = []any{
).Scan(&stats.PrivateMessageSenders); err != nil { 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 return hostdomain.EngagementStats{}, err
} }

View File

@ -27,11 +27,17 @@ func TestGetHostEngagementStatsAggregatesOwnedFacts(t *testing.T) {
FROM user_private_message_events FORCE INDEX (idx_private_message_target_time) FROM user_private_message_events FORCE INDEX (idx_private_message_target_time)
WHERE app_code = ? AND target_user_id = ? WHERE app_code = ? AND target_user_id = ?
AND occurred_at_ms >= ? AND occurred_at_ms < ? 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 ) matched
INNER JOIN users sender INNER JOIN users sender
ON sender.user_id = matched.sender_user_id AND sender.app_code = ?`)). ON sender.user_id = matched.sender_user_id AND sender.app_code = ?`)).
WithArgs( WithArgs(
"fami", int64(42), int64(1783296000000), int64(1783900800000), "fami", int64(42), int64(1783296000000), int64(1783900800000),
"lalu", int64(42), int64(1783296000000), int64(1783900800000),
"fami", "fami",
). ).
WillReturnRows(sqlmock.NewRows([]string{"senders"}).AddRow(int64(6))) WillReturnRows(sqlmock.NewRows([]string{"senders"}).AddRow(int64(6)))