feat: 添加订单退款处理的异步任务及相关逻辑
This commit is contained in:
45
backend/app/jobs/args/order_refund.go
Normal file
45
backend/app/jobs/args/order_refund.go
Normal file
@@ -0,0 +1,45 @@
|
||||
package args
|
||||
|
||||
import (
|
||||
"quyun/v2/providers/job"
|
||||
|
||||
. "github.com/riverqueue/river"
|
||||
"go.ipao.vip/atom/contracts"
|
||||
)
|
||||
|
||||
var _ contracts.JobArgs = OrderRefundJob{}
|
||||
|
||||
// OrderRefundJob 表示“订单退款处理”的一次性异步任务。
|
||||
//
|
||||
// 设计说明:
|
||||
// - 该任务用于将订单从 refunding 推进到 refunded/failed(由 worker 执行核心退款逻辑)。
|
||||
// - 幂等由 River Unique + 业务侧 DB 幂等(ledger idempotency、状态机)共同保证。
|
||||
type OrderRefundJob struct {
|
||||
// TenantID 租户ID(用于多租户隔离与唯一性维度)。
|
||||
TenantID int64 `json:"tenant_id" river:"unique"`
|
||||
// OrderID 订单ID(用于唯一性维度)。
|
||||
OrderID int64 `json:"order_id" river:"unique"`
|
||||
|
||||
// OperatorUserID 退款操作人用户ID(租户管理员/系统),用于 worker 在必要时回填审计字段。
|
||||
OperatorUserID int64 `json:"operator_user_id"`
|
||||
// Force 是否强制退款(绕过时间窗)。
|
||||
Force bool `json:"force"`
|
||||
// Reason 退款原因(用于审计;worker 可用于补齐订单字段)。
|
||||
Reason string `json:"reason"`
|
||||
}
|
||||
|
||||
func (OrderRefundJob) Kind() string { return "order_refund" }
|
||||
|
||||
func (a OrderRefundJob) UniqueID() string { return a.Kind() }
|
||||
|
||||
func (OrderRefundJob) InsertOpts() InsertOpts {
|
||||
return InsertOpts{
|
||||
Queue: job.QueueDefault,
|
||||
Priority: job.PriorityDefault,
|
||||
// 失败可重试;由 worker 判断不可重试的场景并 JobCancel。
|
||||
MaxAttempts: 10,
|
||||
UniqueOpts: UniqueOpts{
|
||||
ByArgs: true,
|
||||
},
|
||||
}
|
||||
}
|
||||
58
backend/app/jobs/order_refund.go
Normal file
58
backend/app/jobs/order_refund.go
Normal file
@@ -0,0 +1,58 @@
|
||||
package jobs
|
||||
|
||||
import (
|
||||
"context"
|
||||
"time"
|
||||
|
||||
jobs_args "quyun/v2/app/jobs/args"
|
||||
"quyun/v2/app/services"
|
||||
|
||||
. "github.com/riverqueue/river"
|
||||
log "github.com/sirupsen/logrus"
|
||||
)
|
||||
|
||||
var _ Worker[jobs_args.OrderRefundJob] = (*OrderRefundJobWorker)(nil)
|
||||
|
||||
// OrderRefundJobWorker 负责执行订单退款的异步处理。
|
||||
//
|
||||
// @provider(job)
|
||||
type OrderRefundJobWorker struct {
|
||||
WorkerDefaults[jobs_args.OrderRefundJob]
|
||||
}
|
||||
|
||||
func (w *OrderRefundJobWorker) Work(ctx context.Context, job *Job[jobs_args.OrderRefundJob]) error {
|
||||
args := job.Args
|
||||
|
||||
logger := log.WithFields(log.Fields{
|
||||
"job_kind": args.Kind(),
|
||||
"tenant_id": args.TenantID,
|
||||
"order_id": args.OrderID,
|
||||
"operator_user_id": args.OperatorUserID,
|
||||
"force": args.Force,
|
||||
"attempt": job.Attempt,
|
||||
})
|
||||
|
||||
// 只允许在异步 worker 层做执行,不要在这里再次入队其他任务(避免耦合与递归依赖)。
|
||||
logger.Info("jobs.order_refund.start")
|
||||
|
||||
_, err := services.Order.ProcessRefundingOrder(ctx, &services.ProcessRefundingOrderParams{
|
||||
TenantID: args.TenantID,
|
||||
OrderID: args.OrderID,
|
||||
OperatorUserID: args.OperatorUserID,
|
||||
Force: args.Force,
|
||||
Reason: args.Reason,
|
||||
Now: time.Now().UTC(),
|
||||
})
|
||||
if err != nil {
|
||||
// 业务层会返回可识别的“不可重试”错误:由它内部完成状态落库(failed)后,这里直接 cancel。
|
||||
if services.IsRefundJobNonRetryableError(err) {
|
||||
logger.WithError(err).Warn("jobs.order_refund.cancel")
|
||||
return JobCancel(err)
|
||||
}
|
||||
logger.WithError(err).Warn("jobs.order_refund.retry")
|
||||
return err
|
||||
}
|
||||
|
||||
logger.Info("jobs.order_refund.ok")
|
||||
return nil
|
||||
}
|
||||
@@ -37,5 +37,17 @@ func Provide(opts ...opt.Option) error {
|
||||
}, atom.GroupInitial); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := container.Container.Provide(func(
|
||||
__job *job.Job,
|
||||
) (contracts.Initial, error) {
|
||||
obj := &OrderRefundJobWorker{}
|
||||
if err := river.AddWorkerSafely(__job.Workers, obj); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return obj, nil
|
||||
}, atom.GroupInitial); err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user