Files
MAcode/tcs-iptv/web-console/src/api.js
T
selfrelease 73e22f79d2 feat(phase2-fe): 二期可视化(分账/追责/确权/授权/回传)
- GovernancePanel: 监管片库详情新增'权益与治理'标签(分账/追责取证/确权举证/授权管理)
- 分账面板: 播放聚合统计+CP60/平台34/服务费6分账展示
- 追责面板: 全链路存证Timeline+审播一致/篡改定位结果
- 确权面板: 证据链+谁先锁定谁有权声明
- 授权面板: 登记授权范围(地域/平台/期限)+核验
- 运营商台: 回传播放(含购买)按钮喂分账数据
- 前端build通过, HMR生效
2026-06-14 17:31:49 +08:00

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 }),
}