|
@@ -0,0 +1,1590 @@
|
|
|
+# 解析任务管理类API接口操作说明文档
|
|
|
+
|
|
|
+> **版本**: v1.0
|
|
|
+> **更新时间**: 2025-01-18
|
|
|
+> **适用对象**: 前端开发工程师
|
|
|
+
|
|
|
+## 📋 目录
|
|
|
+
|
|
|
+1. [接口概览](#接口概览)
|
|
|
+2. [通用说明](#通用说明)
|
|
|
+3. [接口详细说明](#接口详细说明)
|
|
|
+ - [1. 获取解析任务列表](#1-获取解析任务列表)
|
|
|
+ - [2. 获取解析任务详情](#2-获取解析任务详情)
|
|
|
+ - [3. 新增解析任务](#3-新增解析任务)
|
|
|
+ - [4. 执行解析任务](#4-执行解析任务)
|
|
|
+ - [5. 处理解析结果](#5-处理解析结果)
|
|
|
+4. [前端集成示例](#前端集成示例)
|
|
|
+5. [错误处理](#错误处理)
|
|
|
+6. [最佳实践](#最佳实践)
|
|
|
+
|
|
|
+---
|
|
|
+
|
|
|
+## 接口概览
|
|
|
+
|
|
|
+| 序号 | 接口名称 | HTTP方法 | 接口路径 | 功能描述 |
|
|
|
+|------|----------|----------|----------|----------|
|
|
|
+| 1 | 获取解析任务列表 | GET | `/api/data-parse/get-parse-tasks` | 分页查询解析任务列表,支持过滤 |
|
|
|
+| 2 | 获取解析任务详情 | GET | `/api/data-parse/get-parse-task-detail` | 根据任务名称获取详细信息 |
|
|
|
+| 3 | 新增解析任务 | POST | `/api/data-parse/add-parse-task` | 创建新的解析任务并上传文件 |
|
|
|
+| 4 | 执行解析任务 | POST | `/api/data-parse/execute-parse-task` | 执行指定的批量解析任务 |
|
|
|
+| 5 | 处理解析结果 | POST | `/api/data-parse/add-parsed-talents` | 将解析结果写入人才数据库 |
|
|
|
+
|
|
|
+---
|
|
|
+
|
|
|
+## 通用说明
|
|
|
+
|
|
|
+### 🌐 服务器信息
|
|
|
+- **开发环境**: `http://localhost:5000`
|
|
|
+- **生产环境**: `http://192.168.3.143:5000`
|
|
|
+- **API前缀**: `/api/data-parse`
|
|
|
+
|
|
|
+### 🔒 认证方式
|
|
|
+- 当前版本暂无认证要求
|
|
|
+- 后续版本可能需要Bearer Token
|
|
|
+
|
|
|
+### 📊 响应格式
|
|
|
+所有接口均返回统一的JSON格式:
|
|
|
+
|
|
|
+```json
|
|
|
+{
|
|
|
+ "success": boolean, // 请求是否成功
|
|
|
+ "message": string, // 响应消息
|
|
|
+ "data": object | null // 响应数据
|
|
|
+}
|
|
|
+```
|
|
|
+
|
|
|
+### 📋 任务类型说明
|
|
|
+
|
|
|
+| 任务类型 | 代码值 | 支持文件格式 | 存储目录 | 说明 |
|
|
|
+|----------|--------|--------------|----------|------|
|
|
|
+| 名片 | `"名片"` | JPG, PNG | `talent_photos/` | 名片图片解析 |
|
|
|
+| 简历 | `"简历"` | PDF | `resume_files/` | 简历文档解析 |
|
|
|
+| 新任命 | `"新任命"` | MD | `appointment_files/` | 任命文档解析 |
|
|
|
+| 招聘 | `"招聘"` | 无需文件 | 无 | 数据库记录处理 |
|
|
|
+| 杂项 | `"杂项"` | 任意格式 | `misc_files/` | 其他类型文件 |
|
|
|
+
|
|
|
+---
|
|
|
+
|
|
|
+## 接口详细说明
|
|
|
+
|
|
|
+### 1. 获取解析任务列表
|
|
|
+
|
|
|
+#### 📝 基本信息
|
|
|
+- **接口路径**: `GET /api/data-parse/get-parse-tasks`
|
|
|
+- **功能**: 分页查询解析任务列表,支持按任务类型和状态过滤
|
|
|
+- **内容类型**: `application/json`
|
|
|
+
|
|
|
+#### 📥 请求参数
|
|
|
+
|
|
|
+| 参数名 | 类型 | 必填 | 默认值 | 说明 | 示例值 |
|
|
|
+|--------|------|------|--------|------|--------|
|
|
|
+| `page` | integer | 否 | 1 | 页码,从1开始 | `1` |
|
|
|
+| `per_page` | integer | 否 | 10 | 每页记录数,最大100 | `20` |
|
|
|
+| `task_type` | string | 否 | - | 任务类型过滤 | `"名片"` |
|
|
|
+| `task_status` | string | 否 | - | 任务状态过滤 | `"待解析"` |
|
|
|
+
|
|
|
+#### 📤 响应结果
|
|
|
+
|
|
|
+```json
|
|
|
+{
|
|
|
+ "success": true,
|
|
|
+ "message": "获取解析任务列表成功",
|
|
|
+ "data": {
|
|
|
+ "tasks": [
|
|
|
+ {
|
|
|
+ "id": 123,
|
|
|
+ "task_name": "parse_task_20250118_a1b2c3d4",
|
|
|
+ "task_status": "待解析",
|
|
|
+ "task_type": "名片",
|
|
|
+ "task_source": {
|
|
|
+ "minio_paths_json": [
|
|
|
+ "talent_photos/talent_photo_20250118_143012_a1b2c3d4.jpg"
|
|
|
+ ],
|
|
|
+ "upload_time": "2025-01-18T14:30:25.123456"
|
|
|
+ },
|
|
|
+ "collection_count": 5,
|
|
|
+ "parse_count": 0,
|
|
|
+ "parse_result": null,
|
|
|
+ "created_at": "2025-01-18 14:30:25",
|
|
|
+ "created_by": "api_user",
|
|
|
+ "updated_at": "2025-01-18 14:30:25",
|
|
|
+ "updated_by": "api_user"
|
|
|
+ }
|
|
|
+ ],
|
|
|
+ "pagination": {
|
|
|
+ "page": 1,
|
|
|
+ "per_page": 10,
|
|
|
+ "total": 25,
|
|
|
+ "pages": 3,
|
|
|
+ "has_next": true,
|
|
|
+ "has_prev": false
|
|
|
+ }
|
|
|
+ }
|
|
|
+}
|
|
|
+```
|
|
|
+
|
|
|
+#### 🎯 前端调用示例
|
|
|
+
|
|
|
+```javascript
|
|
|
+// 使用fetch API
|
|
|
+async function getParseTaskList(page = 1, perPage = 10, taskType = null, taskStatus = null) {
|
|
|
+ const params = new URLSearchParams();
|
|
|
+ params.append('page', page);
|
|
|
+ params.append('per_page', perPage);
|
|
|
+ if (taskType) params.append('task_type', taskType);
|
|
|
+ if (taskStatus) params.append('task_status', taskStatus);
|
|
|
+
|
|
|
+ try {
|
|
|
+ const response = await fetch(`/api/data-parse/get-parse-tasks?${params}`, {
|
|
|
+ method: 'GET',
|
|
|
+ headers: {
|
|
|
+ 'Content-Type': 'application/json',
|
|
|
+ }
|
|
|
+ });
|
|
|
+
|
|
|
+ const result = await response.json();
|
|
|
+
|
|
|
+ if (result.success) {
|
|
|
+ console.log('任务列表获取成功:', result.data);
|
|
|
+ return result.data;
|
|
|
+ } else {
|
|
|
+ console.error('获取失败:', result.message);
|
|
|
+ throw new Error(result.message);
|
|
|
+ }
|
|
|
+ } catch (error) {
|
|
|
+ console.error('请求异常:', error);
|
|
|
+ throw error;
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+// 使用示例
|
|
|
+getParseTaskList(1, 20, '名片', '待解析')
|
|
|
+ .then(data => {
|
|
|
+ console.log('总任务数:', data.pagination.total);
|
|
|
+ console.log('任务列表:', data.tasks);
|
|
|
+ })
|
|
|
+ .catch(error => {
|
|
|
+ console.error('获取任务列表失败:', error);
|
|
|
+ });
|
|
|
+```
|
|
|
+
|
|
|
+#### 📊 状态码说明
|
|
|
+
|
|
|
+| HTTP状态码 | 业务状态 | 说明 |
|
|
|
+|------------|----------|------|
|
|
|
+| 200 | success: true | 查询成功 |
|
|
|
+| 400 | success: false | 请求参数错误 |
|
|
|
+| 500 | success: false | 服务器内部错误 |
|
|
|
+
|
|
|
+---
|
|
|
+
|
|
|
+### 2. 获取解析任务详情
|
|
|
+
|
|
|
+#### 📝 基本信息
|
|
|
+- **接口路径**: `GET /api/data-parse/get-parse-task-detail`
|
|
|
+- **功能**: 根据任务名称获取解析任务的详细信息
|
|
|
+- **内容类型**: `application/json`
|
|
|
+
|
|
|
+#### 📥 请求参数
|
|
|
+
|
|
|
+| 参数名 | 类型 | 必填 | 说明 | 示例值 |
|
|
|
+|--------|------|------|------|--------|
|
|
|
+| `task_name` | string | 是 | 任务名称 | `"parse_task_20250118_a1b2c3d4"` |
|
|
|
+
|
|
|
+#### 📤 响应结果
|
|
|
+
|
|
|
+```json
|
|
|
+{
|
|
|
+ "success": true,
|
|
|
+ "message": "成功获取任务 parse_task_20250118_a1b2c3d4 的详细信息",
|
|
|
+ "data": {
|
|
|
+ "id": 123,
|
|
|
+ "task_name": "parse_task_20250118_a1b2c3d4",
|
|
|
+ "task_status": "解析完成",
|
|
|
+ "task_type": "名片",
|
|
|
+ "task_source": {
|
|
|
+ "minio_paths_json": [
|
|
|
+ "talent_photos/talent_photo_20250118_143012_a1b2c3d4.jpg",
|
|
|
+ "talent_photos/talent_photo_20250118_143015_b2c3d4e5.jpg"
|
|
|
+ ],
|
|
|
+ "upload_time": "2025-01-18T14:30:25.123456"
|
|
|
+ },
|
|
|
+ "collection_count": 2,
|
|
|
+ "parse_count": 2,
|
|
|
+ "parse_result": {
|
|
|
+ "summary": {
|
|
|
+ "total_files": 2,
|
|
|
+ "success_count": 2,
|
|
|
+ "failed_count": 0,
|
|
|
+ "success_rate": 100.0
|
|
|
+ },
|
|
|
+ "results": [
|
|
|
+ {
|
|
|
+ "index": 0,
|
|
|
+ "success": true,
|
|
|
+ "data": {
|
|
|
+ "name_zh": "张三",
|
|
|
+ "title_zh": "技术总监",
|
|
|
+ "hotel_zh": "北京万豪酒店",
|
|
|
+ "mobile": "13800138000",
|
|
|
+ "email": "zhangsan@marriott.com"
|
|
|
+ }
|
|
|
+ }
|
|
|
+ ]
|
|
|
+ },
|
|
|
+ "created_at": "2025-01-18 14:30:25",
|
|
|
+ "created_by": "api_user",
|
|
|
+ "updated_at": "2025-01-18 15:45:30",
|
|
|
+ "updated_by": "system"
|
|
|
+ }
|
|
|
+}
|
|
|
+```
|
|
|
+
|
|
|
+#### 🎯 前端调用示例
|
|
|
+
|
|
|
+```javascript
|
|
|
+// 使用fetch API
|
|
|
+async function getParseTaskDetail(taskName) {
|
|
|
+ if (!taskName) {
|
|
|
+ throw new Error('任务名称不能为空');
|
|
|
+ }
|
|
|
+
|
|
|
+ const params = new URLSearchParams();
|
|
|
+ params.append('task_name', taskName);
|
|
|
+
|
|
|
+ try {
|
|
|
+ const response = await fetch(`/api/data-parse/get-parse-task-detail?${params}`, {
|
|
|
+ method: 'GET',
|
|
|
+ headers: {
|
|
|
+ 'Content-Type': 'application/json',
|
|
|
+ }
|
|
|
+ });
|
|
|
+
|
|
|
+ const result = await response.json();
|
|
|
+
|
|
|
+ if (result.success) {
|
|
|
+ console.log('任务详情获取成功:', result.data);
|
|
|
+ return result.data;
|
|
|
+ } else {
|
|
|
+ console.error('获取失败:', result.message);
|
|
|
+ throw new Error(result.message);
|
|
|
+ }
|
|
|
+ } catch (error) {
|
|
|
+ console.error('请求异常:', error);
|
|
|
+ throw error;
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+// 使用示例
|
|
|
+getParseTaskDetail('parse_task_20250118_a1b2c3d4')
|
|
|
+ .then(taskDetail => {
|
|
|
+ console.log('任务状态:', taskDetail.task_status);
|
|
|
+ console.log('解析结果:', taskDetail.parse_result);
|
|
|
+ })
|
|
|
+ .catch(error => {
|
|
|
+ console.error('获取任务详情失败:', error);
|
|
|
+ });
|
|
|
+```
|
|
|
+
|
|
|
+#### 📊 状态码说明
|
|
|
+
|
|
|
+| HTTP状态码 | 业务状态 | 说明 |
|
|
|
+|------------|----------|------|
|
|
|
+| 200 | success: true | 查询成功 |
|
|
|
+| 400 | success: false | 请求参数错误 |
|
|
|
+| 404 | success: false | 任务不存在 |
|
|
|
+| 500 | success: false | 服务器内部错误 |
|
|
|
+
|
|
|
+---
|
|
|
+
|
|
|
+### 3. 新增解析任务
|
|
|
+
|
|
|
+#### 📝 基本信息
|
|
|
+- **接口路径**: `POST /api/data-parse/add-parse-task`
|
|
|
+- **功能**: 创建新的解析任务并上传文件到MinIO存储
|
|
|
+- **内容类型**: `multipart/form-data`
|
|
|
+
|
|
|
+#### 📥 请求参数
|
|
|
+
|
|
|
+| 参数名 | 类型 | 必填 | 说明 | 示例值 |
|
|
|
+|--------|------|------|------|--------|
|
|
|
+| `task_type` | string | 是 | 任务类型 | `"名片"` |
|
|
|
+| `files` | File[] | 否* | 文件数组 | `[file1.jpg, file2.png]` |
|
|
|
+| `created_by` | string | 否 | 创建者 | `"frontend_user"` |
|
|
|
+
|
|
|
+> **注意**: 除"招聘"类型外,其他类型必须上传文件
|
|
|
+
|
|
|
+#### 📤 响应结果
|
|
|
+
|
|
|
+```json
|
|
|
+{
|
|
|
+ "success": true,
|
|
|
+ "message": "解析任务创建成功,所有文件上传完成",
|
|
|
+ "data": {
|
|
|
+ "task_info": {
|
|
|
+ "id": 124,
|
|
|
+ "task_name": "parse_task_20250118_b2c3d4e5",
|
|
|
+ "task_status": "待解析",
|
|
|
+ "task_type": "名片",
|
|
|
+ "task_source": {
|
|
|
+ "minio_paths_json": [
|
|
|
+ "talent_photos/talent_photo_20250118_143012_a1b2c3d4.jpg"
|
|
|
+ ],
|
|
|
+ "upload_time": "2025-01-18T14:30:25.123456"
|
|
|
+ },
|
|
|
+ "collection_count": 2,
|
|
|
+ "parse_count": 0,
|
|
|
+ "parse_result": null,
|
|
|
+ "created_by": "frontend_user",
|
|
|
+ "updated_by": "frontend_user",
|
|
|
+ "created_at": "2025-01-18 14:30:25",
|
|
|
+ "updated_at": "2025-01-18 14:30:25"
|
|
|
+ },
|
|
|
+ "upload_summary": {
|
|
|
+ "task_type": "名片",
|
|
|
+ "total_files": 2,
|
|
|
+ "uploaded_count": 2,
|
|
|
+ "failed_count": 0,
|
|
|
+ "uploaded_files": [
|
|
|
+ {
|
|
|
+ "original_filename": "business_card1.jpg",
|
|
|
+ "minio_path": "talent_photos/talent_photo_20250118_143012_a1b2c3d4.jpg",
|
|
|
+ "relative_path": "talent_photos/talent_photo_20250118_143012_a1b2c3d4.jpg",
|
|
|
+ "file_size": 256000
|
|
|
+ }
|
|
|
+ ],
|
|
|
+ "failed_uploads": []
|
|
|
+ }
|
|
|
+ }
|
|
|
+}
|
|
|
+```
|
|
|
+
|
|
|
+#### 🎯 前端调用示例
|
|
|
+
|
|
|
+```javascript
|
|
|
+// 使用fetch API和FormData
|
|
|
+async function createParseTask(taskType, files, createdBy = 'frontend_user') {
|
|
|
+ const formData = new FormData();
|
|
|
+
|
|
|
+ // 添加任务类型
|
|
|
+ formData.append('task_type', taskType);
|
|
|
+
|
|
|
+ // 添加创建者
|
|
|
+ formData.append('created_by', createdBy);
|
|
|
+
|
|
|
+ // 添加文件(如果有的话)
|
|
|
+ if (files && files.length > 0) {
|
|
|
+ for (let i = 0; i < files.length; i++) {
|
|
|
+ formData.append('files', files[i]);
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ try {
|
|
|
+ const response = await fetch('/api/data-parse/add-parse-task', {
|
|
|
+ method: 'POST',
|
|
|
+ body: formData
|
|
|
+ // 注意:不要设置Content-Type,让浏览器自动设置multipart/form-data
|
|
|
+ });
|
|
|
+
|
|
|
+ const result = await response.json();
|
|
|
+
|
|
|
+ if (result.success) {
|
|
|
+ console.log('任务创建成功:', result.data);
|
|
|
+ return result.data;
|
|
|
+ } else {
|
|
|
+ console.error('创建失败:', result.message);
|
|
|
+ throw new Error(result.message);
|
|
|
+ }
|
|
|
+ } catch (error) {
|
|
|
+ console.error('请求异常:', error);
|
|
|
+ throw error;
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+// HTML文件上传示例
|
|
|
+function setupFileUpload() {
|
|
|
+ const fileInput = document.getElementById('fileInput');
|
|
|
+ const taskTypeSelect = document.getElementById('taskTypeSelect');
|
|
|
+ const uploadBtn = document.getElementById('uploadBtn');
|
|
|
+
|
|
|
+ uploadBtn.addEventListener('click', async () => {
|
|
|
+ const taskType = taskTypeSelect.value;
|
|
|
+ const files = fileInput.files;
|
|
|
+
|
|
|
+ if (!taskType) {
|
|
|
+ alert('请选择任务类型');
|
|
|
+ return;
|
|
|
+ }
|
|
|
+
|
|
|
+ if (taskType !== '招聘' && files.length === 0) {
|
|
|
+ alert('请选择要上传的文件');
|
|
|
+ return;
|
|
|
+ }
|
|
|
+
|
|
|
+ try {
|
|
|
+ const result = await createParseTask(taskType, files);
|
|
|
+ alert(`任务创建成功!任务ID: ${result.task_info.id}`);
|
|
|
+
|
|
|
+ // 清空文件选择
|
|
|
+ fileInput.value = '';
|
|
|
+ } catch (error) {
|
|
|
+ alert(`任务创建失败: ${error.message}`);
|
|
|
+ }
|
|
|
+ });
|
|
|
+}
|
|
|
+
|
|
|
+// 页面加载完成后初始化
|
|
|
+document.addEventListener('DOMContentLoaded', setupFileUpload);
|
|
|
+```
|
|
|
+
|
|
|
+#### 🌐 HTML示例
|
|
|
+
|
|
|
+```html
|
|
|
+<!DOCTYPE html>
|
|
|
+<html>
|
|
|
+<head>
|
|
|
+ <title>创建解析任务</title>
|
|
|
+</head>
|
|
|
+<body>
|
|
|
+ <div>
|
|
|
+ <label for="taskTypeSelect">任务类型:</label>
|
|
|
+ <select id="taskTypeSelect">
|
|
|
+ <option value="">请选择任务类型</option>
|
|
|
+ <option value="名片">名片</option>
|
|
|
+ <option value="简历">简历</option>
|
|
|
+ <option value="新任命">新任命</option>
|
|
|
+ <option value="招聘">招聘</option>
|
|
|
+ <option value="杂项">杂项</option>
|
|
|
+ </select>
|
|
|
+ </div>
|
|
|
+
|
|
|
+ <div>
|
|
|
+ <label for="fileInput">选择文件:</label>
|
|
|
+ <input type="file" id="fileInput" multiple accept="image/*,.pdf,.md">
|
|
|
+ </div>
|
|
|
+
|
|
|
+ <button id="uploadBtn">创建任务</button>
|
|
|
+
|
|
|
+ <script src="parse-task.js"></script>
|
|
|
+</body>
|
|
|
+</html>
|
|
|
+```
|
|
|
+
|
|
|
+#### 📊 状态码说明
|
|
|
+
|
|
|
+| HTTP状态码 | 业务状态 | 说明 |
|
|
|
+|------------|----------|------|
|
|
|
+| 200 | success: true | 所有文件上传成功,任务创建成功 |
|
|
|
+| 206 | success: true | 部分文件上传成功,任务创建成功 |
|
|
|
+| 400 | success: false | 请求参数错误 |
|
|
|
+| 500 | success: false | 服务器内部错误 |
|
|
|
+
|
|
|
+---
|
|
|
+
|
|
|
+### 4. 执行解析任务
|
|
|
+
|
|
|
+#### 📝 基本信息
|
|
|
+- **接口路径**: `POST /api/data-parse/execute-parse-task`
|
|
|
+- **功能**: 根据任务类型执行相应的批量处理函数
|
|
|
+- **内容类型**: `application/json`
|
|
|
+
|
|
|
+#### 📥 请求参数
|
|
|
+
|
|
|
+| 参数名 | 类型 | 必填 | 说明 | 示例值 |
|
|
|
+|--------|------|------|------|--------|
|
|
|
+| `task_type` | string | 是 | 任务类型 | `"名片"` |
|
|
|
+| `data` | array | 是 | 数据列表,格式因任务类型而异 | `["path1.jpg", "path2.jpg"]` |
|
|
|
+| `publish_time` | string | 否* | 发布时间(仅新任命任务需要) | `"2025-01-18"` |
|
|
|
+| `process_type` | string | 否 | 处理类型(仅杂项任务) | `"table"` |
|
|
|
+| `id` | integer | 是 | 解析任务ID | `123` |
|
|
|
+
|
|
|
+> **注意**: 新任命任务必须提供`publish_time`参数
|
|
|
+
|
|
|
+#### 📋 不同任务类型的data格式
|
|
|
+
|
|
|
+| 任务类型 | data格式 | 示例 |
|
|
|
+|----------|----------|------|
|
|
|
+| 名片 | MinIO路径数组 | `["talent_photos/card1.jpg", "talent_photos/card2.jpg"]` |
|
|
|
+| 简历 | PDF文件路径数组 | `["resume_files/resume1.pdf", "resume_files/resume2.pdf"]` |
|
|
|
+| 新任命 | Markdown文件路径数组 | `["appointment_files/appointment1.md"]` |
|
|
|
+| 招聘 | 招聘数据对象数组 | `[{"name": "张三", "position": "经理"}]` |
|
|
|
+| 杂项 | 文件路径数组 | `["misc_files/file1.xlsx", "misc_files/file2.docx"]` |
|
|
|
+
|
|
|
+#### 📤 响应结果
|
|
|
+
|
|
|
+```json
|
|
|
+{
|
|
|
+ "success": true,
|
|
|
+ "message": "批量处理完成,全部 2 个文件处理成功",
|
|
|
+ "data": {
|
|
|
+ "summary": {
|
|
|
+ "total_files": 2,
|
|
|
+ "success_count": 2,
|
|
|
+ "failed_count": 0,
|
|
|
+ "success_rate": 100.0
|
|
|
+ },
|
|
|
+ "results": [
|
|
|
+ {
|
|
|
+ "index": 0,
|
|
|
+ "filename": "card1.jpg",
|
|
|
+ "success": true,
|
|
|
+ "error": null,
|
|
|
+ "data": {
|
|
|
+ "name_zh": "张三",
|
|
|
+ "title_zh": "技术总监",
|
|
|
+ "hotel_zh": "北京万豪酒店",
|
|
|
+ "mobile": "13800138000",
|
|
|
+ "email": "zhangsan@marriott.com",
|
|
|
+ "career_path": [
|
|
|
+ {
|
|
|
+ "date": "2025-01-18",
|
|
|
+ "hotel_zh": "北京万豪酒店",
|
|
|
+ "title_zh": "技术总监",
|
|
|
+ "source": "business_card"
|
|
|
+ }
|
|
|
+ ]
|
|
|
+ },
|
|
|
+ "message": "处理成功"
|
|
|
+ }
|
|
|
+ ],
|
|
|
+ "processed_time": "2025-01-18T15:30:45.123456"
|
|
|
+ }
|
|
|
+}
|
|
|
+```
|
|
|
+
|
|
|
+#### 🎯 前端调用示例
|
|
|
+
|
|
|
+```javascript
|
|
|
+// 执行解析任务
|
|
|
+async function executeParseTask(taskType, data, taskId, publishTime = null, processType = 'table') {
|
|
|
+ const requestBody = {
|
|
|
+ task_type: taskType,
|
|
|
+ data: data,
|
|
|
+ id: taskId
|
|
|
+ };
|
|
|
+
|
|
|
+ // 新任命任务需要发布时间
|
|
|
+ if (taskType === '新任命' && publishTime) {
|
|
|
+ requestBody.publish_time = publishTime;
|
|
|
+ }
|
|
|
+
|
|
|
+ // 杂项任务可以指定处理类型
|
|
|
+ if (taskType === '杂项') {
|
|
|
+ requestBody.process_type = processType;
|
|
|
+ }
|
|
|
+
|
|
|
+ try {
|
|
|
+ const response = await fetch('/api/data-parse/execute-parse-task', {
|
|
|
+ method: 'POST',
|
|
|
+ headers: {
|
|
|
+ 'Content-Type': 'application/json',
|
|
|
+ },
|
|
|
+ body: JSON.stringify(requestBody)
|
|
|
+ });
|
|
|
+
|
|
|
+ const result = await response.json();
|
|
|
+
|
|
|
+ if (result.success) {
|
|
|
+ console.log('解析任务执行成功:', result.data);
|
|
|
+ return result.data;
|
|
|
+ } else {
|
|
|
+ console.error('执行失败:', result.message);
|
|
|
+ throw new Error(result.message);
|
|
|
+ }
|
|
|
+ } catch (error) {
|
|
|
+ console.error('请求异常:', error);
|
|
|
+ throw error;
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+// 使用示例
|
|
|
+const taskData = [
|
|
|
+ "talent_photos/talent_photo_20250118_143012_a1b2c3d4.jpg",
|
|
|
+ "talent_photos/talent_photo_20250118_143015_b2c3d4e5.jpg"
|
|
|
+];
|
|
|
+
|
|
|
+executeParseTask('名片', taskData, 123)
|
|
|
+ .then(result => {
|
|
|
+ console.log('处理结果:', result);
|
|
|
+
|
|
|
+ // 检查处理统计
|
|
|
+ const summary = result.summary;
|
|
|
+ console.log(`总共${summary.total_files}个文件,成功${summary.success_count}个,失败${summary.failed_count}个`);
|
|
|
+
|
|
|
+ // 处理每个结果
|
|
|
+ result.results.forEach((item, index) => {
|
|
|
+ if (item.success) {
|
|
|
+ console.log(`第${index + 1}个文件处理成功:`, item.data);
|
|
|
+ } else {
|
|
|
+ console.error(`第${index + 1}个文件处理失败:`, item.error);
|
|
|
+ }
|
|
|
+ });
|
|
|
+ })
|
|
|
+ .catch(error => {
|
|
|
+ console.error('解析任务执行失败:', error);
|
|
|
+ });
|
|
|
+
|
|
|
+// 新任命任务示例
|
|
|
+const appointmentData = ["appointment_files/appointment_20250118.md"];
|
|
|
+executeParseTask('新任命', appointmentData, 124, '2025-01-18')
|
|
|
+ .then(result => {
|
|
|
+ console.log('新任命解析完成:', result);
|
|
|
+ })
|
|
|
+ .catch(error => {
|
|
|
+ console.error('新任命解析失败:', error);
|
|
|
+ });
|
|
|
+```
|
|
|
+
|
|
|
+#### 📊 状态码说明
|
|
|
+
|
|
|
+| HTTP状态码 | 业务状态 | 说明 |
|
|
|
+|------------|----------|------|
|
|
|
+| 200 | success: true | 全部处理成功 |
|
|
|
+| 206 | success: true | 部分处理成功 |
|
|
|
+| 400 | success: false | 请求参数错误 |
|
|
|
+| 500 | success: false | 服务器内部错误 |
|
|
|
+
|
|
|
+---
|
|
|
+
|
|
|
+### 5. 处理解析结果
|
|
|
+
|
|
|
+#### 📝 基本信息
|
|
|
+- **接口路径**: `POST /api/data-parse/add-parsed-talents`
|
|
|
+- **功能**: 处理execute-parse-task API的响应数据,将人才信息写入business_cards表
|
|
|
+- **内容类型**: `application/json`
|
|
|
+
|
|
|
+#### 📥 请求参数
|
|
|
+
|
|
|
+请求体为execute-parse-task API的完整返回数据:
|
|
|
+
|
|
|
+```json
|
|
|
+{
|
|
|
+ "success": true,
|
|
|
+ "message": "处理完成",
|
|
|
+ "data": {
|
|
|
+ "summary": {
|
|
|
+ "total_files": 5,
|
|
|
+ "success_count": 4,
|
|
|
+ "failed_count": 1,
|
|
|
+ "success_rate": 80.0
|
|
|
+ },
|
|
|
+ "results": [
|
|
|
+ {
|
|
|
+ "index": 0,
|
|
|
+ "success": true,
|
|
|
+ "data": {
|
|
|
+ "name_zh": "张三",
|
|
|
+ "title_zh": "经理",
|
|
|
+ "hotel_zh": "某酒店",
|
|
|
+ "image_path": "talent_photos/image1.jpg"
|
|
|
+ }
|
|
|
+ }
|
|
|
+ ],
|
|
|
+ "processed_time": "2025-01-18T10:30:00"
|
|
|
+ }
|
|
|
+}
|
|
|
+```
|
|
|
+
|
|
|
+#### 📤 响应结果
|
|
|
+
|
|
|
+```json
|
|
|
+{
|
|
|
+ "success": true,
|
|
|
+ "message": "批量处理完成,全部 4 条人才数据写入成功",
|
|
|
+ "data": {
|
|
|
+ "summary": {
|
|
|
+ "total_files": 4,
|
|
|
+ "success_count": 4,
|
|
|
+ "failed_count": 0,
|
|
|
+ "success_rate": 100.0,
|
|
|
+ "original_summary": {
|
|
|
+ "total_files": 5,
|
|
|
+ "success_count": 4,
|
|
|
+ "failed_count": 1,
|
|
|
+ "success_rate": 80.0
|
|
|
+ }
|
|
|
+ },
|
|
|
+ "results": [
|
|
|
+ {
|
|
|
+ "index": 0,
|
|
|
+ "original_index": 0,
|
|
|
+ "success": true,
|
|
|
+ "error": null,
|
|
|
+ "data": {
|
|
|
+ "id": 1001,
|
|
|
+ "name_zh": "张三",
|
|
|
+ "action": "created",
|
|
|
+ "message": "成功创建新的人才记录"
|
|
|
+ },
|
|
|
+ "message": "成功处理人员: 张三"
|
|
|
+ }
|
|
|
+ ],
|
|
|
+ "processed_time": "2025-01-18T16:45:30.789012",
|
|
|
+ "original_api_response": {
|
|
|
+ // 保留原始API响应数据用于调试
|
|
|
+ }
|
|
|
+ }
|
|
|
+}
|
|
|
+```
|
|
|
+
|
|
|
+#### 🎯 前端调用示例
|
|
|
+
|
|
|
+```javascript
|
|
|
+// 处理解析结果
|
|
|
+async function processParseResults(executeTaskResponse) {
|
|
|
+ if (!executeTaskResponse || typeof executeTaskResponse !== 'object') {
|
|
|
+ throw new Error('API响应数据格式错误');
|
|
|
+ }
|
|
|
+
|
|
|
+ try {
|
|
|
+ const response = await fetch('/api/data-parse/add-parsed-talents', {
|
|
|
+ method: 'POST',
|
|
|
+ headers: {
|
|
|
+ 'Content-Type': 'application/json',
|
|
|
+ },
|
|
|
+ body: JSON.stringify(executeTaskResponse)
|
|
|
+ });
|
|
|
+
|
|
|
+ const result = await response.json();
|
|
|
+
|
|
|
+ if (result.success) {
|
|
|
+ console.log('解析结果处理成功:', result.data);
|
|
|
+ return result.data;
|
|
|
+ } else {
|
|
|
+ console.error('处理失败:', result.message);
|
|
|
+ throw new Error(result.message);
|
|
|
+ }
|
|
|
+ } catch (error) {
|
|
|
+ console.error('请求异常:', error);
|
|
|
+ throw error;
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+// 完整的解析流程示例
|
|
|
+async function completeParseWorkflow(taskType, data, taskId, publishTime = null) {
|
|
|
+ try {
|
|
|
+ // 第一步:执行解析任务
|
|
|
+ console.log('步骤1: 执行解析任务...');
|
|
|
+ const executeResult = await executeParseTask(taskType, data, taskId, publishTime);
|
|
|
+
|
|
|
+ // 第二步:处理解析结果
|
|
|
+ console.log('步骤2: 处理解析结果...');
|
|
|
+ const processResult = await processParseResults(executeResult);
|
|
|
+
|
|
|
+ // 第三步:显示最终结果
|
|
|
+ console.log('解析流程完成!');
|
|
|
+ console.log('原始解析统计:', executeResult.summary);
|
|
|
+ console.log('数据写入统计:', processResult.summary);
|
|
|
+
|
|
|
+ return {
|
|
|
+ executeResult,
|
|
|
+ processResult
|
|
|
+ };
|
|
|
+
|
|
|
+ } catch (error) {
|
|
|
+ console.error('解析流程失败:', error);
|
|
|
+ throw error;
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+// 使用示例
|
|
|
+const taskData = ["talent_photos/card1.jpg", "talent_photos/card2.jpg"];
|
|
|
+
|
|
|
+completeParseWorkflow('名片', taskData, 123)
|
|
|
+ .then(results => {
|
|
|
+ const { executeResult, processResult } = results;
|
|
|
+
|
|
|
+ // 显示处理统计
|
|
|
+ console.log(`原始解析: ${executeResult.summary.success_count}/${executeResult.summary.total_files} 成功`);
|
|
|
+ console.log(`数据写入: ${processResult.summary.success_count}/${processResult.summary.total_files} 成功`);
|
|
|
+
|
|
|
+ // 显示详细结果
|
|
|
+ processResult.results.forEach((item, index) => {
|
|
|
+ if (item.success) {
|
|
|
+ console.log(`人员${index + 1}: ${item.message}`);
|
|
|
+ } else {
|
|
|
+ console.error(`人员${index + 1}: ${item.error}`);
|
|
|
+ }
|
|
|
+ });
|
|
|
+ })
|
|
|
+ .catch(error => {
|
|
|
+ console.error('完整解析流程失败:', error);
|
|
|
+ });
|
|
|
+```
|
|
|
+
|
|
|
+#### 📊 状态码说明
|
|
|
+
|
|
|
+| HTTP状态码 | 业务状态 | 说明 |
|
|
|
+|------------|----------|------|
|
|
|
+| 200 | success: true | 全部处理成功 |
|
|
|
+| 206 | success: true | 部分处理成功 |
|
|
|
+| 400 | success: false | 请求参数错误 |
|
|
|
+| 500 | success: false | 服务器内部错误 |
|
|
|
+
|
|
|
+---
|
|
|
+
|
|
|
+## 前端集成示例
|
|
|
+
|
|
|
+### 🎨 Vue.js 组件示例
|
|
|
+
|
|
|
+```vue
|
|
|
+<template>
|
|
|
+ <div class="parse-task-manager">
|
|
|
+ <h2>解析任务管理</h2>
|
|
|
+
|
|
|
+ <!-- 创建任务 -->
|
|
|
+ <div class="create-task-section">
|
|
|
+ <h3>创建新任务</h3>
|
|
|
+ <el-form ref="taskForm" :model="taskForm" label-width="120px">
|
|
|
+ <el-form-item label="任务类型">
|
|
|
+ <el-select v-model="taskForm.taskType" placeholder="请选择任务类型">
|
|
|
+ <el-option label="名片" value="名片"></el-option>
|
|
|
+ <el-option label="简历" value="简历"></el-option>
|
|
|
+ <el-option label="新任命" value="新任命"></el-option>
|
|
|
+ <el-option label="招聘" value="招聘"></el-option>
|
|
|
+ <el-option label="杂项" value="杂项"></el-option>
|
|
|
+ </el-select>
|
|
|
+ </el-form-item>
|
|
|
+
|
|
|
+ <el-form-item label="文件上传" v-if="taskForm.taskType !== '招聘'">
|
|
|
+ <el-upload
|
|
|
+ ref="upload"
|
|
|
+ :file-list="fileList"
|
|
|
+ :auto-upload="false"
|
|
|
+ :multiple="true"
|
|
|
+ :on-change="handleFileChange"
|
|
|
+ :on-remove="handleFileRemove">
|
|
|
+ <el-button size="small" type="primary">选择文件</el-button>
|
|
|
+ </el-upload>
|
|
|
+ </el-form-item>
|
|
|
+
|
|
|
+ <el-form-item>
|
|
|
+ <el-button type="primary" @click="createTask" :loading="loading">创建任务</el-button>
|
|
|
+ </el-form-item>
|
|
|
+ </el-form>
|
|
|
+ </div>
|
|
|
+
|
|
|
+ <!-- 任务列表 -->
|
|
|
+ <div class="task-list-section">
|
|
|
+ <h3>任务列表</h3>
|
|
|
+ <el-table :data="taskList" style="width: 100%">
|
|
|
+ <el-table-column prop="id" label="ID" width="80"></el-table-column>
|
|
|
+ <el-table-column prop="task_name" label="任务名称" width="200"></el-table-column>
|
|
|
+ <el-table-column prop="task_type" label="类型" width="100"></el-table-column>
|
|
|
+ <el-table-column prop="task_status" label="状态" width="100"></el-table-column>
|
|
|
+ <el-table-column prop="collection_count" label="文件数" width="80"></el-table-column>
|
|
|
+ <el-table-column prop="parse_count" label="解析数" width="80"></el-table-column>
|
|
|
+ <el-table-column prop="created_at" label="创建时间" width="180"></el-table-column>
|
|
|
+ <el-table-column label="操作">
|
|
|
+ <template slot-scope="scope">
|
|
|
+ <el-button size="mini" @click="viewDetail(scope.row)">详情</el-button>
|
|
|
+ <el-button size="mini" type="primary" @click="executeTask(scope.row)"
|
|
|
+ :disabled="scope.row.task_status !== '待解析'">执行</el-button>
|
|
|
+ </template>
|
|
|
+ </el-table-column>
|
|
|
+ </el-table>
|
|
|
+
|
|
|
+ <!-- 分页 -->
|
|
|
+ <el-pagination
|
|
|
+ @current-change="handlePageChange"
|
|
|
+ :current-page="currentPage"
|
|
|
+ :page-size="pageSize"
|
|
|
+ :total="total"
|
|
|
+ layout="total, prev, pager, next">
|
|
|
+ </el-pagination>
|
|
|
+ </div>
|
|
|
+
|
|
|
+ <!-- 任务详情对话框 -->
|
|
|
+ <el-dialog title="任务详情" :visible.sync="detailVisible" width="80%">
|
|
|
+ <div v-if="currentTask">
|
|
|
+ <h4>基本信息</h4>
|
|
|
+ <p><strong>任务名称:</strong> {{ currentTask.task_name }}</p>
|
|
|
+ <p><strong>任务类型:</strong> {{ currentTask.task_type }}</p>
|
|
|
+ <p><strong>任务状态:</strong> {{ currentTask.task_status }}</p>
|
|
|
+ <p><strong>文件数量:</strong> {{ currentTask.collection_count }}</p>
|
|
|
+ <p><strong>解析数量:</strong> {{ currentTask.parse_count }}</p>
|
|
|
+
|
|
|
+ <h4 v-if="currentTask.parse_result">解析结果</h4>
|
|
|
+ <pre v-if="currentTask.parse_result">{{ JSON.stringify(currentTask.parse_result, null, 2) }}</pre>
|
|
|
+ </div>
|
|
|
+ </el-dialog>
|
|
|
+ </div>
|
|
|
+</template>
|
|
|
+
|
|
|
+<script>
|
|
|
+export default {
|
|
|
+ name: 'ParseTaskManager',
|
|
|
+ data() {
|
|
|
+ return {
|
|
|
+ loading: false,
|
|
|
+ taskForm: {
|
|
|
+ taskType: '',
|
|
|
+ },
|
|
|
+ fileList: [],
|
|
|
+ taskList: [],
|
|
|
+ currentPage: 1,
|
|
|
+ pageSize: 10,
|
|
|
+ total: 0,
|
|
|
+ detailVisible: false,
|
|
|
+ currentTask: null
|
|
|
+ }
|
|
|
+ },
|
|
|
+ mounted() {
|
|
|
+ this.loadTaskList();
|
|
|
+ },
|
|
|
+ methods: {
|
|
|
+ // 文件选择处理
|
|
|
+ handleFileChange(file, fileList) {
|
|
|
+ this.fileList = fileList;
|
|
|
+ },
|
|
|
+
|
|
|
+ handleFileRemove(file, fileList) {
|
|
|
+ this.fileList = fileList;
|
|
|
+ },
|
|
|
+
|
|
|
+ // 创建任务
|
|
|
+ async createTask() {
|
|
|
+ if (!this.taskForm.taskType) {
|
|
|
+ this.$message.error('请选择任务类型');
|
|
|
+ return;
|
|
|
+ }
|
|
|
+
|
|
|
+ if (this.taskForm.taskType !== '招聘' && this.fileList.length === 0) {
|
|
|
+ this.$message.error('请选择要上传的文件');
|
|
|
+ return;
|
|
|
+ }
|
|
|
+
|
|
|
+ this.loading = true;
|
|
|
+
|
|
|
+ try {
|
|
|
+ const files = this.fileList.map(item => item.raw);
|
|
|
+ const result = await this.createParseTask(this.taskForm.taskType, files);
|
|
|
+
|
|
|
+ this.$message.success('任务创建成功');
|
|
|
+ this.resetForm();
|
|
|
+ this.loadTaskList();
|
|
|
+
|
|
|
+ } catch (error) {
|
|
|
+ this.$message.error(`任务创建失败: ${error.message}`);
|
|
|
+ } finally {
|
|
|
+ this.loading = false;
|
|
|
+ }
|
|
|
+ },
|
|
|
+
|
|
|
+ // 重置表单
|
|
|
+ resetForm() {
|
|
|
+ this.taskForm.taskType = '';
|
|
|
+ this.fileList = [];
|
|
|
+ this.$refs.upload.clearFiles();
|
|
|
+ },
|
|
|
+
|
|
|
+ // 加载任务列表
|
|
|
+ async loadTaskList() {
|
|
|
+ try {
|
|
|
+ const data = await this.getParseTaskList(this.currentPage, this.pageSize);
|
|
|
+ this.taskList = data.tasks;
|
|
|
+ this.total = data.pagination.total;
|
|
|
+ } catch (error) {
|
|
|
+ this.$message.error(`加载任务列表失败: ${error.message}`);
|
|
|
+ }
|
|
|
+ },
|
|
|
+
|
|
|
+ // 分页处理
|
|
|
+ handlePageChange(page) {
|
|
|
+ this.currentPage = page;
|
|
|
+ this.loadTaskList();
|
|
|
+ },
|
|
|
+
|
|
|
+ // 查看详情
|
|
|
+ async viewDetail(task) {
|
|
|
+ try {
|
|
|
+ this.currentTask = await this.getParseTaskDetail(task.task_name);
|
|
|
+ this.detailVisible = true;
|
|
|
+ } catch (error) {
|
|
|
+ this.$message.error(`获取任务详情失败: ${error.message}`);
|
|
|
+ }
|
|
|
+ },
|
|
|
+
|
|
|
+ // 执行任务
|
|
|
+ async executeTask(task) {
|
|
|
+ if (!task.task_source || !task.task_source.minio_paths_json) {
|
|
|
+ this.$message.error('任务数据不完整,无法执行');
|
|
|
+ return;
|
|
|
+ }
|
|
|
+
|
|
|
+ try {
|
|
|
+ this.$loading = this.$loading.service({
|
|
|
+ lock: true,
|
|
|
+ text: '正在执行解析任务...',
|
|
|
+ spinner: 'el-icon-loading',
|
|
|
+ background: 'rgba(0, 0, 0, 0.7)'
|
|
|
+ });
|
|
|
+
|
|
|
+ // 执行解析
|
|
|
+ const executeResult = await this.executeParseTask(
|
|
|
+ task.task_type,
|
|
|
+ task.task_source.minio_paths_json,
|
|
|
+ task.id
|
|
|
+ );
|
|
|
+
|
|
|
+ // 处理结果
|
|
|
+ const processResult = await this.processParseResults(executeResult);
|
|
|
+
|
|
|
+ this.$message.success(`解析完成,成功处理 ${processResult.summary.success_count} 条记录`);
|
|
|
+ this.loadTaskList();
|
|
|
+
|
|
|
+ } catch (error) {
|
|
|
+ this.$message.error(`任务执行失败: ${error.message}`);
|
|
|
+ } finally {
|
|
|
+ this.$loading.close();
|
|
|
+ }
|
|
|
+ },
|
|
|
+
|
|
|
+ // API调用方法
|
|
|
+ async createParseTask(taskType, files, createdBy = 'vue_user') {
|
|
|
+ // 实现createParseTask方法
|
|
|
+ // ... (参考前面的JavaScript示例)
|
|
|
+ },
|
|
|
+
|
|
|
+ async getParseTaskList(page, perPage, taskType = null, taskStatus = null) {
|
|
|
+ // 实现getParseTaskList方法
|
|
|
+ // ... (参考前面的JavaScript示例)
|
|
|
+ },
|
|
|
+
|
|
|
+ async getParseTaskDetail(taskName) {
|
|
|
+ // 实现getParseTaskDetail方法
|
|
|
+ // ... (参考前面的JavaScript示例)
|
|
|
+ },
|
|
|
+
|
|
|
+ async executeParseTask(taskType, data, taskId, publishTime = null) {
|
|
|
+ // 实现executeParseTask方法
|
|
|
+ // ... (参考前面的JavaScript示例)
|
|
|
+ },
|
|
|
+
|
|
|
+ async processParseResults(executeTaskResponse) {
|
|
|
+ // 实现processParseResults方法
|
|
|
+ // ... (参考前面的JavaScript示例)
|
|
|
+ }
|
|
|
+ }
|
|
|
+}
|
|
|
+</script>
|
|
|
+
|
|
|
+<style scoped>
|
|
|
+.parse-task-manager {
|
|
|
+ padding: 20px;
|
|
|
+}
|
|
|
+
|
|
|
+.create-task-section,
|
|
|
+.task-list-section {
|
|
|
+ margin-bottom: 30px;
|
|
|
+ padding: 20px;
|
|
|
+ border: 1px solid #ebeef5;
|
|
|
+ border-radius: 4px;
|
|
|
+}
|
|
|
+
|
|
|
+.el-pagination {
|
|
|
+ margin-top: 20px;
|
|
|
+ text-align: center;
|
|
|
+}
|
|
|
+
|
|
|
+pre {
|
|
|
+ background-color: #f5f5f5;
|
|
|
+ padding: 10px;
|
|
|
+ border-radius: 4px;
|
|
|
+ overflow-x: auto;
|
|
|
+ max-height: 400px;
|
|
|
+}
|
|
|
+</style>
|
|
|
+```
|
|
|
+
|
|
|
+### 🔧 工具类封装
|
|
|
+
|
|
|
+```javascript
|
|
|
+// parseTaskAPI.js - API调用工具类
|
|
|
+class ParseTaskAPI {
|
|
|
+ constructor(baseURL = '/api/data-parse') {
|
|
|
+ this.baseURL = baseURL;
|
|
|
+ }
|
|
|
+
|
|
|
+ // 通用请求方法
|
|
|
+ async request(url, options = {}) {
|
|
|
+ const fullURL = `${this.baseURL}${url}`;
|
|
|
+
|
|
|
+ try {
|
|
|
+ const response = await fetch(fullURL, {
|
|
|
+ headers: {
|
|
|
+ 'Content-Type': 'application/json',
|
|
|
+ ...options.headers
|
|
|
+ },
|
|
|
+ ...options
|
|
|
+ });
|
|
|
+
|
|
|
+ const result = await response.json();
|
|
|
+
|
|
|
+ if (result.success) {
|
|
|
+ return result.data;
|
|
|
+ } else {
|
|
|
+ throw new Error(result.message);
|
|
|
+ }
|
|
|
+ } catch (error) {
|
|
|
+ console.error(`API请求失败: ${fullURL}`, error);
|
|
|
+ throw error;
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ // GET请求(用于查询类接口)
|
|
|
+ async get(url, params = {}) {
|
|
|
+ const queryString = new URLSearchParams(params).toString();
|
|
|
+ const fullURL = queryString ? `${url}?${queryString}` : url;
|
|
|
+
|
|
|
+ return this.request(fullURL, { method: 'GET' });
|
|
|
+ }
|
|
|
+
|
|
|
+ // POST请求(用于操作类接口)
|
|
|
+ async post(url, data) {
|
|
|
+ return this.request(url, {
|
|
|
+ method: 'POST',
|
|
|
+ body: JSON.stringify(data)
|
|
|
+ });
|
|
|
+ }
|
|
|
+
|
|
|
+ // FormData POST请求(用于文件上传)
|
|
|
+ async postFormData(url, formData) {
|
|
|
+ const fullURL = `${this.baseURL}${url}`;
|
|
|
+
|
|
|
+ try {
|
|
|
+ const response = await fetch(fullURL, {
|
|
|
+ method: 'POST',
|
|
|
+ body: formData
|
|
|
+ });
|
|
|
+
|
|
|
+ const result = await response.json();
|
|
|
+
|
|
|
+ if (result.success) {
|
|
|
+ return result.data;
|
|
|
+ } else {
|
|
|
+ throw new Error(result.message);
|
|
|
+ }
|
|
|
+ } catch (error) {
|
|
|
+ console.error(`FormData请求失败: ${fullURL}`, error);
|
|
|
+ throw error;
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ // 1. 获取解析任务列表
|
|
|
+ async getTaskList(page = 1, perPage = 10, taskType = null, taskStatus = null) {
|
|
|
+ const params = { page, per_page: perPage };
|
|
|
+ if (taskType) params.task_type = taskType;
|
|
|
+ if (taskStatus) params.task_status = taskStatus;
|
|
|
+
|
|
|
+ return this.get('/get-parse-tasks', params);
|
|
|
+ }
|
|
|
+
|
|
|
+ // 2. 获取解析任务详情
|
|
|
+ async getTaskDetail(taskName) {
|
|
|
+ if (!taskName) {
|
|
|
+ throw new Error('任务名称不能为空');
|
|
|
+ }
|
|
|
+
|
|
|
+ return this.get('/get-parse-task-detail', { task_name: taskName });
|
|
|
+ }
|
|
|
+
|
|
|
+ // 3. 新增解析任务
|
|
|
+ async createTask(taskType, files = [], createdBy = 'api_user') {
|
|
|
+ const formData = new FormData();
|
|
|
+ formData.append('task_type', taskType);
|
|
|
+ formData.append('created_by', createdBy);
|
|
|
+
|
|
|
+ if (files && files.length > 0) {
|
|
|
+ for (let i = 0; i < files.length; i++) {
|
|
|
+ formData.append('files', files[i]);
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ return this.postFormData('/add-parse-task', formData);
|
|
|
+ }
|
|
|
+
|
|
|
+ // 4. 执行解析任务
|
|
|
+ async executeTask(taskType, data, taskId, publishTime = null, processType = 'table') {
|
|
|
+ const requestBody = {
|
|
|
+ task_type: taskType,
|
|
|
+ data: data,
|
|
|
+ id: taskId
|
|
|
+ };
|
|
|
+
|
|
|
+ if (taskType === '新任命' && publishTime) {
|
|
|
+ requestBody.publish_time = publishTime;
|
|
|
+ }
|
|
|
+
|
|
|
+ if (taskType === '杂项') {
|
|
|
+ requestBody.process_type = processType;
|
|
|
+ }
|
|
|
+
|
|
|
+ return this.post('/execute-parse-task', requestBody);
|
|
|
+ }
|
|
|
+
|
|
|
+ // 5. 处理解析结果
|
|
|
+ async processResults(executeTaskResponse) {
|
|
|
+ if (!executeTaskResponse || typeof executeTaskResponse !== 'object') {
|
|
|
+ throw new Error('API响应数据格式错误');
|
|
|
+ }
|
|
|
+
|
|
|
+ return this.post('/add-parsed-talents', executeTaskResponse);
|
|
|
+ }
|
|
|
+
|
|
|
+ // 组合方法:完整的解析流程
|
|
|
+ async completeParseWorkflow(taskType, data, taskId, publishTime = null) {
|
|
|
+ try {
|
|
|
+ // 执行解析任务
|
|
|
+ const executeResult = await this.executeTask(taskType, data, taskId, publishTime);
|
|
|
+
|
|
|
+ // 处理解析结果
|
|
|
+ const processResult = await this.processResults(executeResult);
|
|
|
+
|
|
|
+ return {
|
|
|
+ executeResult,
|
|
|
+ processResult
|
|
|
+ };
|
|
|
+ } catch (error) {
|
|
|
+ console.error('完整解析流程失败:', error);
|
|
|
+ throw error;
|
|
|
+ }
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+// 导出实例
|
|
|
+export default new ParseTaskAPI();
|
|
|
+
|
|
|
+// 使用示例:
|
|
|
+// import parseTaskAPI from './parseTaskAPI.js';
|
|
|
+//
|
|
|
+// // 获取任务列表
|
|
|
+// const taskList = await parseTaskAPI.getTaskList(1, 20, '名片');
|
|
|
+//
|
|
|
+// // 创建任务
|
|
|
+// const createResult = await parseTaskAPI.createTask('名片', files);
|
|
|
+//
|
|
|
+// // 执行完整流程
|
|
|
+// const result = await parseTaskAPI.completeParseWorkflow('名片', data, taskId);
|
|
|
+```
|
|
|
+
|
|
|
+---
|
|
|
+
|
|
|
+## 错误处理
|
|
|
+
|
|
|
+### 🚨 常见错误类型
|
|
|
+
|
|
|
+| 错误类型 | HTTP状态码 | 错误信息示例 | 处理建议 |
|
|
|
+|----------|------------|--------------|----------|
|
|
|
+| 参数错误 | 400 | `"缺少task_type参数"` | 检查请求参数完整性 |
|
|
|
+| 文件格式错误 | 400 | `"不支持的文件格式"` | 检查文件类型是否匹配任务类型 |
|
|
|
+| 任务不存在 | 404 | `"未找到任务名称为 xxx 的记录"` | 确认任务名称正确 |
|
|
|
+| 服务器错误 | 500 | `"数据库连接失败"` | 联系后端开发人员 |
|
|
|
+| 部分成功 | 206 | `"部分文件上传成功"` | 检查失败的文件,重新上传 |
|
|
|
+
|
|
|
+### 🛠️ 错误处理最佳实践
|
|
|
+
|
|
|
+```javascript
|
|
|
+// 统一错误处理函数
|
|
|
+function handleAPIError(error, context = '') {
|
|
|
+ console.error(`${context}失败:`, error);
|
|
|
+
|
|
|
+ // 根据错误类型给出不同的用户提示
|
|
|
+ if (error.message.includes('网络')) {
|
|
|
+ return '网络连接异常,请检查网络后重试';
|
|
|
+ } else if (error.message.includes('参数')) {
|
|
|
+ return '请求参数错误,请检查输入信息';
|
|
|
+ } else if (error.message.includes('权限')) {
|
|
|
+ return '权限不足,请联系管理员';
|
|
|
+ } else if (error.message.includes('不存在')) {
|
|
|
+ return '请求的资源不存在';
|
|
|
+ } else {
|
|
|
+ return `操作失败: ${error.message}`;
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+// 带重试机制的API调用
|
|
|
+async function apiCallWithRetry(apiFunction, maxRetries = 3, delay = 1000) {
|
|
|
+ for (let i = 0; i < maxRetries; i++) {
|
|
|
+ try {
|
|
|
+ return await apiFunction();
|
|
|
+ } catch (error) {
|
|
|
+ if (i === maxRetries - 1) {
|
|
|
+ throw error;
|
|
|
+ }
|
|
|
+
|
|
|
+ // 如果是网络错误或服务器错误,等待后重试
|
|
|
+ if (error.message.includes('fetch') || error.message.includes('500')) {
|
|
|
+ console.log(`第${i + 1}次重试,${delay}ms后重新请求...`);
|
|
|
+ await new Promise(resolve => setTimeout(resolve, delay));
|
|
|
+ delay *= 2; // 指数退避
|
|
|
+ } else {
|
|
|
+ throw error; // 其他错误直接抛出
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+// 使用示例
|
|
|
+try {
|
|
|
+ const result = await apiCallWithRetry(
|
|
|
+ () => parseTaskAPI.getTaskList(1, 10),
|
|
|
+ 3,
|
|
|
+ 1000
|
|
|
+ );
|
|
|
+ console.log('获取任务列表成功:', result);
|
|
|
+} catch (error) {
|
|
|
+ const userMessage = handleAPIError(error, '获取任务列表');
|
|
|
+ alert(userMessage);
|
|
|
+}
|
|
|
+```
|
|
|
+
|
|
|
+---
|
|
|
+
|
|
|
+## 最佳实践
|
|
|
+
|
|
|
+### 🎯 前端开发建议
|
|
|
+
|
|
|
+1. **参数验证**
|
|
|
+ ```javascript
|
|
|
+ // 在调用API前进行客户端验证
|
|
|
+ function validateTaskType(taskType) {
|
|
|
+ const validTypes = ['名片', '简历', '新任命', '招聘', '杂项'];
|
|
|
+ if (!validTypes.includes(taskType)) {
|
|
|
+ throw new Error(`无效的任务类型: ${taskType}`);
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ function validateFiles(files, taskType) {
|
|
|
+ if (taskType === '招聘') {
|
|
|
+ return; // 招聘任务不需要文件
|
|
|
+ }
|
|
|
+
|
|
|
+ if (!files || files.length === 0) {
|
|
|
+ throw new Error('请选择要上传的文件');
|
|
|
+ }
|
|
|
+
|
|
|
+ // 文件格式验证
|
|
|
+ const allowedFormats = {
|
|
|
+ '名片': ['jpg', 'jpeg', 'png'],
|
|
|
+ '简历': ['pdf'],
|
|
|
+ '新任命': ['md'],
|
|
|
+ '杂项': ['*'] // 支持所有格式
|
|
|
+ };
|
|
|
+
|
|
|
+ const formats = allowedFormats[taskType];
|
|
|
+ if (formats && !formats.includes('*')) {
|
|
|
+ files.forEach(file => {
|
|
|
+ const ext = file.name.split('.').pop().toLowerCase();
|
|
|
+ if (!formats.includes(ext)) {
|
|
|
+ throw new Error(`${taskType}任务不支持${ext}格式文件`);
|
|
|
+ }
|
|
|
+ });
|
|
|
+ }
|
|
|
+ }
|
|
|
+ ```
|
|
|
+
|
|
|
+2. **进度显示**
|
|
|
+ ```javascript
|
|
|
+ // 文件上传进度显示
|
|
|
+ function showUploadProgress(current, total) {
|
|
|
+ const percentage = Math.round((current / total) * 100);
|
|
|
+ console.log(`上传进度: ${current}/${total} (${percentage}%)`);
|
|
|
+
|
|
|
+ // 更新UI进度条
|
|
|
+ updateProgressBar(percentage);
|
|
|
+ }
|
|
|
+
|
|
|
+ // 解析进度显示
|
|
|
+ function showParseProgress(results) {
|
|
|
+ const completed = results.filter(r => r.success || r.error).length;
|
|
|
+ const total = results.length;
|
|
|
+ const percentage = Math.round((completed / total) * 100);
|
|
|
+
|
|
|
+ console.log(`解析进度: ${completed}/${total} (${percentage}%)`);
|
|
|
+ }
|
|
|
+ ```
|
|
|
+
|
|
|
+3. **缓存策略**
|
|
|
+ ```javascript
|
|
|
+ // 任务列表缓存
|
|
|
+ class TaskListCache {
|
|
|
+ constructor(ttl = 5 * 60 * 1000) { // 5分钟TTL
|
|
|
+ this.cache = new Map();
|
|
|
+ this.ttl = ttl;
|
|
|
+ }
|
|
|
+
|
|
|
+ getKey(page, perPage, taskType, taskStatus) {
|
|
|
+ return `${page}_${perPage}_${taskType || ''}_${taskStatus || ''}`;
|
|
|
+ }
|
|
|
+
|
|
|
+ get(page, perPage, taskType, taskStatus) {
|
|
|
+ const key = this.getKey(page, perPage, taskType, taskStatus);
|
|
|
+ const item = this.cache.get(key);
|
|
|
+
|
|
|
+ if (!item) return null;
|
|
|
+
|
|
|
+ if (Date.now() - item.timestamp > this.ttl) {
|
|
|
+ this.cache.delete(key);
|
|
|
+ return null;
|
|
|
+ }
|
|
|
+
|
|
|
+ return item.data;
|
|
|
+ }
|
|
|
+
|
|
|
+ set(page, perPage, taskType, taskStatus, data) {
|
|
|
+ const key = this.getKey(page, perPage, taskType, taskStatus);
|
|
|
+ this.cache.set(key, {
|
|
|
+ data,
|
|
|
+ timestamp: Date.now()
|
|
|
+ });
|
|
|
+ }
|
|
|
+
|
|
|
+ clear() {
|
|
|
+ this.cache.clear();
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ const taskListCache = new TaskListCache();
|
|
|
+ ```
|
|
|
+
|
|
|
+4. **状态管理**
|
|
|
+ ```javascript
|
|
|
+ // 使用Vuex或类似状态管理
|
|
|
+ const parseTaskStore = {
|
|
|
+ state: {
|
|
|
+ taskList: [],
|
|
|
+ currentTask: null,
|
|
|
+ loading: false,
|
|
|
+ error: null
|
|
|
+ },
|
|
|
+
|
|
|
+ mutations: {
|
|
|
+ SET_TASK_LIST(state, tasks) {
|
|
|
+ state.taskList = tasks;
|
|
|
+ },
|
|
|
+ SET_CURRENT_TASK(state, task) {
|
|
|
+ state.currentTask = task;
|
|
|
+ },
|
|
|
+ SET_LOADING(state, loading) {
|
|
|
+ state.loading = loading;
|
|
|
+ },
|
|
|
+ SET_ERROR(state, error) {
|
|
|
+ state.error = error;
|
|
|
+ }
|
|
|
+ },
|
|
|
+
|
|
|
+ actions: {
|
|
|
+ async loadTaskList({ commit }, { page, perPage, taskType, taskStatus }) {
|
|
|
+ commit('SET_LOADING', true);
|
|
|
+ commit('SET_ERROR', null);
|
|
|
+
|
|
|
+ try {
|
|
|
+ const data = await parseTaskAPI.getTaskList(page, perPage, taskType, taskStatus);
|
|
|
+ commit('SET_TASK_LIST', data.tasks);
|
|
|
+ return data;
|
|
|
+ } catch (error) {
|
|
|
+ commit('SET_ERROR', error.message);
|
|
|
+ throw error;
|
|
|
+ } finally {
|
|
|
+ commit('SET_LOADING', false);
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+ };
|
|
|
+ ```
|
|
|
+
|
|
|
+### 📱 响应式设计
|
|
|
+
|
|
|
+```css
|
|
|
+/* 移动端适配 */
|
|
|
+@media (max-width: 768px) {
|
|
|
+ .parse-task-manager {
|
|
|
+ padding: 10px;
|
|
|
+ }
|
|
|
+
|
|
|
+ .el-table {
|
|
|
+ font-size: 12px;
|
|
|
+ }
|
|
|
+
|
|
|
+ .el-table-column {
|
|
|
+ min-width: 80px;
|
|
|
+ }
|
|
|
+
|
|
|
+ .create-task-section,
|
|
|
+ .task-list-section {
|
|
|
+ padding: 15px;
|
|
|
+ margin-bottom: 20px;
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+/* 大屏优化 */
|
|
|
+@media (min-width: 1200px) {
|
|
|
+ .parse-task-manager {
|
|
|
+ max-width: 1200px;
|
|
|
+ margin: 0 auto;
|
|
|
+ }
|
|
|
+}
|
|
|
+```
|
|
|
+
|
|
|
+### 🔍 调试工具
|
|
|
+
|
|
|
+```javascript
|
|
|
+// 开发环境调试工具
|
|
|
+if (process.env.NODE_ENV === 'development') {
|
|
|
+ window.parseTaskDebug = {
|
|
|
+ // 显示API调用日志
|
|
|
+ enableAPILogging() {
|
|
|
+ const originalRequest = parseTaskAPI.request;
|
|
|
+ parseTaskAPI.request = function(...args) {
|
|
|
+ console.group(`API调用: ${args[0]}`);
|
|
|
+ console.log('参数:', args[1]);
|
|
|
+ console.time('请求耗时');
|
|
|
+
|
|
|
+ return originalRequest.apply(this, args)
|
|
|
+ .then(result => {
|
|
|
+ console.log('响应:', result);
|
|
|
+ console.timeEnd('请求耗时');
|
|
|
+ console.groupEnd();
|
|
|
+ return result;
|
|
|
+ })
|
|
|
+ .catch(error => {
|
|
|
+ console.error('错误:', error);
|
|
|
+ console.timeEnd('请求耗时');
|
|
|
+ console.groupEnd();
|
|
|
+ throw error;
|
|
|
+ });
|
|
|
+ };
|
|
|
+ },
|
|
|
+
|
|
|
+ // 模拟API响应
|
|
|
+ mockAPI(endpoint, response) {
|
|
|
+ const originalRequest = parseTaskAPI.request;
|
|
|
+ parseTaskAPI.request = function(url, options) {
|
|
|
+ if (url.includes(endpoint)) {
|
|
|
+ console.log(`Mock API: ${endpoint}`, response);
|
|
|
+ return Promise.resolve(response);
|
|
|
+ }
|
|
|
+ return originalRequest.apply(this, arguments);
|
|
|
+ };
|
|
|
+ }
|
|
|
+ };
|
|
|
+}
|
|
|
+```
|
|
|
+
|
|
|
+---
|
|
|
+
|
|
|
+## 📞 技术支持
|
|
|
+
|
|
|
+如有任何问题,请联系:
|
|
|
+
|
|
|
+- **后端开发团队**: backend@company.com
|
|
|
+- **API文档维护**: api-docs@company.com
|
|
|
+- **技术支持**: tech-support@company.com
|
|
|
+
|
|
|
+---
|
|
|
+
|
|
|
+> **文档更新记录**
|
|
|
+> - v1.0 (2025-01-18): 初始版本,包含5个解析任务管理接口的完整说明
|
|
|
+> - 下次更新预计: 根据接口变更情况更新
|