收口七月卡状态回调与系列授权兼容契约
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

@@ -3,6 +3,7 @@ package task
import (
"context"
"errors"
"strconv"
"time"
"github.com/bytedance/sonic"
@@ -12,6 +13,7 @@ import (
"gorm.io/gorm"
"gorm.io/gorm/clause"
cardObservationApp "github.com/break/junhong_cmp_fiber/internal/application/cardobservation"
packagedomain "github.com/break/junhong_cmp_fiber/internal/domain/package"
"github.com/break/junhong_cmp_fiber/internal/model"
packagepkg "github.com/break/junhong_cmp_fiber/internal/service/package"
@@ -41,6 +43,7 @@ type AutoPurchaseHandler struct {
redis *redis.Client
asynqClient *asynq.Client // 用于事务提交成功后触发佣金计算任务
logger *zap.Logger
observationSeriesEvents cardObservationApp.SeriesEventWriter
}
// NewAutoPurchaseHandler 创建充值后自动购包处理器
@@ -55,6 +58,7 @@ func NewAutoPurchaseHandler(
redisClient *redis.Client,
asynqClient *asynq.Client,
logger *zap.Logger,
observationSeriesEvents cardObservationApp.SeriesEventWriter,
) *AutoPurchaseHandler {
if orderStore == nil {
orderStore = postgres.NewOrderStore(db, redisClient)
@@ -89,6 +93,7 @@ func NewAutoPurchaseHandler(
redis: redisClient,
asynqClient: asynqClient,
logger: logger,
observationSeriesEvents: observationSeriesEvents,
}
}
@@ -248,6 +253,23 @@ func (h *AutoPurchaseHandler) ProcessTask(ctx context.Context, task *asynq.Task)
if err = h.activatePackages(ctx, tx, order, packages, now); err != nil {
return err
}
if h.observationSeriesEvents == nil {
return pkgerrors.New(pkgerrors.CodeInternalError, "自动购包观测 Outbox Writer 未配置")
}
resourceType, resourceID := orderObservationResource(order)
if resourceID == 0 {
return pkgerrors.New(pkgerrors.CodeInvalidParam, "自动购包观测事件缺少载体")
}
requestID := "card-observation:auto-purchase:" + strconv.FormatUint(uint64(order.ID), 10)
if err = h.observationSeriesEvents.AppendSeriesRequested(ctx, tx, cardObservationApp.SeriesRequestedEvent{
EventID: requestID, Scene: constants.CardObservationScenePackageChanged,
ResourceType: resourceType, ResourceID: resourceID,
SyncTypes: []string{constants.CardObservationSyncTypeRealname, constants.CardObservationSyncTypeTraffic, constants.CardObservationSyncTypeNetwork},
Source: constants.CardObservationSourceBusinessEvent, OccurredAt: now,
RequestID: requestID, CorrelationID: requestID,
}); err != nil {
return err
}
if err = tx.Model(&model.RechargeOrder{}).
Where("id = ?", rechargeOrder.ID).
@@ -289,6 +311,16 @@ func (h *AutoPurchaseHandler) ProcessTask(ctx context.Context, task *asynq.Task)
return nil
}
func orderObservationResource(order *model.Order) (string, uint) {
if order != nil && order.DeviceID != nil {
return constants.CardObservationResourceTypeDevice, *order.DeviceID
}
if order != nil && order.IotCardID != nil {
return constants.CardObservationResourceTypeCard, *order.IotCardID
}
return "", 0
}
// NewAutoPurchaseTask 创建充值后自动购包任务
func NewAutoPurchaseTask(rechargeOrderID uint) (*asynq.Task, error) {
payloadBytes, err := sonic.Marshal(AutoPurchasePayload{RechargeOrderID: rechargeOrderID})