Commit 5fc62e5b authored by Data Governance Dev's avatar Data Governance Dev

feat(web3): 前端 API 接 AbortSignal + 三个分页端点

client.js: request() 第三个参数 { signal } 透传给 fetch;
http.{get,post,put,patch,delete} 都接受可选 signal。

queries.js: 删 runQuery,新增三个:
  - startQuery(taskId, { pageSize, signal }) → /api/queries/start
  - fetchPage(sessionId, pageNo, { signal }) → /api/queries/page
  - cancelQuery(sessionId, { signal })    → /api/queries/cancel

signal 是 AbortController.signal,配合 orchestrator 实现:
  1. fetch 主动 reject(节省带宽)
  2. 后端 /cancel 摘掉 session(后续 /page 立刻 404 短路)

无单测(纯 plumbing,配合 commit 1 后端测试覆盖)。
parent 63b3b770
......@@ -4,11 +4,13 @@
* - baseURL:开发模式走 vite proxy (/api),生产可改为绝对地址
* - 统一 JSON 解析 + 错误抛出
* - 不引 axios,省一个依赖
* - 支持 AbortSignal:调用方传 { signal } → 透传给 fetch,实现可中断请求
* (如 DataQualityView 的「中断查询」按钮)
*/
const BASE = '/api'
async function request(path, { method = 'GET', body, params } = {}) {
async function request(path, { method = 'GET', body, params } = {}, { signal } = {}) {
// GET 请求把 params 拼到 query string
let fullPath = path
if (params && Object.keys(params).length > 0) {
......@@ -20,6 +22,9 @@ async function request(path, { method = 'GET', body, params } = {}) {
const init = {
method,
headers: { 'Content-Type': 'application/json' },
// AbortController.abort() 触发后 fetch 直接 reject,节省带宽
// 老调用方不传 signal 也没事,fetch 在没信号时正常跑
signal,
}
if (body !== undefined) init.body = body
......@@ -37,9 +42,9 @@ async function request(path, { method = 'GET', body, params } = {}) {
}
export const http = {
get: (path, opts) => request(path, { method: 'GET', ...(opts || {}) }),
post: (path, body) => request(path, { method: 'POST', body: JSON.stringify(body ?? {}) }),
put: (path, body) => request(path, { method: 'PUT', body: JSON.stringify(body ?? {}) }),
patch: (path, body) => request(path, { method: 'PATCH', body: JSON.stringify(body ?? {}) }),
delete: (path) => request(path, { method: 'DELETE' }),
get: (path, opts, opts2) => request(path, { method: 'GET', ...(opts || {}) }, opts2 || {}),
post: (path, body, opts2) => request(path, { method: 'POST', body: JSON.stringify(body ?? {}) }, opts2 || {}),
put: (path, body, opts2) => request(path, { method: 'PUT', body: JSON.stringify(body ?? {}) }, opts2 || {}),
patch: (path, body, opts2) => request(path, { method: 'PATCH', body: JSON.stringify(body ?? {}) }, opts2 || {}),
delete: (path, opts2) => request(path, { method: 'DELETE' }, opts2 || {}),
}
\ No newline at end of file
/**
* 数据查询 / 校验 API
* 数据查询 / 校验 API(分页版,2026-08-21 重构)
*
* - POST /api/queries/run 按任务配置:连目标库 → 读表 → 跑规则 → 返回结果
* 老端点 POST /api/queries/run(一次性同步返回)已删。
* 新三段式:
* - startQuery(taskId, { pageSize, signal }) 创建 session + 返 total_rows/total_pages/field_list
* - fetchPage(sessionId, pageNo, { signal }) 拉一页(可并发,多页同时拉)
* - cancelQuery(sessionId, { signal }) 主动取消,best-effort
*
* 三段都支持 AbortSignal:传 signal 后前端 abort 会让 fetch 直接 reject
* (网络中断 + 节省带宽),同时 server 端 /cancel 摘掉 session 让后续 /page 立刻 404。
*/
import { http } from './client'
/**
* 跑一次任务查询 + 校验
* 创建查询 session(跑 COUNT + 编译规则)
*
* 扫描范围是**全表**(后端不带 LIMIT,流式逐行读)。
* maxRows 只截断返回的行数,不影响 scanned / bad_rows —— 它们始终是全表真值。
* @param {number} taskId
* @param {{ pageSize?: number, signal?: AbortSignal }} [opts]
* @returns {Promise<{
* ok: boolean,
* message?: string,
* session_id: string,
* task_id: number,
* total_rows: number,
* total_pages: number,
* page_size: number,
* field_list: Array<{
* id: number, field_key: string, show_default: boolean, ord: number,
* rules: number, rule_list: Array<{ id, desc, rule_type, regex?, code? }>,
* field_comment?: string,
* }>,
* }>}
*/
export function startQuery(taskId, { pageSize = 500, signal } = {}) {
return http.post(
'/queries/start',
{ task_id: taskId, page_size: pageSize },
{ signal },
)
}
/**
* 拉一页(单页独立连接,跑规则,只返不合规行)
*
* @param {number} taskId 任务 id
* @param {number} maxRows 最多返回多少条不合规行(后端默认 5000)
* @returns {Promise<{ok, message?, scanned, bad_rows, total, truncated, rows: [], field_list: []}>}
* @param {string} sessionId /start 返回的 session token
* @param {number} pageNo 1-based
* @param {{ signal?: AbortSignal }} [opts]
* @returns {Promise<{
* ok: boolean,
* session_id: string,
* page_no: number,
* page_size: number,
* row_start: number,
* bad_rows: Array<{ __row_index: number, errorCells: string[], issues: Array, ...业务列 }>,
* scanned_delta: number,
* bad_delta: number,
* cancelled: boolean, // session 已被 /cancel 时 true
* done: boolean, // 本页是最后一页
* }>}
*/
export function runQuery(taskId, maxRows = 5000) {
return http.post('/queries/run', { task_id: taskId, max_rows: maxRows })
export function fetchPage(sessionId, pageNo, { signal } = {}) {
return http.post(
'/queries/page',
{ session_id: sessionId, page_no: pageNo },
{ signal },
)
}
/**
* 取消查询 session(idempotent:不存在/已取消也返 ok=true)
*
* @param {string} sessionId
* @param {{ signal?: AbortSignal }} [opts]
* @returns {Promise<{
* ok: boolean,
* cancelled: boolean,
* pages_scanned: number,
* bad_rows_so_far: number,
* }>}
*/
export function cancelQuery(sessionId, { signal } = {}) {
return http.post(
'/queries/cancel',
{ session_id: sessionId },
{ signal },
)
}
\ No newline at end of file
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment