feat: 迁移注册充值明细和筛选导出
This commit is contained in:
@@ -0,0 +1,398 @@
|
||||
package business
|
||||
|
||||
import (
|
||||
"context"
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
"gorm.io/gorm"
|
||||
"kra/internal/biz/business"
|
||||
)
|
||||
|
||||
type ledgerRepo struct{ data Provider }
|
||||
|
||||
func NewLedgerRepo(data Provider) business.LedgerRepo { return &ledgerRepo{data: data} }
|
||||
func ledgerBasePromotes(db *gorm.DB, scope business.LedgerScope, filter business.LedgerFilter, alias string) *gorm.DB {
|
||||
q := db.Table("tab_promote " + alias)
|
||||
if len(filter.Sources) > 0 {
|
||||
q = q.Where(alias+".source_db IN ?", filter.Sources)
|
||||
}
|
||||
if !scope.IncludeSub {
|
||||
q = q.Where(alias + ".promote_level = 1")
|
||||
}
|
||||
if !scope.Unrestricted {
|
||||
bindings := channelBindingQuery(db, scope.Viewer, business.ChannelFilter{Sources: filter.Sources, BusierAccount: filter.BusierAccount}).Select("1").Where("b.source_db=" + alias + ".source_db AND b.platform_busier_id=" + alias + ".busier_id")
|
||||
q = q.Where("EXISTS (?)", bindings)
|
||||
}
|
||||
return q
|
||||
}
|
||||
func ledgerPromotes(db *gorm.DB, scope business.LedgerScope, filter business.LedgerFilter, alias string) *gorm.DB {
|
||||
q := ledgerBasePromotes(db, scope, filter, alias)
|
||||
if filter.PromoteAccount == "" {
|
||||
return q
|
||||
}
|
||||
hits := ledgerBasePromotes(db, scope, filter, "hit")
|
||||
exact := "TRIM(hit.account) = ? COLLATE BINARY"
|
||||
if db.Dialector.Name() == "mysql" {
|
||||
exact = "BINARY TRIM(hit.account) = ?"
|
||||
}
|
||||
hits = hits.Where(exact, filter.PromoteAccount).Where("hit.source_db=" + alias + ".source_db")
|
||||
if scope.IncludeSub {
|
||||
root := "CASE WHEN hit.promote_level=1 THEN hit.id WHEN hit.top_promote_id>0 THEN hit.top_promote_id ELSE hit.id END"
|
||||
hits = hits.Where("(" + alias + ".id = (" + root + ") OR " + alias + ".top_promote_id = (" + root + "))")
|
||||
} else {
|
||||
hits = hits.Where("hit.id=" + alias + ".id")
|
||||
}
|
||||
return q.Where("EXISTS (?)", hits.Select("1"))
|
||||
}
|
||||
func prepareLedger(db *gorm.DB, scope business.LedgerScope, filter business.LedgerFilter) (empty, available bool, err error) {
|
||||
if !scope.Unrestricted {
|
||||
var bindings []struct{ UserID uint }
|
||||
if err := channelBindingQuery(db, scope.Viewer, business.ChannelFilter{Sources: filter.Sources, BusierAccount: filter.BusierAccount}).Select("b.user_id").Limit(1).Scan(&bindings).Error; err != nil {
|
||||
return false, false, business.ErrStorage
|
||||
}
|
||||
if len(bindings) == 0 {
|
||||
return true, true, nil
|
||||
}
|
||||
}
|
||||
available, err = businessTableAvailable(db, "tab_promote")
|
||||
if err != nil || !available {
|
||||
return false, available, err
|
||||
}
|
||||
var rows []struct{ ID uint64 }
|
||||
if err := ledgerPromotes(db, scope, filter, "p").Select("p.id").Limit(1).Scan(&rows).Error; err != nil {
|
||||
return false, false, business.ErrStorage
|
||||
}
|
||||
return len(rows) == 0, true, nil
|
||||
}
|
||||
func ledgerScopeQuery(db *gorm.DB, scope business.LedgerScope, filter business.LedgerFilter, table string) *gorm.DB {
|
||||
q := db.Table(table + " r")
|
||||
if len(filter.Sources) > 0 {
|
||||
q = q.Where("r.source_db IN ?", filter.Sources)
|
||||
}
|
||||
if scope.Unrestricted && scope.IncludeSub && filter.PromoteAccount == "" {
|
||||
return q
|
||||
}
|
||||
return q.Where("EXISTS (?)", ledgerPromotes(db, scope, filter, "p").Select("1").Where("p.source_db=r.source_db AND p.id=r.promote_id"))
|
||||
}
|
||||
|
||||
type registrationPO struct {
|
||||
ID uint64
|
||||
SourceDB string `gorm:"column:source_db"`
|
||||
RegisterTime *string
|
||||
FirstGameID *int64 `gorm:"column:fgame_id"`
|
||||
FirstGameName string `gorm:"column:fgame_name"`
|
||||
RegisterIP string `gorm:"column:register_ip"`
|
||||
DeviceName string
|
||||
PromoteID *int64
|
||||
}
|
||||
|
||||
func registrationQuery(db *gorm.DB, scope business.LedgerScope, filter business.LedgerFilter) *gorm.DB {
|
||||
cast := "CAST(r.register_time AS INTEGER)"
|
||||
if db.Dialector.Name() == "mysql" {
|
||||
cast = "CAST(r.register_time AS UNSIGNED)"
|
||||
}
|
||||
q := ledgerScopeQuery(db, scope, filter, "tab_user").Where("r.puid = 0").Where(cast+" BETWEEN ? AND ?", filter.BeginUnix, filter.EndUnix)
|
||||
if filter.UserID != nil {
|
||||
q = q.Where("r.id = ?", *filter.UserID)
|
||||
}
|
||||
if len(filter.GameNames) > 0 {
|
||||
q = q.Where("EXISTS (?)", db.Table("tab_game g").Select("1").Where("g.source_db=r.source_db AND g.id=r.fgame_id AND g.game_name IN ?", filter.GameNames))
|
||||
}
|
||||
return q
|
||||
}
|
||||
func (r *ledgerRepo) Registrations(ctx context.Context, scope business.LedgerScope, filter business.LedgerFilter, options business.LedgerListOptions) (*business.RegistrationPage, error) {
|
||||
db := r.data.DB().WithContext(ctx)
|
||||
page := &business.RegistrationPage{Rows: []*business.Registration{}, Available: true}
|
||||
empty, available, err := prepareLedger(db, scope, filter)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
page.Available = available
|
||||
if empty || !available {
|
||||
return page, nil
|
||||
}
|
||||
for _, table := range append([]string{"tab_user"}, func() []string {
|
||||
if len(filter.GameNames) > 0 {
|
||||
return []string{"tab_game"}
|
||||
}
|
||||
return nil
|
||||
}()...) {
|
||||
available, err := businessTableAvailable(db, table)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if !available {
|
||||
page.Available = false
|
||||
return page, nil
|
||||
}
|
||||
}
|
||||
payAvailable, err := businessTableAvailable(db, "tab_spend")
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if filter.SortField == "totalPayAmount" && !payAvailable {
|
||||
page.Available = false
|
||||
return page, nil
|
||||
}
|
||||
q := registrationQuery(db, scope, filter)
|
||||
if err := q.Count(&page.Total).Error; err != nil {
|
||||
return nil, business.ErrStorage
|
||||
}
|
||||
if options.RejectAbove > 0 && page.Total > options.RejectAbove {
|
||||
return page, nil
|
||||
}
|
||||
direction := " DESC"
|
||||
if filter.Ascending {
|
||||
direction = " ASC"
|
||||
}
|
||||
order := "CAST(r.register_time AS INTEGER)"
|
||||
if db.Dialector.Name() == "mysql" {
|
||||
order = "CAST(r.register_time AS UNSIGNED)"
|
||||
}
|
||||
if filter.SortField == "totalPayAmount" {
|
||||
order = "(SELECT COALESCE(SUM(s.pay_amount),0) FROM tab_spend s WHERE s.source_db=r.source_db AND s.user_id=r.id AND s.pay_status=1)"
|
||||
}
|
||||
var pos []registrationPO
|
||||
if err := q.Select("r.id,r.source_db,r.register_time,r.fgame_id,r.fgame_name,r.register_ip,r.device_name,r.promote_id").Order(order + direction).Order("r.id DESC,r.source_db ASC").Offset(options.Offset).Limit(options.Limit).Scan(&pos).Error; err != nil {
|
||||
return nil, business.ErrStorage
|
||||
}
|
||||
meta := []ledgerMetaTarget{}
|
||||
keys := []business.ChannelKey{}
|
||||
for _, po := range pos {
|
||||
row := &business.Registration{ChannelKey: business.ChannelKey{SourceDB: po.SourceDB, ID: po.ID}, LedgerNames: business.LedgerNames{PromoteID: po.PromoteID}, FirstGameID: po.FirstGameID, FirstGameName: po.FirstGameName, RegisterIP: po.RegisterIP, DeviceName: po.DeviceName}
|
||||
if po.RegisterTime != nil {
|
||||
value, err := strconv.ParseInt(strings.TrimSpace(*po.RegisterTime), 10, 64)
|
||||
if err == nil {
|
||||
row.RegisterTime = &value
|
||||
}
|
||||
}
|
||||
if payAvailable {
|
||||
zero := "0"
|
||||
row.TotalPayAmount = &zero
|
||||
}
|
||||
page.Rows = append(page.Rows, row)
|
||||
meta = append(meta, ledgerMetaTarget{Source: row.SourceDB, Names: &row.LedgerNames})
|
||||
keys = append(keys, row.ChannelKey)
|
||||
}
|
||||
if err := fillLedgerNames(db, scope, filter, meta); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if payAvailable && len(keys) > 0 {
|
||||
amounts := map[business.ChannelKey]string{}
|
||||
for start := 0; start < len(keys); start += 300 {
|
||||
var values []struct {
|
||||
SourceDB string `gorm:"column:source_db"`
|
||||
UserID uint64
|
||||
Amount string
|
||||
}
|
||||
if err := db.Table("tab_spend").Select("source_db,user_id,COALESCE(SUM(pay_amount),0) AS amount").Where("pay_status=1 AND (source_db,user_id) IN ?", channelPairs(keys[start:min(start+300, len(keys))])).Group("source_db,user_id").Scan(&values).Error; err != nil {
|
||||
return nil, business.ErrStorage
|
||||
}
|
||||
for _, value := range values {
|
||||
amounts[business.ChannelKey{SourceDB: value.SourceDB, ID: value.UserID}] = value.Amount
|
||||
}
|
||||
}
|
||||
for _, row := range page.Rows {
|
||||
if amount, ok := amounts[row.ChannelKey]; ok {
|
||||
row.TotalPayAmount = &amount
|
||||
}
|
||||
}
|
||||
}
|
||||
return page, nil
|
||||
}
|
||||
|
||||
type spendPO struct {
|
||||
ID uint64
|
||||
SourceDB string `gorm:"column:source_db"`
|
||||
UserID, GameID, PromoteID *int64
|
||||
PayOrderNumber, GameName, ServerID, ServerName, GamePlayerName, SpendIP, PromoteAccount string
|
||||
PayAmount *string
|
||||
PayTime *int64
|
||||
PayWay, PayStatus *int
|
||||
}
|
||||
|
||||
func spendQuery(db *gorm.DB, scope business.LedgerScope, filter business.LedgerFilter, successOnly bool) *gorm.DB {
|
||||
q := ledgerScopeQuery(db, scope, filter, "tab_spend").Where("r.pay_time BETWEEN ? AND ?", filter.BeginUnix, filter.EndUnix)
|
||||
if successOnly {
|
||||
q = q.Where("r.pay_status = 1")
|
||||
} else if filter.PayStatus != nil {
|
||||
q = q.Where("r.pay_status = ?", *filter.PayStatus)
|
||||
}
|
||||
if len(filter.GameNames) > 0 {
|
||||
q = q.Where("r.game_name IN ?", filter.GameNames)
|
||||
}
|
||||
if filter.UserID != nil {
|
||||
q = q.Where("r.user_id = ?", *filter.UserID)
|
||||
}
|
||||
if filter.PayWay != nil {
|
||||
q = q.Where("r.pay_way = ?", *filter.PayWay)
|
||||
}
|
||||
if filter.PlayerName != "" {
|
||||
q = q.Where("r.game_player_name LIKE ?", "%"+filter.PlayerName+"%")
|
||||
}
|
||||
if filter.OrderNumber != "" {
|
||||
q = q.Where("r.pay_order_number LIKE ?", "%"+filter.OrderNumber+"%")
|
||||
}
|
||||
return q
|
||||
}
|
||||
func (r *ledgerRepo) Spends(ctx context.Context, scope business.LedgerScope, filter business.LedgerFilter, options business.LedgerListOptions) (*business.SpendPage, error) {
|
||||
db := r.data.DB().WithContext(ctx)
|
||||
zero := "0"
|
||||
page := &business.SpendPage{Rows: []*business.Spend{}, Available: true, TotalPayAmount: &zero}
|
||||
empty, available, err := prepareLedger(db, scope, filter)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
page.Available = available
|
||||
if !available {
|
||||
page.TotalPayAmount = nil
|
||||
}
|
||||
if empty || !available {
|
||||
return page, nil
|
||||
}
|
||||
available, err = businessTableAvailable(db, "tab_spend")
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if !available {
|
||||
page.Available = false
|
||||
page.TotalPayAmount = nil
|
||||
return page, nil
|
||||
}
|
||||
q := spendQuery(db, scope, filter, false)
|
||||
if err := q.Count(&page.Total).Error; err != nil {
|
||||
return nil, business.ErrStorage
|
||||
}
|
||||
if options.RejectAbove > 0 && page.Total > options.RejectAbove {
|
||||
return page, nil
|
||||
}
|
||||
if err := spendQuery(db, scope, filter, true).Select("COALESCE(SUM(r.pay_amount),0)").Scan(page.TotalPayAmount).Error; err != nil {
|
||||
return nil, business.ErrStorage
|
||||
}
|
||||
order := "r.pay_time"
|
||||
if filter.SortField == "payAmount" {
|
||||
order = "r.pay_amount"
|
||||
}
|
||||
if filter.Ascending {
|
||||
order += " ASC"
|
||||
} else {
|
||||
order += " DESC"
|
||||
}
|
||||
var pos []spendPO
|
||||
if err := q.Select("r.id,r.source_db,r.user_id,r.pay_order_number,r.game_id,r.game_name,r.server_id,r.server_name,r.game_player_name,r.pay_amount,r.pay_time,r.pay_way,r.pay_status,r.spend_ip,r.promote_id,r.promote_account").Order(order).Order("r.id DESC,r.source_db ASC").Offset(options.Offset).Limit(options.Limit).Scan(&pos).Error; err != nil {
|
||||
return nil, business.ErrStorage
|
||||
}
|
||||
meta := []ledgerMetaTarget{}
|
||||
for _, po := range pos {
|
||||
row := &business.Spend{ChannelKey: business.ChannelKey{SourceDB: po.SourceDB, ID: po.ID}, LedgerNames: business.LedgerNames{PromoteID: po.PromoteID, PromoteAccount: po.PromoteAccount}, UserID: po.UserID, GameID: po.GameID, OrderNumber: po.PayOrderNumber, GameName: po.GameName, ServerID: po.ServerID, ServerName: po.ServerName, PlayerName: po.GamePlayerName, PayAmount: po.PayAmount, PayTime: po.PayTime, PayWay: po.PayWay, PayStatus: po.PayStatus, IP: po.SpendIP}
|
||||
page.Rows = append(page.Rows, row)
|
||||
meta = append(meta, ledgerMetaTarget{Source: row.SourceDB, Names: &row.LedgerNames})
|
||||
}
|
||||
if err := fillLedgerNames(db, scope, filter, meta); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return page, nil
|
||||
}
|
||||
|
||||
type ledgerMetaTarget struct {
|
||||
Source string
|
||||
Names *business.LedgerNames
|
||||
}
|
||||
|
||||
func fillLedgerNames(db *gorm.DB, scope business.LedgerScope, filter business.LedgerFilter, targets []ledgerMetaTarget) error {
|
||||
keys := []business.ChannelKey{}
|
||||
seen := map[business.ChannelKey]bool{}
|
||||
for _, target := range targets {
|
||||
if target.Names.PromoteID == nil || *target.Names.PromoteID < 0 {
|
||||
continue
|
||||
}
|
||||
key := business.ChannelKey{SourceDB: target.Source, ID: uint64(*target.Names.PromoteID)}
|
||||
if !seen[key] {
|
||||
keys = append(keys, key)
|
||||
seen[key] = true
|
||||
}
|
||||
}
|
||||
channels := map[business.ChannelKey]channelPO{}
|
||||
pairs := []business.PlatformKey{}
|
||||
pairSeen := map[business.PlatformKey]bool{}
|
||||
for start := 0; start < len(keys); start += 300 {
|
||||
var rows []channelPO
|
||||
if err := ledgerPromotes(db, scope, filter, "p").Select("p.id,p.source_db,p.account,p.busier_id").Where("(p.source_db,p.id) IN ?", channelPairs(keys[start:min(start+300, len(keys))])).Scan(&rows).Error; err != nil {
|
||||
return business.ErrStorage
|
||||
}
|
||||
for _, row := range rows {
|
||||
channels[business.ChannelKey{SourceDB: row.SourceDB, ID: row.ID}] = row
|
||||
key := business.PlatformKey{SourceDB: row.SourceDB, BusierID: row.BusierID}
|
||||
if !pairSeen[key] {
|
||||
pairs = append(pairs, key)
|
||||
pairSeen[key] = true
|
||||
}
|
||||
}
|
||||
}
|
||||
type bindName struct {
|
||||
SourceDB string `gorm:"column:source_db"`
|
||||
PlatformBusierID uint64
|
||||
PlatformBusierAccount, Username string
|
||||
}
|
||||
names := map[business.PlatformKey]bindName{}
|
||||
for start := 0; start < len(pairs); start += 300 {
|
||||
var rows []bindName
|
||||
q := channelBindingQuery(db, scope.Viewer, business.ChannelFilter{}).Select("b.source_db,b.platform_busier_id,b.platform_busier_account,u.username").Where("(b.source_db,b.platform_busier_id) IN ?", portraitBindingPairs(pairs[start:min(start+300, len(pairs))])).Order("b.user_id")
|
||||
if err := q.Scan(&rows).Error; err != nil {
|
||||
return business.ErrStorage
|
||||
}
|
||||
for _, row := range rows {
|
||||
key := business.PlatformKey{SourceDB: row.SourceDB, BusierID: row.PlatformBusierID}
|
||||
if _, ok := names[key]; !ok {
|
||||
names[key] = row
|
||||
}
|
||||
}
|
||||
}
|
||||
for _, target := range targets {
|
||||
n := target.Names
|
||||
if n.PromoteID == nil || *n.PromoteID < 0 {
|
||||
continue
|
||||
}
|
||||
if c, ok := channels[business.ChannelKey{SourceDB: target.Source, ID: uint64(*n.PromoteID)}]; ok {
|
||||
if strings.TrimSpace(n.PromoteAccount) == "" {
|
||||
n.PromoteAccount = c.Account
|
||||
}
|
||||
if binding, ok := names[business.PlatformKey{SourceDB: c.SourceDB, BusierID: c.BusierID}]; ok {
|
||||
n.BindingAccount = binding.PlatformBusierAccount
|
||||
n.BindingUserName = binding.Username
|
||||
}
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
func (r *ledgerRepo) Games(ctx context.Context, sources []string, keyword string, limit int) ([]*business.GameOption, error) {
|
||||
db := r.data.DB().WithContext(ctx)
|
||||
available, err := businessTableAvailable(db, "tab_game")
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if !available {
|
||||
return nil, business.ErrGameOptionsUnavailable
|
||||
}
|
||||
q := db.Table("tab_game")
|
||||
if len(sources) > 0 {
|
||||
q = q.Where("source_db IN ?", sources)
|
||||
}
|
||||
if keyword != "" {
|
||||
q = q.Where("game_name LIKE ?", "%"+keyword+"%")
|
||||
}
|
||||
var rows []struct {
|
||||
ID uint64
|
||||
SourceDB string `gorm:"column:source_db"`
|
||||
GameName string
|
||||
}
|
||||
if err := q.Select("id,source_db,game_name").Order("game_name,id,source_db").Limit(limit).Scan(&rows).Error; err != nil {
|
||||
return nil, business.ErrStorage
|
||||
}
|
||||
result := make([]*business.GameOption, 0, len(rows))
|
||||
for _, row := range rows {
|
||||
result = append(result, &business.GameOption{ID: row.ID, SourceDB: row.SourceDB, Name: row.GameName})
|
||||
}
|
||||
return result, nil
|
||||
}
|
||||
Reference in New Issue
Block a user