feat(A1/A3+C8): 客户端令牌链路打通(按人鉴权就绪)+ 快照 JSON 写入节流

A1/A3 客户端令牌链路(服务端守卫已能按 JWT 鉴权,缺口在客户端不带身份)
- 前端:新增 user-token-bridge(纯逻辑,值变化才推送)+ user-token-sync(启动安装,
  pywebviewready/storage/60s 轮询补推),main.ts 接入;桥接口补 set_user_token
- 客户端:新增 crawler_core/user_token.py(持有 + 对自家 Java 端点注入
  Authorization: Bearer <jwt>,Session.request 包装,第三方域名不注入、已有头不覆盖、
  登出清空、SHUFUAI_DISABLE_USER_TOKEN_HOOK 可关);main.py 暴露桥方法并在启动安装钩子
- 服务端:守卫契约测试补 5 个用户态前缀用例(开关关闭放行 / 打开后匿名 401 /
  用户令牌通过 / 内部令牌仍放行 / /api/ziniao 第二层)
- 翻开关的前置条件(客户端铺开后置 aiimage.security.user-tool-guard-enabled=true)写入报告

C8 快照 JSON 写入节流
- 读端改以 biz_task_result_item 行为准、JSON 仅兜底(历史任务 JSON 仍是唯一副本时可用)
- 整档 JSON 改 30s 节流写,终态路径 force 立即写;新增 2 个契约测试钉住语义

验证:mvn test 2888 全绿;npm test 741 全绿;user_token 钩子自测(注入/归一/第三方跳过/不覆盖/登出)通过
This commit is contained in:
2026-09-14 10:20:15 +08:00
parent 7643094f1d
commit c55c4a140b
10 changed files with 423 additions and 9 deletions
@@ -186,6 +186,7 @@ import { formatDateTime } from '@/shared/utils/datetime'
import { runBatchDelete } from '@/shared/utils/batch-delete'
import { isRecordMissingError } from '@/shared/utils/task-queue-state.ts'
import { useTablePaging } from '@/shared/composables/useTablePaging.ts'
import { getPollingProgressBatch } from '@/shared/api/task-progress-polling.ts'
const shopInput = ref('')
const candidates = ref<ProductRiskCandidateVo[]>([])
@@ -752,7 +753,10 @@ async function refreshTaskBatch() {
const ids = pollingTaskIds.value.filter((id) => id > 0)
if (!ids.length) return
try {
const batch = await getProductRiskTaskProgressBatch(ids)
// F5:轮询只读 task.id/status(轻量白名单覆盖),走 light 端点减小响应体,异常回退重型 batch
const batch = await getPollingProgressBatch('productRisk', ids, {
fallback: () => getProductRiskTaskProgressBatch(ids),
})
let changed = false
const nextSnapshots = { ...taskSnapshots.value }
for (const d of batch.items || []) {
@@ -835,7 +839,10 @@ async function waitForTaskTerminal(taskId: number) {
while (true) {
if (disposed) return 'STOPPED'
try {
const batch = await getProductRiskTaskProgressBatch([taskId])
// F5:等待终态的紧循环只读 status,走 light 端点(异常回退重型 batch)
const batch = await getPollingProgressBatch('productRisk', [taskId], {
fallback: () => getProductRiskTaskProgressBatch([taskId]),
})
if (transientErrorCount > 0) {
queuePushResult.value = `任务 ${taskId} 服务已恢复,继续等待执行结果...`
}