feat(listing): 公共筛选皮肤与IP地区多选改为AND全匹配,新增跨分组测试
This commit is contained in:
+1
-1
@@ -39,4 +39,4 @@ deploy/caddy/show-dist/**
|
||||
deploy/caddy/conf.d/show.caddy
|
||||
|
||||
logs
|
||||
|
||||
.gocache
|
||||
|
||||
@@ -28,7 +28,7 @@ func matchesPublicQuery(item ListingDTO, query PublicListQuery) bool {
|
||||
if !matchesAny(query.Server, strings.TrimSpace(item.ServerRegion)) {
|
||||
return false
|
||||
}
|
||||
if len(query.Region) > 0 && !intersects(query.Region, assetRegionsFromSummary(item.AssetSummary)) {
|
||||
if len(query.Region) > 0 && !containsAll(query.Region, assetRegionsFromSummary(item.AssetSummary)) {
|
||||
return false
|
||||
}
|
||||
if !matchesAny(query.LoginMethod, strings.TrimSpace(item.LoginPlatform)) {
|
||||
@@ -47,11 +47,9 @@ func matchesPublicQuery(item ListingDTO, query PublicListQuery) bool {
|
||||
return false
|
||||
}
|
||||
if len(query.SkinName) > 0 {
|
||||
if len(query.SkinGroup) > 0 {
|
||||
if !skinGroupsContainAny(item.AssetSummary, query.SkinGroup, query.SkinName) {
|
||||
return false
|
||||
}
|
||||
} else if !intersects(query.SkinName, skinNamesFromSummary(item.AssetSummary)) {
|
||||
// 皮肤与 IP 地区均为多值字段:多选时要求账号包含所有选中项(AND),
|
||||
// 而不是包含任一即命中(OR)。
|
||||
if !containsAll(query.SkinName, skinNamesFromSummary(item.AssetSummary)) {
|
||||
return false
|
||||
}
|
||||
} else if len(query.SkinGroup) > 0 && !skinGroupsHaveAny(item.AssetSummary, query.SkinGroup) {
|
||||
@@ -213,6 +211,25 @@ func matchesAny(options []string, value string) bool {
|
||||
return false
|
||||
}
|
||||
|
||||
// containsAll 要求所有选中项都出现在 values 中(AND 匹配),
|
||||
// 用于多值字段(IP 地区、皮肤)的多选筛选。
|
||||
func containsAll(options []string, values []string) bool {
|
||||
if len(options) == 0 {
|
||||
return true
|
||||
}
|
||||
valueSet := make(map[string]struct{}, len(values))
|
||||
for _, value := range values {
|
||||
valueSet[value] = struct{}{}
|
||||
}
|
||||
for _, option := range options {
|
||||
if _, ok := valueSet[option]; !ok {
|
||||
return false
|
||||
}
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
// intersects 用于管理端等场景:账号包含任一选中项即命中(OR)。
|
||||
func intersects(options []string, values []string) bool {
|
||||
if len(options) == 0 {
|
||||
return true
|
||||
@@ -294,6 +311,7 @@ func skinNamesFromSummary(summary map[string]any) []string {
|
||||
return result
|
||||
}
|
||||
|
||||
// skinGroupsContainAny 用于管理端等场景:任一选中分组包含任一选中皮肤即命中(OR)。
|
||||
func skinGroupsContainAny(summary map[string]any, groupKeys []string, skinNames []string) bool {
|
||||
groups := skinGroupsFromSummary(summary)
|
||||
for _, groupKey := range groupKeys {
|
||||
|
||||
@@ -21,6 +21,139 @@ func TestFilterPublicListingsByRatio(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func TestFilterPublicListingsRegionMultiSelectRequiresAll(t *testing.T) {
|
||||
items := []ListingDTO{
|
||||
{
|
||||
ID: 1,
|
||||
AssetSummary: map[string]any{
|
||||
"common_regions": []any{"北京", "上海"},
|
||||
},
|
||||
},
|
||||
{
|
||||
ID: 2,
|
||||
AssetSummary: map[string]any{
|
||||
"common_regions": []any{"北京", "广东"},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
// 选中「北京+上海」:只有同时包含两个地区的账号才进入待选(AND)
|
||||
filtered := filterPublicListings(items, PublicListQuery{
|
||||
Region: []string{"北京", "上海"},
|
||||
})
|
||||
if len(filtered) != 1 || filtered[0].ID != 1 {
|
||||
t.Fatalf("expected only listing 1 matching all regions, got %#v", filtered)
|
||||
}
|
||||
|
||||
// 单选不受影响
|
||||
filtered = filterPublicListings(items, PublicListQuery{
|
||||
Region: []string{"北京"},
|
||||
})
|
||||
if len(filtered) != 2 {
|
||||
t.Fatalf("expected 2 listings matching single region, got %#v", filtered)
|
||||
}
|
||||
}
|
||||
|
||||
func TestFilterPublicListingsSkinMultiSelectRequiresAll(t *testing.T) {
|
||||
items := []ListingDTO{
|
||||
{
|
||||
ID: 1,
|
||||
AssetSummary: map[string]any{
|
||||
"skin_groups": map[string]any{
|
||||
"operatorRed": []any{"凌霄成卫", "蚀金玫瑰"},
|
||||
"melee": []any{"坠星者"},
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
ID: 2,
|
||||
AssetSummary: map[string]any{
|
||||
"skin_groups": map[string]any{
|
||||
"operatorRed": []any{"凌霄成卫"},
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
// 选中「凌霄成卫+蚀金玫瑰」:账号必须同时拥有两个皮肤才进入待选(AND)
|
||||
filtered := filterPublicListings(items, PublicListQuery{
|
||||
SkinName: []string{"凌霄成卫", "蚀金玫瑰"},
|
||||
})
|
||||
if len(filtered) != 1 || filtered[0].ID != 1 {
|
||||
t.Fatalf("expected only listing 1 matching all skins, got %#v", filtered)
|
||||
}
|
||||
|
||||
// 单选不受影响
|
||||
filtered = filterPublicListings(items, PublicListQuery{
|
||||
SkinName: []string{"凌霄成卫"},
|
||||
})
|
||||
if len(filtered) != 2 {
|
||||
t.Fatalf("expected 2 listings matching single skin, got %#v", filtered)
|
||||
}
|
||||
|
||||
// 移动端带 skin_group 时同样要求全部皮肤命中
|
||||
filtered = filterPublicListings(items, PublicListQuery{
|
||||
SkinGroup: []string{"operatorRed"},
|
||||
SkinName: []string{"凌霄成卫", "坠星者"},
|
||||
})
|
||||
if len(filtered) != 1 || filtered[0].ID != 1 {
|
||||
t.Fatalf("expected only listing 1 matching all skins with group, got %#v", filtered)
|
||||
}
|
||||
}
|
||||
|
||||
func TestFilterPublicListingsSkinGroupsAllAnd(t *testing.T) {
|
||||
// 刀皮/红皮/枪皮(以及金皮)跨组多选:账号必须同时拥有所有选中的皮肤(AND)
|
||||
items := []ListingDTO{
|
||||
{
|
||||
ID: 1,
|
||||
AssetSummary: map[string]any{
|
||||
"skin_groups": map[string]any{
|
||||
"melee": []any{"坠星者", "暗星"},
|
||||
"operatorRed": []any{"凌霄成卫"},
|
||||
"operatorGold": []any{"露娜-古墓丽影联动"},
|
||||
"weapon": []any{"M7棱镜攻势S2"},
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
// 缺枪皮:只命中刀皮+红皮,不应进入待选
|
||||
ID: 2,
|
||||
AssetSummary: map[string]any{
|
||||
"skin_groups": map[string]any{
|
||||
"melee": []any{"坠星者"},
|
||||
"operatorRed": []any{"凌霄成卫"},
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
// 缺红皮
|
||||
ID: 3,
|
||||
AssetSummary: map[string]any{
|
||||
"skin_groups": map[string]any{
|
||||
"melee": []any{"坠星者"},
|
||||
"weapon": []any{"M7棱镜攻势S2"},
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
query := PublicListQuery{
|
||||
SkinName: []string{"坠星者", "凌霄成卫", "M7棱镜攻势S2"},
|
||||
}
|
||||
filtered := filterPublicListings(items, query)
|
||||
if len(filtered) != 1 || filtered[0].ID != 1 {
|
||||
t.Fatalf("expected only listing 1 with all knife/red/weapon skins, got %#v", filtered)
|
||||
}
|
||||
|
||||
// 同组内多选(刀皮选两个)同样要求全部拥有
|
||||
filtered = filterPublicListings(items, PublicListQuery{
|
||||
SkinName: []string{"坠星者", "暗星"},
|
||||
})
|
||||
if len(filtered) != 1 || filtered[0].ID != 1 {
|
||||
t.Fatalf("expected only listing 1 with both knife skins, got %#v", filtered)
|
||||
}
|
||||
}
|
||||
|
||||
func TestSortPublicListingsDefaultUsesShuffleKey(t *testing.T) {
|
||||
items := []ListingDTO{
|
||||
{ID: 101},
|
||||
|
||||
@@ -36,7 +36,7 @@ export function useListingQuery(
|
||||
insurance: joinCSV(filters.insurance),
|
||||
stamina: joinCSV(filters.stamina),
|
||||
load: joinCSV(filters.load),
|
||||
// 多选皮肤名;后端 skin_name CSV 为 OR 匹配
|
||||
// 多选皮肤名;后端 skin_name CSV 为 AND 匹配(账号需包含所有选中皮肤)
|
||||
skin_name: joinCSV(filters.skinNames),
|
||||
min_coin: filters.minCoin,
|
||||
max_coin: filters.maxCoin,
|
||||
|
||||
Reference in New Issue
Block a user