feat: 迁移注册充值明细和筛选导出
This commit is contained in:
@@ -0,0 +1,115 @@
|
||||
package business
|
||||
|
||||
import (
|
||||
"context"
|
||||
"kra/internal/biz/business"
|
||||
"kra/internal/service/dto"
|
||||
"strings"
|
||||
"time"
|
||||
)
|
||||
|
||||
type LedgerUsecase interface {
|
||||
Registrations(context.Context, business.LedgerFilter, business.LedgerListOptions) (*business.RegistrationPage, error)
|
||||
Spends(context.Context, business.LedgerFilter, business.LedgerListOptions) (*business.SpendPage, error)
|
||||
ExportRegistrations(context.Context, business.LedgerFilter) ([]*business.Registration, error)
|
||||
ExportSpends(context.Context, business.LedgerFilter) ([]*business.Spend, error)
|
||||
Games(context.Context, []string, string, int) ([]*business.GameOption, error)
|
||||
}
|
||||
type LedgerService struct{ uc LedgerUsecase }
|
||||
|
||||
func NewLedgerService(uc *business.LedgerUsecase) *LedgerService { return &LedgerService{uc: uc} }
|
||||
func ledgerRequest(req *dto.LedgerQuery, spend bool) (business.LedgerFilter, business.LedgerListOptions, error) {
|
||||
if req == nil {
|
||||
return business.LedgerFilter{}, business.LedgerListOptions{}, business.ErrLedgerInput
|
||||
}
|
||||
sources, err := normalizePlatforms(req.Sources)
|
||||
if err != nil {
|
||||
return business.LedgerFilter{}, business.LedgerListOptions{}, err
|
||||
}
|
||||
f := business.LedgerFilter{Sources: sources, GameNames: splitQueryValues(req.GameNames), UserID: req.UserID, PromoteAccount: strings.TrimSpace(req.PromoteAccount), BusierAccount: strings.TrimSpace(req.BusierAccount), PlayerName: strings.TrimSpace(req.PlayerName), OrderNumber: strings.TrimSpace(req.OrderNumber), PayStatus: req.PayStatus, PayWay: req.PayWay, SortField: strings.TrimSpace(req.SortField), Ascending: strings.EqualFold(strings.TrimSpace(req.SortOrder), "asc") || strings.EqualFold(strings.TrimSpace(req.SortOrder), "ascending")}
|
||||
begin, end := req.BeginRegisterTime, req.EndRegisterTime
|
||||
if spend {
|
||||
begin, end = req.BeginPayTime, req.EndPayTime
|
||||
}
|
||||
if strings.TrimSpace(begin) != "" && strings.TrimSpace(end) != "" {
|
||||
location := time.FixedZone("Asia/Shanghai", 8*3600)
|
||||
b, err := time.ParseInLocation("2006-01-02", strings.TrimSpace(begin), location)
|
||||
if err != nil {
|
||||
return f, business.LedgerListOptions{}, business.ErrLedgerInput
|
||||
}
|
||||
e, err := time.ParseInLocation("2006-01-02", strings.TrimSpace(end), location)
|
||||
if err != nil {
|
||||
return f, business.LedgerListOptions{}, business.ErrLedgerInput
|
||||
}
|
||||
f.StartDate = &b
|
||||
f.EndDate = &e
|
||||
}
|
||||
page, size := req.Page, req.PageSize
|
||||
if page < 1 {
|
||||
page = 1
|
||||
}
|
||||
if size < 1 {
|
||||
size = 10
|
||||
}
|
||||
if size > 500 {
|
||||
size = 500
|
||||
}
|
||||
if page > int(^uint(0)>>1)/size {
|
||||
return f, business.LedgerListOptions{}, business.ErrLedgerInput
|
||||
}
|
||||
return f, business.LedgerListOptions{Offset: (page - 1) * size, Limit: size}, nil
|
||||
}
|
||||
func registrationResponse(r *business.Registration) *dto.RegistrationResponse {
|
||||
return &dto.RegistrationResponse{SourceDB: r.SourceDB, SourceDBLabel: business.PlatformName(r.SourceDB), ID: r.ID, UserID: r.ID, RegisterTime: r.RegisterTime, FirstGameID: r.FirstGameID, FirstGameName: r.FirstGameName, TotalPayAmount: r.TotalPayAmount, RegisterIP: r.RegisterIP, DeviceName: r.DeviceName, PromoteID: r.PromoteID, PromoteAccount: r.PromoteAccount, BusierName: r.DisplayBusiness()}
|
||||
}
|
||||
func spendResponse(r *business.Spend) *dto.SpendResponse {
|
||||
return &dto.SpendResponse{SourceDB: r.SourceDB, SourceDBLabel: business.PlatformName(r.SourceDB), ID: r.ID, UserID: r.UserID, OrderNumber: r.OrderNumber, GameID: r.GameID, GameName: r.GameName, ServerID: r.ServerID, ServerName: r.ServerName, PlayerName: r.PlayerName, PayAmount: r.PayAmount, PayTime: r.PayTime, PayWay: r.PayWay, PayStatus: r.PayStatus, IP: r.IP, PromoteID: r.PromoteID, PromoteAccount: r.PromoteAccount, BusierName: r.DisplayBusiness()}
|
||||
}
|
||||
func (s *LedgerService) Registrations(ctx context.Context, req *dto.LedgerQuery) (*dto.RegistrationPage, error) {
|
||||
f, o, err := ledgerRequest(req, false)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
page, err := s.uc.Registrations(ctx, f, o)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
out := &dto.RegistrationPage{Rows: []*dto.RegistrationResponse{}, Total: page.Total, Available: page.Available}
|
||||
for _, row := range page.Rows {
|
||||
out.Rows = append(out.Rows, registrationResponse(row))
|
||||
}
|
||||
return out, nil
|
||||
}
|
||||
func (s *LedgerService) Spends(ctx context.Context, req *dto.LedgerQuery) (*dto.SpendPage, error) {
|
||||
f, o, err := ledgerRequest(req, true)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
page, err := s.uc.Spends(ctx, f, o)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
out := &dto.SpendPage{Rows: []*dto.SpendResponse{}, Total: page.Total, TotalPayAmount: page.TotalPayAmount, Available: page.Available}
|
||||
for _, row := range page.Rows {
|
||||
out.Rows = append(out.Rows, spendResponse(row))
|
||||
}
|
||||
return out, nil
|
||||
}
|
||||
func (s *LedgerService) Games(ctx context.Context, req *dto.GameOptionQuery) ([]*dto.GameOptionResponse, error) {
|
||||
if req == nil {
|
||||
return nil, business.ErrLedgerInput
|
||||
}
|
||||
sources, err := normalizePlatforms(req.Sources)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
rows, err := s.uc.Games(ctx, sources, req.Keyword, req.Limit)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
result := make([]*dto.GameOptionResponse, 0, len(rows))
|
||||
for _, row := range rows {
|
||||
result = append(result, &dto.GameOptionResponse{SourceDB: row.SourceDB, SourceDBLabel: business.PlatformName(row.SourceDB), GameID: row.ID, GameName: row.Name})
|
||||
}
|
||||
return result, nil
|
||||
}
|
||||
@@ -0,0 +1,101 @@
|
||||
package business
|
||||
|
||||
import (
|
||||
"context"
|
||||
"strconv"
|
||||
|
||||
"github.com/xuri/excelize/v2"
|
||||
"kra/internal/service/dto"
|
||||
)
|
||||
|
||||
type ledgerCell struct {
|
||||
value string
|
||||
numeric bool
|
||||
}
|
||||
|
||||
func textCell(value string) ledgerCell { return ledgerCell{value: value} }
|
||||
func numericCell(value string) ledgerCell { return ledgerCell{value: value, numeric: true} }
|
||||
func optionalNumber[T ~int | ~int64](value *T) ledgerCell {
|
||||
if value == nil {
|
||||
return textCell("")
|
||||
}
|
||||
return numericCell(strconv.FormatInt(int64(*value), 10))
|
||||
}
|
||||
func optionalMoney(value *string) ledgerCell {
|
||||
if value == nil {
|
||||
return textCell("")
|
||||
}
|
||||
return numericCell(*value)
|
||||
}
|
||||
func ledgerWorkbook(sheet string, headers []string, rows [][]ledgerCell) ([]byte, error) {
|
||||
file := excelize.NewFile()
|
||||
defer file.Close()
|
||||
if err := file.SetSheetName(file.GetSheetName(0), sheet); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
for i, label := range headers {
|
||||
cell, _ := excelize.CoordinatesToCellName(i+1, 1)
|
||||
if err := file.SetCellStr(sheet, cell, label); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
for i, row := range rows {
|
||||
for j, value := range row {
|
||||
cell, _ := excelize.CoordinatesToCellName(j+1, i+2)
|
||||
var err error
|
||||
// 金额直接写入数值 XML,避免先转 float64;文本始终作为文本,保留订单号前导零。
|
||||
if value.numeric {
|
||||
err = file.SetCellDefault(sheet, cell, value.value)
|
||||
} else {
|
||||
err = file.SetCellStr(sheet, cell, value.value)
|
||||
}
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
}
|
||||
last, _ := excelize.ColumnNumberToName(len(headers))
|
||||
if err := file.SetColWidth(sheet, "A", last, 20); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if err := file.SetPanes(sheet, &excelize.Panes{Freeze: true, YSplit: 1, TopLeftCell: "A2", ActivePane: "bottomLeft"}); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
buffer, err := file.WriteToBuffer()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return buffer.Bytes(), nil
|
||||
}
|
||||
func (s *LedgerService) ExportRegistrations(ctx context.Context, req *dto.LedgerQuery) ([]byte, error) {
|
||||
f, _, err := ledgerRequest(req, false)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
items, err := s.uc.ExportRegistrations(ctx, f)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
rows := make([][]ledgerCell, 0, len(items))
|
||||
for _, item := range items {
|
||||
r := registrationResponse(item)
|
||||
rows = append(rows, []ledgerCell{textCell(r.SourceDB), textCell(r.SourceDBLabel), numericCell(strconv.FormatUint(r.ID, 10)), numericCell(strconv.FormatUint(r.UserID, 10)), optionalNumber(r.RegisterTime), optionalNumber(r.FirstGameID), textCell(r.FirstGameName), optionalMoney(r.TotalPayAmount), textCell(r.RegisterIP), textCell(r.DeviceName), textCell(r.PromoteAccount), optionalNumber(r.PromoteID), textCell(r.BusierName)})
|
||||
}
|
||||
return ledgerWorkbook("注册明细", []string{"sourceDb", "所属平台", "ID", "玩家ID", "注册时间", "fgameId", "游戏", "累计充值", "注册IP", "机型", "渠道", "promoteId", "所属商务"}, rows)
|
||||
}
|
||||
func (s *LedgerService) ExportSpends(ctx context.Context, req *dto.LedgerQuery) ([]byte, error) {
|
||||
f, _, err := ledgerRequest(req, true)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
items, err := s.uc.ExportSpends(ctx, f)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
rows := make([][]ledgerCell, 0, len(items))
|
||||
for _, item := range items {
|
||||
r := spendResponse(item)
|
||||
rows = append(rows, []ledgerCell{textCell(r.SourceDB), textCell(r.SourceDBLabel), numericCell(strconv.FormatUint(r.ID, 10)), optionalNumber(r.UserID), textCell(r.OrderNumber), optionalNumber(r.GameID), textCell(r.GameName), textCell(r.ServerID), textCell(r.ServerName), textCell(r.PlayerName), optionalMoney(r.PayAmount), optionalNumber(r.PayTime), optionalNumber(r.PayWay), optionalNumber(r.PayStatus), textCell(r.IP), textCell(r.PromoteAccount), optionalNumber(r.PromoteID), textCell(r.BusierName)})
|
||||
}
|
||||
return ledgerWorkbook("充值明细", []string{"sourceDb", "所属平台", "ID", "玩家ID", "订单号", "gameId", "游戏", "serverId", "区服", "角色", "实付金额", "完成时间", "方式", "状态", "IP", "渠道", "promoteId", "所属商务"}, rows)
|
||||
}
|
||||
@@ -0,0 +1,58 @@
|
||||
package business
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"context"
|
||||
"testing"
|
||||
|
||||
"github.com/xuri/excelize/v2"
|
||||
"kra/internal/biz/business"
|
||||
"kra/internal/service/dto"
|
||||
)
|
||||
|
||||
type ledgerExportFake struct{ LedgerUsecase }
|
||||
|
||||
func (ledgerExportFake) ExportRegistrations(context.Context, business.LedgerFilter) ([]*business.Registration, error) {
|
||||
amount := "9007199254740993.10"
|
||||
stamp := int64(1793577600)
|
||||
return []*business.Registration{{ChannelKey: business.ChannelKey{SourceDB: "lx", ID: 7}, RegisterTime: &stamp, FirstGameName: "游戏", TotalPayAmount: &amount, LedgerNames: business.LedgerNames{PromoteAccount: "=1+1"}}}, nil
|
||||
}
|
||||
func (ledgerExportFake) ExportSpends(context.Context, business.LedgerFilter) ([]*business.Spend, error) {
|
||||
amount := "123.45"
|
||||
return []*business.Spend{{ChannelKey: business.ChannelKey{SourceDB: "lx", ID: 8}, OrderNumber: "000000000000000001", PayAmount: &amount}}, nil
|
||||
}
|
||||
func TestLedgerWorkbookPreservesNumbersAndText(t *testing.T) {
|
||||
s := &LedgerService{uc: ledgerExportFake{}}
|
||||
data, err := s.ExportRegistrations(context.Background(), &dto.LedgerQuery{})
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
f, err := excelize.OpenReader(bytes.NewReader(data))
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
defer f.Close()
|
||||
amount, _ := f.GetCellValue("注册明细", "H2", excelize.Options{RawCellValue: true})
|
||||
formula, _ := f.GetCellFormula("注册明细", "K2")
|
||||
text, _ := f.GetCellValue("注册明细", "K2")
|
||||
if amount != "9007199254740993.10" || formula != "" || text != "=1+1" {
|
||||
t.Fatalf("导出丢失金额精度或把账号当成公式: %s %s %s", amount, formula, text)
|
||||
}
|
||||
rows, err := f.GetRows("注册明细")
|
||||
if err != nil || len(rows) != 2 || rows[0][0] != "sourceDb" || rows[0][12] != "所属商务" {
|
||||
t.Fatal("原导出字段与顺序丢失")
|
||||
}
|
||||
data, err = s.ExportSpends(context.Background(), &dto.LedgerQuery{})
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
pay, err := excelize.OpenReader(bytes.NewReader(data))
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
defer pay.Close()
|
||||
order, _ := pay.GetCellValue("充值明细", "E2")
|
||||
if order != "000000000000000001" {
|
||||
t.Fatal("订单号前导零丢失")
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,78 @@
|
||||
package dto
|
||||
|
||||
type LedgerQuery struct {
|
||||
Page int `form:"pageNum" json:"pageNum"`
|
||||
PageSize int `form:"pageSize" json:"pageSize"`
|
||||
Sources []string `form:"sourceDb" json:"sourceDb"`
|
||||
GameNames []string `form:"gameName" json:"gameName"`
|
||||
UserID *int64 `form:"userId" json:"userId"`
|
||||
PromoteAccount string `form:"promoteAccount" json:"promoteAccount"`
|
||||
BusierAccount string `form:"busierAccount" json:"busierAccount"`
|
||||
PlayerName string `form:"gamePlayerName" json:"gamePlayerName"`
|
||||
OrderNumber string `form:"payOrderNumber" json:"payOrderNumber"`
|
||||
PayStatus *int `form:"payStatus" json:"payStatus"`
|
||||
PayWay *int `form:"payWay" json:"payWay"`
|
||||
BeginRegisterTime string `form:"beginRegisterTime" json:"beginRegisterTime"`
|
||||
EndRegisterTime string `form:"endRegisterTime" json:"endRegisterTime"`
|
||||
BeginPayTime string `form:"beginPayTime" json:"beginPayTime"`
|
||||
EndPayTime string `form:"endPayTime" json:"endPayTime"`
|
||||
SortField string `form:"sortField" json:"sortField"`
|
||||
SortOrder string `form:"sortOrder" json:"sortOrder"`
|
||||
}
|
||||
type RegistrationResponse struct {
|
||||
SourceDB string `json:"sourceDb"`
|
||||
SourceDBLabel string `json:"sourceDbLabel"`
|
||||
ID uint64 `json:"id"`
|
||||
UserID uint64 `json:"userId"`
|
||||
RegisterTime *int64 `json:"registerTime"`
|
||||
FirstGameID *int64 `json:"fgameId"`
|
||||
FirstGameName string `json:"fgameName"`
|
||||
TotalPayAmount *string `json:"totalPayAmount"`
|
||||
RegisterIP string `json:"registerIp"`
|
||||
DeviceName string `json:"deviceName"`
|
||||
PromoteAccount string `json:"promoteAccount"`
|
||||
PromoteID *int64 `json:"promoteId"`
|
||||
BusierName string `json:"busierName"`
|
||||
}
|
||||
type SpendResponse struct {
|
||||
SourceDB string `json:"sourceDb"`
|
||||
SourceDBLabel string `json:"sourceDbLabel"`
|
||||
ID uint64 `json:"id"`
|
||||
UserID *int64 `json:"userId"`
|
||||
OrderNumber string `json:"payOrderNumber"`
|
||||
GameID *int64 `json:"gameId"`
|
||||
GameName string `json:"gameName"`
|
||||
ServerID string `json:"serverId"`
|
||||
ServerName string `json:"serverName"`
|
||||
PlayerName string `json:"gamePlayerName"`
|
||||
PayAmount *string `json:"payAmount"`
|
||||
PayTime *int64 `json:"payTime"`
|
||||
PayWay *int `json:"payWay"`
|
||||
PayStatus *int `json:"payStatus"`
|
||||
IP string `json:"spendIp"`
|
||||
PromoteAccount string `json:"promoteAccount"`
|
||||
PromoteID *int64 `json:"promoteId"`
|
||||
BusierName string `json:"busierName"`
|
||||
}
|
||||
type RegistrationPage struct {
|
||||
Rows []*RegistrationResponse `json:"rows"`
|
||||
Total int64 `json:"total"`
|
||||
Available bool `json:"available"`
|
||||
}
|
||||
type SpendPage struct {
|
||||
Rows []*SpendResponse `json:"rows"`
|
||||
Total int64 `json:"total"`
|
||||
TotalPayAmount *string `json:"totalPayAmount"`
|
||||
Available bool `json:"available"`
|
||||
}
|
||||
type GameOptionQuery struct {
|
||||
Sources []string `form:"sourceDb"`
|
||||
Keyword string `form:"keyword"`
|
||||
Limit int `form:"limit"`
|
||||
}
|
||||
type GameOptionResponse struct {
|
||||
SourceDB string `json:"sourceDb"`
|
||||
SourceDBLabel string `json:"sourceDbLabel"`
|
||||
GameID uint64 `json:"gameId"`
|
||||
GameName string `json:"gameName"`
|
||||
}
|
||||
@@ -23,6 +23,7 @@ var ProviderSet = wire.NewSet(
|
||||
businessservice.NewChannelService,
|
||||
businessservice.NewBusinessPortraitService,
|
||||
businessservice.NewChannelPortraitService,
|
||||
businessservice.NewLedgerService,
|
||||
systemservice.ProviderSet,
|
||||
paymentservice.ProviderSet,
|
||||
integrationservice.ProviderSet,
|
||||
|
||||
Reference in New Issue
Block a user