mirror of
https://github.com/Rain-kl/OpenFlare.git
synced 2026-10-01 14:46:36 +08:00
191 lines
5.3 KiB
Go
191 lines
5.3 KiB
Go
/*
|
|
Copyright 2025 linux.do
|
|
|
|
Licensed under the Apache License, Version 2.0 (the "License");
|
|
you may not use this file except in compliance with the License.
|
|
You may obtain a copy of the License at
|
|
|
|
http://www.apache.org/licenses/LICENSE-2.0
|
|
|
|
Unless required by applicable law or agreed to in writing, software
|
|
distributed under the License is distributed on an "AS IS" BASIS,
|
|
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
See the License for the specific language governing permissions and
|
|
limitations under the License.
|
|
*/
|
|
|
|
package order
|
|
|
|
import (
|
|
"context"
|
|
"encoding/json"
|
|
"fmt"
|
|
"time"
|
|
|
|
"github.com/hibiken/asynq"
|
|
"github.com/linux-do/credit/internal/config"
|
|
"github.com/linux-do/credit/internal/db"
|
|
"github.com/linux-do/credit/internal/logger"
|
|
"github.com/linux-do/credit/internal/model"
|
|
"github.com/linux-do/credit/internal/service"
|
|
"github.com/linux-do/credit/internal/util"
|
|
"gorm.io/gorm"
|
|
"gorm.io/gorm/clause"
|
|
)
|
|
|
|
// HandleSyncOrdersToClickHouse 同步订单数据
|
|
func HandleSyncOrdersToClickHouse(ctx context.Context, t *asynq.Task) error {
|
|
if !config.Config.ClickHouse.Enabled {
|
|
logger.InfoF(ctx, "ClickHouse 未启用,跳过订单同步")
|
|
return nil
|
|
}
|
|
|
|
var payload struct {
|
|
StartTime *time.Time `json:"start_time"`
|
|
EndTime *time.Time `json:"end_time"`
|
|
}
|
|
_ = json.Unmarshal(t.Payload(), &payload)
|
|
|
|
var startOfDay, endOfDay time.Time
|
|
if payload.StartTime != nil && payload.EndTime != nil {
|
|
startOfDay = *payload.StartTime
|
|
endOfDay = *payload.EndTime
|
|
} else {
|
|
now := time.Now()
|
|
yesterday := now.AddDate(0, 0, -1)
|
|
startOfDay = time.Date(yesterday.Year(), yesterday.Month(), yesterday.Day(), 0, 0, 0, 0, yesterday.Location())
|
|
endOfDay = startOfDay.AddDate(0, 0, 1)
|
|
}
|
|
|
|
logger.InfoF(ctx, "开始同步订单到 ClickHouse: %s ~ %s", startOfDay.Format("2006-01-02 15:04:05"), endOfDay.Format("2006-01-02 15:04:05"))
|
|
|
|
pageSize := 10000
|
|
lastID := uint64(0)
|
|
totalSynced := 0
|
|
|
|
for {
|
|
var orders []model.Order
|
|
if err := db.DB(ctx).
|
|
Where("updated_at >= ? AND updated_at < ? AND id > ?", startOfDay, endOfDay, lastID).
|
|
Order("id ASC").
|
|
Limit(pageSize).
|
|
Find(&orders).Error; err != nil {
|
|
logger.ErrorF(ctx, "查询订单失败: %v", err)
|
|
return err
|
|
}
|
|
|
|
if len(orders) == 0 {
|
|
break
|
|
}
|
|
|
|
if err := batchInsertToClickHouse(ctx, orders); err != nil {
|
|
logger.ErrorF(ctx, "写入 ClickHouse 失败: %v", err)
|
|
return err
|
|
}
|
|
|
|
totalSynced += len(orders)
|
|
lastID = orders[len(orders)-1].ID
|
|
|
|
logger.InfoF(ctx, "已同步 %d 条订单,当前进度 ID: %d", len(orders), lastID)
|
|
}
|
|
|
|
logger.InfoF(ctx, "订单同步完成,共同步 %d 条", totalSynced)
|
|
return nil
|
|
}
|
|
|
|
// batchInsertToClickHouse 批量写入订单
|
|
func batchInsertToClickHouse(ctx context.Context, orders []model.Order) error {
|
|
batch, err := db.ChConn.PrepareBatch(ctx, `
|
|
INSERT INTO orders (
|
|
id, order_name, merchant_order_no, client_id,
|
|
payer_user_id, payee_user_id, amount,
|
|
status, type, remark, payment_type,
|
|
trade_time, expires_at, created_at, updated_at
|
|
) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
|
|
`)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
for _, order := range orders {
|
|
if err := batch.Append(
|
|
order.ID,
|
|
order.OrderName,
|
|
util.DerefString(order.MerchantOrderNo),
|
|
order.ClientID,
|
|
order.PayerUserID,
|
|
order.PayeeUserID,
|
|
order.Amount,
|
|
string(order.Status),
|
|
string(order.Type),
|
|
order.Remark,
|
|
order.PaymentType,
|
|
order.TradeTime,
|
|
order.ExpiresAt,
|
|
order.CreatedAt,
|
|
order.UpdatedAt,
|
|
); err != nil {
|
|
return err
|
|
}
|
|
}
|
|
|
|
return batch.Send()
|
|
}
|
|
|
|
// HandleSettlePendingPayments 处理到期的延迟到账订单
|
|
func HandleSettlePendingPayments(ctx context.Context, _ *asynq.Task) error {
|
|
batchSize := 500
|
|
totalSettled := 0
|
|
|
|
for {
|
|
orders, err := model.GetDueTransferOrders(ctx, batchSize)
|
|
if err != nil {
|
|
logger.ErrorF(ctx, "查询到期待结算订单失败: %v", err)
|
|
return err
|
|
}
|
|
|
|
if len(orders) == 0 {
|
|
break
|
|
}
|
|
|
|
for _, order := range orders {
|
|
if err := db.DB(ctx).Transaction(func(tx *gorm.DB) error {
|
|
// 锁定行
|
|
var lockedOrderTransfer model.OrderTransfer
|
|
if err := tx.Clauses(clause.Locking{Strength: "UPDATE", Options: "NOWAIT"}).
|
|
Where("id = ? AND status = ?", order.ID, model.OrderTransferStatusPending).
|
|
First(&lockedOrderTransfer).Error; err != nil {
|
|
return fmt.Errorf("lock order failed: %w", err)
|
|
}
|
|
|
|
// 移动到可用余额
|
|
if err := service.SettlePendingToAvailable(tx, lockedOrderTransfer.PayeeUserID, lockedOrderTransfer.Amount); err != nil {
|
|
return fmt.Errorf("settle balance failed: %w", err)
|
|
}
|
|
|
|
// 更新订单状态
|
|
if err := tx.Model(&lockedOrderTransfer).Updates(
|
|
map[string]interface{}{
|
|
"transfer_at": time.Now(),
|
|
"status": model.OrderTransferStatusCompleted,
|
|
},
|
|
).Error; err != nil {
|
|
return fmt.Errorf("update order status failed: %w", err)
|
|
}
|
|
|
|
logger.InfoF(ctx, "订单[ID:%d]延迟到账结算成功: 商户[ID:%d] 金额[%s]", lockedOrderTransfer.ID, lockedOrderTransfer.PayeeUserID, lockedOrderTransfer.Amount.String())
|
|
return nil
|
|
}); err != nil {
|
|
logger.ErrorF(ctx, "结算订单[ID:%d]失败: %v", order.ID, err)
|
|
continue
|
|
}
|
|
totalSettled++
|
|
}
|
|
}
|
|
|
|
if totalSettled > 0 {
|
|
logger.InfoF(ctx, "延迟到账结算完成,共结算 %d 笔", totalSettled)
|
|
}
|
|
return nil
|
|
}
|