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 optionalID(value *int64) ledgerCell { if value == nil { return textCell("") } return textCell(strconv.FormatInt(*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)) // 流式读取器依赖 dimension;Excelize 的初始 A1 范围不会随写入单元格自动扩展。 if err := file.SetSheetDimension(sheet, "A1:"+last+strconv.Itoa(len(rows)+1)); err != nil { return nil, err } 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), textCell(strconv.FormatUint(r.ID, 10)), textCell(strconv.FormatUint(r.UserID, 10)), optionalNumber(r.RegisterTime), optionalID(r.FirstGameID), textCell(r.FirstGameName), optionalMoney(r.TotalPayAmount), textCell(r.RegisterIP), textCell(r.DeviceName), textCell(r.PromoteAccount), optionalID(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), textCell(strconv.FormatUint(r.ID, 10)), optionalID(r.UserID), textCell(r.OrderNumber), optionalID(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), optionalID(r.PromoteID), textCell(r.BusierName)}) } return ledgerWorkbook("充值明细", []string{"sourceDb", "所属平台", "ID", "玩家ID", "订单号", "gameId", "游戏", "serverId", "区服", "角色", "实付金额", "完成时间", "方式", "状态", "IP", "渠道", "promoteId", "所属商务"}, rows) }