Browse Source

新增网页人才API文档
新增talent_profile字段,支持人才档案信息

maxiaolong 2 days ago
parent
commit
f543f7d098

+ 444 - 89
add_webpage_talent_api_docs.md

@@ -2,7 +2,7 @@
 
 ## API概述
 
-`/add-webpage-talent` 接口用于批量添加网页提取的人才信息到系统中。该接口能够将网页内容保存到MinIO存储,并为每个人才创建对应的业务卡片记录。
+`/add-webpage-talent` 接口用于批量添加网页提取的人才信息到系统中。该接口能够将网页内容保存到MinIO存储,并为每个人才创建对应的业务卡片记录。系统会自动设置数据来源信息(`origin_source`)和人才档案信息(`talent_profile`)。
 
 ---
 
@@ -46,7 +46,9 @@
       "birthday": "1980-01-01",
       "age": 44,
       "native_place": "北京市",
-      "residence": "北京市朝阳区"
+      "residence": "北京市朝阳区",
+      "pic_url": "https://example.com/photo.jpg",
+      "talent_profile": "资深酒店管理专家,拥有20年行业经验..."
     }
   ],
   "web_md": "# 酒店任命公告\n\n## 人事变动\n\n1. **张三** 被任命为北京万豪酒店总经理...\n\n更多内容..."
@@ -130,6 +132,17 @@
 - **描述**: 年龄
 - **示例**: `44`
 
+#### `talent_list[].pic_url` (可选)
+- **类型**: `String`
+- **描述**: 人才照片URL地址,系统会自动下载并保存到MinIO
+- **示例**: `"https://example.com/photo.jpg"`
+- **注意**: 如果提供了有效的图片URL,系统会自动下载图片并保存到MinIO存储中
+
+#### `talent_list[].talent_profile` (可选)
+- **类型**: `String`
+- **描述**: 人才档案信息,包含个人简介、工作经历、专业技能等详细信息
+- **示例**: `"资深酒店管理专家,拥有20年行业经验,曾在多家国际知名酒店担任高级管理职位..."`
+
 #### `web_md` (必填)
 - **类型**: `String`
 - **描述**: 网页的markdown格式文本内容,将被保存到MinIO存储
@@ -147,41 +160,89 @@
   "code": 200,
   "success": true,
   "message": "所有3条人才记录处理成功",
