From cd719fc3559d3fc068195f4e0021e1b81f862cb9 Mon Sep 17 00:00:00 2001 From: yml2213 Date: Sat, 5 Sep 2026 11:25:14 +0800 Subject: [PATCH] =?UTF-8?q?fix(query):=20=E6=94=AF=E6=8C=8112=E4=BD=8D?= =?UTF-8?q?=E7=BA=AF=E6=95=B0=E5=AD=97=E6=97=B6=E9=97=B4=E7=BC=96=E5=8F=B7?= =?UTF-8?q?(=E5=A6=82202609040184)=E5=95=86=E5=93=81=E7=BC=96=E5=8F=B7?= =?UTF-8?q?=E7=9A=84=E7=B2=BE=E5=87=86=E7=B4=A2=E5=BC=95=E8=B7=AF=E7=94=B1?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- backend/internal/modules/chat/support.go | 4 +- backend/internal/modules/dispute/query.go | 10 ++-- .../modules/dispute/repository_test.go | 6 +-- .../modules/listing/admin_filter_test.go | 54 ++++++++++++++++++- backend/internal/modules/listing/query.go | 17 +++++- backend/internal/modules/order/queries.go | 8 +-- .../internal/modules/order/repository_test.go | 6 +-- backend/internal/modules/pickup/repository.go | 9 ++-- .../modules/pickup/repository_test.go | 6 +-- 9 files changed, 91 insertions(+), 29 deletions(-) diff --git a/backend/internal/modules/chat/support.go b/backend/internal/modules/chat/support.go index 725e711..1f20422 100644 --- a/backend/internal/modules/chat/support.go +++ b/backend/internal/modules/chat/support.go @@ -311,9 +311,9 @@ func (r *Repository) adminConversationBase(ctx context.Context, principal Princi db = db.Joins("LEFT JOIN users AS renter ON renter.id = explicit_lo.renter_id"). Joins("LEFT JOIN users AS owner ON owner.id = COALESCE(explicit_lo.owner_id, l.owner_id)"). Where("(renter.phone = ? OR owner.phone = ?)", keyword, keyword) - } else if strings.HasPrefix(upper, "RO") || strings.HasPrefix(upper, "ORD") || (len(keyword) >= 12 && isAllDigits(keyword)) { + } else if strings.HasPrefix(upper, "RO") || strings.HasPrefix(upper, "ORD") { db = db.Where("(explicit_lo.order_no = ? OR c.latest_order_no = ?)", keyword, keyword) - } else if strings.HasPrefix(upper, "L") { + } else if strings.HasPrefix(upper, "L") || (len(keyword) >= 12 && isAllDigits(keyword)) { db = db.Where("l.listing_no = ?", keyword) } else { db = db.Joins("LEFT JOIN users AS renter ON renter.id = explicit_lo.renter_id"). diff --git a/backend/internal/modules/dispute/query.go b/backend/internal/modules/dispute/query.go index 9e1c368..55e237d 100644 --- a/backend/internal/modules/dispute/query.go +++ b/backend/internal/modules/dispute/query.go @@ -125,11 +125,11 @@ func (r *Repository) adminDisputeCountQuery(ctx context.Context, query AdminList return r.db.WithContext(ctx).Table("disputes AS d"). Joins("JOIN rental_orders AS o ON o.id = d.order_id") } - if strings.HasPrefix(upper, "RO") || strings.HasPrefix(upper, "ORD") || (len(kw) >= 12 && isAllDigits(kw)) { + if strings.HasPrefix(upper, "RO") || strings.HasPrefix(upper, "ORD") { return r.db.WithContext(ctx).Table("disputes AS d"). Joins("JOIN rental_orders AS o ON o.id = d.order_id") } - if strings.HasPrefix(upper, "L") { + if strings.HasPrefix(upper, "L") || (len(kw) >= 12 && isAllDigits(kw)) { return r.db.WithContext(ctx).Table("disputes AS d"). Joins("JOIN rental_orders AS o ON o.id = d.order_id"). Joins("JOIN rental_listings AS l ON l.id = o.listing_id") @@ -155,11 +155,11 @@ func applyAdminListFilters(db *gorm.DB, query AdminListQuery) *gorm.DB { if len(keyword) == 11 && keyword[0] == '1' && isAllDigits(keyword) { // 11 位手机号:命中 users(phone) 唯一索引 db = db.Where("(owner.phone = ? OR renter.phone = ?)", keyword, keyword) - } else if strings.HasPrefix(upper, "RO") || strings.HasPrefix(upper, "ORD") || (len(keyword) >= 12 && isAllDigits(keyword)) { + } else if strings.HasPrefix(upper, "RO") || strings.HasPrefix(upper, "ORD") { // 订单号:命中 rental_orders(order_no) 唯一索引 db = db.Where("o.order_no = ?", keyword) - } else if strings.HasPrefix(upper, "L") { - // 商品编号:命中 rental_listings(listing_no) 唯一索引 + } else if strings.HasPrefix(upper, "L") || (len(keyword) >= 12 && isAllDigits(keyword)) { + // 商品编号(如 L前缀或时间计算生成的 12位纯数字如 202609040184):命中 rental_listings(listing_no) 唯一索引 db = db.Where("l.listing_no = ?", keyword) } else if id, err := strconv.ParseUint(keyword, 10, 64); err == nil && id > 0 && keyword[0] != '0' && id < 100000000 { // 短纯数字 ID(非零开头):命中纠纷 ID、订单 ID 或关联 ID diff --git a/backend/internal/modules/dispute/repository_test.go b/backend/internal/modules/dispute/repository_test.go index 22c2f88..f8e7b36 100644 --- a/backend/internal/modules/dispute/repository_test.go +++ b/backend/internal/modules/dispute/repository_test.go @@ -736,9 +736,9 @@ func TestApplyAdminListFiltersSmartRouting(t *testing.T) { wantSQLFrag: "d.id = ? OR d.order_id = ? OR o.listing_id = ? OR o.owner_id = ? OR o.renter_id = ?", }, { - name: "长纯数字路由至订单号", - query: AdminListQuery{Keyword: "2026090510001234"}, - wantSQLFrag: "o.order_no = ?", + name: "12位时间计算的纯数字商品编号路由至rental_listings(listing_no)", + query: AdminListQuery{Keyword: "202609040184"}, + wantSQLFrag: "l.listing_no = ?", }, { name: "普通文字退回多列LIKE模糊匹配", diff --git a/backend/internal/modules/listing/admin_filter_test.go b/backend/internal/modules/listing/admin_filter_test.go index f9c6e19..fc3098d 100644 --- a/backend/internal/modules/listing/admin_filter_test.go +++ b/backend/internal/modules/listing/admin_filter_test.go @@ -1,6 +1,12 @@ package listing -import "testing" +import ( + "strings" + "testing" + + "gorm.io/driver/sqlite" + "gorm.io/gorm" +) func TestFilterAdminListingsByAssetLevels(t *testing.T) { items := []ListingDTO{ @@ -92,3 +98,49 @@ func TestFilterAdminListingsBySkinGroupOnly(t *testing.T) { t.Fatalf("expected only listing 2 by gold skin group, got %#v", filtered) } } + +func TestApplyAdminListFiltersSmartRouting(t *testing.T) { + db, err := gorm.Open(sqlite.Open(":memory:"), &gorm.Config{DryRun: true}) + if err != nil { + t.Fatalf("打开测试数据库失败: %v", err) + } + repo := &Repository{} + + tests := []struct { + name string + query AdminListQuery + wantSQLFrag string + }{ + { + name: "12位时间计算的纯数字商品编号路由至rental_listings(listing_no)", + query: AdminListQuery{Keyword: "202609040184"}, + wantSQLFrag: "l.listing_no = ?", + }, + { + name: "L前缀商品编号路由至rental_listings(listing_no)", + query: AdminListQuery{Keyword: "L20260905100"}, + wantSQLFrag: "l.listing_no = ?", + }, + { + name: "RO前缀订单号路由至EXISTS订单唯一索引", + query: AdminListQuery{Keyword: "RO202609051000"}, + wantSQLFrag: "o.order_no = ?", + }, + { + name: "短数字ID路由至主键及关联ID", + query: AdminListQuery{Keyword: "12345"}, + wantSQLFrag: "l.listing_no = ? OR EXISTS (SELECT 1 FROM rental_orders AS o WHERE o.listing_id = l.id AND o.order_no = ?) OR l.id = ?", + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + stmt := repo.applyAdminListFilters(db.Table("rental_listings AS l"), tt.query).Find(&[]struct{}{}).Statement + sql := stmt.SQL.String() + if !strings.Contains(sql, tt.wantSQLFrag) { + t.Fatalf("期望 SQL 包含 %q, 实际生成: %s", tt.wantSQLFrag, sql) + } + }) + } +} + diff --git a/backend/internal/modules/listing/query.go b/backend/internal/modules/listing/query.go index 45c32cb..d656227 100644 --- a/backend/internal/modules/listing/query.go +++ b/backend/internal/modules/listing/query.go @@ -104,8 +104,8 @@ func (r *Repository) applyAdminListFilters(db *gorm.DB, query AdminListQuery) *g if strings.HasPrefix(upper, "RO") || strings.HasPrefix(upper, "ORD") { // 订单号:命中 rental_orders(order_no) 唯一索引 db = db.Where("EXISTS (SELECT 1 FROM rental_orders AS o WHERE o.listing_id = l.id AND o.order_no = ?)", keyword) - } else if strings.HasPrefix(upper, "L") { - // 商品编号:命中 rental_listings(listing_no) 唯一索引 + } else if strings.HasPrefix(upper, "L") || (len(keyword) >= 12 && isAllDigits(keyword)) { + // 商品编号(如 L前缀或时间计算生成的 12位纯数字如 202609040184):命中 rental_listings(listing_no) 唯一索引 db = db.Where("l.listing_no = ?", keyword) } else if id, err := strconv.ParseUint(keyword, 10, 64); err == nil && id > 0 && keyword[0] != '0' && id < 100000000 { db = db.Where( @@ -277,3 +277,16 @@ func (r *Repository) nextListingNo(tx *gorm.DB, now time.Time) (string, error) { } return fmt.Sprintf("%s%04d", bizDate, seq), nil } + +func isAllDigits(s string) bool { + if s == "" { + return false + } + for i := 0; i < len(s); i++ { + if s[i] < '0' || s[i] > '9' { + return false + } + } + return true +} + diff --git a/backend/internal/modules/order/queries.go b/backend/internal/modules/order/queries.go index ca0a87e..b7577d6 100644 --- a/backend/internal/modules/order/queries.go +++ b/backend/internal/modules/order/queries.go @@ -97,7 +97,7 @@ func (r *Repository) adminOrderCountQuery(ctx context.Context, query AdminOrderQ upper := strings.ToUpper(kw) needsJoin := false if kw != "" { - if strings.HasPrefix(upper, "RO") || strings.HasPrefix(upper, "ORD") || (len(kw) >= 12 && isAllDigits(kw)) { + if strings.HasPrefix(upper, "RO") || strings.HasPrefix(upper, "ORD") { needsJoin = false } else if id, err := strconv.ParseUint(kw, 10, 64); err == nil && id > 0 && kw[0] != '0' && len(kw) < 9 { needsJoin = false @@ -170,11 +170,11 @@ func applyAdminOrderFilters(db *gorm.DB, query AdminOrderQuery) *gorm.DB { if len(keyword) == 11 && keyword[0] == '1' && isAllDigits(keyword) { // 11 位手机号:命中 users(phone) 唯一索引 db = db.Where("(owner.phone = ? OR renter.phone = ?)", keyword, keyword) - } else if strings.HasPrefix(upper, "RO") || strings.HasPrefix(upper, "ORD") || (len(keyword) >= 12 && isAllDigits(keyword)) { + } else if strings.HasPrefix(upper, "RO") || strings.HasPrefix(upper, "ORD") { // 订单号:命中 rental_orders(order_no) 唯一索引 db = db.Where("o.order_no = ?", keyword) - } else if strings.HasPrefix(upper, "L") { - // 商品编号:命中 rental_listings(listing_no) 唯一索引 + } else if strings.HasPrefix(upper, "L") || (len(keyword) >= 12 && isAllDigits(keyword)) { + // 商品编号(如 L前缀或时间计算生成的 12位纯数字如 202609040184):命中 rental_listings(listing_no) 唯一索引 db = db.Where("l.listing_no = ?", keyword) } else if id, err := strconv.ParseUint(keyword, 10, 64); err == nil && id > 0 && keyword[0] != '0' && id < 100000000 { // 短纯数字 ID(非零开头):命中 rental_orders(id) 主键及关联 ID diff --git a/backend/internal/modules/order/repository_test.go b/backend/internal/modules/order/repository_test.go index 56b7151..0a6be77 100644 --- a/backend/internal/modules/order/repository_test.go +++ b/backend/internal/modules/order/repository_test.go @@ -496,9 +496,9 @@ func TestApplyAdminOrderFiltersSmartRouting(t *testing.T) { wantSQLFrag: "o.order_no = ?", }, { - name: "长纯数字订单号路由至rental_orders(order_no)", - query: AdminOrderQuery{Keyword: "2026090510001234"}, - wantSQLFrag: "o.order_no = ?", + name: "12位时间计算的纯数字商品编号路由至rental_listings(listing_no)", + query: AdminOrderQuery{Keyword: "202609040184"}, + wantSQLFrag: "l.listing_no = ?", }, { name: "L前缀商品编号路由至rental_listings(listing_no)", diff --git a/backend/internal/modules/pickup/repository.go b/backend/internal/modules/pickup/repository.go index f518898..4bc1f71 100644 --- a/backend/internal/modules/pickup/repository.go +++ b/backend/internal/modules/pickup/repository.go @@ -743,15 +743,12 @@ func applyAdminPickupFilters(db *gorm.DB, query AdminPickupQuery) *gorm.DB { } else if strings.HasPrefix(upper, "PK") || strings.HasPrefix(upper, "P") { // 提号编号:命中 admin_pickups(pickup_no) 唯一索引 db = db.Where("p.pickup_no = ?", kw) - } else if strings.HasPrefix(upper, "L") { - // 商品编号:命中 rental_listings(listing_no) 唯一索引 + } else if strings.HasPrefix(upper, "L") || (len(kw) >= 12 && isAllDigits(kw)) { + // 商品编号(如 L前缀或时间计算生成的 12位纯数字如 202609040184):命中 rental_listings(listing_no) 唯一索引 db = db.Where("l.listing_no = ?", kw) } else if id, err := strconv.ParseUint(kw, 10, 64); err == nil && id > 0 && kw[0] != '0' && id < 100000000 { // 短纯数字 ID(非零开头):命中 admin_pickups 主键及关联 ID db = db.Where("(p.id = ? OR p.listing_id = ? OR p.account_id = ? OR p.owner_id = ?)", id, id, id, id) - } else if len(kw) >= 12 && isAllDigits(kw) { - // 12位以上纯数字可能为商品编号或提号编号 - db = db.Where("(p.pickup_no = ? OR l.listing_no = ?)", kw, kw) } else { like := "%" + kw + "%" db = db.Where( @@ -782,7 +779,7 @@ func (r *Repository) adminPickupCountQuery(ctx context.Context, query AdminPicku return r.db.WithContext(ctx).Table("admin_pickups AS p"). Joins("JOIN users AS owner ON owner.id = p.owner_id") } - if strings.HasPrefix(upper, "L") { + if strings.HasPrefix(upper, "L") || (len(kw) >= 12 && isAllDigits(kw)) { return r.db.WithContext(ctx).Table("admin_pickups AS p"). Joins("JOIN rental_listings AS l ON l.id = p.listing_id") } diff --git a/backend/internal/modules/pickup/repository_test.go b/backend/internal/modules/pickup/repository_test.go index e2f6269..429ffbf 100644 --- a/backend/internal/modules/pickup/repository_test.go +++ b/backend/internal/modules/pickup/repository_test.go @@ -736,9 +736,9 @@ func TestApplyAdminPickupFiltersSmartRouting(t *testing.T) { wantSQLFrag: "p.id = ? OR p.listing_id = ? OR p.account_id = ? OR p.owner_id = ?", }, { - name: "长纯数字路由至提号编号或商品编号", - query: AdminPickupQuery{Keyword: "2026090510001234"}, - wantSQLFrag: "p.pickup_no = ? OR l.listing_no = ?", + name: "12位时间计算的纯数字商品编号路由至rental_listings(listing_no)", + query: AdminPickupQuery{Keyword: "202609040184"}, + wantSQLFrag: "l.listing_no = ?", }, { name: "普通文字退回多列LIKE模糊匹配",