package services import ( "context" "encoding/json" "fmt" "strings" "time" "github.com/dromara/carbon/v2" "github.com/go-pay/gopay" "github.com/go-pay/gopay/alipay" "github.com/go-pay/gopay/wechat/v3" "github.com/goravel/framework/contracts/database/orm" "github.com/goravel/framework/facades" apperrors "goravel/app/errors" "goravel/app/models" "goravel/app/utils" "goravel/app/utils/errorlog" "goravel/database/migrations" ) type PaymentService interface { // GetPaymentMethodByID 根据ID获取支付方式 GetPaymentMethodByID(id uint) (*models.PaymentMethod, error) // GetPaymentMethodByCode 根据代码获取支付方式 GetPaymentMethodByCode(code string) (*models.PaymentMethod, error) // GetPaymentMethods 获取支付方式列表 GetPaymentMethods(filters PaymentMethodFilters, page, pageSize int) ([]models.PaymentMethod, int64, error) // CreatePaymentMethod 创建支付方式 CreatePaymentMethod(name, code, paymentType string, config map[string]any, isActive bool, sort int, description string) (*models.PaymentMethod, error) // UpdatePaymentMethod 更新支付方式(保留兼容) UpdatePaymentMethod(id uint, name string, config map[string]any, isActive bool, sort int, description string) error // Update 更新支付方式(新模式) UpdatePaymentMethodModel(paymentMethod *models.PaymentMethod) error // DeletePaymentMethod 删除支付方式 DeletePaymentMethod(id uint) error // GetPaymentByID 根据ID获取支付记录 GetPaymentByID(id uint) (*models.Payment, error) // GetPaymentByPaymentNo 根据支付单号获取支付记录 GetPaymentByPaymentNo(paymentNo string) (*models.Payment, error) // GetPayments 获取支付记录列表 GetPayments(filters PaymentFilters, page, pageSize int) ([]models.Payment, int64, error) // CreatePayment 创建支付记录 CreatePayment(orderNo string, paymentMethodID uint, userID uint, amount float64, remark string) (*models.Payment, error) // UpdatePaymentStatus 更新支付状态(paymentNo 可选,提供则更快定位分表) UpdatePaymentStatus(paymentID uint, status string, thirdPartyNo string, payTime *time.Time, failReason string, notifyData map[string]any, paymentNo ...string) error // CreatePaymentOrder 创建支付订单(调用第三方支付) CreatePaymentOrder(payment *models.Payment, clientIP string) (map[string]any, error) // QueryPaymentOrder 查询支付订单状态 QueryPaymentOrder(payment *models.Payment) (map[string]any, error) // HandlePaymentNotify 处理支付回调通知 HandlePaymentNotify(paymentMethod *models.PaymentMethod, notifyData map[string]any) (*models.Payment, error) } // PaymentMethodFilters 支付方式查询过滤器 type PaymentMethodFilters struct { Name string Code string Type string IsActive string Description string OrderBy string } // PaymentFilters 支付记录查询过滤器 type PaymentFilters struct { PaymentNo string OrderNo string PaymentMethodID uint UserID uint Status string StartTime time.Time EndTime time.Time OrderBy string } // PaymentCountThreshold 支付记录分页统计优化阈值(超过此值使用执行计划估算) const PaymentCountThreshold int64 = 100000 // BuildPaymentQuery 构建支付记录分表查询(包含时间范围 + 通用筛选),供列表查询/导出复用 func BuildPaymentQuery(tableName string, filters PaymentFilters) orm.Query { query := facades.Orm().Query().Table(tableName).Where("deleted_at IS NULL") // 时间范围 if !filters.StartTime.IsZero() { query = query.Where("created_at >= ?", utils.FormatDateTime(filters.StartTime)) } if !filters.EndTime.IsZero() { query = query.Where("created_at <= ?", utils.FormatDateTime(filters.EndTime)) } // 精确匹配条件 if filters.PaymentNo != "" { query = query.Where("payment_no = ?", filters.PaymentNo) } if filters.OrderNo != "" { query = query.Where("order_no = ?", filters.OrderNo) } if filters.PaymentMethodID > 0 { query = query.Where("payment_method_id", filters.PaymentMethodID) } if filters.UserID > 0 { query = query.Where("user_id", filters.UserID) } if filters.Status != "" { query = query.Where("status", filters.Status) } return query } type PaymentServiceImpl struct { shardingService ShardingService shardingQueryService ShardingQueryService } func NewPaymentService() PaymentService { service := &PaymentServiceImpl{ shardingService: NewShardingService(), } // 初始化分表查询服务 service.shardingQueryService = NewShardingQueryService(ShardingQueryConfig{ BaseTableName: "payments", GetColumns: func() string { return service.getPaymentTableColumns() }, BuildWhereClause: func(filters any) (string, []any) { return service.buildPaymentShardingWhereClause(filters) }, GetAllowedOrderFields: func() map[string]bool { return map[string]bool{ "id": true, "payment_no": true, "order_no": true, "user_id": true, "amount": true, "status": true, "created_at": true, "updated_at": true, } }, DefaultOrderBy: "created_at:desc", ModuleName: "payment", CountThreshold: PaymentCountThreshold, }) return service } // GetPaymentMethodByID 根据ID获取支付方式 func (s *PaymentServiceImpl) GetPaymentMethodByID(id uint) (*models.PaymentMethod, error) { var paymentMethod models.PaymentMethod if err := facades.Orm().Query().Where("id", id).FirstOrFail(&paymentMethod); err != nil { return nil, apperrors.ErrPaymentMethodNotFound.WithError(err) } return &paymentMethod, nil } // GetPaymentMethodByCode 根据代码获取支付方式 func (s *PaymentServiceImpl) GetPaymentMethodByCode(code string) (*models.PaymentMethod, error) { var paymentMethod models.PaymentMethod if err := facades.Orm().Query().Where("code", code).Where("is_active", true).FirstOrFail(&paymentMethod); err != nil { return nil, apperrors.ErrPaymentMethodNotFound.WithError(err) } return &paymentMethod, nil } // GetPaymentMethods 获取支付方式列表 func (s *PaymentServiceImpl) GetPaymentMethods(filters PaymentMethodFilters, page, pageSize int) ([]models.PaymentMethod, int64, error) { query := facades.Orm().Query().Model(&models.PaymentMethod{}) // 应用筛选条件 if filters.Name != "" { query = query.Where("name LIKE ?", "%"+filters.Name+"%") } if filters.Code != "" { query = query.Where("code", filters.Code) } if filters.Type != "" { query = query.Where("type", filters.Type) } if filters.IsActive != "" { switch filters.IsActive { case "1": query = query.Where("is_active", true) case "0": query = query.Where("is_active", false) } } // 应用排序 if filters.OrderBy != "" { query = s.applyOrderBy(query, filters.OrderBy) } else { query = query.Order("sort asc").Order("id desc") } // 分页查询 var paymentMethods []models.PaymentMethod var total int64 if err := query.Paginate(page, pageSize, &paymentMethods, &total); err != nil { return nil, 0, apperrors.ErrQueryFailed.WithError(err) } return paymentMethods, total, nil } // CreatePaymentMethod 创建支付方式 func (s *PaymentServiceImpl) CreatePaymentMethod(name, code, paymentType string, config map[string]any, isActive bool, sort int, description string) (*models.PaymentMethod, error) { // 检查代码是否已存在 exists, err := facades.Orm().Query().Model(&models.PaymentMethod{}).Where("code", code).Exists() if err != nil { return nil, apperrors.ErrCreateFailed.WithError(err) } if exists { return nil, apperrors.ErrPaymentMethodCodeExists } // 序列化配置 configJSON, err := json.Marshal(config) if err != nil { return nil, apperrors.ErrPaymentConfigRequired.WithError(err) } // 使用 map 创建,确保 IsActive 为 false 时也能正确保存 // GORM 在处理结构体时会忽略零值字段,使用 map 可以确保所有字段都被保存 now := carbon.Now() paymentMethod := &models.PaymentMethod{} createData := map[string]any{ "name": name, "code": code, "type": paymentType, "config": string(configJSON), "is_active": isActive, "sort": sort, "description": description, "created_at": now, "updated_at": now, } if err := facades.Orm().Query().Model(paymentMethod).Create(createData); err != nil { return nil, apperrors.ErrCreateFailed.WithError(err) } return paymentMethod, nil } // UpdatePaymentMethod 更新支付方式 func (s *PaymentServiceImpl) UpdatePaymentMethod(id uint, name string, config map[string]any, isActive bool, sort int, description string) error { paymentMethod, err := s.GetPaymentMethodByID(id) if err != nil { return err } // 序列化配置 var configJSON string if config != nil { configBytes, err := json.Marshal(config) if err != nil { return apperrors.ErrPaymentConfigRequired.WithError(err) } configJSON = string(configBytes) } else { configJSON = paymentMethod.Config // 保持原有配置 } updateData := map[string]any{ "name": name, "config": configJSON, "is_active": isActive, "sort": sort, "description": description, } if _, err := facades.Orm().Query().Where("id", id).Update(&models.PaymentMethod{}, updateData); err != nil { return apperrors.ErrUpdateFailed.WithError(err) } return nil } // UpdatePaymentMethodModel 更新支付方式(新模式) func (s *PaymentServiceImpl) UpdatePaymentMethodModel(paymentMethod *models.PaymentMethod) error { if err := facades.Orm().Query().Save(paymentMethod); err != nil { return apperrors.ErrUpdateFailed.WithError(err) } return nil } // DeletePaymentMethod 删除支付方式 func (s *PaymentServiceImpl) DeletePaymentMethod(id uint) error { _, err := s.GetPaymentMethodByID(id) if err != nil { return err } if _, err := facades.Orm().Query().Where("id", id).Delete(&models.PaymentMethod{}); err != nil { return apperrors.ErrDeleteFailed.WithError(err) } return nil } // GetPaymentByID 根据ID获取支付记录(支持分表) func (s *PaymentServiceImpl) GetPaymentByID(id uint) (*models.Payment, error) { // 使用分表查找 payment, err := s.findPaymentByID(id) if err != nil { return nil, err } // 手动加载支付方式 if payment.PaymentMethodID > 0 { paymentMethod, err := s.GetPaymentMethodByID(payment.PaymentMethodID) if err == nil { payment.PaymentMethod = *paymentMethod } } return payment, nil } // GetPaymentByPaymentNo 根据支付单号获取支付记录(支持分表,直接定位更高效) func (s *PaymentServiceImpl) GetPaymentByPaymentNo(paymentNo string) (*models.Payment, error) { // 使用分表查找(通过支付单号直接定位分表) payment, err := s.findPaymentByPaymentNo(paymentNo) if err != nil { return nil, err } // 手动加载支付方式 if payment.PaymentMethodID > 0 { paymentMethod, err := s.GetPaymentMethodByID(payment.PaymentMethodID) if err == nil { payment.PaymentMethod = *paymentMethod } } return payment, nil } // GetPayments 获取支付记录列表(支持分表,限制不超过3个月) func (s *PaymentServiceImpl) GetPayments(filters PaymentFilters, page, pageSize int) ([]models.Payment, int64, error) { // 验证时间范围不超过3个月 valid, err := utils.ValidateTimeRange(filters.StartTime, filters.EndTime) if !valid { return nil, 0, err } // 获取需要查询的所有分表 tableNames := utils.GetShardingTableNames("payments", filters.StartTime, filters.EndTime) if len(tableNames) == 0 { return []models.Payment{}, 0, nil } var payments []models.Payment var total int64 // 如果只有一个分表,直接查询 if len(tableNames) == 1 { payments, total, err = s.querySinglePaymentTable(tableNames[0], filters, page, pageSize) } else { // 多个分表:使用 UNION ALL 在数据库层面合并 payments, total, err = s.queryMultiplePaymentTablesWithUnion(tableNames, filters, page, pageSize) } if err != nil { return nil, 0, apperrors.ErrQueryFailed.WithError(err) } // 批量加载支付方式 paymentMethodIDs := make(map[uint]bool) for _, payment := range payments { if payment.PaymentMethodID > 0 { paymentMethodIDs[payment.PaymentMethodID] = true } } // 批量查询支付方式 paymentMethodsMap := make(map[uint]*models.PaymentMethod) if len(paymentMethodIDs) > 0 { var ids []uint for id := range paymentMethodIDs { ids = append(ids, id) } // 转换为 []any idsAny := make([]any, len(ids)) for i, id := range ids { idsAny[i] = id } var paymentMethods []models.PaymentMethod if err := facades.Orm().Query().Model(&models.PaymentMethod{}).WhereIn("id", idsAny).Find(&paymentMethods); err == nil { for i := range paymentMethods { paymentMethodsMap[paymentMethods[i].ID] = &paymentMethods[i] } } } // 关联支付方式 for i := range payments { if pm, ok := paymentMethodsMap[payments[i].PaymentMethodID]; ok { payments[i].PaymentMethod = *pm } } return payments, total, nil } // CreatePayment 创建支付记录(写入分表) func (s *PaymentServiceImpl) CreatePayment(orderNo string, paymentMethodID uint, userID uint, amount float64, remark string) (*models.Payment, error) { // 验证金额 if amount <= 0 { return nil, apperrors.ErrPaymentAmountInvalid } // 验证支付方式 paymentMethod, err := s.GetPaymentMethodByID(paymentMethodID) if err != nil { return nil, err } if !paymentMethod.IsActive { return nil, apperrors.ErrPaymentMethodDisabled } // 当前时间用于分表 now := time.Now() // 确保分表存在 tableName, err := s.ensurePaymentShardingTableExists(now) if err != nil { return nil, apperrors.ErrCreatePaymentFailed.WithError(err) } // 生成支付单号(包含日期,便于后续定位分表) paymentNo := utils.GenerateShardingNo(utils.PaymentNoConfig) payment := &models.Payment{ PaymentNo: paymentNo, OrderNo: orderNo, PaymentMethodID: paymentMethodID, UserID: userID, Amount: amount, Status: "pending", Remark: remark, } // 写入分表 if err := facades.Orm().Query().Table(tableName).Create(payment); err != nil { errorlog.Record(context.Background(), "payment", "创建支付记录失败", map[string]any{ "table_name": tableName, "order_no": orderNo, "payment_method_id": paymentMethodID, "user_id": userID, "amount": amount, "error": err.Error(), }, "创建支付记录失败: %v", err) return nil, apperrors.ErrCreatePaymentFailed.WithError(err) } return payment, nil } // UpdatePaymentStatus 更新支付状态(支持分表) // paymentNo 可选,如果提供则可以更快定位分表 func (s *PaymentServiceImpl) UpdatePaymentStatus(paymentID uint, status string, thirdPartyNo string, payTime *time.Time, failReason string, notifyData map[string]any, paymentNo ...string) error { // 先查找支付记录以确定分表 var payment *models.Payment var err error if len(paymentNo) > 0 && paymentNo[0] != "" { payment, err = s.findPaymentByPaymentNo(paymentNo[0]) } else { payment, err = s.findPaymentByID(paymentID) } if err != nil { return apperrors.ErrPaymentNotFound.WithError(err) } // 确定分表名 timeStr := payment.CreatedAt.ToDateTimeString() createdAt, _ := utils.ParseDateTimeUTC(timeStr) tableName := utils.GetShardingTableName("payments", createdAt) updateData := map[string]any{ "status": status, } if thirdPartyNo != "" { updateData["third_party_no"] = thirdPartyNo } if payTime != nil { updateData["pay_time"] = payTime } if failReason != "" { updateData["fail_reason"] = failReason } if notifyData != nil { notifyJSON, err := json.Marshal(notifyData) if err == nil { updateData["notify_data"] = string(notifyJSON) } } if _, err := facades.Orm().Query().Table(tableName).Where("id", payment.ID).Update(&models.Payment{}, updateData); err != nil { return apperrors.ErrUpdateFailed.WithError(err) } return nil } // CreatePaymentOrder 创建支付订单(调用第三方支付) func (s *PaymentServiceImpl) CreatePaymentOrder(payment *models.Payment, clientIP string) (map[string]any, error) { // 获取支付方式 paymentMethod, err := s.GetPaymentMethodByID(payment.PaymentMethodID) if err != nil { return nil, err } // 解析配置 var config map[string]any if err := json.Unmarshal([]byte(paymentMethod.Config), &config); err != nil { return nil, apperrors.ErrPaymentConfigRequired.WithError(err) } // 根据支付类型调用不同的支付接口 switch paymentMethod.Type { case "wechat": return s.createWechatPayment(payment, config, clientIP) case "alipay": return s.createAlipayPayment(payment, config, clientIP) default: return nil, apperrors.ErrInvalidPaymentType.WithMessage(fmt.Sprintf("不支持的支付类型: %s", paymentMethod.Type)) } } // createWechatPayment 创建微信支付订单 func (s *PaymentServiceImpl) createWechatPayment(payment *models.Payment, config map[string]any, clientIP string) (map[string]any, error) { // 这里需要根据 gopay 的微信支付文档实现 // 示例代码,实际使用时需要根据 gopay 的最新 API 调整 // 参考: https://github.com/go-pay/gopay/tree/main/wechat/v3 // 获取配置参数 appID, _ := config["app_id"].(string) mchID, _ := config["mch_id"].(string) apiV3Key, _ := config["api_v3_key"].(string) certSerialNo, _ := config["cert_serial_no"].(string) privateKeyPath, _ := config["private_key_path"].(string) if appID == "" || mchID == "" || apiV3Key == "" { return nil, apperrors.ErrPaymentConfigRequired.WithMessage("微信支付配置不完整") } // 创建微信支付客户端 client, err := wechat.NewClientV3(appID, mchID, apiV3Key, certSerialNo) if err != nil { return nil, apperrors.ErrCreatePaymentFailed.WithError(err) } // 设置私钥(如果需要) if privateKeyPath != "" { // 这里需要根据 gopay 的实际 API 设置私钥 // client.SetPrivateKey(...) } // 设置回调地址(需要从配置中读取) notifyURL, _ := config["notify_url"].(string) if notifyURL == "" { notifyURL = fmt.Sprintf("%s/api/payment/notify/wechat", facades.Config().GetString("app.url")) } // 创建支付订单 bm := make(gopay.BodyMap) bm.Set("out_trade_no", payment.PaymentNo) bm.Set("description", payment.Remark) bm.Set("amount", map[string]any{ "total": int(payment.Amount * 100), // 转换为分 "currency": "CNY", }) bm.Set("notify_url", notifyURL) bm.Set("payer", map[string]any{ "openid": config["openid"], // 需要从订单或用户信息中获取 }) // 注意:这里需要根据 gopay 的最新 API 调用 // 示例代码,实际使用时需要根据 gopay 的最新文档调整 ctx := context.Background() wxRsp, err := client.V3TransactionJsapi(ctx, bm) if err != nil { return nil, apperrors.ErrCreatePaymentFailed.WithError(err) } if wxRsp.Code != wechat.Success { return nil, apperrors.ErrCreatePaymentFailed.WithMessage(wxRsp.Error) } return map[string]any{ "payment_no": payment.PaymentNo, "prepay_id": wxRsp.Response.PrepayId, // PaySign 可能需要从其他地方获取或计算 }, nil } // createAlipayPayment 创建支付宝支付订单 func (s *PaymentServiceImpl) createAlipayPayment(payment *models.Payment, config map[string]any, clientIP string) (map[string]any, error) { // 这里需要根据 gopay 的支付宝文档实现 // 示例代码,实际使用时需要根据 gopay 的最新 API 调整 // 参考: https://github.com/go-pay/gopay/tree/main/alipay // 获取配置参数 appID, _ := config["app_id"].(string) privateKey, _ := config["private_key"].(string) // appCertPublicKey, _ := config["app_cert_public_key"].(string) // alipayRootCert, _ := config["alipay_root_cert"].(string) // alipayPublicCert, _ := config["alipay_public_cert"].(string) if appID == "" || privateKey == "" { return nil, apperrors.ErrPaymentConfigRequired.WithMessage("支付宝配置不完整") } // 创建支付宝客户端 client, err := alipay.NewClient(appID, privateKey, false) if err != nil { return nil, apperrors.ErrCreatePaymentFailed.WithError(err) } // 设置证书(如果需要) // 注意:这里需要根据 gopay 的最新 API 设置证书 // 示例代码,实际使用时需要根据 gopay 的最新文档调整 // if appCertPublicKey != "" { // client.SetAppCertPublicKey(appCertPublicKey) // } // if alipayRootCert != "" { // client.SetAlipayRootCert(alipayRootCert) // } // if alipayPublicCert != "" { // client.SetAlipayPublicCert(alipayPublicCert) // } // 设置回调地址 notifyURL, _ := config["notify_url"].(string) if notifyURL == "" { notifyURL = fmt.Sprintf("%s/api/payment/notify/alipay", facades.Config().GetString("app.url")) } client.SetNotifyUrl(notifyURL) // 创建支付订单 bm := make(gopay.BodyMap) bm.Set("out_trade_no", payment.PaymentNo) bm.Set("subject", payment.Remark) bm.Set("total_amount", fmt.Sprintf("%.2f", payment.Amount)) bm.Set("product_code", "QUICK_MSECURITY_PAY") ctx := context.Background() payUrl, err := client.TradeAppPay(ctx, bm) if err != nil { return nil, apperrors.ErrCreatePaymentFailed.WithError(err) } return map[string]any{ "payment_no": payment.PaymentNo, "pay_url": payUrl, }, nil } // QueryPaymentOrder 查询支付订单状态 func (s *PaymentServiceImpl) QueryPaymentOrder(payment *models.Payment) (map[string]any, error) { // 获取支付方式 paymentMethod, err := s.GetPaymentMethodByID(payment.PaymentMethodID) if err != nil { return nil, err } // 解析配置 var config map[string]any if err := json.Unmarshal([]byte(paymentMethod.Config), &config); err != nil { return nil, apperrors.ErrPaymentConfigRequired.WithError(err) } // 根据支付类型查询 switch paymentMethod.Type { case "wechat": return s.queryWechatPayment(payment, config) case "alipay": return s.queryAlipayPayment(payment, config) default: return nil, apperrors.ErrInvalidPaymentType.WithMessage(fmt.Sprintf("不支持的支付类型: %s", paymentMethod.Type)) } } // queryWechatPayment 查询微信支付订单状态 func (s *PaymentServiceImpl) queryWechatPayment(payment *models.Payment, config map[string]any) (map[string]any, error) { // 实现微信支付查询逻辑 // 这里需要根据 gopay 的微信支付文档实现 return nil, fmt.Errorf("微信支付查询功能待实现") } // queryAlipayPayment 查询支付宝支付订单状态 func (s *PaymentServiceImpl) queryAlipayPayment(payment *models.Payment, config map[string]any) (map[string]any, error) { // 实现支付宝支付查询逻辑 // 这里需要根据 gopay 的支付宝文档实现 return nil, fmt.Errorf("支付宝支付查询功能待实现") } // HandlePaymentNotify 处理支付回调通知 func (s *PaymentServiceImpl) HandlePaymentNotify(paymentMethod *models.PaymentMethod, notifyData map[string]any) (*models.Payment, error) { // 根据支付类型处理回调 switch paymentMethod.Type { case "wechat": return s.handleWechatNotify(paymentMethod, notifyData) case "alipay": return s.handleAlipayNotify(paymentMethod, notifyData) default: return nil, apperrors.ErrInvalidPaymentType.WithMessage(fmt.Sprintf("不支持的支付类型: %s", paymentMethod.Type)) } } // handleWechatNotify 处理微信支付回调 func (s *PaymentServiceImpl) handleWechatNotify(paymentMethod *models.PaymentMethod, notifyData map[string]any) (*models.Payment, error) { // 实现微信支付回调处理逻辑 // 这里需要根据 gopay 的微信支付文档实现 return nil, fmt.Errorf("微信支付回调处理功能待实现") } // handleAlipayNotify 处理支付宝支付回调 func (s *PaymentServiceImpl) handleAlipayNotify(paymentMethod *models.PaymentMethod, notifyData map[string]any) (*models.Payment, error) { // 实现支付宝支付回调处理逻辑 // 这里需要根据 gopay 的支付宝文档实现 return nil, fmt.Errorf("支付宝支付回调处理功能待实现") } // buildPaymentMethodWhereClause 构建支付方式查询的 WHERE 条件 func (s *PaymentServiceImpl) buildPaymentMethodWhereClause(filters PaymentMethodFilters) (string, []any) { var conditions []string var args []any if filters.Name != "" { conditions = append(conditions, "name LIKE ?") args = append(args, "%"+filters.Name+"%") } if filters.Code != "" { conditions = append(conditions, "code = ?") args = append(args, filters.Code) } if filters.Type != "" { conditions = append(conditions, "type = ?") args = append(args, filters.Type) } if filters.IsActive != "" { switch filters.IsActive { case "1": conditions = append(conditions, "is_active = ?") args = append(args, true) case "0": conditions = append(conditions, "is_active = ?") args = append(args, false) } } if filters.Description != "" { conditions = append(conditions, "description LIKE ?") args = append(args, "%"+filters.Description+"%") } if len(conditions) == 0 { return "", nil } return strings.Join(conditions, " AND "), args } // buildPaymentWhereClause 构建支付记录查询的 WHERE 条件 func (s *PaymentServiceImpl) buildPaymentWhereClause(filters PaymentFilters) (string, []any) { var conditions []string var args []any if filters.PaymentNo != "" { conditions = append(conditions, "payment_no LIKE ?") args = append(args, filters.PaymentNo+"%") } if filters.OrderNo != "" { conditions = append(conditions, "order_no LIKE ?") args = append(args, filters.OrderNo+"%") } if filters.PaymentMethodID > 0 { conditions = append(conditions, "payment_method_id = ?") args = append(args, filters.PaymentMethodID) } if filters.UserID > 0 { conditions = append(conditions, "user_id = ?") args = append(args, filters.UserID) } if filters.Status != "" { conditions = append(conditions, "status = ?") args = append(args, filters.Status) } if !filters.StartTime.IsZero() { conditions = append(conditions, "created_at >= ?") args = append(args, filters.StartTime) } if !filters.EndTime.IsZero() { conditions = append(conditions, "created_at <= ?") args = append(args, filters.EndTime) } if len(conditions) == 0 { return "", nil } return strings.Join(conditions, " AND "), args } // applyOrderBy 应用排序 func (s *PaymentServiceImpl) 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, "name": true, "code": true, "type": true, "is_active": true, "sort": 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") } } // getPaymentTableColumns 获取支付记录表的所有列名(用于 UNION ALL 查询) func (s *PaymentServiceImpl) getPaymentTableColumns() string { columns := []string{ "id", "payment_no", "order_no", "payment_method_id", "user_id", "amount", "status", "third_party_no", "pay_time", "fail_reason", "notify_data", "remark", "created_at", "updated_at", "deleted_at", } return strings.Join(columns, ", ") } // buildPaymentShardingWhereClause 构建支付记录分表查询的 WHERE 条件(用于通用分表查询服务) func (s *PaymentServiceImpl) buildPaymentShardingWhereClause(filters any) (string, []any) { paymentFilters, ok := filters.(PaymentFilters) if !ok { return "", nil } var conditions []string var args []any // 时间范围(必填) if !paymentFilters.StartTime.IsZero() { conditions = append(conditions, "created_at >= ?") args = append(args, paymentFilters.StartTime) } if !paymentFilters.EndTime.IsZero() { conditions = append(conditions, "created_at <= ?") args = append(args, paymentFilters.EndTime) } // 支付单号筛选 if paymentFilters.PaymentNo != "" { conditions = append(conditions, "payment_no = ?") args = append(args, paymentFilters.PaymentNo) } // 订单号筛选 if paymentFilters.OrderNo != "" { conditions = append(conditions, "order_no = ?") args = append(args, paymentFilters.OrderNo) } // 支付方式ID筛选 if paymentFilters.PaymentMethodID > 0 { conditions = append(conditions, "payment_method_id = ?") args = append(args, paymentFilters.PaymentMethodID) } // 用户ID筛选 if paymentFilters.UserID > 0 { conditions = append(conditions, "user_id = ?") args = append(args, paymentFilters.UserID) } // 状态筛选 if paymentFilters.Status != "" { conditions = append(conditions, "status = ?") args = append(args, paymentFilters.Status) } return strings.Join(conditions, " AND "), args } // findPaymentByPaymentNo 通过支付单号查找支付记录(直接定位分表) // 支付单号格式:PAY + YYYYMMDD + ULID,可以从中提取日期 func (s *PaymentServiceImpl) findPaymentByPaymentNo(paymentNo string) (*models.Payment, error) { // 从支付单号中解析日期 if parsedTime, ok := utils.ParseShardingNoDate(paymentNo, utils.PaymentNoConfig); ok { // 成功解析日期,直接查询对应分表 tableName := utils.GetShardingTableName("payments", parsedTime) if facades.Schema().HasTable(tableName) { var payment models.Payment if err := facades.Orm().Query().Model(&models.Payment{}).Table(tableName).Where("payment_no", paymentNo).First(&payment); err == nil { return &payment, nil } } } // 如果无法从支付单号解析日期,或者在对应分表中找不到,遍历最近6个月的分表 now := time.Now().UTC() startTime := now.AddDate(0, -6, 0) tableNames := utils.GetShardingTableNames("payments", startTime, now) // 从最新的分表开始查询(Model 自动应用软删除过滤) for i := len(tableNames) - 1; i >= 0; i-- { if !facades.Schema().HasTable(tableNames[i]) { continue } var payment models.Payment if err := facades.Orm().Query().Model(&models.Payment{}).Table(tableNames[i]).Where("payment_no", paymentNo).First(&payment); err == nil { return &payment, nil } } return nil, apperrors.ErrPaymentNotFound } // findPaymentByID 通过支付记录ID查找支付记录 func (s *PaymentServiceImpl) findPaymentByID(paymentID uint, paymentNo ...string) (*models.Payment, error) { // 如果提供了支付单号,优先使用支付单号直接定位分表(更高效) if len(paymentNo) > 0 && paymentNo[0] != "" { payment, err := s.findPaymentByPaymentNo(paymentNo[0]) if err == nil { if paymentID > 0 && payment.ID != paymentID { // 支付单号找到了但ID不匹配,继续用ID查找 } else { return payment, nil } } } // 如果没有支付单号或通过支付单号查找失败,使用ID遍历分表 if paymentID == 0 { return nil, apperrors.ErrPaymentNotFound } // 查询最近6个月的分表(足够覆盖大部分场景) now := time.Now().UTC() startTime := now.AddDate(0, -6, 0) tableNames := utils.GetShardingTableNames("payments", startTime, now) // 从最新的分表开始查询(Model 自动应用软删除过滤) for i := len(tableNames) - 1; i >= 0; i-- { if !facades.Schema().HasTable(tableNames[i]) { continue } var payment models.Payment if err := facades.Orm().Query().Model(&models.Payment{}).Table(tableNames[i]).Where("id", paymentID).First(&payment); err == nil { return &payment, nil } } return nil, apperrors.ErrPaymentNotFound } // querySinglePaymentTable 查询单个分表 func (s *PaymentServiceImpl) querySinglePaymentTable(tableName string, filters PaymentFilters, page, pageSize int) ([]models.Payment, int64, error) { // 应用排序 orderBy := filters.OrderBy if orderBy == "" { orderBy = "created_at:desc" } // 构建基础查询条件 query := s.buildPaymentShardingQuery(tableName, filters) query = s.applyOrderBy(query, orderBy) // 获取总数(使用 CountOptimizer 优化,超过阈值使用 EXPLAIN 估算) whereClause, whereArgs := s.buildPaymentShardingWhereClause(filters) countOptimizer := utils.NewCountOptimizer(PaymentCountThreshold, "payment") total, _, err := countOptimizer.OptimizedCountWithTable(tableName, whereClause, whereArgs...) if err != nil { return nil, 0, err } // 执行分页查询 var payments []models.Payment if err := query.Offset((page - 1) * pageSize).Limit(pageSize).Find(&payments); err != nil { return nil, 0, err } return payments, total, nil } // buildPaymentShardingQuery 构建分表查询条件 func (s *PaymentServiceImpl) buildPaymentShardingQuery(tableName string, filters PaymentFilters) 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) } // 支付单号筛选 if filters.PaymentNo != "" { query = query.Where("payment_no = ?", filters.PaymentNo) } // 订单号筛选 if filters.OrderNo != "" { query = query.Where("order_no = ?", filters.OrderNo) } // 支付方式ID筛选 if filters.PaymentMethodID > 0 { query = query.Where("payment_method_id = ?", filters.PaymentMethodID) } // 用户ID筛选 if filters.UserID > 0 { query = query.Where("user_id = ?", filters.UserID) } // 状态筛选 if filters.Status != "" { query = query.Where("status = ?", filters.Status) } return query } // queryMultiplePaymentTablesWithUnion 使用 UNION ALL 查询多个分表 func (s *PaymentServiceImpl) queryMultiplePaymentTablesWithUnion(tableNames []string, filters PaymentFilters, page, pageSize int) ([]models.Payment, int64, error) { var payments []models.Payment total, err := s.shardingQueryService.QueryMultipleTables(tableNames, filters, page, pageSize, &payments) if err != nil { return nil, 0, err } return payments, total, nil } // ensurePaymentShardingTableExists 确保支付记录分表存在 func (s *PaymentServiceImpl) ensurePaymentShardingTableExists(paymentTime time.Time) (string, error) { tableName := utils.GetShardingTableName("payments", paymentTime) // 检查分表是否存在 if !facades.Schema().HasTable(tableName) { // 使用迁移函数创建分表 if err := migrations.CreatePaymentsShardingTable(tableName); err != nil { errorlog.Record(context.Background(), "payment", "创建支付记录分表失败", map[string]any{ "table_name": tableName, "error": err.Error(), }, "创建支付记录分表失败: %v", err) return "", fmt.Errorf("创建支付记录分表失败: %v", err) } } return tableName, nil }