// 领域 API 层:资源路径与语义逐一对照 web.archived/src/shared/api/dataProvider.js。 // 全部经由 requestErrorConfig.request(Basic Auth + 401 全局登出)。 import { AUTH_STORAGE_KEY, RequestError, jsonOptions, request, unauthorized } from '@/requestErrorConfig'; export const resourcePaths = { browsers: '/browsers', gateways: '/gateways', accounts: '/phase-a/accounts', drafts: '/phase-a/drafts', confirmations: '/phase-a/confirmations', tasks: '/phase-a/tasks', attempts: '/phase-a/attempts', 'network-exits': '/network-exits', 'creator-accounts': '/creator/accounts', 'creator-competitors': '/creator/competitors', 'creator-competitor-share-jobs': '/creator/competitor-share-jobs', 'creator-works': '/creator/works', 'creator-comments': '/creator/comments', 'creator-leads': '/creator/leads', 'creator-rules': '/creator/rules', 'creator-operations': '/creator/operations', 'creator-conversations': '/creator/conversations', 'creator-events': '/creator/events', 'creator-listeners': '/creator/listeners', } as const; export type Resource = keyof typeof resourcePaths; // 与归档版 filterKeys 一致:资源 → 允许透传的查询字段。 const filterKeys: Partial> = { drafts: ['account_id'], confirmations: ['draft_id'], tasks: ['account_id', 'draft_id', 'state'], 'creator-competitors': ['platform'], 'creator-competitor-share-jobs': ['platform', 'status'], 'creator-works': ['platform', 'source_id', 'source_type', 'published_at_status', 'published_after', 'published_before', 'min_likes', 'min_comments', 'min_shares'], 'creator-comments': ['platform', 'work_id'], 'creator-leads': ['platform'], 'creator-operations': ['account_id'], 'creator-conversations': ['account_id'], 'creator-events': ['account_id'], }; export interface ListParams { resource: Resource; page?: number; pageSize?: number; filters?: Record; } // creator-* 走 page/page_size 分页;其余资源由后端返回全量或自带 total。 export async function getList({ resource, page, pageSize, filters = {} }: ListParams): Promise<{ data: T[]; total: number; hasNext?: boolean }> { const path = resourcePaths[resource]; const query = new URLSearchParams(); const allowed = filterKeys[resource] ?? []; for (const key of allowed) { const value = filters[key]; if (value !== undefined && value !== null && `${value}` !== '') query.set(key, `${value}`); } if (resource.startsWith('creator-')) { if (page) query.set('page', `${page}`); if (pageSize) query.set('page_size', `${pageSize}`); } const records = await request(`${path}${query.size ? `?${query}` : ''}`); const data: T[] = Array.isArray(records) ? records : records.data; return { data: data.map((record: any, index: number) => ({ ...record, id: record.id ?? record.alias ?? record.version ?? record.name ?? index })), total: records.total ?? data.length, ...(records && !Array.isArray(records) && Object.hasOwn(records, 'has_next') ? { hasNext: Boolean(records.has_next) } : {}), }; } const GETONE_ALLOWED: Resource[] = ['accounts', 'network-exits', 'browsers', 'drafts', 'confirmations', 'tasks', 'attempts', 'creator-accounts', 'creator-competitors', 'creator-competitor-share-jobs', 'creator-works', 'creator-comments', 'creator-rules', 'creator-operations']; export async function getOne(resource: Resource, id: string | number): Promise { if (!GETONE_ALLOWED.includes(resource)) throw new Error(`${resource} 不支持 getOne`); return request(`${resourcePaths[resource]}/${encodeURIComponent(id)}`); } export async function create(resource: Resource, variables: unknown): Promise { return request(resourcePaths[resource], jsonOptions('POST', variables)); } export async function update(resource: Resource, id: string | number, variables: unknown): Promise { if (resource === 'browsers') throw new Error('browsers 不支持 update'); await request(`${resourcePaths[resource]}/${encodeURIComponent(id)}`, jsonOptions('PUT', variables)); } export async function remove(resource: Resource, id: string | number): Promise { if (!['accounts', 'creator-competitors'].includes(resource)) throw new Error(`${resource} 不支持 deleteOne`); await request(`${resourcePaths[resource]}/${encodeURIComponent(id)}`, { method: 'DELETE' }); } // ===== 显式动词的领域动作(不伪装成 CRUD)===== export async function browserAction(alias: string, action: 'start' | 'stop' | 'recycle'): Promise { const targets: Record = { start: [`/browsers/${encodeURIComponent(alias)}/start`, 'POST'], stop: [`/browsers/${encodeURIComponent(alias)}/stop`, 'POST'], recycle: [`/browsers/${encodeURIComponent(alias)}`, 'DELETE'], }; const target = targets[action]; if (!target) throw new Error(`未知运行环境操作: ${action}`); await request(target[0], { method: target[1] }); } export async function accountAction(id: string, action: 'pause' | 'resume'): Promise { if (action !== 'pause' && action !== 'resume') throw new Error(`未知账号操作: ${action}`); await request(`/phase-a/accounts/${encodeURIComponent(id)}/${action}`, { method: 'POST' }); } export function createDraft(accountID: string, content: string) { return request('/phase-a/drafts', jsonOptions('POST', { account_id: accountID, content })); } export function confirmDraft(draftID: string, accountVersion: number, draftVersion: number) { return request('/phase-a/confirmations', jsonOptions('POST', { draft_id: draftID, account_version: accountVersion, draft_version: draftVersion })); } export function enqueueConfirmation(confirmationID: string) { return request('/phase-a/tasks', jsonOptions('POST', { confirmation_id: confirmationID })); } export async function taskAction(id: string, action: 'verify' | 'resume' | 'finish' | 'cancel', data?: unknown): Promise { if (!['verify', 'resume', 'finish', 'cancel'].includes(action)) throw new Error(`未知任务操作: ${action}`); const options = data ? jsonOptions('POST', data) : { method: 'POST' }; await request(`/phase-a/tasks/${encodeURIComponent(id)}/${action}`, options); } export async function networkExitAction(id: string, action: 'check' | 'disable' | 'enable'): Promise { if (!['check', 'disable', 'enable'].includes(action)) throw new Error(`未知网络出口操作: ${action}`); return request(`/network-exits/${encodeURIComponent(id)}/${action}`, { method: 'POST' }); } // creator-* 领域的透传请求(路径由调用方给出,含列表页/actions)。 export function creatorRequest(path: string, options?: RequestInit) { return request(path, options); } export function creatorAction(path: string, data?: unknown) { return request(path, data === undefined ? { method: 'POST' } : jsonOptions('POST', data)); } export function creatorCreate(path: string, data: unknown) { return request(path, jsonOptions('POST', data)); } export function creatorGet(path: string) { return request(path); } export function creatorUpdate(path: string, data: unknown) { return request(path, jsonOptions('PUT', data)); } export function creatorSyncConversation(id: string, limit = 200) { return request(`/creator/conversations/${encodeURIComponent(id)}/sync?limit=${encodeURIComponent(limit)}`, { method: 'POST' }); } // SSE 订阅:语义对齐归档版 dataProvider.creatorSubscribe(fetch 流式读取,按 data: 帧分发)。 export async function creatorSubscribe( path: string, onMessage: (frame: string) => void, signal: AbortSignal, onStatus?: (status: 'connected' | 'disconnected') => void, ): Promise { const auth = localStorage.getItem(AUTH_STORAGE_KEY); const headers: Record = {}; if (auth) headers.Authorization = `Basic ${btoa(auth)}`; const response = await fetch(`/api${path}`, { headers, signal }); if (response.status === 401) unauthorized(); if (!response.ok) { const body = await response.json().catch(() => ({})); throw new RequestError(body.error || `请求失败 (${response.status})`, response.status, body); } if (!response.body) throw new RequestError('业务更新流不可用', response.status); const reader = response.body.getReader(); onStatus?.('connected'); const decoder = new TextDecoder(); let buffer = ''; const dispatch = (frame: string) => { if (frame.split(/\r\n|\n|\r/).some((line) => line.startsWith('data:'))) onMessage(frame); }; for (;;) { const { value, done } = await reader.read(); buffer += decoder.decode(value || new Uint8Array(), { stream: !done }); let match: RegExpExecArray | null; while ((match = /\r\n\r\n|\n\n|\r\r/.exec(buffer))) { dispatch(buffer.slice(0, match.index)); buffer = buffer.slice(match.index + match[0].length); } if (done) { if (buffer.trim()) dispatch(buffer); onStatus?.('disconnected'); return; } } }