feat: 更新CC码监管方案业务版文档,新增摘要、UGC专项、直播专项、存量处置、误判申诉、水印嵌入、CC码生命周期、组织保障等章节;同步macode到cccode重命名及其他代码变更

This commit is contained in:
freedakgmail
2026-07-20 22:25:40 +08:00
parent 9f1e2d0a21
commit eb4a82a877
76 changed files with 2600 additions and 2122 deletions
+11 -11
View File
@@ -8,14 +8,14 @@
| 方法 | 权限 | 说明 |
|------|------|------|
| `IssueMA(maCode, ctid, merkleRoot, fileHash, contentJSON)` | 仅监管节点 | 签发 MA 码并 1:1 强绑定哈希;不可重复、不可解绑 |
| `IssueMA(ccCode, ctid, merkleRoot, fileHash, contentJSON)` | 仅监管节点 | 签发 MA 码并 1:1 强绑定哈希;不可重复、不可解绑 |
| `RegisterHashBinding(ctid, bindingJSON)` | 审核/监管 | 追加哈希绑定(转码版父子关系) |
| `RegisterMapping(ctid, party, partyID, cdnEndpoint)` | 三方 | 注册编码映射;MA 必须已签发 |
| `VerifyHash(maCode, fileHash) -> bool` | 任意 | 校验提交哈希与绑定哈希是否一致 |
| `QueryContent(maCode) -> json` | 任意 | 查询内容主记录 |
| `QueryMappings(maCode) -> json` | 任意 | 查询全部三方映射与 CDN 端点 |
| `VerifyHash(ccCode, fileHash) -> bool` | 任意 | 校验提交哈希与绑定哈希是否一致 |
| `QueryContent(ccCode) -> json` | 任意 | 查询内容主记录 |
| `QueryMappings(ccCode) -> json` | 任意 | 查询全部三方映射与 CDN 端点 |
| `RecordVersionChange(ctid, vcJSON)` | 审核/监管 | 记录版本变更,触发重审 |
| `Revoke(maCode, reason)` | 仅监管节点 | 下架,返回受影响映射 |
| `Revoke(ccCode, reason)` | 仅监管节点 | 下架,返回受影响映射 |
## 权限模型(对应需求14
@@ -33,12 +33,12 @@
## 状态键设计(KV
```
content:{maCode} -> Content JSON
binding:{maCode}:{idx} -> HashBinding JSON
hashidx:{fileHash} -> maCode (防换壳重发)
mapping:{maCode}:{idx} -> Mapping JSON
version:{maCode}:{idx} -> VersionChange JSON
ctid2ma:{ctid} -> maCode
content:{ccCode} -> Content JSON
binding:{ccCode}:{idx} -> HashBinding JSON
hashidx:{fileHash} -> ccCode (防换壳重发)
mapping:{ccCode}:{idx} -> Mapping JSON
version:{ccCode}:{idx} -> VersionChange JSON
ctid2ma:{ctid} -> ccCode
```
## 构建与部署(真实链,二期接入)
+45 -45
View File
@@ -6,18 +6,18 @@
//
// 状态键设计(KV):
//
// content:{maCode} -> Content JSON(含 status
// binding:{maCode}:0 -> 整剧 file 哈希绑定 JSON
// binding:{maCode}:p -> 感知哈希绑定 JSON
// ep:{maCode}:{n} -> 集级绑定 JSON {episode,hash,revoked,reason}
// epcount:{maCode} -> 集数 N
// hashidx:{fileHash} -> maCode(防换壳重发)
// mapping:{maCode}:{idx} -> Mapping JSON
// mapcount:{maCode} -> 映射数 N
// version:{maCode}:{idx} -> VersionChange JSON
// vercount:{maCode} -> 版本变更数 N
// ctid2ma:{ctid} -> maCode
// allmacodes -> []maCode(供 ListContents 遍历)
// content:{ccCode} -> Content JSON(含 status
// binding:{ccCode}:0 -> 整剧 file 哈希绑定 JSON
// binding:{ccCode}:p -> 感知哈希绑定 JSON
// ep:{ccCode}:{n} -> 集级绑定 JSON {episode,hash,revoked,reason}
// epcount:{ccCode} -> 集数 N
// hashidx:{fileHash} -> ccCode(防换壳重发)
// mapping:{ccCode}:{idx} -> Mapping JSON
// mapcount:{ccCode} -> 映射数 N
// version:{ccCode}:{idx} -> VersionChange JSON
// vercount:{ccCode} -> 版本变更数 N
// ctid2ma:{ctid} -> ccCode
// allcccodes -> []ccCode(供 ListContents 遍历)
//
// 权限:通过 sender 组织证书判断(仅监管组织可 IssueMA/Revoke/RevokeEpisode/Restore)。
package main
@@ -76,15 +76,15 @@ type epBinding struct {
Reason string `json:"revoked_reason,omitempty"`
}
// appendMACode 把新发码加入全局列表(供 ListContents 遍历)。
func appendMACode(maCode string) {
// appendCCCode 把新发码加入全局列表(供 ListContents 遍历)。
func appendCCCode(ccCode string) {
var all []string
if v, _ := sdk.Instance.GetStateByte("allmacodes", ""); len(v) > 0 {
if v, _ := sdk.Instance.GetStateByte("allcccodes", ""); len(v) > 0 {
_ = json.Unmarshal(v, &all)
}
all = append(all, maCode)
all = append(all, ccCode)
b, _ := json.Marshal(all)
_ = sdk.Instance.PutStateByte("allmacodes", "", b)
_ = sdk.Instance.PutStateByte("allcccodes", "", b)
}
// ---- 写方法 ----
@@ -95,11 +95,11 @@ func (t *TCSRegistry) IssueMA() protogo.Response {
return sdk.Error("permission denied: only regulator can issue MA")
}
args := sdk.Instance.GetArgs()
maCode := string(args["ma_code"])
ccCode := string(args["ma_code"])
ctid := string(args["ctid"])
fileHash := string(args["file_hash"])
if existing, _ := sdk.Instance.GetStateByte("content", maCode); len(existing) > 0 {
if existing, _ := sdk.Instance.GetStateByte("content", ccCode); len(existing) > 0 {
return sdk.Error("MA already issued (1:1 binding immutable)")
}
if bound, _ := sdk.Instance.GetStateByte("hashidx", fileHash); len(bound) > 0 {
@@ -112,21 +112,21 @@ func (t *TCSRegistry) IssueMA() protogo.Response {
if content == nil {
content = map[string]interface{}{}
}
content["ma_code"] = maCode
content["ma_code"] = ccCode
content["content_twin_id"] = ctid
content["status"] = "approved"
cj, _ := json.Marshal(content)
_ = sdk.Instance.PutStateByte("content", maCode, cj)
_ = sdk.Instance.PutStateByte("content", ccCode, cj)
// 整剧 file 绑定 + 感知哈希绑定
fb, _ := json.Marshal(epBinding{Episode: 0, HashValue: fileHash})
_ = sdk.Instance.PutStateByte("binding", maCode+":0", fb)
_ = sdk.Instance.PutStateByte("binding", ccCode+":0", fb)
if ph := string(args["perceptual_hash"]); ph != "" {
pb, _ := json.Marshal(map[string]string{"hash_type": "perceptual", "hash_value": ph})
_ = sdk.Instance.PutStateByte("binding", maCode+":p", pb)
_ = sdk.Instance.PutStateByte("binding", ccCode+":p", pb)
}
_ = sdk.Instance.PutStateByte("hashidx", fileHash, []byte(maCode))
_ = sdk.Instance.PutStateByte("ctid2ma", ctid, []byte(maCode))
_ = sdk.Instance.PutStateByte("hashidx", fileHash, []byte(ccCode))
_ = sdk.Instance.PutStateByte("ctid2ma", ctid, []byte(ccCode))
// 集级哈希
var eps []map[string]interface{}
@@ -139,19 +139,19 @@ func (t *TCSRegistry) IssueMA() protogo.Response {
continue
}
eb, _ := json.Marshal(epBinding{Episode: ep, HashValue: hv})
_ = sdk.Instance.PutStateByte("ep", maCode+":"+strconv.Itoa(ep), eb)
_ = sdk.Instance.PutStateByte("ep", ccCode+":"+strconv.Itoa(ep), eb)
if exist, _ := sdk.Instance.GetStateByte("hashidx", hv); len(exist) == 0 {
_ = sdk.Instance.PutStateByte("hashidx", hv, []byte(maCode))
_ = sdk.Instance.PutStateByte("hashidx", hv, []byte(ccCode))
}
if ep > n {
n = ep
}
}
putInt("epcount", maCode, n)
appendMACode(maCode)
putInt("epcount", ccCode, n)
appendCCCode(ccCode)
sdk.Instance.EmitEvent("RegisterSuccess", []string{maCode, fileHash})
return sdk.Success([]byte(maCode))
sdk.Instance.EmitEvent("RegisterSuccess", []string{ccCode, fileHash})
return sdk.Success([]byte(ccCode))
}
func toFloat(v interface{}) float64 {
@@ -226,8 +226,8 @@ func (t *TCSRegistry) SetContentStatus() protogo.Response {
return setStatus(string(args["ma_code"]), string(args["status"]), "StatusChanged")
}
func setStatus(maCode, status, event string) protogo.Response {
cj, _ := sdk.Instance.GetStateByte("content", maCode)
func setStatus(ccCode, status, event string) protogo.Response {
cj, _ := sdk.Instance.GetStateByte("content", ccCode)
if len(cj) == 0 {
return sdk.Error("not found")
}
@@ -235,8 +235,8 @@ func setStatus(maCode, status, event string) protogo.Response {
_ = json.Unmarshal(cj, &content)
content["status"] = status
nj, _ := json.Marshal(content)
_ = sdk.Instance.PutStateByte("content", maCode, nj)
sdk.Instance.EmitEvent(event, []string{maCode, status})
_ = sdk.Instance.PutStateByte("content", ccCode, nj)
sdk.Instance.EmitEvent(event, []string{ccCode, status})
return sdk.Success([]byte("ok"))
}
@@ -266,8 +266,8 @@ func (t *TCSRegistry) Restore() protogo.Response {
return setStatus(string(sdk.Instance.GetArgs()["ma_code"]), "published", "Restored")
}
func setEpisodeRevoked(maCode, epStr string, revoked bool, reason string) protogo.Response {
key := maCode + ":" + epStr
func setEpisodeRevoked(ccCode, epStr string, revoked bool, reason string) protogo.Response {
key := ccCode + ":" + epStr
v, _ := sdk.Instance.GetStateByte("ep", key)
if len(v) == 0 {
return sdk.Error("not found")
@@ -316,11 +316,11 @@ func (t *TCSRegistry) HashExists() protogo.Response {
// ListEpisodes 返回某 MA 的集级绑定数组(JSON)。
func (t *TCSRegistry) ListEpisodes() protogo.Response {
maCode := string(sdk.Instance.GetArgs()["ma_code"])
n := getInt("epcount", maCode)
ccCode := string(sdk.Instance.GetArgs()["ma_code"])
n := getInt("epcount", ccCode)
out := make([]epBinding, 0, n)
for i := 1; i <= n; i++ {
if v, _ := sdk.Instance.GetStateByte("ep", maCode+":"+strconv.Itoa(i)); len(v) > 0 {
if v, _ := sdk.Instance.GetStateByte("ep", ccCode+":"+strconv.Itoa(i)); len(v) > 0 {
var eb epBinding
_ = json.Unmarshal(v, &eb)
out = append(out, eb)
@@ -341,12 +341,12 @@ func (t *TCSRegistry) QueryContent() protogo.Response {
// QueryMappings 返回某 MA 的全部映射(JSON {mappings:[],cdn_endpoints:[]})。
func (t *TCSRegistry) QueryMappings() protogo.Response {
maCode := string(sdk.Instance.GetArgs()["ma_code"])
n := getInt("mapcount", maCode)
ccCode := string(sdk.Instance.GetArgs()["ma_code"])
n := getInt("mapcount", ccCode)
maps := make([]map[string]interface{}, 0, n)
cdns := []string{}
for i := 1; i <= n; i++ {
if v, _ := sdk.Instance.GetStateByte("mapping", maCode+":"+strconv.Itoa(i)); len(v) > 0 {
if v, _ := sdk.Instance.GetStateByte("mapping", ccCode+":"+strconv.Itoa(i)); len(v) > 0 {
var m map[string]interface{}
_ = json.Unmarshal(v, &m)
maps = append(maps, m)
@@ -355,7 +355,7 @@ func (t *TCSRegistry) QueryMappings() protogo.Response {
}
}
}
b, _ := json.Marshal(map[string]interface{}{"ma_code": maCode, "mappings": maps, "cdn_endpoints": cdns})
b, _ := json.Marshal(map[string]interface{}{"ma_code": ccCode, "mappings": maps, "cdn_endpoints": cdns})
return sdk.Success(b)
}
@@ -363,7 +363,7 @@ func (t *TCSRegistry) QueryMappings() protogo.Response {
func (t *TCSRegistry) ListContents() protogo.Response {
status := string(sdk.Instance.GetArgs()["status"])
var all []string
if v, _ := sdk.Instance.GetStateByte("allmacodes", ""); len(v) > 0 {
if v, _ := sdk.Instance.GetStateByte("allcccodes", ""); len(v) > 0 {
_ = json.Unmarshal(v, &all)
}
out := make([]map[string]interface{}, 0, len(all))