Files
kami_backend/internal/logic/camel_oil/order_query.go
danial 85b552eec3 feat(camel_oil): add order export to Excel functionality
- Add ExportOrder RPC method to camel_oil API and service interfaces
- Implement service logic to query orders and generate Excel file with order data
- Include card number and password fields in order export
- Create HTTP handler to stream Excel file with proper headers for download
- Handle token status update on frequent error ban (oneDay case)
- Fix order processing query to filter by status and pay status correctly
- Add new error code for one-day ban in camel_oil_api and handle in client logic
- Update order model and response to include card number and password
- Remove redundant logging of SendCaptcha request data in camel_oil_api client
- Add access control checks on ExportOrder endpoint for authorized users only
2025-12-11 20:13:52 +08:00

390 lines
12 KiB
Go

package camel_oil
import (
"context"
"fmt"
"github.com/gogf/gf/v2/errors/gerror"
"github.com/gogf/gf/v2/os/glog"
"github.com/xuri/excelize/v2"
"github.com/gogf/gf/v2/os/gtime"
v1 "kami/api/camel_oil/v1"
"kami/internal/consts"
"kami/internal/dao"
"kami/internal/model/entity"
"kami/utility/config"
)
// ====================================================================================
// 订单查询相关方法
// ====================================================================================
// ListOrder 查询订单列表
func (s *sCamelOil) ListOrder(ctx context.Context, req *v1.ListOrderReq) (res *v1.ListOrderRes, err error) {
m := dao.V1CamelOilOrder.Ctx(ctx).DB(config.GetDatabaseV1())
// 构建查询条件
if req.MerchantOrderId != "" {
m = m.Where(dao.V1CamelOilOrder.Columns().MerchantOrderId, req.MerchantOrderId)
}
if req.OrderNo != "" {
m = m.Where(dao.V1CamelOilOrder.Columns().OrderNo, req.OrderNo)
}
if req.AccountId != 0 {
m = m.Where(dao.V1CamelOilOrder.Columns().AccountId, req.AccountId)
}
if req.Status > 0 {
m = m.Where(dao.V1CamelOilOrder.Columns().Status, int(req.Status))
}
if req.PayStatus > 0 {
m = m.Where(dao.V1CamelOilOrder.Columns().PayStatus, int(req.PayStatus))
}
if len(req.DateRange) == 2 && req.DateRange[0] != nil && req.DateRange[1] != nil {
m = m.WhereBetween(dao.V1CamelOilOrder.Columns().CreatedAt, req.DateRange[0], req.DateRange[1])
}
// 查询总数
total, err := m.Count()
if err != nil {
return nil, gerror.Wrap(err, "查询订单总数失败")
}
// 分页查询
var orders []*entity.V1CamelOilOrder
err = m.Page(req.Current, req.PageSize).
OrderDesc(dao.V1CamelOilOrder.Columns().CreatedAt).
Scan(&orders)
if err != nil {
return nil, gerror.Wrap(err, "查询订单列表失败")
}
// 构造返回结果
items := make([]v1.OrderListItem, 0, len(orders))
for _, order := range orders {
items = append(items, v1.OrderListItem{
OrderNo: order.OrderNo,
MerchantOrderId: order.MerchantOrderId,
AccountId: order.AccountId,
AccountName: order.AccountName,
Amount: order.Amount.InexactFloat64(),
AlipayUrl: order.AlipayUrl,
CardNumber: order.CardNumber,
CardPassword: order.CardPassword,
Status: consts.CamelOilOrderStatus(order.Status),
PayStatus: consts.CamelOilPayStatus(order.PayStatus),
NotifyStatus: consts.CamelOilNotifyStatus(order.NotifyStatus),
NotifyCount: order.NotifyCount,
PaidAt: order.PaidAt,
LastCheckAt: order.LastCheckAt,
FailureReason: order.FailureReason,
CreatedAt: order.CreatedAt,
UpdatedAt: order.UpdatedAt,
})
}
res = &v1.ListOrderRes{}
res.Total = total
res.List = items
return res, nil
}
// OrderDetail 查询订单详情
func (s *sCamelOil) OrderDetail(ctx context.Context, req *v1.OrderDetailReq) (res *v1.OrderDetailRes, err error) {
// 查询订单信息
var order *entity.V1CamelOilOrder
err = dao.V1CamelOilOrder.Ctx(ctx).DB(config.GetDatabaseV1()).
Where(dao.V1CamelOilOrder.Columns().OrderNo, req.OrderNo).
Scan(&order)
if err != nil {
return nil, gerror.Wrap(err, "查询订单失败")
}
if order == nil {
return nil, gerror.New("订单不存在")
}
// 查询账号信息
var account *entity.V1CamelOilAccount
if order.AccountId > 0 {
err = dao.V1CamelOilAccount.Ctx(ctx).DB(config.GetDatabaseV1()).
Where(dao.V1CamelOilAccount.Columns().Id, order.AccountId).
Scan(&account)
if err != nil {
return nil, gerror.Wrap(err, "查询账号失败")
}
}
res = &v1.OrderDetailRes{}
// 填充订单信息
res.OrderInfo.OrderNo = order.OrderNo
res.OrderInfo.MerchantOrderId = order.MerchantOrderId
res.OrderInfo.AccountId = order.AccountId
res.OrderInfo.AccountName = order.AccountName
res.OrderInfo.Amount = order.Amount.InexactFloat64()
res.OrderInfo.AlipayUrl = order.AlipayUrl
res.OrderInfo.Status = consts.CamelOilOrderStatus(order.Status)
res.OrderInfo.PayStatus = consts.CamelOilPayStatus(order.PayStatus)
res.OrderInfo.NotifyStatus = consts.CamelOilNotifyStatus(order.NotifyStatus)
res.OrderInfo.NotifyCount = order.NotifyCount
res.OrderInfo.PaidAt = order.PaidAt
res.OrderInfo.LastCheckAt = order.LastCheckAt
res.OrderInfo.Attach = order.Attach
res.OrderInfo.FailureReason = order.FailureReason
res.OrderInfo.CreatedAt = order.CreatedAt
res.OrderInfo.UpdatedAt = order.UpdatedAt
// 填充账号信息
if account != nil {
res.AccountInfo.AccountId = account.Id
res.AccountInfo.AccountName = account.AccountName
res.AccountInfo.Phone = maskPhone(account.Phone)
res.AccountInfo.Status = consts.CamelOilAccountStatus(account.Status)
res.AccountInfo.LastUsedAt = account.LastUsedAt
}
return res, nil
}
// ListPrefetchOrder 查询预拉取订单列表
func (s *sCamelOil) ListPrefetchOrder(ctx context.Context, req *v1.ListPrefetchOrderReq) (res *v1.ListPrefetchOrderRes, err error) {
m := dao.V1CamelOilPrefetchOrder.Ctx(ctx).DB(config.GetDatabaseV1())
// 构建查询条件
if req.AccountId != 0 {
m = m.Where(dao.V1CamelOilPrefetchOrder.Columns().AccountId, req.AccountId)
}
if req.AccountName != "" {
m = m.WhereLike(dao.V1CamelOilPrefetchOrder.Columns().AccountName, "%"+req.AccountName+"%")
}
if req.Amount > 0 {
m = m.Where(dao.V1CamelOilPrefetchOrder.Columns().Amount, req.Amount)
}
if req.PlatformOrderNo != "" {
m = m.Where(dao.V1CamelOilPrefetchOrder.Columns().PlatformOrderNo, req.PlatformOrderNo)
}
if req.Status > 0 {
m = m.Where(dao.V1CamelOilPrefetchOrder.Columns().Status, int(req.Status))
}
if len(req.DateRange) == 2 && req.DateRange[0] != nil && req.DateRange[1] != nil {
m = m.WhereBetween(dao.V1CamelOilPrefetchOrder.Columns().CreatedAt, req.DateRange[0], req.DateRange[1])
}
// 排除已删除的记录
m = m.WhereNull(dao.V1CamelOilPrefetchOrder.Columns().DeletedAt)
// 查询总数
total, err := m.Count()
if err != nil {
return nil, gerror.Wrap(err, "查询预拉取订单总数失败")
}
// 分页查询
var orders []*entity.V1CamelOilPrefetchOrder
err = m.Page(req.Current, req.PageSize).
OrderDesc(dao.V1CamelOilPrefetchOrder.Columns().CreatedAt).
Scan(&orders)
if err != nil {
return nil, gerror.Wrap(err, "查询预拉取订单列表失败")
}
// 构造返回结果
items := make([]v1.PrefetchOrderListItem, 0, len(orders))
for _, order := range orders {
items = append(items, v1.PrefetchOrderListItem{
Id: order.Id,
AccountId: order.AccountId,
AccountName: order.AccountName,
Amount: order.Amount.InexactFloat64(),
PlatformOrderNo: order.PlatformOrderNo,
AlipayUrl: order.AlipayUrl,
Status: consts.CamelOilPrefetchOrderStatus(order.Status),
OrderNo: order.OrderNo,
MatchedAt: order.MatchedAt,
ExpireAt: order.ExpireAt,
FailureReason: order.FailureReason,
Remark: order.Remark,
CreatedAt: order.CreatedAt,
UpdatedAt: order.UpdatedAt,
})
}
res = &v1.ListPrefetchOrderRes{}
res.Total = total
res.List = items
return res, nil
}
// ====================================================================================
// 辅助函数
// ====================================================================================
// ExportOrder 导出订单数据为Excel
func (s *sCamelOil) ExportOrder(ctx context.Context, req *v1.ExportOrderReq) (fileName string, content []byte, err error) {
// 构建查询条件
m := dao.V1CamelOilOrder.Ctx(ctx).DB(config.GetDatabaseV1())
if req.MerchantOrderId != "" {
m = m.Where(dao.V1CamelOilOrder.Columns().MerchantOrderId, req.MerchantOrderId)
}
if req.OrderNo != "" {
m = m.Where(dao.V1CamelOilOrder.Columns().OrderNo, req.OrderNo)
}
if req.AccountId != 0 {
m = m.Where(dao.V1CamelOilOrder.Columns().AccountId, req.AccountId)
}
if req.Status > 0 {
m = m.Where(dao.V1CamelOilOrder.Columns().Status, int(req.Status))
}
if req.PayStatus > 0 {
m = m.Where(dao.V1CamelOilOrder.Columns().PayStatus, int(req.PayStatus))
}
if len(req.DateRange) == 2 && req.DateRange[0] != nil && req.DateRange[1] != nil {
m = m.WhereBetween(dao.V1CamelOilOrder.Columns().CreatedAt, req.DateRange[0], req.DateRange[1])
}
// 查询所有符合条件的订单
var orders []*entity.V1CamelOilOrder
err = m.OrderDesc(dao.V1CamelOilOrder.Columns().CreatedAt).Scan(&orders)
if err != nil {
return "", nil, gerror.Wrap(err, "查询订单列表失败")
}
// 收集所有账号ID
accountIds := make([]int64, 0, len(orders))
accountMap := make(map[int64]*entity.V1CamelOilAccount)
for _, order := range orders {
if order.AccountId > 0 {
accountIds = append(accountIds, order.AccountId)
}
}
// 批量查询账号信息
if len(accountIds) > 0 {
var accounts []*entity.V1CamelOilAccount
err = dao.V1CamelOilAccount.Ctx(ctx).DB(config.GetDatabaseV1()).
WhereIn(dao.V1CamelOilAccount.Columns().Id, accountIds).
Scan(&accounts)
if err == nil {
for _, account := range accounts {
accountMap[account.Id] = account
}
}
}
// 创建Excel文件
f := excelize.NewFile()
defer func() {
if err := f.Close(); err != nil {
glog.Error(ctx, "关闭Excel文件失败", err)
}
}()
// 设置工作表名称
sheetName := "骆驼油订单数据"
f.SetSheetName("Sheet1", sheetName)
// 设置表头
headers := []string{"订单ID", "系统订单号", "商户订单号", "手机号", "卡号", "卡密", "订单金额", "订单状态", "支付状态", "创建时间"}
for i, header := range headers {
cell := fmt.Sprintf("%s1", string(rune('A'+i)))
f.SetCellValue(sheetName, cell, header)
}
// 填充数据
for i, order := range orders {
row := i + 2
// 订单ID
f.SetCellValue(sheetName, fmt.Sprintf("A%d", row), order.Id)
// 系统订单号
f.SetCellValue(sheetName, fmt.Sprintf("B%d", row), order.OrderNo)
// 商户订单号
f.SetCellValue(sheetName, fmt.Sprintf("C%d", row), order.MerchantOrderId)
// 手机号
phone := ""
if account, exists := accountMap[order.AccountId]; exists && account != nil {
phone = account.Phone
}
f.SetCellValue(sheetName, fmt.Sprintf("D%d", row), phone)
// 卡号
f.SetCellValue(sheetName, fmt.Sprintf("E%d", row), order.CardNumber)
// 卡密
f.SetCellValue(sheetName, fmt.Sprintf("F%d", row), order.CardPassword)
// 订单金额
f.SetCellValue(sheetName, fmt.Sprintf("G%d", row), order.Amount.InexactFloat64())
// 订单状态
statusText := s.getOrderStatusText(consts.CamelOilOrderStatus(order.Status))
f.SetCellValue(sheetName, fmt.Sprintf("H%d", row), statusText)
// 支付状态
payStatusText := s.getPayStatusText(consts.CamelOilPayStatus(order.PayStatus))
f.SetCellValue(sheetName, fmt.Sprintf("I%d", row), payStatusText)
// 创建时间
if order.CreatedAt != nil {
f.SetCellValue(sheetName, fmt.Sprintf("J%d", row), order.CreatedAt.Format("Y-m-d H:i:s"))
}
}
// 生成文件名
fileName = fmt.Sprintf("骆驼油订单数据_%s.xlsx", gtime.Now().Format("Y-m-d_H-i-s"))
// 保存到字节数组
buf, err := f.WriteToBuffer()
if err != nil {
return "", nil, gerror.Wrap(err, "生成Excel文件失败")
}
return fileName, buf.Bytes(), nil
}
// getOrderStatusText 获取订单状态文本
func (s *sCamelOil) getOrderStatusText(status consts.CamelOilOrderStatus) string {
switch status {
case consts.CamelOilOrderStatusPending:
return "待处理"
case consts.CamelOilOrderStatusProcessing:
return "处理中"
case consts.CamelOilOrderStatusCompleted:
return "已完成"
case consts.CamelOilOrderStatusFailed:
return "已失败"
default:
return "未知"
}
}
// getPayStatusText 获取支付状态文本
func (s *sCamelOil) getPayStatusText(status consts.CamelOilPayStatus) string {
switch status {
case consts.CamelOilPaymentStatusUnpaid:
return "未支付"
case consts.CamelOilPaymentStatusPaid:
return "已支付"
case consts.CamelOilPaymentStatusRefunded:
return "已退款"
case consts.CamelOilPaymentStatusTimeout:
return "已超时"
default:
return "未知"
}
}
// maskPhone 手机号脱敏
func maskPhone(phone string) string {
if len(phone) < 11 {
return phone
}
return phone[:3] + "****" + phone[7:]
}