收口七月卡状态回调与系列授权兼容契约
Some checks failed
构建并部署到测试环境(无 SSH) / build-and-deploy (push) Has been cancelled

完成运营商实名回调、业务事件观测序列与受控配置装配,同时恢复 UR43 已交付的 packages[].remove 字段及旧响应兼容,统一更新 OpenSpec、OpenAPI 和交付文档。

Constraint: 七月测试环境里程碑不新增或运行自动化测试

Rejected: 以必填 operation_type 替换 packages[].remove | 会破坏已交付前端契约

Confidence: high

Scope-risk: broad

Directive: 后续修改系列套餐管理接口必须保持 packages[].remove 和 ShopSeriesGrantResponse 兼容

Tested: go run ./cmd/gendocs;go build -buildvcs=false ./...;openspec validate complete-july-iteration-test-release --strict;git diff --check

Not-tested: 按本 Change 约定未运行 go test,真实运营商与 Gateway 联调延期
This commit is contained in:
2026-07-24 19:59:24 +08:00
parent a18ed8bc8d
commit 5c4d17e9fc
79 changed files with 4341 additions and 478 deletions

View File

@@ -11,6 +11,7 @@ import (
"github.com/google/uuid"
"gorm.io/datatypes"
"gorm.io/gorm"
"gorm.io/gorm/clause"
"github.com/break/junhong_cmp_fiber/internal/model"
"github.com/break/junhong_cmp_fiber/pkg/asynctask"
@@ -42,6 +43,15 @@ func NewRepository() *Repository {
// Append 在业务事务中持久化稳定事件身份和必要快照。
func (r *Repository) Append(ctx context.Context, tx *gorm.DB, envelope Envelope) (*model.OutboxEvent, error) {
return r.append(ctx, tx, envelope, false)
}
// AppendIdempotent 在稳定 EventID 重投时保持原事件,不重复创建事实。
func (r *Repository) AppendIdempotent(ctx context.Context, tx *gorm.DB, envelope Envelope) (*model.OutboxEvent, error) {
return r.append(ctx, tx, envelope, true)
}
func (r *Repository) append(ctx context.Context, tx *gorm.DB, envelope Envelope, idempotent bool) (*model.OutboxEvent, error) {
if tx == nil {
return nil, stderrors.New("Outbox 追加必须传入 GORM 事务句柄")
}
@@ -71,7 +81,11 @@ func (r *Repository) Append(ctx context.Context, tx *gorm.DB, envelope Envelope)
RequestID: envelope.RequestID, CorrelationID: envelope.CorrelationID, Payload: datatypes.JSON(payload),
Status: constants.OutboxStatusPending, MaxRetries: constants.OutboxDefaultMaxRetries, NextAttemptAt: now,
}
if err := tx.WithContext(ctx).Create(event).Error; err != nil {
create := tx.WithContext(ctx)
if idempotent {
create = create.Clauses(clause.OnConflict{Columns: []clause.Column{{Name: "event_id"}}, DoNothing: true})
}
if err := create.Create(event).Error; err != nil {
return nil, err
}
return event, nil