feat: 更新CC码监管方案业务版文档,新增摘要、UGC专项、直播专项、存量处置、误判申诉、水印嵌入、CC码生命周期、组织保障等章节;同步macode到cccode重命名及其他代码变更

This commit is contained in:
freedakgmail
2026-07-20 22:25:40 +08:00
parent 9f1e2d0a21
commit eb4a82a877
76 changed files with 2600 additions and 2122 deletions
+1 -1
View File
@@ -28,7 +28,7 @@ export default function App() {
TCS-IPTV 内容可信锁定系统
</Title>
<Text style={{ color: '#b3c5ff', marginLeft: 16, whiteSpace: 'nowrap' }}>
陕西IPTV运营公司 · MA+哈希双锚定
陕西IPTV运营公司 · CC+哈希双锚定
</Text>
<Menu
mode="horizontal" theme="dark" selectedKeys={[view]}
+15 -15
View File
@@ -28,7 +28,7 @@ export default function FlowDemo() {
const [current, setCurrent] = useState(0)
const [running, setRunning] = useState(false)
const [logs, setLogs] = useState([])
const [ctx, setCtx] = useState({}) // reviewID/ctid/maCode/cert/fileHash/episodeHashes
const [ctx, setCtx] = useState({}) // reviewID/ctid/ccCode/cert/fileHash/episodeHashes
const [done, setDone] = useState(false)
const [episodes, setEpisodes] = useState([]) // 集级哈希列表
const [epVerify, setEpVerify] = useState({}) // {episode: 'match'|'mismatch'}
@@ -70,8 +70,8 @@ export default function FlowDemo() {
break
case 'issue':
res = await api.issue({ review_id: shared.reviewID, issuer: '陕西IPTV运营公司' })
if (res.ok) { shared.maCode = res.data.data.ma_code; shared.cert = res.data.data.certificate }
addLog(step.role, step.title, res.ok, res.ok ? `MA${shared.maCode}` : res.data.message)
if (res.ok) { shared.ccCode = res.data.data.ma_code; shared.cert = res.data.data.certificate }
addLog(step.role, step.title, res.ok, res.ok ? `CC${shared.ccCode}` : res.data.message)
break
case 'csps':
res = await api.csps({ review_id: shared.reviewID, approved: true, reviewer_id: 'sxiptv-审核01' })
@@ -79,18 +79,18 @@ export default function FlowDemo() {
break
case 'ingest':
res = await api.ingest({
ma_code: shared.maCode, content_twin_id: shared.ctid,
ma_code: shared.ccCode, content_twin_id: shared.ctid,
media_asset_id: 'SXMEDIA-' + fileHash, lib_name: '陕西IPTV媒体资源库',
})
addLog(step.role, step.title, res.ok, res.ok ? '已入媒资库' : res.data.message)
break
case 'publish':
res = await api.publish({ ma_code: shared.maCode, certificate: shared.cert })
res = await api.publish({ ma_code: shared.ccCode, certificate: shared.cert })
addLog(step.role, step.title, res.ok, res.ok ? '已发布' : res.data.message)
break
case 'inject':
res = await api.inject({
content_twin_id: shared.ctid, ma_code: shared.maCode, file_sha256: fileHash,
content_twin_id: shared.ctid, ma_code: shared.ccCode, file_sha256: fileHash,
operator_id: v.opId, cdn_endpoint: v.cdn,
})
addLog(step.role, step.title, res.ok,
@@ -119,11 +119,11 @@ export default function FlowDemo() {
setCurrent(STEPS.length)
setCtx(shared); setDone(true); setRunning(false)
message.success('全流程跑通:审过即锁定,锁定即通行')
await loadEpisodes(shared.maCode)
await loadEpisodes(shared.ccCode)
}
async function loadEpisodes(maCode) {
const res = await api.episodes(maCode)
async function loadEpisodes(ccCode) {
const res = await api.episodes(ccCode)
if (res.ok) setEpisodes(res.data.data.episodes || [])
}
@@ -131,7 +131,7 @@ export default function FlowDemo() {
async function verifyEp(ep, correct) {
const realHash = (ctx.episodeHashes || []).find((e) => e.episode === ep)?.hash
const submit = correct ? realHash : 'TAMPERED-' + realHash
const res = await api.verifyEpisode(ctx.maCode, ep, submit)
const res = await api.verifyEpisode(ctx.ccCode, ep, submit)
const matched = res.ok && res.data.data?.match
setEpVerify((prev) => ({ ...prev, [ep]: matched ? 'match' : 'mismatch' }))
if (matched) message.success(`${ep}集验真通过`)
@@ -139,7 +139,7 @@ export default function FlowDemo() {
}
async function doTakedown() {
const res = await api.takedown(ctx.maCode, '监管演示下架')
const res = await api.takedown(ctx.ccCode, '监管演示下架')
if (res.ok) {
addLog('regulator', '违规应急下架', true,
`秒级下架,受影响 CDN: ${(res.data.data.cdn_endpoints || []).join(', ')}`)
@@ -149,7 +149,7 @@ export default function FlowDemo() {
async function doTamperTest() {
const res = await api.inject({
content_twin_id: ctx.ctid, ma_code: ctx.maCode, file_sha256: 'TAMPERED-' + ctx.fileHash,
content_twin_id: ctx.ctid, ma_code: ctx.ccCode, file_sha256: 'TAMPERED-' + ctx.fileHash,
operator_id: 'OP-X', cdn_endpoint: 'cdn://x',
})
addLog('operator', '篡改注入测试', !res.ok, res.ok ? '⚠️ 异常:篡改竟通过' : '✅ 已拒绝:' + res.data.message)
@@ -210,7 +210,7 @@ export default function FlowDemo() {
{done && (
<Card title={<Space><SafetyCertificateOutlined />赋码结果双锚定</Space>} style={{ marginBottom: 16 }}>
<Descriptions bordered size="small" column={1}>
<Descriptions.Item label="MA 码(监管锚点)"><Text strong copyable>{ctx.maCode}</Text></Descriptions.Item>
<Descriptions.Item label="MA 码(监管锚点)"><Text strong copyable>{ctx.ccCode}</Text></Descriptions.Item>
<Descriptions.Item label="文件哈希(技术锚点)">{ctx.fileHash}</Descriptions.Item>
<Descriptions.Item label="CTID(机器主键)">{ctx.ctid}</Descriptions.Item>
</Descriptions>
@@ -226,14 +226,14 @@ export default function FlowDemo() {
<Card
title={<Space>集级面板<Tag color="purple">一剧一码 · {episodes.length} 集独立哈希</Tag></Space>}
style={{ marginBottom: 16 }}
extra={<Text type="secondary">集级子标识{ctx.maCode}#E01 </Text>}
extra={<Text type="secondary">集级子标识{ctx.ccCode}#E01 </Text>}
>
<Table
size="small" rowKey="episode" pagination={false}
dataSource={episodes}
columns={[
{ title: '集号', dataIndex: 'episode', width: 70, render: (e) => <Tag> {e} </Tag> },
{ title: '集级子标识', render: (_, r) => <Text code>{`${ctx.maCode}#E${String(r.episode).padStart(2, '0')}`}</Text> },
{ title: '集级子标识', render: (_, r) => <Text code>{`${ctx.ccCode}#E${String(r.episode).padStart(2, '0')}`}</Text> },
{ title: '该集哈希', dataIndex: 'hash_value', ellipsis: true },
{
title: '验真状态', width: 110, render: (_, r) => {
+16 -16
View File
@@ -14,16 +14,16 @@ const nodeLabel = {
}
// 分账面板
function SettlementPanel({ maCode }) {
function SettlementPanel({ ccCode }) {
const [sum, setSum] = useState(null)
const [st, setSt] = useState(null)
async function load() {
const s = await api.playbackSummary(maCode)
const s = await api.playbackSummary(ccCode)
setSum(s.data?.data)
const r = await api.settlement(maCode, '2026-06')
const r = await api.settlement(ccCode, '2026-06')
if (r.ok) setSt(r.data.data)
}
useEffect(() => { load() }, [maCode])
useEffect(() => { load() }, [ccCode])
if (!sum) return null
return (
<div>
@@ -49,9 +49,9 @@ function SettlementPanel({ maCode }) {
}
// 追责取证面板
function AccountabilityPanel({ maCode }) {
function AccountabilityPanel({ ccCode }) {
const [rep, setRep] = useState(null)
useEffect(() => { api.accountability(maCode).then((r) => setRep(r.data?.data)) }, [maCode])
useEffect(() => { api.accountability(ccCode).then((r) => setRep(r.data?.data)) }, [ccCode])
if (!rep) return null
return (
<div>
@@ -74,9 +74,9 @@ function AccountabilityPanel({ maCode }) {
}
// 确权举证面板
function EvidencePanel({ maCode }) {
function EvidencePanel({ ccCode }) {
const [ev, setEv] = useState(null)
useEffect(() => { api.evidence(maCode).then((r) => setEv(r.data?.data)) }, [maCode])
useEffect(() => { api.evidence(ccCode).then((r) => setEv(r.data?.data)) }, [ccCode])
if (!ev) return null
return (
<Card size="small" title="版权确权证据链">
@@ -94,7 +94,7 @@ function EvidencePanel({ maCode }) {
}
// 授权管理面板
function AuthPanel({ maCode }) {
function AuthPanel({ ccCode }) {
const [form] = Form.useForm()
const [checkResult, setCheckResult] = useState(null)
async function grant() {
@@ -102,13 +102,13 @@ function AuthPanel({ maCode }) {
const regions = v.regions ? v.regions.split(',').map((s) => s.trim()).filter(Boolean) : []
const platforms = v.platforms ? v.platforms.split(',').map((s) => s.trim()).filter(Boolean) : []
const expiry = v.expiry ? v.expiry.toISOString() : ''
const res = await api.authorize(maCode, regions, platforms, expiry)
const res = await api.authorize(ccCode, regions, platforms, expiry)
if (res.ok) message.success('授权已登记')
else message.error(res.data.message)
}
async function check() {
const v = form.getFieldsValue()
const res = await api.authCheck(maCode, v.checkRegion || '', v.checkPlatform || '')
const res = await api.authCheck(ccCode, v.checkRegion || '', v.checkPlatform || '')
setCheckResult(res.data?.data)
}
return (
@@ -134,13 +134,13 @@ function AuthPanel({ maCode }) {
)
}
export default function GovernancePanel({ maCode }) {
export default function GovernancePanel({ ccCode }) {
return (
<Tabs size="small" items={[
{ key: 'settle', label: '💰 分账', children: <SettlementPanel maCode={maCode} /> },
{ key: 'account', label: '⚖️ 追责取证', children: <AccountabilityPanel maCode={maCode} /> },
{ key: 'evidence', label: '📜 确权举证', children: <EvidencePanel maCode={maCode} /> },
{ key: 'auth', label: '🔑 授权管理', children: <AuthPanel maCode={maCode} /> },
{ key: 'settle', label: '💰 分账', children: <SettlementPanel ccCode={ccCode} /> },
{ key: 'account', label: '⚖️ 追责取证', children: <AccountabilityPanel ccCode={ccCode} /> },
{ key: 'evidence', label: '📜 确权举证', children: <EvidencePanel ccCode={ccCode} /> },
{ key: 'auth', label: '🔑 授权管理', children: <AuthPanel ccCode={ccCode} /> },
]} />
)
}
+7 -7
View File
@@ -268,7 +268,7 @@ function LibraryDesk({ tick, onChanged }) {
const [filter, setFilter] = useState('all')
const [rows, setRows] = useState([])
const [loading, setLoading] = useState(false)
const [detail, setDetail] = useState(null) // {maCode, mappings, episodes}
const [detail, setDetail] = useState(null) // {ccCode, mappings, episodes}
async function load() {
setLoading(true)
@@ -304,13 +304,13 @@ function LibraryDesk({ tick, onChanged }) {
})
}
function takedownEpisode(maCode, episode) {
function takedownEpisode(ccCode, episode) {
Modal.confirm({
title: `集级下架 · 第 ${episode}`,
content: `只下架《${maCode}#E${String(episode).padStart(2, '0')}》本集,整剧其他集继续流通。确认?`,
content: `只下架《${ccCode}#E${String(episode).padStart(2, '0')}》本集,整剧其他集继续流通。确认?`,
okText: '下架本集', okType: 'danger', cancelText: '取消',
onOk: async () => {
const res = await api.takedownEpisode(maCode, episode, '监管片库集级下架')
const res = await api.takedownEpisode(ccCode, episode, '监管片库集级下架')
if (res.ok) {
message.success(`${episode} 集已下架`)
await viewDetail(detail.content) // 刷新弹窗
@@ -326,8 +326,8 @@ function LibraryDesk({ tick, onChanged }) {
else message.error(res.data.message || '恢复失败')
}
async function restoreEpisode(maCode, episode) {
const res = await api.restoreEpisode(maCode, episode)
async function restoreEpisode(ccCode, episode) {
const res = await api.restoreEpisode(ccCode, episode)
if (res.ok) {
message.success('第 ' + episode + ' 集已恢复上架')
await viewDetail(detail.content)
@@ -420,7 +420,7 @@ function LibraryDesk({ tick, onChanged }) {
</Card>
</>
) },
{ key: 'gov', label: '权益与治理', children: <GovernancePanel maCode={detail.content.ma_code} /> },
{ key: 'gov', label: '权益与治理', children: <GovernancePanel ccCode={detail.content.ma_code} /> },
]} />
)}
</Modal>
+16 -16
View File
@@ -32,14 +32,14 @@ function ScreenTags({ screens }) {
// ============ 跨域解析网关(C.1/C.2============
function ResolvePanel() {
const [maCode, setMaCode] = useState('')
const [ccCode, setCcCode] = useState('')
const [res, setRes] = useState(null)
const [loading, setLoading] = useState(false)
async function doResolve() {
if (!maCode) return message.warning('请输入 MA 码(支持集级子标识 #E03)')
if (!ccCode) return message.warning('请输入 MA 码(支持集级子标识 #E03)')
setLoading(true)
const r = await api.resolve(maCode.trim())
const r = await api.resolve(ccCode.trim())
setLoading(false)
if (r.ok) setRes(r.data.data)
else { setRes(null); message.error(r.data.message || '解析失败') }
@@ -50,7 +50,7 @@ function ResolvePanel() {
<Card size="small" title={<Space><GlobalOutlined />MA 跨域解析网关 · 同一码三屏统一解析</Space>}>
<Space.Compact style={{ width: '100%', maxWidth: 640 }}>
<Input placeholder="如 MA.156.8531.6101/WD/20260000021 或 ...#E03"
value={maCode} onChange={(e) => setMaCode(e.target.value)} onPressEnter={doResolve} />
value={ccCode} onChange={(e) => setCcCode(e.target.value)} onPressEnter={doResolve} />
<Button type="primary" loading={loading} onClick={doResolve}>解析</Button>
</Space.Compact>
@@ -89,14 +89,14 @@ function ResolvePanel() {
// ============ 扫码验真(B.2============
function ScanVerifyPanel() {
const [maCode, setMaCode] = useState('')
const [ccCode, setCcCode] = useState('')
const [res, setRes] = useState(null)
const [loading, setLoading] = useState(false)
async function doScan() {
if (!maCode) return message.warning('请输入/扫描 MA 码')
if (!ccCode) return message.warning('请输入/扫描 MA 码')
setLoading(true)
const r = await api.scanVerify(maCode.trim())
const r = await api.scanVerify(ccCode.trim())
setLoading(false)
if (r.ok) setRes(r.data.data)
else { setRes(null); message.error(r.data.message || '验真失败') }
@@ -112,8 +112,8 @@ function ScanVerifyPanel() {
return (
<Card size="small" title={<Space><ScanOutlined />用户扫码验真 · 防盗版</Space>}>
<Space.Compact style={{ width: '100%', maxWidth: 640 }}>
<Input placeholder="模拟扫码:粘贴 MA 码" value={maCode}
onChange={(e) => setMaCode(e.target.value)} onPressEnter={doScan} />
<Input placeholder="模拟扫码:粘贴 MA 码" value={ccCode}
onChange={(e) => setCcCode(e.target.value)} onPressEnter={doScan} />
<Button type="primary" icon={<ScanOutlined />} loading={loading} onClick={doScan}>扫码验真</Button>
</Space.Compact>
@@ -140,7 +140,7 @@ function ScanVerifyPanel() {
// ============ 跨屏权益通兑(D.1============
function RightsPanel() {
const [maCode, setMaCode] = useState('')
const [ccCode, setCcCode] = useState('')
const [userHash, setUserHash] = useState('user-demo-001')
const [buyScreen, setBuyScreen] = useState('iptv')
const [verifyScreen, setVerifyScreen] = useState('app')
@@ -148,14 +148,14 @@ function RightsPanel() {
const [verifyRes, setVerifyRes] = useState(null)
async function doBuy() {
if (!maCode) return message.warning('请输入 MA 码')
const r = await api.purchase(maCode.trim(), userHash, buyScreen)
if (!ccCode) return message.warning('请输入 MA 码')
const r = await api.purchase(ccCode.trim(), userHash, buyScreen)
if (r.ok) { setBuyRes(r.data.data); message.success(`已在「${screenMeta[buyScreen].label}」购买`) }
else message.error(r.data.message || '购买失败')
}
async function doVerify() {
if (!maCode) return message.warning('请输入 MA 码')
const r = await api.verifyRights(maCode.trim(), userHash, verifyScreen)
if (!ccCode) return message.warning('请输入 MA 码')
const r = await api.verifyRights(ccCode.trim(), userHash, verifyScreen)
if (r.ok) setVerifyRes(r.data.data)
else message.error(r.data.message || '核验失败')
}
@@ -166,8 +166,8 @@ function RightsPanel() {
<Card size="small" title={<Space><ShoppingOutlined />跨屏权益通兑 · 一次购买全屏通看</Space>}>
<Space direction="vertical" style={{ width: '100%' }}>
<Space wrap>
<Input addonBefore="MA 码" style={{ width: 380 }} value={maCode}
onChange={(e) => setMaCode(e.target.value)} placeholder="已发布的 MA 码" />
<Input addonBefore="MA 码" style={{ width: 380 }} value={ccCode}
onChange={(e) => setCcCode(e.target.value)} placeholder="已发布的 MA 码" />
<Input addonBefore="用户" style={{ width: 220 }} value={userHash}
onChange={(e) => setUserHash(e.target.value)} />
</Space>
+19 -19
View File
@@ -53,30 +53,30 @@ export const api = {
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 }),
verify: (ccCode, fileHash) => request('regulator', 'POST', '/content/verify', { ma_code: ccCode, file_sha256: fileHash }),
mappings: (ccCode) => request('regulator', 'GET', '/content/mappings?ma_code=' + encodeURIComponent(ccCode)),
takedown: (ccCode, reason) => request('regulator', 'POST', '/content/takedown', { ma_code: ccCode, reason }),
takedownEpisode: (ccCode, episode, reason) => request('regulator', 'POST', '/content/takedown-episode', { ma_code: ccCode, episode, reason }),
restore: (ccCode) => request('regulator', 'POST', '/content/restore', { ma_code: ccCode }),
restoreEpisode: (ccCode, episode) => request('regulator', 'POST', '/content/restore-episode', { ma_code: ccCode, 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 }),
episodes: (ccCode) => request('regulator', 'GET', '/content/episodes?ma_code=' + encodeURIComponent(ccCode)),
verifyEpisode: (ccCode, episode, fileHash) => request('regulator', 'POST', '/content/verify-episode', { ma_code: ccCode, 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 }),
playbackSummary: (ccCode) => request('regulator', 'GET', '/data/playback-summary?ma_code=' + encodeURIComponent(ccCode)),
settlement: (ccCode, period) => request('regulator', 'POST', '/settlement/compute', { ma_code: ccCode, period }),
accountability: (ccCode) => request('regulator', 'GET', '/content/accountability?ma_code=' + encodeURIComponent(ccCode)),
evidence: (ccCode) => request('regulator', 'GET', '/content/evidence?ma_code=' + encodeURIComponent(ccCode)),
authorize: (ccCode, regions, platforms, expiryAt) => request('regulator', 'POST', '/content/authorize', { ma_code: ccCode, regions, platforms, expiry_at: expiryAt }),
authCheck: (ccCode, region, platform) => request('regulator', 'POST', '/content/auth-check', { ma_code: ccCode, region, platform }),
crossProvince: (ccCode, fileHash, province) => request('regulator', 'POST', '/content/cross-province', { ma_code: ccCode, file_sha256: fileHash, province }),
// 四期:大小屏融合(跨域解析/扫码验真/跨屏权益)
resolve: (maCode) => request('regulator', 'GET', '/content/resolve?ma_code=' + encodeURIComponent(maCode)),
scanVerify: (maCode) => request('operator', 'POST', '/content/scan-verify', { ma_code: maCode }),
purchase: (maCode, userHash, screen) => request('operator', 'POST', '/rights/purchase', { ma_code: maCode, user_hash: userHash, screen }),
verifyRights: (maCode, userHash, screen) => request('operator', 'POST', '/rights/verify', { ma_code: maCode, user_hash: userHash, screen }),
resolve: (ccCode) => request('regulator', 'GET', '/content/resolve?ma_code=' + encodeURIComponent(ccCode)),
scanVerify: (ccCode) => request('operator', 'POST', '/content/scan-verify', { ma_code: ccCode }),
purchase: (ccCode, userHash, screen) => request('operator', 'POST', '/rights/purchase', { ma_code: ccCode, user_hash: userHash, screen }),
verifyRights: (ccCode, userHash, screen) => request('operator', 'POST', '/rights/verify', { ma_code: ccCode, user_hash: userHash, screen }),
}