Pārlūkot izejas kodu

修改数据解析模块里解析提示词问题。
修改数据解析模块里图片下传到前端的问题。
修改数据模型模块里创建数据模型和编辑数据模型的问题。

maxiaolong 1 mēnesi atpakaļ
vecāks
revīzija
92e675f94c

+ 75 - 31
app/api/data_parse/routes.py

@@ -1,7 +1,49 @@
-from flask import jsonify, request, make_response
+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, get_minio_client, MINIO_BUCKET
+from app.core.data_parse.parse import parse_data, process_business_card, update_business_card, get_business_cards, update_business_card_status, get_business_card_image_from_minio
+from app.config.config import DevelopmentConfig, ProductionConfig
 import logging
+import boto3
+from botocore.config import Config
+from botocore.exceptions import ClientError
+from io import BytesIO
+import base64
+import os
+import urllib.parse
+from minio import Minio
+
+# Define logger
+logger = logging.getLogger(__name__)
+
+# For failure responses
+def failed(message, code=500):
+    return {
+        'success': False,
+        'message': message,
+        'data': None
+    }, code
+
+# 根据环境选择配置
+if os.environ.get('FLASK_ENV') == 'production':
+    config = ProductionConfig()
+else:
+    config = DevelopmentConfig()
+
+# 使用配置变量
+minio_url = f"{'https' if config.MINIO_SECURE else 'http'}://{config.MINIO_HOST}"
+minio_access_key = config.MINIO_USER
+minio_secret_key = config.MINIO_PASSWORD
+minio_bucket = config.MINIO_BUCKET
+use_ssl = config.MINIO_SECURE
+
+def get_minio_client():
+    """获取 MinIO 客户端实例"""
+    return Minio(
+        '192.168.3.143:9000',
+        access_key=config.MINIO_USER,
+        secret_key=config.MINIO_PASSWORD,
+        secure=config.MINIO_SECURE
+    )
 
 # 测试用的解析数据接口。没有实际使用。
 @bp.route('/parse', methods=['POST'])
@@ -168,39 +210,41 @@ def get_business_card_image(image_path):
         - 图片数据流
     """
     try:
-        # 获取MinIO客户端
+        # 记录下载请求信息,便于调试
+        logger.info(f"获取名片图片请求: {image_path}")
+        
+        # 获取 MinIO 客户端
         minio_client = get_minio_client()
         
         if not minio_client:
-            return jsonify({
-                'success': False,
-                'message': 'MinIO客户端初始化失败',
-            }), 500
-        
-        # 从MinIO获取图片
-        response = minio_client.get_object(
-            Bucket=MINIO_BUCKET,
-            Key=image_path
-        )
-        
-        # 读取图片数据
-        image_data = response['Body'].read()
+            return jsonify(failed("MinIO客户端初始化失败")), 500
         
-        # 确定内容类型
-        content_type = response.get('ContentType', 'image/jpeg')
-        
-        # 创建响应
-        response = make_response(image_data)
-        response.headers.set('Content-Type', content_type)
-        # 设置缓存,提高性能
-        response.headers.set('Cache-Control', 'public, max-age=31536000')
-        
-        return response
+        try:
+            # 使用正确的MinIO客户端方法
+            data = minio_client.get_object(minio_bucket, image_path)
+            
+            # 创建内存文件流
+            file_stream = BytesIO(data.read())
+            
+            # 获取文件名
+            file_name = image_path.split('/')[-1]
+            
+            # 返回文件
+            return send_file(
+                file_stream,
+                as_attachment=False,  # 设置为False,让浏览器直接显示图片
+                download_name=file_name,
+                mimetype='image/jpeg'  # 根据实际图片类型设置
+            )
+        except Exception as e:
+            logger.error(f"MinIO获取文件失败: {str(e)}")
+            return jsonify(failed(f"文件获取失败: {str(e)}")), 404
         
     except Exception as e:
-        logging.error(f"获取图片失败: {str(e)}")
-        return jsonify({
-            'success': False,
-            'message': f'获取图片失败: {str(e)}',
-        }), 404
+        logger.error(f"文件下载失败: {str(e)}")
+        return jsonify(failed(str(e))), 500
+    finally:
+        # 确保关闭数据流
+        if 'data' in locals():
+            data.close()
 

+ 2 - 2
app/config/config.py

@@ -66,7 +66,7 @@ class DevelopmentConfig(BaseConfig):
     MINIO_USER = 'citu-test'
     MINIO_PASSWORD = 'citu-test'
     MINIO_SECURE = False
-    BUCKET_NAME = 'dataops-bucket'
+    MINIO_BUCKET = 'dataops-bucket'
     PREFIX = ''
     
     # 开发环境 PostgreSQL 配置
@@ -99,7 +99,7 @@ class ProductionConfig(BaseConfig):
     MINIO_USER = os.environ.get('MINIO_USER', 'citu-dataops-acc-key')
     MINIO_PASSWORD = os.environ.get('MINIO_PASSWORD', 'citu-dataops-secret-key')
     MINIO_SECURE = False
-    BUCKET_NAME = 'dataops-bucket'
+    MINIO_BUCKET = 'dataops-bucket'
     PREFIX = ''
     
     # 生产环境 PostgreSQL 配置

+ 164 - 150
app/core/data_model/model.py

@@ -56,13 +56,15 @@ def calculate_model_level(id):
         record = result.single()
         data = record["max_level"] if record and "max_level" in record else 0
         
-        # 更新level属性
-        update_query = """
-            MATCH (n:data_model)
-            WHERE id(n) = $nodeId
-            SET n.level = $level
-            RETURN n
-            """
+    # 更新level属性
+    update_query = """
+    MATCH (n:data_model)
+    WHERE id(n) = $nodeId
+    SET n.level = $level
+    RETURN n
+    """
+    
+    with connect_graph().session() as session:
         session.run(update_query, nodeId=node_id, level=data)
 
 
@@ -98,7 +100,7 @@ def handle_model_relation(resource_ids):
 
     with connect_graph().session() as session:
         result = session.run(query, resource_Ids=resource_ids)
-        return result.data()
+    return result.data()
 
 
 # 创建一个数据模型节点
@@ -115,59 +117,62 @@ def handle_data_model(data_model, result_list, result, receiver):
     Returns:
         tuple: (id, data_model_node)
     """
