Files
kra-new/internal/data/business/ledger.go
T

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
}