-  "data": {
-    "total_count": 3,
-    "success_count": 3,
-    "failed_count": 0,
-    "success_records": [
-      {
-        "index": 1,
-        "data": {
-          "id": 123,
-          "name_zh": "张三",
-          "name_en": "Zhang San",
-          "title_zh": "总经理",
-          "title_en": "General Manager",
+  "data": [
+    {
+      "id": 123,
+      "name_zh": "张三",
+      "name_en": "Zhang San",
+      "title_zh": "总经理",
+      "title_en": "General Manager",
+      "hotel_zh": "北京万豪酒店",
+      "hotel_en": "Beijing Marriott Hotel",
+      "mobile": "13800138000",
+      "email": "zhangsan@example.com",
+      "phone": "010-12345678",
+      "address_zh": "北京市朝阳区XXX路123号",
+      "address_en": "123 XXX Road, Chaoyang District, Beijing",
+      "postal_code_zh": "100000",
+      "postal_code_en": "100000",
+      "brand_zh": "万豪",
+      "brand_en": "Marriott",
+      "affiliation_zh": "万豪国际集团",
+      "affiliation_en": "Marriott International",
+      "birthday": "1980-01-01",
+      "age": 44,
+      "native_place": "北京市",
+      "residence": "北京市朝阳区",
+      "brand_group": "万豪",
+      "image_path": "talent_photos/talent_photo_张三_20240101_12345678.jpg",
+      "career_path": [
+        {
+          "date": "2024-01-01",
           "hotel_zh": "北京万豪酒店",
           "hotel_en": "Beijing Marriott Hotel",
-          "mobile": "13800138000",
-          "email": "zhangsan@example.com",
-          "brand_group": "万豪",
-          "origin_source": {
-            "type": "webpage_talent",
-            "minio_path": "webpage_talent/webpage_talent_20240101_12345678.md",
-            "source_date": "2024-01-01 12:00:00",
-            "talent_data": {...},
-            "web_md_content": "# 酒店任命公告..."
-          },
-          "created_at": "2024-01-01 12:00:00",
-          "updated_at": "2024-01-01 12:00:00",
-          "status": "active"
-        },
-        "message": "名片信息保存成功。未发现重复记录"
-      }
-    ],
-    "failed_records": [],
-    "minio_md_path": "webpage_talent/webpage_talent_20240101_12345678.md"
-  }
+          "title_zh": "总经理",
+          "title_en": "General Manager",
+          "image_path": "talent_photos/talent_photo_张三_20240101_12345678.jpg",
+          "source": "webpage_extraction"
+        }
+      ],
+      "talent_profile": "资深酒店管理专家,拥有20年行业经验...",
+      "origin_source": {
+        "type": "webpage_talent",
+        "minio_path": "webpage_talent/webpage_talent_20240101_12345678.md",
+        "source_date": "2024-01-01 12:00:00"
+      },
+      "created_at": "2024-01-01 12:00:00",
+      "updated_at": "2024-01-01 12:00:00",
+      "updated_by": "webpage_talent_system",
+      "status": "active"
+    },
+    {
+      "id": 124,
+      "name_zh": "李四",
+      "name_en": "Li Si",
+      "title_zh": "副总经理",
+      "title_en": "Deputy General Manager",
+      "hotel_zh": "上海希尔顿酒店",
+      "hotel_en": "Shanghai Hilton Hotel",
+      "mobile": "13900139000",
+      "email": "lisi@example.com",
+      "brand_group": "希尔顿",
+      "image_path": "talent_photos/talent_photo_李四_20240101_12345679.jpg",
+      "career_path": [
+        {
+          "date": "2024-01-01",
+          "hotel_zh": "上海希尔顿酒店",
+          "hotel_en": "Shanghai Hilton Hotel",
+          "title_zh": "副总经理",
+          "title_en": "Deputy General Manager",
+          "image_path": "talent_photos/talent_photo_李四_20240101_12345679.jpg",
+          "source": "webpage_extraction"
+        }
+      ],
+      "talent_profile": "酒店业资深管理者,专注于市场营销和品牌推广...",
+             "origin_source": {
+         "type": "webpage_talent",
+         "minio_path": "webpage_talent/webpage_talent_20240101_12345678.md",
+         "source_date": "2024-01-01 12:00:00"
+       },
+      "created_at": "2024-01-01 12:00:00",
+      "updated_at": "2024-01-01 12:00:00",
+      "updated_by": "webpage_talent_system",
+      "status": "active"
+    }
+  ]
 }
 ```
 
@@ -195,15 +256,53 @@
     "total_count": 3,
     "success_count": 2,
     "failed_count": 1,
-    "success_records": [...],
+    "success_records": [
+      {
+        "id": 123,
+        "name_zh": "张三",
+        "name_en": "Zhang San",
+        "title_zh": "总经理",
+        "title_en": "General Manager",
+        "hotel_zh": "北京万豪酒店",
+        "hotel_en": "Beijing Marriott Hotel",
+        "mobile": "13800138000",
+        "email": "zhangsan@example.com",
+        "brand_group": "万豪",
+        "image_path": "talent_photos/talent_photo_张三_20240101_12345678.jpg",
+        "talent_profile": "资深酒店管理专家,拥有20年行业经验...",
+                 "origin_source": {
+           "type": "webpage_talent",
+           "minio_path": "webpage_talent/webpage_talent_20240101_12345678.md",
+           "source_date": "2024-01-01 12:00:00"
+         },
+        "created_at": "2024-01-01 12:00:00",
+        "updated_at": "2024-01-01 12:00:00",
+        "status": "active"
+      },
+      {
+        "id": 124,
+        "name_zh": "李四",
+        "title_zh": "副总经理",
+        "hotel_zh": "上海希尔顿酒店",
+        "brand_group": "希尔顿",
+        "image_path": "talent_photos/talent_photo_李四_20240101_12345679.jpg",
+        "talent_profile": "酒店业资深管理者...",
+        "origin_source": {
+           "type": "webpage_talent",
+           "minio_path": "webpage_talent/webpage_talent_20240101_12345678.md",
+           "source_date": "2024-01-01 12:00:00"
+         },
+        "created_at": "2024-01-01 12:00:00",
+        "status": "active"
+      }
+    ],
     "failed_records": [
       {
-        "index": 3,
-        "data": {
+        "error": "第3个记录缺少name_zh字段",
+        "input_data": {
           "name_zh": "",
           "title_zh": "经理"
-        },
-        "error": "第3个记录缺少name_zh字段"
+        }
       }
     ],
     "minio_md_path": "webpage_talent/webpage_talent_20240101_12345678.md"
@@ -224,19 +323,60 @@
 ### 响应字段说明
 
 #### 通用字段
-- **`code`**: HTTP状态码
-- **`success`**: 操作是否成功
-- **`message`**: 响应消息
-- **`data`**: 响应数据
+- **`code`**: HTTP状态码 (200=全部成功, 206=部分成功, 400=参数错误, 500=服务器错误)
+- **`success`**: 操作是否成功 (true/false)
+- **`message`**: 响应消息,描述操作结果
+- **`data`**: 响应数据内容
+
+#### 全部成功时的data字段 (code=200)
+- **类型**: `Array<Object>`
+- **描述**: 成功创建/更新的业务卡片记录数组,每个对象包含完整的名片信息
 
-#### 成功响应数据字段
+#### 部分成功时的data字段 (code=206)
 - **`total_count`**: 总记录数
 - **`success_count`**: 成功处理的记录数
 - **`failed_count`**: 失败记录数
-- **`success_records`**: 成功记录详情列表
-- **`failed_records`**: 失败记录详情列表
+- **`success_records`**: 成功记录数组,每个对象包含完整的名片信息
+- **`failed_records`**: 失败记录数组,包含错误信息和输入数据
 - **`minio_md_path`**: 网页内容在MinIO中的存储路径
 
+#### 业务卡片记录字段说明
+每个成功创建的业务卡片记录包含以下字段:
+- **`id`**: 数据库记录ID
+- **`name_zh`**: 中文姓名
+- **`name_en`**: 英文姓名
+- **`title_zh`**: 中文职位
+- **`title_en`**: 英文职位
+- **`hotel_zh`**: 酒店中文名称
+- **`hotel_en`**: 酒店英文名称
+- **`mobile`**: 手机号码
+- **`email`**: 电子邮箱
+- **`phone`**: 固定电话
+- **`address_zh`**: 中文地址
+- **`address_en`**: 英文地址
+- **`postal_code_zh`**: 中文邮编
+- **`postal_code_en`**: 英文邮编
+- **`brand_zh`**: 品牌中文名称
+- **`brand_en`**: 品牌英文名称
+- **`affiliation_zh`**: 中文所属机构
+- **`affiliation_en`**: 英文所属机构
+- **`birthday`**: 生日 (YYYY-MM-DD格式)
+- **`age`**: 年龄
+- **`native_place`**: 籍贯
+- **`residence`**: 居住地
+- **`brand_group`**: 品牌组合
+- **`image_path`**: 人才照片在MinIO中的存储路径(如果提供了pic_url)
+- **`career_path`**: 职业轨迹数组,包含职位变化历史
+- **`talent_profile`**: 人才档案信息
+- **`origin_source`**: 数据来源信息,包含以下子字段:
+  - **`type`**: 数据来源类型,固定为"webpage_talent"
+  - **`minio_path`**: 原始网页内容在MinIO中的存储路径
+  - **`source_date`**: 数据处理时间
+- **`created_at`**: 创建时间
+- **`updated_at`**: 更新时间
+- **`updated_by`**: 更新人
+- **`status`**: 记录状态 (通常为"active")
+
 ---
 
 ## 返回状态码
@@ -281,21 +421,50 @@ async function addWebpageTalent(talentList, webMd) {
             console.log('处理成功:', result.message);
             console.log('处理详情:', result.data);
             
-            // 处理成功记录
-            if (result.data.success_records.length > 0) {
-                console.log(`成功处理 ${result.data.success_count} 条记录`);
-                result.data.success_records.forEach((record, index) => {
-                    console.log(`记录 ${record.index}: ${record.data.name_zh} - ${record.message}`);
-                });
-            }
-            
-            // 处理失败记录
-            if (result.data.failed_records.length > 0) {
-                console.log(`失败 ${result.data.failed_count} 条记录`);
-                result.data.failed_records.forEach((record, index) => {
-                    console.error(`记录 ${record.index}: ${record.error}`);
-                });
-            }
+                          // 处理成功记录 - 区分全部成功和部分成功的响应格式
+             if (result.code === 200) {
+                 // 全部成功:data是业务卡片记录数组
+                 console.log(`成功处理 ${result.data.length} 条记录`);
+                 result.data.forEach((record, index) => {
+                     console.log(`记录 ${index + 1}: ${record.name_zh}`);
+                     
+                     // 显示图片路径信息
+                     if (record.image_path) {
+                         console.log(`  图片路径: ${record.image_path}`);
+                     }
+                     
+                     // 显示人才档案信息
+                     if (record.talent_profile) {
+                         console.log(`  人才档案: ${record.talent_profile.substring(0, 50)}...`);
+                     }
+                     
+                     // 显示数据来源信息
+                     if (record.origin_source) {
+                         console.log(`  数据来源: ${record.origin_source.type}`);
+                         console.log(`  原始文档: ${record.origin_source.minio_path}`);
+                     }
+                 });
+             } else if (result.code === 206) {
+                 // 部分成功:data包含详细的成功和失败信息
+                 console.log(`部分成功:${result.data.success_count}/${result.data.total_count} 条记录成功`);
+                 
+                 // 显示成功记录
+                 if (result.data.success_records.length > 0) {
+                     result.data.success_records.forEach((record, index) => {
+                         console.log(`成功记录 ${record.index}: ${record.data.name_zh} - ${record.message}`);
+                         if (record.data.image_path) {
+                             console.log(`  图片路径: ${record.data.image_path}`);
+                         }
+                     });
+                 }
+                 
+                 // 显示失败记录
+                 if (result.data.failed_records.length > 0) {
+                     result.data.failed_records.forEach((record, index) => {
+                         console.error(`失败记录 ${record.index}: ${record.error}`);
+                     });
+                 }
+             }
             
             return result;
         } else {
@@ -319,7 +488,9 @@ const talentData = [
         hotel_en: "Beijing Marriott Hotel",
         brand_group: "万豪",
         mobile: "13800138000",
-        email: "zhangsan@example.com"
+        email: "zhangsan@example.com",
+        pic_url: "https://example.com/photos/zhangsan.jpg",
+        talent_profile: "资深酒店管理专家,拥有20年行业经验,曾在多家国际知名酒店担任高级管理职位。精通酒店运营管理、客户服务、团队建设等多个领域。"
     },
     {
         name_zh: "李四",
@@ -330,7 +501,9 @@ const talentData = [
         hotel_en: "Shanghai Hilton Hotel",
         brand_group: "希尔顿",
         mobile: "13900139000",
-        email: "lisi@example.com"
+        email: "lisi@example.com",
+        pic_url: "https://example.com/photos/lisi.jpg",
+        talent_profile: "酒店业资深管理者,专注于市场营销和品牌推广,具有丰富的国际酒店管理经验。"
     }
 ];
 
@@ -339,13 +512,19 @@ const webContent = `
 
 ## 重要人事变动
 
