duidui_fiber/internal/order/dao/order_dao.go
2026-03-27 10:34:03 +08:00

622 lines
18 KiB
Go
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

package dao
import (
"database/sql"
"fmt"
"time"
"dd_fiber_api/internal/order"
"dd_fiber_api/pkg/database"
"dd_fiber_api/pkg/utils"
"github.com/didi/gendry/builder"
)
// OrderDAO 订单数据访问对象
type OrderDAO struct {
client *database.MySQLClient
}
// NewOrderDAO 创建订单DAO实例
func NewOrderDAO(client *database.MySQLClient) *OrderDAO {
return &OrderDAO{client: client}
}
// CreateOrder 创建订单(使用新的 orders 表)
func (d *OrderDAO) CreateOrder(
orderID, userID string,
orderType order.OrderType,
originalAmount, discountAmount, actualAmount int32,
couponID string,
status order.OrderStatus,
paymentMethod order.PaymentMethod,
paymentTime *time.Time,
) error {
table := "orders"
data := []map[string]any{{
"order_id": orderID,
"user_id": userID,
"order_type": string(orderType),
"original_amount": originalAmount,
"discount_amount": discountAmount,
"actual_amount": actualAmount,
"coupon_id": couponID,
"status": string(status),
}}
// 处理支付方式如果是空字符串PaymentMethodUnknown使用 NULL
if paymentMethod == order.PaymentMethodUnknown || paymentMethod == "" {
data[0]["payment_method"] = nil
} else {
data[0]["payment_method"] = string(paymentMethod)
}
// 如果提供了支付时间0元订单直接完成添加到数据中
if paymentTime != nil {
data[0]["payment_time"] = *paymentTime
}
cond, vals, err := builder.BuildInsert(table, data)
if err != nil {
return fmt.Errorf("构建插入失败: %v", err)
}
// 添加幂等性处理如果订单ID已存在不更新
cond += " ON DUPLICATE KEY UPDATE order_id=order_id"
if _, err := d.client.DB.Exec(cond, vals...); err != nil {
return fmt.Errorf("创建订单失败: %v", err)
}
return nil
}
// CreateOrderBusinessData 创建订单业务数据(关联订单和小节)
func (d *OrderDAO) CreateOrderBusinessData(orderID, campID, sectionID string) error {
table := "order_business_data"
data := []map[string]any{{
"order_id": orderID,
"camp_id": campID,
"section_id": sectionID,
}}
cond, vals, err := builder.BuildInsert(table, data)
if err != nil {
return fmt.Errorf("构建插入失败: %v", err)
}
// 添加幂等性处理
cond += " ON DUPLICATE KEY UPDATE order_id=order_id"
if _, err := d.client.DB.Exec(cond, vals...); err != nil {
// 如果表不存在,忽略错误(向后兼容)
return nil
}
return nil
}
// CheckUserHasSection 检查用户是否已拥有某个小节(通过查询已支付的订单)
func (d *OrderDAO) CheckUserHasSection(userID, sectionID string) (bool, error) {
// 先尝试查询 order_business_data 表
table := "order_business_data"
where := map[string]any{
"section_id": sectionID,
}
// 查询该小节的所有订单ID
cond, vals, err := builder.BuildSelect(table, where, []string{"order_id"})
if err != nil {
// 如果表不存在,返回 false向后兼容
return false, nil
}
rows, err := d.client.DB.Query(cond, vals...)
if err != nil {
// 如果表不存在,返回 false向后兼容
return false, nil
}
defer rows.Close()
var orderIDs []string
for rows.Next() {
var orderID string
if err := rows.Scan(&orderID); err != nil {
continue
}
orderIDs = append(orderIDs, orderID)
}
if len(orderIDs) == 0 {
return false, nil
}
// 查询这些订单中是否有该用户的已支付订单
ordersTable := "orders"
ordersWhere := map[string]any{
"user_id": userID,
"status": string(order.OrderStatusPaid),
}
// 构建 IN 查询
ordersCond, ordersVals, err := builder.BuildSelect(ordersTable, ordersWhere, []string{"COUNT(*) as count"})
if err != nil {
return false, nil
}
// 添加 order_id IN (...) 条件
if len(orderIDs) > 0 {
placeholders := ""
for i, id := range orderIDs {
if i > 0 {
placeholders += ","
}
placeholders += "?"
ordersVals = append(ordersVals, id)
}
ordersCond += " AND order_id IN (" + placeholders + ")"
}
var count int
if err := d.client.DB.QueryRow(ordersCond, ordersVals...).Scan(&count); err != nil {
return false, nil
}
return count > 0, nil
}
// GetUserSectionAccessTime 获取用户某小节的「开启」时间(上一节开启时的起点:已支付订单的 payment_time若无则 created_at
func (d *OrderDAO) GetUserSectionAccessTime(userID, sectionID string) (*time.Time, error) {
table := "order_business_data"
where := map[string]any{"section_id": sectionID}
cond, vals, err := builder.BuildSelect(table, where, []string{"order_id"})
if err != nil {
return nil, nil
}
rows, err := d.client.DB.Query(cond, vals...)
if err != nil {
return nil, nil
}
defer rows.Close()
var orderIDs []string
for rows.Next() {
var orderID string
if err := rows.Scan(&orderID); err != nil {
continue
}
orderIDs = append(orderIDs, orderID)
}
if len(orderIDs) == 0 {
return nil, nil
}
placeholders := ""
for i := range orderIDs {
if i > 0 {
placeholders += ","
}
placeholders += "?"
}
args := make([]any, 0, len(orderIDs)+2)
args = append(args, userID, string(order.OrderStatusPaid))
for _, id := range orderIDs {
args = append(args, id)
}
q := "SELECT COALESCE(payment_time, created_at) FROM orders WHERE user_id=? AND status=? AND order_id IN (" + placeholders + ") ORDER BY COALESCE(payment_time, created_at) DESC LIMIT 1"
var accessAt sql.NullTime
err = d.client.DB.QueryRow(q, args...).Scan(&accessAt)
if err != nil || !accessAt.Valid {
return nil, nil
}
t := accessAt.Time
return &t, nil
}
// GetOrderByID 根据订单ID查询订单
func (d *OrderDAO) GetOrderByID(orderID string) (*order.Order, error) {
return d.getOrderByCondition(map[string]any{"order_id": orderID})
}
// GetOrderByOrderNo 根据订单号查询订单(兼容方法,新表使用 order_id
func (d *OrderDAO) GetOrderByOrderNo(orderNo string) (*order.Order, error) {
return d.getOrderByCondition(map[string]any{"order_id": orderNo})
}
// getOrderByCondition 根据条件查询订单(内部方法,使用新的 orders 表)
// 使用 Go 代码实现业务数据关联,避免 JOIN 查询
func (d *OrderDAO) getOrderByCondition(where map[string]any) (*order.Order, error) {
table := "orders"
// 先查询订单表(不使用 JOIN
selectFields := []string{
"order_id", "user_id", "order_type", "original_amount", "discount_amount",
"actual_amount", "coupon_id", "status", "payment_method", "transaction_id",
"payment_time", "created_at", "updated_at",
}
cond, vals, err := builder.BuildSelect(table, where, selectFields)
if err != nil {
return nil, fmt.Errorf("构建查询失败: %v", err)
}
cond += " LIMIT 1"
var (
orderID, userID, orderTypeStr, couponID, statusStr, paymentMethodStr, transactionID sql.NullString
originalAmount, discountAmount, actualAmount int32
createdAt, updatedAt, paymentTime sql.NullTime
)
err = d.client.DB.QueryRow(cond, vals...).Scan(
&orderID, &userID, &orderTypeStr, &originalAmount, &discountAmount,
&actualAmount, &couponID, &statusStr, &paymentMethodStr, &transactionID,
&paymentTime, &createdAt, &updatedAt,
)
if err != nil {
if err == sql.ErrNoRows {
return nil, nil // 订单不存在
}
return nil, fmt.Errorf("查询订单失败: %v", err)
}
result := &order.Order{
OrderID: orderID.String,
UserID: userID.String,
OrderType: order.OrderType(orderTypeStr.String),
OriginalAmount: originalAmount,
DiscountAmount: discountAmount,
ActualAmount: actualAmount,
CouponID: couponID.String,
Status: order.OrderStatus(statusStr.String),
PaymentMethod: order.PaymentMethod(paymentMethodStr.String),
TransactionID: transactionID.String,
PaymentTime: utils.FormatNullTimeToStd(paymentTime),
CreatedAt: utils.FormatNullTimeToStd(createdAt),
UpdatedAt: utils.FormatNullTimeToStd(updatedAt),
}
// 使用 Go 代码查询业务数据并关联
if orderID.Valid {
businessDataMap, err := d.getOrderBusinessDataBatch([]string{orderID.String})
if err == nil {
if data, ok := businessDataMap[orderID.String]; ok {
result.CampID = data.CampID
result.SectionID = data.SectionID
}
}
}
return result, nil
}
// ListOrders 查询订单列表(支持多条件筛选和分页,使用新的 orders 表)
func (d *OrderDAO) ListOrders(
userID, campID, sectionID string,
orderStatus order.OrderStatus,
paymentMethod order.PaymentMethod,
page, pageSize int,
) ([]*order.Order, int, error) {
table := "orders"
where := make(map[string]any)
if userID != "" {
where["user_id"] = userID
}
// 注意:新的 orders 表没有 camp_id 和 section_id 字段,这些信息需要从业务数据中获取
// 如果传入这些参数,可以通过 order_type = 'CAMP_SECTION' 来筛选打卡营小节订单
if campID != "" || sectionID != "" {
where["order_type"] = string(order.OrderTypeCampSection)
}
if orderStatus != order.OrderStatusUnknown && orderStatus != "" {
where["status"] = string(orderStatus)
}
if paymentMethod != order.PaymentMethodUnknown && paymentMethod != "" {
where["payment_method"] = string(paymentMethod)
}
// count - 直接查询 orders 表(不使用 JOIN
countCond, countVals, err := builder.BuildSelect(table, where, []string{"COUNT(*) as total"})
if err != nil {
return nil, 0, fmt.Errorf("构建统计查询失败: %v", err)
}
var total int
if err := d.client.DB.QueryRow(countCond, countVals...).Scan(&total); err != nil {
return nil, 0, fmt.Errorf("查询总数失败: %v", err)
}
// select - 先查询订单表(不使用 JOIN
selectFields := []string{
"order_id", "user_id", "order_type", "original_amount", "discount_amount",
"actual_amount", "coupon_id", "status", "payment_method", "transaction_id",
"payment_time", "created_at", "updated_at",
}
cond, vals, err := builder.BuildSelect(table, where, selectFields)
if err != nil {
return nil, 0, fmt.Errorf("构建查询失败: %v", err)
}
offset := (page - 1) * pageSize
cond += " ORDER BY created_at DESC LIMIT ? OFFSET ?"
vals = append(vals, pageSize, offset)
rows, err := d.client.DB.Query(cond, vals...)
if err != nil {
return nil, 0, fmt.Errorf("查询订单列表失败: %v", err)
}
defer rows.Close()
list := make([]*order.Order, 0)
orderIDs := make([]string, 0)
// 第一遍:读取所有订单数据
for rows.Next() {
var (
orderID, userID, orderTypeStr, couponID, statusStr, paymentMethodStr, transactionID sql.NullString
originalAmount, discountAmount, actualAmount int32
createdAt, updatedAt, paymentTime sql.NullTime
)
if err := rows.Scan(
&orderID, &userID, &orderTypeStr, &originalAmount, &discountAmount,
&actualAmount, &couponID, &statusStr, &paymentMethodStr, &transactionID,
&paymentTime, &createdAt, &updatedAt,
); err != nil {
continue
}
orderObj := &order.Order{
OrderID: orderID.String,
UserID: userID.String,
OrderType: order.OrderType(orderTypeStr.String),
OriginalAmount: originalAmount,
DiscountAmount: discountAmount,
ActualAmount: actualAmount,
CouponID: couponID.String,
Status: order.OrderStatus(statusStr.String),
PaymentMethod: order.PaymentMethod(paymentMethodStr.String),
TransactionID: transactionID.String,
PaymentTime: utils.FormatNullTimeToStd(paymentTime),
CreatedAt: utils.FormatNullTimeToStd(createdAt),
UpdatedAt: utils.FormatNullTimeToStd(updatedAt),
}
list = append(list, orderObj)
orderIDs = append(orderIDs, orderID.String)
}
if err := rows.Err(); err != nil {
return nil, 0, fmt.Errorf("遍历订单列表失败: %v", err)
}
// 第二遍:批量查询业务数据并关联(使用 Go 代码实现,避免 JOIN
if len(orderIDs) > 0 {
businessDataMap, err := d.getOrderBusinessDataBatch(orderIDs)
if err == nil {
// 将业务数据关联到订单对象
for _, orderObj := range list {
if data, ok := businessDataMap[orderObj.OrderID]; ok {
orderObj.CampID = data.CampID
orderObj.SectionID = data.SectionID
}
}
}
}
return list, total, nil
}
// OrderBusinessData 订单业务数据
type OrderBusinessData struct {
OrderID string
CampID string
SectionID string
}
// getOrderBusinessDataBatch 批量查询订单业务数据
func (d *OrderDAO) getOrderBusinessDataBatch(orderIDs []string) (map[string]*OrderBusinessData, error) {
if len(orderIDs) == 0 {
return make(map[string]*OrderBusinessData), nil
}
table := "order_business_data"
// 构建 IN 查询
placeholders := ""
vals := make([]any, 0)
for i, id := range orderIDs {
if i > 0 {
placeholders += ","
}
placeholders += "?"
vals = append(vals, id)
}
cond := "SELECT order_id, camp_id, section_id FROM " + table + " WHERE order_id IN (" + placeholders + ")"
rows, err := d.client.DB.Query(cond, vals...)
if err != nil {
// 如果表不存在,返回空映射(向后兼容)
return make(map[string]*OrderBusinessData), nil
}
defer rows.Close()
result := make(map[string]*OrderBusinessData)
for rows.Next() {
var (
orderID, campID, sectionID sql.NullString
)
if err := rows.Scan(&orderID, &campID, &sectionID); err != nil {
continue
}
if orderID.Valid {
result[orderID.String] = &OrderBusinessData{
OrderID: orderID.String,
CampID: campID.String,
SectionID: sectionID.String,
}
}
}
return result, nil
}
// UpdateOrderStatus 更新订单状态(使用新的 orders 表)
func (d *OrderDAO) UpdateOrderStatus(
orderID, orderNo string,
orderStatus order.OrderStatus,
paymentMethod order.PaymentMethod,
thirdPartyOrderNo string,
paymentTime *time.Time,
) error {
table := "orders"
where := make(map[string]any)
if orderID != "" {
where["order_id"] = orderID
} else if orderNo != "" {
where["order_id"] = orderNo // 新表使用 order_id
} else {
return fmt.Errorf("订单ID和订单号不能同时为空")
}
data := make(map[string]any)
if orderStatus != order.OrderStatusUnknown && orderStatus != "" {
data["status"] = string(orderStatus)
}
if paymentMethod != order.PaymentMethodUnknown && paymentMethod != "" {
data["payment_method"] = string(paymentMethod)
}
if thirdPartyOrderNo != "" {
data["transaction_id"] = thirdPartyOrderNo
}
if paymentTime != nil {
data["payment_time"] = *paymentTime
}
if len(data) == 0 {
return fmt.Errorf("没有需要更新的字段")
}
cond, vals, err := builder.BuildUpdate(table, where, data)
if err != nil {
return fmt.Errorf("构建更新语句失败: %v", err)
}
_, err = d.client.DB.Exec(cond, vals...)
if err != nil {
return fmt.Errorf("更新订单状态失败: %v", err)
}
return nil
}
// RefundOrder 退款订单(使用新的 orders 表,注意:新表没有 refund_amount_fen, refund_reason, refund_time 字段)
// 退款操作只需要更新订单状态为 REFUNDED
func (d *OrderDAO) RefundOrder(
orderID, orderNo string,
refundAmountFen int32,
refundReason string,
) error {
table := "orders"
where := make(map[string]any)
if orderID != "" {
where["order_id"] = orderID
} else if orderNo != "" {
where["order_id"] = orderNo // 新表使用 order_id
} else {
return fmt.Errorf("订单ID和订单号不能同时为空")
}
// 新表只有 status 字段,退款时更新为 REFUNDED
// 注意refund_amount_fen, refund_reason, refund_time 字段在新表中不存在
// 如果需要记录这些信息,可能需要额外的退款记录表
data := map[string]any{
"status": string(order.OrderStatusRefunded),
}
cond, vals, err := builder.BuildUpdate(table, where, data)
if err != nil {
return fmt.Errorf("构建更新语句失败: %v", err)
}
_, err = d.client.DB.Exec(cond, vals...)
if err != nil {
return fmt.Errorf("退款订单失败: %v", err)
}
return nil
}
// ========== 辅助函数 ==========
// convertOrderStatus 转换订单状态枚举为数据库值
func convertOrderStatus(status order.OrderStatus) *string {
switch status {
case order.OrderStatusPending:
s := "PENDING"
return &s
case order.OrderStatusPaid:
s := "PAID"
return &s
case order.OrderStatusFailed:
s := "FAILED"
return &s
case order.OrderStatusRefunded:
s := "REFUNDED"
return &s
case order.OrderStatusCancelled:
s := "CANCELLED"
return &s
default:
return nil // NULL
}
}
// parseOrderStatus 解析订单状态字符串为枚举
func parseOrderStatus(s string) order.OrderStatus {
switch s {
case "PENDING":
return order.OrderStatusPending
case "PAID":
return order.OrderStatusPaid
case "FAILED":
return order.OrderStatusFailed
case "REFUNDED":
return order.OrderStatusRefunded
case "CANCELLED":
return order.OrderStatusCancelled
default:
return order.OrderStatusUnknown
}
}
// convertPaymentMethod 转换支付方式枚举为数据库值
func convertPaymentMethod(method order.PaymentMethod) *string {
switch method {
case order.PaymentMethodWechat:
s := "WECHAT"
return &s
case order.PaymentMethodAlipay:
s := "ALIPAY"
return &s
case order.PaymentMethodBalance:
s := "BALANCE"
return &s
default:
return nil // NULL
}
}
// parsePaymentMethod 解析支付方式字符串为枚举
func parsePaymentMethod(s string) order.PaymentMethod {
switch s {
case "WECHAT":
return order.PaymentMethodWechat
case "ALIPAY":
return order.PaymentMethodAlipay
case "BALANCE":
return order.PaymentMethodBalance
default:
return order.PaymentMethodUnknown
}
}
// parseAccessSource 解析访问来源字符串为枚举
func parseAccessSource(s string) order.AccessSource {
switch s {
case "GRANT":
return order.AccessSourceGrant
case "PURCHASE":
return order.AccessSourcePurchase
default:
return order.AccessSourceUnknown
}
}