Files
leaudit-platform-frontend/auth_doc/review_points_api_frontend.md
2025-12-05 00:09:32 +08:00

626 lines
16 KiB
Markdown
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# getReviewPoints 接口对接文档
> 面向前端开发的 API 对接指南
## 📋 概述
本接口用于替代前端直接调用 PostgREST 的 `getReviewPoints` 方法,**数据格式与原实现完全一致**,前端只需更改调用地址即可完成迁移。
---
## 🔗 接口信息
| 项目 | 内容 |
|-----|------|
| **接口地址** | `GET /api/v3/review-points/{document_id}` |
| **请求方式** | GET |
| **认证方式** | JWT TokenAuthorization: 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 <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 示例
```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<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**: 返回空数据结构:
```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
**维护者**:后端开发团队