+![张三照片](https://example.com/photos/zhangsan.jpg)
+
 1. **张三**被任命为北京万豪酒店总经理
    - 手机:13800138000
    - 邮箱:zhangsan@example.com
+   - 简介:资深酒店管理专家,拥有20年行业经验
+
+![李四照片](https://example.com/photos/lisi.jpg)
 
 2. **李四**被任命为上海希尔顿酒店副总经理
    - 手机:13900139000
    - 邮箱:lisi@example.com
+   - 简介:酒店业资深管理者,专注于市场营销和品牌推广
 `;
 
 // 调用函数
@@ -374,9 +553,37 @@ function addWebpageTalent(talentList, webMd) {
             if (result.success) {
                 console.log('处理成功:', result.message);
                 
-                // 显示处理结果
-                if (result.data.success_count > 0) {
-                    alert(`成功处理 ${result.data.success_count} 条记录`);
+                                 // 显示处理结果 - 区分全部成功和部分成功的响应格式
+                 if (result.code === 200) {
+                     // 全部成功
+                     alert(`成功处理 ${result.data.length} 条记录`);
+                     
+                     // 显示详细信息
+                     result.data.forEach(record => {
+                         console.log(`人才: ${record.name_zh}`);
+                         if (record.image_path) {
+                             console.log(`图片已保存: ${record.image_path}`);
+                         }
+                         if (record.talent_profile) {
+                             console.log(`人才档案: ${record.talent_profile.substring(0, 50)}...`);
+                         }
+                     });
+                 } else if (result.code === 206) {
+                     // 部分成功
+                     alert(`部分成功: ${result.data.success_count}/${result.data.total_count} 条记录成功`);
+                     
+                     // 显示成功记录
+                     result.data.success_records.forEach(record => {
+                         console.log(`成功: ${record.data.name_zh}`);
+                         if (record.data.image_path) {
+                             console.log(`图片已保存: ${record.data.image_path}`);
+                         }
+                     });
+                     
+                     // 显示失败记录
+                     result.data.failed_records.forEach(record => {
+                         console.error(`失败: ${record.error}`);
+                     });
                 }
                 
                 if (result.data.failed_count > 0) {
@@ -402,11 +609,13 @@ const talentData = [
         name_zh: "张三",
         title_zh: "总经理",
         hotel_zh: "北京万豪酒店",
-        mobile: "13800138000"
+        mobile: "13800138000",
+        pic_url: "https://example.com/photos/zhangsan.jpg",
+        talent_profile: "资深酒店管理专家,拥有20年行业经验..."
     }
 ];
 
-const webContent = "# 人事任命公告\n\n张三被任命为总经理...";
+const webContent = "# 人事任命公告\n\n![张三照片](https://example.com/photos/zhangsan.jpg)\n\n张三被任命为总经理...";
 
 addWebpageTalent(talentData, webContent);
 ```
@@ -445,6 +654,15 @@ addWebpageTalent(talentData, webContent);
         <label>电子邮箱:</label>
         <input v-model="talent.email" type="email">
       </div>
+      <div class="form-group">
+        <label>照片URL:</label>
+        <input v-model="talent.pic_url" type="url" placeholder="https://example.com/photo.jpg">
+      </div>
+      <div class="form-group">
+        <label>人才档案:</label>
+        <textarea v-model="talent.talent_profile" rows="4" cols="50" 
+                  placeholder="请输入人才的详细档案信息,包括工作经历、专业技能等..."></textarea>
+      </div>
     </div>
     
     <!-- 网页内容 -->
@@ -466,19 +684,68 @@ addWebpageTalent(talentData, webContent);
       <h4>处理结果</h4>
       <p :class="result.success ? 'success' : 'error'">{{ result.message }}</p>
       
-      <div v-if="result.data">
-        <p>总记录数: {{ result.data.total_count }}</p>
-        <p>成功: {{ result.data.success_count }}</p>
-        <p>失败: {{ result.data.failed_count }}</p>
-        
-        <div v-if="result.data.failed_records.length > 0">
-          <h5>失败记录:</h5>
-          <ul>
-            <li v-for="record in result.data.failed_records" :key="record.index">
-              记录 {{ record.index }}: {{ record.error }}
-            </li>
-          </ul>
-        </div>
+              <div v-if="result.data">
+         <!-- 全部成功的显示 -->
+         <div v-if="result.code === 200">
+           <p>总记录数: {{ result.data.length }}</p>
+           <p>全部处理成功</p>
+           
+           <div class="success-records">
+             <h5>成功记录:</h5>
+             <ul>
+               <li v-for="(record, index) in result.data" :key="index">
+                 <strong>{{ record.name_zh }}</strong>
+                 <div v-if="record.image_path" class="detail">
+                   图片已保存: {{ record.image_path }}
+                 </div>
+                 <div v-if="record.talent_profile" class="detail">
+                   人才档案: {{ record.talent_profile.substring(0, 100) }}...
+                 </div>
+                 <div v-if="record.origin_source" class="detail">
+                   数据来源: {{ record.origin_source.type }} 
+                   ({{ record.origin_source.source_date }})
+                 </div>
+               </li>
+             </ul>
+           </div>
+         </div>
+         
+         <!-- 部分成功的显示 -->
+         <div v-if="result.code === 206">
+           <p>总记录数: {{ result.data.total_count }}</p>
+           <p>成功: {{ result.data.success_count }}</p>
+           <p>失败: {{ result.data.failed_count }}</p>
+           
+           <!-- 成功记录详情 -->
+           <div v-if="result.data.success_records.length > 0" class="success-records">
+             <h5>成功记录:</h5>
+             <ul>
+               <li v-for="record in result.data.success_records" :key="record.index">
+                 <strong>{{ record.data.name_zh }}</strong> - {{ record.message }}
+                 <div v-if="record.data.image_path" class="detail">
+                   图片已保存: {{ record.data.image_path }}
+                 </div>
+                 <div v-if="record.data.talent_profile" class="detail">
+                   人才档案: {{ record.data.talent_profile.substring(0, 100) }}...
+                 </div>
+                                    <div v-if="record.data.origin_source" class="detail">
+                     数据来源: {{ record.data.origin_source.type }} 
+                     ({{ record.data.origin_source.source_date }})
+                   </div>
+               </li>
+             </ul>
+           </div>
+           
+           <!-- 失败记录详情 -->
+           <div v-if="result.data.failed_records.length > 0" class="failed-records">
+             <h5>失败记录:</h5>
+             <ul>
+               <li v-for="record in result.data.failed_records" :key="record.index">
+                 记录 {{ record.index }}: {{ record.error }}
+               </li>
+             </ul>
+           </div>
+         </div>
       </div>
     </div>
   </div>
@@ -502,7 +769,9 @@ export default {
           email: '',
           phone: '',
           address_zh: '',
-          address_en: ''
+          address_en: '',
+          pic_url: '',
+          talent_profile: ''
         }
       ],
       webMd: '',
@@ -524,7 +793,9 @@ export default {
         email: '',
         phone: '',
         address_zh: '',
-        address_en: ''
+        address_en: '',
+        pic_url: '',
+        talent_profile: ''
       });
     },
     
