Explorar o código

修改MinIO配置,使用代码定义的MinIO访问参数。

maxiaolong hai 5 días
pai
achega
8c419c08d4
Modificáronse 4 ficheiros con 729 adicións e 4 borrados
  1. 338 0
      api-talent-tags.md
  2. 145 1
      app/api/data_parse/routes.py
  3. 3 3
      app/config/config.py
  4. 243 0
      app/core/data_parse/parse.py

+ 338 - 0
api-talent-tags.md

@@ -0,0 +1,338 @@
+# 人才标签管理 API 文档
+
+本文档描述了与人才标签关系管理相关的 API 接口,包括获取人才标签和更新人才标签关系。
+
+## API 接口: `/talent-get-tags/{talent_id}`
+
+该 API 接口允许您根据人才 ID 获取关联的标签信息。
+
+### 请求详情
+
+- **请求方法**: GET
+- **URL**: `/api/data_parse/talent-get-tags/{talent_id}`
+- **Content-Type**: application/json
+
+### 路径参数
+
+| 参数名 | 类型 | 是否必需 | 描述 |
+|-------|------|----------|------|
+| `talent_id` | 整数 | 是 | 人才节点在 Neo4j 数据库中的 ID |
+
+#### 请求示例
+```
+GET /api/data_parse/talent-get-tags/12345
+```
+
+### 响应格式
+
+API 返回具有以下结构的 JSON 对象:
+
+| 字段 | 类型 | 描述 |
+|-----|------|------|
+| `code` | 整数 | HTTP 状态码(200 表示成功,4xx/5xx 表示错误)|
+| `success` | 布尔值 | 操作是否成功 |
+| `message` | 字符串 | 结果描述或错误信息 |
+| `data` | 数组 | 包含人才标签关系的对象数组 |
+
+#### 成功响应示例
+```json
+{
+  "code": 200,
+  "success": true,
+  "message": "获取人才标签成功",
+  "data": [
+    {
+      "talent": 12345,
+      "tag": "市场营销"
+    },
+    {
+      "talent": 12345,
+      "tag": "酒店管理"
+    }
+  ]
+}
+```
+
+#### 错误响应示例
+```json
+{
+  "code": 500,
+  "success": false,
+  "message": "获取人才标签失败: 人才节点ID 12345 不存在",
+  "data": []
+}
+```
+
+### 响应状态码
+
+| 状态码 | 描述 |
+|-------|------|
+| 200 | 请求成功 |
+| 500 | 服务器错误(处理错误或数据库连接失败)|
+
+### 错误处理
+
+如果发生错误,响应将包括:
+- 非 200 状态码
+- `success` 设置为 `false`
+- 描述性错误信息
+- 空的 `data` 数组
+
+
+## API 接口: `/talent-update-tags`
+
+该 API 接口允许您创建或更新人才与标签之间的关系。如果指定的标签不存在,系统会自动创建标签节点。
+
+### 请求详情
+
+- **请求方法**: POST
+- **URL**: `/api/data_parse/talent-update-tags`
+- **Content-Type**: application/json
+
+### 请求参数
+
+请求体应为 JSON 数组,包含多个对象,每个对象指定一个人才与标签的关系:
+
+| 字段 | 类型 | 是否必需 | 描述 |
+|-----|------|----------|------|
+| `talent` | 整数 | 是 | 人才节点在 Neo4j 数据库中的 ID |
+| `tag` | 字符串 | 是 | 标签名称 |
+
+#### 请求体示例
+```json
+[
+  {"talent": 12345, "tag": "市场营销"},
+  {"talent": 12345, "tag": "酒店管理"},
+  {"talent": 67890, "tag": "领导力"}
+]
+```
+
+### 响应格式
+
+API 返回具有以下结构的 JSON 对象:
+
+| 字段 | 类型 | 描述 |
+|-----|------|------|
+| `code` | 整数 | HTTP 状态码(200 表示成功,206 表示部分成功,4xx/5xx 表示错误)|
+| `success` | 布尔值 | 操作是否成功 |
+| `message` | 字符串 | 结果描述或错误信息 |
+| `data` | 对象 | 包含成功和失败信息的详细数据 |
+
+#### 成功响应示例
+```json
+{
+  "code": 200,
+  "success": true,
+  "message": "成功创建或更新了 3 个标签关系",
+  "data": {
+    "success_count": 3,
+    "total_count": 3,
+    "failed_items": []
+  }
+}
+```
+
+#### 部分成功响应示例
+```json
+{
+  "code": 206,
+  "success": true,
+  "message": "部分成功: 创建或更新了 2/3 个标签关系",
+  "data": {
+    "success_count": 2,
+    "total_count": 3,
+    "failed_items": [
+      {"talent": 67890, "tag": "领导力"}
+    ]
+  }
+}
+```
+
+#### 错误响应示例
+```json
+{
+  "code": 400,
+  "success": false,
+  "message": "参数格式错误,需要JSON数组",
+  "data": null
+}
+```
+
+### 响应状态码
+
+| 状态码 | 描述 |
+|-------|------|
+| 200 | 请求完全成功 |
+| 206 | 部分内容(部分更新成功,部分失败)|
+| 400 | 错误请求(缺少或无效参数)|
+| 404 | 未找到(指定的人才节点不存在)|
+| 500 | 服务器错误(处理错误或数据库连接失败)|
+
+### 错误处理
+
+如果发生错误,响应将包括:
+- 相应的状态码
+- `success` 设置为 `false`(对于完全失败的情况)
+- 描述性错误信息
+- 失败项目的详细信息(对于部分成功的情况)
+
+## 前端集成示例
+
+### 获取人才标签
+
+```javascript
+// 使用 Axios 的 Vue.js 示例
+import axios from 'axios';
+
+export default {
+  data() {
+    return {
+      talentId: null,
+      talentTags: [],
+      isLoading: false,
+      error: null
+    }
+  },
+  methods: {
+    async fetchTalentTags(talentId) {
+      this.isLoading = true;
+      this.error = null;
+      
+      try {
+        const response = await axios.get(`/api/data_parse/talent-get-tags/${talentId}`);
+        
+        if (response.data.success) {
+          this.talentTags = response.data.data;
+        } else {
+          this.error = response.data.message;
+        }
+      } catch (error) {
+        this.error = error.response?.data?.message || '获取标签失败,请稍后重试';
+      } finally {
+        this.isLoading = false;
+      }
+    }
+  }
+}
+```
+
+### 更新人才标签
+
+```javascript
+// 使用 Axios 的 Vue.js 示例
+import axios from 'axios';
+
+export default {
+  data() {
+    return {
+      talentTagsData: [
+        {talent: 12345, tag: "市场营销"},
+        {talent: 12345, tag: "酒店管理"}
+      ],
+      isUpdating: false,
+      updateResult: null,
+      error: null
+    }
+  },
+  methods: {
+    async updateTalentTags() {
+      this.isUpdating = true;
+      this.error = null;
+      this.updateResult = null;
+      
+      try {
+        const response = await axios.post('/api/data_parse/talent-update-tags', this.talentTagsData);
+        
+        this.updateResult = {
+          success: response.data.success,
+          message: response.data.message,
+          details: response.data.data
+        };
+      } catch (error) {
+        this.error = error.response?.data?.message || '更新标签失败,请稍后重试';
+      } finally {
+        this.isUpdating = false;
+      }
+    }
+  }
+}
+```
+
+```html
+<!-- Vue.js 模板示例:更新人才标签 -->
+<template>
+  <div class="talent-tags-update">
+    <h2>更新人才标签</h2>
+    
+    <div class="tag-input-form">
+      <div v-for="(item, index) in talentTagsData" :key="index" class="tag-input-row">
+        <div class="form-group">
+          <label>人才 ID:</label>
+          <input type="number" v-model.number="item.talent" />
+        </div>
+        <div class="form-group">
+          <label>标签名称:</label>
+          <input type="text" v-model="item.tag" />
+        </div>
+        <button @click="removeTag(index)" class="remove-btn">删除</button>
+      </div>
+      
+      <button @click="addTagRow" class="add-btn">添加标签</button>
+      <button 
+        @click="updateTalentTags" 
+        :disabled="isUpdating || !isValidData"
+        class="submit-btn"
+      >
+        {{ isUpdating ? '更新中...' : '提交更新' }}
+      </button>
+    </div>
+    
+    <div v-if="error" class="error-message">
+      {{ error }}
+    </div>
+    
+    <div v-if="updateResult" class="result-summary">
+      <h3>更新结果</h3>
+      <p :class="{'success': updateResult.success, 'partial': updateResult.success && updateResult.details?.failed_items?.length > 0}">
+        {{ updateResult.message }}
+      </p>
+      
+      <div v-if="updateResult.details?.failed_items?.length > 0" class="failed-items">
+        <h4>失败项目:</h4>
+        <ul>
+          <li v-for="(item, index) in updateResult.details.failed_items" :key="index">
+            人才 ID: {{ item.talent }}, 标签: {{ item.tag }}
+          </li>
+        </ul>
+      </div>
+    </div>
+  </div>
+</template>
+```
+
+## 使用场景示例
+
+### 场景一:获取人才标签
+
+1. 展示人才详情页面时,获取该人才的所有标签
+2. 根据获取的标签展示标签云或标签列表
+3. 让用户了解人才的专业领域和特长
+
+### 场景二:批量更新人才标签
+
+1. 人力资源部门根据人才评估结果为多个人才分配标签
+2. 在人才管理系统中,为选定的人才添加或更新专业能力标签
+3. 在知识图谱维护工具中,批量创建人才与标签的关联关系
+
+### 场景三:人才分类管理
+
+1. 先通过标签接口获取人才现有标签
+2. 根据业务需求调整和更新人才标签
+3. 利用更新后的标签体系进行人才筛选和分类
+
+## 注意事项
+
+- 更新操作使用 MERGE 语句,如果关系已存在则更新,不存在则创建
+- 如果指定的标签不存在,系统会自动创建标签节点,无需单独创建
+- 批量更新时,如果某些项目失败,其他项目仍会继续处理
+- 当传入大量数据时,建议分批处理以避免请求超时 

