合并 main 分支并融合订单列表最小化与 SQL 治理
- Auth/权限中间件取 main 演进版(错误细分、token 来源),保留 dev 的用户 cookie 与登出撤销语义 - 后台订单列表:融合 main 的首页免 COUNT 优化与关键词索引路由,保留 dev 的最小化 AdminOrderListItemDTO(结账明细仍由详情接口返回,同步调整对应集成测试) - 保留 dev 的 token_version 冻结/解冻递增与 main 的状态变化判断及通知 - 前端:保留 dev 登出接口与 main 的 fetchMe silent 参数、SSE 登录跳转;后台订单表按 dev 最小化 DTO 移除号主到手列
This commit is contained in:
@@ -120,12 +120,6 @@ func isSellerHandoffStatus(status string) bool {
|
||||
}
|
||||
|
||||
func (r *Repository) ListAdmin(ctx context.Context, query AdminOrderQuery) (*AdminOrderListPaginatedResult, error) {
|
||||
var total int64
|
||||
countDB := applyAdminOrderFilters(r.adminOrderJoinQuery(ctx), query)
|
||||
if err := countDB.Count(&total).Error; err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
page := query.Page
|
||||
pageSize := query.PageSize
|
||||
if page < 1 {
|
||||
@@ -135,6 +129,7 @@ func (r *Repository) ListAdmin(ctx context.Context, query AdminOrderQuery) (*Adm
|
||||
pageSize = 20
|
||||
}
|
||||
offset := (page - 1) * pageSize
|
||||
|
||||
var rows []orderRow
|
||||
err := applyAdminOrderFilters(r.adminQuery(ctx), query).
|
||||
Order("o.id DESC").
|
||||
@@ -145,6 +140,17 @@ func (r *Repository) ListAdmin(ctx context.Context, query AdminOrderQuery) (*Adm
|
||||
return nil, err
|
||||
}
|
||||
|
||||
var total int64
|
||||
// 首页查询且返回条数小于 pageSize 时,总数等于当前返回条数,免除第二次 COUNT(*) 扫描
|
||||
if page == 1 && len(rows) < pageSize {
|
||||
total = int64(len(rows))
|
||||
} else {
|
||||
countDB := applyAdminOrderFilters(r.adminOrderCountQuery(ctx, query), query)
|
||||
if err := countDB.Count(&total).Error; err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
|
||||
items := make([]AdminOrderListItemDTO, 0, len(rows))
|
||||
for _, row := range rows {
|
||||
items = append(items, row.toAdminListItem())
|
||||
@@ -171,6 +177,60 @@ func normalizePagination(page, pageSize int) (int, int) {
|
||||
return page, pageSize
|
||||
}
|
||||
|
||||
func (r *Repository) adminOrderCountQuery(ctx context.Context, query AdminOrderQuery) *gorm.DB {
|
||||
kw := strings.TrimSpace(query.Keyword)
|
||||
upper := strings.ToUpper(kw)
|
||||
needsJoin := false
|
||||
if 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
|
||||
} else {
|
||||
needsJoin = true
|
||||
}
|
||||
}
|
||||
if !needsJoin {
|
||||
return r.db.WithContext(ctx).Table("rental_orders AS o")
|
||||
}
|
||||
return r.adminOrderJoinQuery(ctx)
|
||||
}
|
||||
|
||||
// latestAdminCheckouts loads one latest checkout per order so the admin order
|
||||
// list can display finalized income without issuing one query per row.
|
||||
func (r *Repository) latestAdminCheckouts(ctx context.Context, rows []orderRow) (map[uint64]model.OrderCheckout, error) {
|
||||
result := make(map[uint64]model.OrderCheckout, len(rows))
|
||||
if len(rows) == 0 {
|
||||
return result, nil
|
||||
}
|
||||
orderIDs := make([]uint64, 0, len(rows))
|
||||
for _, row := range rows {
|
||||
orderIDs = append(orderIDs, row.ID)
|
||||
}
|
||||
latest := r.db.WithContext(ctx).
|
||||
Table("order_checkouts").
|
||||
Select("MAX(id) AS id").
|
||||
Where("order_id IN ?", orderIDs).
|
||||
Group("order_id")
|
||||
var checkouts []model.OrderCheckout
|
||||
if err := r.db.WithContext(ctx).
|
||||
Table("order_checkouts AS oc").
|
||||
Joins("JOIN (?) AS latest ON latest.id = oc.id", latest).
|
||||
Find(&checkouts).Error; err != nil {
|
||||
return nil, err
|
||||
}
|
||||
for _, checkout := range checkouts {
|
||||
result[checkout.OrderID] = checkout
|
||||
}
|
||||
return result, nil
|
||||
}
|
||||
|
||||
func shouldAttachAdminCheckout(row orderRow) bool {
|
||||
return shouldAttachCheckout(row.Status) ||
|
||||
row.SettlementStatus == settlementStatusSettled ||
|
||||
row.SettlementStatus == settlementStatusArbitrated
|
||||
}
|
||||
|
||||
func applyAdminOrderFilters(db *gorm.DB, query AdminOrderQuery) *gorm.DB {
|
||||
if query.Status != "" {
|
||||
db = db.Where("o.status = ?", query.Status)
|
||||
@@ -191,21 +251,21 @@ func applyAdminOrderFilters(db *gorm.DB, query AdminOrderQuery) *gorm.DB {
|
||||
db = db.Where("COALESCE(NULLIF(o.offline_settlement_status, ''), 'none') = ?", query.OfflineSettlementStatus)
|
||||
}
|
||||
if keyword := strings.TrimSpace(query.Keyword); keyword != "" {
|
||||
like := "%" + keyword + "%"
|
||||
if id, err := strconv.ParseUint(keyword, 10, 64); err == nil {
|
||||
db = db.Where(
|
||||
`(o.order_no LIKE ? OR l.listing_no LIKE ? OR a.title LIKE ? OR owner.phone LIKE ? OR renter.phone LIKE ? OR o.id = ? OR o.listing_id = ? OR o.owner_id = ? OR o.renter_id = ?)`,
|
||||
like,
|
||||
like,
|
||||
like,
|
||||
like,
|
||||
like,
|
||||
id,
|
||||
id,
|
||||
id,
|
||||
id,
|
||||
)
|
||||
upper := strings.ToUpper(keyword)
|
||||
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") {
|
||||
// 订单号:命中 rental_orders(order_no) 唯一索引
|
||||
db = db.Where("o.order_no = ?", keyword)
|
||||
} 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
|
||||
db = db.Where("(o.id = ? OR o.listing_id = ? OR o.owner_id = ? OR o.renter_id = ?)", id, id, id, id)
|
||||
} else {
|
||||
like := "%" + keyword + "%"
|
||||
db = db.Where(
|
||||
`(o.order_no LIKE ? OR l.listing_no LIKE ? OR a.title LIKE ? OR owner.phone LIKE ? OR renter.phone LIKE ?)`,
|
||||
like,
|
||||
@@ -377,3 +437,12 @@ type orderRow struct {
|
||||
OwnerPhone string
|
||||
RenterPhone string
|
||||
}
|
||||
|
||||
func isAllDigits(s string) bool {
|
||||
for i := 0; i < len(s); i++ {
|
||||
if s[i] < '0' || s[i] > '9' {
|
||||
return false
|
||||
}
|
||||
}
|
||||
return len(s) > 0
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user