init: AIGC-Hub/AVCC 方案文档 + TCS-IPTV 内容可信锁定系统 MVP
- 方案文档: AVCC 体系建设、IPTV TCS 需求(0-req)/PRD(1-prd)/任务(2-task)/二三四期任务 - tcs-iptv: Go 后端(哈希SDK/MA码生成/可信数据空间mock/业务编排/HTTP API+HMAC鉴权) - web-console: React+AntD 监管大屏(角色工作台/全流程演示/监管片库) - 一剧一码+集级哈希, 集级下架/恢复, 全栈测试通过
This commit is contained in:
@@ -0,0 +1,103 @@
|
||||
package httpx
|
||||
|
||||
import (
|
||||
"crypto/hmac"
|
||||
"crypto/sha256"
|
||||
"encoding/base64"
|
||||
"fmt"
|
||||
"strings"
|
||||
|
||||
"github.com/gin-gonic/gin"
|
||||
)
|
||||
|
||||
// 鉴权采用 API Key + HMAC-SHA256(需求17-AC4、需求20)。
|
||||
// 请求头:
|
||||
// Authorization: TCS {apiKey}:{signature}
|
||||
// X-TCS-Role: regulator | reviewer | cp | operator
|
||||
// signature = base64(HMAC-SHA256(apiSecret, method+"\n"+path))
|
||||
|
||||
// KeyStore 提供 apiKey -> (apiSecret, role) 的查询。MVP 用内存实现。
|
||||
type KeyStore interface {
|
||||
Lookup(apiKey string) (secret string, role string, ok bool)
|
||||
}
|
||||
|
||||
// MemoryKeyStore 内存密钥库。
|
||||
type MemoryKeyStore struct {
|
||||
keys map[string]struct {
|
||||
secret string
|
||||
role string
|
||||
}
|
||||
}
|
||||
|
||||
// NewMemoryKeyStore 创建并预置密钥。
|
||||
func NewMemoryKeyStore() *MemoryKeyStore {
|
||||
return &MemoryKeyStore{keys: map[string]struct {
|
||||
secret string
|
||||
role string
|
||||
}{}}
|
||||
}
|
||||
|
||||
// Add 注册一个密钥及其角色。
|
||||
func (m *MemoryKeyStore) Add(apiKey, secret, role string) {
|
||||
m.keys[apiKey] = struct {
|
||||
secret string
|
||||
role string
|
||||
}{secret, role}
|
||||
}
|
||||
|
||||
// Lookup 查询密钥。
|
||||
func (m *MemoryKeyStore) Lookup(apiKey string) (string, string, bool) {
|
||||
v, ok := m.keys[apiKey]
|
||||
return v.secret, v.role, ok
|
||||
}
|
||||
|
||||
// Sign 计算签名(供客户端/测试使用)。
|
||||
func Sign(secret, method, path string) string {
|
||||
mac := hmac.New(sha256.New, []byte(secret))
|
||||
mac.Write([]byte(method + "\n" + path))
|
||||
return base64.StdEncoding.EncodeToString(mac.Sum(nil))
|
||||
}
|
||||
|
||||
// 上下文键。
|
||||
const ctxRoleKey = "tcs_role"
|
||||
|
||||
// AuthMiddleware 校验 HMAC 签名,将角色写入上下文(需求14 权限基础)。
|
||||
func AuthMiddleware(store KeyStore) gin.HandlerFunc {
|
||||
return func(c *gin.Context) {
|
||||
authz := c.GetHeader("Authorization")
|
||||
if !strings.HasPrefix(authz, "TCS ") {
|
||||
Error(c, 401, "UNAUTHORIZED", "缺少或非法 Authorization 头")
|
||||
c.Abort()
|
||||
return
|
||||
}
|
||||
parts := strings.SplitN(strings.TrimPrefix(authz, "TCS "), ":", 2)
|
||||
if len(parts) != 2 {
|
||||
Error(c, 401, "UNAUTHORIZED", "Authorization 格式应为 TCS {apiKey}:{signature}")
|
||||
c.Abort()
|
||||
return
|
||||
}
|
||||
apiKey, sig := parts[0], parts[1]
|
||||
secret, role, ok := store.Lookup(apiKey)
|
||||
if !ok {
|
||||
Error(c, 401, "UNAUTHORIZED", "未知 API Key")
|
||||
c.Abort()
|
||||
return
|
||||
}
|
||||
expected := Sign(secret, c.Request.Method, c.Request.URL.Path)
|
||||
if !hmac.Equal([]byte(expected), []byte(sig)) {
|
||||
Error(c, 401, "UNAUTHORIZED", "签名校验失败")
|
||||
c.Abort()
|
||||
return
|
||||
}
|
||||
c.Set(ctxRoleKey, role)
|
||||
c.Next()
|
||||
}
|
||||
}
|
||||
|
||||
// RoleFromContext 取出鉴权后的角色。
|
||||
func RoleFromContext(c *gin.Context) string {
|
||||
if v, ok := c.Get(ctxRoleKey); ok {
|
||||
return fmt.Sprintf("%v", v)
|
||||
}
|
||||
return ""
|
||||
}
|
||||
@@ -0,0 +1,39 @@
|
||||
package httpx
|
||||
|
||||
import (
|
||||
"github.com/gin-gonic/gin"
|
||||
)
|
||||
|
||||
// Resp 是统一响应结构。
|
||||
type Resp struct {
|
||||
Code string `json:"code"`
|
||||
Message string `json:"message,omitempty"`
|
||||
Data interface{} `json:"data,omitempty"`
|
||||
}
|
||||
|
||||
// OK 返回成功响应。
|
||||
func OK(c *gin.Context, data interface{}) {
|
||||
c.JSON(200, Resp{Code: "SUCCESS", Data: data})
|
||||
}
|
||||
|
||||
// Created 返回 201。
|
||||
func Created(c *gin.Context, data interface{}) {
|
||||
c.JSON(201, Resp{Code: "CREATED", Data: data})
|
||||
}
|
||||
|
||||
// Accepted 返回 202(异步任务已受理)。
|
||||
func Accepted(c *gin.Context, data interface{}) {
|
||||
c.JSON(202, Resp{Code: "ACCEPTED", Data: data})
|
||||
}
|
||||
|
||||
// Error 返回错误响应。
|
||||
func Error(c *gin.Context, status int, code, message string) {
|
||||
c.JSON(status, Resp{Code: code, Message: message})
|
||||
}
|
||||
|
||||
// Health 注册通用健康检查端点。
|
||||
func Health(r *gin.Engine, service string) {
|
||||
r.GET("/healthz", func(c *gin.Context) {
|
||||
c.JSON(200, gin.H{"status": "ok", "service": service})
|
||||
})
|
||||
}
|
||||
Reference in New Issue
Block a user