+ 145 - 1
app/api/data_parse/routes.py

@@ -1,6 +1,6 @@
 from flask import jsonify, request, make_response, Blueprint, current_app, send_file
 from app.api.data_parse import bp
-from app.core.data_parse.parse import parse_data, process_business_card, update_business_card, get_business_cards, update_business_card_status, create_talent_tag, get_talent_tag_list, update_talent_tag, delete_talent_tag, query_neo4j_graph
+from app.core.data_parse.parse import parse_data, process_business_card, update_business_card, get_business_cards, update_business_card_status, create_talent_tag, get_talent_tag_list, update_talent_tag, delete_talent_tag, query_neo4j_graph, talent_get_tags, talent_update_tags
 from app.config.config import DevelopmentConfig, ProductionConfig
 import logging
 import boto3
@@ -460,3 +460,147 @@ def query_kg():
             'data': []
         }), 500
 
+@bp.route('/talent-get-tags/<int:talent_id>', methods=['GET'])
+def talent_get_tags_route(talent_id):
+    """
+    获取人才标签的API接口
+    
+    路径参数:
+        - talent_id: 人才节点ID
+        
+    返回:
+        - JSON: 包含人才关联的标签列表和处理状态
+    """
+    try:
+        # 调用业务逻辑函数获取人才标签
+        result = talent_get_tags(talent_id)
+        
+        # 根据处理结果设置HTTP状态码
+        status_code = 200 if result['success'] else 500
+        
+        return jsonify(result), status_code
+        
+    except Exception as e:
+        logger.error(f"获取人才标签失败: {str(e)}")
+        return jsonify({
+            'code': 500,
+            'success': False,
+            'message': f"获取人才标签失败: {str(e)}",
+            'data': []
+        }), 500
+
+@bp.route('/talent-update-tags', methods=['POST'])
+def talent_update_tags_route():
+    """
+    更新人才标签关系的API接口
+    
+    请求参数:
+        - JSON数组,包含talent和tag字段的对象列表
+          例如: [
+              {"talent": 12345, "tag": "市场营销"},
+              {"talent": 12345, "tag": "酒店管理"}
+          ]
+        
+    返回:
+        - JSON: 包含更新结果的状态信息
+    """
+    try:
+        # 获取请求数据
+        data = request.json
+        
+        if not data:
+            return jsonify({
+                'code': 400,
+                'success': False,
+                'message': '请求数据为空',
+                'data': None
+            }), 400
+        
+        # 调用业务逻辑函数处理标签关系更新
+        result = talent_update_tags(data)
+        
+        # 根据处理结果设置HTTP状态码
+        if result['code'] == 200:
+            status_code = 200
+        elif result['code'] == 206:
+            status_code = 206  # Partial Content
+        elif result['code'] == 400:
+            status_code = 400  # Bad Request
+        elif result['code'] == 404:
+            status_code = 404  # Not Found
+        else:
+            status_code = 500  # Internal Server Error
+        
+        return jsonify(result), status_code
+        
+    except Exception as e:
+        logger.error(f"更新人才标签关系失败: {str(e)}")
+        return jsonify({
+            'code': 500,
+            'success': False,
+            'message': f"更新人才标签关系失败: {str(e)}",
+            'data': None
+        }), 500
+
+# 测试MinIO连接
+def test_minio_connection():
+    """测试MinIO连接是否正常"""
+    try:
+        client = get_minio_client()
+        if client.bucket_exists(minio_bucket):
+            return {
+                'success': True,
+                'message': f'连接MinIO服务器成功,存储桶 {minio_bucket} 存在',
+                'config': {
+                    'host': config.MINIO_HOST,
+                    'bucket': minio_bucket,
+                    'secure': use_ssl
+                }
+            }
+        else:
+            return {
+                'success': False,
+                'message': f'连接MinIO服务器成功,但存储桶 {minio_bucket} 不存在',
+                'config': {
+                    'host': config.MINIO_HOST,
+                    'bucket': minio_bucket,
+                    'secure': use_ssl
+                }
+            }
+    except Exception as e:
+        return {
+            'success': False,
+            'message': f'连接MinIO服务器失败: {str(e)}',
+            'config': {
+                'host': config.MINIO_HOST,
+                'bucket': minio_bucket,
+                'secure': use_ssl
+            }
+        }
+
+# MinIO测试接口
+@bp.route('/test-minio-connection', methods=['GET'])
+def test_minio_connection_route():
+    """
+    测试MinIO连接的API接口
+    
+    返回:
+        - JSON: 包含连接测试结果
+    """
+    try:
+        result = test_minio_connection()
+        status_code = 200 if result['success'] else 500
+        return jsonify(result), status_code
+        
+    except Exception as e:
+        logger.error(f"测试MinIO连接失败: {str(e)}")
+        return jsonify({
+            'success': False,
+            'message': f'测试MinIO连接失败: {str(e)}',
+            'config': {
+                'host': config.MINIO_HOST,
+                'bucket': minio_bucket,
+                'secure': use_ssl
+            }
+        }), 500
+

