package listing import ( "context" "strings" "time" "gorm.io/datatypes" "gorm.io/gorm" ) func (r *Repository) ListPublic(ctx context.Context, query PublicListQuery) (*PublicListResult, error) { page, pageSize := normalizedPublicPage(query) if canListPublicWithSQL(query) { return r.listPublicPage(ctx, query, page, pageSize) } var rows []listingRow err := r.baseQuery(ctx). Where("l.status = ? AND l.review_status = ? AND l.in_transaction = ?", "published", "approved", false). Order("l.published_at DESC, l.id DESC"). Scan(&rows).Error if err != nil { return nil, err } items := publicListings(rowsToDTO(rows)) baseQuery := query baseQuery.Zone = "" items = filterPublicListings(items, baseQuery) zoneCounts := publicZoneCounts(items) if query.Zone != "" && query.Zone != "all" { items = filterPublicListings(items, query) } sortPublicListings(items, query.Sort) 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 &PublicListResult{ Items: items, Total: total, Page: page, PageSize: pageSize, ZoneCounts: zoneCounts, }, nil } func (r *Repository) listPublicPage(ctx context.Context, query PublicListQuery, page int, pageSize int) (*PublicListResult, error) { var total int64 if err := r.db.WithContext(ctx).Table("rental_listings AS l"). Where("l.status = ? AND l.review_status = ? AND l.in_transaction = ?", "published", "approved", false). Count(&total).Error; err != nil { return nil, err } var rows []listingRow offset := (page - 1) * pageSize err := applyPublicSQLSort(r.baseQuery(ctx), query.Sort). Where("l.status = ? AND l.review_status = ? AND l.in_transaction = ?", "published", "approved", false). Limit(pageSize). Offset(offset). Scan(&rows).Error if err != nil { return nil, err } zoneCounts, err := r.publicZoneCountsCached(ctx) if err != nil { return nil, err } return &PublicListResult{ Items: publicListings(rowsToDTO(rows)), Total: total, Page: page, PageSize: pageSize, ZoneCounts: zoneCounts, }, nil } func normalizedPublicPage(query PublicListQuery) (int, int) { page := query.Page if page <= 0 { page = 1 } pageSize := query.PageSize if pageSize <= 0 { pageSize = 20 } if pageSize > 50 { pageSize = 50 } return page, pageSize } func canListPublicWithSQL(query PublicListQuery) bool { if query.Keyword != "" { return false } if query.Zone != "" && query.Zone != "all" { return false } if len(query.Server) > 0 || len(query.Region) > 0 || len(query.LoginMethod) > 0 || len(query.Rank) > 0 { return false } if len(query.Insurance) > 0 || len(query.Stamina) > 0 || len(query.Load) > 0 { return false } if len(query.SkinGroup) > 0 || len(query.SkinName) > 0 || len(query.ResourceRanges) > 0 { return false } if query.MinCoin != nil || query.MaxCoin != nil || query.MinPrice != nil || query.MaxPrice != nil { return false } if query.MinDeposit != nil || query.MaxDeposit != nil || query.MinTotal != nil || query.MaxTotal != nil { return false } if query.MinFireLevel != nil || query.MaxFireLevel != nil || query.MinSecretKD != nil || query.MaxSecretKD != nil { return false } switch query.Sort { case "", "published", "recommended", "comprehensive", "priceAsc", "priceDesc", "coinDesc": return true default: return false } } func applyPublicSQLSort(db *gorm.DB, sortKey string) *gorm.DB { switch sortKey { case "priceAsc": return db.Order("l.price_cent ASC, l.published_at DESC, l.id DESC") case "priceDesc": return db.Order("l.price_cent DESC, l.published_at DESC, l.id DESC") case "coinDesc": return db.Order("a.haf_coin_amount DESC, l.published_at DESC, l.id DESC") default: return db.Order("l.published_at DESC, l.id DESC") } } func (r *Repository) publicZoneCountsCached(ctx context.Context) (map[string]int64, error) { now := time.Now() r.publicZoneCountsMu.Lock() defer r.publicZoneCountsMu.Unlock() if r.publicZoneCounts.Counts != nil && now.Before(r.publicZoneCounts.ExpiresAt) { return copyPublicZoneCounts(r.publicZoneCounts.Counts), nil } var rows []publicZoneRow err := r.db.WithContext(ctx).Table("rental_listings AS l"). Select("a.login_platform, a.haf_coin_amount, a.asset_summary"). Joins("JOIN game_accounts AS a ON a.id = l.account_id"). Where("l.status = ? AND l.review_status = ? AND l.in_transaction = ?", "published", "approved", false). Scan(&rows).Error if err != nil { return nil, err } counts := map[string]int64{ "all": int64(len(rows)), "sale": 0, "gift": 0, "night": 0, "password": 0, "highCoin": 0, } for _, row := range rows { summary := decodeAssetSummary(row.AssetSummary) if isAcceleratedSale(summary) { counts["sale"]++ } if hasGiftResourcesSummary(summary) { counts["gift"]++ } if isNightAvailableSummary(summary) { counts["night"]++ } if strings.Contains(row.LoginPlatform, "账密") || strings.Contains(row.LoginPlatform, "账号密码") { counts["password"]++ } if float64(row.HafCoinAmount)/1000000 >= 100 { counts["highCoin"]++ } } r.publicZoneCounts = publicZoneCountCache{ Counts: counts, ExpiresAt: now.Add(publicZoneCountCacheTTL), } return copyPublicZoneCounts(counts), nil } func copyPublicZoneCounts(counts map[string]int64) map[string]int64 { copied := make(map[string]int64, len(counts)) for key, value := range counts { copied[key] = value } return copied } func (r *Repository) FindPublic(ctx context.Context, id uint64) (*ListingDTO, error) { dto, err := r.findDTO(ctx, "l.id = ? AND l.status = ? AND l.review_status = ? AND l.in_transaction = ?", id, "published", "approved", false) if err != nil { return nil, err } applyPublicListingURLs(dto) return dto, nil } func (r *Repository) FindPublicCoverKey(ctx context.Context, id uint64) (string, error) { return r.FindPublicScreenshotKey(ctx, id, 0) } func (r *Repository) FindPublicScreenshotKey(ctx context.Context, id uint64, index int) (string, error) { if index < 0 { return "", gorm.ErrRecordNotFound } dto, err := r.findDTO(ctx, "l.id = ? AND l.status = ? AND l.review_status = ? AND l.in_transaction = ?", id, "published", "approved", false) if err != nil { return "", err } if index >= len(dto.ScreenshotURLS) { return "", gorm.ErrRecordNotFound } if key := extractListingObjectKey(dto.ScreenshotURLS[index]); key != "" { return key, nil } return "", gorm.ErrRecordNotFound } type publicZoneRow struct { LoginPlatform string HafCoinAmount int64 AssetSummary datatypes.JSON `gorm:"column:asset_summary"` }