-    # 添加数据资源 血缘关系的字段 blood_resource
-    data_model_en = result_list[0] if result_list and len(result_list) > 0 else ""
-    receiver['id_list'] = result
-    add_attribute = {
-        'time': get_formatted_time(),
-        'en_name': data_model_en
-    }
-    receiver.update(add_attribute)
-    data_model_node = get_node('data_model', name=data_model) or create_or_get_node('data_model', **receiver)
+    try:
+        # 添加数据资源 血缘关系的字段 blood_resource
+        data_model_en = result_list[0] if result_list and len(result_list) > 0 else ""
+        receiver['id_list'] = result
+        add_attribute = {
+            'time': get_formatted_time(),
+            'en_name': data_model_en
+        }
+        receiver.update(add_attribute)
+        data_model_node = get_node('data_model', name=data_model) or create_or_get_node('data_model', **receiver)
 
-    child_list = receiver.get('childrenId', [])
-    for child_id in child_list:
-        child_node = get_node_by_id_no_label(child_id)
-        if child_node and not relationship_exists(data_model_node, 'child', child_node):
-            with connect_graph().session() as session:
-                relationship = Relationship(data_model_node, 'child', child_node)
-                session.execute_write(
-                    lambda tx: tx.run(
-                        "MATCH (a), (b) WHERE id(a) = $a_id AND id(b) = $b_id CREATE (a)-[:child]->(b)",
-                        a_id=data_model_node.id, b_id=child_node.id
+        # 安全地处理子节点关系
+        child_list = receiver.get('childrenId', [])
+        for child_id in child_list:
+            child_node = get_node_by_id_no_label(child_id)
+            if child_node and not relationship_exists(data_model_node, 'child', child_node):
+                with connect_graph().session() as session:
+                    session.execute_write(
+                        lambda tx: tx.run(
+                            "MATCH (a), (b) WHERE id(a) = $a_id AND id(b) = $b_id CREATE (a)-[:child]->(b)",
+                            a_id=data_model_node.id, b_id=child_node.id
+                        )
                     )
-                )
 
-    # 根据传入参数id,和数据标签建立关系
-    if receiver.get('tag'):
-        # 使用 Cypher 查询通过 id 查找节点
-        tag = get_node_by_id('data_label', receiver['tag'])
-        if tag and not relationship_exists(data_model_node, 'label', tag):
-            with connect_graph().session() as session:
-                session.execute_write(
-                    lambda tx: tx.run(
-                        "MATCH (a), (b) WHERE id(a) = $a_id AND id(b) = $b_id CREATE (a)-[:label]->(b)",
-                        a_id=data_model_node.id, b_id=tag.id
+        # 根据传入参数id,和数据标签建立关系
+        if receiver.get('tag'):
+            tag = get_node_by_id('data_label', receiver['tag'])
+            if tag and not relationship_exists(data_model_node, 'label', tag):
+                with connect_graph().session() as session:
+                    session.execute_write(
+                        lambda tx: tx.run(
+                            "MATCH (a), (b) WHERE id(a) = $a_id AND id(b) = $b_id CREATE (a)-[:label]->(b)",
+                            a_id=data_model_node.id, b_id=tag.id
+                        )
                     )
-                )
 
-    # 获取节点ID
-    node_id = None
-    if hasattr(data_model_node, 'id'):
-        node_id = data_model_node.id
-    else:
-        # 如果节点没有id属性,尝试通过查询获取
-        query = """
-        MATCH (n:data_model {name: $name})
-        RETURN id(n) as node_id
-        """
-        with connect_graph().session() as session:
-            result = session.run(query, name=data_model)
-            record = result.single()
-            if record and "node_id" in record:
-                node_id = record["node_id"]
+        # 获取节点ID
+        node_id = None
+        if hasattr(data_model_node, 'id'):
+            node_id = data_model_node.id
+        else:
+            # 如果节点没有identity属性,尝试通过查询获取
+            query = """
+            MATCH (n:data_model {name: $name})
+            RETURN id(n) as node_id
+            """
+            with connect_graph().session() as session:
+                result = session.run(query, name=data_model)
+                record = result.single()
+                if record and "node_id" in record:
+                    node_id = record["node_id"]
 
-    return node_id, data_model_node
+        return node_id, data_model_node
+    except Exception as e:
+        logging.error(f"Error in handle_data_model: {str(e)}")
+        raise
 
 
 # (从数据资源中选取)
@@ -301,10 +306,10 @@ def handle_no_meta_data_model(id_lists, receiver, data_model_node):
             """
             with connect_graph().session() as session:
                 result = session.run(query, node_id=id)
-                if result:
-                    record = result.data()
-                    if record:
-                        meta_node_list.append(record[0]['n'])
+            if result:
+                record = result.data()
+                if record:
+                    meta_node_list.append(record[0]['n'])
         
         # 提取接收到的数据并创建meta_node节点
         meta_node = None
@@ -345,117 +350,126 @@ def handle_no_meta_data_model(id_lists, receiver, data_model_node):
 # 数据模型-详情接口
 def handle_id_model(id):
     """
-    处理数据模型详情请求,查询指定ID的数据模型节点的完整信息
+    获取数据模型详情
     
     Args:
-        id: 数据模型节点ID
+        id: 数据模型节点ID
         
     Returns:
-        dict: 包含数据模型完整详情信息的字典
+        dict: 包含数据模型详情的字典,格式为:
+        {"data_model": {
+            "resource_selected": [...],
+            "leader": ...,
+            "origin": ...,
+            "frequency": ...,
+            "childrenId": [...],
+            "organization": ...,
+            "name": ...,
+            "en_name": ...,
+            "data_sensitivity": ...,
+            "describe": ...,
+            "tag": ...,
+            "time": ...,
+            "category": ...,
+            "status": ...
+        }}
     """
-    try:
-        # 确保id是整数类型
-        node_id = int(id) if id is not None else None
-        
-        logging.info(f"查询数据模型详情,ID: {node_id}")
-        
-        # 构建Cypher查询 - 查询模型基本信息和关联节点
-        cql = """
-        MATCH (n:data_model)
-        WHERE id(n) = $nodeId
-        
-        // 获取与模型关联的资源节点
-        OPTIONAL MATCH (n)-[:resource]->(resource:data_resource)
-        
-        // 获取与模型关联的数据源节点
-        OPTIONAL MATCH (n)-[:use]->(source:data_source)
-        
-        // 获取模型的标签
+    node_id = id
+    cql = """
+        MATCH (n:data_model) WHERE id(n) = $nodeId
+        OPTIONAL MATCH (n)-[:connection]->(meta:meta_node)
+        OPTIONAL MATCH (n)<-[:belongs_to]-(resource:data_resource)
         OPTIONAL MATCH (n)-[:label]->(tag:data_label)
-        
-        // 获取模型的下级节点(子节点)
-        OPTIONAL MATCH (n)-[:child]->(child:data_model)
-        
-        // 获取模型关联的元数据节点
-        OPTIONAL MATCH (n)-[:component]->(meta:meta_node)
-        
-        // 收集所有关联节点信息
+        OPTIONAL MATCH (uses:model_use)-[:use]->(n)
+        OPTIONAL MATCH (n)-[:has_component]->(component)
         WITH n, 
-             collect(DISTINCT resource) AS resources,
-             collect(DISTINCT source) AS sources,
-             tag,
-             collect(DISTINCT {id: id(child), name: child.name, en_name: child.en_name}) AS children,
-             collect(DISTINCT meta) AS meta_nodes
-             
+            collect(DISTINCT meta) as meta_nodes,
+            collect(DISTINCT resource) as resources,
+            collect(DISTINCT component) as components,
+            collect(DISTINCT uses) as uses,
+            collect(DISTINCT tag) as tags,
+            CASE WHEN n.childrenId IS NOT NULL THEN n.childrenId ELSE [] END as children
         RETURN {
             // 基本信息
             id: id(n),
             name: n.name,
             en_name: n.en_name,
-            // 标签信息从tag变量中获取
-            label: tag.name,
             time: n.time,
             description: n.description,
+            describe: n.description,  // 使用description作为describe字段
             category: n.category,
             level: n.level,
-            
-            // 标签信息
-            tag: CASE 
-                WHEN tag IS NOT NULL THEN {id: id(tag), name: tag.name}
-                ELSE null 
-            END,
-            
-            // 关联的资源列表
-            resources: [resource IN resources WHERE resource IS NOT NULL | {
+            tag: CASE WHEN size(tags) > 0 AND tags[0] IS NOT NULL THEN {id: id(tags[0]), name: tags[0].name} ELSE null END,
+            // 添加其他必需字段
+            leader: n.leader,
+            origin: n.origin,
+            blood_resource: n.blood_resource,
+            frequency: n.frequency,
+            organization: n.organization,
+            data_sensitivity: n.data_sensitivity,
+            status: n.status,
+            // 子节点列表
+            childrenId: children
+        } AS result,
+        // 资源列表
+        [{
+            data_resource: [resource IN resources WHERE resource IS NOT NULL | {
                 id: id(resource),
                 name: resource.name,
                 en_name: resource.en_name,
                 description: resource.description
             }],
-            
-            // 关联的数据源列表
-            sources: [source IN sources WHERE source IS NOT NULL | {
-                id: id(source),
-                name: source.name,
-                en_name: source.en_name
-            }],
-            
-            // 子节点列表
-            children: children,
-            
-            // 元数据列表
-            meta_nodes: [meta IN meta_nodes WHERE meta IS NOT NULL | {
+            resource_id: [resource IN resources WHERE resource IS NOT NULL | id(resource)],
+            meta_ids: [meta IN meta_nodes WHERE meta IS NOT NULL | {
                 id: id(meta),
                 name: meta.name,
                 en_name: meta.en_name,
                 data_type: meta.data_type
-            }],
-            
-            // 原始属性
-            properties: properties(n)
-        } AS result
+            }]
+        }] AS resource_selected
         """
         
-        with connect_graph().session() as session:
-            result = session.run(cql, nodeId=node_id)
+    with connect_graph().session() as session:
+        result = session.run(cql, nodeId=node_id)
+        
+        # 处理查询结果
+        record = result.single()
+        logging.info(f"获得查询结果---------->>>{record}")
+        
+        if record:
+            # 获取基本属性和资源选择列表
+            properties = record["result"]
+            resource_selected = record["resource_selected"]
             
-            # 处理查询结果
-            record = result.single()
-            logging.info(f"获得查询结果---------->>>{record}")
+            # 确保所有必需字段都有默认值,避免空值
+            required_fields = ['tag', 'description', 'leader', 'origin', 'blood_resource', 
+                              'frequency', 'describe', 'organization', 'name', 'en_name', 
+                              'data_sensitivity', 'time', 'category', 'status', 'childrenId']
             
-            # 直接检查record是否存在,而不是检查"result"键
-            if record:
-                # 已经包含"result"键,直接获取其值
-                data_model = record["result"]
-                logging.info(f"成功获取数据模型详情,ID: {node_id}")
-                return data_model
-            else:
-                logging.warning(f"未找到ID为 {node_id} 的数据模型")
-                return {}
-                
-    except Exception as e:
-        logging.error(f"获取数据模型详情出错: {str(e)}", exc_info=True)
-        return {}
+            for field in required_fields:
+                if field not in properties or properties[field] is None:
+                    if field == 'tag':
+                        properties[field] = {}
+                    elif field == 'childrenId':
+                        properties[field] = []
+                    else:
+                        properties[field] = ""
+            
+            # 构建最终返回格式
+            final_data = {
+                "resource_selected": resource_selected,
+                **properties
+            }
+            
+            return {"data_model": final_data}
+        else:
+            # 如果没有查询到结果,返回空的结构
+            return {"data_model": {
+                "resource_selected": [{"meta_ids": [], "data_resource": None, "resource_id": None}],
+                "leader": None, "origin": None, "frequency": None, "childrenId": [],
+                "organization": None, "name": None, "en_name": None, "data_sensitivity": None,
+                "describe": None, "tag": {}, "time": None, "category": None, "status": None
+            }}
 
 
 # 数据模型列表
@@ -492,20 +506,20 @@ def model_list(skip_count, page_size, en_name_filter=None, name_filter=None,
         if category is not None:
             where_clause.append("n.category = $category")
             params['category'] = category
-
+    
         if level is not None:
             where_clause.append("n.level = $level")
             params['level'] = level
-
+    
         if tag is not None:
             where_clause.append("id(t) = $tag")
             params['tag'] = tag
 
-        # 处理where子句
+        # At the end of where_clause construction
         where_str = " AND ".join(where_clause)
         if where_str:
             where_str = f"WHERE {where_str}"
-        
+    
         # 构建查询
         with connect_graph().session() as session:
             # 计算总数量
@@ -525,9 +539,9 @@ def model_list(skip_count, page_size, en_name_filter=None, name_filter=None,
             OPTIONAL MATCH (n)-[:label]->(t)
             {where_str}
             RETURN DISTINCT 
-            id(n) as id,
-            n.name as name,
-            n.en_name as en_name,
+            id(n) as id, 
+            n.name as name, 
+            n.en_name as en_name, 
             n.time as time,
             n.description as description,
             n.level as level,
@@ -537,7 +551,7 @@ def model_list(skip_count, page_size, en_name_filter=None, name_filter=None,
             SKIP $skip
             LIMIT $limit
             """
-            
+        
             result = session.run(query, skip=skip_count, limit=page_size, **params)
             
             # 处理结果
@@ -985,7 +999,7 @@ def data_model_edit(receiver):
     
     with connect_graph().session() as session:
         result = session.run(query, id=id, name=name, en_name=en_name, 
-                           category=category, description=description).data()
+                             category=category, description=description).data()
     
     # 处理标签关系
     if tag:

+ 170 - 37
app/core/data_parse/parse.py

@@ -14,6 +14,7 @@ from io import BytesIO
 import pytesseract
 import base64
 from openai import OpenAI
+from app.config.config import DevelopmentConfig, ProductionConfig
 
 
 # 测试用的解析数据接口。没有实际使用。      
@@ -29,6 +30,7 @@ def parse_data(data: Dict[str, Any]) -> Dict[str, Any]:
     """
     # TODO: 实现数据解析逻辑
     return {
+        'code': 200,
         'status': 'success',
         'message': 'Data parsed successfully',
         'data': data
@@ -57,6 +59,8 @@ class BusinessCard(db.Model):
     affiliation_zh = db.Column(db.String(200))
     affiliation_en = db.Column(db.String(200))
     image_path = db.Column(db.String(255))  # MinIO中存储的路径
+    career_path = db.Column(db.JSON)  # 职业轨迹,JSON格式
+    brand_group = db.Column(db.String(200))  # 品牌组合
     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))
@@ -83,6 +87,8 @@ class BusinessCard(db.Model):
             'affiliation_zh': self.affiliation_zh,
             'affiliation_en': self.affiliation_en,
             'image_path': self.image_path,
+            'career_path': self.career_path,
+            'brand_group': self.brand_group,
             '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,
@@ -92,13 +98,6 @@ class BusinessCard(db.Model):
 
 # 名片解析功能模块
 
-# MinIO配置
-MINIO_URL = os.environ.get('MINIO_URL', 'localhost:19000')
-MINIO_ACCESS_KEY = os.environ.get('MINIO_ACCESS_KEY', 'miniomxl')
-MINIO_SECRET_KEY = os.environ.get('MINIO_SECRET_KEY', 'minio2357!')
-MINIO_BUCKET = os.environ.get('MINIO_BUCKET', 'business-cards')
-USE_SSL = os.environ.get('MINIO_USE_SSL', 'false').lower() == 'true'
-
 # DeepSeek API配置
 DEEPSEEK_API_KEY = os.environ.get('DEEPSEEK_API_KEY', 'sk-2aea6e8b159b448aa3c1e29acd6f4349')
 DEEPSEEK_API_URL = os.environ.get('DEEPSEEK_API_URL', 'https://api.deepseek.com/v1/chat/completions')
@@ -111,21 +110,53 @@ DEEPSEEK_API_URL_BACKUP = 'https://api.deepseek.com/v1/completions'
 # OCR语言设置,支持多语言
 OCR_LANG = os.environ.get('OCR_LANG', 'chi_sim+eng')
 
+
+# 根据环境选择配置
+""" 
+if os.environ.get('FLASK_ENV') == 'production':
+    config = ProductionConfig()
+else:
+    config = DevelopmentConfig() 
+"""
+
+# 使用配置变量,缺省认为在生产环境运行
+config = ProductionConfig()
+# 使用配置变量
+minio_url = f"{'https' if config.MINIO_SECURE else 'http'}://{config.MINIO_HOST}"
+minio_access_key = config.MINIO_USER
+minio_secret_key = config.MINIO_PASSWORD
+minio_bucket = config.MINIO_BUCKET
+use_ssl = config.MINIO_SECURE
+
 def get_minio_client():
     """获取MinIO客户端连接"""
     try:
+        # 使用全局配置变量
+        global minio_url, minio_access_key, minio_secret_key, minio_bucket, use_ssl
+        
+        logging.info(f"尝试连接MinIO服务器: {minio_url}")
+        
         minio_client = boto3.client(
             's3',
-            endpoint_url=f'{"https" if USE_SSL else "http"}://{MINIO_URL}',
-            aws_access_key_id=MINIO_ACCESS_KEY,
-            aws_secret_access_key=MINIO_SECRET_KEY,
-            config=Config(signature_version='s3v4'),
-            region_name='us-east-1'  # 可选,但某些S3客户端可能需要
+            endpoint_url=minio_url,
+            aws_access_key_id=minio_access_key,
+            aws_secret_access_key=minio_secret_key,
+            config=Config(
+                signature_version='s3v4',
+                retries={'max_attempts': 3, 'mode': 'standard'},
+                connect_timeout=10,
+                read_timeout=30
+            )
         )
         
         # 确保存储桶存在
-        if MINIO_BUCKET not in [bucket['Name'] for bucket in minio_client.list_buckets()['Buckets']]:
-            minio_client.create_bucket(Bucket=MINIO_BUCKET)
+        buckets = minio_client.list_buckets()
+        bucket_names = [bucket['Name'] for bucket in buckets.get('Buckets', [])]
+        logging.info(f"成功连接到MinIO服务器,现有存储桶: {bucket_names}")
+        
+        if minio_bucket not in bucket_names:
+            logging.info(f"创建存储桶: {minio_bucket}")
+            minio_client.create_bucket(Bucket=minio_bucket)
             
         return minio_client
     except Exception as e:
@@ -229,6 +260,8 @@ def parse_text_with_deepseek(text):
 - email: 电子邮箱
 - address_zh: 中文地址
 - address_en: 英文地址
+- brand_group: 品牌组合(如有多个品牌,以逗号分隔)
+- career_path: 职业轨迹(如果能从文本中推断出,以JSON数组格式返回,包含公司名称和职位)
 
 名片文本:
 {text}
@@ -271,10 +304,10 @@ def parse_text_with_deepseek(text):
             extracted_data = extract_fields_from_text(content)
         
         # 确保所有必要的字段都存在
-        required_fields = ['name', 'title', 'company', 'phone', 'email', 'address']
+        required_fields = ['name', 'title', 'company', 'phone', 'email', 'address', 'brand_group', 'career_path']
         for field in required_fields:
             if field not in extracted_data:
-                extracted_data[field] = ""
+                extracted_data[field] = "" if field != 'career_path' else []
         
         logging.info(f"成功从DeepSeek API获取解析结果")
         return extracted_data
@@ -464,23 +497,52 @@ def parse_text_with_qwen25VLplus(image_data):
             base_url="https://dashscope.aliyuncs.com/compatible-mode/v1",
         )
         
-        # 构建提示语
-        prompt = """请分析这张名片,提取以下信息,需分别识别中英文内容:
+        # 构建优化后的提示语
+        prompt = """你是专业的名片信息提取助手。请仔细分析图片中的名片,精确提取以下信息:
+
+## 提取要求
+- 区分中英文内容,分别提取
+- 保持提取信息的原始格式(如大小写、标点)
+- 对于无法识别或名片中不存在的信息,返回空字符串
+
+## 需提取的字段
 1. 中文姓名 (name_zh)
 2. 英文姓名 (name_en)
 3. 中文职位/头衔 (title_zh)
 4. 英文职位/头衔 (title_en)
 5. 中文酒店/公司名称 (hotel_zh)
 6. 英文酒店/公司名称 (hotel_en)
-7. 手机号码 (mobile)
-8. 固定电话 (phone)
+7. 手机号码 (mobile) - 如有多个,使用逗号分隔
+8. 固定电话 (phone) - 如有多个,使用逗号分隔
 9. 电子邮箱 (email)
 10. 中文地址 (address_zh)
 11. 英文地址 (address_en)
 12. 中文邮政编码 (postal_code_zh)
 13. 英文邮政编码 (postal_code_en)
+14. 品牌组合 (brand_group) - 如有多个品牌,使用逗号分隔
+15. 职业轨迹 (career_path) - 如能从名片中推断,以JSON数组格式返回
 
-请以JSON格式返回结果,不要有多余的文字说明。每个字段如果名片中没有相应信息,返回空字符串。"""
+## 输出格式
+请以严格的JSON格式返回结果,不要添加任何额外解释文字。JSON格式如下:
+```json
+{
+  "name_zh": "",
+  "name_en": "",
+  "title_zh": "",
+  "title_en": "",
+  "hotel_zh": "",
+  "hotel_en": "",
+  "mobile": "",
+  "phone": "",
+  "email": "",
+  "address_zh": "",
+  "address_en": "",
+  "postal_code_zh": "",
+  "postal_code_en": "",
+  "brand_group": "",
+  "career_path": []
+}
+```"""
         
         # 调用 Qwen 2.5 VL Plus API
         logging.info("发送请求到 Qwen 2.5 VL Plus 模型")
@@ -494,12 +556,14 @@ def parse_text_with_qwen25VLplus(image_data):
                         {"type": "image_url", "image_url": {"url": f"data:image/jpeg;base64,{base64_image}"}}
                     ]
                 }
-            ]
+            ],
+            temperature=0.1,  # 降低温度增加精确性
+            response_format={"type": "json_object"}  # 要求输出JSON格式
         )
         
         # 解析响应
         response_content = completion.choices[0].message.content
