feat(phase2): 追更/授权链/跨省复用/终端抽检/CI(F21/F22/F13/F08/K)

- F21 追更: AddEpisodes 追加新集不重新发码; Merkle定位变更集
- F22 授权链: RecordAuthorization + CheckAuthorization(地域/平台/期限), 嵌入注入前核验
- F13 跨省复用: CrossProvinceAdmit 三重校验(MA有效+哈希一致+非黑名单)快速准入
- F08 终端抽检: TerminalVerifySegment 片段校验+断流提示
- K.1 CI: .gitlab-ci.yml(后端构建/测试/前端构建)
- 新增6个API; 16项测试通过; 二期纯代码功能全部完成
- A(真实链)/B(BFF)延后至有环境/三期, MemoryChain接口已就绪可平滑替换
This commit is contained in:
selfrelease
2026-06-14 17:24:56 +08:00
parent dc3095a2d5
commit 468c3b5daa
7 changed files with 529 additions and 81 deletions
+103
View File
@@ -50,6 +50,11 @@ func (h *Handler) Register(rg *gin.RouterGroup) {
rg.GET("/content/accountability", h.accountability) // 责任界定取证(需求22
rg.GET("/content/evidence", h.evidence) // 版权确权证据链(需求23
rg.POST("/content/infringe-match", h.infringeMatch) // 感知哈希侵权比对(需求23
rg.POST("/content/authorize", h.authorize) // 登记授权(需求25
rg.POST("/content/auth-check", h.authCheck) // 授权核验(需求25
rg.POST("/content/add-episodes", h.addEpisodes) // 追更新集(需求24
rg.POST("/content/cross-province", h.crossProvince) // 跨省复用准入(需求13
rg.POST("/terminal/verify-segment", h.terminalVerify) // 终端片段抽检(需求8
}
func roleOf(c *gin.Context) chain.Role {
@@ -514,3 +519,101 @@ func (h *Handler) infringeMatch(c *gin.Context) {
}
httpx.OK(c, gin.H{"matches": matches, "count": len(matches)})
}
// ---- 二期:授权链/追更/跨省/终端抽检(需求25/24/13/8 ----
type authorizeReq struct {
MACode string `json:"ma_code"`
Regions []string `json:"regions"`
Platforms []string `json:"platforms"`
ExpiryAt string `json:"expiry_at"` // RFC3339,空=长期
}
func (h *Handler) authorize(c *gin.Context) {
var req authorizeReq
if err := c.ShouldBindJSON(&req); err != nil {
httpx.Error(c, http.StatusBadRequest, "INVALID_REQUEST", err.Error())
return
}
var expiry time.Time
if req.ExpiryAt != "" {
expiry, _ = time.Parse(time.RFC3339, req.ExpiryAt)
}
if err := h.svc.RecordAuthorization(req.MACode, 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})
}
type authCheckReq struct {
MACode string `json:"ma_code"`
Region string `json:"region"`
Platform string `json:"platform"`
}
func (h *Handler) authCheck(c *gin.Context) {
var req authCheckReq
if err := c.ShouldBindJSON(&req); err != nil {
httpx.Error(c, http.StatusBadRequest, "INVALID_REQUEST", err.Error())
return
}
httpx.OK(c, h.svc.CheckAuthorization(req.MACode, req.Region, req.Platform))
}
type addEpisodesReq struct {
MACode string `json:"ma_code"`
Episodes []struct {
Episode int `json:"episode"`
FileSHA256 string `json:"file_sha256"`
MerkleRoot string `json:"merkle_root"`
} `json:"episodes"`
}
func (h *Handler) addEpisodes(c *gin.Context) {
var req addEpisodesReq
if err := c.ShouldBindJSON(&req); err != nil {
httpx.Error(c, http.StatusBadRequest, "INVALID_REQUEST", err.Error())
return
}
eps := make([]model.EpisodeHash, 0, len(req.Episodes))
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 {
httpx.Error(c, http.StatusBadRequest, "ADD_EPISODES_FAILED", err.Error())
return
}
httpx.OK(c, gin.H{"ma_code": req.MACode, "added": len(eps)})
}
type crossProvinceReq struct {
MACode string `json:"ma_code"`
FileHash string `json:"file_sha256"`
Province string `json:"province"`
}
func (h *Handler) crossProvince(c *gin.Context) {
var req crossProvinceReq
if err := c.ShouldBindJSON(&req); err != nil {
httpx.Error(c, http.StatusBadRequest, "INVALID_REQUEST", err.Error())
return
}
httpx.OK(c, h.svc.CrossProvinceAdmit(req.MACode, req.FileHash, req.Province))
}
type terminalVerifyReq struct {
MACode string `json:"ma_code"`
Episode int `json:"episode"`
SegHash string `json:"segment_hash"`
}
func (h *Handler) terminalVerify(c *gin.Context) {
var req terminalVerifyReq
if err := c.ShouldBindJSON(&req); err != nil {
httpx.Error(c, http.StatusBadRequest, "INVALID_REQUEST", err.Error())
return
}
ok, msg := h.svc.TerminalVerifySegment(req.MACode, req.Episode, req.SegHash)
httpx.OK(c, gin.H{"ok": ok, "message": msg})
}