16 KiB
16 KiB
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} |
请求示例
GET /api/v3/review-points/123 HTTP/1.1
Host: your-api-domain.com
Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...
📤 响应数据
成功响应 (HTTP 200)
interface GetReviewPointsResponse {
data: ReviewPointResult[]; // 评查点结果列表
stats: StatsData; // 统计数据
reviewInfo: ReviewInfo; // 评查信息
document: DocumentData; // 文档数据
comparison_document: ComparisonDoc; // 合同结构比对数据
scoring_proposals: ScoringProposal[]; // 评分提案
}
📊 数据结构详解
1. ReviewPointResult(评查点结果)
每个评查点结果包含以下字段:
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(统计数据)
interface StatsData {
total: number; // 总评查点数量
success: number; // 通过数量
warning: number; // 警告数量
error: number; // 错误数量
score: number; // 总分数
}
3. ReviewInfo(评查信息)
interface ReviewInfo {
reviewTime: string; // 评查时间(YYYY-MM-DD HH:mm:ss)
reviewModel: string; // 评查模型(固定 "DeepSeek")
ruleGroup: string; // 规则组名称(用顿号分隔)
result: string; // 总体结果:success/warning
issueCount: number; // 问题数量
}
4. DocumentData(文档数据)
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(合同结构比对数据)
interface ComparisonDoc {
id?: string | number;
document_id?: string | number;
template_contract_path: string; // 模板文件路径
comparison_results?: object; // 比对结果
}
注意:当无比对数据时,返回 { template_contract_path: "" }
6. ScoringProposal(评分提案)
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;
}
📝 完整响应示例
{
"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
}
]
}
❌ 错误响应
错误响应格式
interface ErrorResponse {
error: string; // 错误消息
status: number; // HTTP状态码
}
错误码说明
| HTTP状态码 | error 消息 | 说明 |
|---|---|---|
| 401 | 用户身份验证失败 | Token无效或过期 |
| 403 | 无权访问此文档 | 用户无权限查看该文档 |
| 404 | 文档不存在 | document_id对应的文档不存在 |
| 500 | 获取评查点结果失败: xxx | 服务器内部错误 |
错误响应示例
{
"error": "无权访问此文档",
"status": 403
}
🔄 前端迁移指南
迁移前(直接调用 PostgREST)
// 原来需要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', {...});
// 然后在前端组装数据...
迁移后(调用后端接口)
// 现在只需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 示例
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 示例
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 <Loading />;
if (error) return <Error message={error.message} />;
return (
<div>
<Stats data={data.stats} />
<ReviewInfo data={data.reviewInfo} />
<ReviewPointsList data={data.data} />
</div>
);
}
Remix Loader 示例
// 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<typeof loader>();
// 直接使用,数据格式与原来完全一致
return (
<div>
{/* 统计信息 */}
<div>
<p>总计: {stats.total}</p>
<p>通过: {stats.success}</p>
<p>警告: {stats.warning}</p>
<p>错误: {stats.error}</p>
<p>总分: {stats.score}</p>
</div>
{/* 评查信息 */}
<div>
<p>评查时间: {reviewInfo.reviewTime}</p>
<p>评查模型: {reviewInfo.reviewModel}</p>
<p>规则组: {reviewInfo.ruleGroup}</p>
<p>总体结果: {reviewInfo.result}</p>
<p>问题数量: {reviewInfo.issueCount}</p>
</div>
{/* 评查点列表 */}
{data.map(point => (
<div key={point.id}>
<h3>{point.title}</h3>
<p>状态: {point.status}</p>
<p>建议: {point.suggestion}</p>
</div>
))}
</div>
);
}
✅ 迁移检查清单
- 更新 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: 返回空数据结构:
{
"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 维护者:后端开发团队