perf(admin): 下推商品资产筛选并统一工作台缓存刷新
This commit is contained in:
@@ -0,0 +1,73 @@
|
||||
package listing
|
||||
|
||||
import (
|
||||
"strings"
|
||||
|
||||
"gorm.io/gorm"
|
||||
)
|
||||
|
||||
// applyAdminAssetSQLFilters 把资产与皮肤条件下推到 MySQL JSON 字段,避免先加载全表再在内存过滤。
|
||||
func applyAdminAssetSQLFilters(db *gorm.DB, query AdminListQuery) *gorm.DB {
|
||||
if len(query.Insurance) > 0 {
|
||||
db = db.Where(
|
||||
"JSON_UNQUOTE(JSON_EXTRACT(a.asset_summary, '$.season_insurance')) IN ?",
|
||||
query.Insurance,
|
||||
)
|
||||
}
|
||||
if len(query.Stamina) > 0 {
|
||||
db = db.Where(
|
||||
"JSON_UNQUOTE(JSON_EXTRACT(a.asset_summary, '$.stamina_level')) IN ?",
|
||||
query.Stamina,
|
||||
)
|
||||
}
|
||||
if len(query.Load) > 0 {
|
||||
db = db.Where(
|
||||
"JSON_UNQUOTE(JSON_EXTRACT(a.asset_summary, '$.load_level')) IN ?",
|
||||
query.Load,
|
||||
)
|
||||
}
|
||||
|
||||
// 皮肤名称按 AND 语义过滤;同时选择多款皮肤时必须全部命中。
|
||||
if len(query.SkinName) > 0 {
|
||||
for _, skin := range query.SkinName {
|
||||
skin = strings.TrimSpace(skin)
|
||||
if skin == "" {
|
||||
continue
|
||||
}
|
||||
db = db.Where(
|
||||
"JSON_SEARCH(a.asset_summary, 'one', ?, '!', '$.skin_groups.*') IS NOT NULL",
|
||||
escapeJSONSearchPattern(skin),
|
||||
)
|
||||
}
|
||||
return db
|
||||
}
|
||||
|
||||
if len(query.SkinGroup) > 0 {
|
||||
clauses := make([]string, 0, len(query.SkinGroup))
|
||||
args := make([]any, 0, len(query.SkinGroup))
|
||||
for _, group := range query.SkinGroup {
|
||||
group = strings.TrimSpace(group)
|
||||
if group == "" {
|
||||
continue
|
||||
}
|
||||
clauses = append(clauses, "JSON_LENGTH(JSON_EXTRACT(a.asset_summary, ?)) > 0")
|
||||
args = append(args, jsonMemberPath("$.skin_groups", group))
|
||||
}
|
||||
if len(clauses) == 0 {
|
||||
return db.Where("1 = 0")
|
||||
}
|
||||
db = db.Where("("+strings.Join(clauses, " OR ")+")", args...)
|
||||
}
|
||||
return db
|
||||
}
|
||||
|
||||
func jsonMemberPath(prefix string, key string) string {
|
||||
escaped := strings.ReplaceAll(key, `\`, `\\`)
|
||||
escaped = strings.ReplaceAll(escaped, `"`, `\"`)
|
||||
return prefix + `."` + escaped + `"`
|
||||
}
|
||||
|
||||
func escapeJSONSearchPattern(value string) string {
|
||||
replacer := strings.NewReplacer("!", "!!", "%", "!%", "_", "!_")
|
||||
return replacer.Replace(value)
|
||||
}
|
||||
@@ -129,6 +129,65 @@ func TestFilterAdminListingsByMultipleSkinsRequiresAll(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func TestApplyAdminListFiltersPushesAssetFiltersToSQL(t *testing.T) {
|
||||
db := database.NewDryRunDB()
|
||||
repo := &Repository{}
|
||||
|
||||
query := AdminListQuery{
|
||||
Insurance: []string{"3*3"},
|
||||
Stamina: []string{"7级"},
|
||||
Load: []string{"7级"},
|
||||
}
|
||||
sql := repo.applyAdminListFilters(db.Table("rental_listings AS l"), query).
|
||||
Find(&[]struct{}{}).Statement.SQL.String()
|
||||
|
||||
for _, fragment := range []string{
|
||||
"JSON_UNQUOTE(JSON_EXTRACT(a.asset_summary, '$.season_insurance')) IN (?)",
|
||||
"JSON_UNQUOTE(JSON_EXTRACT(a.asset_summary, '$.stamina_level')) IN (?)",
|
||||
"JSON_UNQUOTE(JSON_EXTRACT(a.asset_summary, '$.load_level')) IN (?)",
|
||||
} {
|
||||
if !strings.Contains(sql, fragment) {
|
||||
t.Fatalf("期望资产筛选下推 SQL 包含 %q, 实际生成: %s", fragment, sql)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestApplyAdminListFiltersPushesSkinNameAndGroupFiltersToSQL(t *testing.T) {
|
||||
db := database.NewDryRunDB()
|
||||
repo := &Repository{}
|
||||
|
||||
skinNameSQL := repo.applyAdminListFilters(
|
||||
db.Table("rental_listings AS l"),
|
||||
AdminListQuery{SkinName: []string{"凌霄成卫", "M7棱镜攻势S2"}},
|
||||
).Find(&[]struct{}{}).Statement.SQL.String()
|
||||
if strings.Count(skinNameSQL, "JSON_SEARCH(a.asset_summary") != 2 {
|
||||
t.Fatalf("多皮肤筛选应生成两个 AND 条件, 实际: %s", skinNameSQL)
|
||||
}
|
||||
if !strings.Contains(skinNameSQL, "'$.skin_groups.*'") {
|
||||
t.Fatalf("皮肤名称筛选应遍历全部分组, 实际: %s", skinNameSQL)
|
||||
}
|
||||
|
||||
groupSQL := repo.applyAdminListFilters(
|
||||
db.Table("rental_listings AS l"),
|
||||
AdminListQuery{SkinGroup: []string{"operatorRed", "weapon"}},
|
||||
).Find(&[]struct{}{}).Statement.SQL.String()
|
||||
if !strings.Contains(groupSQL, "JSON_LENGTH(JSON_EXTRACT(a.asset_summary, ?)) > 0") {
|
||||
t.Fatalf("皮肤分组筛选应下推 JSON_LENGTH, 实际: %s", groupSQL)
|
||||
}
|
||||
if !strings.Contains(groupSQL, " OR ") {
|
||||
t.Fatalf("多皮肤分组应使用 OR 语义, 实际: %s", groupSQL)
|
||||
}
|
||||
}
|
||||
|
||||
func TestAdminAssetFilterHelpers(t *testing.T) {
|
||||
if got := jsonMemberPath("$.skin_groups", `operator"Red`); got != `$.skin_groups."operator\"Red"` {
|
||||
t.Fatalf("jsonMemberPath = %q", got)
|
||||
}
|
||||
if got := escapeJSONSearchPattern(`50%_暗星!`); got != `50!%!_暗星!!` {
|
||||
t.Fatalf("escapeJSONSearchPattern = %q", got)
|
||||
}
|
||||
}
|
||||
|
||||
func TestApplyAdminListFiltersSmartRouting(t *testing.T) {
|
||||
db := database.NewDryRunDB()
|
||||
repo := &Repository{}
|
||||
|
||||
@@ -35,37 +35,6 @@ func (r *Repository) ListAdmin(ctx context.Context, query AdminListQuery) (*Admi
|
||||
pageSize = 100
|
||||
}
|
||||
|
||||
if hasAdminAssetFilters(query) {
|
||||
var rows []listingRow
|
||||
err := r.applyAdminListFilters(r.baseQuery(ctx), query).
|
||||
Order("l.id DESC").
|
||||
Scan(&rows).Error
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
items := filterAdminListings(rowsToDTO(rows), query)
|
||||
total := int64(len(items))
|
||||
start := (page - 1) * pageSize
|
||||
if start < 0 {
|
||||
start = 0
|
||||
}
|
||||
if start >= len(items) {
|
||||
items = []ListingDTO{}
|
||||
} else {
|
||||
end := start + pageSize
|
||||
if end > len(items) {
|
||||
end = len(items)
|
||||
}
|
||||
items = items[start:end]
|
||||
}
|
||||
return &AdminListResult{
|
||||
Items: items,
|
||||
Total: total,
|
||||
Page: page,
|
||||
PageSize: pageSize,
|
||||
}, nil
|
||||
}
|
||||
|
||||
db := r.applyAdminListFilters(r.baseQuery(ctx), query)
|
||||
offset := (page - 1) * pageSize
|
||||
if offset < 0 {
|
||||
@@ -82,7 +51,12 @@ func (r *Repository) ListAdmin(ctx context.Context, query AdminListQuery) (*Admi
|
||||
if page == 1 && len(rows) < pageSize {
|
||||
total = int64(len(rows))
|
||||
} else {
|
||||
countDB := r.applyAdminListFilters(r.db.WithContext(ctx).Table("rental_listings AS l"), query)
|
||||
countDB := r.applyAdminListFilters(
|
||||
r.db.WithContext(ctx).
|
||||
Table("rental_listings AS l").
|
||||
Joins("JOIN game_accounts AS a ON a.id = l.account_id"),
|
||||
query,
|
||||
)
|
||||
if err := countDB.Count(&total).Error; err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@@ -149,6 +123,7 @@ func (r *Repository) applyAdminListFilters(db *gorm.DB, query AdminListQuery) *g
|
||||
if query.MaxDeposit != nil {
|
||||
db = db.Where("l.deposit_amount_cent <= ?", yuanToAdminCent(*query.MaxDeposit))
|
||||
}
|
||||
db = applyAdminAssetSQLFilters(db, query)
|
||||
return db
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user