固化七月迭代审计治理进展以隔离线上热修
Constraint: 切换 main 前必须保存当前七月分支全部项目进展,套餐生效提案仅属于 Iteration/7-11。 Rejected: 将七月套餐修复直接移植到 main | 两个分支的可靠投递架构不同。 Confidence: medium Scope-risk: broad Directive: 不得将本提交整体 cherry-pick 到 main;main 套餐热修必须基于其纯 Asynq 代码独立实施。 Tested: git diff --check;openspec validate fix-package-activation-starvation --strict。 Not-tested: 按用户要求未运行自动化测试;go build ./... 因当前审计改造中的 Enterprise 模型字面量和 role.recordFailure 参数类型错误未通过。
This commit is contained in:
@@ -7,6 +7,7 @@ import (
|
||||
"encoding/hex"
|
||||
"strings"
|
||||
"time"
|
||||
"unicode/utf8"
|
||||
|
||||
"github.com/google/uuid"
|
||||
"gorm.io/datatypes"
|
||||
@@ -46,15 +47,18 @@ type Attempt struct {
|
||||
|
||||
// Completion 描述外部尝试从待处理状态进入终态的结果。
|
||||
type Completion struct {
|
||||
Result string
|
||||
HTTPStatus int
|
||||
ProviderCode string
|
||||
ProviderMessage string
|
||||
ResponseSummary any
|
||||
DurationMS int64
|
||||
StateChanged bool
|
||||
AuditEventID *uint
|
||||
RecoveryStrategy string
|
||||
Result string
|
||||
HTTPStatus int
|
||||
ProviderCode string
|
||||
ProviderMessage string
|
||||
SafeProviderMessage string
|
||||
ResponseSummary any
|
||||
DurationMS int64
|
||||
StateChanged bool
|
||||
ResourceID *string
|
||||
ResourceKey *string
|
||||
AuditEventID *uint
|
||||
RecoveryStrategy string
|
||||
}
|
||||
|
||||
// InboundAttempt 描述业务处理前必须保存的入站回调安全事实。
|
||||
@@ -105,6 +109,13 @@ func (r *Repository) Start(ctx context.Context, input Attempt) (*model.Integrati
|
||||
}
|
||||
if input.Attempt <= 0 {
|
||||
input.Attempt = 1
|
||||
if input.TriggerSeries != nil {
|
||||
if err := r.db.WithContext(ctx).Model(&model.IntegrationLog{}).
|
||||
Select("COALESCE(MAX(attempt), 0) + 1").
|
||||
Where("trigger_series = ?", *input.TriggerSeries).Scan(&input.Attempt).Error; err != nil {
|
||||
return nil, pkgerrors.Wrap(pkgerrors.CodeDatabaseError, err, "计算 Integration Log 尝试序号失败")
|
||||
}
|
||||
}
|
||||
}
|
||||
if input.StartedAt == nil {
|
||||
startedAt := r.now().UTC()
|
||||
@@ -136,12 +147,19 @@ func (r *Repository) Complete(ctx context.Context, integrationID string, complet
|
||||
if r == nil || r.db == nil {
|
||||
return nil, pkgerrors.New(pkgerrors.CodeInvalidStatus, "Integration Log 数据库未配置")
|
||||
}
|
||||
if integrationID == "" || !isTerminalResult(completion.Result) {
|
||||
if !validRequiredString(integrationID, constants.IntegrationIDMaxLength) ||
|
||||
!validOptionalString(completion.ResourceID, constants.IntegrationResourceIDMaxLength) ||
|
||||
!validOptionalString(completion.ResourceKey, constants.IntegrationResourceKeyMaxLength) ||
|
||||
!isTerminalResult(completion.Result) {
|
||||
return nil, pkgerrors.New(pkgerrors.CodeInvalidParam, "Integration Log 终态参数无效")
|
||||
}
|
||||
if completion.Result == constants.IntegrationResultUnknown && strings.TrimSpace(completion.RecoveryStrategy) == "" {
|
||||
return nil, pkgerrors.New(pkgerrors.CodeInvalidParam, "结果未知必须记录明确恢复策略")
|
||||
}
|
||||
safeProviderMessage := strings.TrimSpace(completion.SafeProviderMessage)
|
||||
if safeProviderMessage != "" && utf8.RuneCountInString(constants.IntegrationSafeMessagePrefix+safeProviderMessage) > constants.IntegrationProviderMessageMaxLength {
|
||||
return nil, pkgerrors.New(pkgerrors.CodeInvalidParam, "Integration Log 安全结果摘要过长")
|
||||
}
|
||||
responseSummary, err := marshalSummary(completion.ResponseSummary)
|
||||
if err != nil {
|
||||
return nil, pkgerrors.Wrap(pkgerrors.CodeInvalidParam, err, "Integration Log 响应摘要无效")
|
||||
@@ -157,12 +175,20 @@ func (r *Repository) Complete(ctx context.Context, integrationID string, complet
|
||||
if completion.ProviderCode != "" {
|
||||
updates["provider_code"] = completion.ProviderCode
|
||||
}
|
||||
if completion.ProviderMessage != "" {
|
||||
if safeProviderMessage != "" {
|
||||
updates["provider_message"] = constants.IntegrationSafeMessagePrefix + safeProviderMessage
|
||||
} else if completion.ProviderMessage != "" {
|
||||
updates["provider_message"] = sanitizer.TextSummary(completion.ProviderMessage)
|
||||
}
|
||||
if completion.AuditEventID != nil {
|
||||
updates["audit_event_id"] = completion.AuditEventID
|
||||
}
|
||||
if completion.ResourceID != nil {
|
||||
updates["resource_id"] = completion.ResourceID
|
||||
}
|
||||
if completion.ResourceKey != nil {
|
||||
updates["resource_key"] = completion.ResourceKey
|
||||
}
|
||||
if completion.RecoveryStrategy != "" {
|
||||
updates["recovery_strategy"] = completion.RecoveryStrategy
|
||||
}
|
||||
@@ -187,12 +213,17 @@ func (r *Repository) RecordInbound(ctx context.Context, input InboundAttempt) (*
|
||||
if r == nil || r.db == nil {
|
||||
return nil, false, pkgerrors.New(pkgerrors.CodeInvalidStatus, "Integration Log 数据库未配置")
|
||||
}
|
||||
if input.Provider == "" || input.Operation == "" || input.IdempotencyKey == "" {
|
||||
if input.Provider == "" || input.Operation == "" || input.IdempotencyKey == "" ||
|
||||
!validGeneratedString(input.IntegrationID, constants.IntegrationIDMaxLength) ||
|
||||
!validOptionalString(input.ResourceID, constants.IntegrationResourceIDMaxLength) ||
|
||||
!validOptionalString(input.ResourceKey, constants.IntegrationResourceKeyMaxLength) ||
|
||||
!validOptionalString(input.CorrelationID, constants.IntegrationCorrelationIDMaxLength) {
|
||||
return nil, false, pkgerrors.New(pkgerrors.CodeInvalidParam, "入站 Integration Log 参数无效")
|
||||
}
|
||||
if input.IntegrationID == "" {
|
||||
input.IntegrationID = uuid.NewString()
|
||||
}
|
||||
triggerSeries := input.IntegrationID
|
||||
hash := sha256.Sum256(input.RawPayload)
|
||||
summary, err := marshalSummary(map[string]any{
|
||||
"content_type": input.ContentType,
|
||||
@@ -208,7 +239,8 @@ func (r *Repository) RecordInbound(ctx context.Context, input InboundAttempt) (*
|
||||
Provider: input.Provider, Direction: constants.IntegrationDirectionInbound, Operation: input.Operation,
|
||||
ExternalID: optionalString(input.ExternalID), ResourceType: optionalString(input.ResourceType),
|
||||
ResourceID: input.ResourceID, ResourceKey: input.ResourceKey, StartedAt: &now, Attempt: 1,
|
||||
Result: constants.IntegrationResultPending, RequestSummary: summary,
|
||||
TriggerSeries: &triggerSeries,
|
||||
Result: constants.IntegrationResultPending, RequestSummary: summary,
|
||||
ContentHash: hex.EncodeToString(hash[:]), RequestID: input.RequestID, CorrelationID: input.CorrelationID,
|
||||
}
|
||||
result := r.db.WithContext(ctx).Clauses(clause.OnConflict{
|
||||
@@ -289,9 +321,28 @@ func validateAttempt(input Attempt) error {
|
||||
if input.InitialResult != "" && input.InitialResult != constants.IntegrationResultPending && !isUnsentResult(input.InitialResult) {
|
||||
return pkgerrors.New(pkgerrors.CodeInvalidParam, "Integration Log 初始结果只能是待处理或未发送终态")
|
||||
}
|
||||
if !validGeneratedString(input.IntegrationID, constants.IntegrationIDMaxLength) ||
|
||||
!validOptionalString(input.TriggerSeries, constants.IntegrationTriggerSeriesMaxLength) ||
|
||||
!validOptionalString(input.CorrelationID, constants.IntegrationCorrelationIDMaxLength) ||
|
||||
!validOptionalString(input.ResourceID, constants.IntegrationResourceIDMaxLength) ||
|
||||
!validOptionalString(input.ResourceKey, constants.IntegrationResourceKeyMaxLength) {
|
||||
return pkgerrors.New(pkgerrors.CodeInvalidParam, "Integration Log 链路或资源标识无效")
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func validGeneratedString(value string, maxLength int) bool {
|
||||
return value == "" || validRequiredString(value, maxLength)
|
||||
}
|
||||
|
||||
func validRequiredString(value string, maxLength int) bool {
|
||||
return strings.TrimSpace(value) == value && value != "" && utf8.RuneCountInString(value) <= maxLength
|
||||
}
|
||||
|
||||
func validOptionalString(value *string, maxLength int) bool {
|
||||
return value == nil || validRequiredString(*value, maxLength)
|
||||
}
|
||||
|
||||
func isTerminalResult(result string) bool {
|
||||
switch result {
|
||||
case constants.IntegrationResultSuccess, constants.IntegrationResultFailed, constants.IntegrationResultUnknown,
|
||||
|
||||
Reference in New Issue
Block a user