Files
admin-web/src/modules/downloads/api/platformExportTasks.ts
T

168 lines
5.2 KiB
TypeScript

import axios from 'axios'
import { ApiResponseError, http, unwrapApiResponse, type ApiEnvelope } from '@/shared/api/http'
export type ExportTaskStatus = 'PENDING' | 'RUNNING' | 'SUCCEEDED' | 'FAILED' | 'EXPIRED'
export type KnownExportTaskType =
| 'LAST_MILE_ORDER_LIST'
| 'OVERSEAS_INVENTORY_BALANCE'
| 'OVERSEAS_INVENTORY_LEDGER'
| 'OVERSEAS_QUOTE_LIST'
| 'MONTHLY_EXCHANGE_RATE_LIST'
| 'VEHICLE_LIST'
| 'FLEET_QUOTE_LIST'
| 'TRIP_OPERATIONS_LIST'
| 'SELFOP_INVENTORY_TURNOVER'
| 'CUSTOMER_LIST'
| 'DOMESTIC_WAREHOUSE_ORDER_LIST'
| 'FINANCE_CUSTOMER_ACCOUNT_LIST'
| 'FINANCE_CUSTOMER_ACCOUNT_LEDGER'
| 'FINANCE_FEE_SOURCE_LIST'
| 'FINANCE_RECONCILIATION_STATEMENT_LIST'
| 'FINANCE_RECEIVABLE_SETTLEMENT_LIST'
| 'FINANCE_RECEIVABLE_SETTLEMENT_DETAIL'
| 'FINANCE_PAYABLE_SETTLEMENT_LIST'
| 'FINANCE_PAYABLE_SETTLEMENT_DETAIL'
export type ExportTaskType = KnownExportTaskType | (string & {})
export interface PlatformExportTask {
id: string
taskNo: string
exportType: ExportTaskType
format: 'XLSX' | string
taskName: string
status: ExportTaskStatus
totalRows: number | null
processedRows: number
fileName: string | null
contentType: string | null
sizeBytes: number | null
failureMessage: string | null
attemptCount: number
maxAttempts: number
nextRetryAt: string | null
requestedBy: string
requestedUsername: string | null
requestedAt: string
startedAt: string | null
completedAt: string | null
expiresAt: string | null
}
export interface PlatformExportTaskPage {
records: PlatformExportTask[]
page: number
size: number
total: number
pages: number
}
export interface PlatformExportTaskQuery {
page: number
size: number
exportType?: string
status?: ExportTaskStatus
requestedFrom?: string
requestedTo?: string
}
export interface CreatePlatformExportTaskRequest {
exportType: ExportTaskType
format: 'XLSX'
taskName: string
parameters: Record<string, string | number | boolean | null | undefined | string[]>
}
const basePath = '/platform/v1/files/export-tasks'
const downloadTimeoutMs = 10 * 60 * 1000
const maxErrorEnvelopeBytes = 1024 * 1024
async function parseDownloadErrorBlob(blob: Blob, status?: number): Promise<ApiResponseError | null> {
if (!blob.type.toLowerCase().includes('json')) return null
if (blob.size > maxErrorEnvelopeBytes) {
return new ApiResponseError(
'下载接口返回了超出限制的 JSON 响应',
'INVALID_DOWNLOAD_RESPONSE',
status,
)
}
try {
const payload = JSON.parse(await blob.text()) as Partial<ApiEnvelope<unknown>>
if (!payload || typeof payload !== 'object' || Array.isArray(payload)) {
return new ApiResponseError(
'下载接口返回了无法识别的 JSON 响应',
'INVALID_DOWNLOAD_RESPONSE',
status,
)
}
return new ApiResponseError(
payload.code === '200' ? '下载接口未返回文件内容' : payload.message || '文件下载失败',
payload.code === '200' ? 'INVALID_DOWNLOAD_RESPONSE' : payload.code || (status ? `HTTP_${status}` : 'DOWNLOAD_FAILED'),
status,
payload.data,
payload.requestId,
payload.traceId,
)
} catch {
return new ApiResponseError(
'下载接口返回了无法识别的 JSON 响应',
'INVALID_DOWNLOAD_RESPONSE',
status,
)
}
}
export async function createPlatformExportTask(payload: CreatePlatformExportTaskRequest) {
const response = await http.post<ApiEnvelope<PlatformExportTask>, ApiEnvelope<PlatformExportTask>>(basePath, payload)
return unwrapApiResponse(response)
}
export async function listPlatformExportTasks(query: PlatformExportTaskQuery) {
const response = await http.get<ApiEnvelope<PlatformExportTaskPage>, ApiEnvelope<PlatformExportTaskPage>>(basePath, {
params: query,
})
const page = unwrapApiResponse(response)
return {
...page,
page: Number(page.page),
size: Number(page.size),
total: Number(page.total),
pages: Number(page.pages),
}
}
export async function getPlatformExportTask(taskId: string) {
const response = await http.get<ApiEnvelope<PlatformExportTask>, ApiEnvelope<PlatformExportTask>>(
`${basePath}/${encodeURIComponent(taskId)}`,
)
return unwrapApiResponse(response)
}
export async function downloadPlatformExportTask(taskId: string) {
try {
const blob = await http.get<Blob, Blob>(`${basePath}/${encodeURIComponent(taskId)}/download`, {
responseType: 'blob',
// 导出文件上限 50 MB,不能继承普通接口的 15 秒超时,也不能无限等待半断开的连接。
timeout: downloadTimeoutMs,
})
const responseError = await parseDownloadErrorBlob(blob, 200)
if (responseError) throw responseError
return blob
} catch (error) {
if (axios.isAxiosError(error) && error.response?.data instanceof Blob) {
const responseError = await parseDownloadErrorBlob(error.response.data, error.response.status)
if (responseError) throw responseError
}
throw error
}
}
export async function retryPlatformExportTask(taskId: string) {
const response = await http.post<ApiEnvelope<PlatformExportTask>, ApiEnvelope<PlatformExportTask>>(
`${basePath}/${encodeURIComponent(taskId)}/retry`,
{},
)
return unwrapApiResponse(response)
}