feat(收口): 补齐 8 月迭代缺口并同步 Spec 与证据链
Some checks failed
构建并部署到测试环境(无 SSH) / build-and-deploy (push) Has been cancelled
Some checks failed
构建并部署到测试环境(无 SSH) / build-and-deploy (push) Has been cancelled
- 新增六对成对迁移 000232–000237:H5 弹窗类型、退款结算标识与申请人备注、优先轮询事实字段与两个新终态、通道阈值命中留痕、手机号最近解绑人、提现资格校验留痕 - 退款:原因必填与申请人备注、来源支付与渠道流水冻结、线下处理流水号补录审计、按订单查询可选退款方式、企微审批材料补齐且新增字段缺失映射即明确失败 - 优先轮询:人工关闭、有效期到期独立周期任务、失败与过期人工重触发、事实字段与异常重试查询、资产解析端点只读投影 - 通道阈值:命中事实同事务留痕与命中记录查询;员工账单:列表筛选与详情投影;商户池:列表投影与统计周期语义;H5:弹窗类型与类别排序 - 手机号:有效关联数量与最近解绑人、短信验证码失败次数限制;导出:佣金明细十五列与报表序号列 - 时间筛选:三处新增筛选纳入统一严格解析契约,员工账单产生时间参数改名 - 同步 12 份主 Spec 需求、两端点与异步任务证据链,门禁 context-health 与 OpenSpec 校验通过
This commit is contained in:
@@ -132,7 +132,7 @@ func (b *PollingBase) priorityInPollingScope(_ context.Context, round *pollingPr
|
||||
return false
|
||||
}
|
||||
|
||||
// MarkExecution 标记即将真正发起上游调用,并在调用之前累加尝试次数。
|
||||
// MarkExecution 标记即将真正发起上游调用,并在调用之前累加尝试次数与执行开始时间。
|
||||
// 崩溃遗留的执行中项被接管后据此按尝试上限收敛。
|
||||
func (r *pollingPriorityRound) MarkExecution(_ context.Context) {
|
||||
if r == nil {
|
||||
@@ -140,7 +140,8 @@ func (r *pollingPriorityRound) MarkExecution(_ context.Context) {
|
||||
}
|
||||
ctx, cancel := pollingFallbackContext()
|
||||
defer cancel()
|
||||
hit, err := r.base.priorityStore.IncrementAttempt(ctx, r.item.ID)
|
||||
now := time.Now()
|
||||
hit, err := r.base.priorityStore.MarkExecutionStarted(ctx, r.item.ID, now)
|
||||
if err != nil {
|
||||
r.base.logger.Warn("累加优先轮询项尝试次数失败",
|
||||
zap.Uint("priority_item_id", r.item.ID), zap.Error(err))
|
||||
@@ -151,6 +152,40 @@ func (r *pollingPriorityRound) MarkExecution(_ context.Context) {
|
||||
}
|
||||
r.attempts = r.item.AttemptCount + 1
|
||||
r.item.AttemptCount = r.attempts
|
||||
r.item.StartedAt = &now
|
||||
r.item.FinishedAt = nil
|
||||
r.item.NextRunAt = nil
|
||||
}
|
||||
|
||||
// RecordIntegrationLog 把本次执行对应的集成交互日志标识写回优先项。
|
||||
// 只保存日志标识(不复制凭证或响应原文);写入失败只损失溯源细节,不阻断执行。
|
||||
func (r *pollingPriorityRound) RecordIntegrationLog(integrationLogID string) {
|
||||
if r == nil || r.item == nil || integrationLogID == "" {
|
||||
return
|
||||
}
|
||||
ctx, cancel := pollingFallbackContext()
|
||||
defer cancel()
|
||||
if err := r.base.priorityStore.RecordIntegrationLog(ctx, r.item.ID, integrationLogID); err != nil {
|
||||
r.base.logger.Warn("记录优先轮询项集成交互日志标识失败",
|
||||
zap.Uint("priority_item_id", r.item.ID), zap.Error(err))
|
||||
return
|
||||
}
|
||||
r.item.IntegrationLogID = integrationLogID
|
||||
}
|
||||
|
||||
// nextRunAt 计算该项退回活动态后的下一次计划执行时间,复用与重入队完全相同的间隔解析。
|
||||
func (r *pollingPriorityRound) nextRunAt() time.Time {
|
||||
if r == nil || r.base == nil {
|
||||
return time.Now().Add(pollingRequeueFallbackInterval)
|
||||
}
|
||||
ctx, cancel := pollingFallbackContext()
|
||||
defer cancel()
|
||||
nextCheckAt, ok := r.base.pollingNextCheckAt(ctx, r.item.CardID, r.taskType)
|
||||
if !ok {
|
||||
// 该任务类型轮询间隔为 NULL:普通轮询不会接管,仍写下兜底时间让重试路径可见。
|
||||
return time.Now().Add(pollingRequeueFallbackInterval)
|
||||
}
|
||||
return nextCheckAt
|
||||
}
|
||||
|
||||
// Complete 记录优先项执行成功并出队,该卡继续按普通轮询运行。
|
||||
@@ -161,6 +196,7 @@ func (r *pollingPriorityRound) Complete(_ context.Context) {
|
||||
ctx, cancel := pollingFallbackContext()
|
||||
defer cancel()
|
||||
before := r.stateData()
|
||||
finishedAt := time.Now()
|
||||
hit, err := r.base.priorityStore.MarkCompleted(ctx, r.item.ID)
|
||||
if err != nil {
|
||||
r.base.logger.Warn("更新优先轮询项完成状态失败",
|
||||
@@ -174,11 +210,14 @@ func (r *pollingPriorityRound) Complete(_ context.Context) {
|
||||
r.item.Status = constants.PollingPriorityStatusCompleted
|
||||
r.item.Result = constants.PollingPriorityResultSuccess
|
||||
r.item.FailureReason = ""
|
||||
r.item.FinishedAt = &finishedAt
|
||||
r.item.DequeuedAt = &finishedAt
|
||||
r.item.NextRunAt = nil
|
||||
r.writeAudit(constants.AuditActionPollingPriorityCompleted, "卡轮询优先项执行完成并出队", constants.AuditResultSuccess, "", "", before)
|
||||
}
|
||||
|
||||
// FailRetryable 记录一次可恢复失败(上游超时、失败或响应无效)。
|
||||
// 未达尝试上限时退回活动态并按既有轮询间隔由调用方重排;达到上限时记录失败终态并出队。
|
||||
// 未达尝试上限时退回活动态并写下一次计划执行时间;达到上限时记录失败终态并出队。
|
||||
func (r *pollingPriorityRound) FailRetryable(_ context.Context, reason string) {
|
||||
if r == nil {
|
||||
return
|
||||
@@ -186,8 +225,10 @@ func (r *pollingPriorityRound) FailRetryable(_ context.Context, reason string) {
|
||||
ctx, cancel := pollingFallbackContext()
|
||||
defer cancel()
|
||||
before := r.stateData()
|
||||
finishedAt := time.Now()
|
||||
if r.attempts < constants.PollingPriorityMaxAttempts {
|
||||
hit, err := r.base.priorityStore.ReturnToPending(ctx, r.item.ID, reason)
|
||||
nextRunAt := r.nextRunAt()
|
||||
hit, err := r.base.priorityStore.ReturnToPending(ctx, r.item.ID, reason, nextRunAt)
|
||||
if err != nil {
|
||||
r.base.logger.Warn("退回优先轮询项活动状态失败",
|
||||
zap.Uint("priority_item_id", r.item.ID), zap.Error(err))
|
||||
@@ -201,6 +242,8 @@ func (r *pollingPriorityRound) FailRetryable(_ context.Context, reason string) {
|
||||
r.item.Result = constants.PollingPriorityResultFailed
|
||||
r.item.FailureReason = reason
|
||||
r.item.ClaimedAt = nil
|
||||
r.item.FinishedAt = &finishedAt
|
||||
r.item.NextRunAt = &nextRunAt
|
||||
r.writeAudit(constants.AuditActionPollingPriorityFailed, "卡轮询优先项执行失败", constants.AuditResultFailed, "", reason, before)
|
||||
r.writeAudit(constants.AuditActionPollingPriorityRetried, "卡轮询优先项未达尝试上限,退回活动态重新排期", constants.AuditResultFailed, "", reason, before)
|
||||
return
|
||||
@@ -219,6 +262,9 @@ func (r *pollingPriorityRound) FailRetryable(_ context.Context, reason string) {
|
||||
r.item.Status = constants.PollingPriorityStatusFailed
|
||||
r.item.Result = constants.PollingPriorityResultFailed
|
||||
r.item.FailureReason = reason
|
||||
r.item.FinishedAt = &finishedAt
|
||||
r.item.DequeuedAt = &finishedAt
|
||||
r.item.NextRunAt = nil
|
||||
r.writeAudit(constants.AuditActionPollingPriorityFailed, "卡轮询优先项执行失败", constants.AuditResultFailed, "", reason, before)
|
||||
r.writeAudit(constants.AuditActionPollingPriorityDequeued, "卡轮询优先项达到尝试上限,失败出队", constants.AuditResultFailed, "", reason, before)
|
||||
}
|
||||
@@ -241,9 +287,13 @@ func (r *pollingPriorityRound) FailFinal(_ context.Context, reason string) {
|
||||
zap.Uint("priority_item_id", r.item.ID))
|
||||
return
|
||||
}
|
||||
finishedAt := time.Now()
|
||||
r.item.Status = constants.PollingPriorityStatusFailed
|
||||
r.item.Result = constants.PollingPriorityResultFailed
|
||||
r.item.FailureReason = reason
|
||||
r.item.FinishedAt = &finishedAt
|
||||
r.item.DequeuedAt = &finishedAt
|
||||
r.item.NextRunAt = nil
|
||||
r.writeAudit(constants.AuditActionPollingPriorityFailed, "卡轮询优先项执行业务校验失败", constants.AuditResultFailed, "", reason, before)
|
||||
r.writeAudit(constants.AuditActionPollingPriorityDequeued, "卡轮询优先项失败出队且不重试", constants.AuditResultFailed, "", reason, before)
|
||||
}
|
||||
@@ -251,17 +301,23 @@ func (r *pollingPriorityRound) FailFinal(_ context.Context, reason string) {
|
||||
// stateData 返回当前优先项状态的前后值快照,覆盖触发来源、操作来源、时间与执行结果。
|
||||
func (r *pollingPriorityRound) stateData() map[string]any {
|
||||
return map[string]any{
|
||||
"status": r.item.Status,
|
||||
"result": r.item.Result,
|
||||
"trigger_type": r.item.TriggerType,
|
||||
"trigger_types": r.item.TriggerTypes,
|
||||
"trigger_count": r.item.TriggerCount,
|
||||
"last_triggered_at": r.item.LastTriggeredAt,
|
||||
"attempt_count": r.item.AttemptCount,
|
||||
"claimed_at": r.item.ClaimedAt,
|
||||
"failure_reason": r.item.FailureReason,
|
||||
"source": constants.AuditSourceWorker,
|
||||
"occurred_at": time.Now(),
|
||||
"status": r.item.Status,
|
||||
"result": r.item.Result,
|
||||
"trigger_type": r.item.TriggerType,
|
||||
"trigger_types": r.item.TriggerTypes,
|
||||
"trigger_count": r.item.TriggerCount,
|
||||
"last_triggered_at": r.item.LastTriggeredAt,
|
||||
"attempt_count": r.item.AttemptCount,
|
||||
"attempt_limit": r.item.AttemptLimit,
|
||||
"claimed_at": r.item.ClaimedAt,
|
||||
"started_at": r.item.StartedAt,
|
||||
"finished_at": r.item.FinishedAt,
|
||||
"next_run_at": r.item.NextRunAt,
|
||||
"dequeued_at": r.item.DequeuedAt,
|
||||
"integration_log_id": r.item.IntegrationLogID,
|
||||
"failure_reason": r.item.FailureReason,
|
||||
"source": constants.AuditSourceWorker,
|
||||
"occurred_at": time.Now(),
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user