Files
server/app/services/order_service.go
T
2026-01-16 15:49:34 +08:00

1028 lines
34 KiB
Go
Raw 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 services
import (
"context"
"errors"
"fmt"
"sort"
"strings"
"time"
"github.com/goravel/framework/contracts/database/orm"
"github.com/goravel/framework/facades"
"github.com/oklog/ulid/v2"
apperrors "goravel/app/errors"
"goravel/app/models"
"goravel/app/utils"
"goravel/app/utils/errorlog"
)
const OrderCountThreshold int64 = 100000
// ApplyOrderFiltersToQuery 只负责通用筛选(不包含时间范围),供列表查询/导出复用,避免重复/不一致。
func ApplyOrderFiltersToQuery(query orm.Query, filters OrderFilters) orm.Query {
// 用户ID筛选
if filters.UserID > 0 {
query = query.Where("user_id = ?", filters.UserID)
}
// 订单号模糊搜索
if filters.OrderNo != "" {
query = query.Where("order_no = ?", filters.OrderNo)
}
// 订单状态筛选
if filters.Status != "" {
query = query.Where("status = ?", filters.Status)
}
// 金额范围筛选
if filters.MinAmount > 0 {
query = query.Where("amount >= ?", filters.MinAmount)
}
if filters.MaxAmount > 0 {
query = query.Where("amount <= ?", filters.MaxAmount)
}
return query
}
// BuildOrderQuery 构建订单分表查询(包含时间范围 + 通用筛选),供列表查询/导出复用。
func BuildOrderQuery(tableName string, filters OrderFilters) orm.Query {
query := facades.Orm().Query().Table(tableName)
// 时间范围(导出/列表都需要)
if !filters.StartTime.IsZero() {
query = query.Where("created_at >= ?", filters.StartTime)
}
if !filters.EndTime.IsZero() {
query = query.Where("created_at <= ?", filters.EndTime)
}
return ApplyOrderFiltersToQuery(query, filters)
}
// GetOrderDetailsTableFromOrdersTable 从订单分表名获取对应的订单详情分表名
// ordersTableName: 订单分表名,如 "orders_202501"
// 返回: 订单详情分表名,如 "order_details_202501"
func GetOrderDetailsTableFromOrdersTable(ordersTableName string) string {
// 将 orders_YYYYMM 转换为 order_details_YYYYMM
return strings.Replace(ordersTableName, "orders_", "order_details_", 1)
}
type OrderService interface {
// CreateOrder 创建订单(带防重复提交)
CreateOrder(userID uint, amount float64, products []OrderProduct, requestID string, remark string) (*models.Order, []models.OrderDetail, error)
// GetOrderByID 根据ID查询订单
GetOrderByID(orderID uint, orderTime time.Time) (*models.Order, []models.OrderDetail, error)
// GetOrderByOrderNo 根据订单号查询订单(直接定位分表,更高效)
GetOrderByOrderNo(orderNo string) (*models.Order, []models.OrderDetail, error)
// GetOrders 查询订单列表(限制不超过3个月)
GetOrders(filters OrderFilters, page, pageSize int) ([]models.Order, int64, error)
// GetOrdersWithDetails 查询订单列表(包含详情,限制不超过3个月)
GetOrdersWithDetails(filters OrderFilters, page, pageSize int) ([]OrderWithDetails, int64, error)
// GetAllOrdersForExport 获取所有订单用于导出(限制不超过3个月,不分页)
GetAllOrdersForExport(filters OrderFilters) ([]models.Order, error)
// GetAllOrdersWithDetailsForExport 获取所有订单及详情用于导出(限制不超过3个月,不分页)
GetAllOrdersWithDetailsForExport(filters OrderFilters) ([]OrderWithDetails, error)
// UpdateOrder 更新订单(状态和备注)
// 如果提供了订单号,优先使用订单号查找订单(更高效,可直接定位分表)
UpdateOrder(orderID uint, orderTime time.Time, status string, remark string, orderNo ...string) error
// UpdateOrderByOrderNo 根据订单号更新订单(状态和备注)
UpdateOrderByOrderNo(orderNo string, status string, remark string) error
// DeleteOrder 删除订单
// 如果提供了订单号,优先使用订单号查找订单(更高效,可直接定位分表)
DeleteOrder(orderID uint, orderTime time.Time, orderNo ...string) error
// DeleteOrderByOrderNo 根据订单号删除订单
DeleteOrderByOrderNo(orderNo string) error
// GetOrdersCountInYear 获取最近一年的订单总数(用于仪表盘统计)
GetOrdersCountInYear() (int64, error)
}
// OrderFilters 订单查询筛选条件
type OrderFilters struct {
UserID uint // 用户ID0表示不筛选)
OrderNo string // 订单号(模糊搜索)
Status string // 订单状态
MinAmount float64 // 最小金额(0表示不筛选)
MaxAmount float64 // 最大金额(0表示不筛选)
StartTime time.Time // 开始时间
EndTime time.Time // 结束时间
OrderBy string // 排序字段(格式:字段:asc/desc,如:created_at:desc
}
// 订单分页统计优化阈值(超过此值使用执行计划估算)
const Order int64 = 100000
type OrderServiceImpl struct {
shardingService ShardingService
shardingQueryService ShardingQueryService
countThreshold int64 // count 查询优化阈值
}
type OrderProduct struct {
ProductID uint `json:"product_id"`
ProductName string `json:"product_name"`
Price float64 `json:"price"`
Quantity int `json:"quantity"`
}
// OrderExportData 订单导出数据结构(用于扩展导出字段)
type OrderExportData struct {
Order models.Order
}
// OrderWithDetails 订单及详情
type OrderWithDetails struct {
models.Order
Details []models.OrderDetail `json:"details"`
}
func NewOrderService() *OrderServiceImpl {
service := &OrderServiceImpl{
shardingService: NewShardingService(),
}
// 初始化分表查询服务
service.shardingQueryService = NewShardingQueryService(ShardingQueryConfig{
BaseTableName: "orders",
GetColumns: func() string {
return service.getOrderTableColumns()
},
BuildWhereClause: func(filters any) (string, []any) {
return service.buildOrderWhereClause(filters)
},
GetAllowedOrderFields: func() map[string]bool {
return map[string]bool{
"id": true,
"order_no": true,
"user_id": true,
"amount": true,
"status": true,
"created_at": true,
"updated_at": true,
}
},
DefaultOrderBy: "created_at:desc",
ModuleName: "order",
CountThreshold: OrderCountThreshold, // 订单数据量大,超过此值使用估算值,不设置或者0直接使用count
})
return service
}
// CreateOrder 创建订单
func (s *OrderServiceImpl) CreateOrder(userID uint, amount float64, products []OrderProduct, requestID string, remark string) (*models.Order, []models.OrderDetail, error) {
// 防重复提交:使用 Redis 锁
if requestID == "" {
requestID = ulid.Make().String()
}
lockKey := fmt.Sprintf("order:lock:%s", requestID)
lockValue := fmt.Sprintf("%d_%d", userID, time.Now().Unix())
// 尝试获取锁,过期时间5秒
// 先检查是否已存在
var cachedValue string
cacheResult := facades.Cache().Get(lockKey, &cachedValue)
if cacheResult != nil && cachedValue != "" {
return nil, nil, errors.New("订单正在处理中,请勿重复提交")
}
// 设置锁,过期时间5秒
if err := facades.Cache().Put(lockKey, lockValue, 5*time.Second); err != nil {
errorlog.Record(context.Background(), "order", "获取锁失败", map[string]any{
"user_id": userID,
"request_id": requestID,
"lock_key": lockKey,
"error": err.Error(),
}, "获取锁失败: %v", err)
return nil, nil, apperrors.ErrGetLockFailed.WithError(err)
}
// 确保释放锁
defer func() {
_ = facades.Cache().Forget(lockKey)
}()
// 生成订单号并创建订单(带重试机制,防止并发下订单号重复)
now := time.Now().UTC() // 使用 UTC 时间,确保分表按 UTC 时区分
tableName := utils.GetShardingTableName("orders", now)
// 确保分表存在
if err := s.shardingService.EnsureShardingTable(tableName, "orders"); err != nil {
_ = facades.Cache().Forget(lockKey)
return nil, nil, err
}
// 重试机制:如果订单号重复,重新生成(最多重试3次)
var order *models.Order
maxRetries := 3
for i := range maxRetries {
orderNo := s.generateOrderNo()
// 创建订单主表记录
order = &models.Order{
OrderNo: orderNo,
UserID: userID,
Amount: amount,
Status: "pending",
Remark: remark,
// CreatedAt 由 orm.Model 自动设置
}
// 尝试插入订单(数据库唯一索引会防止重复)
err := facades.Orm().Query().Table(tableName).Create(order)
if err == nil {
// 插入成功,跳出循环
break
}
// 检查是否是唯一索引冲突错误(订单号重复)
// MySQL 错误码 1062 表示重复键错误
errStr := err.Error()
if strings.Contains(errStr, "Duplicate entry") || strings.Contains(errStr, "1062") {
// 订单号重复,重试(ULID 碰撞概率极低,但理论上可能发生)
if i == maxRetries-1 {
// 最后一次重试也失败,返回错误
_ = facades.Cache().Forget(lockKey)
errorlog.Record(context.Background(), "order", "生成唯一订单号失败", map[string]any{
"user_id": userID,
"request_id": requestID,
"retries": maxRetries,
}, "生成唯一订单号失败,请重试")
return nil, nil, apperrors.ErrGenerateOrderNoFailed
}
// 继续下一次重试
continue
}
// 其他错误,直接返回
_ = facades.Cache().Forget(lockKey)
errorlog.Record(context.Background(), "order", "创建订单失败", map[string]any{
"user_id": userID,
"request_id": requestID,
"amount": amount,
"error": err.Error(),
}, "创建订单失败: %v", err)
return nil, nil, apperrors.ErrCreateOrderFailed.WithError(err)
}
// 创建订单详情记录
var details []models.OrderDetail
detailTableName := utils.GetShardingTableName("order_details", now)
// 确保订单详情分表存在
if err := s.shardingService.EnsureShardingTable(detailTableName, "order_details"); err != nil {
_ = facades.Cache().Forget(lockKey)
return nil, nil, err
}
for _, product := range products {
detail := models.OrderDetail{
OrderID: order.ID,
ProductID: product.ProductID,
ProductName: product.ProductName,
Price: product.Price,
Quantity: product.Quantity,
Subtotal: product.Price * float64(product.Quantity),
// CreatedAt 由 orm.Model 自动设置
}
if err := facades.Orm().Query().Table(detailTableName).Create(&detail); err != nil {
// 如果详情创建失败,删除已创建的订单
_, _ = facades.Orm().Query().Table(tableName).Where("id", order.ID).Delete(&models.Order{})
_ = facades.Cache().Forget(lockKey)
errorlog.Record(context.Background(), "order", "创建订单详情失败", map[string]any{
"order_id": order.ID,
"user_id": userID,
"product_id": product.ProductID,
"error": err.Error(),
}, "创建订单详情失败: %v", err)
return nil, nil, apperrors.ErrCreateOrderDetailFailed.WithError(err)
}
details = append(details, detail)
}
return order, details, nil
}
// findOrderByID 通过订单ID查找订单
// 如果提供了订单号,会优先使用订单号直接定位分表(更高效)
// 如果没有订单号,则遍历最近几个月的分表
func (s *OrderServiceImpl) findOrderByID(orderID uint, orderNo ...string) (*models.Order, error) {
// 如果提供了订单号,优先使用订单号直接定位分表(更高效)
if len(orderNo) > 0 && orderNo[0] != "" {
order, err := s.findOrderByOrderNo(orderNo[0])
if err == nil {
// 如果通过订单号找到了,验证订单ID是否匹配(如果提供了订单ID)
if orderID > 0 && order.ID != orderID {
// 订单号找到了但ID不匹配,继续用ID查找
} else {
return order, nil
}
}
// 如果通过订单号查找失败,继续用ID查找
}
// 如果没有订单号或通过订单号查找失败,使用ID遍历分表
if orderID == 0 {
return nil, apperrors.ErrOrderIDRequired
}
// 查询最近6个月的分表(足够覆盖大部分场景)
now := time.Now().UTC()
startTime := now.AddDate(0, -6, 0)
tableNames := utils.GetShardingTableNames("orders", startTime, now)
// 从最新的分表开始查询(Model 自动应用软删除过滤)
for i := len(tableNames) - 1; i >= 0; i-- {
var order models.Order
if err := facades.Orm().Query().Model(&models.Order{}).Table(tableNames[i]).Where("id", orderID).First(&order); err == nil {
return &order, nil
}
}
return nil, apperrors.ErrOrderNotFound
}
// GetOrderByID 根据ID查询订单
func (s *OrderServiceImpl) GetOrderByID(orderID uint, orderTime time.Time) (*models.Order, []models.OrderDetail, error) {
// 先查找订单获取 created_at
order, err := s.findOrderByID(orderID)
if err != nil {
return nil, nil, err
}
// 使用订单的 created_at 确定分表
timeStr := order.CreatedAt.ToDateTimeString()
createdAt, _ := utils.ParseDateTimeUTC(timeStr)
// 查询订单详情
detailTableName := utils.GetShardingTableName("order_details", createdAt)
var details []models.OrderDetail
if err := facades.Orm().Query().Table(detailTableName).Where("order_id", orderID).Find(&details); err != nil {
return nil, nil, apperrors.ErrQueryOrderDetailFailed.WithError(err)
}
return order, details, nil
}
// GetOrderByOrderNo 根据订单号查询订单(直接定位分表,更高效)
func (s *OrderServiceImpl) GetOrderByOrderNo(orderNo string) (*models.Order, []models.OrderDetail, error) {
// 通过订单号查找订单(直接定位分表)
order, err := s.findOrderByOrderNo(orderNo)
if err != nil {
return nil, nil, err
}
// 使用订单的 created_at 确定详情分表
timeStr := order.CreatedAt.ToDateTimeString()
createdAt, _ := utils.ParseDateTimeUTC(timeStr)
// 查询订单详情
detailTableName := utils.GetShardingTableName("order_details", createdAt)
var details []models.OrderDetail
if err := facades.Orm().Query().Table(detailTableName).Where("order_id", order.ID).Find(&details); err != nil {
return nil, nil, apperrors.ErrQueryOrderDetailFailed.WithError(err)
}
return order, details, nil
}
// GetOrders 查询订单列表(限制不超过3个月)
func (s *OrderServiceImpl) GetOrders(filters OrderFilters, page, pageSize int) ([]models.Order, int64, error) {
// 验证时间范围不超过3个月
valid, err := utils.ValidateTimeRange(filters.StartTime, filters.EndTime)
if !valid {
return nil, 0, err
}
// 获取需要查询的所有分表
tableNames := utils.GetShardingTableNames("orders", filters.StartTime, filters.EndTime)
if len(tableNames) == 0 {
return []models.Order{}, 0, nil
}
// 如果只有一个分表,直接查询
if len(tableNames) == 1 {
return s.querySingleTable(tableNames[0], filters, page, pageSize)
}
// 多个分表:使用 UNION ALL 在数据库层面合并
return s.queryMultipleTablesWithUnion(tableNames, filters, page, pageSize)
}
// buildShardingQuery 构建分表查询条件(辅助函数,减少重复代码)
func (s *OrderServiceImpl) buildShardingQuery(tableName string, filters OrderFilters) orm.Query {
return BuildOrderQuery(tableName, filters)
}
// buildOrderWhereClause 构建订单查询的 WHERE 条件(用于通用分表查询服务)
func (s *OrderServiceImpl) buildOrderWhereClause(filters any) (string, []any) {
orderFilters, ok := filters.(OrderFilters)
if !ok {
return "", nil
}
var conditions []string
var args []any
// 时间范围(必填)
if !orderFilters.StartTime.IsZero() {
conditions = append(conditions, "created_at >= ?")
args = append(args, orderFilters.StartTime)
}
if !orderFilters.EndTime.IsZero() {
conditions = append(conditions, "created_at <= ?")
args = append(args, orderFilters.EndTime)
}
// 用户ID筛选
if orderFilters.UserID > 0 {
conditions = append(conditions, "user_id = ?")
args = append(args, orderFilters.UserID)
}
// 订单号模糊搜索
if orderFilters.OrderNo != "" {
// conditions = append(conditions, "order_no LIKE ?")
// args = append(args, "%"+orderFilters.OrderNo+"%")
conditions = append(conditions, "order_no = ?")
args = append(args, orderFilters.OrderNo)
}
// 订单状态筛选
if orderFilters.Status != "" {
conditions = append(conditions, "status = ?")
args = append(args, orderFilters.Status)
}
// 金额范围筛选
if orderFilters.MinAmount > 0 {
conditions = append(conditions, "amount >= ?")
args = append(args, orderFilters.MinAmount)
}
if orderFilters.MaxAmount > 0 {
conditions = append(conditions, "amount <= ?")
args = append(args, orderFilters.MaxAmount)
}
return strings.Join(conditions, " AND "), args
}
// getOrderTableColumns 获取订单表的所有列名(用于 UNION ALL 查询)
// 明确指定列名,避免不同分表列数不一致的问题
// 只查询 Order 模型中存在的字段,忽略可能不存在的扩展字段(如 payment_method
func (s *OrderServiceImpl) getOrderTableColumns() string {
// 列顺序必须与 Order 模型字段顺序一致
// 只包含 Order 模型中定义的字段,确保所有分表都有这些字段
// 注意:不包含 payment_method,因为:
// 1. Order 模型中没有该字段
// 2. 某些旧分表可能没有该字段
// 3. 如果将来需要该字段,应该先更新 Order 模型,然后确保所有分表都有该字段
columns := []string{
"id",
"order_no",
"user_id",
"amount",
"status",
"remark",
"created_at",
"updated_at",
"deleted_at",
}
return strings.Join(columns, ", ")
}
// queryMultipleTablesWithUnion 使用 UNION ALL 查询多个分表
// 在数据库层面合并多个分表,统一排序和分页,性能更优
// 使用通用分表查询服务
func (s *OrderServiceImpl) queryMultipleTablesWithUnion(tableNames []string, filters OrderFilters, page, pageSize int) ([]models.Order, int64, error) {
var orders []models.Order
total, err := s.shardingQueryService.QueryMultipleTables(tableNames, filters, page, pageSize, &orders)
if err != nil {
return nil, 0, err
}
return orders, total, nil
}
// queryMultipleTablesWithUnionForExport 使用 UNION ALL 查询多个分表(用于导出,不分页)
// 在数据库层面合并多个分表,统一排序,性能更优
// 使用通用分表查询服务
func (s *OrderServiceImpl) queryMultipleTablesWithUnionForExport(tableNames []string, filters OrderFilters) ([]models.Order, error) {
var orders []models.Order
err := s.shardingQueryService.QueryMultipleTablesForExport(tableNames, filters, &orders)
if err != nil {
return nil, err
}
return orders, nil
}
// sortOrders 对订单列表进行排序
func (s *OrderServiceImpl) sortOrders(orders []models.Order, orderBy string) {
parts := strings.Split(orderBy, ":")
field := "created_at"
desc := true
if len(parts) == 2 {
field = parts[0]
desc = strings.ToLower(parts[1]) == "desc"
}
// 使用 sort.Slice 进行排序
sort.Slice(orders, func(i, j int) bool {
var less bool
switch field {
case "id":
less = orders[i].ID < orders[j].ID
case "amount":
less = orders[i].Amount < orders[j].Amount
case "created_at":
less = orders[i].CreatedAt.ToDateTimeString() < orders[j].CreatedAt.ToDateTimeString()
case "updated_at":
less = orders[i].UpdatedAt.ToDateTimeString() < orders[j].UpdatedAt.ToDateTimeString()
default:
less = orders[i].CreatedAt.ToDateTimeString() < orders[j].CreatedAt.ToDateTimeString()
}
if desc {
return !less
}
return less
})
}
// querySingleTable 查询单个分表
func (s *OrderServiceImpl) querySingleTable(tableName string, filters OrderFilters, page, pageSize int) ([]models.Order, int64, error) {
// 应用排序
orderBy := filters.OrderBy
if orderBy == "" {
orderBy = "created_at:desc"
}
// 构建基础查询条件
query := s.buildShardingQuery(tableName, filters)
query = s.applyOrderBy(query, orderBy)
// 获取总数(使用 CountOptimizer 优化,超过阈值使用 EXPLAIN 估算)
whereClause, whereArgs := s.buildOrderWhereClause(filters)
countOptimizer := utils.NewCountOptimizer(OrderCountThreshold, "order")
total, _, err := countOptimizer.OptimizedCountWithTable(tableName, whereClause, whereArgs...)
if err != nil {
return nil, 0, err
}
// 构建分页查询(重新构建确保使用分表)
findQuery := s.buildShardingQuery(tableName, filters)
findQuery = s.applyOrderBy(findQuery, orderBy)
// 执行分页查询
var orders []models.Order
if err := findQuery.Offset((page - 1) * pageSize).Limit(pageSize).Find(&orders); err != nil {
return nil, 0, err
}
return orders, total, nil
}
// GetOrdersWithDetails 查询订单列表(包含详情,限制不超过3个月)
func (s *OrderServiceImpl) GetOrdersWithDetails(filters OrderFilters, page, pageSize int) ([]OrderWithDetails, int64, error) {
// 先查询订单列表
orders, total, err := s.GetOrders(filters, page, pageSize)
if err != nil {
return nil, 0, err
}
if len(orders) == 0 {
return []OrderWithDetails{}, total, nil
}
result := make([]OrderWithDetails, len(orders))
// 按分表分组订单ID
orderIDsByTable := make(map[string][]uint)
orderIndexByID := make(map[uint]int)
for i, order := range orders {
// 将订单转换为 OrderWithDetails
result[i] = OrderWithDetails{
Order: order,
Details: []models.OrderDetail{},
}
// 根据订单的 created_at 确定详情分表
timeStr := order.CreatedAt.ToDateTimeString()
createdAt, _ := utils.ParseDateTimeUTC(timeStr)
// 获取详情分表名
detailTableName := utils.GetShardingTableName("order_details", createdAt)
// 按分表分组订单ID
if orderIDsByTable[detailTableName] == nil {
orderIDsByTable[detailTableName] = []uint{}
}
orderIDsByTable[detailTableName] = append(orderIDsByTable[detailTableName], order.ID)
orderIndexByID[order.ID] = i
}
// 按分表批量查询订单详情
for tableName, orderIDs := range orderIDsByTable {
if len(orderIDs) == 0 {
continue
}
orderIDsAny := make([]any, len(orderIDs))
for i, id := range orderIDs {
orderIDsAny[i] = id
}
var details []models.OrderDetail
if err := facades.Orm().Query().Table(tableName).
WhereIn("order_id", orderIDsAny).
Find(&details); err == nil {
// 将详情分配到对应的订单
for _, detail := range details {
if index, ok := orderIndexByID[detail.OrderID]; ok {
result[index].Details = append(result[index].Details, detail)
}
}
}
}
return result, total, nil
}
// applyOrderBy 应用排序
func (s *OrderServiceImpl) applyOrderBy(query orm.Query, orderBy string) orm.Query {
// 解析排序字段,格式:字段:asc/desc
parts := strings.Split(orderBy, ":")
if len(parts) != 2 {
// 默认排序
return query.Order("created_at desc")
}
field := parts[0]
direction := strings.ToLower(parts[1])
// 允许排序的字段
allowedFields := map[string]bool{
"id": true,
"order_no": true,
"user_id": true,
"amount": true,
"status": true,
"created_at": true,
"updated_at": true,
}
if !allowedFields[field] {
// 如果字段不允许,使用默认排序
return query.Order("created_at desc")
}
if direction == "asc" {
return query.Order(field + " asc")
} else {
return query.Order(field + " desc")
}
}
// GetAllOrdersForExport 获取所有订单用于导出(限制不超过3个月,不分页)
// 使用 UNION ALL 优化,在数据库层面合并和排序
func (s *OrderServiceImpl) GetAllOrdersForExport(filters OrderFilters) ([]models.Order, error) {
// 验证时间范围不超过3个月
valid, err := utils.ValidateTimeRange(filters.StartTime, filters.EndTime)
if !valid {
return nil, err
}
// 获取需要查询的所有分表
tableNames := utils.GetShardingTableNames("orders", filters.StartTime, filters.EndTime)
if len(tableNames) == 0 {
return []models.Order{}, nil
}
// 如果只有一个分表,直接查询
if len(tableNames) == 1 {
query := s.buildShardingQuery(tableNames[0], filters)
orderBy := filters.OrderBy
if orderBy == "" {
orderBy = "created_at:desc"
}
query = s.applyOrderBy(query, orderBy)
var orders []models.Order
if err := query.Find(&orders); err != nil {
return nil, apperrors.ErrQueryFailed.WithError(err)
}
return orders, nil
}
// 多个分表:使用 UNION ALL 在数据库层面合并
return s.queryMultipleTablesWithUnionForExport(tableNames, filters)
}
// GetAllOrdersWithDetailsForExport 获取所有订单及详情用于导出(限制不超过3个月,不分页)
// 使用 UNION ALL 优化,在数据库层面合并和排序
// 优化:使用批量查询避免 N+1 查询问题
func (s *OrderServiceImpl) GetAllOrdersWithDetailsForExport(filters OrderFilters) ([]OrderWithDetails, error) {
// 先获取所有订单(使用优化的 UNION ALL 方法)
allOrders, err := s.GetAllOrdersForExport(filters)
if err != nil {
return nil, err
}
if len(allOrders) == 0 {
return []OrderWithDetails{}, nil
}
// 初始化结果
result := make([]OrderWithDetails, len(allOrders))
// 按分表分组订单ID,避免 N+1 查询
orderIDsByTable := make(map[string][]uint)
orderIndexByID := make(map[uint]int)
for i, order := range allOrders {
result[i] = OrderWithDetails{
Order: order,
Details: []models.OrderDetail{},
}
// 根据订单的 created_at 确定详情分表
timeStr := order.CreatedAt.ToDateTimeString()
createdAt, _ := utils.ParseDateTimeUTC(timeStr)
// 获取详情分表名
detailTableName := utils.GetShardingTableName("order_details", createdAt)
// 按分表分组订单ID
if orderIDsByTable[detailTableName] == nil {
orderIDsByTable[detailTableName] = []uint{}
}
orderIDsByTable[detailTableName] = append(orderIDsByTable[detailTableName], order.ID)
orderIndexByID[order.ID] = i
}
// 按分表批量查询订单详情(避免 N+1 查询)
for tableName, orderIDs := range orderIDsByTable {
if len(orderIDs) == 0 {
continue
}
// 将 []uint 转换为 []any
orderIDsAny := make([]any, len(orderIDs))
for i, id := range orderIDs {
orderIDsAny[i] = id
}
var details []models.OrderDetail
if err := facades.Orm().Query().Table(tableName).
WhereIn("order_id", orderIDsAny).
Find(&details); err == nil {
// 将详情分配到对应的订单
for _, detail := range details {
if index, ok := orderIndexByID[detail.OrderID]; ok {
result[index].Details = append(result[index].Details, detail)
}
}
}
}
return result, nil
}
// UpdateOrder 更新订单(状态和备注)
// 如果提供了订单号,优先使用订单号查找订单(更高效,可直接定位分表)
func (s *OrderServiceImpl) UpdateOrder(orderID uint, orderTime time.Time, status string, remark string, orderNo ...string) error {
// 先查找订单获取 created_at
// 如果提供了订单号,优先使用订单号查找
var order *models.Order
var err error
if len(orderNo) > 0 && orderNo[0] != "" {
order, err = s.findOrderByID(orderID, orderNo[0])
} else {
order, err = s.findOrderByID(orderID)
}
if err != nil {
return err
}
// 使用订单的 created_at 确定分表
timeStr := order.CreatedAt.ToDateTimeString()
createdAt, _ := utils.ParseDateTimeUTC(timeStr)
tableName := utils.GetShardingTableName("orders", createdAt)
// 构建更新数据(始终更新备注,即使为空字符串)
updateData := map[string]any{
"status": status,
"remark": remark,
}
_, err = facades.Orm().Query().Table(tableName).
Where("id", orderID).
Update(updateData)
return err
}
// DeleteOrder 删除订单(软删除)
// 如果提供了订单号,优先使用订单号查找订单(更高效,可直接定位分表)
func (s *OrderServiceImpl) DeleteOrder(orderID uint, orderTime time.Time, orderNo ...string) error {
// 先查找订单获取 created_at(只查询未删除的订单)
// 如果提供了订单号,优先使用订单号查找
var order *models.Order
var err error
if len(orderNo) > 0 && orderNo[0] != "" {
order, err = s.findOrderByID(orderID, orderNo[0])
} else {
order, err = s.findOrderByID(orderID)
}
if err != nil {
return err
}
// 使用订单的 created_at 确定分表(将 carbon.DateTime 转换为 time.Time
// 通过格式化字符串再解析的方式转换
timeStr := order.CreatedAt.ToDateTimeString()
createdAt, _ := utils.ParseDateTimeUTC(timeStr)
// 软删除订单详情
detailTableName := utils.GetShardingTableName("order_details", createdAt)
if _, err := facades.Orm().Query().Table(detailTableName).Where("order_id", orderID).Delete(&models.OrderDetail{}); err != nil {
errorlog.Record(context.Background(), "order", "删除订单详情失败", map[string]any{
"order_id": orderID,
"error": err.Error(),
}, "删除订单详情失败: %v", err)
return apperrors.ErrDeleteOrderDetailFailed.WithError(err)
}
// 软删除订单主表
tableName := utils.GetShardingTableName("orders", createdAt)
_, err = facades.Orm().Query().Table(tableName).Where("id", orderID).Delete(&models.Order{})
return err
}
// UpdateOrderByOrderNo 根据订单号更新订单(状态和备注)
func (s *OrderServiceImpl) UpdateOrderByOrderNo(orderNo string, status string, remark string) error {
// 通过订单号查找订单
order, err := s.findOrderByOrderNo(orderNo)
if err != nil {
return err
}
// 使用订单的 created_at 确定分表
timeStr := order.CreatedAt.ToDateTimeString()
createdAt, _ := utils.ParseDateTimeUTC(timeStr)
tableName := utils.GetShardingTableName("orders", createdAt)
// 构建更新数据(始终更新备注,即使为空字符串)
updateData := map[string]any{
"status": status,
"remark": remark,
}
// 更新订单
_, err = facades.Orm().Query().Table(tableName).Where("order_no", orderNo).Update(updateData)
return err
}
// DeleteOrderByOrderNo 根据订单号删除订单(软删除)
func (s *OrderServiceImpl) DeleteOrderByOrderNo(orderNo string) error {
// 通过订单号查找订单
order, err := s.findOrderByOrderNo(orderNo)
if err != nil {
return err
}
// 使用订单的 created_at 确定分表
timeStr := order.CreatedAt.ToDateTimeString()
createdAt, _ := utils.ParseDateTimeUTC(timeStr)
// 软删除订单详情
detailTableName := utils.GetShardingTableName("order_details", createdAt)
_, err = facades.Orm().Query().Table(detailTableName).Where("order_id", order.ID).Delete(&models.OrderDetail{})
if err != nil {
errorlog.Record(context.Background(), "order", "删除订单详情失败", map[string]any{
"order_no": orderNo,
"error": err.Error(),
}, "删除订单详情失败: %v", err)
return apperrors.ErrDeleteOrderDetailFailed.WithError(err)
}
// 软删除订单主表
tableName := utils.GetShardingTableName("orders", createdAt)
_, err = facades.Orm().Query().Table(tableName).Where("order_no", orderNo).Delete(&models.Order{})
return err
}
// generateOrderNo 生成订单号(格式:ORD + YYYYMM + ULID
// 例如:ORD20250101ARZ3S0K5M2X9P4Q6R8T1V3W5Y7Z9
// 其中 202501 表示 2025年01月,用于快速定位分表
func (s *OrderServiceImpl) generateOrderNo() string {
return utils.GenerateShardingNo(utils.OrderNoConfig)
}
// parseOrderNoYearMonth 从订单号解析年月信息
// 订单号格式:ORD + YYYYMM + ULID
// 返回:年月字符串(如 "202501")和是否成功解析
func parseOrderNoYearMonth(orderNo string) (string, bool) {
return utils.ParseShardingNoYearMonth(orderNo, utils.OrderNoConfig)
}
// findOrderByOrderNo 通过订单号查找订单(直接定位分表)
func (s *OrderServiceImpl) findOrderByOrderNo(orderNo string) (*models.Order, error) {
// 从订单号解析年月信息
yearMonth, ok := parseOrderNoYearMonth(orderNo)
if !ok {
// 如果无法解析(可能是旧格式订单号),回退到遍历分表的方式
now := time.Now().UTC()
startTime := now.AddDate(0, -6, 0)
tableNames := utils.GetShardingTableNames("orders", startTime, now)
// 从最新的分表开始查询(
for i := len(tableNames) - 1; i >= 0; i-- {
var order models.Order
if err := facades.Orm().Query().Model(&models.Order{}).Table(tableNames[i]).Where("order_no", orderNo).First(&order); err == nil {
return &order, nil
}
}
return nil, apperrors.ErrOrderNotFound
}
// 解析年月字符串为时间
// 格式:200601 -> 2025年01月
parsedTime, err := time.Parse("200601", yearMonth)
if err != nil {
// 解析失败,回退到遍历分表
now := time.Now().UTC()
startTime := now.AddDate(0, -6, 0)
tableNames := utils.GetShardingTableNames("orders", startTime, now)
for i := len(tableNames) - 1; i >= 0; i-- {
var order models.Order
if err := facades.Orm().Query().Model(&models.Order{}).Table(tableNames[i]).Where("order_no", orderNo).First(&order); err == nil {
return &order, nil
}
}
return nil, apperrors.ErrOrderNotFound
}
// 使用解析的年月确定分表
tableName := utils.GetShardingTableName("orders", parsedTime)
// 直接查询对应的分表(Model 自动应用软删除过滤)
var order models.Order
if err := facades.Orm().Query().Model(&models.Order{}).Table(tableName).Where("order_no", orderNo).First(&order); err == nil {
return &order, nil
}
return nil, apperrors.ErrOrderNotFound
}
// GetOrdersCountInYear 获取最近一年的订单总数(用于仪表盘统计)
// 使用 EXPLAIN 获取预估行数,性能更好(牺牲精确度换速度)
func (s *OrderServiceImpl) GetOrdersCountInYear() (int64, error) {
// 计算最近一年的时间范围
now := time.Now().UTC()
startTime := now.AddDate(-1, 0, 0) // 一年前
endTime := now
// 获取需要查询的所有分表
tableNames := utils.GetShardingTableNames("orders", startTime, endTime)
if len(tableNames) == 0 {
return 0, nil
}
var total int64
// 使用 EXPLAIN 获取预估行数(比 COUNT 快很多)
for _, tableName := range tableNames {
// 检查表是否存在
if !facades.Schema().HasTable(tableName) {
continue
}
// 使用 EXPLAIN 获取预估行数
var explainResult []struct {
Rows int64 `gorm:"column:rows"`
}
sql := fmt.Sprintf("EXPLAIN SELECT * FROM `%s` WHERE created_at >= ? AND created_at <= ?", tableName)
err := facades.Orm().Query().Raw(sql, startTime, endTime).Scan(&explainResult)
if err != nil {
errorlog.Record(context.Background(), "order", "查询分表预估行数失败", map[string]any{
"table_name": tableName,
"error": err.Error(),
}, "查询分表 %s 预估行数失败: %v", tableName, err)
continue
}
if len(explainResult) > 0 {
total += explainResult[0].Rows
}
}
return total, nil
}