feat(phase2): 追责取证与确权举证(F19/F20)

- internal/provenance: 全链路存证(送审/审核/发码/转码/入库/注入)+责任界定
- service: Provenance/Accountability(定位首次哈希变化节点)/CopyrightEvidence/MatchInfringement
- api: /content/provenance, /accountability, /evidence, /infringe-match
- 转码版哈希不误判为篡改; 感知哈希侵权比对分级(high/medium)
- 11项新测试通过; 端到端: 审播一致判定+证据链+侵权命中
This commit is contained in:
selfrelease
2026-06-14 17:13:58 +08:00
parent f44c53c5bb
commit dc3095a2d5
7 changed files with 405 additions and 15 deletions
+64
View File
@@ -46,6 +46,10 @@ func (h *Handler) Register(rg *gin.RouterGroup) {
rg.POST("/data/playback", h.reportPlayback) // 播放数据回传(需求9
rg.GET("/data/playback-summary", h.playbackSummary) // 按MA码聚合可信播放数据(需求9/21)
rg.POST("/settlement/compute", h.computeSettlement) // 基于可信播放数据分账(需求21
rg.GET("/content/provenance", h.provenance) // 全链路存证(需求22
rg.GET("/content/accountability", h.accountability) // 责任界定取证(需求22
rg.GET("/content/evidence", h.evidence) // 版权确权证据链(需求23
rg.POST("/content/infringe-match", h.infringeMatch) // 感知哈希侵权比对(需求23
}
func roleOf(c *gin.Context) chain.Role {
@@ -450,3 +454,63 @@ func (h *Handler) computeSettlement(c *gin.Context) {
}
httpx.OK(c, st)
}
// ---- 二期:追责取证与确权举证(需求22/23) ----
func (h *Handler) provenance(c *gin.Context) {
maCode := c.Query("ma_code")
if maCode == "" {
httpx.Error(c, http.StatusBadRequest, "INVALID_REQUEST", "缺少 ma_code")
return
}
httpx.OK(c, gin.H{"ma_code": maCode, "trail": h.svc.Provenance(maCode)})
}
func (h *Handler) accountability(c *gin.Context) {
maCode := c.Query("ma_code")
if maCode == "" {
httpx.Error(c, http.StatusBadRequest, "INVALID_REQUEST", "缺少 ma_code")
return
}
httpx.OK(c, h.svc.Accountability(maCode))
}
func (h *Handler) evidence(c *gin.Context) {
maCode := c.Query("ma_code")
if maCode == "" {
httpx.Error(c, http.StatusBadRequest, "INVALID_REQUEST", "缺少 ma_code")
return
}
ev, err := h.svc.CopyrightEvidence(maCode)
if err != nil {
httpx.Error(c, http.StatusNotFound, "NOT_FOUND", err.Error())
return
}
httpx.OK(c, ev)
}
type infringeReq struct {
Perceptual string `json:"perceptual_hash"`
High int `json:"high_threshold"`
Medium int `json:"medium_threshold"`
}
func (h *Handler) infringeMatch(c *gin.Context) {
var req infringeReq
if err := c.ShouldBindJSON(&req); err != nil {
httpx.Error(c, http.StatusBadRequest, "INVALID_REQUEST", err.Error())
return
}
if req.High == 0 {
req.High = 5
}
if req.Medium == 0 {
req.Medium = 10
}
matches, err := h.svc.MatchInfringement(req.Perceptual, req.High, req.Medium)
if err != nil {
httpx.Error(c, http.StatusBadRequest, "MATCH_FAILED", err.Error())
return
}
httpx.OK(c, gin.H{"matches": matches, "count": len(matches)})
}