257 lines
7.0 KiB
Go
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"`
|
|
}
|