修复了问题
This commit is contained in:
parent
0cd30e222d
commit
d8c07b07ac
@ -109,17 +109,13 @@ func (s *MongoRechargeBillSource) ListRechargeBills(ctx context.Context, query l
|
||||
if s == nil || s.collection == nil {
|
||||
return nil, 0, fmt.Errorf("legacy bill source is not configured")
|
||||
}
|
||||
filter, matchable, err := s.billFilter(ctx, query)
|
||||
filter, regionCodes, matchable, err := s.billFilter(ctx, query)
|
||||
if err != nil {
|
||||
return nil, 0, err
|
||||
}
|
||||
if !matchable {
|
||||
return []rechargeBillDTO{}, 0, nil
|
||||
}
|
||||
total, err := s.collection.CountDocuments(ctx, filter)
|
||||
if err != nil {
|
||||
return nil, 0, fmt.Errorf("count legacy recharge bills for %s: %w", s.config.AppCode, err)
|
||||
}
|
||||
page := query.Page
|
||||
if page < 1 {
|
||||
page = 1
|
||||
@ -128,6 +124,17 @@ func (s *MongoRechargeBillSource) ListRechargeBills(ctx context.Context, query l
|
||||
if pageSize < 1 {
|
||||
pageSize = 10
|
||||
}
|
||||
if len(regionCodes) == 0 {
|
||||
return s.listBillsByFilter(ctx, filter, page, pageSize)
|
||||
}
|
||||
return s.listBillsByRegionPipeline(ctx, filter, regionCodes, page, pageSize)
|
||||
}
|
||||
|
||||
func (s *MongoRechargeBillSource) listBillsByFilter(ctx context.Context, filter bson.M, page int, pageSize int) ([]rechargeBillDTO, int64, error) {
|
||||
total, err := s.collection.CountDocuments(ctx, filter)
|
||||
if err != nil {
|
||||
return nil, 0, fmt.Errorf("count legacy recharge bills for %s: %w", s.config.AppCode, err)
|
||||
}
|
||||
cursor, err := s.collection.Find(ctx, filter, options.Find().
|
||||
SetSort(bson.D{{Key: "createTime", Value: -1}, {Key: "_id", Value: -1}}).
|
||||
SetSkip(int64(page-1)*int64(pageSize)).
|
||||
@ -136,26 +143,77 @@ func (s *MongoRechargeBillSource) ListRechargeBills(ctx context.Context, query l
|
||||
return nil, 0, fmt.Errorf("query legacy recharge bills for %s: %w", s.config.AppCode, err)
|
||||
}
|
||||
defer cursor.Close(ctx)
|
||||
items, err := s.decodeBillCursor(ctx, cursor, pageSize)
|
||||
if err != nil {
|
||||
return nil, 0, err
|
||||
}
|
||||
return items, total, nil
|
||||
}
|
||||
|
||||
// listBillsByRegionPipeline 处理区域筛选:likei 账单的 countryCode 长期为空,区域归属只能按付款用户
|
||||
// user_run_profile.countryCode 反查,因此列表和计数都要走 $lookup 聚合。
|
||||
func (s *MongoRechargeBillSource) listBillsByRegionPipeline(ctx context.Context, filter bson.M, regionCodes []string, page int, pageSize int) ([]rechargeBillDTO, int64, error) {
|
||||
base := append(mongo.Pipeline{bson.D{{Key: "$match", Value: filter}}}, legacyRegionLookupStages(regionCodes)...)
|
||||
|
||||
countCursor, err := s.collection.Aggregate(ctx, append(clonePipeline(base), bson.D{{Key: "$count", Value: "n"}}))
|
||||
if err != nil {
|
||||
return nil, 0, fmt.Errorf("count legacy recharge bills for %s: %w", s.config.AppCode, err)
|
||||
}
|
||||
defer countCursor.Close(ctx)
|
||||
var total int64
|
||||
if countCursor.Next(ctx) {
|
||||
var row struct {
|
||||
N int64 `bson:"n"`
|
||||
}
|
||||
if err := countCursor.Decode(&row); err != nil {
|
||||
return nil, 0, fmt.Errorf("decode legacy recharge bill count for %s: %w", s.config.AppCode, err)
|
||||
}
|
||||
total = row.N
|
||||
}
|
||||
if err := countCursor.Err(); err != nil {
|
||||
return nil, 0, fmt.Errorf("iterate legacy recharge bill count for %s: %w", s.config.AppCode, err)
|
||||
}
|
||||
if total == 0 {
|
||||
return []rechargeBillDTO{}, 0, nil
|
||||
}
|
||||
|
||||
pagePipeline := append(clonePipeline(base),
|
||||
bson.D{{Key: "$sort", Value: bson.D{{Key: "createTime", Value: -1}, {Key: "_id", Value: -1}}}},
|
||||
bson.D{{Key: "$skip", Value: int64(page-1) * int64(pageSize)}},
|
||||
bson.D{{Key: "$limit", Value: int64(pageSize)}},
|
||||
)
|
||||
cursor, err := s.collection.Aggregate(ctx, pagePipeline)
|
||||
if err != nil {
|
||||
return nil, 0, fmt.Errorf("query legacy recharge bills for %s: %w", s.config.AppCode, err)
|
||||
}
|
||||
defer cursor.Close(ctx)
|
||||
items, err := s.decodeBillCursor(ctx, cursor, pageSize)
|
||||
if err != nil {
|
||||
return nil, 0, err
|
||||
}
|
||||
return items, total, nil
|
||||
}
|
||||
|
||||
func (s *MongoRechargeBillSource) decodeBillCursor(ctx context.Context, cursor *mongo.Cursor, pageSize int) ([]rechargeBillDTO, error) {
|
||||
items := make([]rechargeBillDTO, 0, pageSize)
|
||||
for cursor.Next(ctx) {
|
||||
var document legacyPurchaseDocument
|
||||
if err := cursor.Decode(&document); err != nil {
|
||||
return nil, 0, fmt.Errorf("decode legacy recharge bill for %s: %w", s.config.AppCode, err)
|
||||
return nil, fmt.Errorf("decode legacy recharge bill for %s: %w", s.config.AppCode, err)
|
||||
}
|
||||
items = append(items, legacyRechargeBillDTO(s.config, document))
|
||||
}
|
||||
if err := cursor.Err(); err != nil {
|
||||
return nil, 0, fmt.Errorf("iterate legacy recharge bills for %s: %w", s.config.AppCode, err)
|
||||
return nil, fmt.Errorf("iterate legacy recharge bills for %s: %w", s.config.AppCode, err)
|
||||
}
|
||||
return items, total, nil
|
||||
return items, nil
|
||||
}
|
||||
|
||||
func (s *MongoRechargeBillSource) SummarizeRechargeBills(ctx context.Context, query legacyRechargeBillQuery) (rechargeBillSummaryDTO, error) {
|
||||
if s == nil || s.collection == nil {
|
||||
return rechargeBillSummaryDTO{}, fmt.Errorf("legacy bill source is not configured")
|
||||
}
|
||||
filter, matchable, err := s.billFilter(ctx, query)
|
||||
filter, regionCodes, matchable, err := s.billFilter(ctx, query)
|
||||
if err != nil {
|
||||
return rechargeBillSummaryDTO{}, err
|
||||
}
|
||||
@ -174,19 +232,18 @@ func (s *MongoRechargeBillSource) SummarizeRechargeBills(ctx context.Context, qu
|
||||
bson.M{"$convert": bson.M{"input": "$$this.content", "to": "long", "onError": 0, "onNull": 0}},
|
||||
}},
|
||||
}}
|
||||
cursor, err := s.collection.Aggregate(ctx, mongo.Pipeline{
|
||||
{{Key: "$match", Value: filter}},
|
||||
{{Key: "$group", Value: bson.M{
|
||||
"_id": bson.M{"$cond": bson.A{
|
||||
bson.M{"$eq": bson.A{"$factory.factoryCode", "GOOGLE"}},
|
||||
"google",
|
||||
"third_party",
|
||||
}},
|
||||
"billCount": bson.M{"$sum": 1},
|
||||
"usd": bson.M{"$sum": bson.M{"$ifNull": bson.A{"$amountUsd", 0}}},
|
||||
"coin": bson.M{"$sum": coinExpr},
|
||||
}}},
|
||||
})
|
||||
pipeline := append(mongo.Pipeline{bson.D{{Key: "$match", Value: filter}}}, legacyRegionLookupStages(regionCodes)...)
|
||||
pipeline = append(pipeline, bson.D{{Key: "$group", Value: bson.M{
|
||||
"_id": bson.M{"$cond": bson.A{
|
||||
bson.M{"$eq": bson.A{"$factory.factoryCode", "GOOGLE"}},
|
||||
"google",
|
||||
"third_party",
|
||||
}},
|
||||
"billCount": bson.M{"$sum": 1},
|
||||
"usd": bson.M{"$sum": bson.M{"$ifNull": bson.A{"$amountUsd", 0}}},
|
||||
"coin": bson.M{"$sum": coinExpr},
|
||||
}}})
|
||||
cursor, err := s.collection.Aggregate(ctx, pipeline)
|
||||
if err != nil {
|
||||
return rechargeBillSummaryDTO{}, fmt.Errorf("summarize legacy recharge bills for %s: %w", s.config.AppCode, err)
|
||||
}
|
||||
@ -228,7 +285,8 @@ func (s *MongoRechargeBillSource) SummarizeRechargeBills(ctx context.Context, qu
|
||||
}
|
||||
|
||||
// billFilter 构造与 hyapp 账单列表同口径的 Mongo 过滤条件;返回 matchable=false 表示筛选注定无结果。
|
||||
func (s *MongoRechargeBillSource) billFilter(ctx context.Context, query legacyRechargeBillQuery) (bson.M, bool, error) {
|
||||
// regionCodes 非空时调用方必须叠加 legacyRegionLookupStages:likei 账单不带国家码,区域归属按付款用户资料判定。
|
||||
func (s *MongoRechargeBillSource) billFilter(ctx context.Context, query legacyRechargeBillQuery) (bson.M, []string, bool, error) {
|
||||
filter := bson.M{
|
||||
"sysOrigin": s.config.SysOrigin,
|
||||
"status": "SUCCESS",
|
||||
@ -255,14 +313,45 @@ func (s *MongoRechargeBillSource) billFilter(ctx context.Context, query legacyRe
|
||||
if query.RegionID > 0 {
|
||||
codes, handled, err := s.regionCountryCodes(ctx, query.RegionID)
|
||||
if err != nil {
|
||||
return nil, false, err
|
||||
return nil, nil, false, err
|
||||
}
|
||||
if !handled || len(codes) == 0 {
|
||||
return nil, false, nil
|
||||
return nil, nil, false, nil
|
||||
}
|
||||
filter["countryCode"] = bson.M{"$in": codes}
|
||||
return filter, codes, true, nil
|
||||
}
|
||||
return filter, true, nil
|
||||
return filter, nil, true, nil
|
||||
}
|
||||
|
||||
const legacyUserProfileCollection = "user_run_profile"
|
||||
|
||||
// legacyRegionLookupStages 按付款用户的 user_run_profile.countryCode 过滤区域;codes 为空时不追加任何阶段。
|
||||
func legacyRegionLookupStages(codes []string) mongo.Pipeline {
|
||||
if len(codes) == 0 {
|
||||
return mongo.Pipeline{}
|
||||
}
|
||||
return mongo.Pipeline{
|
||||
bson.D{{Key: "$lookup", Value: bson.M{
|
||||
"from": legacyUserProfileCollection,
|
||||
"localField": "acceptUserId",
|
||||
"foreignField": "_id",
|
||||
"as": "legacyBillUser",
|
||||
}}},
|
||||
bson.D{{Key: "$addFields", Value: bson.M{
|
||||
"legacyBillCountry": bson.M{"$toUpper": bson.M{"$ifNull": bson.A{
|
||||
bson.M{"$first": "$legacyBillUser.countryCode"},
|
||||
"",
|
||||
}}},
|
||||
}}},
|
||||
bson.D{{Key: "$match", Value: bson.M{"legacyBillCountry": bson.M{"$in": codes}}}},
|
||||
}
|
||||
}
|
||||
|
||||
// clonePipeline 复制聚合管道,避免 append 复用底层数组导致 count/分页两条管道互相踩踏。
|
||||
func clonePipeline(pipeline mongo.Pipeline) mongo.Pipeline {
|
||||
cloned := make(mongo.Pipeline, len(pipeline))
|
||||
copy(cloned, pipeline)
|
||||
return cloned
|
||||
}
|
||||
|
||||
func (s *MongoRechargeBillSource) regionCountryCodes(ctx context.Context, regionID int64) ([]string, bool, error) {
|
||||
|
||||
@ -124,7 +124,7 @@ func TestLegacyBillFilterBuildsQuery(t *testing.T) {
|
||||
config: yumiBillSourceConfig(),
|
||||
regionResolvers: []legacyRegionCountryResolver{staticLegacyRegionResolver{codes: []string{"tr", "SA"}, handled: true}},
|
||||
}
|
||||
filter, matchable, err := source.billFilter(context.Background(), legacyRechargeBillQuery{
|
||||
filter, regionCodes, matchable, err := source.billFilter(context.Background(), legacyRechargeBillQuery{
|
||||
Keyword: "GPA.1",
|
||||
RegionID: 9_000_000_000_000_000_001,
|
||||
StartAtMS: 1_000,
|
||||
@ -140,13 +140,12 @@ func TestLegacyBillFilterBuildsQuery(t *testing.T) {
|
||||
if !ok || !timeRange["$gte"].(time.Time).Equal(time.UnixMilli(1_000)) || !timeRange["$lt"].(time.Time).Equal(time.UnixMilli(2_000)) {
|
||||
t.Fatalf("unexpected time range: %v", filter["createTime"])
|
||||
}
|
||||
countries, ok := filter["countryCode"].(bson.M)
|
||||
if !ok {
|
||||
t.Fatalf("country filter missing: %v", filter)
|
||||
// likei 账单不带国家码;区域条件必须以国家码清单返回,由聚合阶段按付款用户资料匹配。
|
||||
if len(regionCodes) != 2 || regionCodes[0] != "TR" || regionCodes[1] != "SA" {
|
||||
t.Fatalf("unexpected region codes: %v", regionCodes)
|
||||
}
|
||||
codes, ok := countries["$in"].([]string)
|
||||
if !ok || len(codes) != 2 || codes[0] != "TR" || codes[1] != "SA" {
|
||||
t.Fatalf("unexpected country codes: %v", countries["$in"])
|
||||
if _, ok := filter["countryCode"]; ok {
|
||||
t.Fatalf("bill countryCode filter should not be used: %v", filter)
|
||||
}
|
||||
if _, ok := filter["$or"]; !ok {
|
||||
t.Fatalf("keyword filter missing: %v", filter)
|
||||
@ -155,7 +154,7 @@ func TestLegacyBillFilterBuildsQuery(t *testing.T) {
|
||||
|
||||
func TestLegacyBillFilterUnresolvedRegionMatchesNothing(t *testing.T) {
|
||||
source := &MongoRechargeBillSource{config: yumiBillSourceConfig()}
|
||||
_, matchable, err := source.billFilter(context.Background(), legacyRechargeBillQuery{RegionID: 123})
|
||||
_, _, matchable, err := source.billFilter(context.Background(), legacyRechargeBillQuery{RegionID: 123})
|
||||
if err != nil {
|
||||
t.Fatalf("billFilter err=%v", err)
|
||||
}
|
||||
@ -163,3 +162,29 @@ func TestLegacyBillFilterUnresolvedRegionMatchesNothing(t *testing.T) {
|
||||
t.Fatalf("unresolved region should not be matchable")
|
||||
}
|
||||
}
|
||||
|
||||
func TestLegacyRegionLookupStages(t *testing.T) {
|
||||
if stages := legacyRegionLookupStages(nil); len(stages) != 0 {
|
||||
t.Fatalf("empty codes should not add stages: %v", stages)
|
||||
}
|
||||
stages := legacyRegionLookupStages([]string{"TR", "SA"})
|
||||
if len(stages) != 3 {
|
||||
t.Fatalf("expected lookup/addFields/match stages, got %d", len(stages))
|
||||
}
|
||||
lookup, ok := stages[0][0].Value.(bson.M)
|
||||
if !ok || lookup["from"] != legacyUserProfileCollection || lookup["localField"] != "acceptUserId" || lookup["foreignField"] != "_id" {
|
||||
t.Fatalf("unexpected lookup stage: %v", stages[0])
|
||||
}
|
||||
match, ok := stages[2][0].Value.(bson.M)
|
||||
if !ok {
|
||||
t.Fatalf("unexpected match stage: %v", stages[2])
|
||||
}
|
||||
condition, ok := match["legacyBillCountry"].(bson.M)
|
||||
if !ok {
|
||||
t.Fatalf("match stage missing country condition: %v", match)
|
||||
}
|
||||
codes, ok := condition["$in"].([]string)
|
||||
if !ok || len(codes) != 2 || codes[0] != "TR" || codes[1] != "SA" {
|
||||
t.Fatalf("unexpected region codes in match: %v", condition["$in"])
|
||||
}
|
||||
}
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user