-        logging.info(f"成功从 Qwen 模型获取响应: {response_content[:100]}...")
+        logging.info(f"成功从 Qwen 模型获取响应: {response_content}")
         
         # 尝试从响应中提取 JSON
         try:
@@ -515,12 +579,12 @@ def parse_text_with_qwen25VLplus(image_data):
             'name_zh', 'name_en', 'title_zh', 'title_en', 
             'hotel_zh', 'hotel_en', 'mobile', 'phone', 
             'email', 'address_zh', 'address_en',
-            'postal_code_zh', 'postal_code_en'
+            'postal_code_zh', 'postal_code_en', 'brand_group', 'career_path'
         ]
         
         for field in required_fields:
             if field not in extracted_data:
-                extracted_data[field] = ""
+                extracted_data[field] = [] if field == 'career_path' else ""
         
         return extracted_data
         
@@ -557,6 +621,7 @@ def process_business_card(image_file):
                 # extracted_data = extract_text_from_image(image_data)
         except Exception as e:
             return {
+                'code': 500,
                 'success': False,
                 'message': f"名片解析失败: {str(e)}",
                 'data': None
@@ -574,17 +639,20 @@ def process_business_card(image_file):
             # 尝试上传到MinIO
             minio_client = get_minio_client()
             if minio_client:
-                minio_client.put_object(
-                    Bucket=MINIO_BUCKET,
-                    Key=minio_path,
-                    Body=image_file,
-                    ContentType=image_file.content_type
-                )
-                logging.info(f"图片已上传到MinIO: {minio_path}")
-                
-                # 只存储相对路径,不存储完整URL
-                # 完整URL会由前端通过API代理访问
-                logging.info(f"存储图片路径: {minio_path}")
+                try:
+                    # 上传文件
+                    logging.info(f"上传文件到MinIO: {minio_path}")
+                    minio_client.put_object(
+                        Bucket=minio_bucket,
+                        Key=minio_path,
+                        Body=image_file,
+                        ContentType=image_file.content_type
+                    )
+                    logging.info(f"图片已上传到MinIO: {minio_path}")
+                except Exception as upload_err:
+                    logging.error(f"上传文件到MinIO时出错: {str(upload_err)}")
+                    # 即使上传失败,仍继续处理,但路径为None
+                    minio_path = None
             else:
                 minio_path = None
                 logging.warning("MinIO客户端未初始化,图片未上传")
@@ -613,6 +681,8 @@ def process_business_card(image_file):
                 affiliation_zh=extracted_data.get('affiliation_zh', ''),
                 affiliation_en=extracted_data.get('affiliation_en', ''),
                 image_path=minio_path,  # 存储相对路径
+                career_path=extracted_data.get('career_path', []),  # 添加职业轨迹
+                brand_group=extracted_data.get('brand_group', ''),  # 添加品牌组合
                 status='active',
                 updated_by='system'
             )
