# getReviewPoints 接口对接文档 > 面向前端开发的 API 对接指南 ## 📋 概述 本接口用于替代前端直接调用 PostgREST 的 `getReviewPoints` 方法,**数据格式与原实现完全一致**,前端只需更改调用地址即可完成迁移。 --- ## 🔗 接口信息 | 项目 | 内容 | |-----|------| | **接口地址** | `GET /api/v3/review-points/{document_id}` | | **请求方式** | GET | | **认证方式** | JWT Token(Authorization: Bearer xxx) | | **Content-Type** | application/json | --- ## 📥 请求参数 ### 路径参数 | 参数名 | 类型 | 必填 | 说明 | |-------|------|-----|------| | `document_id` | number | ✅ | 文档ID,必须大于0 | ### 请求头 | Header | 必填 | 说明 | |--------|-----|------| | `Authorization` | ✅ | Bearer Token,格式:`Bearer {JWT_TOKEN}` | ### 请求示例 ```http GET /api/v3/review-points/123 HTTP/1.1 Host: your-api-domain.com Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9... ``` --- ## 📤 响应数据 ### 成功响应 (HTTP 200) ```typescript interface GetReviewPointsResponse { data: ReviewPointResult[]; // 评查点结果列表 stats: StatsData; // 统计数据 reviewInfo: ReviewInfo; // 评查信息 document: DocumentData; // 文档数据 comparison_document: ComparisonDoc; // 合同结构比对数据 scoring_proposals: ScoringProposal[]; // 评分提案 } ``` --- ## 📊 数据结构详解 ### 1. ReviewPointResult(评查点结果) 每个评查点结果包含以下字段: ```typescript interface ReviewPointResult { // ===== 基本信息 ===== id: string | number; // 评查结果ID documentId: string; // 文档ID pointId: string | number; // 评查点ID // ===== 审核状态 ===== editAuditStatusId: string | number; // 审核状态ID editAuditStatus: number; // 审核状态:0-待审核,1-已审核 editAuditStatusMessage: string; // 审核意见 // ===== 显示信息 ===== title: string; // 评查点标题/问题描述 pointName: string; // 评查点名称 groupName: string; // 评查点组名称 status: string; // 评查状态:success/warning/error // ===== 内容数据 ===== content: { // 评查内容(原始数据) [key: string]: { page: number | string; value: string; } }; contentPage: { // 页码映射 [key: string]: string; }; // ===== 建议和配置 ===== suggestion: string; // 建议内容 postAction: string; // 后续动作:manual/auto/none actionContent: string | object; // 动作配置 legalBasis: object; // 法律依据 evaluationConfig: object; // 评查配置 // ===== 评分信息 ===== score: number; // 满分分数 finalScore: number | null; // 最终得分(交叉评查) machineScore: number | null; // 机器评分 result: boolean; // 评查结果:true-通过/false-不通过 // ===== 交叉评查 ===== failMessage: string; // 不通过提示 passMessage: string; // 通过提示 // ===== 日志 ===== evaluatedPointResultsLog: { // 规则执行日志 rules?: Array<{ id: string; type: string; res: boolean; config: object; }>; }; } ``` ### 2. StatsData(统计数据) ```typescript interface StatsData { total: number; // 总评查点数量 success: number; // 通过数量 warning: number; // 警告数量 error: number; // 错误数量 score: number; // 总分数 } ``` ### 3. ReviewInfo(评查信息) ```typescript interface ReviewInfo { reviewTime: string; // 评查时间(YYYY-MM-DD HH:mm:ss) reviewModel: string; // 评查模型(固定 "DeepSeek") ruleGroup: string; // 规则组名称(用顿号分隔) result: string; // 总体结果:success/warning issueCount: number; // 问题数量 } ``` ### 4. DocumentData(文档数据) ```typescript interface DocumentData { id: string | number; name: string; // 文档名称 path: string; // 文档存储路径 user_id: string | number; // 上传用户ID document_type_id: string | number; // 文档类型ID audit_status: number; // 审核状态 created_at: string; // 创建时间 updated_at: string; // 更新时间 ocrResult: { // OCR结果 ocr_result: { [表单名称: string]: { pages: number[]; } } }; // ... 其他文档字段 } ``` ### 5. ComparisonDoc(合同结构比对数据) ```typescript interface ComparisonDoc { id?: string | number; document_id?: string | number; template_contract_path: string; // 模板文件路径 comparison_results?: object; // 比对结果 } ``` **注意**:当无比对数据时,返回 `{ template_contract_path: "" }` ### 6. ScoringProposal(评分提案) ```typescript interface ScoringProposal { id: string | number; evaluation_result_id: string | number; proposer_id: string | number; proposed_score: number; // 建议分数 reason: string; // 提议理由 status: string; // 提案状态 created_at: string; updated_at: string; document_id: string | number; } ``` --- ## 📝 完整响应示例 ```json { "data": [ { "id": 1001, "documentId": "123", "pointId": 5, "editAuditStatusId": "", "editAuditStatus": 0, "editAuditStatusMessage": "", "title": "立案报告表中的当事人信息与营业执照不一致", "pointName": "当事人信息一致性检查", "groupName": "合同形式要素", "status": "error", "content": { "立案报告表-当事人-单位-名称": { "page": 1, "value": "张三烟草店" }, "证据复制(提取)单-营业执照-名称": { "page": 4, "value": "李四烟草店" } }, "contentPage": { "立案报告表-当事人-单位-名称": "1", "证据复制(提取)单-营业执照-名称": "4" }, "suggestion": "请核对当事人信息,确保立案报告表与营业执照信息一致", "postAction": "manual", "actionContent": "", "legalBasis": { "name": "烟草专卖法", "articles": ["第三十七条", "第三十八条"] }, "evaluationConfig": { "type": "consistency", "compareMethod": "exact" }, "score": 5, "finalScore": null, "machineScore": 5, "result": false, "failMessage": "当事人信息不一致", "passMessage": "当事人信息一致", "evaluatedPointResultsLog": { "rules": [ { "id": "0", "type": "consistency", "res": false, "config": { "logic": "all", "pairs": [ { "sourceField": { "立案报告表-当事人-单位-名称": { "page": 1, "value": "张三烟草店" } }, "targetField": { "证据复制(提取)单-营业执照-名称": { "page": 4, "value": "李四烟草店" } }, "compareMethod": "exact", "res": false } ] } } ] } }, { "id": 1002, "documentId": "123", "pointId": 8, "editAuditStatusId": "201", "editAuditStatus": 1, "editAuditStatusMessage": "已人工审核确认", "title": "签名完整性检查通过", "pointName": "执法人员签名检查", "groupName": "程序合法性", "status": "success", "content": { "现场笔录-执法人员签名": { "page": 3, "value": "有" } }, "contentPage": { "现场笔录-执法人员签名": "3" }, "suggestion": "", "postAction": "manual", "actionContent": "", "legalBasis": {}, "evaluationConfig": {}, "score": 10, "finalScore": null, "machineScore": 10, "result": true, "failMessage": "", "passMessage": "签名完整", "evaluatedPointResultsLog": { "rules": [] } } ], "stats": { "total": 15, "success": 12, "warning": 2, "error": 1, "score": 100 }, "reviewInfo": { "reviewTime": "2024-01-15 10:30:00", "reviewModel": "DeepSeek", "ruleGroup": "合同形式要素、合同实质要素、程序合法性", "result": "warning", "issueCount": 3 }, "document": { "id": 123, "name": "烟草专卖案卷-示例.pdf", "path": "/uploads/2024/01/document123.pdf", "user_id": 6, "document_type_id": 1, "audit_status": 0, "created_at": "2024-01-15T09:00:00Z", "updated_at": "2024-01-15T10:30:00Z", "ocrResult": { "ocr_result": { "立案报告表": { "pages": [1] }, "现场笔录": { "pages": [2, 3] }, "证据复制(提取)单": { "pages": [4, 5] } } } }, "comparison_document": { "id": 45, "document_id": 123, "template_contract_path": "/templates/standard_contract.pdf", "comparison_results": { "合同封面": [ { "field": "合同名称", "status": "normal", "similarity": 1.0 } ] } }, "scoring_proposals": [ { "id": 301, "evaluation_result_id": 1001, "proposer_id": 12, "proposed_score": 3, "reason": "虽然信息不一致,但属于笔误,建议降低扣分", "status": "pending", "created_at": "2024-01-15T11:00:00Z", "updated_at": "2024-01-15T11:00:00Z", "document_id": 123 } ] } ``` --- ## ❌ 错误响应 ### 错误响应格式 ```typescript interface ErrorResponse { error: string; // 错误消息 status: number; // HTTP状态码 } ``` ### 错误码说明 | HTTP状态码 | error 消息 | 说明 | |-----------|-----------|------| | 401 | 用户身份验证失败 | Token无效或过期 | | 403 | 无权访问此文档 | 用户无权限查看该文档 | | 404 | 文档不存在 | document_id对应的文档不存在 | | 500 | 获取评查点结果失败: xxx | 服务器内部错误 | ### 错误响应示例 ```json { "error": "无权访问此文档", "status": 403 } ``` --- ## 🔄 前端迁移指南 ### 迁移前(直接调用 PostgREST) ```typescript // 原来需要7次API调用 const documentData = await getDocumentWithNoUserId(fileId, frontendJWT); const contractStructureComparisonData = await postgrestGet('contract_structure_comparison', {...}); const evaluationResultsData = await postgrestGet('evaluation_results', {...}); const evaluationPointsData = await postgrestGet('evaluation_points', {...}); const groupsData = await postgrestGet('evaluation_point_groups', {...}); const auditStatusData = await postgrestGet('audit_status', {...}); const scoringProposalsData = await postgrestGet('cross_scoring_proposals', {...}); // 然后在前端组装数据... ``` ### 迁移后(调用后端接口) ```typescript // 现在只需1次API调用 const response = await fetch(`/api/v3/review-points/${fileId}`, { method: 'GET', headers: { 'Authorization': `Bearer ${frontendJWT}`, 'Content-Type': 'application/json' } }); const result = await response.json(); if (response.ok) { // 直接使用,数据格式完全一致 const { data, stats, reviewInfo, document, comparison_document, scoring_proposals } = result; } else { // 错误处理 console.error(result.error); } ``` ### Axios 示例 ```typescript import axios from 'axios'; async function getReviewPoints(fileId: string, token: string) { try { const response = await axios.get(`/api/v3/review-points/${fileId}`, { headers: { 'Authorization': `Bearer ${token}` } }); return response.data; } catch (error) { if (axios.isAxiosError(error) && error.response) { // 处理错误响应 const { error: errorMsg, status } = error.response.data; throw new Error(errorMsg); } throw error; } } ``` ### React Query 示例 ```typescript import { useQuery } from '@tanstack/react-query'; function useReviewPoints(fileId: string) { return useQuery({ queryKey: ['reviewPoints', fileId], queryFn: async () => { const response = await fetch(`/api/v3/review-points/${fileId}`, { headers: { 'Authorization': `Bearer ${getToken()}`, } }); if (!response.ok) { const error = await response.json(); throw new Error(error.error); } return response.json(); }, enabled: !!fileId, }); } // 使用 function ReviewPointsPage({ fileId }) { const { data, isLoading, error } = useReviewPoints(fileId); if (isLoading) return ; if (error) return ; return (
); } ``` ### Remix Loader 示例 ```typescript // app/routes/review.$fileId.tsx import { json, LoaderFunctionArgs } from '@remix-run/node'; import { useLoaderData } from '@remix-run/react'; export async function loader({ params, request }: LoaderFunctionArgs) { const fileId = params.fileId; if (!fileId) { return json({ error: '文档ID不能为空' }, { status: 400 }); } // 获取JWT Token const token = await getJWTFromRequest(request); // 调用后端接口 const response = await fetch( `${process.env.API_BASE_URL}/api/v3/review-points/${fileId}`, { headers: { 'Authorization': `Bearer ${token}`, } } ); const result = await response.json(); if (!response.ok) { return json({ error: result.error }, { status: result.status || 500 }); } return json(result); } export default function ReviewPage() { const { data, stats, reviewInfo, document, comparison_document, scoring_proposals } = useLoaderData(); // 直接使用,数据格式与原来完全一致 return (
{/* 统计信息 */}

