// 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 }