This commit is contained in:
2025-09-12 01:15:09 +08:00
parent c563b2266b
commit e05ad9e223
103 changed files with 20034 additions and 1041 deletions

View File

@@ -13,6 +13,7 @@ import (
finance_services "tyapi-server/internal/domains/finance/services"
user_repositories "tyapi-server/internal/domains/user/repositories"
"tyapi-server/internal/shared/database"
"tyapi-server/internal/shared/export"
"tyapi-server/internal/shared/interfaces"
"tyapi-server/internal/shared/payment"
@@ -30,6 +31,7 @@ type FinanceApplicationServiceImpl struct {
alipayOrderRepo finance_repositories.AlipayOrderRepository
userRepo user_repositories.UserRepository
txManager *database.TransactionManager
exportManager *export.ExportManager
logger *zap.Logger
config *config.Config
}
@@ -45,6 +47,7 @@ func NewFinanceApplicationService(
txManager *database.TransactionManager,
logger *zap.Logger,
config *config.Config,
exportManager *export.ExportManager,
) FinanceApplicationService {
return &FinanceApplicationServiceImpl{
aliPayClient: aliPayClient,
@@ -54,6 +57,7 @@ func NewFinanceApplicationService(
alipayOrderRepo: alipayOrderRepo,
userRepo: userRepo,
txManager: txManager,
exportManager: exportManager,
logger: logger,
config: config,
}
@@ -344,6 +348,290 @@ func (s *FinanceApplicationServiceImpl) GetAdminWalletTransactions(ctx context.C
}, nil
}
// ExportAdminWalletTransactions 导出管理端钱包交易记录
func (s *FinanceApplicationServiceImpl) ExportAdminWalletTransactions(ctx context.Context, filters map[string]interface{}, format string) ([]byte, error) {
const batchSize = 1000 // 每批处理1000条记录
var allTransactions []*finance_entities.WalletTransaction
var productNameMap map[string]string
// 分批获取数据
page := 1
for {
// 查询当前批次的数据
batchProductNameMap, transactions, _, err := s.walletTransactionRepository.ListWithFiltersAndProductName(ctx, filters, interfaces.ListOptions{
Page: page,
PageSize: batchSize,
Sort: "created_at",
Order: "desc",
})
if err != nil {
s.logger.Error("查询导出钱包交易记录失败", zap.Error(err))
return nil, err
}
// 合并产品名称映射
if productNameMap == nil {
productNameMap = batchProductNameMap
} else {
for k, v := range batchProductNameMap {
productNameMap[k] = v
}
}
// 添加到总数据中
allTransactions = append(allTransactions, transactions...)
// 如果当前批次数据少于批次大小,说明已经是最后一批
if len(transactions) < batchSize {
break
}
page++
}
// 检查是否有数据
if len(allTransactions) == 0 {
return nil, fmt.Errorf("没有找到符合条件的数据")
}
// 批量获取企业名称映射避免N+1查询问题
companyNameMap, err := s.batchGetCompanyNames(ctx, allTransactions)
if err != nil {
companyNameMap = make(map[string]string)
}
// 准备导出数据
headers := []string{"交易ID", "企业名称", "产品名称", "消费金额", "消费时间"}
columnWidths := []float64{20, 25, 20, 15, 20}
data := make([][]interface{}, len(allTransactions))
for i, transaction := range allTransactions {
companyName := companyNameMap[transaction.UserID]
if companyName == "" {
companyName = "未知企业"
}
productName := productNameMap[transaction.ProductID]
if productName == "" {
productName = "未知产品"
}
data[i] = []interface{}{
transaction.TransactionID,
companyName,
productName,
transaction.Amount.String(),
transaction.CreatedAt.Format("2006-01-02 15:04:05"),
}
}
// 创建导出配置
config := &export.ExportConfig{
SheetName: "消费记录",
Headers: headers,
Data: data,
ColumnWidths: columnWidths,
}
// 使用导出管理器生成文件
return s.exportManager.Export(ctx, config, format)
}
// batchGetCompanyNames 批量获取企业名称映射
func (s *FinanceApplicationServiceImpl) batchGetCompanyNames(ctx context.Context, transactions []*finance_entities.WalletTransaction) (map[string]string, error) {
// 收集所有唯一的用户ID
userIDSet := make(map[string]bool)
for _, transaction := range transactions {
userIDSet[transaction.UserID] = true
}
// 转换为切片
userIDs := make([]string, 0, len(userIDSet))
for userID := range userIDSet {
userIDs = append(userIDs, userID)
}
// 批量查询用户信息
users, err := s.userRepo.BatchGetByIDsWithEnterpriseInfo(ctx, userIDs)
if err != nil {
return nil, err
}
// 构建企业名称映射
companyNameMap := make(map[string]string)
for _, user := range users {
companyName := "未知企业"
if user.EnterpriseInfo != nil {
companyName = user.EnterpriseInfo.CompanyName
}
companyNameMap[user.ID] = companyName
}
return companyNameMap, nil
}
// ExportAdminRechargeRecords 导出管理端充值记录
func (s *FinanceApplicationServiceImpl) ExportAdminRechargeRecords(ctx context.Context, filters map[string]interface{}, format string) ([]byte, error) {
const batchSize = 1000 // 每批处理1000条记录
var allRecords []finance_entities.RechargeRecord
// 分批获取数据
page := 1
for {
// 查询当前批次的数据
records, err := s.rechargeRecordService.GetAll(ctx, filters, interfaces.ListOptions{
Page: page,
PageSize: batchSize,
Sort: "created_at",
Order: "desc",
})
if err != nil {
s.logger.Error("查询导出充值记录失败", zap.Error(err))
return nil, err
}
// 添加到总数据中
allRecords = append(allRecords, records...)
// 如果当前批次数据少于批次大小,说明已经是最后一批
if len(records) < batchSize {
break
}
page++
}
// 批量获取企业名称映射避免N+1查询问题
companyNameMap, err := s.batchGetCompanyNamesForRechargeRecords(ctx, convertToRechargeRecordPointers(allRecords))
if err != nil {
s.logger.Warn("批量获取企业名称失败,使用默认值", zap.Error(err))
companyNameMap = make(map[string]string)
}
// 准备导出数据
headers := []string{"企业名称", "充值金额", "充值类型", "状态", "支付宝订单号", "转账订单号", "备注", "充值时间"}
columnWidths := []float64{25, 15, 15, 10, 20, 20, 20, 20}
data := make([][]interface{}, len(allRecords))
for i, record := range allRecords {
// 从映射中获取企业名称
companyName := companyNameMap[record.UserID]
if companyName == "" {
companyName = "未知企业"
}
// 获取订单号
alipayOrderID := ""
if record.AlipayOrderID != nil && *record.AlipayOrderID != "" {
alipayOrderID = *record.AlipayOrderID
}
transferOrderID := ""
if record.TransferOrderID != nil && *record.TransferOrderID != "" {
transferOrderID = *record.TransferOrderID
}
// 获取备注
notes := ""
if record.Notes != "" {
notes = record.Notes
}
// 格式化时间
createdAt := record.CreatedAt.Format("2006-01-02 15:04:05")
data[i] = []interface{}{
companyName,
record.Amount.String(),
translateRechargeType(record.RechargeType),
translateRechargeStatus(record.Status),
alipayOrderID,
transferOrderID,
notes,
createdAt,
}
}
// 创建导出配置
config := &export.ExportConfig{
SheetName: "充值记录",
Headers: headers,
Data: data,
ColumnWidths: columnWidths,
}
// 使用导出管理器生成文件
return s.exportManager.Export(ctx, config, format)
}
// translateRechargeType 翻译充值类型为中文
func translateRechargeType(rechargeType finance_entities.RechargeType) string {
switch rechargeType {
case finance_entities.RechargeTypeAlipay:
return "支付宝充值"
case finance_entities.RechargeTypeTransfer:
return "对公转账"
case finance_entities.RechargeTypeGift:
return "赠送"
default:
return "未知类型"
}
}
// translateRechargeStatus 翻译充值状态为中文
func translateRechargeStatus(status finance_entities.RechargeStatus) string {
switch status {
case finance_entities.RechargeStatusPending:
return "待处理"
case finance_entities.RechargeStatusSuccess:
return "成功"
case finance_entities.RechargeStatusFailed:
return "失败"
case finance_entities.RechargeStatusCancelled:
return "已取消"
default:
return "未知状态"
}
}
// convertToRechargeRecordPointers 将RechargeRecord切片转换为指针切片
func convertToRechargeRecordPointers(records []finance_entities.RechargeRecord) []*finance_entities.RechargeRecord {
pointers := make([]*finance_entities.RechargeRecord, len(records))
for i := range records {
pointers[i] = &records[i]
}
return pointers
}
// batchGetCompanyNamesForRechargeRecords 批量获取企业名称映射(用于充值记录)
func (s *FinanceApplicationServiceImpl) batchGetCompanyNamesForRechargeRecords(ctx context.Context, records []*finance_entities.RechargeRecord) (map[string]string, error) {
// 收集所有唯一的用户ID
userIDSet := make(map[string]bool)
for _, record := range records {
userIDSet[record.UserID] = true
}
// 转换为切片
userIDs := make([]string, 0, len(userIDSet))
for userID := range userIDSet {
userIDs = append(userIDs, userID)
}
// 批量查询用户信息
users, err := s.userRepo.BatchGetByIDsWithEnterpriseInfo(ctx, userIDs)
if err != nil {
return nil, err
}
// 构建企业名称映射
companyNameMap := make(map[string]string)
for _, user := range users {
companyName := "未知企业"
if user.EnterpriseInfo != nil {
companyName = user.EnterpriseInfo.CompanyName
}
companyNameMap[user.ID] = companyName
}
return companyNameMap, nil
}
// HandleAlipayCallback 处理支付宝回调
func (s *FinanceApplicationServiceImpl) HandleAlipayCallback(ctx context.Context, r *http.Request) error {
@@ -402,7 +690,7 @@ func (s *FinanceApplicationServiceImpl) processAlipayPaymentSuccess(ctx context.
// 该服务内部会处理所有必要的检查、事务和更新操作
err = s.rechargeRecordService.HandleAlipayPaymentSuccess(ctx, outTradeNo, amount, tradeNo)
if err != nil {
s.logger.Error("处理支付宝支付成功失败",
s.logger.Error("处理支付宝支付成功失败",
zap.String("out_trade_no", outTradeNo),
zap.Error(err),
)
@@ -665,14 +953,14 @@ func (s *FinanceApplicationServiceImpl) GetAdminRechargeRecords(ctx context.Cont
var items []responses.RechargeRecordResponse
for _, record := range records {
item := responses.RechargeRecordResponse{
ID: record.ID,
UserID: record.UserID,
Amount: record.Amount,
RechargeType: string(record.RechargeType),
Status: string(record.Status),
Notes: record.Notes,
CreatedAt: record.CreatedAt,
UpdatedAt: record.UpdatedAt,
ID: record.ID,
UserID: record.UserID,
Amount: record.Amount,
RechargeType: string(record.RechargeType),
Status: string(record.Status),
Notes: record.Notes,
CreatedAt: record.CreatedAt,
UpdatedAt: record.UpdatedAt,
}
// 根据充值类型设置相应的订单号
@@ -719,8 +1007,8 @@ func (s *FinanceApplicationServiceImpl) GetRechargeConfig(ctx context.Context) (
})
}
return &responses.RechargeConfigResponse{
MinAmount: s.config.Wallet.MinAmount,
MaxAmount: s.config.Wallet.MaxAmount,
MinAmount: s.config.Wallet.MinAmount,
MaxAmount: s.config.Wallet.MaxAmount,
AlipayRechargeBonus: bonus,
}, nil
}