feat: 更新CC码监管方案业务版文档,新增摘要、UGC专项、直播专项、存量处置、误判申诉、水印嵌入、CC码生命周期、组织保障等章节;同步macode到cccode重命名及其他代码变更
This commit is contained in:
@@ -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
|
||||
```
|
||||
|
||||
## 构建与部署(真实链,二期接入)
|
||||
|
||||
@@ -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))
|
||||
|
||||
Reference in New Issue
Block a user