73e22f79d2
- GovernancePanel: 监管片库详情新增'权益与治理'标签(分账/追责取证/确权举证/授权管理) - 分账面板: 播放聚合统计+CP60/平台34/服务费6分账展示 - 追责面板: 全链路存证Timeline+审播一致/篡改定位结果 - 确权面板: 证据链+谁先锁定谁有权声明 - 授权面板: 登记授权范围(地域/平台/期限)+核验 - 运营商台: 回传播放(含购买)按钮喂分账数据 - 前端build通过, HMR生效
78 lines
4.6 KiB
JavaScript
78 lines
4.6 KiB
JavaScript
// API 客户端:Web Crypto 实现 HMAC-SHA256 签名,与后端 httpx.Sign 一致。
|
|
//
|
|
// ⚠️ 安全提示:四角色密钥放前端仅用于 MVP/演示。
|
|
// 生产必须改为「控制台 BFF + 会话令牌」,密钥不下发浏览器。
|
|
|
|
const enc = new TextEncoder()
|
|
|
|
async function hmacSha256Base64(secret, message) {
|
|
const key = await crypto.subtle.importKey(
|
|
'raw', enc.encode(secret),
|
|
{ name: 'HMAC', hash: 'SHA-256' }, false, ['sign']
|
|
)
|
|
const sig = await crypto.subtle.sign('HMAC', key, enc.encode(message))
|
|
let bin = ''
|
|
for (const b of new Uint8Array(sig)) bin += String.fromCharCode(b)
|
|
return btoa(bin)
|
|
}
|
|
|
|
// 四角色演示密钥(与 api-svc 预置一致)
|
|
export const ROLE_KEYS = {
|
|
regulator: { apiKey: 'ak-regulator', apiSecret: 'sk-regulator', label: '监管主体' },
|
|
reviewer: { apiKey: 'ak-reviewer', apiSecret: 'sk-reviewer', label: '审核/媒资' },
|
|
cp: { apiKey: 'ak-cp', apiSecret: 'sk-cp', label: '内容提供商' },
|
|
operator: { apiKey: 'ak-operator', apiSecret: 'sk-operator', label: '运营商' },
|
|
}
|
|
|
|
async function request(role, method, path, body) {
|
|
const cred = ROLE_KEYS[role]
|
|
const signPath = '/api/v1' + path.split('?')[0]
|
|
const sig = await hmacSha256Base64(cred.apiSecret, method + '\n' + signPath)
|
|
const headers = { Authorization: `TCS ${cred.apiKey}:${sig}` }
|
|
const opts = { method, headers }
|
|
if (body !== undefined) {
|
|
headers['Content-Type'] = 'application/json'
|
|
opts.body = JSON.stringify(body)
|
|
}
|
|
const resp = await fetch('/api/v1' + path, opts)
|
|
const data = await resp.json().catch(() => ({}))
|
|
return { status: resp.status, ok: resp.status >= 200 && resp.status < 300, data }
|
|
}
|
|
|
|
// 通用:按角色发起请求(供多角色工作台使用)
|
|
export function call(role, method, path, body) {
|
|
return request(role, method, path, body)
|
|
}
|
|
|
|
export const api = {
|
|
// 全流程各步骤(标注发起角色)
|
|
register: (body) => request('cp', 'POST', '/content/register', body),
|
|
issue: (body) => request('regulator', 'POST', '/content/issue', body),
|
|
csps: (body) => request('reviewer', 'POST', '/content/csps-result', body),
|
|
ingest: (body) => request('reviewer', 'POST', '/content/ingest', body),
|
|
publish: (body) => request('reviewer', 'POST', '/content/publish', body),
|
|
inject: (body) => request('operator', 'POST', '/content/inject', body),
|
|
// 监管功能
|
|
verify: (maCode, fileHash) => request('regulator', 'POST', '/content/verify', { ma_code: maCode, file_sha256: fileHash }),
|
|
mappings: (maCode) => request('regulator', 'GET', '/content/mappings?ma_code=' + encodeURIComponent(maCode)),
|
|
takedown: (maCode, reason) => request('regulator', 'POST', '/content/takedown', { ma_code: maCode, reason }),
|
|
takedownEpisode: (maCode, episode, reason) => request('regulator', 'POST', '/content/takedown-episode', { ma_code: maCode, episode, reason }),
|
|
restore: (maCode) => request('regulator', 'POST', '/content/restore', { ma_code: maCode }),
|
|
restoreEpisode: (maCode, episode) => request('regulator', 'POST', '/content/restore-episode', { ma_code: maCode, episode }),
|
|
// 集级粒度(一剧一码 + 集级哈希)
|
|
episodes: (maCode) => request('regulator', 'GET', '/content/episodes?ma_code=' + encodeURIComponent(maCode)),
|
|
verifyEpisode: (maCode, episode, fileHash) => request('regulator', 'POST', '/content/verify-episode', { ma_code: maCode, episode, file_sha256: fileHash }),
|
|
// 工作队列(多角色工作台)
|
|
reviews: (role, status) => request(role, 'GET', '/content/reviews?status=' + status),
|
|
list: (role, status) => request(role, 'GET', '/content/list?status=' + status),
|
|
// 二期:分账/追责/确权/授权/跨省/追更/回传
|
|
playback: (platformId, batch) => request('operator', 'POST', '/data/playback', { platform_id: platformId, batch }),
|
|
playbackSummary: (maCode) => request('regulator', 'GET', '/data/playback-summary?ma_code=' + encodeURIComponent(maCode)),
|
|
settlement: (maCode, period) => request('regulator', 'POST', '/settlement/compute', { ma_code: maCode, period }),
|
|
accountability: (maCode) => request('regulator', 'GET', '/content/accountability?ma_code=' + encodeURIComponent(maCode)),
|
|
evidence: (maCode) => request('regulator', 'GET', '/content/evidence?ma_code=' + encodeURIComponent(maCode)),
|
|
authorize: (maCode, regions, platforms, expiryAt) => request('regulator', 'POST', '/content/authorize', { ma_code: maCode, regions, platforms, expiry_at: expiryAt }),
|
|
authCheck: (maCode, region, platform) => request('regulator', 'POST', '/content/auth-check', { ma_code: maCode, region, platform }),
|
|
crossProvince: (maCode, fileHash, province) => request('regulator', 'POST', '/content/cross-province', { ma_code: maCode, file_sha256: fileHash, province }),
|
|
}
|