@@ -623,6 +693,7 @@ def process_business_card(image_file):
             logging.info(f"名片信息已保存到数据库,ID: {business_card.id}")
             
             return {
+                'code': 200,
                 'success': True,
                 'message': '名片解析成功',
                 'data': business_card.to_dict()
@@ -634,6 +705,7 @@ def process_business_card(image_file):
             
             # 即使数据库操作失败,仍返回提取的信息
             return {
+                'code': 500,
                 'success': False,
                 'message': error_msg,
                 'data': {
@@ -656,6 +728,8 @@ def process_business_card(image_file):
                     'affiliation_zh': extracted_data.get('affiliation_zh', ''),
                     'affiliation_en': extracted_data.get('affiliation_en', ''),
                     'image_path': minio_path,  # 返回相对路径
+                    'career_path': extracted_data.get('career_path', []),  # 添加职业轨迹
+                    'brand_group': extracted_data.get('brand_group', ''),  # 添加品牌组合
                     'created_at': datetime.now().strftime('%Y-%m-%d %H:%M:%S'),
                     'updated_at': None,
                     'updated_by': 'system',
@@ -669,6 +743,7 @@ def process_business_card(image_file):
         logging.error(error_msg, exc_info=True)
         
         return {
+            'code': 500,
             'success': False,
             'message': error_msg,
             'data': None
@@ -691,6 +766,7 @@ def update_business_card(card_id, data):
         
         if not card:
             return {
+                'code': 500,
                 'success': False,
                 'message': f'未找到ID为{card_id}的名片记录',
                 'data': None
@@ -714,12 +790,15 @@ def update_business_card(card_id, data):
         card.brand_en = data.get('brand_en', card.brand_en)
         card.affiliation_zh = data.get('affiliation_zh', card.affiliation_zh)
         card.affiliation_en = data.get('affiliation_en', card.affiliation_en)
+        card.career_path = data.get('career_path', card.career_path)  # 更新职业轨迹
+        card.brand_group = data.get('brand_group', card.brand_group)  # 更新品牌组合
         card.updated_by = data.get('updated_by', 'user')  # 可以根据实际情况修改为当前用户
         
         # 保存更新
         db.session.commit()
         
         return {
+            'code': 200,
             'success': True,
             'message': '名片信息已更新',
             'data': card.to_dict()
@@ -731,6 +810,7 @@ def update_business_card(card_id, data):
         logging.error(error_msg, exc_info=True)
         
         return {
+            'code': 500,
             'success': False,
             'message': error_msg,
             'data': None
@@ -751,6 +831,7 @@ def get_business_cards():
         cards_data = [card.to_dict() for card in cards]
         
         return {
+            'code': 200,
             'success': True,
             'message': '获取名片列表成功',
             'data': cards_data
@@ -761,6 +842,7 @@ def get_business_cards():
         logging.error(error_msg, exc_info=True)
         
         return {
+            'code': 500,
             'success': False,
             'message': error_msg,
             'data': []
@@ -783,6 +865,7 @@ def update_business_card_status(card_id, status):
         
         if not card:
             return {
+                'code': 500,
                 'success': False,
                 'message': f'未找到ID为{card_id}的名片记录',
                 'data': None
@@ -791,6 +874,7 @@ def update_business_card_status(card_id, status):
         # 验证状态值
         if status not in ['active', 'inactive']:
             return {
+                'code': 500,
                 'success': False,
                 'message': f'无效的状态值: {status},必须为 active 或 inactive',
                 'data': None
@@ -805,6 +889,7 @@ def update_business_card_status(card_id, status):
         db.session.commit()
         
         return {
+            'code': 200,
             'success': True,
             'message': f'名片状态已更新为: {status}',
             'data': card.to_dict()
@@ -816,7 +901,55 @@ def update_business_card_status(card_id, status):
         logging.error(error_msg, exc_info=True)
         
         return {
+            'code': 500,
             'success': False,
             'message': error_msg,
             'data': None
-        }
+        }
+
+'''
+def get_business_card_image_from_minio(image_path):
+    """
+    从MinIO获取名片图片
+    
+    Args:
+        image_path (str): MinIO中的图片路径
+        
+    Returns:
+        tuple: (success, file_data, content_type, status_code)
+            - success: 是否成功获取图片
+            - file_data: 图片二进制数据
+            - content_type: 图片内容类型
+            - status_code: HTTP状态码
+    """
+    response = None
+    try:
+        minio_client = get_minio_client()
+        
+        if not minio_client:
+            logging.error("MinIO客户端初始化失败")
+            return False, None, None, 500
+        
+        # 获取文件
+        try:
+            response = minio_client.get_object(
+                Bucket=minio_bucket,
+                Key=image_path
+            )
+            file_data = response.read()
+            content_type = response.content_type
+            
+            return True, file_data, content_type, 200
+            
+        except Exception as e:
+            logging.error(f"MinIO获取图片失败: {str(e)}")
+            return False, None, None, 404
+        
+    except Exception as e:
+        logging.error(f"获取图片过程中发生错误: {str(e)}")
+        return False, None, None, 500
+    finally:
+        # 关闭响应连接(如果存在)
+        if response and hasattr(response.get('Body', None), 'close'):
+            response['Body'].close()
+'''            

+ 308 - 0
data_parse_api_docs.md

@@ -0,0 +1,308 @@
+# 数据解析 API 文档
+
+本文档描述了数据解析模块的 API 接口,包括通用数据解析和名片解析功能。所有接口均采用 RESTful 风格,返回 JSON 格式的响应。
+
+## 基础路径
+
+所有 API 接口的基础路径为:`/api/parse`
+
+## 1. 通用数据解析接口
+
+### 1.1 解析数据
+
+**接口地址**:`/parse`
+
+**请求方法**:POST
+
+**功能描述**:测试用的数据解析接口,目前没有实际使用。
+
+**请求参数**:
+
+请求体为 JSON 格式,具体格式取决于实际使用场景。
+
+**返回结果**:
+
+```json
+{
+  "status": "success",
+  "message": "Data parsed successfully",
+  "data": {} // 解析后的数据
+}
+```
+
+**错误码**:
+
+| HTTP 状态码 | 错误描述           |
+|---------|----------------|
+| 400     | No data provided |
+| 500     | 内部服务器错误       |
+
+## 2. 名片解析接口
+
+### 2.1 解析名片图片
+
+**接口地址**:`/business-card-parse`
+
+**请求方法**:POST
+
+**功能描述**:处理上传的名片图片,提取名片中的信息,并保存到数据库。
+
+**请求参数**:
+
+Content-Type: multipart/form-data
+
+| 参数名   | 参数类型 | 必须 | 描述   |
+|-------|------|----|----|
+| image | file | 是  | 名片图片 |
+
+**返回结果**:
+
+成功时返回:
+
+```json
+{
+  "success": true,
+  "message": "名片解析成功",
+  "data": {
+    "id": 1,
+    "name_zh": "张三",
+    "name_en": "Zhang San",
+    "title_zh": "项目经理",
+    "title_en": "Project Manager",
+    "mobile": "13800138000",
+    "phone": "010-12345678",
+    "email": "zhangsan@example.com",
+    "hotel_zh": "某酒店",
+    "hotel_en": "Some Hotel",
+    "address_zh": "北京市朝阳区某街某号",
+    "address_en": "Some Street, Chaoyang District, Beijing",
+    "postal_code_zh": "100000",
+    "postal_code_en": "100000",
+    "brand_zh": "某品牌",
+    "brand_en": "Some Brand",
+    "affiliation_zh": "某集团",
+    "affiliation_en": "Some Group",
+    "image_path": "unique-filename.jpg",
+    "career_path": [],
+    "brand_group": "",
+    "created_at": "2023-01-01 12:00:00",
+    "updated_at": null,
+    "updated_by": "system",
+    "status": "active"
+  }
+}
+```
+
+**错误码**:
+
+| HTTP 状态码 | 错误描述       | 错误消息            |
+|---------|------------|-----------------|
+| 400     | 请求参数错误     | 未上传图片           |
+| 400     | 请求参数错误     | 未选择文件           |
+| 400     | 请求参数错误     | 上传的文件不是图片       |
+| 500     | 服务器内部错误    | 名片解析失败: {错误信息}  |
+| 500     | 服务器内部错误    | OCR无法从图像中提取文本   |
+| 500     | 服务器内部错误    | 保存名片信息到数据库失败    |
+
+### 2.2 更新名片信息
+
+**接口地址**:`/business-cards/<int:card_id>`
+
+**请求方法**:PUT
+
+**功能描述**:更新已有名片记录的信息。
+
+**路径参数**:
+
+| 参数名      | 参数类型 | 描述    |
+|----------|------|-------|
+| card_id  | int  | 名片记录ID |
+
+**请求参数**:
+
+请求体为 JSON 格式,可包含以下字段(所有字段都是可选的):
+
+```json
+{
+  "name_zh": "张三",
+  "name_en": "Zhang San",
+  "title_zh": "高级项目经理",
+  "title_en": "Senior Project Manager",
+  "mobile": "13800138000",
+  "phone": "010-12345678",
+  "email": "zhangsan@example.com",
+  "hotel_zh": "某酒店",
+  "hotel_en": "Some Hotel",
+  "address_zh": "北京市朝阳区某街某号",
+  "address_en": "Some Street, Chaoyang District, Beijing",
+  "postal_code_zh": "100000",
+  "postal_code_en": "100000",
+  "brand_zh": "某品牌",
+  "brand_en": "Some Brand",
+  "affiliation_zh": "某集团",
+  "affiliation_en": "Some Group",
+  "career_path": [{"company": "XX公司", "position": "经理"}],
+  "brand_group": "品牌A,品牌B",
+  "updated_by": "user"
+}
+```
+
+**返回结果**:
+
+成功时返回:
+
+```json
+{
+  "success": true,
+  "message": "名片信息已更新",
+  "data": {
+    // 更新后的完整名片信息对象
+  }
+}
+```
+
+**错误码**:
+
+| HTTP 状态码 | 错误描述    | 错误消息                    |
+|---------|---------|-------------------------|
+| 400     | 请求参数错误  | 请求数据为空                  |
+| 404     | 资源不存在   | 未找到ID为{card_id}的名片记录    |
+| 500     | 服务器内部错误 | 更新名片信息失败: {错误信息}        |
+
+### 2.3 获取所有名片列表
+
+**接口地址**:`/get-business-cards`
+
+**请求方法**:GET
+
+**功能描述**:获取系统中所有的名片记录列表。
+
+**请求参数**:无
+
+**返回结果**:
+
+成功时返回:
+
+```json
+{
+  "success": true,
+  "message": "获取名片列表成功",
+  "data": [
+    {
+      // 名片1的完整信息对象
+    },
+    {
+      // 名片2的完整信息对象
+    }
+    // ...更多名片对象
+  ]
+}
+```
+
+**错误码**:
+
+| HTTP 状态码 | 错误描述    | 错误消息              |
+|---------|---------|-------------------|
+| 500     | 服务器内部错误 | 获取名片列表失败: {错误信息}  |
+
+### 2.4 更新名片状态
+
+**接口地址**:`/update-business-cards/<int:card_id>/status`
+
+**请求方法**:PUT
+
+**功能描述**:更新名片的状态(激活或禁用)。
+
+**路径参数**:
+
+| 参数名      | 参数类型 | 描述    |
+|----------|------|-------|
+| card_id  | int  | 名片记录ID |
+
+**请求参数**:
+
+```json
+{
+  "status": "active" // 可选值: "active" 或 "inactive"
+}
+```
+
+**返回结果**:
+
+成功时返回:
+
+```json
+{
+  "success": true,
+  "message": "名片状态已更新为: active",
+  "data": {
+    // 更新后的完整名片信息对象
+  }
+}
+```
+
+**错误码**:
+
+| HTTP 状态码 | 错误描述    | 错误消息                          |
+|---------|---------|-------------------------------|
+| 400     | 请求参数错误  | 请求数据为空或缺少status字段             |
+| 400     | 请求参数错误  | 无效的状态值: {状态值},必须为 active 或 inactive |
+| 404     | 资源不存在   | 未找到ID为{card_id}的名片记录          |
+| 500     | 服务器内部错误 | 更新名片状态失败: {错误信息}              |
+
+### 2.5 获取名片图片
+
+**接口地址**:`/business-cards/image/<path:image_path>`
+
+**请求方法**:GET
+
+**功能描述**:从 MinIO 存储中获取名片图片。
+
+**路径参数**:
+
+| 参数名         | 参数类型   | 描述           |
+|-------------|--------|--------------|
+| image_path  | string | MinIO中的图片路径  |
+
+**返回结果**:
+
+成功时返回图片的二进制数据流,Content-Type 根据图片类型设置。
+
+**错误码**:
+
+| HTTP 状态码 | 错误描述    | 错误消息                |
+|---------|---------|---------------------|
+| 500     | 服务器内部错误 | MinIO客户端初始化失败       |
+| 404     | 资源不存在   | 获取图片失败: {错误信息}      |
+
+## 数据模型
+
+### 名片模型 (BusinessCard)
+
+| 字段名            | 类型       | 描述        |
+|----------------|----------|-----------|
+| id             | Integer  | 主键        |
+| name_zh        | String   | 中文姓名      |
+| name_en        | String   | 英文姓名      |
+| title_zh       | String   | 中文职位/头衔   |
+| title_en       | String   | 英文职位/头衔   |
+| mobile         | String   | 手机号码      |
+| phone          | String   | 固定电话      |
+| email          | String   | 电子邮箱      |
+| hotel_zh       | String   | 中文酒店/公司名称 |
+| hotel_en       | String   | 英文酒店/公司名称 |
+| address_zh     | Text     | 中文地址      |
+| address_en     | Text     | 英文地址      |
+| postal_code_zh | String   | 中文邮政编码    |
+| postal_code_en | String   | 英文邮政编码    |
+| brand_zh       | String   | 中文品牌名称    |
+| brand_en       | String   | 英文品牌名称    |
+| affiliation_zh | String   | 中文隶属关系    |
+| affiliation_en | String   | 英文隶属关系    |
+| image_path     | String   | MinIO中存储的路径 |
+| career_path    | JSON     | 职业轨迹      |
+| brand_group    | String   | 品牌组合      |
+| created_at     | DateTime | 创建时间      |
+| updated_at     | DateTime | 更新时间      |
+| updated_by     | String   | 更新者       |
+| status         | String   | 状态        | 

+ 69 - 0
sample_handle_id_model.txt

@@ -0,0 +1,69 @@
+
+def type_cql_query():
+    query = """
+    MATCH (n:data_model)
+    WHERE id(n) = $nodeId
+    // 获取元数据节点, 数据模型
+    WITH n
+    OPTIONAL MATCH (n)-[:connection]->(a:meta_node)
+    // 获取数据标准
+    OPTIONAL MATCH (n)-[:clean_model]-(d:data_standard)-[:clean_model]->(a) 
+    // 获取数据标签
+    OPTIONAL MATCH (n)-[:label]->(la:data_label)
+    OPTIONAL MATCH (child) where id(child) = n.childernId 
+    OPTIONAL MATCH (ma) where id(ma) = a.master_data 
+    WITH n, a, d, la, n.childrenId AS childrenIds,ma
+    // 遍历 childrenIds 并获取对应的子节点
+    UNWIND childrenIds AS child_id
+    OPTIONAL MATCH (child) WHERE id(child) = child_id
+    // 收集子节点信息
+    WITH n, a, d, la, collect(DISTINCT {id: id(child), name: child.name}) AS childrenId,ma
+    // 收集元数据信息并排序
+    WITH a, d, la, n, childrenId, ma
+    WITH n, collect(DISTINCT {id: id(a), name: a.name, en_name: a.en_name,
+                             data_type: a.data_type, master_data: {id: id(ma), name: ma.name},
+                             data_standard: {id: id(d), name: d.name}}) AS meta_ids,
+            properties(n) AS properties, {id: id(la), name: la.name} AS tag, childrenId
+   // 对 meta_ids 进行排序
+    UNWIND meta_ids AS meta_id
+    WITH n, tag, properties, childrenId, meta_id
+    ORDER BY meta_id.id
+    WITH n, tag, properties, childrenId, collect(meta_id) AS sorted_meta_ids
+    // 构建结果集
+    WITH [{data_resource: null, resource_id: null, meta_ids: sorted_meta_ids}]AS resources, 
+         id(n) as nodeid, tag, properties, n, childrenId
+    UNWIND resources as resource
+    WITH nodeid, collect(resource) as results, tag, properties, n,childrenId
+    // 合并结果集
+    RETURN results, tag, properties, childrenId
+    """
+
+    return query
+
+
+# 数据模型编辑接口
+def handle_id_model(id):
+    # 获取数据模型的名称,元数据名称,对应选中的数据资源名称
+    query = type_cql_query()
+
+    data_ = connect_graph.run(query, nodeId=id)
+
+    res_list = []
+    properties = {}
+
+    for record in data_:
+        res_list = record['results']
+        properties = record['properties']
+        properties['tag'] = record['tag']
+        properties['childrenId'] = record['childrenId']
+        properties.pop('id_list', None)
+        if 'tag' not in properties:
+            properties['tag'] = None
+        if 'describe' not in properties:
+            properties['describe'] = None
+
+    res_dict = {"resource_selected": res_list}
+    merged_dict = {**res_dict, **properties}
+    response_data = {"data_model": merged_dict}
+
+    return response_data

+ 31 - 0
test_minio.py

@@ -0,0 +1,31 @@
+from minio import Minio
+import logging
+
+# Configure logging
+logging.basicConfig(level=logging.INFO)
+logger = logging.getLogger(__name__)
+
+def test_minio_connection():
+    try:
+        # Initialize MinIO client
+        minio_client = Minio(
+            "192.168.3.143:9000",
+            access_key="citu-dataops-acc-key",
+            secret_key="citu-dataops-secret-key",
+            secure=False
+        )
+        
+        # List buckets
+        buckets = minio_client.list_buckets()
+        logger.info("Successfully connected to MinIO server")
+        logger.info("Available buckets:")
+        for bucket in buckets:
+            logger.info(f"- {bucket.name}")
+            
+        return True
+    except Exception as e:
+        logger.error(f"Failed to connect to MinIO server: {str(e)}")
+        return False
+
+if __name__ == "__main__":
+    test_minio_connection()