收口七月卡状态回调与系列授权兼容契约
Some checks failed
构建并部署到测试环境(无 SSH) / build-and-deploy (push) Has been cancelled
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:
67
internal/infrastructure/carriercallback/cmcc_realname.go
Normal file
67
internal/infrastructure/carriercallback/cmcc_realname.go
Normal file
@@ -0,0 +1,67 @@
|
||||
package carriercallback
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"strings"
|
||||
|
||||
"github.com/bytedance/sonic"
|
||||
"gorm.io/gorm"
|
||||
|
||||
"github.com/break/junhong_cmp_fiber/pkg/errors"
|
||||
pkgvalidator "github.com/break/junhong_cmp_fiber/pkg/validator"
|
||||
)
|
||||
|
||||
// CMCCRealnameTranslation 是移动实名 JSON 回调的受控语义。
|
||||
type CMCCRealnameTranslation struct {
|
||||
ICCID string
|
||||
BusiSeq string
|
||||
}
|
||||
|
||||
// CMCCRealnameTranslator 解析移动实名成功回调的固定 JSON 结构。
|
||||
type CMCCRealnameTranslator struct{}
|
||||
|
||||
// CMCCCardResolver 复用系统级 ICCID 精确查询约束。
|
||||
type CMCCCardResolver = CTCCCardResolver
|
||||
|
||||
// NewCMCCCardResolver 创建移动回调卡定位器。
|
||||
func NewCMCCCardResolver(db *gorm.DB) *CMCCCardResolver {
|
||||
return NewCTCCCardResolver(db)
|
||||
}
|
||||
|
||||
// NewCMCCRealnameTranslator 创建移动实名回调 Translator。
|
||||
func NewCMCCRealnameTranslator() *CMCCRealnameTranslator {
|
||||
return &CMCCRealnameTranslator{}
|
||||
}
|
||||
|
||||
type cmccRealnamePayload struct {
|
||||
Status string `json:"status"`
|
||||
Message string `json:"message"`
|
||||
Result []struct {
|
||||
RegStatus string `json:"regStatus"`
|
||||
BusiSeq string `json:"busiSeq"`
|
||||
ICCID string `json:"iccid"`
|
||||
} `json:"result"`
|
||||
}
|
||||
|
||||
// Translate 只接受 status=0、message=正确、首条结果实名成功且 ICCID 合法。
|
||||
func (t *CMCCRealnameTranslator) Translate(body []byte) (CMCCRealnameTranslation, error) {
|
||||
var result CMCCRealnameTranslation
|
||||
if len(bytes.TrimSpace(body)) == 0 {
|
||||
return result, errors.New(errors.CodeInvalidParam, "移动实名回调报文为空")
|
||||
}
|
||||
var payload cmccRealnamePayload
|
||||
if err := sonic.Unmarshal(body, &payload); err != nil {
|
||||
return result, errors.Wrap(errors.CodeInvalidParam, err, "移动实名回调 JSON 解析失败")
|
||||
}
|
||||
if len(payload.Result) > 0 {
|
||||
result.BusiSeq = strings.TrimSpace(payload.Result[0].BusiSeq)
|
||||
result.ICCID = strings.TrimSpace(payload.Result[0].ICCID)
|
||||
}
|
||||
if payload.Status != "0" || payload.Message != "正确" || len(payload.Result) == 0 || payload.Result[0].RegStatus != "00000" {
|
||||
return result, errors.New(errors.CodeInvalidParam, "移动实名回调业务结果无效")
|
||||
}
|
||||
if validation := pkgvalidator.ValidateICCIDWithoutCarrier(result.ICCID); !validation.Valid {
|
||||
return result, errors.New(errors.CodeInvalidParam, "移动实名回调 ICCID 无效")
|
||||
}
|
||||
return result, nil
|
||||
}
|
||||
107
internal/infrastructure/carriercallback/ctcc_realname.go
Normal file
107
internal/infrastructure/carriercallback/ctcc_realname.go
Normal file
@@ -0,0 +1,107 @@
|
||||
// Package carriercallback 提供运营商回调协议到内部观测模型的适配能力。
|
||||
package carriercallback
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"context"
|
||||
"encoding/xml"
|
||||
"strings"
|
||||
|
||||
"gorm.io/gorm"
|
||||
|
||||
"github.com/break/junhong_cmp_fiber/internal/model"
|
||||
"github.com/break/junhong_cmp_fiber/pkg/errors"
|
||||
pkgvalidator "github.com/break/junhong_cmp_fiber/pkg/validator"
|
||||
)
|
||||
|
||||
const (
|
||||
// CTCCRealnameActionVerified 表示电信实名补录已完成。
|
||||
CTCCRealnameActionVerified = "verified"
|
||||
// CTCCRealnameActionIgnored 表示报文合法但无需改变实名事实。
|
||||
CTCCRealnameActionIgnored = "ignored"
|
||||
)
|
||||
|
||||
// CTCCRealnameTranslation 是电信 XML 回调的受控内部语义。
|
||||
type CTCCRealnameTranslation struct {
|
||||
ICCID string
|
||||
AcceptMessage string
|
||||
ResultMessage string
|
||||
GroupTransactionID string
|
||||
Action string
|
||||
}
|
||||
|
||||
type ctccContractRoot struct {
|
||||
XMLName xml.Name `xml:"ContractRoot"`
|
||||
ICCID string `xml:"ICCID"`
|
||||
AcceptMessage string `xml:"ACCEPTMSG"`
|
||||
ResultMessage string `xml:"RESULTMSG"`
|
||||
GroupTransactionID string `xml:"GROUP_TRANSACTIONID"`
|
||||
}
|
||||
|
||||
// CTCCRealnameTranslator 解析已知电信 XML 协议字段。
|
||||
type CTCCRealnameTranslator struct{}
|
||||
|
||||
// NewCTCCRealnameTranslator 创建电信实名回调 Translator。
|
||||
func NewCTCCRealnameTranslator() *CTCCRealnameTranslator {
|
||||
return &CTCCRealnameTranslator{}
|
||||
}
|
||||
|
||||
// Translate 将 XML 转为受控语义;未知业务结果保留为 ignored。
|
||||
func (t *CTCCRealnameTranslator) Translate(body []byte) (CTCCRealnameTranslation, error) {
|
||||
var result CTCCRealnameTranslation
|
||||
if len(bytes.TrimSpace(body)) == 0 {
|
||||
return result, errors.New(errors.CodeInvalidParam, "电信实名回调报文为空")
|
||||
}
|
||||
var payload ctccContractRoot
|
||||
if err := xml.Unmarshal(body, &payload); err != nil {
|
||||
return result, errors.Wrap(errors.CodeInvalidParam, err, "电信实名回调 XML 解析失败")
|
||||
}
|
||||
result.ICCID = strings.TrimSpace(payload.ICCID)
|
||||
result.AcceptMessage = strings.TrimSpace(payload.AcceptMessage)
|
||||
result.ResultMessage = strings.TrimSpace(payload.ResultMessage)
|
||||
result.GroupTransactionID = strings.TrimSpace(payload.GroupTransactionID)
|
||||
if err := t.validateICCID(result.ICCID); err != nil {
|
||||
return result, err
|
||||
}
|
||||
result.Action = CTCCRealnameActionIgnored
|
||||
if result.ResultMessage == "成功" && strings.Contains(result.AcceptMessage, "已完成实名信息补录") {
|
||||
result.Action = CTCCRealnameActionVerified
|
||||
}
|
||||
return result, nil
|
||||
}
|
||||
|
||||
func (t *CTCCRealnameTranslator) validateICCID(iccid string) error {
|
||||
if result := pkgvalidator.ValidateICCIDWithoutCarrier(iccid); !result.Valid {
|
||||
return errors.New(errors.CodeInvalidParam, "电信实名回调 ICCID 无效")
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// CTCCCardResolver 使用系统级精确列查询定位电信回调卡。
|
||||
type CTCCCardResolver struct {
|
||||
db *gorm.DB
|
||||
}
|
||||
|
||||
// NewCTCCCardResolver 创建电信回调卡定位器。
|
||||
func NewCTCCCardResolver(db *gorm.DB) *CTCCCardResolver {
|
||||
return &CTCCCardResolver{db: db}
|
||||
}
|
||||
|
||||
// Resolve 按 ICCID 长度选择唯一列,禁止跨列降级。
|
||||
func (r *CTCCCardResolver) Resolve(ctx context.Context, iccid string) ([]*model.IotCard, error) {
|
||||
if r == nil || r.db == nil {
|
||||
return nil, errors.New(errors.CodeInternalError, "电信实名回调卡查询能力未配置")
|
||||
}
|
||||
column := "iccid_19"
|
||||
if len(iccid) == 20 {
|
||||
column = "iccid_20"
|
||||
}
|
||||
var cards []*model.IotCard
|
||||
if err := r.db.WithContext(ctx).
|
||||
Where(column+" = ? AND deleted_at IS NULL", iccid).
|
||||
Limit(2).
|
||||
Find(&cards).Error; err != nil {
|
||||
return nil, errors.Wrap(errors.CodeDatabaseError, err, "查询电信实名回调卡失败")
|
||||
}
|
||||
return cards, nil
|
||||
}
|
||||
22
internal/infrastructure/carriercallback/cucc_realname.go
Normal file
22
internal/infrastructure/carriercallback/cucc_realname.go
Normal file
@@ -0,0 +1,22 @@
|
||||
package carriercallback
|
||||
|
||||
// CUCCRealnameTranslation 是联通实名成功回调的受控语义。
|
||||
type CUCCRealnameTranslation = CUCCRealnameRemovalTranslation
|
||||
|
||||
// CUCCRealnameTranslator 解析联通外层 data 字符串及其内层实名 JSON。
|
||||
type CUCCRealnameTranslator struct {
|
||||
parser *CUCCRealnameRemovalTranslator
|
||||
}
|
||||
|
||||
// NewCUCCRealnameTranslator 创建联通实名成功回调 Translator。
|
||||
func NewCUCCRealnameTranslator() *CUCCRealnameTranslator {
|
||||
return &CUCCRealnameTranslator{parser: NewCUCCRealnameRemovalTranslator()}
|
||||
}
|
||||
|
||||
// Translate 解析联通实名成功报文;协议结构与解除实名报文一致。
|
||||
func (t *CUCCRealnameTranslator) Translate(body []byte) (CUCCRealnameTranslation, error) {
|
||||
if t == nil || t.parser == nil {
|
||||
return NewCUCCRealnameRemovalTranslator().Translate(body)
|
||||
}
|
||||
return t.parser.Translate(body)
|
||||
}
|
||||
@@ -0,0 +1,67 @@
|
||||
package carriercallback
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"strings"
|
||||
|
||||
"github.com/bytedance/sonic"
|
||||
"gorm.io/gorm"
|
||||
|
||||
"github.com/break/junhong_cmp_fiber/pkg/errors"
|
||||
pkgvalidator "github.com/break/junhong_cmp_fiber/pkg/validator"
|
||||
)
|
||||
|
||||
// CUCCRealnameRemovalTranslation 是联通解除实名回调的受控语义。
|
||||
type CUCCRealnameRemovalTranslation struct {
|
||||
ICCID string
|
||||
DateChanged string
|
||||
}
|
||||
|
||||
// CUCCRealnameRemovalTranslator 解析外层 data 字符串及其内层 JSON。
|
||||
type CUCCRealnameRemovalTranslator struct{}
|
||||
|
||||
// NewCUCCRealnameRemovalTranslator 创建联通解除实名 Translator。
|
||||
func NewCUCCRealnameRemovalTranslator() *CUCCRealnameRemovalTranslator {
|
||||
return &CUCCRealnameRemovalTranslator{}
|
||||
}
|
||||
|
||||
// Translate 解析并校验联通解除实名报文,不执行任何业务写入。
|
||||
func (t *CUCCRealnameRemovalTranslator) Translate(body []byte) (CUCCRealnameRemovalTranslation, error) {
|
||||
var result CUCCRealnameRemovalTranslation
|
||||
if len(bytes.TrimSpace(body)) == 0 {
|
||||
return result, errors.New(errors.CodeInvalidParam, "联通解除实名回调报文为空")
|
||||
}
|
||||
var outer struct {
|
||||
Data string `json:"data"`
|
||||
}
|
||||
if err := sonic.Unmarshal(body, &outer); err != nil {
|
||||
return result, errors.Wrap(errors.CodeInvalidParam, err, "联通解除实名外层 JSON 解析失败")
|
||||
}
|
||||
if strings.TrimSpace(outer.Data) == "" {
|
||||
return result, errors.New(errors.CodeInvalidParam, "联通解除实名回调 data 为空")
|
||||
}
|
||||
var inner struct {
|
||||
ICCID string `json:"iccid"`
|
||||
DateChanged string `json:"dateChanged"`
|
||||
}
|
||||
if err := sonic.Unmarshal([]byte(outer.Data), &inner); err != nil {
|
||||
return result, errors.Wrap(errors.CodeInvalidParam, err, "联通解除实名内层 JSON 解析失败")
|
||||
}
|
||||
result.ICCID = strings.TrimSpace(inner.ICCID)
|
||||
result.DateChanged = strings.TrimSpace(inner.DateChanged)
|
||||
if validation := pkgvalidator.ValidateICCIDWithoutCarrier(result.ICCID); !validation.Valid {
|
||||
return result, errors.New(errors.CodeInvalidParam, "联通解除实名回调 ICCID 无效")
|
||||
}
|
||||
if result.DateChanged == "" {
|
||||
return result, errors.New(errors.CodeInvalidParam, "联通解除实名回调变更时间为空")
|
||||
}
|
||||
return result, nil
|
||||
}
|
||||
|
||||
// CUCCCardResolver 复用系统级 ICCID 精确查询约束。
|
||||
type CUCCCardResolver = CTCCCardResolver
|
||||
|
||||
// NewCUCCCardResolver 创建联通回调卡定位器。
|
||||
func NewCUCCCardResolver(db *gorm.DB) *CUCCCardResolver {
|
||||
return NewCTCCCardResolver(db)
|
||||
}
|
||||
Reference in New Issue
Block a user