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