399 lines
15 KiB
Go
399 lines
15 KiB
Go
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
|
|
}
|