feat(distribution): 优化货架批量操作、结算大事务、大盘与前台多端解析缓存及货架分页
This commit is contained in:
@@ -346,6 +346,7 @@ func (r *Repository) UpdateSite(ctx context.Context, adminID, id uint64, req Upd
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
r.evictSiteInfoCache(ctx, &row)
|
||||
item := siteDTO(row)
|
||||
return &item, nil
|
||||
}
|
||||
@@ -855,6 +856,130 @@ func (r *Repository) ListAccountDisplays(ctx context.Context, siteID uint64, sta
|
||||
return items, nil
|
||||
}
|
||||
|
||||
func (r *Repository) ListAccountDisplaysPaged(ctx context.Context, siteID uint64, q AccountDisplayQuery) (*AccountDisplayPageDTO, error) {
|
||||
page := q.Page
|
||||
if page <= 0 {
|
||||
page = 1
|
||||
}
|
||||
pageSize := q.PageSize
|
||||
if pageSize <= 0 {
|
||||
pageSize = 20
|
||||
}
|
||||
if pageSize > 100 {
|
||||
pageSize = 100
|
||||
}
|
||||
|
||||
baseQuery := r.db.WithContext(ctx).
|
||||
Table("distribution_site_accounts AS dsa").
|
||||
Joins("LEFT JOIN rental_listings AS l ON l.id = dsa.listing_id").
|
||||
Joins("LEFT JOIN game_accounts AS ga ON ga.id = dsa.account_id").
|
||||
Where("dsa.site_id = ?", siteID)
|
||||
|
||||
if q.Status != "" {
|
||||
baseQuery = baseQuery.Where("dsa.display_status = ?", q.Status)
|
||||
}
|
||||
keyword := strings.TrimSpace(q.Keyword)
|
||||
if keyword != "" {
|
||||
baseQuery = baseQuery.Where("(ga.title LIKE ? OR l.listing_no LIKE ? OR ga.rank_level LIKE ?)", "%"+keyword+"%", "%"+keyword+"%", "%"+keyword+"%")
|
||||
}
|
||||
|
||||
var total int64
|
||||
if err := baseQuery.Count(&total).Error; err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
type accountDisplayRow struct {
|
||||
ID uint64 `gorm:"column:id"`
|
||||
SiteID uint64 `gorm:"column:site_id"`
|
||||
AccountID uint64 `gorm:"column:account_id"`
|
||||
ListingID uint64 `gorm:"column:listing_id"`
|
||||
DisplayStatus string `gorm:"column:display_status"`
|
||||
MarkupAmountCent int64 `gorm:"column:markup_amount_cent"`
|
||||
ShelfRatioDelta float64 `gorm:"column:shelf_ratio_delta"`
|
||||
ListingNo string `gorm:"column:listing_no"`
|
||||
Title string `gorm:"column:title"`
|
||||
GameName string `gorm:"column:game_name"`
|
||||
ServerRegion string `gorm:"column:server_region"`
|
||||
LoginPlatform string `gorm:"column:login_platform"`
|
||||
RankLevel string `gorm:"column:rank_level"`
|
||||
HafCoinAmount int64 `gorm:"column:haf_coin_amount"`
|
||||
PriceCent int64 `gorm:"column:price_cent"`
|
||||
DepositAmountCent int64 `gorm:"column:deposit_amount_cent"`
|
||||
ListingStatus string `gorm:"column:listing_status"`
|
||||
InTransaction bool `gorm:"column:in_transaction"`
|
||||
ListingSiteID *uint64 `gorm:"column:listing_site_id"`
|
||||
SourceChannel string `gorm:"column:source_channel"`
|
||||
}
|
||||
|
||||
var rows []accountDisplayRow
|
||||
err := baseQuery.Select(`
|
||||
dsa.id,
|
||||
dsa.site_id,
|
||||
dsa.account_id,
|
||||
dsa.listing_id,
|
||||
dsa.display_status,
|
||||
COALESCE(dsa.markup_amount_cent, 0) AS markup_amount_cent,
|
||||
COALESCE(dsa.shelf_ratio_delta, 0) AS shelf_ratio_delta,
|
||||
COALESCE(l.listing_no, '') AS listing_no,
|
||||
l.distribution_site_id AS listing_site_id,
|
||||
COALESCE(l.source_channel, '') AS source_channel,
|
||||
COALESCE(ga.title, '') AS title,
|
||||
COALESCE(ga.game_name, '') AS game_name,
|
||||
COALESCE(ga.server_region, '') AS server_region,
|
||||
COALESCE(ga.login_platform, '') AS login_platform,
|
||||
COALESCE(ga.rank_level, '') AS rank_level,
|
||||
COALESCE(ga.haf_coin_amount, 0) AS haf_coin_amount,
|
||||
COALESCE(l.price_cent, 0) AS price_cent,
|
||||
COALESCE(l.deposit_amount_cent, 0) AS deposit_amount_cent,
|
||||
COALESCE(l.status, '') AS listing_status,
|
||||
COALESCE(l.in_transaction, 0) AS in_transaction
|
||||
`).
|
||||
Order("dsa.id DESC").
|
||||
Offset((page - 1) * pageSize).
|
||||
Limit(pageSize).
|
||||
Scan(&rows).Error
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
items := make([]AccountDisplayDTO, 0, len(rows))
|
||||
for _, row := range rows {
|
||||
sitePrice, baseRatio, siteRatio := listingpkg.ApplyShelfRatio(row.HafCoinAmount, row.PriceCent, row.ShelfRatioDelta)
|
||||
items = append(items, AccountDisplayDTO{
|
||||
ID: row.ID,
|
||||
SiteID: row.SiteID,
|
||||
AccountID: row.AccountID,
|
||||
ListingID: row.ListingID,
|
||||
DisplayStatus: row.DisplayStatus,
|
||||
MarkupAmountCent: sitePrice - row.PriceCent,
|
||||
ShelfRatioDelta: row.ShelfRatioDelta,
|
||||
BaseRatio: baseRatio,
|
||||
SiteRatio: siteRatio,
|
||||
ListingNo: row.ListingNo,
|
||||
Title: row.Title,
|
||||
GameName: row.GameName,
|
||||
ServerRegion: row.ServerRegion,
|
||||
LoginPlatform: row.LoginPlatform,
|
||||
RankLevel: row.RankLevel,
|
||||
HafCoinAmount: row.HafCoinAmount,
|
||||
PriceCent: row.PriceCent,
|
||||
SitePriceCent: sitePrice,
|
||||
DepositAmountCent: row.DepositAmountCent,
|
||||
ListingStatus: row.ListingStatus,
|
||||
InTransaction: row.InTransaction,
|
||||
ListingSiteID: row.ListingSiteID,
|
||||
SourceChannel: row.SourceChannel,
|
||||
})
|
||||
}
|
||||
|
||||
return &AccountDisplayPageDTO{
|
||||
Items: items,
|
||||
Total: total,
|
||||
Page: page,
|
||||
PageSize: pageSize,
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (r *Repository) UpdateAccountDisplay(ctx context.Context, adminID, id uint64, status string) (*AccountDisplayDTO, error) {
|
||||
var row model.DistributionSiteAccount
|
||||
if err := r.db.WithContext(ctx).First(&row, id).Error; err != nil {
|
||||
@@ -1172,12 +1297,21 @@ func (r *Repository) CreateSettlementBatch(ctx context.Context, adminID uint64,
|
||||
return err
|
||||
}
|
||||
total := int64(0)
|
||||
items := make([]model.DistributionSettlementItem, 0, len(parties))
|
||||
for _, party := range parties {
|
||||
item := model.DistributionSettlementItem{BatchID: batch.ID, OrderID: party.OrderID, PartyID: party.ID, AmountCent: party.IncomeAmountCent, Status: "pending"}
|
||||
if err := tx.Create(&item).Error; err != nil {
|
||||
items = append(items, model.DistributionSettlementItem{
|
||||
BatchID: batch.ID,
|
||||
OrderID: party.OrderID,
|
||||
PartyID: party.ID,
|
||||
AmountCent: party.IncomeAmountCent,
|
||||
Status: "pending",
|
||||
})
|
||||
total += party.IncomeAmountCent
|
||||
}
|
||||
if len(items) > 0 {
|
||||
if err := tx.CreateInBatches(&items, 500).Error; err != nil {
|
||||
return err
|
||||
}
|
||||
total += party.IncomeAmountCent
|
||||
}
|
||||
batch.TotalAmountCent = total
|
||||
return tx.Model(&model.DistributionSettlementBatch{}).Where("id = ?", batch.ID).
|
||||
@@ -1887,6 +2021,22 @@ func (r *Repository) ListPublicSiteListings(ctx context.Context, siteCode string
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (r *Repository) evictSiteInfoCache(ctx context.Context, site *model.DistributionSite) {
|
||||
if r.redis == nil || site == nil {
|
||||
return
|
||||
}
|
||||
keys := make([]string, 0, 2)
|
||||
if site.SiteCode != "" {
|
||||
keys = append(keys, fmt.Sprintf("dist:site_info:code:%s", site.SiteCode))
|
||||
}
|
||||
if site.Domain != "" {
|
||||
keys = append(keys, fmt.Sprintf("dist:site_info:domain:%s", site.Domain))
|
||||
}
|
||||
if len(keys) > 0 {
|
||||
_ = r.redis.Del(ctx, keys...).Err()
|
||||
}
|
||||
}
|
||||
|
||||
func (r *Repository) GetPublicSiteInfo(ctx context.Context, code, domain string) (*PublicSiteInfoDTO, error) {
|
||||
if r.db == nil {
|
||||
return nil, ErrDependencyUnavailable
|
||||
@@ -1897,6 +2047,22 @@ func (r *Repository) GetPublicSiteInfo(ctx context.Context, code, domain string)
|
||||
return nil, ErrSiteNotFound
|
||||
}
|
||||
|
||||
var cacheKey string
|
||||
if code != "" {
|
||||
cacheKey = fmt.Sprintf("dist:site_info:code:%s", code)
|
||||
} else if domain != "" {
|
||||
cacheKey = fmt.Sprintf("dist:site_info:domain:%s", domain)
|
||||
}
|
||||
if cacheKey != "" && r.redis != nil {
|
||||
val, err := r.redis.Get(ctx, cacheKey).Result()
|
||||
if err == nil && val != "" {
|
||||
var cached PublicSiteInfoDTO
|
||||
if jsonErr := json.Unmarshal([]byte(val), &cached); jsonErr == nil {
|
||||
return &cached, nil
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
var site model.DistributionSite
|
||||
query := r.db.WithContext(ctx).Where("status = 'active'")
|
||||
if code != "" {
|
||||
@@ -1912,7 +2078,7 @@ func (r *Repository) GetPublicSiteInfo(ctx context.Context, code, domain string)
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return &PublicSiteInfoDTO{
|
||||
res := &PublicSiteInfoDTO{
|
||||
SiteCode: site.SiteCode,
|
||||
SiteName: site.SiteName,
|
||||
Domain: site.Domain,
|
||||
@@ -1924,7 +2090,18 @@ func (r *Repository) GetPublicSiteInfo(ctx context.Context, code, domain string)
|
||||
BannersJSON: site.BannersJSON,
|
||||
AnnouncementsJSON: site.AnnouncementsJSON,
|
||||
FooterInfoJSON: site.FooterInfoJSON,
|
||||
}, nil
|
||||
}
|
||||
if r.redis != nil {
|
||||
if bytes, err := json.Marshal(res); err == nil {
|
||||
if site.SiteCode != "" {
|
||||
_ = r.redis.Set(ctx, fmt.Sprintf("dist:site_info:code:%s", site.SiteCode), string(bytes), 10*time.Minute).Err()
|
||||
}
|
||||
if site.Domain != "" {
|
||||
_ = r.redis.Set(ctx, fmt.Sprintf("dist:site_info:domain:%s", site.Domain), string(bytes), 10*time.Minute).Err()
|
||||
}
|
||||
}
|
||||
}
|
||||
return res, nil
|
||||
}
|
||||
|
||||
func generateWithdrawNo() (string, error) {
|
||||
@@ -1995,6 +2172,7 @@ func (r *Repository) UpdateSiteBranding(ctx context.Context, adminID, siteID uin
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
r.evictSiteInfoCache(ctx, &site)
|
||||
dto := siteDTO(site)
|
||||
return &dto, nil
|
||||
}
|
||||
@@ -2035,15 +2213,77 @@ func (r *Repository) BatchUpdateAccountMarkup(ctx context.Context, adminID, site
|
||||
if err := r.ensureSiteAccess(ctx, adminID, siteID); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// 模式 A:单次提交多个商品的个性化加价列表
|
||||
if len(req.Items) > 0 {
|
||||
return r.db.WithContext(ctx).Transaction(func(tx *gorm.DB) error {
|
||||
for _, item := range req.Items {
|
||||
if item.ShelfRatioDelta < 0 {
|
||||
return ErrInvalidAmount
|
||||
}
|
||||
q := tx.Model(&model.DistributionSiteAccount{}).Where("site_id = ?", siteID)
|
||||
if item.ID > 0 {
|
||||
q = q.Where("id = ?", item.ID)
|
||||
} else if item.ListingID > 0 {
|
||||
q = q.Where("listing_id = ?", item.ListingID)
|
||||
} else {
|
||||
continue
|
||||
}
|
||||
if err := q.Update("shelf_ratio_delta", item.ShelfRatioDelta).Error; err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
})
|
||||
}
|
||||
|
||||
// 模式 B:统一加价比例
|
||||
if req.ShelfRatioDelta < 0 {
|
||||
return ErrInvalidAmount
|
||||
}
|
||||
if len(req.ListingIDs) == 0 {
|
||||
displayIDs := uniquePositiveIDs(req.DisplayIDs)
|
||||
listingIDs := uniquePositiveIDs(req.ListingIDs)
|
||||
if len(displayIDs) == 0 && len(listingIDs) == 0 {
|
||||
return ErrEmptyListingIDs
|
||||
}
|
||||
return r.db.WithContext(ctx).Model(&model.DistributionSiteAccount{}).
|
||||
Where("site_id = ? AND listing_id IN ?", siteID, req.ListingIDs).
|
||||
Update("shelf_ratio_delta", req.ShelfRatioDelta).Error
|
||||
|
||||
q := r.db.WithContext(ctx).Model(&model.DistributionSiteAccount{}).Where("site_id = ?", siteID)
|
||||
if len(displayIDs) > 0 && len(listingIDs) > 0 {
|
||||
q = q.Where("(id IN ? OR listing_id IN ?)", displayIDs, listingIDs)
|
||||
} else if len(displayIDs) > 0 {
|
||||
q = q.Where("id IN ?", displayIDs)
|
||||
} else {
|
||||
q = q.Where("listing_id IN ?", listingIDs)
|
||||
}
|
||||
return q.Update("shelf_ratio_delta", req.ShelfRatioDelta).Error
|
||||
}
|
||||
|
||||
func (r *Repository) BatchUpdateAccountDisplay(ctx context.Context, adminID, siteID uint64, req BatchUpdateAccountDisplayRequest) error {
|
||||
if siteID == 0 {
|
||||
return ErrSiteNotFound
|
||||
}
|
||||
if err := r.ensureSiteAccess(ctx, adminID, siteID); err != nil {
|
||||
return err
|
||||
}
|
||||
targetStatus := strings.TrimSpace(req.TargetStatus)
|
||||
if targetStatus != "enabled" && targetStatus != "disabled" {
|
||||
return ErrInvalidListingStatus
|
||||
}
|
||||
displayIDs := uniquePositiveIDs(req.DisplayIDs)
|
||||
listingIDs := uniquePositiveIDs(req.ListingIDs)
|
||||
if len(displayIDs) == 0 && len(listingIDs) == 0 {
|
||||
return ErrEmptyListingIDs
|
||||
}
|
||||
|
||||
q := r.db.WithContext(ctx).Model(&model.DistributionSiteAccount{}).Where("site_id = ?", siteID)
|
||||
if len(displayIDs) > 0 && len(listingIDs) > 0 {
|
||||
q = q.Where("(id IN ? OR listing_id IN ?)", displayIDs, listingIDs)
|
||||
} else if len(displayIDs) > 0 {
|
||||
q = q.Where("id IN ?", displayIDs)
|
||||
} else {
|
||||
q = q.Where("listing_id IN ?", listingIDs)
|
||||
}
|
||||
return q.Update("display_status", targetStatus).Error
|
||||
}
|
||||
|
||||
func (r *Repository) ListReceivedListings(ctx context.Context, adminID, siteID uint64, q ReceivedListingQuery) (*ReceivedListingPage, error) {
|
||||
@@ -2450,6 +2690,17 @@ func (r *Repository) GetDashboardAnalytics(ctx context.Context, adminID, siteID
|
||||
return nil, err
|
||||
}
|
||||
|
||||
cacheKey := fmt.Sprintf("dist:analytics:%d", siteID)
|
||||
if r.redis != nil {
|
||||
val, err := r.redis.Get(ctx, cacheKey).Result()
|
||||
if err == nil && val != "" {
|
||||
var cached DistributionAnalyticsDTO
|
||||
if jsonErr := json.Unmarshal([]byte(val), &cached); jsonErr == nil {
|
||||
return &cached, nil
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
todayStr := time.Now().Format("2006-01-02")
|
||||
monthStr := time.Now().Format("2006-01")
|
||||
|
||||
@@ -2555,7 +2806,7 @@ func (r *Repository) GetDashboardAnalytics(ctx context.Context, adminID, siteID
|
||||
})
|
||||
}
|
||||
|
||||
return &DistributionAnalyticsDTO{
|
||||
dto := &DistributionAnalyticsDTO{
|
||||
TodayReceivedCount: todayReceived,
|
||||
TotalReceivedCount: totalReceived,
|
||||
ActiveListingCount: activeListings,
|
||||
@@ -2565,5 +2816,11 @@ func (r *Repository) GetDashboardAnalytics(ctx context.Context, adminID, siteID
|
||||
MonthProfitCent: monthProfit,
|
||||
AvailableBalanceCent: wallet.AvailableBalanceCent,
|
||||
TrendDays: trendDays,
|
||||
}, nil
|
||||
}
|
||||
if r.redis != nil {
|
||||
if bytes, err := json.Marshal(dto); err == nil {
|
||||
_ = r.redis.Set(ctx, cacheKey, string(bytes), 60*time.Second).Err()
|
||||
}
|
||||
}
|
||||
return dto, nil
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user