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
|
deploy/caddy/conf.d/show.caddy
|
||||||
|
|
||||||
logs
|
logs
|
||||||
|
.gocache
|
||||||
|
|||||||
@@ -28,7 +28,7 @@ func matchesPublicQuery(item ListingDTO, query PublicListQuery) bool {
|
|||||||
if !matchesAny(query.Server, strings.TrimSpace(item.ServerRegion)) {
|
if !matchesAny(query.Server, strings.TrimSpace(item.ServerRegion)) {
|
||||||
return false
|
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
|
return false
|
||||||
}
|
}
|
||||||
if !matchesAny(query.LoginMethod, strings.TrimSpace(item.LoginPlatform)) {
|
if !matchesAny(query.LoginMethod, strings.TrimSpace(item.LoginPlatform)) {
|
||||||
@@ -47,11 +47,9 @@ func matchesPublicQuery(item ListingDTO, query PublicListQuery) bool {
|
|||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
if len(query.SkinName) > 0 {
|
if len(query.SkinName) > 0 {
|
||||||
if len(query.SkinGroup) > 0 {
|
// 皮肤与 IP 地区均为多值字段:多选时要求账号包含所有选中项(AND),
|
||||||
if !skinGroupsContainAny(item.AssetSummary, query.SkinGroup, query.SkinName) {
|
// 而不是包含任一即命中(OR)。
|
||||||
return false
|
if !containsAll(query.SkinName, skinNamesFromSummary(item.AssetSummary)) {
|
||||||
}
|
|
||||||
} else if !intersects(query.SkinName, skinNamesFromSummary(item.AssetSummary)) {
|
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
} else if len(query.SkinGroup) > 0 && !skinGroupsHaveAny(item.AssetSummary, query.SkinGroup) {
|
} else if len(query.SkinGroup) > 0 && !skinGroupsHaveAny(item.AssetSummary, query.SkinGroup) {
|
||||||
@@ -213,6 +211,25 @@ func matchesAny(options []string, value string) bool {
|
|||||||
return false
|
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 {
|
func intersects(options []string, values []string) bool {
|
||||||
if len(options) == 0 {
|
if len(options) == 0 {
|
||||||
return true
|
return true
|
||||||
@@ -294,6 +311,7 @@ func skinNamesFromSummary(summary map[string]any) []string {
|
|||||||
return result
|
return result
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// skinGroupsContainAny 用于管理端等场景:任一选中分组包含任一选中皮肤即命中(OR)。
|
||||||
func skinGroupsContainAny(summary map[string]any, groupKeys []string, skinNames []string) bool {
|
func skinGroupsContainAny(summary map[string]any, groupKeys []string, skinNames []string) bool {
|
||||||
groups := skinGroupsFromSummary(summary)
|
groups := skinGroupsFromSummary(summary)
|
||||||
for _, groupKey := range groupKeys {
|
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) {
|
func TestSortPublicListingsDefaultUsesShuffleKey(t *testing.T) {
|
||||||
items := []ListingDTO{
|
items := []ListingDTO{
|
||||||
{ID: 101},
|
{ID: 101},
|
||||||
|
|||||||
@@ -36,7 +36,7 @@ export function useListingQuery(
|
|||||||
insurance: joinCSV(filters.insurance),
|
insurance: joinCSV(filters.insurance),
|
||||||
stamina: joinCSV(filters.stamina),
|
stamina: joinCSV(filters.stamina),
|
||||||
load: joinCSV(filters.load),
|
load: joinCSV(filters.load),
|
||||||
// 多选皮肤名;后端 skin_name CSV 为 OR 匹配
|
// 多选皮肤名;后端 skin_name CSV 为 AND 匹配(账号需包含所有选中皮肤)
|
||||||
skin_name: joinCSV(filters.skinNames),
|
skin_name: joinCSV(filters.skinNames),
|
||||||
min_coin: filters.minCoin,
|
min_coin: filters.minCoin,
|
||||||
max_coin: filters.maxCoin,
|
max_coin: filters.maxCoin,
|
||||||
|
|||||||
Reference in New Issue
Block a user