@@ -553,6 +824,35 @@ export default {
         
         if (result.success) {
           this.$message.success(result.message);
+          
+                     // 显示详细处理结果
+           if (result.code === 200) {
+             console.log('成功处理的记录详情:');
+             result.data.forEach(record => {
+               console.log(`- ${record.name_zh}:`);
+               if (record.image_path) {
+                 console.log(`  图片: ${record.image_path}`);
+               }
+               if (record.talent_profile) {
+                 console.log(`  档案: ${record.talent_profile.substring(0, 50)}...`);
+               }
+             });
+           } else if (result.code === 206) {
+             console.log('部分成功的记录详情:');
+             result.data.success_records.forEach(record => {
+               console.log(`- ${record.data.name_zh}: ${record.message}`);
+               if (record.data.image_path) {
+                 console.log(`  图片: ${record.data.image_path}`);
+               }
+             });
+             
+             if (result.data.failed_records.length > 0) {
+               console.log('失败的记录详情:');
+               result.data.failed_records.forEach(record => {
+                 console.error(`- 记录 ${record.index}: ${record.error}`);
+               });
+             }
+           }
         } else {
           this.$message.error(result.message);
         }
@@ -646,6 +946,16 @@ export default {
 .error {
   color: #dc3545;
 }
+
+.success-records, .failed-records {
+  margin-top: 15px;
+}
+
+.detail {
+  font-size: 12px;
+  color: #666;
+  margin-left: 10px;
+}
 </style>
 ```
 
@@ -667,12 +977,44 @@ export default {
 **错误**: `上传网页内容到MinIO失败`
 **解决**: 检查MinIO服务状态和配置
 
-#### 4. 数据库错误
+#### 4. 图片下载失败
+**错误**: `人员XXX的图片处理失败`
+**解决**: 检查pic_url是否有效,网络连接是否正常。图片下载失败不会影响其他数据的处理
+
+#### 5. 数据库错误
 **错误**: `数据库操作失败`
 **解决**: 检查数据库连接和表结构
 
 ---
 
+## 数据字段详解
+
+### 新增字段说明
+
+#### `origin_source` (JSON类型)
+系统自动生成的数据来源信息,包含:
+- **`type`**: 数据来源类型,网页人才固定为"webpage_talent"
+- **`minio_path`**: 原始网页内容在MinIO中的存储路径
+- **`source_date`**: 数据处理时间
+
+#### `talent_profile` (文本类型)
+人才档案信息,可包含:
+- 个人简介和背景
+- 工作经历和职业发展
+- 专业技能和资质认证
+- 教育背景
+- 获奖情况和荣誉
+- 其他相关信息
+
+#### `image_path` (字符串类型)
+如果提供了`pic_url`,系统会:
+1. 自动下载图片
+2. 保存到MinIO的`talent_photos/`目录
+3. 生成唯一的文件名
+4. 将MinIO路径保存到此字段
+
+---
+
 ## 注意事项
 
 1. **数据完整性**: 确保talent_list中每个对象都包含name_zh字段
@@ -681,15 +1023,28 @@ export default {
 4. **重复检查**: 系统会自动检查重复记录并相应处理
 5. **存储路径**: 网页内容会自动保存到MinIO的`webpage_talent/`目录下
 6. **数据溯源**: origin_source字段保存了完整的数据来源信息,便于后续追溯
+7. **图片处理**: 
+   - 支持常见图片格式(JPG、PNG、GIF、WebP)
+   - 图片下载失败不会影响其他数据处理
+   - 图片文件名会包含人员姓名、时间戳和唯一标识
+8. **人才档案**: talent_profile字段支持存储大量文本信息,建议合理控制内容长度
+9. **自动字段**: origin_source字段由系统自动生成,无需在请求中提供
+10. **向后兼容**: 新增字段都是可选的,现有的API调用不会受到影响
 
 ---
 
 ## 版本信息
 
-- **API版本**: v1.0
-- **文档版本**: 1.0.0
-- **创建日期**: 2024年
-- **最后更新**: 2024年
+- **API版本**: v1.1
+- **文档版本**: 1.1.0
+- **创建日期**: 2025年
+- **最后更新**: 2025年07月10日
+- **更新内容**: 
+  - 新增 `origin_source` 字段支持
+  - 新增 `talent_profile` 字段支持
+  - 新增 `pic_url` 字段和图片自动下载功能
+  - 更新响应示例和代码示例
+  - 增加详细的字段说明和注意事项
 
 ---
 

+ 4 - 0
app/core/data_parse/parse.py

@@ -46,6 +46,7 @@ class BusinessCard(db.Model):
     career_path = db.Column(db.JSON)  # 职业轨迹,JSON格式
     brand_group = db.Column(db.String(200))  # 品牌组合
     origin_source = db.Column(db.JSON)  # 原始资料记录,JSON格式
+    talent_profile = db.Column(db.Text)  # 人才档案,文本格式
     created_at = db.Column(db.DateTime, default=datetime.now, nullable=False)
     updated_at = db.Column(db.DateTime, onupdate=datetime.now)
     updated_by = db.Column(db.String(50))
@@ -79,6 +80,7 @@ class BusinessCard(db.Model):
             'career_path': self.career_path,
             'brand_group': self.brand_group,
             'origin_source': self.origin_source,
+            'talent_profile': self.talent_profile,
             'created_at': self.created_at.strftime('%Y-%m-%d %H:%M:%S') if self.created_at else None,
             'updated_at': self.updated_at.strftime('%Y-%m-%d %H:%M:%S') if self.updated_at else None,
             'updated_by': self.updated_by,
@@ -424,6 +426,7 @@ def create_main_card_with_duplicates(extracted_data, minio_path, suspected_dupli
             career_path=initial_career_path,  # 包含图片路径的职业轨迹
             brand_group=extracted_data.get('brand_group', ''),
             origin_source=extracted_data.get('origin_source'),  # 原始资料记录
+            talent_profile=extracted_data.get('talent_profile', ''),  # 人才档案
             status='active',
             updated_by='system'
         )
@@ -1060,6 +1063,7 @@ def update_business_card(card_id, data):
         card.career_path = data.get('career_path', card.career_path)  # 更新职业轨迹
         card.brand_group = data.get('brand_group', card.brand_group)  # 更新品牌组合
         card.origin_source = data.get('origin_source', card.origin_source)  # 更新原始资料记录
+        card.talent_profile = data.get('talent_profile', card.talent_profile)  # 更新人才档案
         card.updated_by = data.get('updated_by', 'user')  # 可以根据实际情况修改为当前用户
         
         # 保存更新

+ 2 - 0
app/core/data_parse/parse_card.py

@@ -251,6 +251,7 @@ def add_business_card(card_data, image_file=None):
                 existing_card.brand_group = card_data.get('brand_group', existing_card.brand_group)
                 existing_card.image_path = minio_path  # 更新为最新的图片路径
                 existing_card.origin_source = card_data.get('origin_source', existing_card.origin_source)  # 更新原始资料记录
+                existing_card.talent_profile = card_data.get('talent_profile', existing_card.talent_profile)  # 更新人才档案
                 existing_card.updated_by = 'system'
                 
                 # 更新职业轨迹,传递图片路径
@@ -344,6 +345,7 @@ def add_business_card(card_data, image_file=None):
                     career_path=initial_career_path,  # 包含图片路径的职业轨迹
                     brand_group=card_data.get('brand_group', ''),
                     origin_source=card_data.get('origin_source'),  # 原始资料记录
+                    talent_profile=card_data.get('talent_profile', ''),  # 人才档案
                     status='active',
                     updated_by='system'
                 )

+ 129 - 15
app/core/data_parse/parse_web.py

@@ -4,6 +4,7 @@ import logging
 import re
 import uuid
 import boto3
+import requests
 from botocore.config import Config
 from io import BytesIO
 from datetime import datetime
@@ -187,8 +188,7 @@ def add_webpage_talent(talent_list, web_md):
                 talent_data['origin_source'] = {
                     'type': 'webpage_talent',
                     'minio_path': minio_md_path,
-                    'source_date': datetime.now().strftime('%Y-%m-%d %H:%M:%S'),
-                    'web_md_content': web_md[:1000]  # 保存部分网页内容作为参考
+                    'source_date': datetime.now().strftime('%Y-%m-%d %H:%M:%S')
                 }
                 
                 # 处理business_card记录
@@ -223,11 +223,13 @@ def add_webpage_talent(talent_list, web_md):
         
         # 生成最终结果
         if results['success_count'] == results['total_count']:
+            # 全部成功时,返回业务卡片记录数组,与add_business_card保持一致
+            success_data = [record['data'] for record in results['success_records']]
             return {
                 'code': 200,
                 'success': True,
                 'message': f'所有{results["total_count"]}条人才记录处理成功',
-                'data': results
+                'data': success_data
             }
         elif results['success_count'] > 0:
             return {
@@ -255,6 +257,104 @@ def add_webpage_talent(talent_list, web_md):
         }
 
 
+def download_and_upload_image_to_minio(pic_url, person_name):
+    """
+    从URL下载图片并上传到MinIO
+    
+    Args:
+        pic_url (str): 图片的URL地址
+        person_name (str): 人员姓名,用于生成文件名
+        
+    Returns:
+        str: MinIO中的图片路径,如果失败返回None
+    """
+    try:
+        if not pic_url or not pic_url.strip():
+            logging.info("pic_url为空,跳过图片下载")
+            return None
+            
+        # 验证URL格式
+        if not pic_url.startswith(('http://', 'https://')):
+            logging.warning(f"无效的图片URL格式: {pic_url}")
+            return None
+        
+        logging.info(f"开始下载图片: {pic_url}")
+        
+        # 设置请求头,模拟浏览器请求
+        headers = {
+            'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/91.0.4472.124 Safari/537.36'
+        }
+        
+        # 下载图片
+        response = requests.get(pic_url, headers=headers, timeout=30)
+        response.raise_for_status()
+        
+        # 检查响应内容类型
+        content_type = response.headers.get('content-type', '').lower()
+        if not content_type.startswith('image/'):
+            logging.warning(f"URL返回的不是图片内容,content-type: {content_type}")
+            return None
+        
+        # 获取图片数据
+        image_data = response.content
+        if len(image_data) == 0:
+            logging.warning("下载的图片数据为空")
+            return None
+        
+        # 从URL或content-type推断文件扩展名
+        file_extension = '.jpg'  # 默认扩展名
+        if 'png' in content_type:
+            file_extension = '.png'
+        elif 'gif' in content_type:
+            file_extension = '.gif'
+        elif 'webp' in content_type:
+            file_extension = '.webp'
+        elif pic_url.lower().endswith(('.jpg', '.jpeg', '.png', '.gif', '.webp')):
+            file_extension = '.' + pic_url.split('.')[-1].lower()
+        
+        # 生成文件名
+        timestamp = datetime.now().strftime('%Y%m%d_%H%M%S')
+        unique_id = uuid.uuid4().hex[:8]
+        safe_name = re.sub(r'[^\w\s-]', '', person_name.replace(' ', '_'))[:20]  # 清理人名用作文件名
+        filename = f"talent_photo_{safe_name}_{timestamp}_{unique_id}{file_extension}"
+        
+        # 获取MinIO客户端
+        minio_client = get_minio_client()
+        if not minio_client:
+            logging.error("无法获取MinIO客户端")
+            return None
+        
+        # 将图片数据转换为字节流
+        image_stream = BytesIO(image_data)
+        
+        # 上传到MinIO
+        minio_path = f"talent_photos/{filename}"
+        logging.info(f"开始上传图片到MinIO: {minio_path}")
+        
+        minio_client.put_object(
+            Bucket=minio_bucket,
+            Key=minio_path,
+            Body=image_stream,
+            ContentType=content_type,
+            Metadata={
+                'original_url': pic_url,
+                'person_name': person_name,
+                'upload_time': datetime.now().isoformat(),
+                'content_type': 'talent_photo'
+            }
+        )
+        
+        logging.info(f"图片成功上传到MinIO: {minio_path}")
+        return minio_path
+        
+    except requests.RequestException as e:
+        logging.error(f"下载图片失败 ({pic_url}): {str(e)}")
+        return None
+    except Exception as e:
+        logging.error(f"上传图片到MinIO失败: {str(e)}", exc_info=True)
+        return None
+
+
 def process_single_talent_card(talent_data, minio_md_path):
     """
     处理单个人才的名片记录创建
@@ -267,6 +367,16 @@ def process_single_talent_card(talent_data, minio_md_path):
         dict: 处理结果
     """
     try:
+        # 下载并上传图片到MinIO
+        image_path = None
+        if talent_data.get('pic_url'):
+            person_name = talent_data.get('name_zh', 'unknown')
+            image_path = download_and_upload_image_to_minio(talent_data['pic_url'], person_name)
+            if image_path:
+                logging.info(f"成功处理人员 {person_name} 的图片,MinIO路径: {image_path}")
+            else:
+                logging.warning(f"人员 {person_name} 的图片处理失败")
+        
         # 检查重复记录
         try:
             duplicate_check = check_duplicate_business_card(talent_data)
@@ -340,18 +450,23 @@ def process_single_talent_card(talent_data, minio_md_path):
             existing_card.brand_group = talent_data.get('brand_group', existing_card.brand_group)
             existing_card.updated_by = 'webpage_talent_system'
             
-            existing_card.updated_by = 'webpage_talent_system'
+            # 更新图片路径
+            if image_path:
+                existing_card.image_path = image_path
+            
+            # 更新人才档案
+            if talent_data.get('talent_profile'):
+                existing_card.talent_profile = talent_data.get('talent_profile')
             
             # 设置origin_source为原始资料记录
             existing_card.origin_source = {
                 'type': 'webpage_talent',
                 'minio_path': minio_md_path,
-                'source_date': datetime.now().strftime('%Y-%m-%d %H:%M:%S'),
-                'talent_data': talent_data
+                'source_date': datetime.now().strftime('%Y-%m-%d %H:%M:%S')
             }
             
-            # 更新职业轨迹,传递网页来源信息
-            existing_card.career_path = update_career_path(existing_card, talent_data, image_path='')
+            # 更新职业轨迹,传递图片路径
+            existing_card.career_path = update_career_path(existing_card, talent_data, image_path=image_path or '')
             
             db.session.commit()
             
@@ -367,7 +482,7 @@ def process_single_talent_card(talent_data, minio_md_path):
             # 创建新记录作为主记录,并保存疑似重复记录信息
             main_card, duplicate_record = create_main_card_with_duplicates(
                 talent_data, 
-                None,  # 网页提取没有图片路径
+                image_path,  # 传递图片路径
                 duplicate_check['suspected_duplicates'],
                 duplicate_check['reason']
             )
@@ -378,8 +493,7 @@ def process_single_talent_card(talent_data, minio_md_path):
             main_card.origin_source = {
                 'type': 'webpage_talent',
                 'minio_path': minio_md_path,
-                'source_date': datetime.now().strftime('%Y-%m-%d %H:%M:%S'),
-                'talent_data': talent_data
+                'source_date': datetime.now().strftime('%Y-%m-%d %H:%M:%S')
             }
             db.session.commit()
             
@@ -405,7 +519,7 @@ def process_single_talent_card(talent_data, minio_md_path):
                 'hotel_en': talent_data.get('hotel_en', ''),
                 'title_zh': talent_data.get('title_zh', ''),
                 'title_en': talent_data.get('title_en', ''),
-                'image_path': '',  # 网页提取没有图片路径
+                'image_path': image_path or '',  # 使用下载的图片路径
                 'source': 'webpage_extraction'
             }
             initial_career_path = [initial_entry]
@@ -442,15 +556,15 @@ def process_single_talent_card(talent_data, minio_md_path):
                 age=age_value,
                 native_place=talent_data.get('native_place', ''),
                 residence=talent_data.get('residence', ''),
-                image_path=None,  # 网页提取没有图片路径
+                image_path=image_path,  # 使用下载的图片路径
                 career_path=initial_career_path,
                 brand_group=talent_data.get('brand_group', ''),
                 origin_source={
                     'type': 'webpage_talent',
                     'minio_path': minio_md_path,
-                    'source_date': datetime.now().strftime('%Y-%m-%d %H:%M:%S'),
-                    'talent_data': talent_data
+                    'source_date': datetime.now().strftime('%Y-%m-%d %H:%M:%S')
                 },
+                talent_profile=talent_data.get('talent_profile', ''),  # 人才档案
                 status='active',
                 updated_by='webpage_talent_system'
             )