fix: remove unused channel activity filters

This commit is contained in:
Yvan
2026-11-05 14:51:51 +08:00
parent dd02620b5e
commit 56874b33da
7 changed files with 14 additions and 72 deletions
-1
View File
@@ -30,7 +30,6 @@ type AnalyticsFilter struct {
Sources, GameNames []string
PromoteName string
PromoteType *int
HasRegister, HasPay *bool
StartDate, EndDate *time.Time
Begin, End, SortField string
Ascending bool
+3 -32
View File
@@ -2,7 +2,6 @@ package business
import (
"context"
"math/big"
"sort"
"strings"
"time"
@@ -172,7 +171,6 @@ func (r *analyticsRepo) Aggregate(ctx context.Context, kind business.AnalyticsKi
var global []portraitCountPO
globalPay := "0"
withGlobal := kind == business.ChannelAnalytics && scope.Viewer.All
metricFiltered := kind == business.ChannelAnalytics && (f.HasRegister != nil || f.HasPay != nil)
group, queryContext := errgroup.WithContext(ctx)
// 每个请求最多两个并行查询,复用同一存储客户端;结果读取完后再组装 DO。
group.SetLimit(2)
@@ -191,7 +189,7 @@ func (r *analyticsRepo) Aggregate(ctx context.Context, kind business.AnalyticsKi
}
return nil
})
if withGlobal && !metricFiltered {
if withGlobal {
group.Go(func() error {
q := analyticsQuery(queryDB, scope, f, "tab_sw_report_day_key", mode)
if err := q.Select("r.metric,COUNT(DISTINCT " + portraitIdentity(queryDB, scope.IDCardDedup) + ") AS count").Group("r.metric").Scan(&global).Error; err != nil {
@@ -216,8 +214,8 @@ func (r *analyticsRepo) Aggregate(ctx context.Context, kind business.AnalyticsKi
putAnalyticsCount(&rowFor(po).Counts, po.Metric, po.Count)
}
}
// Report rows come from facts in the selected period. Registration/payment
// filters narrow that same set; they must not add the entire channel catalog.
// Report rows come from facts in the selected period. Looking up channel
// metadata must not expand them into the entire channel catalog.
if kind == business.ChannelAnalytics && !scope.Viewer.All {
if err := filterAnalyticsRows(db, scope, f, rowMap); err != nil {
return nil, err
@@ -228,33 +226,6 @@ func (r *analyticsRepo) Aggregate(ctx context.Context, kind business.AnalyticsKi
return nil, err
}
}
if metricFiltered {
for key, row := range rowMap {
pay, ok := new(big.Rat).SetString(row.Pay)
if !ok {
return nil, business.ErrStorage
}
if f.HasRegister != nil && (row.Counts.Register > 0) != *f.HasRegister || f.HasPay != nil && (pay.Sign() > 0) != *f.HasPay {
delete(rowMap, key)
}
}
// Recompute the administrator's distinct-player totals over matched
// channel trees, before pagination. Summing per-row ratios is incorrect.
if withGlobal && len(rowMap) > 0 {
keys := make([]business.AnalyticsKey, 0, len(rowMap))
for key := range rowMap {
keys = append(keys, key)
}
q, id := analyticsRoot(db, kind, scope, f, "tab_sw_report_day_key", mode, false)
if err := q.Where("(r.source_db,"+id+") IN ?", analyticsPairs(keys)).Select("r.metric,COUNT(DISTINCT " + portraitIdentity(db, scope.IDCardDedup) + ") AS count").Group("r.metric").Scan(&global).Error; err != nil {
return nil, business.ErrStorage
}
q, id = analyticsRoot(db, kind, scope, f, "tab_sw_report_day", 0, true)
if err := q.Where("(r.source_db,"+id+") IN ?", analyticsPairs(keys)).Select("COALESCE(SUM(r.total_pay),0)").Scan(&globalPay).Error; err != nil {
return nil, business.ErrStorage
}
}
}
if withGlobal {
totals := toPortraitCounts(global)
result.GlobalCounts, result.GlobalPay = &totals, globalPay
+5 -28
View File
@@ -61,7 +61,7 @@ func TestAnalyticsChannelTreeAndAdminOfficialRows(t *testing.T) {
}
}
func TestAnalyticsFiltersOnlyNarrowPeriodRowsAndRecalculateTotals(t *testing.T) {
func TestAnalyticsRowsOnlyComeFromSelectedPeriod(t *testing.T) {
db, repo, scope, filter := prepareAnalyticsData(t)
for _, sql := range []string{
"INSERT INTO tab_promote(id,source_db,account,promote_level,parent_id,top_promote_id,busier_id) VALUES(200,'lx','outside-period',1,0,0,14),(201,'lx','active-only',1,0,0,14),(202,'lx','never-active',1,0,0,14)",
@@ -72,10 +72,8 @@ func TestAnalyticsFiltersOnlyNarrowPeriodRowsAndRecalculateTotals(t *testing.T)
t.Fatal(err)
}
}
yes, no := true, false
for _, viewer := range []business.UserScope{scope.Viewer, {All: true}} {
scope.Viewer = viewer
filter.HasRegister, filter.HasPay = nil, nil
baseline, err := repo.Aggregate(context.Background(), business.ChannelAnalytics, scope, filter)
if err != nil {
t.Fatal(err)
@@ -90,37 +88,16 @@ func TestAnalyticsFiltersOnlyNarrowPeriodRowsAndRecalculateTotals(t *testing.T)
if !keys[business.AnalyticsKey{SourceDB: "lx", ID: 201}] {
t.Fatal("仅有活跃、没有注册或充值的统计行不能遗漏")
}
for _, flags := range [][2]*bool{{&no, &no}, {&no, nil}, {nil, &no}, {&yes, nil}, {nil, &yes}, {&yes, &yes}, {&no, &yes}, {&yes, &no}} {
filter.HasRegister, filter.HasPay = flags[0], flags[1]
data, err := repo.Aggregate(context.Background(), business.ChannelAnalytics, scope, filter)
if err != nil {
t.Fatal(err)
}
for _, row := range data.Rows {
if !keys[row.AnalyticsKey] {
t.Fatalf("筛选不能扩大默认统计范围: %+v", row)
}
if flags[0] != nil && (row.Counts.Register > 0) != *flags[0] || flags[1] != nil && (portraitTestMoney(row.Pay) != "0.00") != *flags[1] {
t.Fatalf("筛选条件没有同时生效: %+v", row)
}
}
if flags[0] == &no && flags[1] == &no {
if len(data.Rows) != 1 || data.Rows[0].ID != 201 {
t.Fatalf("无注册且无充值仍应保留仅活跃渠道: %+v", data.Rows)
}
if viewer.All && (data.GlobalCounts.Register != 0 || data.GlobalCounts.Active != 1 || portraitTestMoney(data.GlobalPay) != "0.00") {
t.Fatalf("管理员合计未跟随过滤: %+v", data)
}
}
if viewer.All && (baseline.GlobalCounts.Register != 4 || baseline.GlobalCounts.Active != 3 || portraitTestMoney(baseline.GlobalPay) != "2070.60") {
t.Fatalf("区间外记录不能改变全量合计: %+v", baseline)
}
}
filter.HasRegister, filter.HasPay = &yes, &yes
filter.Sources, filter.PromoteName = []string{"lx"}, "root-channel"
data, err := repo.Aggregate(context.Background(), business.ChannelAnalytics, scope, filter)
if err != nil || len(data.Rows) != 1 || portraitTestMoney(data.GlobalPay) != "60.60" || data.GlobalCounts.Register != data.Rows[0].Counts.Register {
t.Fatalf("平台、名称及指标筛选须共同约束列表与全量合计: %+v %v", data, err)
t.Fatalf("平台、名称筛选须共同约束列表与全量合计: %+v %v", data, err)
}
filter = business.AnalyticsFilter{Sources: []string{"lx", "hw"}, Begin: "2027-01-01", End: "2027-01-02", HasRegister: &no, HasPay: &no}
filter = business.AnalyticsFilter{Sources: []string{"lx", "hw"}, Begin: "2027-01-01", End: "2027-01-02"}
data, err = repo.Aggregate(context.Background(), business.ChannelAnalytics, scope, filter)
if err != nil || len(data.Rows) != 0 || data.GlobalCounts.Register != 0 || portraitTestMoney(data.GlobalPay) != "0.00" {
t.Fatalf("无报表记录的区间应返回空列表与零合计: %+v %v", data, err)
+1 -1
View File
@@ -25,7 +25,7 @@ func analyticsRequest(req *dto.AnalyticsQuery) (business.AnalyticsFilter, error)
if err != nil {
return business.AnalyticsFilter{}, err
}
f := business.AnalyticsFilter{Sources: sources, GameNames: splitQueryValues(req.GameNames), PromoteName: strings.TrimSpace(req.PromoteName), PromoteType: req.PromoteType, HasRegister: req.HasRegister, HasPay: req.HasPay, SortField: strings.TrimSpace(req.SortField), Ascending: strings.EqualFold(strings.TrimSpace(req.SortOrder), "asc")}
f := business.AnalyticsFilter{Sources: sources, GameNames: splitQueryValues(req.GameNames), PromoteName: strings.TrimSpace(req.PromoteName), PromoteType: req.PromoteType, SortField: strings.TrimSpace(req.SortField), Ascending: strings.EqualFold(strings.TrimSpace(req.SortOrder), "asc")}
begin, end := strings.TrimSpace(req.BeginTime), strings.TrimSpace(req.EndTime)
location := time.FixedZone("Asia/Shanghai", 8*3600)
if begin != "" {
+4 -5
View File
@@ -32,12 +32,11 @@ func TestRejectIncompleteAndReversedDateRanges(t *testing.T) {
}
}
func TestAnalyticsOpenEndedDatesAndMetricFilters(t *testing.T) {
no, yes := false, true
func TestAnalyticsDateBoundsPassThroughToUsecase(t *testing.T) {
for _, dates := range [][2]string{{"", ""}, {"2026-11-01", ""}, {"", "2026-11-03"}} {
filter, err := analyticsRequest(&dto.AnalyticsQuery{BeginTime: dates[0], EndTime: dates[1], HasRegister: &no, HasPay: &yes})
if err != nil || (filter.StartDate == nil) != (dates[0] == "") || (filter.EndDate == nil) != (dates[1] == "") || *filter.HasRegister || !*filter.HasPay {
t.Fatalf("独立起止日期及是否有数据筛选必须原样传递: %+v %v", filter, err)
filter, err := analyticsRequest(&dto.AnalyticsQuery{BeginTime: dates[0], EndTime: dates[1]})
if err != nil || (filter.StartDate == nil) != (dates[0] == "") || (filter.EndDate == nil) != (dates[1] == "") {
t.Fatalf("独立起止日期必须原样传递给业务层填充默认值: %+v %v", filter, err)
}
}
for _, dates := range [][2]string{{"bad", ""}, {"", "bad"}, {"2026-11-03", "2026-11-01"}} {
-2
View File
@@ -7,8 +7,6 @@ type AnalyticsQuery struct {
Sources []string `form:"sourceDb"`
GameNames []string `form:"gameName"`
PromoteType *int `form:"promoteType"`
HasRegister *bool `form:"hasRegister"`
HasPay *bool `form:"hasPay"`
PromoteName string `form:"promoteName"`
BeginTime string `form:"beginTime"`
EndTime string `form:"endTime"`
@@ -9,8 +9,6 @@
</el-form-item>
<el-form-item v-if="isChannel" label="渠道类型"><el-select v-model="query.promoteType" clearable placeholder="全部渠道类型" style="width: 150px"><el-option label="公会" :value="1" /><el-option label="个人" :value="0" /></el-select></el-form-item>
<el-form-item v-if="isChannel" label="渠道名称"><el-input v-model="query.promoteName" clearable placeholder="渠道名称或账号" /></el-form-item>
<el-form-item v-if="isChannel" label="是否有注册"><el-select v-model="query.hasRegister" clearable placeholder="全部" style="width:130px" @change="search"><el-option label="有注册" :value="true" /><el-option label="无注册" :value="false" /></el-select></el-form-item>
<el-form-item v-if="isChannel" label="是否有充值"><el-select v-model="query.hasPay" clearable placeholder="全部" style="width:130px" @change="search"><el-option label="有充值" :value="true" /><el-option label="无充值" :value="false" /></el-select></el-form-item>
<el-form-item v-if="isChannel" label="统计时间"><div class="report-date-range"><el-date-picker v-model="dates[0]" type="date" value-format="YYYY-MM-DD" placeholder="统计开始时间" :shortcuts="startShortcuts" clearable /><span>至</span><el-date-picker v-model="dates[1]" type="date" value-format="YYYY-MM-DD" placeholder="统计结束时间" :shortcuts="endShortcuts" clearable /></div></el-form-item>
<el-form-item v-else label="统计时间"><el-date-picker v-model="dates" type="daterange" value-format="YYYY-MM-DD" start-placeholder="开始日期" end-placeholder="结束日期" range-separator="至" :clearable="false" :shortcuts="shortcuts" style="width: 300px" /></el-form-item>
<el-form-item><el-button type="primary" icon="search" native-type="submit">{{ isChannel ? '搜索' : '查询' }}</el-button></el-form-item>
@@ -57,7 +55,7 @@ const startShortcuts = shortcuts.map(item => ({ text: item.text, value: () => it
const endShortcuts = shortcuts.map(item => ({ text: item.text, value: () => item.value()[1] }))
const sources = ref(Object.keys(platforms)), games = ref([]), gameOptions = ref([]), gameLoading = ref(false)
const selectedGame = computed({ get: () => games.value[0] || '', set: value => { games.value = value ? [value] : [] } })
const query = reactive({ pageNum: 1, pageSize: 10, promoteType: undefined, promoteName: '', hasRegister: undefined, hasPay: undefined, sortField: isChannel.value ? '' : 'totalPay', sortOrder: 'desc' })
const query = reactive({ pageNum: 1, pageSize: 10, promoteType: undefined, promoteName: '', sortField: isChannel.value ? '' : 'totalPay', sortOrder: 'desc' })
const rows = ref([]), total = ref(0), summary = ref(null), result = ref(null), loading = ref(false), available = ref(true), error = ref('')
const loadedQuery = ref(null), exporting = ref(false)
let version = 0, gameVersion = 0