Files
MAcode/tcs-iptv/internal/macode/macode.go
T
selfrelease 8db9d33694 feat(phase3): 备案对接/全国统计/号段管理/BFF安全化/链合约源码
- A.1 备案对接: BindFiling/QueryFiling 关联网标号+备案号
- A.2 监管上报: DailyRegulatoryReport 日报
- B.1 号段管理: ListSegments + /admin/segments
- C.1/C.2 全国统计按省聚合 + 跨省协同(单一可信源天然联动)
- F.2 全国监管大屏: NationalStats(按省/类目/状态)
- B(遗留) 监管大屏BFF: internal/bff + cmd/console-bff, 密钥仅存后端浏览器只用会话令牌
- G 真实链合约源码: contracts/tcs_registry/registry.go (ChainMaker Go)
- 新增9个API+BFF服务; 5项新测试; 端到端BFF验证
- D/E(压测/等保/HSM)/F.1(标准)/真实链部署 标注需外部环境
2026-06-14 17:53:12 +08:00

178 lines
5.2 KiB
Go

// Package macode 实现 MA 码生成服务(模式B:自行发码)。
// TCS 与 MA 发码机构合作获取「码段(号段)」与「备案规则」,在本地按规则原子发码。
// 对应需求:需求3(MA码签发)、需求16;与 ISO/IEC 15459 MA 标识体系对齐。
//
// MA 码结构(六段式,可由备案规则配置):
//
// MA.156.{industryNode}.{orgNode}/{category}/{yyyy}{sequence}
//
// 示例:MA.156.8531.4401/WD/20250000123
// - MA 固定前缀(标识体系根)
// - 156 国家码(中国)
// - 8531 行业节点(IPTV 视听内容,由发码机构分配)
// - 4401 机构节点(运营主体/省局,由发码机构分配)
// - WD 内容类目(WD=微短剧 / WJ=网络剧 / DY=网络电影 / DH=网络动画)
// - yyyy 年份
// - sequence 号段内递增序列(按位补零)
package macode
import (
"errors"
"fmt"
"sync"
"time"
)
// 错误定义。
var (
ErrSegmentExhausted = errors.New("macode: code segment exhausted")
ErrUnknownCategory = errors.New("macode: unknown content category")
ErrInvalidSegment = errors.New("macode: invalid segment range")
)
// 内容类目码。
const (
CategoryMicroDrama = "WD" // 网络微短剧
CategoryWebSeries = "WJ" // 网络剧
CategoryWebMovie = "DY" // 网络电影
CategoryAnimation = "DH" // 网络动画
)
// Segment 是 MA 发码机构分配给本运营主体的「码段(号段)」。
// 同一 (industryNode, orgNode, category) 下,序列在 [Start, End] 内递增分配。
type Segment struct {
IndustryNode string // 行业节点(发码机构分配)
OrgNode string // 机构节点(发码机构分配)
Category string // 适用内容类目
Start uint64 // 号段起始序列(含)
End uint64 // 号段结束序列(含)
SeqWidth int // 序列补零宽度,如 7 → 0000123
}
// Validate 校验号段合法性。
func (s Segment) Validate() error {
if s.IndustryNode == "" || s.OrgNode == "" || s.Category == "" {
return ErrInvalidSegment
}
if s.End < s.Start {
return ErrInvalidSegment
}
if s.SeqWidth <= 0 {
s.SeqWidth = 7
}
return nil
}
// AllocationStore 持久化号段游标,保证重启后不重号、并发下原子分配。
// MVP 提供内存实现;生产可用 PostgreSQL 行锁 / Redis INCR 实现。
type AllocationStore interface {
// Next 原子取得指定号段键的下一个序列值;超出 end 返回 ErrSegmentExhausted。
Next(segmentKey string, start, end uint64) (uint64, error)
}
// Generator MA 码生成器。
type Generator struct {
mu sync.RWMutex
segments map[string]Segment // category -> segment
store AllocationStore
clock func() time.Time
}
// NewGenerator 创建生成器。
func NewGenerator(store AllocationStore) *Generator {
return &Generator{
segments: make(map[string]Segment),
store: store,
clock: time.Now,
}
}
// RegisterSegment 登记一个码段(通常在与发码机构对接后配置)。
func (g *Generator) RegisterSegment(seg Segment) error {
if err := seg.Validate(); err != nil {
return err
}
if seg.SeqWidth <= 0 {
seg.SeqWidth = 7
}
g.mu.Lock()
defer g.mu.Unlock()
g.segments[seg.Category] = seg
return nil
}
// segmentKey 唯一标识一个号段(用于持久化游标)。
func segmentKey(s Segment) string {
return fmt.Sprintf("%s:%s:%s", s.IndustryNode, s.OrgNode, s.Category)
}
// Issued 一次发码结果。
type Issued struct {
MACode string `json:"ma_code"`
IndustryNode string `json:"industry_node"`
OrgNode string `json:"org_node"`
Category string `json:"category"`
Sequence uint64 `json:"sequence"`
Year int `json:"year"`
}
// Allocate 为指定类目原子分配并格式化一个全局唯一 MA 码。
func (g *Generator) Allocate(category string) (Issued, error) {
g.mu.RLock()
seg, ok := g.segments[category]
g.mu.RUnlock()
if !ok {
return Issued{}, ErrUnknownCategory
}
seq, err := g.store.Next(segmentKey(seg), seg.Start, seg.End)
if err != nil {
return Issued{}, err
}
year := g.clock().Year()
code := Format(seg, year, seq)
return Issued{
MACode: code,
IndustryNode: seg.IndustryNode,
OrgNode: seg.OrgNode,
Category: category,
Sequence: seq,
Year: year,
}, nil
}
// SegmentInfo 号段使用情况摘要。
type SegmentInfo struct {
Category string `json:"category"`
IndustryNode string `json:"industry_node"`
OrgNode string `json:"org_node"`
Start uint64 `json:"start"`
End uint64 `json:"end"`
Capacity uint64 `json:"capacity"`
}
// Segments 返回已登记号段列表(号段管理后台用,B.1)。
func (g *Generator) Segments() []SegmentInfo {
g.mu.RLock()
defer g.mu.RUnlock()
out := make([]SegmentInfo, 0, len(g.segments))
for _, seg := range g.segments {
out = append(out, SegmentInfo{
Category: seg.Category, IndustryNode: seg.IndustryNode, OrgNode: seg.OrgNode,
Start: seg.Start, End: seg.End, Capacity: seg.End - seg.Start + 1,
})
}
return out
}
// Format 按备案规则拼装 MA 码。
func Format(seg Segment, year int, seq uint64) string {
w := seg.SeqWidth
if w <= 0 {
w = 7
}
return fmt.Sprintf("MA.156.%s.%s/%s/%d%0*d",
seg.IndustryNode, seg.OrgNode, seg.Category, year, w, seq)
}