This commit is contained in:
61
internal/task/audit_daily_archive.go
Normal file
61
internal/task/audit_daily_archive.go
Normal file
@@ -0,0 +1,61 @@
|
||||
package task
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"time"
|
||||
|
||||
"github.com/bytedance/sonic"
|
||||
"github.com/hibiken/asynq"
|
||||
"go.uber.org/zap"
|
||||
|
||||
"github.com/break/junhong_cmp_fiber/internal/application/auditarchive"
|
||||
"github.com/break/junhong_cmp_fiber/pkg/constants"
|
||||
)
|
||||
|
||||
// AuditDailyArchivePayload 是人工补档时可选的任务载荷。
|
||||
type AuditDailyArchivePayload struct {
|
||||
ArchiveDate string `json:"archive_date"`
|
||||
}
|
||||
|
||||
// AuditDailyArchiveHandler 处理统一审计每日冷归档任务。
|
||||
type AuditDailyArchiveHandler struct {
|
||||
service *auditarchive.Service
|
||||
logger *zap.Logger
|
||||
}
|
||||
|
||||
// NewAuditDailyArchiveHandler 创建统一审计每日冷归档任务处理器。
|
||||
func NewAuditDailyArchiveHandler(service *auditarchive.Service, logger *zap.Logger) *AuditDailyArchiveHandler {
|
||||
return &AuditDailyArchiveHandler{service: service, logger: logger}
|
||||
}
|
||||
|
||||
// Handle 执行前一完整自然日归档,或按任务载荷补档指定自然日。
|
||||
func (h *AuditDailyArchiveHandler) Handle(ctx context.Context, task *asynq.Task) error {
|
||||
if h.service == nil {
|
||||
return fmt.Errorf("统一审计归档服务未配置")
|
||||
}
|
||||
var err error
|
||||
if len(task.Payload()) == 0 {
|
||||
err = h.service.ArchivePreviousDay(ctx)
|
||||
} else {
|
||||
var payload AuditDailyArchivePayload
|
||||
if unmarshalErr := sonic.Unmarshal(task.Payload(), &payload); unmarshalErr != nil {
|
||||
return fmt.Errorf("解析统一审计归档任务载荷失败: %w", unmarshalErr)
|
||||
}
|
||||
location, locationErr := time.LoadLocation(constants.AuditArchiveTimezone)
|
||||
if locationErr != nil {
|
||||
return fmt.Errorf("加载统一审计归档时区失败: %w", locationErr)
|
||||
}
|
||||
archiveDate, parseErr := time.ParseInLocation(time.DateOnly, payload.ArchiveDate, location)
|
||||
if parseErr != nil {
|
||||
return fmt.Errorf("解析统一审计归档日期失败: %w", parseErr)
|
||||
}
|
||||
err = h.service.ArchiveDate(ctx, archiveDate)
|
||||
}
|
||||
if err != nil {
|
||||
h.logger.Error("统一审计每日冷归档失败", zap.Error(err))
|
||||
return err
|
||||
}
|
||||
h.logger.Info("统一审计每日冷归档完成")
|
||||
return nil
|
||||
}
|
||||
64
internal/task/audit_monthly_retention.go
Normal file
64
internal/task/audit_monthly_retention.go
Normal file
@@ -0,0 +1,64 @@
|
||||
package task
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"time"
|
||||
|
||||
"github.com/bytedance/sonic"
|
||||
"github.com/hibiken/asynq"
|
||||
"go.uber.org/zap"
|
||||
|
||||
"github.com/break/junhong_cmp_fiber/internal/application/auditarchive"
|
||||
)
|
||||
|
||||
// AuditMonthlyRetentionPayload 是人工补跑月度清理时可选的任务载荷。
|
||||
type AuditMonthlyRetentionPayload struct {
|
||||
ArchiveMonth string `json:"archive_month"`
|
||||
}
|
||||
|
||||
// AuditMonthlyRetentionHandler 处理归档完整性门禁与上月在线日志物理清理。
|
||||
type AuditMonthlyRetentionHandler struct {
|
||||
service *auditarchive.Service
|
||||
logger *zap.Logger
|
||||
}
|
||||
|
||||
// NewAuditMonthlyRetentionHandler 创建月度日志留存清理处理器。
|
||||
func NewAuditMonthlyRetentionHandler(service *auditarchive.Service, logger *zap.Logger) *AuditMonthlyRetentionHandler {
|
||||
return &AuditMonthlyRetentionHandler{service: service, logger: logger}
|
||||
}
|
||||
|
||||
// Handle 校验整月归档后按固定顺序分批物理删除 PostgreSQL 在线日志。
|
||||
func (h *AuditMonthlyRetentionHandler) Handle(ctx context.Context, task *asynq.Task) error {
|
||||
if h.service == nil {
|
||||
return fmt.Errorf("月度日志留存清理服务未配置")
|
||||
}
|
||||
startedAt := time.Now()
|
||||
var result auditarchive.RetentionResult
|
||||
var err error
|
||||
if len(task.Payload()) == 0 {
|
||||
result, err = h.service.CleanupPreviousMonth(ctx)
|
||||
} else {
|
||||
var payload AuditMonthlyRetentionPayload
|
||||
if unmarshalErr := sonic.Unmarshal(task.Payload(), &payload); unmarshalErr != nil {
|
||||
return fmt.Errorf("解析月度日志留存清理任务载荷失败: %w", unmarshalErr)
|
||||
}
|
||||
month, parseErr := parseArchiveMonth(payload.ArchiveMonth)
|
||||
if parseErr != nil {
|
||||
return parseErr
|
||||
}
|
||||
result, err = h.service.CleanupMonth(ctx, month)
|
||||
}
|
||||
fields := []zap.Field{
|
||||
zap.String("archive_month", result.Month), zap.Int64("audit_event_count", result.EventCount),
|
||||
zap.Int64("event_resource_count", result.ResourceCount), zap.Int64("integration_log_count", result.IntegrationCount),
|
||||
zap.Duration("duration", time.Since(startedAt)), zap.Int("manifest_count", len(result.ManifestKeys)),
|
||||
}
|
||||
if err != nil {
|
||||
fields = append(fields, zap.String("severity", "critical"), zap.Error(err))
|
||||
h.logger.Error("月度日志留存清理失败,PostgreSQL 整月清理已阻断或等待断点续跑", fields...)
|
||||
return err
|
||||
}
|
||||
h.logger.Info("月度日志留存清理完成", fields...)
|
||||
return nil
|
||||
}
|
||||
@@ -27,7 +27,10 @@ import (
|
||||
|
||||
// AutoPurchasePayload 充值后自动购包任务载荷
|
||||
type AutoPurchasePayload struct {
|
||||
RechargeOrderID uint `json:"recharge_order_id"`
|
||||
RechargeOrderID uint `json:"recharge_order_id"`
|
||||
RequestID string `json:"request_id,omitempty"`
|
||||
CorrelationID string `json:"correlation_id,omitempty"`
|
||||
ParentEventID string `json:"parent_event_id,omitempty"`
|
||||
}
|
||||
|
||||
// AutoPurchaseHandler 充值后自动购包任务处理器
|
||||
@@ -123,10 +126,14 @@ func (h *AutoPurchaseHandler) ProcessTask(ctx context.Context, task *asynq.Task)
|
||||
h.logger.Error("查询充值订单失败", zap.Uint("recharge_order_id", payload.RechargeOrderID), zap.Error(err))
|
||||
return err
|
||||
}
|
||||
correlationID := payload.CorrelationID
|
||||
if correlationID == "" {
|
||||
correlationID = rechargeOrder.RechargeOrderNo
|
||||
}
|
||||
ctx = auditcontext.With(ctx, auditcontext.Context{
|
||||
ActorKind: constants.AuditActorSystemTask, ActorID: constants.TaskTypeAutoPurchaseAfterRecharge,
|
||||
ActorName: "充值后自动购包任务", Source: constants.AuditSourceWorker,
|
||||
CorrelationID: rechargeOrder.RechargeOrderNo,
|
||||
RequestID: payload.RequestID, CorrelationID: correlationID, ParentEventID: payload.ParentEventID,
|
||||
})
|
||||
|
||||
if rechargeOrder.AutoPurchaseStatus == constants.AutoPurchaseStatusSuccess {
|
||||
@@ -301,7 +308,11 @@ func (h *AutoPurchaseHandler) ProcessTask(ctx context.Context, task *asynq.Task)
|
||||
|
||||
// 事务提交成功后触发佣金计算(不在事务内,防止任务提交后事务回滚的数据一致性问题)
|
||||
if h.asynqClient != nil && createdOrderID > 0 {
|
||||
payloadBytes, marshalErr := sonic.Marshal(map[string]any{"order_id": createdOrderID})
|
||||
linkage := auditcontext.From(ctx)
|
||||
payloadBytes, marshalErr := sonic.Marshal(CommissionCalculationPayload{
|
||||
OrderID: createdOrderID, RequestID: linkage.RequestID,
|
||||
CorrelationID: linkage.CorrelationID, ParentEventID: linkage.ParentEventID,
|
||||
})
|
||||
if marshalErr != nil {
|
||||
h.logger.Warn("佣金任务载荷序列化失败",
|
||||
zap.Uint("order_id", createdOrderID),
|
||||
|
||||
@@ -8,6 +8,8 @@ import (
|
||||
"go.uber.org/zap"
|
||||
|
||||
cardapp "github.com/break/junhong_cmp_fiber/internal/application/cardobservation"
|
||||
"github.com/break/junhong_cmp_fiber/pkg/auditcontext"
|
||||
"github.com/break/junhong_cmp_fiber/pkg/constants"
|
||||
"github.com/break/junhong_cmp_fiber/pkg/errors"
|
||||
)
|
||||
|
||||
@@ -32,6 +34,11 @@ func (h *CardObservationSeriesHandler) Handle(ctx context.Context, task *asynq.T
|
||||
h.logger.Error("解析卡观测序列任务载荷失败", zap.Error(err))
|
||||
return errors.Wrap(errors.CodeInvalidParam, err, "卡观测序列任务载荷无法解析")
|
||||
}
|
||||
ctx = auditcontext.With(ctx, auditcontext.Context{
|
||||
ActorKind: constants.AuditActorSystemTask, ActorID: constants.TaskTypeCardObservationSeries,
|
||||
ActorName: "卡观测序列任务", Source: constants.AuditSourceWorker,
|
||||
RequestID: payload.RequestID, CorrelationID: payload.CorrelationID, ParentEventID: payload.ParentEventID,
|
||||
})
|
||||
if err := h.service.Execute(ctx, payload); err != nil {
|
||||
h.logger.Warn("卡观测序列当前尝试失败",
|
||||
zap.String("series_id", payload.SeriesID), zap.Int("attempt", payload.Attempt), zap.Error(err))
|
||||
|
||||
@@ -18,7 +18,10 @@ const (
|
||||
)
|
||||
|
||||
type CommissionCalculationPayload struct {
|
||||
OrderID uint `json:"order_id"`
|
||||
OrderID uint `json:"order_id"`
|
||||
RequestID string `json:"request_id,omitempty"`
|
||||
CorrelationID string `json:"correlation_id,omitempty"`
|
||||
ParentEventID string `json:"parent_event_id,omitempty"`
|
||||
}
|
||||
|
||||
type CommissionCalculationHandler struct {
|
||||
@@ -48,9 +51,15 @@ func (h *CommissionCalculationHandler) HandleCommissionCalculation(ctx context.C
|
||||
)
|
||||
return asynq.SkipRetry
|
||||
}
|
||||
correlationID := payload.CorrelationID
|
||||
if correlationID == "" {
|
||||
correlationID = task.ResultWriter().TaskID()
|
||||
}
|
||||
ctx = auditcontext.With(ctx, auditcontext.Context{
|
||||
ActorKind: constants.AuditActorSystemTask, ActorID: constants.AuditActorIDCommissionCalculationWorker,
|
||||
ActorName: "订单佣金计算任务", Source: constants.AuditSourceWorker,
|
||||
RequestID: payload.RequestID, CorrelationID: correlationID,
|
||||
ParentEventID: payload.ParentEventID,
|
||||
})
|
||||
|
||||
if err := h.service.CalculateCommission(ctx, payload.OrderID); err != nil {
|
||||
|
||||
113
internal/task/integration_archive.go
Normal file
113
internal/task/integration_archive.go
Normal file
@@ -0,0 +1,113 @@
|
||||
package task
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"time"
|
||||
|
||||
"github.com/bytedance/sonic"
|
||||
"github.com/hibiken/asynq"
|
||||
"go.uber.org/zap"
|
||||
|
||||
"github.com/break/junhong_cmp_fiber/internal/application/auditarchive"
|
||||
"github.com/break/junhong_cmp_fiber/pkg/constants"
|
||||
)
|
||||
|
||||
// IntegrationDailyArchivePayload 是人工补档时可选的任务载荷。
|
||||
type IntegrationDailyArchivePayload struct {
|
||||
ArchiveDate string `json:"archive_date"`
|
||||
}
|
||||
|
||||
// IntegrationMonthlyFinalizePayload 是人工月度复核时可选的任务载荷。
|
||||
type IntegrationMonthlyFinalizePayload struct {
|
||||
ArchiveMonth string `json:"archive_month"`
|
||||
}
|
||||
|
||||
// IntegrationArchiveHandler 处理 Integration Log 每日归档与月度最终复核。
|
||||
type IntegrationArchiveHandler struct {
|
||||
service *auditarchive.Service
|
||||
logger *zap.Logger
|
||||
}
|
||||
|
||||
// NewIntegrationArchiveHandler 创建 Integration Log 归档任务处理器。
|
||||
func NewIntegrationArchiveHandler(service *auditarchive.Service, logger *zap.Logger) *IntegrationArchiveHandler {
|
||||
return &IntegrationArchiveHandler{service: service, logger: logger}
|
||||
}
|
||||
|
||||
// HandleDaily 执行前一完整自然日归档,或按任务载荷补档指定自然日。
|
||||
func (h *IntegrationArchiveHandler) HandleDaily(ctx context.Context, task *asynq.Task) error {
|
||||
if h.service == nil {
|
||||
return fmt.Errorf("Integration Log 归档服务未配置")
|
||||
}
|
||||
var err error
|
||||
if len(task.Payload()) == 0 {
|
||||
err = h.service.ArchivePreviousIntegrationDay(ctx)
|
||||
} else {
|
||||
var payload IntegrationDailyArchivePayload
|
||||
if unmarshalErr := sonic.Unmarshal(task.Payload(), &payload); unmarshalErr != nil {
|
||||
return fmt.Errorf("解析 Integration Log 每日归档任务载荷失败: %w", unmarshalErr)
|
||||
}
|
||||
date, parseErr := parseArchiveDate(payload.ArchiveDate)
|
||||
if parseErr != nil {
|
||||
return parseErr
|
||||
}
|
||||
err = h.service.ArchiveIntegrationDate(ctx, date)
|
||||
}
|
||||
if err != nil {
|
||||
h.logger.Error("Integration Log 每日冷归档失败", zap.Error(err))
|
||||
return err
|
||||
}
|
||||
h.logger.Info("Integration Log 每日冷归档完成")
|
||||
return nil
|
||||
}
|
||||
|
||||
// HandleMonthlyFinalize 执行上一个完整自然月复核,或按任务载荷复核指定月份。
|
||||
func (h *IntegrationArchiveHandler) HandleMonthlyFinalize(ctx context.Context, task *asynq.Task) error {
|
||||
if h.service == nil {
|
||||
return fmt.Errorf("Integration Log 归档服务未配置")
|
||||
}
|
||||
var err error
|
||||
if len(task.Payload()) == 0 {
|
||||
err = h.service.FinalizePreviousIntegrationMonth(ctx)
|
||||
} else {
|
||||
var payload IntegrationMonthlyFinalizePayload
|
||||
if unmarshalErr := sonic.Unmarshal(task.Payload(), &payload); unmarshalErr != nil {
|
||||
return fmt.Errorf("解析 Integration Log 月度复核任务载荷失败: %w", unmarshalErr)
|
||||
}
|
||||
month, parseErr := parseArchiveMonth(payload.ArchiveMonth)
|
||||
if parseErr != nil {
|
||||
return parseErr
|
||||
}
|
||||
err = h.service.FinalizeIntegrationMonth(ctx, month)
|
||||
}
|
||||
if err != nil {
|
||||
h.logger.Error("Integration Log 月度最终版本复核失败,后续清理必须阻止", zap.Error(err))
|
||||
return err
|
||||
}
|
||||
h.logger.Info("Integration Log 月度最终版本复核完成")
|
||||
return nil
|
||||
}
|
||||
|
||||
func parseArchiveDate(value string) (time.Time, error) {
|
||||
location, err := time.LoadLocation(constants.AuditArchiveTimezone)
|
||||
if err != nil {
|
||||
return time.Time{}, fmt.Errorf("加载 Integration Log 归档时区失败: %w", err)
|
||||
}
|
||||
date, err := time.ParseInLocation(time.DateOnly, value, location)
|
||||
if err != nil {
|
||||
return time.Time{}, fmt.Errorf("解析 Integration Log 归档日期失败: %w", err)
|
||||
}
|
||||
return date, nil
|
||||
}
|
||||
|
||||
func parseArchiveMonth(value string) (time.Time, error) {
|
||||
location, err := time.LoadLocation(constants.AuditArchiveTimezone)
|
||||
if err != nil {
|
||||
return time.Time{}, fmt.Errorf("加载 Integration Log 归档时区失败: %w", err)
|
||||
}
|
||||
month, err := time.ParseInLocation("2006-01", value, location)
|
||||
if err != nil {
|
||||
return time.Time{}, fmt.Errorf("解析 Integration Log 归档月份失败: %w", err)
|
||||
}
|
||||
return month, nil
|
||||
}
|
||||
@@ -23,10 +23,11 @@ func NewNotificationCleanupHandler(service *notificationinfra.CleanupService, lo
|
||||
}
|
||||
|
||||
// Handle 执行有界、可重入的通知分批清理。
|
||||
func (h *NotificationCleanupHandler) Handle(ctx context.Context, _ *asynq.Task) error {
|
||||
func (h *NotificationCleanupHandler) Handle(ctx context.Context, task *asynq.Task) error {
|
||||
ctx = auditcontext.With(ctx, auditcontext.Context{
|
||||
ActorKind: constants.AuditActorSystemTask, ActorID: constants.TaskTypeNotificationCleanup,
|
||||
ActorName: "站内通知清理任务", Source: constants.AuditSourceWorker,
|
||||
CorrelationID: task.ResultWriter().TaskID(),
|
||||
})
|
||||
h.logger.Info("开始执行站内通知保留清理")
|
||||
if err := h.service.Run(ctx); err != nil {
|
||||
|
||||
@@ -77,6 +77,7 @@ func (h *PollingCarddataHandler) Handle(ctx context.Context, task *asynq.Task) e
|
||||
if logErr := completeGatewayAttempt(ctx, h.integration, attempt, true, attemptStartedAt); logErr != nil {
|
||||
return h.failAndRequeue(ctx, cardID, startedAt, "完成 Gateway 流量 Integration Log 失败", logErr)
|
||||
}
|
||||
ctx = withPollingWorkerAuditContext(ctx, constants.TaskTypePollingCarddata, "卡流量轮询任务", attempt.IntegrationID)
|
||||
if h.observation == nil || h.carrier == nil {
|
||||
return h.failAndRequeue(ctx, cardID, startedAt, "卡流量观测能力未配置", nil)
|
||||
}
|
||||
|
||||
@@ -76,6 +76,7 @@ func (h *PollingCardStatusHandler) Handle(ctx context.Context, task *asynq.Task)
|
||||
if logErr := completeGatewayAttempt(ctx, h.integration, attempt, true, attemptStartedAt); logErr != nil {
|
||||
return h.failAndRequeue(ctx, cardID, startedAt, "完成 Gateway 网络 Integration Log 失败", logErr)
|
||||
}
|
||||
ctx = withPollingWorkerAuditContext(ctx, constants.TaskTypePollingCardStatus, "卡网络状态轮询任务", attempt.IntegrationID)
|
||||
if h.observation == nil {
|
||||
return h.failAndRequeue(ctx, cardID, startedAt, "卡网络观测能力未配置", nil)
|
||||
}
|
||||
|
||||
@@ -44,6 +44,7 @@ func (h *PollingPackageHandler) Handle(ctx context.Context, t *asynq.Task) error
|
||||
if !ok {
|
||||
return nil
|
||||
}
|
||||
ctx = withPollingWorkerAuditContext(ctx, constants.TaskTypePollingPackage, "套餐状态轮询任务", t.ResultWriter().TaskID())
|
||||
|
||||
if !h.base.acquireConcurrency(ctx, constants.TaskTypePollingPackage) {
|
||||
h.base.logger.Debug("并发已满,重新入队", zap.Uint("card_id", cardID))
|
||||
|
||||
@@ -52,6 +52,7 @@ func (h *PollingProtectHandler) Handle(ctx context.Context, t *asynq.Task) error
|
||||
if !ok {
|
||||
return nil
|
||||
}
|
||||
ctx = withPollingWorkerAuditContext(ctx, constants.TaskTypePollingProtect, "保护期一致性轮询任务", t.ResultWriter().TaskID())
|
||||
|
||||
if !h.base.acquireConcurrency(ctx, constants.TaskTypePollingProtect) {
|
||||
h.base.logger.Debug("并发已满,重新入队", zap.Uint("card_id", cardID))
|
||||
|
||||
@@ -68,6 +68,7 @@ func (h *PollingRealnameHandler) Handle(ctx context.Context, task *asynq.Task) e
|
||||
if logErr := completeGatewayAttempt(ctx, h.integration, attempt, true, attemptStartedAt); logErr != nil {
|
||||
return h.failAndRequeue(ctx, cardID, startedAt, "完成 Gateway 实名 Integration Log 失败", logErr)
|
||||
}
|
||||
ctx = withPollingWorkerAuditContext(ctx, constants.TaskTypePollingRealname, "实名状态轮询任务", attempt.IntegrationID)
|
||||
if h.observation == nil {
|
||||
return h.failAndRequeue(ctx, cardID, startedAt, "卡实名观测能力未配置", nil)
|
||||
}
|
||||
|
||||
@@ -1,14 +1,29 @@
|
||||
package task
|
||||
|
||||
import (
|
||||
"context"
|
||||
"strconv"
|
||||
"time"
|
||||
|
||||
"github.com/bytedance/sonic"
|
||||
"go.uber.org/zap"
|
||||
"gorm.io/gorm"
|
||||
|
||||
"github.com/break/junhong_cmp_fiber/pkg/auditcontext"
|
||||
"github.com/break/junhong_cmp_fiber/pkg/constants"
|
||||
)
|
||||
|
||||
// withPollingWorkerAuditContext 为实际改变业务事实的轮询任务补充真实系统操作者与链路。
|
||||
func withPollingWorkerAuditContext(ctx context.Context, taskType, taskName, correlationID string) context.Context {
|
||||
if correlationID == "" {
|
||||
correlationID = taskType
|
||||
}
|
||||
return auditcontext.With(ctx, auditcontext.Context{
|
||||
ActorKind: constants.AuditActorSystemTask, ActorID: taskType,
|
||||
ActorName: taskName, Source: constants.AuditSourceWorker, CorrelationID: correlationID,
|
||||
})
|
||||
}
|
||||
|
||||
// shortTaskType 从完整任务类型中提取简短名称(如 polling:carddata → carddata)
|
||||
func shortTaskType(fullTaskType string) string {
|
||||
for i := len(fullTaskType) - 1; i >= 0; i-- {
|
||||
|
||||
Reference in New Issue
Block a user