From ce4e6217419c5e025e8cf7d8c584af47e20fd38c Mon Sep 17 00:00:00 2001 From: yorn <1057707203@qq.com> Date: Thu, 5 Jun 2025 21:28:00 +0800 Subject: [PATCH] =?UTF-8?q?=E5=88=A0=E9=99=A4client=E6=96=87=E4=BB=B6?= =?UTF-8?q?=EF=BC=8C=E6=B7=BB=E5=8A=A0=E9=83=A8=E5=88=86progrest=E7=9A=84c?= =?UTF-8?q?onsole=E8=BE=93=E5=85=A5=E5=86=85=E5=AE=B9?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app/api/axios-client.ts | 5 +- app/api/client.ts | 312 -------------------------- app/api/postgrest-client.ts | 80 ++++--- app/components/reviews/ReviewTabs.tsx | 2 +- app/routes/documents._index.tsx | 2 +- app/routes/documents.edit.tsx | 2 +- 6 files changed, 59 insertions(+), 344 deletions(-) delete mode 100644 app/api/client.ts diff --git a/app/api/axios-client.ts b/app/api/axios-client.ts index 8ebd1a4..1c1be83 100644 --- a/app/api/axios-client.ts +++ b/app/api/axios-client.ts @@ -14,10 +14,10 @@ export type ApiResponse = { export type QueryParams = Record; // 获取 API 基础 URL +const API_BASE_URL = 'http://172.16.0.58:8008'; +// const API_BASE_URL = 'http://nas.7bm.co:3000'; // const API_BASE_URL = 'http://172.18.0.100:3000'; -const API_BASE_URL = 'http://nas.7bm.co:3000'; // const API_BASE_URL = 'http://172.16.0.119:9000/admin'; -// export const API_BASE_URL = 'http://nas.7bm.co:3000'; // 文档URL前缀 export const DOCUMENT_URL = 'http://nas.7bm.co:9000/docauditai/'; @@ -205,6 +205,7 @@ export async function apiRequest( url, headers }; + console.log(`📦 axios-client.ts->请求配置: \n${JSON.stringify(config)}`); // 删除body属性,避免axios警告 if ('body' in config) { diff --git a/app/api/client.ts b/app/api/client.ts deleted file mode 100644 index 0b0be04..0000000 --- a/app/api/client.ts +++ /dev/null @@ -1,312 +0,0 @@ -// app/api/client.ts -import { mockData, type MockApiResponse } from './mock'; - -/** - * API响应类型 - */ -export type ApiResponse = { - data?: T; - error?: string; - status: number; - headers?: Record; // 添加对响应头的支持 -}; - -export type QueryParams = Record; - -// 获取 API 基础 URL -// const API_BASE_URL = 'http://172.18.0.100:3000'; -const API_BASE_URL = 'http://nas.7bm.co:3000'; -// const API_BASE_URL = 'http://172.16.0.119:9000/admin'; -// export const API_BASE_URL = 'http://nas.7bm.co:3000'; - -// 文档URL前缀 -export const DOCUMENT_URL = 'http://nas.7bm.co:9000/docauditai/'; - -// 是否使用模拟数据(开发环境使用) -const USE_MOCK_DATA = false; // 设置为true使用模拟数据,避免API连接问题 - -/** - * 将编码后的URL解码为可读格式 - * @param url 编码后的URL - * @returns 解码后的可读URL - */ -function decodeUrlForDisplay(url: string): string { - try { - // 首先解码整个URL - const decodedUrl = decodeURIComponent(url); - - // 如果URL中包含@符号作为前缀,则移除它 - if (decodedUrl.startsWith('@')) { - return decodedUrl.substring(1); - } - - return decodedUrl; - } catch (error) { - // 如果解码失败,返回原始URL - console.error('URL解码失败:', error); - return url; - } -} - -/** - * 构建完整的 API URL - */ -function buildUrl(endpoint: string, params?: QueryParams): string { - let fullUrl; - - // 检查endpoint是否已经是完整URL - if (endpoint.startsWith('http')) { - fullUrl = endpoint; - } else { - // 确保API_BASE_URL格式正确 - const baseUrl = API_BASE_URL.endsWith('/') ? API_BASE_URL.slice(0, -1) : API_BASE_URL; - const path = endpoint.startsWith('/') ? endpoint : `/${endpoint}`; - fullUrl = `${baseUrl}${path}`; - } - - try { - // 创建URL对象 - const url = new URL(fullUrl); - - // 添加查询参数 - if (params) { - Object.entries(params).forEach(([key, value]) => { - if (value !== undefined) { - url.searchParams.append(key, String(value)); - } - }); - } - - return url.toString(); - } catch (error) { - console.error('URL构建错误:', fullUrl, error); - throw new Error(`无法构建URL: ${fullUrl}, 错误: ${error}`); - } -} - -// 超时控制 -const fetchWithTimeout = async (url: string, options: RequestInit, timeout = 5000) => { - const controller = new AbortController(); - const id = setTimeout(() => controller.abort(), timeout); - - try { - // console.log(`📦 client.ts->请求URL: ${decodeUrlForDisplay(url)}`); - const response = await fetch(url, { - ...options, - signal: controller.signal - }); - clearTimeout(id); - return response; - } catch (error) { - clearTimeout(id); - console.error(`📦 client.ts->请求请求失败: ${error}`); - - // 检查是否是网络连接问题 - if (error instanceof TypeError && error.message.includes('fetch failed')) { - console.error('网络连接错误,请检查API_BASE_URL配置是否正确'); - } - - throw error; - } -}; - -/** - * 获取模拟响应数据 - */ -function getMockResponse(endpoint: string): ApiResponse { - // console.log(`[开发模式] 使用模拟数据: ${endpoint}`); - - // 移除开头的斜杠以便于匹配 - const path = endpoint.startsWith('/') ? endpoint.substring(1) : endpoint; - - // 查找匹配的模拟数据 - for (const mockPath in mockData) { - const normalizedMockPath = mockPath.startsWith('/') ? mockPath.substring(1) : mockPath; - if (path === normalizedMockPath || path.startsWith(normalizedMockPath + '/')) { - // 如果是详情查询 (如 /evaluation_points/1) - if (path.includes('/') && path !== normalizedMockPath) { - const id = parseInt(path.split('/')[1]); - const mockDataItem = mockData[mockPath as keyof typeof mockData] as MockApiResponse; - - if (Array.isArray(mockDataItem.data)) { - const item = mockDataItem.data.find((item: {id: number}) => item.id === id); - - if (item) { - return { - data: { - code: 0, - msg: "成功", - data: item - } as unknown as T, - status: 200 - }; - } - } - - return { error: '未找到数据', status: 404 }; - } - - // 返回列表数据 - return { - data: mockData[mockPath as keyof typeof mockData] as unknown as T, - status: 200 - }; - } - } - - return { error: '没有匹配的模拟数据', status: 404 }; -} - -/** - * 通用 API 请求函数 - */ -export async function apiRequest( - endpoint: string, - options: RequestInit = {}, - params?: QueryParams -): Promise> { - // 如果使用模拟数据,直接返回模拟响应 - if (USE_MOCK_DATA) { - return getMockResponse(endpoint); - } - - try { - // 构建 URL - const url = buildUrl(endpoint, params); - - // 设置默认请求头 - const headers = new Headers(options.headers || {}); - if (!headers.has('Content-Type') && options.method !== 'GET') { - headers.set('Content-Type', 'application/json'); - } - if (!headers.has('Accept')) { - headers.set('Accept', 'application/json'); - } - - // 针对 PostgREST 的额外处理 - if (endpoint.includes('evaluation_point_groups') && (options.method === 'POST' || options.method === 'PATCH')) { - // console.log('使用 PostgREST 特定配置处理请求'); - // 确保请求体是有效的 JSON 对象 - if (options.body && typeof options.body === 'string') { - try { - JSON.parse(options.body); // 验证 JSON 是否有效 - } catch (e) { - console.error('请求体不是有效的 JSON:', options.body); - throw new Error('请求体必须是有效的 JSON'); - } - } - } - - // console.log(`client.ts->发送 ${options.method || 'GET'} 请求到: ${decodeUrlForDisplay(url)}`); - if (options.body) { - // console.log(`client.ts->请求体: \n${options.body}`); - } - - // 发送请求,10秒超时 - const response = await fetchWithTimeout(url, { - ...options, - headers - }, 10000); - - // 解析响应 - let data = null; - let responseText = ''; - - try { - const contentType = response.headers.get('content-type'); - responseText = await response.text(); - - if (contentType && contentType.includes('application/json') && response.status !== 204 && responseText) { - try { - data = JSON.parse(responseText); - } catch (e) { - console.error('响应不是有效的 JSON:', responseText); - throw new Error('服务器返回无效的 JSON 响应'); - } - } - } catch (e) { - console.error('解析响应失败:', e); - // console.log('原始响应:', responseText); - } - - // 收集响应头信息 - const responseHeaders: Record = {}; - response.headers.forEach((value, key) => { - responseHeaders[key] = value; - }); - - // 打印响应信息 - // console.log(`响应状态: ${response.status} ${response.statusText}`); - // console.log(`响应头:`, responseHeaders); - // console.log(`响应体:`, data || responseText); - - // 检查 PostgREST 特定错误 - if (!response.ok) { - if (response.status === 400) { - console.error('PostgREST 错误 - 无效请求:', data || responseText); - return { - error: data?.message || data?.msg || '无效的请求格式,请检查数据格式是否正确', - status: response.status, - headers: responseHeaders - }; - } else if (response.status === 401) { - console.error('PostgREST 错误 - 未授权:', data || responseText); - return { - error: data?.message || data?.msg || '未授权,请检查认证信息', - status: response.status, - headers: responseHeaders - }; - } else if (response.status === 403) { - console.error('PostgREST 错误 - 禁止访问:', data || responseText); - return { - error: data?.message || data?.msg || '没有权限执行此操作', - status: response.status, - headers: responseHeaders - }; - } else if (response.status === 404) { - console.error('PostgREST 错误 - 资源不存在:', data || responseText); - return { - error: data?.message || data?.msg || '请求的资源不存在', - status: response.status, - headers: responseHeaders - }; - } else { - console.error(`HTTP请求失败: ${response.status} - ${url}`, data || responseText); - return { - error: data?.message || data?.msg || `请求失败: ${response.status}`, - status: response.status, - headers: responseHeaders - }; - } - } - - // 检查API返回的状态码 - if (data && 'code' in data && data.code !== 0) { - console.error(`API请求失败: ${data.message || data.msg || '未知错误'} - ${url}`); - return { - error: data.message || data.msg || '请求失败', - status: response.status, - headers: responseHeaders - }; - } - - return { - data, - status: response.status, - headers: responseHeaders - }; - } catch (error) { - console.error('API请求失败:', error); - - // 如果超时或网络错误,使用模拟数据(仅开发环境) - if (process.env.NODE_ENV !== 'production') { - console.warn('自动使用模拟数据作为回退'); - return getMockResponse(endpoint); - } - - return { - error: error instanceof Error ? error.message : '未知错误', - status: 500 - }; - } -} \ No newline at end of file diff --git a/app/api/postgrest-client.ts b/app/api/postgrest-client.ts index 8e71d75..93d984f 100644 --- a/app/api/postgrest-client.ts +++ b/app/api/postgrest-client.ts @@ -6,14 +6,22 @@ import { handleApiError } from './error-handler'; * PostgresREST 特定的查询参数接口 */ export interface PostgrestParams { + // 查询参数 select?: string; + // 排序参数 order?: string; + // 分页参数 limit?: number; offset?: number; + // 过滤参数 filter?: Record; - schema?: string; // 指定 PostgreSQL schema - or?: Array> | string; // 支持OR条件查询 - [key: string]: unknown; // 允许添加其他参数 + // 指定 PostgreSQL schema + schema?: string; + // 支持OR条件查询 + or?: Array> | string; + // 其他参数 + [key: string]: unknown; + // 自定义头部参数 headers?: Record; } @@ -45,8 +53,9 @@ function decodeUrlForDisplay(url: string): string { * @param endpoint 端点 * @param params 参数 * @param method HTTP 方法 + * @param data 请求体数据(用于POST/PATCH请求) */ -function logPostgrestQuery(endpoint: string, params?: QueryParams, method: string = 'GET'): void { +function logPostgrestQuery(endpoint: string, params?: QueryParams, method: string = 'GET', data?: Record): void { if (process.env.NODE_ENV !== 'production') { // const baseUrl = 'http://172.16.0.119:9000/admin'; // const baseUrl = 'http://172.18.0.100:3000'; @@ -55,22 +64,37 @@ function logPostgrestQuery(endpoint: string, params?: QueryParams, method: strin // 确保 endpoint 格式正确 const normalizedEndpoint = endpoint.startsWith('/') ? endpoint.substring(1) : endpoint; - // console.log('\n📦 PostgREST 查询日志 ======================start============='); - // console.log(`📦 HTTP 方法: ${method}`); - // console.log(`📦 API 端点: ${decodeUrlForDisplay(`${baseUrl}/${normalizedEndpoint}`)}`); + let fullUrl = `${baseUrl}/${normalizedEndpoint}`; - // if (params && Object.keys(params).length > 0) { - // console.log('📦 查询参数:'); + // 如果有查询参数,将其添加到URL中 + if (params && Object.keys(params).length > 0) { + const queryString = Object.entries(params) + .filter(([, value]) => value !== undefined) + .map(([key, value]) => { + // 处理特殊的PostgREST操作符(如eq, gt, lt等) + if (typeof value === 'string' && value.includes('.')) { + return `${key}=${encodeURIComponent(value)}`; + } + return `${key}=${encodeURIComponent(String(value))}`; + }) + .join('&'); - // // 以可读格式单独打印每个参数 - // Object.entries(params).forEach(([key, value]) => { - // if (value !== undefined) { - // console.log(` - ${key}: ${JSON.stringify(value)}`); - // } - // }); - // } + if (queryString) { + fullUrl += `?${queryString}`; + } + } - // console.log('PostgREST 查询日志=============================end============\n'); + console.log('\n📦 PostgREST 查询日志 ========================'); + console.log(`📦 HTTP 方法: ${method}`); + console.log(`📦 完整 URL: ${decodeUrlForDisplay(fullUrl)}`); + + // 打印请求体数据(仅适用于POST/PATCH/PUT请求) + if (['POST', 'PATCH', 'PUT'].includes(method) && data) { + console.log('📦 请求体数据:'); + console.log(JSON.stringify(data, null, 2)); + } + + console.log('📦 PostgREST 查询日志 ========================\n'); } } @@ -297,12 +321,12 @@ export async function postgrestPost>(endpoint: st // 确保端点没有前导斜杠 const apiEndpoint = endpoint.startsWith('/') ? endpoint.substring(1) : endpoint; - // 打印查询信息(POST请求只打印端点) - logPostgrestQuery(apiEndpoint, undefined, 'POST'); - // 预处理数据,确保所有字段类型符合 PostgreSQL 要求 const processedData = preprocessData(data as Record); + // 打印查询信息 + logPostgrestQuery(apiEndpoint, undefined, 'POST', processedData); + // 确保数据是合法的JSON对象 const requestBody = JSON.stringify(processedData); // console.log(`准备发送 PostgreSQL 插入请求到: ${apiEndpoint}`); @@ -422,16 +446,18 @@ export async function postgrestPut( const apiEndpoint = endpoint.startsWith('/') ? endpoint.substring(1) : endpoint; // 构建完整的URL,包含过滤条件 - let fullEndpoint = apiEndpoint; + const fullEndpoint = apiEndpoint; + const queryParams: QueryParams = {}; + if (filters) { - const filterString = Object.entries(filters) - .map(([key, value]) => `${key}=eq.${value}`) - .join('&'); - fullEndpoint = `${apiEndpoint}?${filterString}`; + // 将过滤条件转换为PostgREST格式的查询参数 + Object.entries(filters).forEach(([key, value]) => { + queryParams[key] = `eq.${value}`; + }); } - // 打印查询信息(PUT请求只打印端点) - logPostgrestQuery(fullEndpoint, undefined, 'PATCH'); + // 打印查询信息 + logPostgrestQuery(fullEndpoint, queryParams, 'PATCH', data as unknown as Record); const response = await apiRequest( fullEndpoint, diff --git a/app/components/reviews/ReviewTabs.tsx b/app/components/reviews/ReviewTabs.tsx index af7fa0b..4462b77 100644 --- a/app/components/reviews/ReviewTabs.tsx +++ b/app/components/reviews/ReviewTabs.tsx @@ -5,7 +5,7 @@ import { ReactNode, useState } from 'react'; import { useNavigate } from 'react-router-dom'; import { loadingBarService } from '~/components/ui/LoadingBar'; -import { DOCUMENT_URL } from "~/api/client"; +import { DOCUMENT_URL } from "~/api/axios-client"; interface ReviewTabsProps { activeTab: string; diff --git a/app/routes/documents._index.tsx b/app/routes/documents._index.tsx index cc3723b..3e12dfc 100644 --- a/app/routes/documents._index.tsx +++ b/app/routes/documents._index.tsx @@ -16,7 +16,7 @@ import { updateDocumentAuditStatus } from "~/api/evaluation_points/rules-files"; import { toastService } from "~/components/ui/Toast"; import { messageService } from "~/components/ui/MessageModal"; import { loadingBarService } from "~/components/ui/LoadingBar"; -import { DOCUMENT_URL } from "~/api/client"; +import { DOCUMENT_URL } from "~/api/axios-client"; // 导入样式 export function links() { diff --git a/app/routes/documents.edit.tsx b/app/routes/documents.edit.tsx index 46fe5d4..366151d 100644 --- a/app/routes/documents.edit.tsx +++ b/app/routes/documents.edit.tsx @@ -9,7 +9,7 @@ import { getDocumentTypes } from "~/api/document-types/document-types"; import { FileTag } from "~/components/ui/FileTag"; import { toastService } from "~/components/ui/Toast"; import { Document, Page , pdfjs } from "react-pdf"; -import { DOCUMENT_URL } from "~/api/client"; +import { DOCUMENT_URL } from "~/api/axios-client"; pdfjs.GlobalWorkerOptions.workerSrc = '/pdf.worker.js';