+ 3 - 3
app/config/config.py

@@ -95,9 +95,9 @@ class ProductionConfig(BaseConfig):
     PORT = 80
     
     # 生产环境 MinIO 配置
-    MINIO_HOST = os.environ.get('MINIO_HOST', '192.168.3.143:9000')
-    MINIO_USER = os.environ.get('MINIO_USER', 'citu-dataops-acc-key')
-    MINIO_PASSWORD = os.environ.get('MINIO_PASSWORD', 'citu-dataops-secret-key')
+    MINIO_HOST = '192.168.3.143:9000'
+    MINIO_USER = 'citu-dataops-acc-key'
+    MINIO_PASSWORD = 'citu-dataops-secret-key'
     MINIO_SECURE = False
     MINIO_BUCKET = 'dataops-bucket'
     PREFIX = ''

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

@@ -1432,4 +1432,247 @@ def query_neo4j_graph(query_requirement):
             'success': False,
             'message': error_msg,
             'data': []
+        }
+
+def talent_get_tags(talent_id):
+    """
+    根据talent ID获取人才节点关联的标签
+    
+    Args:
+        talent_id (int): 人才节点ID
+        
+    Returns:
+        dict: 包含人才ID和关联标签的字典,JSON格式
+    """
+    try:
+        # 导入必要的模块
+        from app.services.neo4j_driver import neo4j_driver
+        
+        # 准备查询返回数据
+        response_data = {
+            'code': 200,
+            'success': True,
+            'message': '获取人才标签成功',
+            'data': []
+        }
+        
+        # 构建Cypher查询语句,获取人才节点关联的标签
+        cypher_query = """
+        MATCH (t:talent)-[r:BELONGS_TO]->(tag:talent_tag)
+        WHERE id(t) = $talent_id
+        RETURN id(t) as talent_id, tag.name as tag_name
+        """
+        
+        # 执行查询
+        with neo4j_driver.get_session() as session:
+            result = session.run(cypher_query, talent_id=int(talent_id))
+            records = list(result)
+            
+            # 如果没有查询到标签,返回空数组
+            if not records:
+                response_data['message'] = f'人才ID {talent_id} 没有关联的标签'
+                return response_data
+            
+            # 处理查询结果
+            for record in records:
+                talent_tag = {
+                    'talent': record['talent_id'],
+                    'tag': record['tag_name']
+                }
+                response_data['data'].append(talent_tag)
+            
+        return response_data
+    
+    except Exception as e:
+        error_msg = f"获取人才标签失败: {str(e)}"
+        logging.error(error_msg, exc_info=True)
+        
+        return {
+            'code': 500,
+            'success': False,
+            'message': error_msg,
+            'data': []
+        }
+
+def talent_update_tags(data):
+    """
+    根据传入的JSON数据为人才节点创建与标签的BELONGS_TO关系
+    
+    Args:
+        data (list): 包含talent和tag字段的对象列表
+            例如: [
+                {"talent": 12345, "tag": "市场营销"},
+                {"talent": 12345, "tag": "酒店管理"}
+            ]
+        
+    Returns:
+        dict: 操作结果和状态信息
+    """
+    try:
+        # 导入必要的模块
+        from app.services.neo4j_driver import neo4j_driver
+        
+        # 验证输入参数
+        if not isinstance(data, list):
+            return {
+                'code': 400,
+                'success': False,
+                'message': '参数格式错误,需要JSON数组',
+                'data': None
+            }
+        
+        if len(data) == 0:
+            return {
+                'code': 400,
+                'success': False,
+                'message': '数据列表为空',
+                'data': None
+            }
+        
+        # 获取当前时间
+        current_time = datetime.now().strftime('%Y-%m-%d %H:%M:%S')
+        
+        # 成功和失败计数
+        success_count = 0
+        failed_items = []
+        
+        # 按talent分组处理数据
+        talent_tags = {}
+        for item in data:
+            # 验证每个项目的格式
+            if not isinstance(item, dict) or 'talent' not in item or 'tag' not in item:
+                failed_items.append(item)
+                continue
+                
+            talent_id = item.get('talent')
+            tag_name = item.get('tag')
+            
+            # 验证talent_id和tag_name的值
+            if not talent_id or not tag_name or not isinstance(tag_name, str):
+                failed_items.append(item)
+                continue
+                
+            # 按talent_id分组
+            if talent_id not in talent_tags:
+                talent_tags[talent_id] = []
+                
+            talent_tags[talent_id].append(tag_name)
+        
+        with neo4j_driver.get_session() as session:
+            # 处理每个talent及其标签
+            for talent_id, tags in talent_tags.items():
+                # 首先验证talent节点是否存在
+                check_talent_query = """
+                MATCH (t:talent) 
+                WHERE id(t) = $talent_id
+                RETURN t
+                """
+                talent_result = session.run(check_talent_query, talent_id=int(talent_id))
+                if not talent_result.single():
+                    # 该talent不存在,记录失败项并继续下一个talent
+                    for tag in tags:
+                        failed_items.append({'talent': talent_id, 'tag': tag})
+                    continue
+                
+                # 处理每个标签
+                for tag_name in tags:
+                    try:
+                        # 1. 查找或创建标签节点
+                        # 先查找是否存在该标签
+                        find_tag_query = """
+                        MATCH (tag:talent_tag)
+                        WHERE tag.name = $tag_name
+                        RETURN id(tag) as tag_id
+                        """
+                        tag_result = session.run(find_tag_query, tag_name=tag_name)
+                        tag_record = tag_result.single()
+                        
+                        if tag_record:
+                            tag_id = tag_record['tag_id']
+                        else:
+                            # 创建新标签
+                            create_tag_query = """
+                            CREATE (tag:talent_tag {name: $name, category: $category, updated_at: $updated_at})
+                            RETURN id(tag) as tag_id
+                            """
+                            tag_result = session.run(
+                                create_tag_query, 
+                                name=tag_name,
+                                category='talent',
+                                updated_at=current_time
+                            )
+                            tag_record = tag_result.single()
+                            tag_id = tag_record['tag_id']
+                        
+                        # 2. 创建人才与标签的BELONGS_TO关系(如果不存在)
+                        create_relation_query = """
+                        MATCH (t:talent), (tag:talent_tag)
+                        WHERE id(t) = $talent_id AND id(tag) = $tag_id
+                        MERGE (t)-[r:BELONGS_TO]->(tag)
+                        ON CREATE SET r.created_at = $current_time
+                        ON MATCH SET r.updated_at = $current_time
+                        RETURN r
+                        """
+                        
+                        relation_result = session.run(
+                            create_relation_query,
+                            talent_id=int(talent_id),
+                            tag_id=int(tag_id),
+                            current_time=current_time
+                        )
+                        
+                        if relation_result.single():
+                            success_count += 1
+                        else:
+                            failed_items.append({'talent': talent_id, 'tag': tag_name})
+                            
+                    except Exception as tag_error:
+                        logging.error(f"为标签 {tag_name} 创建关系时出错: {str(tag_error)}")
+                        failed_items.append({'talent': talent_id, 'tag': tag_name})
+        
+        # 返回结果
+        total_items = len(data)
+        if success_count == total_items:
+            return {
+                'code': 200,
+                'success': True,
+                'message': f'成功创建或更新了 {success_count} 个标签关系',
+                'data': {
+                    'success_count': success_count,
+                    'total_count': total_items,
+                    'failed_items': []
+                }
+            }
+        elif success_count > 0:
+            return {
+                'code': 206, # Partial Content
+                'success': True,
+                'message': f'部分成功: 创建或更新了 {success_count}/{total_items} 个标签关系',
+                'data': {
+                    'success_count': success_count,
+                    'total_count': total_items,
+                    'failed_items': failed_items
+                }
+            }
+        else:
+            return {
+                'code': 500,
+                'success': False,
+                'message': '无法创建任何标签关系',
+                'data': {
+                    'success_count': 0,
+                    'total_count': total_items,
+                    'failed_items': failed_items
+                }
+            }
+            
+    except Exception as e:
+        error_msg = f"更新人才标签关系失败: {str(e)}"
+        logging.error(error_msg, exc_info=True)
+        
+        return {
+            'code': 500,
+            'success': False,
+            'message': error_msg,
+            'data': None
         }