总计: {stats.total}

通过: {stats.success}

警告: {stats.warning}

错误: {stats.error}

总分: {stats.score}

{/* 评查信息 */}

评查时间: {reviewInfo.reviewTime}

评查模型: {reviewInfo.reviewModel}

规则组: {reviewInfo.ruleGroup}

总体结果: {reviewInfo.result}

问题数量: {reviewInfo.issueCount}

{/* 评查点列表 */} {data.map(point => (

{point.title}

状态: {point.status}

建议: {point.suggestion}

))}
); } ``` --- ## ✅ 迁移检查清单 - [ ] 更新 API 调用地址为 `/api/v3/review-points/{document_id}` - [ ] 确保 Authorization Header 携带有效的 JWT Token - [ ] 移除前端的多表查询和数据组装逻辑 - [ ] 更新错误处理逻辑以匹配新的错误响应格式 - [ ] 测试各种场景(正常、无评查结果、无权限、文档不存在) --- ## ❓ 常见问题 ### Q1: 数据格式会有变化吗? **A**: 不会。后端接口返回的数据格式与原前端 `getReviewPoints` 方法完全一致,包括字段名、数据类型、嵌套结构等。 ### Q2: 需要修改前端的数据处理逻辑吗? **A**: 不需要。只需将原来的多次 PostgREST 调用替换为单次后端 API 调用即可,返回数据可直接使用。 ### Q3: 性能会有影响吗? **A**: 会更好。原来前端需要发起 7 次 HTTP 请求,现在只需 1 次。后端使用批量查询优化,减少数据库往返次数。 ### Q4: 无评查结果时返回什么? **A**: 返回空数据结构: ```json { "data": [], "stats": { "total": 0, "success": 0, "warning": 0, "error": 0, "score": 0 }, "reviewInfo": { ... }, "document": { ... }, "comparison_document": { "template_contract_path": "" }, "scoring_proposals": [] } ``` ### Q5: 如何处理 Token 过期? **A**: 接口返回 401 状态码时,前端应引导用户重新登录或刷新 Token。 --- ## 📞 联系方式 如有问题,请联系后端开发人员。 --- **文档版本**:v1.0 **最后更新**:2025-11-26 **维护者**:后端开发团队