feat: 更新CC码监管方案业务版文档,新增摘要、UGC专项、直播专项、存量处置、误判申诉、水印嵌入、CC码生命周期、组织保障等章节;同步macode到cccode重命名及其他代码变更
This commit is contained in:
@@ -12,11 +12,11 @@ import (
|
||||
// 严格执行合约级业务规则:签发权限、1:1 不可解绑、映射前置签发、防重复哈希。
|
||||
type MemoryChain struct {
|
||||
mu sync.RWMutex
|
||||
contents map[string]model.Content // maCode -> Content
|
||||
bindings map[string][]model.HashBinding // maCode -> bindings
|
||||
mappings map[string][]model.Mapping // maCode -> mappings
|
||||
contents map[string]model.Content // ccCode -> Content
|
||||
bindings map[string][]model.HashBinding // ccCode -> bindings
|
||||
mappings map[string][]model.Mapping // ccCode -> mappings
|
||||
versions map[string][]model.VersionChange
|
||||
hashIndex map[string]string // fileHash -> maCode(防换壳重发)
|
||||
hashIndex map[string]string // fileHash -> ccCode(防换壳重发)
|
||||
txSeq int
|
||||
}
|
||||
|
||||
@@ -44,7 +44,7 @@ func (m *MemoryChain) IssueMA(role Role, req IssueRequest) (string, error) {
|
||||
m.mu.Lock()
|
||||
defer m.mu.Unlock()
|
||||
|
||||
if _, ok := m.contents[req.MACode]; ok {
|
||||
if _, ok := m.contents[req.CCCode]; ok {
|
||||
return "", ErrMAAlreadyIssued
|
||||
}
|
||||
if existing, ok := m.hashIndex[req.FileHash]; ok {
|
||||
@@ -52,15 +52,15 @@ func (m *MemoryChain) IssueMA(role Role, req IssueRequest) (string, error) {
|
||||
}
|
||||
|
||||
c := req.Content
|
||||
c.MACode = req.MACode
|
||||
c.CCCode = req.CCCode
|
||||
c.ContentTwinID = req.ContentTwinID
|
||||
c.Status = model.StatusApproved
|
||||
if c.CreatedAt.IsZero() {
|
||||
c.CreatedAt = time.Now()
|
||||
}
|
||||
m.contents[req.MACode] = c
|
||||
m.contents[req.CCCode] = c
|
||||
|
||||
m.bindings[req.MACode] = []model.HashBinding{{
|
||||
m.bindings[req.CCCode] = []model.HashBinding{{
|
||||
ContentTwinID: req.ContentTwinID,
|
||||
HashType: model.HashFile,
|
||||
HashValue: req.FileHash,
|
||||
@@ -69,7 +69,7 @@ func (m *MemoryChain) IssueMA(role Role, req IssueRequest) (string, error) {
|
||||
CreatedBy: string(RoleRegulator),
|
||||
}}
|
||||
if req.PerceptualHash != "" {
|
||||
m.bindings[req.MACode] = append(m.bindings[req.MACode], model.HashBinding{
|
||||
m.bindings[req.CCCode] = append(m.bindings[req.CCCode], model.HashBinding{
|
||||
ContentTwinID: req.ContentTwinID,
|
||||
HashType: model.HashPerceptual,
|
||||
HashValue: req.PerceptualHash,
|
||||
@@ -77,11 +77,11 @@ func (m *MemoryChain) IssueMA(role Role, req IssueRequest) (string, error) {
|
||||
CreatedBy: string(RoleRegulator),
|
||||
})
|
||||
}
|
||||
m.hashIndex[req.FileHash] = req.MACode
|
||||
m.hashIndex[req.FileHash] = req.CCCode
|
||||
|
||||
// 集级哈希绑定(分集内容):每集独立哈希,挂在同一 MA 码下。
|
||||
for _, ep := range req.Episodes {
|
||||
m.bindings[req.MACode] = append(m.bindings[req.MACode], model.HashBinding{
|
||||
m.bindings[req.CCCode] = append(m.bindings[req.CCCode], model.HashBinding{
|
||||
ContentTwinID: req.ContentTwinID,
|
||||
HashType: model.HashFile,
|
||||
HashValue: ep.FileSHA256,
|
||||
@@ -94,7 +94,7 @@ func (m *MemoryChain) IssueMA(role Role, req IssueRequest) (string, error) {
|
||||
})
|
||||
if ep.FileSHA256 != "" {
|
||||
if _, ok := m.hashIndex[ep.FileSHA256]; !ok {
|
||||
m.hashIndex[ep.FileSHA256] = req.MACode
|
||||
m.hashIndex[ep.FileSHA256] = req.CCCode
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -109,14 +109,14 @@ func (m *MemoryChain) RegisterHashBinding(role Role, b model.HashBinding) (strin
|
||||
m.mu.Lock()
|
||||
defer m.mu.Unlock()
|
||||
|
||||
maCode := m.maCodeByCTID(b.ContentTwinID)
|
||||
if maCode == "" {
|
||||
ccCode := m.ccCodeByCTID(b.ContentTwinID)
|
||||
if ccCode == "" {
|
||||
return "", ErrMANotIssued
|
||||
}
|
||||
m.bindings[maCode] = append(m.bindings[maCode], b)
|
||||
m.bindings[ccCode] = append(m.bindings[ccCode], b)
|
||||
if b.HashType == model.HashFile || b.HashType == model.HashTranscoded {
|
||||
if _, ok := m.hashIndex[b.HashValue]; !ok {
|
||||
m.hashIndex[b.HashValue] = maCode
|
||||
m.hashIndex[b.HashValue] = ccCode
|
||||
}
|
||||
}
|
||||
return m.nextTx("registerHashBinding"), nil
|
||||
@@ -127,28 +127,28 @@ func (m *MemoryChain) RegisterMapping(role Role, mp model.Mapping) (string, erro
|
||||
m.mu.Lock()
|
||||
defer m.mu.Unlock()
|
||||
|
||||
maCode := m.maCodeByCTID(mp.ContentTwinID)
|
||||
if maCode == "" {
|
||||
ccCode := m.ccCodeByCTID(mp.ContentTwinID)
|
||||
if ccCode == "" {
|
||||
return "", ErrMANotIssued
|
||||
}
|
||||
m.mappings[maCode] = append(m.mappings[maCode], mp)
|
||||
m.mappings[ccCode] = append(m.mappings[ccCode], mp)
|
||||
return m.nextTx("registerMapping"), nil
|
||||
}
|
||||
|
||||
// VerifyHash 按 MA 码校验提交哈希。
|
||||
func (m *MemoryChain) VerifyHash(maCode, fileHash string) (VerifyResult, error) {
|
||||
func (m *MemoryChain) VerifyHash(ccCode, fileHash string) (VerifyResult, error) {
|
||||
m.mu.RLock()
|
||||
defer m.mu.RUnlock()
|
||||
|
||||
bs, ok := m.bindings[maCode]
|
||||
bs, ok := m.bindings[ccCode]
|
||||
if !ok {
|
||||
return VerifyResult{Valid: false, MACode: maCode, SubmittedHash: fileHash}, ErrMANotIssued
|
||||
return VerifyResult{Valid: false, CCCode: ccCode, SubmittedHash: fileHash}, ErrMANotIssued
|
||||
}
|
||||
for _, b := range bs {
|
||||
if b.HashType == model.HashFile || b.HashType == model.HashTranscoded {
|
||||
if b.HashValue == fileHash {
|
||||
return VerifyResult{
|
||||
Valid: true, MACode: maCode,
|
||||
Valid: true, CCCode: ccCode,
|
||||
BoundHash: b.HashValue, SubmittedHash: fileHash,
|
||||
Match: true, Version: b.Version,
|
||||
}, nil
|
||||
@@ -163,7 +163,7 @@ func (m *MemoryChain) VerifyHash(maCode, fileHash string) (VerifyResult, error)
|
||||
break
|
||||
}
|
||||
}
|
||||
return VerifyResult{Valid: true, MACode: maCode, BoundHash: bound, SubmittedHash: fileHash, Match: false}, nil
|
||||
return VerifyResult{Valid: true, CCCode: ccCode, BoundHash: bound, SubmittedHash: fileHash, Match: false}, nil
|
||||
}
|
||||
|
||||
// HashExists 判断内容哈希是否已存在。
|
||||
@@ -175,12 +175,12 @@ func (m *MemoryChain) HashExists(fileHash string) (string, bool) {
|
||||
}
|
||||
|
||||
// VerifyEpisodeHash 按 MA 码+集号校验该集哈希。
|
||||
func (m *MemoryChain) VerifyEpisodeHash(maCode string, episode int, fileHash string) (VerifyResult, error) {
|
||||
func (m *MemoryChain) VerifyEpisodeHash(ccCode string, episode int, fileHash string) (VerifyResult, error) {
|
||||
m.mu.RLock()
|
||||
defer m.mu.RUnlock()
|
||||
bs, ok := m.bindings[maCode]
|
||||
bs, ok := m.bindings[ccCode]
|
||||
if !ok {
|
||||
return VerifyResult{Valid: false, MACode: maCode, SubmittedHash: fileHash}, ErrMANotIssued
|
||||
return VerifyResult{Valid: false, CCCode: ccCode, SubmittedHash: fileHash}, ErrMANotIssued
|
||||
}
|
||||
var bound string
|
||||
for _, b := range bs {
|
||||
@@ -190,7 +190,7 @@ func (m *MemoryChain) VerifyEpisodeHash(maCode string, episode int, fileHash str
|
||||
}
|
||||
if b.HashValue == fileHash {
|
||||
return VerifyResult{
|
||||
Valid: true, MACode: maCode,
|
||||
Valid: true, CCCode: ccCode,
|
||||
BoundHash: b.HashValue, SubmittedHash: fileHash,
|
||||
Match: true, Version: b.Version,
|
||||
}, nil
|
||||
@@ -198,16 +198,16 @@ func (m *MemoryChain) VerifyEpisodeHash(maCode string, episode int, fileHash str
|
||||
}
|
||||
}
|
||||
if bound == "" {
|
||||
return VerifyResult{Valid: false, MACode: maCode, SubmittedHash: fileHash}, ErrNotFound
|
||||
return VerifyResult{Valid: false, CCCode: ccCode, SubmittedHash: fileHash}, ErrNotFound
|
||||
}
|
||||
return VerifyResult{Valid: true, MACode: maCode, BoundHash: bound, SubmittedHash: fileHash, Match: false}, nil
|
||||
return VerifyResult{Valid: true, CCCode: ccCode, BoundHash: bound, SubmittedHash: fileHash, Match: false}, nil
|
||||
}
|
||||
|
||||
// ListEpisodes 返回某 MA 码下的全部集级哈希绑定(episode > 0)。
|
||||
func (m *MemoryChain) ListEpisodes(maCode string) ([]model.HashBinding, error) {
|
||||
func (m *MemoryChain) ListEpisodes(ccCode string) ([]model.HashBinding, error) {
|
||||
m.mu.RLock()
|
||||
defer m.mu.RUnlock()
|
||||
bs, ok := m.bindings[maCode]
|
||||
bs, ok := m.bindings[ccCode]
|
||||
if !ok {
|
||||
return nil, ErrMANotIssued
|
||||
}
|
||||
@@ -221,10 +221,10 @@ func (m *MemoryChain) ListEpisodes(maCode string) ([]model.HashBinding, error) {
|
||||
}
|
||||
|
||||
// QueryContent 查询内容主记录。
|
||||
func (m *MemoryChain) QueryContent(maCode string) (model.Content, error) {
|
||||
func (m *MemoryChain) QueryContent(ccCode string) (model.Content, error) {
|
||||
m.mu.RLock()
|
||||
defer m.mu.RUnlock()
|
||||
c, ok := m.contents[maCode]
|
||||
c, ok := m.contents[ccCode]
|
||||
if !ok {
|
||||
return model.Content{}, ErrNotFound
|
||||
}
|
||||
@@ -252,14 +252,14 @@ func (m *MemoryChain) ListContents(status string) ([]model.Content, error) {
|
||||
}
|
||||
|
||||
// QueryMappings 查询 MA 码绑定的全部映射与 CDN 端点。
|
||||
func (m *MemoryChain) QueryMappings(maCode string) (MappingsResult, error) {
|
||||
func (m *MemoryChain) QueryMappings(ccCode string) (MappingsResult, error) {
|
||||
m.mu.RLock()
|
||||
defer m.mu.RUnlock()
|
||||
if _, ok := m.contents[maCode]; !ok {
|
||||
if _, ok := m.contents[ccCode]; !ok {
|
||||
return MappingsResult{}, ErrNotFound
|
||||
}
|
||||
res := MappingsResult{MACode: maCode, Mappings: m.mappings[maCode]}
|
||||
for _, mp := range m.mappings[maCode] {
|
||||
res := MappingsResult{CCCode: ccCode, Mappings: m.mappings[ccCode]}
|
||||
for _, mp := range m.mappings[ccCode] {
|
||||
if mp.CDNEndpoint != "" {
|
||||
res.CDNEndpoints = append(res.CDNEndpoints, mp.CDNEndpoint)
|
||||
}
|
||||
@@ -271,30 +271,30 @@ func (m *MemoryChain) QueryMappings(maCode string) (MappingsResult, error) {
|
||||
func (m *MemoryChain) RecordVersionChange(vc model.VersionChange) (string, error) {
|
||||
m.mu.Lock()
|
||||
defer m.mu.Unlock()
|
||||
maCode := m.maCodeByCTID(vc.ContentTwinID)
|
||||
if maCode == "" {
|
||||
ccCode := m.ccCodeByCTID(vc.ContentTwinID)
|
||||
if ccCode == "" {
|
||||
return "", ErrMANotIssued
|
||||
}
|
||||
m.versions[maCode] = append(m.versions[maCode], vc)
|
||||
m.versions[ccCode] = append(m.versions[ccCode], vc)
|
||||
return m.nextTx("recordVersionChange"), nil
|
||||
}
|
||||
|
||||
// Revoke 下架,仅监管主体。
|
||||
func (m *MemoryChain) Revoke(role Role, maCode, reason string) (MappingsResult, error) {
|
||||
func (m *MemoryChain) Revoke(role Role, ccCode, reason string) (MappingsResult, error) {
|
||||
if role != RoleRegulator {
|
||||
return MappingsResult{}, ErrPermissionDenied
|
||||
}
|
||||
m.mu.Lock()
|
||||
defer m.mu.Unlock()
|
||||
c, ok := m.contents[maCode]
|
||||
c, ok := m.contents[ccCode]
|
||||
if !ok {
|
||||
return MappingsResult{}, ErrNotFound
|
||||
}
|
||||
c.Status = model.StatusRevoked
|
||||
m.contents[maCode] = c
|
||||
m.contents[ccCode] = c
|
||||
|
||||
res := MappingsResult{MACode: maCode, Mappings: m.mappings[maCode]}
|
||||
for _, mp := range m.mappings[maCode] {
|
||||
res := MappingsResult{CCCode: ccCode, Mappings: m.mappings[ccCode]}
|
||||
for _, mp := range m.mappings[ccCode] {
|
||||
if mp.CDNEndpoint != "" {
|
||||
res.CDNEndpoints = append(res.CDNEndpoints, mp.CDNEndpoint)
|
||||
}
|
||||
@@ -303,13 +303,13 @@ func (m *MemoryChain) Revoke(role Role, maCode, reason string) (MappingsResult,
|
||||
}
|
||||
|
||||
// RevokeEpisode 集级下架:只下架指定集,整剧其他集不受影响(仅监管主体)。
|
||||
func (m *MemoryChain) RevokeEpisode(role Role, maCode string, episode int, reason string) error {
|
||||
func (m *MemoryChain) RevokeEpisode(role Role, ccCode string, episode int, reason string) error {
|
||||
if role != RoleRegulator {
|
||||
return ErrPermissionDenied
|
||||
}
|
||||
m.mu.Lock()
|
||||
defer m.mu.Unlock()
|
||||
bs, ok := m.bindings[maCode]
|
||||
bs, ok := m.bindings[ccCode]
|
||||
if !ok {
|
||||
return ErrMANotIssued
|
||||
}
|
||||
@@ -324,34 +324,34 @@ func (m *MemoryChain) RevokeEpisode(role Role, maCode string, episode int, reaso
|
||||
if !found {
|
||||
return ErrNotFound
|
||||
}
|
||||
m.bindings[maCode] = bs
|
||||
m.bindings[ccCode] = bs
|
||||
return nil
|
||||
}
|
||||
|
||||
// Restore 恢复上架整剧:下架状态恢复为流通中(仅监管主体)。
|
||||
func (m *MemoryChain) Restore(role Role, maCode string) error {
|
||||
func (m *MemoryChain) Restore(role Role, ccCode string) error {
|
||||
if role != RoleRegulator {
|
||||
return ErrPermissionDenied
|
||||
}
|
||||
m.mu.Lock()
|
||||
defer m.mu.Unlock()
|
||||
c, ok := m.contents[maCode]
|
||||
c, ok := m.contents[ccCode]
|
||||
if !ok {
|
||||
return ErrNotFound
|
||||
}
|
||||
c.Status = model.StatusPublished
|
||||
m.contents[maCode] = c
|
||||
m.contents[ccCode] = c
|
||||
return nil
|
||||
}
|
||||
|
||||
// RestoreEpisode 恢复上架指定集(仅监管主体)。
|
||||
func (m *MemoryChain) RestoreEpisode(role Role, maCode string, episode int) error {
|
||||
func (m *MemoryChain) RestoreEpisode(role Role, ccCode string, episode int) error {
|
||||
if role != RoleRegulator {
|
||||
return ErrPermissionDenied
|
||||
}
|
||||
m.mu.Lock()
|
||||
defer m.mu.Unlock()
|
||||
bs, ok := m.bindings[maCode]
|
||||
bs, ok := m.bindings[ccCode]
|
||||
if !ok {
|
||||
return ErrMANotIssued
|
||||
}
|
||||
@@ -366,25 +366,25 @@ func (m *MemoryChain) RestoreEpisode(role Role, maCode string, episode int) erro
|
||||
if !found {
|
||||
return ErrNotFound
|
||||
}
|
||||
m.bindings[maCode] = bs
|
||||
m.bindings[ccCode] = bs
|
||||
return nil
|
||||
}
|
||||
|
||||
// SetContentStatus 更新内容状态。
|
||||
func (m *MemoryChain) SetContentStatus(maCode, status string) error {
|
||||
func (m *MemoryChain) SetContentStatus(ccCode, status string) error {
|
||||
m.mu.Lock()
|
||||
defer m.mu.Unlock()
|
||||
c, ok := m.contents[maCode]
|
||||
c, ok := m.contents[ccCode]
|
||||
if !ok {
|
||||
return ErrNotFound
|
||||
}
|
||||
c.Status = status
|
||||
m.contents[maCode] = c
|
||||
m.contents[ccCode] = c
|
||||
return nil
|
||||
}
|
||||
|
||||
// maCodeByCTID 内部辅助:通过 CTID 反查 MA 码(调用方已持锁)。
|
||||
func (m *MemoryChain) maCodeByCTID(ctid string) string {
|
||||
// ccCodeByCTID 内部辅助:通过 CTID 反查 MA 码(调用方已持锁)。
|
||||
func (m *MemoryChain) ccCodeByCTID(ctid string) string {
|
||||
for ma, c := range m.contents {
|
||||
if c.ContentTwinID == ctid {
|
||||
return ma
|
||||
@@ -397,15 +397,15 @@ func (m *MemoryChain) maCodeByCTID(ctid string) string {
|
||||
func (m *MemoryChain) QueryByHash(fileHash string) (model.ContentQueryResult, error) {
|
||||
m.mu.RLock()
|
||||
defer m.mu.RUnlock()
|
||||
maCode, ok := m.hashIndex[fileHash]
|
||||
ccCode, ok := m.hashIndex[fileHash]
|
||||
if !ok {
|
||||
return model.ContentQueryResult{Found: false}, ErrNotFound
|
||||
}
|
||||
return model.ContentQueryResult{
|
||||
Found: true,
|
||||
Content: m.contents[maCode],
|
||||
Bindings: m.bindings[maCode],
|
||||
Mappings: m.mappings[maCode],
|
||||
Content: m.contents[ccCode],
|
||||
Bindings: m.bindings[ccCode],
|
||||
Mappings: m.mappings[ccCode],
|
||||
}, nil
|
||||
}
|
||||
|
||||
@@ -413,13 +413,13 @@ func (m *MemoryChain) QueryByHash(fileHash string) (model.ContentQueryResult, er
|
||||
func (m *MemoryChain) QueryByProvincialCode(provincialCode string) (model.ContentQueryResult, error) {
|
||||
m.mu.RLock()
|
||||
defer m.mu.RUnlock()
|
||||
for maCode, maps := range m.mappings {
|
||||
for ccCode, maps := range m.mappings {
|
||||
for _, mp := range maps {
|
||||
if mp.Party == model.PartyCP && mp.PartyID == provincialCode {
|
||||
return model.ContentQueryResult{
|
||||
Found: true,
|
||||
Content: m.contents[maCode],
|
||||
Bindings: m.bindings[maCode],
|
||||
Content: m.contents[ccCode],
|
||||
Bindings: m.bindings[ccCode],
|
||||
Mappings: maps,
|
||||
}, nil
|
||||
}
|
||||
@@ -432,13 +432,13 @@ func (m *MemoryChain) QueryByProvincialCode(provincialCode string) (model.Conten
|
||||
func (m *MemoryChain) QueryByLibraryFileID(libraryFileID string) (model.ContentQueryResult, error) {
|
||||
m.mu.RLock()
|
||||
defer m.mu.RUnlock()
|
||||
for maCode, maps := range m.mappings {
|
||||
for ccCode, maps := range m.mappings {
|
||||
for _, mp := range maps {
|
||||
if mp.Party == model.PartyReviewer && mp.PartyID == libraryFileID {
|
||||
return model.ContentQueryResult{
|
||||
Found: true,
|
||||
Content: m.contents[maCode],
|
||||
Bindings: m.bindings[maCode],
|
||||
Content: m.contents[ccCode],
|
||||
Bindings: m.bindings[ccCode],
|
||||
Mappings: maps,
|
||||
}, nil
|
||||
}
|
||||
@@ -456,7 +456,7 @@ func (m *MemoryChain) MergeMA(role Role, req model.MergeRequest) (model.MergeRes
|
||||
m.mu.Lock()
|
||||
defer m.mu.Unlock()
|
||||
|
||||
primary, ok := m.contents[req.PrimaryMACode]
|
||||
primary, ok := m.contents[req.PrimaryCCCode]
|
||||
if !ok {
|
||||
return model.MergeResult{}, ErrNotFound
|
||||
}
|
||||
@@ -464,7 +464,7 @@ func (m *MemoryChain) MergeMA(role Role, req model.MergeRequest) (model.MergeRes
|
||||
|
||||
migratedBindings := 0
|
||||
migratedMappings := 0
|
||||
for _, secMA := range req.SecondaryMACodes {
|
||||
for _, secMA := range req.SecondaryCCCodes {
|
||||
secContent, exists := m.contents[secMA]
|
||||
if !exists {
|
||||
return model.MergeResult{}, fmt.Errorf("%w: secondary MA %s not found", ErrNotFound, secMA)
|
||||
@@ -476,21 +476,21 @@ func (m *MemoryChain) MergeMA(role Role, req model.MergeRequest) (model.MergeRes
|
||||
// 迁移哈希绑定至主 MA 码
|
||||
for _, b := range m.bindings[secMA] {
|
||||
b.ContentTwinID = primary.ContentTwinID
|
||||
m.bindings[req.PrimaryMACode] = append(m.bindings[req.PrimaryMACode], b)
|
||||
m.bindings[req.PrimaryCCCode] = append(m.bindings[req.PrimaryCCCode], b)
|
||||
migratedBindings++
|
||||
}
|
||||
|
||||
// 迁移映射至主 MA 码
|
||||
for _, mp := range m.mappings[secMA] {
|
||||
mp.ContentTwinID = primary.ContentTwinID
|
||||
m.mappings[req.PrimaryMACode] = append(m.mappings[req.PrimaryMACode], mp)
|
||||
m.mappings[req.PrimaryCCCode] = append(m.mappings[req.PrimaryCCCode], mp)
|
||||
migratedMappings++
|
||||
}
|
||||
|
||||
// 更新哈希索引指向主 MA 码
|
||||
for _, b := range m.bindings[secMA] {
|
||||
if b.HashType == model.HashFile || b.HashType == model.HashTranscoded {
|
||||
m.hashIndex[b.HashValue] = req.PrimaryMACode
|
||||
m.hashIndex[b.HashValue] = req.PrimaryCCCode
|
||||
}
|
||||
}
|
||||
|
||||
@@ -503,8 +503,8 @@ func (m *MemoryChain) MergeMA(role Role, req model.MergeRequest) (model.MergeRes
|
||||
|
||||
txID := m.nextTx("mergeMA")
|
||||
return model.MergeResult{
|
||||
PrimaryMACode: req.PrimaryMACode,
|
||||
MergedMACodes: req.SecondaryMACodes,
|
||||
PrimaryCCCode: req.PrimaryCCCode,
|
||||
MergedCCCodes: req.SecondaryCCCodes,
|
||||
MigratedBindings: migratedBindings,
|
||||
MigratedMappings: migratedMappings,
|
||||
TxID: txID,
|
||||
@@ -520,30 +520,30 @@ func (m *MemoryChain) SplitMA(role Role, req model.SplitRequest) (model.SplitRes
|
||||
m.mu.Lock()
|
||||
defer m.mu.Unlock()
|
||||
|
||||
srcContent, ok := m.contents[req.SourceMACode]
|
||||
srcContent, ok := m.contents[req.SourceCCCode]
|
||||
if !ok {
|
||||
return model.SplitResult{}, ErrNotFound
|
||||
}
|
||||
if srcContent.Status == model.StatusMerged || srcContent.Status == model.StatusSplit {
|
||||
return model.SplitResult{}, fmt.Errorf("chain: MA %s already %s", req.SourceMACode, srcContent.Status)
|
||||
return model.SplitResult{}, fmt.Errorf("chain: MA %s already %s", req.SourceCCCode, srcContent.Status)
|
||||
}
|
||||
|
||||
migratedBindings := 0
|
||||
migratedMappings := 0
|
||||
newMACodes := make([]string, 0, len(req.Splits))
|
||||
newCCCodes := make([]string, 0, len(req.Splits))
|
||||
|
||||
for _, split := range req.Splits {
|
||||
// 创建新内容记录
|
||||
newContent := srcContent
|
||||
newContent.MACode = split.NewMACode
|
||||
newContent.ContentTwinID = split.NewMACode + "-ctid"
|
||||
newContent.CCCode = split.NewCCCode
|
||||
newContent.ContentTwinID = split.NewCCCode + "-ctid"
|
||||
newContent.Title = split.Title
|
||||
newContent.Status = model.StatusApproved
|
||||
newContent.CreatedAt = time.Now()
|
||||
m.contents[split.NewMACode] = newContent
|
||||
m.contents[split.NewCCCode] = newContent
|
||||
|
||||
// 按集号迁移哈希绑定
|
||||
for _, b := range m.bindings[req.SourceMACode] {
|
||||
for _, b := range m.bindings[req.SourceCCCode] {
|
||||
shouldMigrate := false
|
||||
if len(split.Episodes) == 0 {
|
||||
// 空集号列表:迁移全部(整剧拆分场景)
|
||||
@@ -559,34 +559,34 @@ func (m *MemoryChain) SplitMA(role Role, req model.SplitRequest) (model.SplitRes
|
||||
if shouldMigrate {
|
||||
newB := b
|
||||
newB.ContentTwinID = newContent.ContentTwinID
|
||||
m.bindings[split.NewMACode] = append(m.bindings[split.NewMACode], newB)
|
||||
m.bindings[split.NewCCCode] = append(m.bindings[split.NewCCCode], newB)
|
||||
// 更新哈希索引
|
||||
if b.HashType == model.HashFile || b.HashType == model.HashTranscoded {
|
||||
m.hashIndex[b.HashValue] = split.NewMACode
|
||||
m.hashIndex[b.HashValue] = split.NewCCCode
|
||||
}
|
||||
migratedBindings++
|
||||
}
|
||||
}
|
||||
|
||||
// 迁移映射(全部映射复制到新 MA 码)
|
||||
for _, mp := range m.mappings[req.SourceMACode] {
|
||||
for _, mp := range m.mappings[req.SourceCCCode] {
|
||||
newMP := mp
|
||||
newMP.ContentTwinID = newContent.ContentTwinID
|
||||
m.mappings[split.NewMACode] = append(m.mappings[split.NewMACode], newMP)
|
||||
m.mappings[split.NewCCCode] = append(m.mappings[split.NewCCCode], newMP)
|
||||
migratedMappings++
|
||||
}
|
||||
|
||||
newMACodes = append(newMACodes, split.NewMACode)
|
||||
newCCCodes = append(newCCCodes, split.NewCCCode)
|
||||
}
|
||||
|
||||
// 源 MA 码状态标记为 split
|
||||
srcContent.Status = model.StatusSplit
|
||||
m.contents[req.SourceMACode] = srcContent
|
||||
m.contents[req.SourceCCCode] = srcContent
|
||||
|
||||
txID := m.nextTx("splitMA")
|
||||
return model.SplitResult{
|
||||
SourceMACode: req.SourceMACode,
|
||||
NewMACodes: newMACodes,
|
||||
SourceCCCode: req.SourceCCCode,
|
||||
NewCCCodes: newCCCodes,
|
||||
MigratedBindings: migratedBindings,
|
||||
MigratedMappings: migratedMappings,
|
||||
TxID: txID,
|
||||
|
||||
Reference in New Issue
Block a user