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 // 用户ID(0表示不筛选) 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 }