package service import ( "fmt" "regexp" "strconv" "strings" "time" "github.com/tcs-iptv/tcs/internal/model" ) // 四期(大小屏融合):跨域解析网关、扫码验真、跨屏权益通兑。 // 将 MA+哈希机制从 IPTV 扩展至 OTT、手机 APP,实现大小屏内容身份互通。 // 对应任务:C.1 跨域解析网关、C.2 身份互通、B.2 扫码验真、D.1 跨屏权益子链。 // maCodePattern 匹配六段式 MA 码(含可选集级子标识 #Exx)。 // 形如 MA.156.8531.6101/WD/20260000004 或 MA.156.8531.6101/WD/20260000004#E07 var maCodePattern = regexp.MustCompile( `^(MA)\.(\d{3})\.([0-9A-Za-z]+)\.([0-9A-Za-z]+)/([0-9A-Za-z]+)/(\d{4})(\d+)(?:#E(\d+))?$`) // ParseMACode 解析六段式 MA 码(纯结构解析,不查链)。 // 返回 Valid=false 表示结构不合法(可能是伪造/损坏的码)。 func ParseMACode(maCode string) model.ParsedMA { p := model.ParsedMA{Raw: maCode} m := maCodePattern.FindStringSubmatch(strings.TrimSpace(maCode)) if m == nil { p.Valid = false return p } p.Prefix = m[1] p.CountryCode = m[2] p.IndustryNode = m[3] p.OrgNode = m[4] p.Category = m[5] p.Year = m[6] p.Sequence = m[7] if m[8] != "" { if ep, err := strconv.Atoi(m[8]); err == nil { p.Episode = ep } } p.Valid = true return p } // baseMACode 去除集级子标识 #Exx,返回整剧 MA 码(用于查链/查权益)。 func baseMACode(maCode string) string { if i := strings.Index(maCode, "#"); i >= 0 { return maCode[:i] } return maCode } // Resolve 跨域解析网关(C.1/C.2):同一 MA 码在 IPTV/OTT/APP 统一解析。 // 返回解析结构 + 流通状态 + 跨屏可用性,保证大小屏解析结果一致。 func (s *Service) Resolve(maCode string) model.ResolveResult { res := model.ResolveResult{MACode: maCode} parsed := ParseMACode(maCode) res.Parsed = parsed if !parsed.Valid { res.Resolved = false res.Message = "MA 码结构不合法,无法解析" return res } c, err := s.chain.QueryContent(baseMACode(maCode)) if err != nil { res.Resolved = false res.Message = "MA 码未在可信数据空间登记" return res } res.Resolved = true res.Title = c.Title res.Status = c.Status res.Issuer = c.Issuer res.IssueDate = c.IssueDate res.InCirculation = c.Status == model.StatusPublished if res.InCirculation { // 大小屏融合:流通中的内容在全部屏统一可用,身份一致。 res.Screens = model.AllScreens() res.Message = "解析成功,内容流通中,大小屏统一身份可用" } else { res.Screens = []model.ScreenType{} res.Message = fmt.Sprintf("解析成功,但内容当前状态为 %s,未在大小屏流通", c.Status) } return res } // ScanVerify 用户扫码验真(B.2):验证内容 MA 码真伪、合规与流通状态,防盗版。 func (s *Service) ScanVerify(maCode string) model.ScanVerifyResult { res := model.ScanVerifyResult{MACode: maCode} parsed := ParseMACode(maCode) res.Parsed = parsed if !parsed.Valid { res.Authentic = false res.Compliant = false res.Message = "MA 码结构不合法,疑似伪造" return res } c, err := s.chain.QueryContent(baseMACode(maCode)) if err != nil { res.Authentic = false res.Compliant = false res.Message = "MA 码未登记,疑似盗版或伪造内容" return res } res.Authentic = true // 链上存在且结构合法 → 真码 res.Title = c.Title res.Status = c.Status res.Compliant = c.Status == model.StatusPublished if res.Compliant { res.Screens = model.AllScreens() res.Message = "验真通过:正版内容,合规流通中" } else if c.Status == model.StatusRevoked { res.Screens = []model.ScreenType{} res.Message = "真码但内容已下架,请勿观看" } else { res.Screens = []model.ScreenType{} res.Message = fmt.Sprintf("真码但当前状态为 %s,尚未正式流通", c.Status) } return res } // RecordPurchase 记录用户购买(D.1):用户在某一屏购买内容,记录跨屏权益。 // 校验 MA 码有效、屏类型合法;同一用户对同一 MA 码重复购买不覆盖首次记录。 func (s *Service) RecordPurchase(maCode, userHash string, screen model.ScreenType) (model.PurchaseRecord, error) { if userHash == "" { return model.PurchaseRecord{}, fmt.Errorf("service: 缺少用户标识") } if !model.ValidScreen(screen) { return model.PurchaseRecord{}, fmt.Errorf("service: 非法屏类型 %q(仅 iptv/ott/app)", screen) } base := baseMACode(maCode) if _, err := s.chain.QueryContent(base); err != nil { return model.PurchaseRecord{}, fmt.Errorf("service: MA 码无效或未登记: %w", err) } s.mu.Lock() defer s.mu.Unlock() acct, ok := s.rights[userHash] if !ok { acct = &model.UserRights{UserHash: userHash, Purchases: make(map[string]model.PurchaseRecord)} s.rights[userHash] = acct } if rec, exists := acct.Purchases[base]; exists { // 已购买:跨屏通兑,不重复扣费,返回首次购买记录。 return rec, nil } rec := model.PurchaseRecord{ MACode: base, UserHash: userHash, Screen: screen, PurchasedAt: time.Now(), } acct.Purchases[base] = rec return rec, nil } // VerifyCrossScreenRights 跨屏权益核验(D.1):任一屏购买即全屏通看,不重复付费。 func (s *Service) VerifyCrossScreenRights(maCode, userHash string, requestScreen model.ScreenType) model.CrossScreenRightsResult { base := baseMACode(maCode) res := model.CrossScreenRightsResult{ MACode: base, UserHash: userHash, RequestScreen: requestScreen, } if !model.ValidScreen(requestScreen) { res.Entitled = false res.Message = fmt.Sprintf("非法屏类型 %q", requestScreen) return res } s.mu.Lock() acct, ok := s.rights[userHash] var rec model.PurchaseRecord var has bool if ok { rec, has = acct.Purchases[base] } s.mu.Unlock() if !has { res.Entitled = false res.Message = "未购买该内容,需先购买" return res } res.Entitled = true res.PurchaseScreen = rec.Screen res.PurchasedAt = rec.PurchasedAt if rec.Screen == requestScreen { res.Message = "本屏已购买,可观看" } else { res.Message = fmt.Sprintf("已在 %s 屏购买,跨屏通兑至 %s 屏观看,不重复付费", rec.Screen, requestScreen) } return res }