Files
hfb_sys/backend/internal/modules/listing/public_query.go
T

257 lines
7.0 KiB
Go

package listing
import (
"context"
"fmt"
"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.MinRatio != nil || query.MaxRatio != nil {
return false
}
if 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
}
if query.MinDailyLoss != nil || query.MaxDailyLoss != 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")
case "published":
return db.Order("l.published_at DESC, l.id DESC")
default:
return db.Order(fmt.Sprintf("((l.id * 1103515245 + %d) %% 2147483647) ASC, l.id ASC", publicShuffleSeed()))
}
}
func publicShuffleSeed() int64 {
now := time.Now().UTC()
return int64(now.Year()*366 + now.YearDay())
}
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"`
}