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
+6 -6
View File
@@ -29,12 +29,12 @@ func (h *CatalogHandler) Register(rg *gin.RouterGroup) {
}
func (h *CatalogHandler) queryByMA(c *gin.Context) {
maCode := c.Query("ma_code")
if maCode == "" {
ccCode := c.Query("ma_code")
if ccCode == "" {
httpx.Error(c, http.StatusBadRequest, "INVALID_REQUEST", "缺少 ma_code 参数")
return
}
result, err := h.cat.QueryByMA(maCode)
result, err := h.cat.QueryByMA(ccCode)
if err != nil {
httpx.Error(c, http.StatusNotFound, "NOT_FOUND", err.Error())
return
@@ -85,12 +85,12 @@ func (h *CatalogHandler) queryByLibraryID(c *gin.Context) {
}
func (h *CatalogHandler) queryAll(c *gin.Context) {
maCode := c.Query("ma_code")
if maCode == "" {
ccCode := c.Query("ma_code")
if ccCode == "" {
httpx.Error(c, http.StatusBadRequest, "INVALID_REQUEST", "缺少 ma_code 参数")
return
}
result, err := h.cat.QueryAll(maCode)
result, err := h.cat.QueryAll(ccCode)
if err != nil {
httpx.Error(c, http.StatusNotFound, "NOT_FOUND", err.Error())
return
@@ -20,7 +20,7 @@ func TestCatalogHandler_QueryByMA(t *testing.T) {
// 发码
_, err := c.IssueMA(chain.RoleRegulator, chain.IssueRequest{
MACode: "MA.156.8531.6101/WD/20260000001", ContentTwinID: "ctid-cat-svc-001",
CCCode: "MA.156.8531.6101/WD/20260000001", ContentTwinID: "ctid-cat-svc-001",
FileHash: "fh-cat-svc-001", MerkleRoot: "mr-cat-svc-001",
Content: model.Content{Title: "目录库服务测试", MAType: "WD", Issuer: "测试局"},
})
@@ -55,7 +55,7 @@ func TestCatalogHandler_QueryByHash(t *testing.T) {
c := chain.NewMemoryChain()
_, err := c.IssueMA(chain.RoleRegulator, chain.IssueRequest{
MACode: "MA.156.8531.6101/WD/20260000002", ContentTwinID: "ctid-cat-svc-002",
CCCode: "MA.156.8531.6101/WD/20260000002", ContentTwinID: "ctid-cat-svc-002",
FileHash: "fh-cat-svc-002", MerkleRoot: "mr-cat-svc-002",
Content: model.Content{Title: "Hash查询服务测试", MAType: "WD", Issuer: "测试局"},
})
@@ -81,7 +81,7 @@ func TestCatalogHandler_QueryByHash(t *testing.T) {
err = json.Unmarshal(w.Body.Bytes(), &resp)
require.NoError(t, err)
assert.True(t, resp.Data.Found)
assert.Equal(t, "MA.156.8531.6101/WD/20260000002", resp.Data.Content.MACode)
assert.Equal(t, "MA.156.8531.6101/WD/20260000002", resp.Data.Content.CCCode)
// 缺少参数
w2 := httptest.NewRecorder()
+72 -72
View File
@@ -44,7 +44,7 @@ func (h *Handler) Register(rg *gin.RouterGroup) {
rg.GET("/content/reviews", h.listReviews) // 送审待办队列(待审/待发码)
rg.GET("/content/list", h.listContents) // 内容队列(待入库/待发布/待注入)
rg.POST("/data/playback", h.reportPlayback) // 播放数据回传(需求9
rg.GET("/data/playback-summary", h.playbackSummary) // 按MA码聚合可信播放数据(需求9/21
rg.GET("/data/playback-summary", h.playbackSummary) // 按CC码聚合可信播放数据(需求9/21
rg.POST("/settlement/compute", h.computeSettlement) // 基于可信播放数据分账(需求21
rg.GET("/content/provenance", h.provenance) // 全链路存证(需求22
rg.GET("/content/accountability", h.accountability) // 责任界定取证(需求22
@@ -147,7 +147,7 @@ func (h *Handler) issue(c *gin.Context) {
}
type verifyReq struct {
MACode string `json:"ma_code"`
CCCode string `json:"ma_code"`
FileHash string `json:"file_sha256"`
}
@@ -157,7 +157,7 @@ func (h *Handler) verify(c *gin.Context) {
httpx.Error(c, http.StatusBadRequest, "INVALID_REQUEST", err.Error())
return
}
res, err := h.svc.Verify(req.MACode, req.FileHash)
res, err := h.svc.Verify(req.CCCode, req.FileHash)
if err != nil {
// 验真不匹配也返回结果体,便于调用方据 match 处理
httpx.Error(c, http.StatusBadRequest, "VERIFY_MISMATCH", err.Error())
@@ -210,7 +210,7 @@ func (h *Handler) bindTranscoded(c *gin.Context) {
}
type ingestReq struct {
MACode string `json:"ma_code"`
CCCode string `json:"ma_code"`
CTID string `json:"content_twin_id"`
MediaAssetID string `json:"media_asset_id"`
LibName string `json:"lib_name"`
@@ -222,15 +222,15 @@ func (h *Handler) ingest(c *gin.Context) {
httpx.Error(c, http.StatusBadRequest, "INVALID_REQUEST", err.Error())
return
}
if err := h.svc.IngestToLibrary(roleOf(c), req.MACode, req.CTID, req.MediaAssetID, req.LibName); err != nil {
if err := h.svc.IngestToLibrary(roleOf(c), req.CCCode, req.CTID, req.MediaAssetID, req.LibName); err != nil {
httpx.Error(c, http.StatusBadRequest, "INGEST_FAILED", err.Error())
return
}
httpx.OK(c, gin.H{"ma_code": req.MACode, "status": "in_library"})
httpx.OK(c, gin.H{"ma_code": req.CCCode, "status": "in_library"})
}
type publishReq struct {
MACode string `json:"ma_code"`
CCCode string `json:"ma_code"`
Certificate string `json:"certificate"`
}
@@ -240,16 +240,16 @@ func (h *Handler) publish(c *gin.Context) {
httpx.Error(c, http.StatusBadRequest, "INVALID_REQUEST", err.Error())
return
}
if err := h.svc.PublishToOperator(service.PublishRequest{MACode: req.MACode, Certificate: req.Certificate}); err != nil {
if err := h.svc.PublishToOperator(service.PublishRequest{CCCode: req.CCCode, Certificate: req.Certificate}); err != nil {
httpx.Error(c, http.StatusBadRequest, "PUBLISH_FAILED", err.Error())
return
}
httpx.OK(c, gin.H{"ma_code": req.MACode, "status": "published"})
httpx.OK(c, gin.H{"ma_code": req.CCCode, "status": "published"})
}
type injectReq struct {
CTID string `json:"content_twin_id"`
MACode string `json:"ma_code"`
CCCode string `json:"ma_code"`
FileHash string `json:"file_sha256"`
OperatorID string `json:"operator_id"`
CDNEndpoint string `json:"cdn_endpoint"`
@@ -261,7 +261,7 @@ func (h *Handler) inject(c *gin.Context) {
httpx.Error(c, http.StatusBadRequest, "INVALID_REQUEST", err.Error())
return
}
res, err := h.svc.InjectToCDN(roleOf(c), req.CTID, req.MACode, req.FileHash, req.OperatorID, req.CDNEndpoint)
res, err := h.svc.InjectToCDN(roleOf(c), req.CTID, req.CCCode, req.FileHash, req.OperatorID, req.CDNEndpoint)
if err != nil {
httpx.Error(c, http.StatusBadRequest, "INJECT_REJECTED", err.Error())
return
@@ -293,7 +293,7 @@ func (h *Handler) versionChange(c *gin.Context) {
}
type takedownReq struct {
MACode string `json:"ma_code"`
CCCode string `json:"ma_code"`
Reason string `json:"reason"`
}
@@ -303,7 +303,7 @@ func (h *Handler) takedown(c *gin.Context) {
httpx.Error(c, http.StatusBadRequest, "INVALID_REQUEST", err.Error())
return
}
res, err := h.svc.Takedown(roleOf(c), req.MACode, req.Reason)
res, err := h.svc.Takedown(roleOf(c), req.CCCode, req.Reason)
if err != nil {
httpx.Error(c, http.StatusForbidden, "TAKEDOWN_FAILED", err.Error())
return
@@ -312,7 +312,7 @@ func (h *Handler) takedown(c *gin.Context) {
}
type takedownEpisodeReq struct {
MACode string `json:"ma_code"`
CCCode string `json:"ma_code"`
Episode int `json:"episode"`
Reason string `json:"reason"`
}
@@ -323,11 +323,11 @@ func (h *Handler) takedownEpisode(c *gin.Context) {
httpx.Error(c, http.StatusBadRequest, "INVALID_REQUEST", err.Error())
return
}
if err := h.svc.TakedownEpisode(roleOf(c), req.MACode, req.Episode, req.Reason); err != nil {
if err := h.svc.TakedownEpisode(roleOf(c), req.CCCode, req.Episode, req.Reason); err != nil {
httpx.Error(c, http.StatusForbidden, "TAKEDOWN_EPISODE_FAILED", err.Error())
return
}
httpx.OK(c, gin.H{"ma_code": req.MACode, "episode": req.Episode, "revoked": true})
httpx.OK(c, gin.H{"ma_code": req.CCCode, "episode": req.Episode, "revoked": true})
}
func (h *Handler) restore(c *gin.Context) {
@@ -336,11 +336,11 @@ func (h *Handler) restore(c *gin.Context) {
httpx.Error(c, http.StatusBadRequest, "INVALID_REQUEST", err.Error())
return
}
if err := h.svc.Restore(roleOf(c), req.MACode); err != nil {
if err := h.svc.Restore(roleOf(c), req.CCCode); err != nil {
httpx.Error(c, http.StatusForbidden, "RESTORE_FAILED", err.Error())
return
}
httpx.OK(c, gin.H{"ma_code": req.MACode, "status": "published"})
httpx.OK(c, gin.H{"ma_code": req.CCCode, "status": "published"})
}
func (h *Handler) restoreEpisode(c *gin.Context) {
@@ -349,20 +349,20 @@ func (h *Handler) restoreEpisode(c *gin.Context) {
httpx.Error(c, http.StatusBadRequest, "INVALID_REQUEST", err.Error())
return
}
if err := h.svc.RestoreEpisode(roleOf(c), req.MACode, req.Episode); err != nil {
if err := h.svc.RestoreEpisode(roleOf(c), req.CCCode, req.Episode); err != nil {
httpx.Error(c, http.StatusForbidden, "RESTORE_EPISODE_FAILED", err.Error())
return
}
httpx.OK(c, gin.H{"ma_code": req.MACode, "episode": req.Episode, "revoked": false})
httpx.OK(c, gin.H{"ma_code": req.CCCode, "episode": req.Episode, "revoked": false})
}
func (h *Handler) mappings(c *gin.Context) {
maCode := c.Query("ma_code")
if maCode == "" {
ccCode := c.Query("ma_code")
if ccCode == "" {
httpx.Error(c, http.StatusBadRequest, "INVALID_REQUEST", "缺少 ma_code")
return
}
res, err := h.svc.QueryMappings(maCode)
res, err := h.svc.QueryMappings(ccCode)
if err != nil {
httpx.Error(c, http.StatusNotFound, "NOT_FOUND", err.Error())
return
@@ -371,7 +371,7 @@ func (h *Handler) mappings(c *gin.Context) {
}
type verifyEpisodeReq struct {
MACode string `json:"ma_code"`
CCCode string `json:"ma_code"`
Episode int `json:"episode"`
FileHash string `json:"file_sha256"`
}
@@ -382,7 +382,7 @@ func (h *Handler) verifyEpisode(c *gin.Context) {
httpx.Error(c, http.StatusBadRequest, "INVALID_REQUEST", err.Error())
return
}
res, err := h.svc.VerifyEpisode(req.MACode, req.Episode, req.FileHash)
res, err := h.svc.VerifyEpisode(req.CCCode, req.Episode, req.FileHash)
if err != nil {
httpx.Error(c, http.StatusBadRequest, "VERIFY_MISMATCH", err.Error())
return
@@ -391,17 +391,17 @@ func (h *Handler) verifyEpisode(c *gin.Context) {
}
func (h *Handler) listEpisodes(c *gin.Context) {
maCode := c.Query("ma_code")
if maCode == "" {
ccCode := c.Query("ma_code")
if ccCode == "" {
httpx.Error(c, http.StatusBadRequest, "INVALID_REQUEST", "缺少 ma_code")
return
}
eps, err := h.svc.ListEpisodes(maCode)
eps, err := h.svc.ListEpisodes(ccCode)
if err != nil {
httpx.Error(c, http.StatusNotFound, "NOT_FOUND", err.Error())
return
}
httpx.OK(c, gin.H{"ma_code": maCode, "episodes": eps, "count": len(eps)})
httpx.OK(c, gin.H{"ma_code": ccCode, "episodes": eps, "count": len(eps)})
}
func (h *Handler) listReviews(c *gin.Context) {
@@ -422,7 +422,7 @@ func (h *Handler) listContents(c *gin.Context) {
type playbackReq struct {
PlatformID string `json:"platform_id"`
Batch []struct {
MACode string `json:"ma_code"`
CCCode string `json:"ma_code"`
Episode int `json:"episode"`
UserHash string `json:"user_hash"`
EventType string `json:"event_type"`
@@ -440,7 +440,7 @@ func (h *Handler) reportPlayback(c *gin.Context) {
events := make([]model.PlaybackEvent, 0, len(req.Batch))
for _, b := range req.Batch {
events = append(events, model.PlaybackEvent{
MACode: b.MACode, Episode: b.Episode, PlatformID: req.PlatformID,
CCCode: b.CCCode, Episode: b.Episode, PlatformID: req.PlatformID,
UserHash: b.UserHash, EventType: model.PlaybackEventType(b.EventType),
DurationSec: b.DurationSec, RevenueCent: b.RevenueCent, EventTime: time.Now(),
})
@@ -450,16 +450,16 @@ func (h *Handler) reportPlayback(c *gin.Context) {
}
func (h *Handler) playbackSummary(c *gin.Context) {
maCode := c.Query("ma_code")
if maCode == "" {
ccCode := c.Query("ma_code")
if ccCode == "" {
httpx.Error(c, http.StatusBadRequest, "INVALID_REQUEST", "缺少 ma_code")
return
}
httpx.OK(c, h.svc.PlaybackSummary(maCode))
httpx.OK(c, h.svc.PlaybackSummary(ccCode))
}
type settlementReq struct {
MACode string `json:"ma_code"`
CCCode string `json:"ma_code"`
Period string `json:"period"`
}
@@ -469,7 +469,7 @@ func (h *Handler) computeSettlement(c *gin.Context) {
httpx.Error(c, http.StatusBadRequest, "INVALID_REQUEST", err.Error())
return
}
st, err := h.svc.ComputeSettlement(req.MACode, req.Period)
st, err := h.svc.ComputeSettlement(req.CCCode, req.Period)
if err != nil {
httpx.Error(c, http.StatusBadRequest, "SETTLEMENT_FAILED", err.Error())
return
@@ -480,30 +480,30 @@ func (h *Handler) computeSettlement(c *gin.Context) {
// ---- 二期:追责取证与确权举证(需求22/23) ----
func (h *Handler) provenance(c *gin.Context) {
maCode := c.Query("ma_code")
if maCode == "" {
ccCode := c.Query("ma_code")
if ccCode == "" {
httpx.Error(c, http.StatusBadRequest, "INVALID_REQUEST", "缺少 ma_code")
return
}
httpx.OK(c, gin.H{"ma_code": maCode, "trail": h.svc.Provenance(maCode)})
httpx.OK(c, gin.H{"ma_code": ccCode, "trail": h.svc.Provenance(ccCode)})
}
func (h *Handler) accountability(c *gin.Context) {
maCode := c.Query("ma_code")
if maCode == "" {
ccCode := c.Query("ma_code")
if ccCode == "" {
httpx.Error(c, http.StatusBadRequest, "INVALID_REQUEST", "缺少 ma_code")
return
}
httpx.OK(c, h.svc.Accountability(maCode))
httpx.OK(c, h.svc.Accountability(ccCode))
}
func (h *Handler) evidence(c *gin.Context) {
maCode := c.Query("ma_code")
if maCode == "" {
ccCode := c.Query("ma_code")
if ccCode == "" {
httpx.Error(c, http.StatusBadRequest, "INVALID_REQUEST", "缺少 ma_code")
return
}
ev, err := h.svc.CopyrightEvidence(maCode)
ev, err := h.svc.CopyrightEvidence(ccCode)
if err != nil {
httpx.Error(c, http.StatusNotFound, "NOT_FOUND", err.Error())
return
@@ -540,7 +540,7 @@ func (h *Handler) infringeMatch(c *gin.Context) {
// ---- 二期:授权链/追更/跨省/终端抽检(需求25/24/13/8 ----
type authorizeReq struct {
MACode string `json:"ma_code"`
CCCode string `json:"ma_code"`
Regions []string `json:"regions"`
Platforms []string `json:"platforms"`
ExpiryAt string `json:"expiry_at"` // RFC3339,空=长期
@@ -556,15 +556,15 @@ func (h *Handler) authorize(c *gin.Context) {
if req.ExpiryAt != "" {
expiry, _ = time.Parse(time.RFC3339, req.ExpiryAt)
}
if err := h.svc.RecordAuthorization(req.MACode, req.Regions, req.Platforms, expiry); err != nil {
if err := h.svc.RecordAuthorization(req.CCCode, req.Regions, req.Platforms, expiry); err != nil {
httpx.Error(c, http.StatusBadRequest, "AUTHORIZE_FAILED", err.Error())
return
}
httpx.OK(c, gin.H{"ma_code": req.MACode, "authorized": true})
httpx.OK(c, gin.H{"ma_code": req.CCCode, "authorized": true})
}
type authCheckReq struct {
MACode string `json:"ma_code"`
CCCode string `json:"ma_code"`
Region string `json:"region"`
Platform string `json:"platform"`
}
@@ -575,11 +575,11 @@ func (h *Handler) authCheck(c *gin.Context) {
httpx.Error(c, http.StatusBadRequest, "INVALID_REQUEST", err.Error())
return
}
httpx.OK(c, h.svc.CheckAuthorization(req.MACode, req.Region, req.Platform))
httpx.OK(c, h.svc.CheckAuthorization(req.CCCode, req.Region, req.Platform))
}
type addEpisodesReq struct {
MACode string `json:"ma_code"`
CCCode string `json:"ma_code"`
Episodes []struct {
Episode int `json:"episode"`
FileSHA256 string `json:"file_sha256"`
@@ -597,15 +597,15 @@ func (h *Handler) addEpisodes(c *gin.Context) {
for _, e := range req.Episodes {
eps = append(eps, model.EpisodeHash{Episode: e.Episode, FileSHA256: e.FileSHA256, MerkleRoot: e.MerkleRoot})
}
if err := h.svc.AddEpisodes(roleOf(c), req.MACode, eps); err != nil {
if err := h.svc.AddEpisodes(roleOf(c), req.CCCode, eps); err != nil {
httpx.Error(c, http.StatusBadRequest, "ADD_EPISODES_FAILED", err.Error())
return
}
httpx.OK(c, gin.H{"ma_code": req.MACode, "added": len(eps)})
httpx.OK(c, gin.H{"ma_code": req.CCCode, "added": len(eps)})
}
type crossProvinceReq struct {
MACode string `json:"ma_code"`
CCCode string `json:"ma_code"`
FileHash string `json:"file_sha256"`
Province string `json:"province"`
}
@@ -616,11 +616,11 @@ func (h *Handler) crossProvince(c *gin.Context) {
httpx.Error(c, http.StatusBadRequest, "INVALID_REQUEST", err.Error())
return
}
httpx.OK(c, h.svc.CrossProvinceAdmit(req.MACode, req.FileHash, req.Province))
httpx.OK(c, h.svc.CrossProvinceAdmit(req.CCCode, req.FileHash, req.Province))
}
type terminalVerifyReq struct {
MACode string `json:"ma_code"`
CCCode string `json:"ma_code"`
Episode int `json:"episode"`
SegHash string `json:"segment_hash"`
}
@@ -631,14 +631,14 @@ func (h *Handler) terminalVerify(c *gin.Context) {
httpx.Error(c, http.StatusBadRequest, "INVALID_REQUEST", err.Error())
return
}
ok, msg := h.svc.TerminalVerifySegment(req.MACode, req.Episode, req.SegHash)
ok, msg := h.svc.TerminalVerifySegment(req.CCCode, req.Episode, req.SegHash)
httpx.OK(c, gin.H{"ok": ok, "message": msg})
}
// ---- 三期:备案对接/全国统计/监管上报/号段管理 ----
type bindFilingReq struct {
MACode string `json:"ma_code"`
CCCode string `json:"ma_code"`
LicenseNo string `json:"license_no"`
FilingNo string `json:"filing_no"`
}
@@ -649,7 +649,7 @@ func (h *Handler) bindFiling(c *gin.Context) {
httpx.Error(c, http.StatusBadRequest, "INVALID_REQUEST", err.Error())
return
}
rec, err := h.svc.BindFiling(req.MACode, req.LicenseNo, req.FilingNo)
rec, err := h.svc.BindFiling(req.CCCode, req.LicenseNo, req.FilingNo)
if err != nil {
httpx.Error(c, http.StatusBadRequest, "BIND_FILING_FAILED", err.Error())
return
@@ -658,8 +658,8 @@ func (h *Handler) bindFiling(c *gin.Context) {
}
func (h *Handler) queryFiling(c *gin.Context) {
maCode := c.Query("ma_code")
rec, ok := h.svc.QueryFiling(maCode)
ccCode := c.Query("ma_code")
rec, ok := h.svc.QueryFiling(ccCode)
if !ok {
httpx.Error(c, http.StatusNotFound, "NOT_FOUND", "未关联备案")
return
@@ -696,16 +696,16 @@ func (h *Handler) listSegments(c *gin.Context) {
// ---- 四期:大小屏融合(跨域解析/扫码验真/跨屏权益)----
func (h *Handler) resolve(c *gin.Context) {
maCode := c.Query("ma_code")
if maCode == "" {
ccCode := c.Query("ma_code")
if ccCode == "" {
httpx.Error(c, http.StatusBadRequest, "INVALID_REQUEST", "缺少 ma_code")
return
}
httpx.OK(c, h.svc.Resolve(maCode))
httpx.OK(c, h.svc.Resolve(ccCode))
}
type scanVerifyReq struct {
MACode string `json:"ma_code"`
CCCode string `json:"ma_code"`
}
func (h *Handler) scanVerify(c *gin.Context) {
@@ -714,11 +714,11 @@ func (h *Handler) scanVerify(c *gin.Context) {
httpx.Error(c, http.StatusBadRequest, "INVALID_REQUEST", err.Error())
return
}
httpx.OK(c, h.svc.ScanVerify(req.MACode))
httpx.OK(c, h.svc.ScanVerify(req.CCCode))
}
type purchaseReq struct {
MACode string `json:"ma_code"`
CCCode string `json:"ma_code"`
UserHash string `json:"user_hash"`
Screen string `json:"screen"` // iptv/ott/app
}
@@ -729,7 +729,7 @@ func (h *Handler) recordPurchase(c *gin.Context) {
httpx.Error(c, http.StatusBadRequest, "INVALID_REQUEST", err.Error())
return
}
rec, err := h.svc.RecordPurchase(req.MACode, req.UserHash, model.ScreenType(req.Screen))
rec, err := h.svc.RecordPurchase(req.CCCode, req.UserHash, model.ScreenType(req.Screen))
if err != nil {
httpx.Error(c, http.StatusBadRequest, "PURCHASE_FAILED", err.Error())
return
@@ -738,7 +738,7 @@ func (h *Handler) recordPurchase(c *gin.Context) {
}
type verifyRightsReq struct {
MACode string `json:"ma_code"`
CCCode string `json:"ma_code"`
UserHash string `json:"user_hash"`
Screen string `json:"screen"` // 当前请求屏
}
@@ -749,7 +749,7 @@ func (h *Handler) verifyRights(c *gin.Context) {
httpx.Error(c, http.StatusBadRequest, "INVALID_REQUEST", err.Error())
return
}
httpx.OK(c, h.svc.VerifyCrossScreenRights(req.MACode, req.UserHash, model.ScreenType(req.Screen)))
httpx.OK(c, h.svc.VerifyCrossScreenRights(req.CCCode, req.UserHash, model.ScreenType(req.Screen)))
}
// ---- 多维度标识查询 handlers ----
@@ -808,7 +808,7 @@ func (h *Handler) mergeMA(c *gin.Context) {
httpx.Error(c, http.StatusBadRequest, "INVALID_REQUEST", err.Error())
return
}
res, err := h.svc.MergeMACodes(roleOf(c), req)
res, err := h.svc.MergeCCCodes(roleOf(c), req)
if err != nil {
httpx.Error(c, http.StatusBadRequest, "MERGE_FAILED", err.Error())
return
@@ -823,7 +823,7 @@ func (h *Handler) splitMA(c *gin.Context) {
httpx.Error(c, http.StatusBadRequest, "INVALID_REQUEST", err.Error())
return
}
res, err := h.svc.SplitMACode(roleOf(c), req)
res, err := h.svc.SplitCCCode(roleOf(c), req)
if err != nil {
httpx.Error(c, http.StatusBadRequest, "SPLIT_FAILED", err.Error())
return
+20 -20
View File
@@ -14,7 +14,7 @@ import (
"github.com/tcs-iptv/tcs/internal/chain"
"github.com/tcs-iptv/tcs/internal/hash"
"github.com/tcs-iptv/tcs/internal/httpx"
"github.com/tcs-iptv/tcs/internal/macode"
"github.com/tcs-iptv/tcs/internal/cccode"
"github.com/tcs-iptv/tcs/internal/service"
)
@@ -24,10 +24,10 @@ func testServer(t *testing.T) (*httptest.Server, *httpx.MemoryKeyStore) {
gin.SetMode(gin.TestMode)
ch := chain.NewMemoryChain()
gen := macode.NewGenerator(macode.NewMemoryStore())
require.NoError(t, gen.RegisterSegment(macode.Segment{
gen := cccode.NewGenerator(cccode.NewMemoryStore())
require.NoError(t, gen.RegisterSegment(cccode.Segment{
IndustryNode: "8531", OrgNode: "4401",
Category: macode.CategoryMicroDrama, Start: 1, End: 9999999, SeqWidth: 7,
Category: cccode.CategoryMicroDrama, Start: 1, End: 9999999, SeqWidth: 7,
}))
svc := service.New(ch, gen)
h := NewHandler(svc)
@@ -115,39 +115,39 @@ func TestE2E_FullLifecycle(t *testing.T) {
"review_id": reviewID, "issuer": "北京市广播电视局",
})
require.Equal(t, http.StatusOK, st)
maCode := dataOf(resp)["ma_code"].(string)
ccCode := dataOf(resp)["ma_code"].(string)
cert := dataOf(resp)["certificate"].(string)
assert.True(t, macode.IsValid(maCode))
assert.True(t, cccode.IsValid(ccCode))
// 4) 入媒资库
st, _ = signedCall(t, b, "ak-reviewer", "sk-reviewer", "POST", "/content/ingest", map[string]any{
"ma_code": maCode, "content_twin_id": ctid, "media_asset_id": "MEDIA-001", "lib_name": "广东IPTV媒资库",
"ma_code": ccCode, "content_twin_id": ctid, "media_asset_id": "MEDIA-001", "lib_name": "广东IPTV媒资库",
})
require.Equal(t, http.StatusOK, st)
// 5) 发布
st, _ = signedCall(t, b, "ak-reviewer", "sk-reviewer", "POST", "/content/publish", map[string]any{
"ma_code": maCode, "certificate": cert,
"ma_code": ccCode, "certificate": cert,
})
require.Equal(t, http.StatusOK, st)
// 6) CDN 注入(匹配)
st, resp = signedCall(t, b, "ak-operator", "sk-operator", "POST", "/content/inject", map[string]any{
"content_twin_id": ctid, "ma_code": maCode, "file_sha256": "fh-e2e",
"content_twin_id": ctid, "ma_code": ccCode, "file_sha256": "fh-e2e",
"operator_id": "CT-IPTV-GD", "cdn_endpoint": "cdn://ct-gd/vod/1",
})
require.Equal(t, http.StatusOK, st)
assert.Equal(t, true, dataOf(resp)["allowed"])
// 7) 映射查询
st, resp = signedCall(t, b, "ak-regulator", "sk-regulator", "GET", "/content/mappings?ma_code="+maCode, nil)
st, resp = signedCall(t, b, "ak-regulator", "sk-regulator", "GET", "/content/mappings?ma_code="+ccCode, nil)
require.Equal(t, http.StatusOK, st)
mappings := dataOf(resp)["mappings"].([]any)
assert.Len(t, mappings, 3) // cp + reviewer + operator
// 8) 监管下架
st, resp = signedCall(t, b, "ak-regulator", "sk-regulator", "POST", "/content/takedown", map[string]any{
"ma_code": maCode, "reason": "违规",
"ma_code": ccCode, "reason": "违规",
})
require.Equal(t, http.StatusOK, st)
assert.NotEmpty(t, dataOf(resp)["cdn_endpoints"])
@@ -171,14 +171,14 @@ func TestE2E_TamperRejected(t *testing.T) {
_, resp = signedCall(t, b, "ak-regulator", "sk-regulator", "POST", "/content/issue", map[string]any{
"review_id": reviewID, "issuer": "x",
})
maCode := dataOf(resp)["ma_code"].(string)
ccCode := dataOf(resp)["ma_code"].(string)
cert := dataOf(resp)["certificate"].(string)
signedCall(t, b, "ak-reviewer", "sk-reviewer", "POST", "/content/ingest", map[string]any{"ma_code": maCode, "content_twin_id": ctid, "media_asset_id": "M", "lib_name": "L"})
signedCall(t, b, "ak-reviewer", "sk-reviewer", "POST", "/content/publish", map[string]any{"ma_code": maCode, "certificate": cert})
signedCall(t, b, "ak-reviewer", "sk-reviewer", "POST", "/content/ingest", map[string]any{"ma_code": ccCode, "content_twin_id": ctid, "media_asset_id": "M", "lib_name": "L"})
signedCall(t, b, "ak-reviewer", "sk-reviewer", "POST", "/content/publish", map[string]any{"ma_code": ccCode, "certificate": cert})
// 篡改文件注入 → 拒绝
st, _ = signedCall(t, b, "ak-operator", "sk-operator", "POST", "/content/inject", map[string]any{
"content_twin_id": ctid, "ma_code": maCode, "file_sha256": "fh-TAMPERED",
"content_twin_id": ctid, "ma_code": ccCode, "file_sha256": "fh-TAMPERED",
"operator_id": "OP", "cdn_endpoint": "cdn://x",
})
assert.Equal(t, http.StatusBadRequest, st, "篡改注入必须被拒")
@@ -235,14 +235,14 @@ func TestE2E_PermissionMatrix(t *testing.T) {
// 正常签发
_, resp = signedCall(t, b, "ak-regulator", "sk-regulator", "POST", "/content/issue", map[string]any{"review_id": reviewID, "issuer": "x"})
maCode := dataOf(resp)["ma_code"].(string)
ccCode := dataOf(resp)["ma_code"].(string)
// 运营商越权下架 → 403
st, _ = signedCall(t, b, "ak-operator", "sk-operator", "POST", "/content/takedown", map[string]any{"ma_code": maCode, "reason": "越权"})
st, _ = signedCall(t, b, "ak-operator", "sk-operator", "POST", "/content/takedown", map[string]any{"ma_code": ccCode, "reason": "越权"})
assert.Equal(t, http.StatusForbidden, st, "运营商不得发起下架")
// 无效签名 → 401
req, _ := http.NewRequest("GET", b+"/api/v1/content/mappings?ma_code="+maCode, nil)
req, _ := http.NewRequest("GET", b+"/api/v1/content/mappings?ma_code="+ccCode, nil)
req.Header.Set("Authorization", "TCS ak-regulator:badsig")
r, _ := http.DefaultClient.Do(req)
assert.Equal(t, http.StatusUnauthorized, r.StatusCode, "错误签名应 401")
@@ -261,10 +261,10 @@ func TestE2E_TakedownLatency(t *testing.T) {
reviewID := dataOf(resp)["review_id"].(string)
signedCall(t, b, "ak-reviewer", "sk-reviewer", "POST", "/content/csps-result", map[string]any{"review_id": reviewID, "approved": true})
_, resp = signedCall(t, b, "ak-regulator", "sk-regulator", "POST", "/content/issue", map[string]any{"review_id": reviewID, "issuer": "x"})
maCode := dataOf(resp)["ma_code"].(string)
ccCode := dataOf(resp)["ma_code"].(string)
start := time.Now()
st, _ := signedCall(t, b, "ak-regulator", "sk-regulator", "POST", "/content/takedown", map[string]any{"ma_code": maCode, "reason": "违规"})
st, _ := signedCall(t, b, "ak-regulator", "sk-regulator", "POST", "/content/takedown", map[string]any{"ma_code": ccCode, "reason": "违规"})
elapsed := time.Since(start)
require.Equal(t, http.StatusOK, st)
assert.Less(t, elapsed, time.Second, "下架端到端应在秒级内(目标分钟级)")