Преглед изворни кода

将Data_model的elementid改为id。
调整data_model的detail返回格式。
增加data_parse的接口。

maxiaolong пре 1 месец
родитељ
комит
b5b20a6793

+ 4 - 1
app/__init__.py

@@ -5,6 +5,7 @@ import logging
 from app.config.config import config, current_env
 import os
 
+
 db = SQLAlchemy()
 
 def create_app():
@@ -28,7 +29,8 @@ def create_app():
     from app.api.graph import bp as graph_bp
     from app.api.system import bp as system_bp
     from app.api.data_source import bp as data_source_bp
-    
+    from app.api.data_parse import bp as data_parse_bp
+
     app.register_blueprint(meta_bp, url_prefix='/api/meta')
     app.register_blueprint(resource_bp, url_prefix='/api/resource')
     app.register_blueprint(data_model_bp, url_prefix='/api/model')
@@ -38,6 +40,7 @@ def create_app():
     app.register_blueprint(graph_bp, url_prefix='/api/graph')
     app.register_blueprint(system_bp, url_prefix='/api/system')
     app.register_blueprint(data_source_bp, url_prefix='/api/datasource')
+    app.register_blueprint(data_parse_bp, url_prefix='/api/parse')
     # Configure logging
     configure_logging(app)
     

+ 8 - 8
app/api/data_model/routes.py

@@ -1,4 +1,4 @@
-from flask import request
+from flask import request, jsonify
 from app.api.data_model import bp
 from app.models.result import success, failed
 from app.api.graph.routes import MyEncoder
@@ -129,7 +129,7 @@ def data_model_detail():
         receiver = request.get_json()
         # 直接使用字符串ID,不做类型转换
         id = receiver.get('id')
-        print(f"Received elementId from frontend: {id}")
+        print(f"Received id from frontend: {id}")
 
         response_data = model_functions.handle_id_model(id)
         res = success(response_data, "success")
@@ -149,12 +149,12 @@ def data_model_delete():
         # 传入请求参数
         receiver = request.get_json()
         id = receiver.get('id')
-        print(f"Deleting data model with elementId: {id}")
+        print(f"Deleting data model with id: {id}")
 
         # 首先删除数据模型节点
         from app.services.neo4j_driver import neo4j_driver
         query = """
-        MATCH (n:data_model) where elementId(n) = $nodeId
+        MATCH (n:data_model) where id(n) = $nodeId
         detach delete n
         """
         with neo4j_driver.get_session() as session:
@@ -236,7 +236,7 @@ def data_model_list_graph():
         where_clause = ""
 
         if nodeid is not None:
-            where_clause = "MATCH (n)-[:label]->(la) WHERE elementId(la) = $nodeId"
+            where_clause = "MATCH (n)-[:label]->(la) WHERE id(la) = $nodeId"
             params['nodeId'] = nodeid
 
         from app.services.neo4j_driver import neo4j_driver
@@ -245,9 +245,9 @@ def data_model_list_graph():
         OPTIONAL MATCH (n)-[:child]->(child)
         {where_clause}
         WITH 
-            collect(DISTINCT {{id: toString(elementId(n)), text: n.name, type: split(labels(n)[0], '_')[1]}}) AS nodes,
-            collect(DISTINCT {{id: toString(elementId(child)), text: child.name, type: split(labels(child)[0], '_')[1]}}) AS nodes2,
-            collect(DISTINCT {{from: toString(elementId(n)), to: toString(elementId(child)), text: '下级'}}) AS lines
+            collect(DISTINCT {{id: toString(id(n)), text: n.name, type: split(labels(n)[0], '_')[1]}}) AS nodes,
+            collect(DISTINCT {{id: toString(id(child)), text: child.name, type: split(labels(child)[0], '_')[1]}}) AS nodes2,
+            collect(DISTINCT {{from: toString(id(n)), to: toString(id(child)), text: '下级'}}) AS lines
         RETURN nodes + nodes2 AS nodes, lines AS lines
         """
 

+ 5 - 0
app/api/data_parse/__init__.py

@@ -0,0 +1,5 @@
+from flask import Blueprint
+
+bp = Blueprint('data_parse', __name__)
+
+from . import routes 

+ 206 - 0
app/api/data_parse/routes.py

@@ -0,0 +1,206 @@
+from flask import jsonify, request, make_response
+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
+import logging
+
+# 测试用的解析数据接口。没有实际使用。
+@bp.route('/parse', methods=['POST'])
+def parse():
+    """
+    解析数据的接口
+    """
+    try:
+        data = request.get_json()
+        if not data:
+            return jsonify({'error': 'No data provided'}), 400
+            
+        result = parse_data(data)
+        return jsonify(result), 200
+        
+    except Exception as e:
+        return jsonify({'error': str(e)}), 500 
+    
+# 名片解析接口
+@bp.route('/business-card-parse', methods=['POST'])
+def parse_business_card_route():
+    """
+    处理名片图片并提取信息的API接口
+    
+    请求参数:
+        - image: 名片图片文件 (multipart/form-data)
+        
+    返回:
+        - JSON: 包含提取的名片信息和处理状态
+    """
+    # 检查是否上传了文件
+    if 'image' not in request.files:
+        return jsonify({
+            'success': False,
+            'message': '未上传图片',
+            'data': None
+        }), 400
+    
+    image_file = request.files['image']
+    
+    # 检查文件是否为空
+    if image_file.filename == '':
+        return jsonify({
+            'success': False,
+            'message': '未选择文件',
+            'data': None
+        }), 400
+    
+    # 检查文件类型是否为图片
+    if not image_file.content_type.startswith('image/'):
+        return jsonify({
+            'success': False,
+            'message': '上传的文件不是图片',
+            'data': None
+        }), 400
+    
+    # 处理名片图片
+    result = process_business_card(image_file)
+    
+    if result['success']:
+        return jsonify(result), 200
+    else:
+        return jsonify(result), 500
+
+# 更新名片信息接口
+@bp.route('/business-cards/<int:card_id>', methods=['PUT'])
+def update_business_card_route(card_id):
+    """
+    更新名片信息的API接口
+    
+    路径参数:
+        - card_id: 名片记录ID
+    
+    请求参数:
+        - JSON格式的名片信息
+        
+    返回:
+        - JSON: 包含更新后的名片信息和处理状态
+    """
+    # 获取请求数据
+    data = request.json
+    
+    if not data:
+        return jsonify({
+            'success': False,
+            'message': '请求数据为空',
+            'data': None
+        }), 400
+    
+    # 调用业务逻辑函数处理更新
+    result = update_business_card(card_id, data)
+    
+    # 根据处理结果设置HTTP状态码
+    status_code = 200 if result['success'] else 500
+    if 'not found' in result.get('message', '').lower() or '未找到' in result.get('message', ''):
+        status_code = 404
+    
+    return jsonify(result), status_code
+
+# 获取所有名片记录的API接口
+@bp.route('/get-business-cards', methods=['GET'])
+def get_business_cards_route():
+    """
+    获取所有名片记录的API接口
+    
+    返回:
+        - JSON: 包含名片记录列表和处理状态
+    """
+    # 调用业务逻辑函数获取名片列表
+    result = get_business_cards()
+    
+    # 根据处理结果设置HTTP状态码
+    status_code = 200 if result['success'] else 500
+    
+    return jsonify(result), status_code
+
+
+@bp.route('/update-business-cards/<int:card_id>/status', methods=['PUT'])
+def update_business_card_status_route(card_id):
+    """
+    更新名片状态的API接口
+    
+    路径参数:
+        - card_id: 名片记录ID
+    
+    请求参数:
+        - JSON格式,包含status字段
+        
+    返回:
+        - JSON: 包含更新后的名片信息和处理状态
+    """
+    # 获取请求数据
+    data = request.json
+    
+    if not data or 'status' not in data:
+        return jsonify({
+            'success': False,
+            'message': '请求数据为空或缺少status字段',
+            'data': None
+        }), 400
+    
+    status = data['status']
+    
+    # 调用业务逻辑函数处理状态更新
+    result = update_business_card_status(card_id, status)
+    
+    # 根据处理结果设置HTTP状态码
+    status_code = 200 if result['success'] else 500
+    if 'not found' in result.get('message', '').lower() or '未找到' in result.get('message', ''):
+        status_code = 404
+    
+    return jsonify(result), status_code
+
+# 从MinIO获取名片图片的API接口
+@bp.route('/business-cards/image/<path:image_path>', methods=['GET'])
+def get_business_card_image(image_path):
+    """
+    从MinIO获取名片图片的API接口
+    
+    路径参数:
+        - image_path: MinIO中的图片路径
+        
+    返回:
+        - 图片数据流
+    """
+    try:
+        # 获取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()
+        
+        # 确定内容类型
+        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
+        
+    except Exception as e:
+        logging.error(f"获取图片失败: {str(e)}")
+        return jsonify({
+            'success': False,
+            'message': f'获取图片失败: {str(e)}',
+        }), 404
+

+ 514 - 564
app/core/data_model/model.py

@@ -31,17 +31,17 @@ def calculate_model_level(id):
     根据child关系计算数据模型当前的level并自动保存
     
     Args:
-        id: 数据模型的节点ID(字符串)
+        id: 数据模型的节点ID(整数)
         
     Returns:
         None
     """
-    # 确保id是字符串类型
-    node_id = str(id) if id is not None else None
+    # 确保id是整数类型
+    node_id = int(id) if id is not None else None
     
     cql = """
     MATCH (start_node:data_model)
-    WHERE elementId(start_node) = $nodeId
+    WHERE id(start_node) = $nodeId
     CALL {
         WITH start_node
         OPTIONAL MATCH path = (start_node)-[:child*]->(end_node)
@@ -59,7 +59,7 @@ def calculate_model_level(id):
         # 更新level属性
         update_query = """
             MATCH (n:data_model)
-            WHERE elementId(n) = $nodeId
+            WHERE id(n) = $nodeId
             SET n.level = $level
             RETURN n
             """
@@ -342,243 +342,120 @@ def handle_no_meta_data_model(id_lists, receiver, data_model_node):
                         )
 
 
-# 定义查询数据模型详情的Cypher查询
-def type_cql_query():
-    """
-    构建获取数据模型详情的Cypher查询
-    
-    Returns:
-        查询语句
-    """
-    query = """
-    MATCH (n:data_model)
-    WHERE elementId(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 (n)-[:child]->(child_node:data_model)
-    // 收集元数据信息,注意ma变量未定义,需要修复
-    OPTIONAL MATCH (a)-[:master_data]->(ma:master_data)
-    WITH n, a, d, la, collect(DISTINCT {id: elementId(child_node), name: child_node.name}) AS childrenId, ma
-    // 收集元数据信息并排序
-    WITH n, collect(DISTINCT {
-        id: elementId(a), 
-        name: COALESCE(a.name, ""), 
-        en_name: COALESCE(a.en_name, ""),
-        data_type: COALESCE(a.data_type, ""), 
-        master_data: CASE 
-            WHEN ma IS NOT NULL THEN {id: elementId(ma), name: COALESCE(ma.name, "")}
-            ELSE NULL 
-        END,
-        data_standard: CASE 
-            WHEN d IS NOT NULL THEN {id: elementId(d), name: COALESCE(d.name, "")}
-            ELSE NULL 
-        END
-    }) AS meta_ids,
-    properties(n) AS properties, 
-    CASE 
-        WHEN la IS NOT NULL THEN {id: elementId(la), name: COALESCE(la.name, "")}
-        ELSE NULL 
-    END 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, 
-         elementId(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):
     """
-    获取数据模型详情
+    处理数据模型详情请求,查询指定ID的数据模型节点的完整信息
     
     Args:
-        id: 数据模型ID (字符串)
+        id: 数据模型节点ID
         
     Returns:
-        数据模型详情
-    """
-    # 获取数据模型的名称,元数据名称,对应选中的数据资源名称
-    query = type_cql_query()
-
-    # 确保id参数为字符串类型并进行日志输出
-    node_id = str(id) if id is not None else None
-    print(f"Querying data model with elementId: {node_id}")
-
-    with connect_graph().session() as session:
-        try:
-            result = session.run(query, nodeId=node_id)
-            data_ = result.data()
-            print(f"Query result: {data_}")
-
-            if not data_:
-                print(f"No data found for elementId: {node_id}")
-                return {"data_model": {}}
-
-            res_list = []
-            properties = {}
-
-            for record in data_:
-                if 'results' in record:
-                    res_list = record['results']
-                if 'properties' in record:
-                    properties = record['properties']
-                if 'tag' in record and record['tag'] is not None:
-                    properties['tag'] = record['tag']
-                if 'childrenId' in record and record['childrenId'] is not None:
-                    properties['childrenId'] = record['childrenId']
-                
-                # 处理id值,确保是字符串格式
-                if 'id' in properties and properties['id'] is not None:
-                    properties['id'] = str(properties['id'])
-                    
-                # 处理tag中的id
-                if 'tag' in properties and properties['tag'] is not None and 'id' in properties['tag']:
-                    properties['tag']['id'] = str(properties['tag']['id'])
-                    
-                # 处理childrenId列表中的id
-                if 'childrenId' in properties and properties['childrenId']:
-                    for child in properties['childrenId']:
-                        if 'id' in child:
-                            child['id'] = str(child['id'])
-                
-                properties.pop('id_list', None)
-                if 'tag' not in properties:
-                    properties['tag'] = None
-                if 'describe' not in properties:
-                    properties['describe'] = None
-
-            # 处理结果中的id值为字符串
-            if res_list:
-                for res in res_list:
-                    if 'resource_id' in res and res['resource_id'] is not None:
-                        res['resource_id'] = str(res['resource_id'])
-                    if 'meta_ids' in res:
-                        for meta in res['meta_ids']:
-                            if 'id' in meta:
-                                meta['id'] = str(meta['id'])
-                            if 'data_standard' in meta and meta['data_standard'] and 'id' in meta['data_standard']:
-                                meta['data_standard']['id'] = str(meta['data_standard']['id'])
-                            if 'master_data' in meta and meta['master_data'] and 'id' in meta['master_data']:
-                                meta['master_data']['id'] = str(meta['master_data']['id'])
-
-            res_dict = {"resource_selected": res_list}
-            merged_dict = {**res_dict, **properties}
-            response_data = {"data_model": merged_dict}
-
-            return response_data
-        except Exception as e:
-            print(f"Error in handle_id_model: {str(e)}")
-            import traceback
-            traceback.print_exc()
-            return {"data_model": {}}
-
-
-# 数据模型详情
-'''
-def handle_id_model(model_id):
-    """
-    获取数据模型详情
-    
-    Args:
-        model_id: 数据模型ID
-        
-    Returns:
-        数据模型详情
-    """
-    model_detail_query = """
-    MATCH (n:data_model) WHERE id(n) = $model_id
-    RETURN n
-    """
-    
-    with connect_graph().session() as session:
-        model_detail_result = session.run(model_detail_query, model_id=model_id).data()
-        
-        if not model_detail_result:
-            return None
-        
-        model_detail = model_detail_result[0]['n']
-        model_info = dict(model_detail)
-        model_info['id'] = model_id
-        
-        # 获取data_model节点连接的resource节点
-        resource_query = """
-        MATCH (n:data_model)-[:resource]->(r:data_resource) WHERE id(n) = $model_id
-        RETURN r
-        """
-        resource_result = session.run(resource_query, model_id=model_id).data()
-        resources = []
-        
-        for item in resource_result:
-            if 'r' in item and hasattr(item['r'], 'id'):
-                resource = dict(item['r'])
-                resource['id'] = item['r'].id
-                resources.append(resource)
-        
-        model_info['resources'] = resources
-        
-        # 获取data_model节点连接的component节点
-        component_query = """
-        MATCH (n:data_model)-[:component]->(m:meta_node) WHERE id(n) = $model_id
-        RETURN m
-        """
-        component_result = session.run(component_query, model_id=model_id).data()
-        components = []
-        
-        for item in component_result:
-            if 'm' in item and hasattr(item['m'], 'id'):
-                component = dict(item['m'])
-                component['id'] = item['m'].id
-                components.append(component)
-        
-        model_info['components'] = components
-        
-        # 获取data_model节点连接的use节点
-        use_query = """
-        MATCH (n:data_model)-[:use]->(u:data_model) WHERE id(n) = $model_id
-        RETURN u
-        """
-        use_result = session.run(use_query, model_id=model_id).data()
-        uses = []
-        
-        for item in use_result:
-            if 'u' in item and hasattr(item['u'], 'id'):
-                use = dict(item['u'])
-                use['id'] = item['u'].id
-                uses.append(use)
-        
-        model_info['uses'] = uses
-        
-        # 获取data_model节点连接的标签
-        tag_query = """
-        MATCH (n:data_model)-[:label]->(t:data_label) WHERE id(n) = $model_id
-        RETURN t
+        dict: 包含数据模型完整详情信息的字典
+    """
+    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)
+        
+        // 获取模型的标签
+        OPTIONAL MATCH (n)-[:label]->(tag:data_label)
+        
+        // 获取模型的下级节点(子节点)
+        OPTIONAL MATCH (n)-[:child]->(child:data_model)
+        
+        // 获取模型关联的元数据节点
+        OPTIONAL MATCH (n)-[:component]->(meta:meta_node)
+        
+        // 收集所有关联节点信息
+        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
+             
+        RETURN {
+            // 基本信息
+            id: id(n),
+            name: n.name,
+            en_name: n.en_name,
+            // 标签信息从tag变量中获取
+            label: tag.name,
+            time: n.time,
+            description: n.description,
+            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 | {
+                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 | {
+                id: id(meta),
+                name: meta.name,
+                en_name: meta.en_name,
+                data_type: meta.data_type
+            }],
+            
+            // 原始属性
+            properties: properties(n)
+        } AS result
         """
-        tag_result = session.run(tag_query, model_id=model_id).data()
-        
-        if tag_result and 't' in tag_result[0] and hasattr(tag_result[0]['t'], 'id'):
-            tag = dict(tag_result[0]['t'])
-            tag['id'] = tag_result[0]['t'].id
-            model_info['tag'] = tag
         
-        return model_info
-'''
+        with connect_graph().session() as session:
+            result = session.run(cql, nodeId=node_id)
+            
+            # 处理查询结果
+            record = result.single()
+            logging.info(f"获得查询结果---------->>>{record}")
+            
+            # 直接检查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 {}
 
 
 # 数据模型列表
@@ -588,424 +465,497 @@ def model_list(skip_count, page_size, en_name_filter=None, name_filter=None,
     获取数据模型列表
     
     Args:
-        skip_count: 跳过的记录数量
-        page_size: 每页记录数量
+        skip_count: 跳过的数量
+        page_size: 页面大小
         en_name_filter: 英文名称过滤条件
         name_filter: 名称过滤条件
-        category: 类过滤条件
+        category: 类过滤条件
         tag: 标签过滤条件
-        level: 级过滤条件
+        level: 级过滤条件
         
     Returns:
-        tuple: (数据列表, 总记录数)
-    """
-    # 构建查询条件
-    params = {}
-    match_clause = "MATCH (n:data_model)"
-    where_clause = []
-    
-    if tag:
-        match_clause = "MATCH (n:data_model)-[:label]->(t:data_label)"
-        where_clause.append("elementId(t) = $tag")
-        params['tag'] = str(tag)
-    
-    if name_filter:
-        where_clause.append("n.name =~ $name_filter")
-        params['name_filter'] = f"(?i).*{name_filter}.*"
-    
-    if en_name_filter:
-        where_clause.append("n.en_name =~ $en_name_filter")
-        params['en_name_filter'] = f"(?i).*{en_name_filter}.*"
-    
-    if category:
-        where_clause.append("n.category = $category")
-        params['category'] = category
-    
-    if level:
-        where_clause.append("n.level = $level")
-        params['level'] = level
-    
-    # 转换为字符串形式
-    where_str = " AND ".join(where_clause)
-    if where_str:
-        where_str = "WHERE " + where_str
-    
-    # 获取数据总数
-    count_query = f"""
-    {match_clause}
-    {where_str}
-    RETURN count(n) as count
+        tuple: (数据模型列表, 总数量)
     """
-    
-    # 使用正确的session方式执行查询
-    driver = connect_graph()
-    if not driver:
-        return [], 0
-        
-    with driver.session() as session:
-        count_result = session.run(count_query, **params)
-        count = count_result.single()["count"]
+    try:
+        # 构建where子句
+        where_clause = []
+        params = {}
+
+        if name_filter is not None:
+            where_clause.append("n.name =~ $name")
+            params['name'] = f".*{name_filter}.*"
         
-        # 获取分页数据
-        params['skip'] = skip_count
-        params['limit'] = page_size
+        if en_name_filter is not None:
+            where_clause.append("n.en_name =~ $en_name")
+            params['en_name'] = f".*{en_name_filter}.*"
+
+        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子句
+        where_str = " AND ".join(where_clause)
+        if where_str:
+            where_str = f"WHERE {where_str}"
         
-        data_query = f"""
-        {match_clause}
-        {where_str}
-        OPTIONAL MATCH (n)-[:label]->(t:data_label)
-        WITH n, t
-        OPTIONAL MATCH (n)-[:component]->(m:meta_node)
-        RETURN 
-            elementId(n) as id, 
-            n.name as name, 
-            n.en_name as en_name, 
-            n.category as category, 
-            n.description as description, 
+        # 构建查询
+        with connect_graph().session() as session:
+            # 计算总数量
+            count_query = f"""
+            MATCH (n:data_model)
+            OPTIONAL MATCH (n)-[:label]->(t)
+            {where_str}
+            RETURN COUNT(DISTINCT n) AS count
+            """
+            count_result = session.run(count_query, **params)
+            count_record = count_result.single()
+            total = count_record['count'] if count_record else 0
+
+            # 查询数据
+            query = f"""
+            MATCH (n:data_model)
+            OPTIONAL MATCH (n)-[:label]->(t)
+            {where_str}
+            RETURN DISTINCT 
+            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,
-            t.name as tag_name,
-            elementId(t) as tag_id,
-            count(m) as component_count
-        ORDER BY n.time DESC
-        SKIP $skip
-        LIMIT $limit
-        """
-        
-        result = session.run(data_query, **params)
-        data = result.data()
-        
-        # 确保结果中的ID是字符串格式
-        for item in data:
-            if 'id' in item:
-                item['id'] = str(item['id'])
-            if 'tag_id' in item and item['tag_id'] is not None:
-                item['tag_id'] = str(item['tag_id'])
-        
-    return data, count
+            id(t) as tag_id,
+            t.name as tag_name
+            ORDER BY n.time DESC
+            SKIP $skip
+            LIMIT $limit
+            """
+            
+            result = session.run(query, skip=skip_count, limit=page_size, **params)
+            
+            # 处理结果
+            data = []
+            for record in result:
+                item = {
+                    "id": record['id'],
+                    "name": record['name'],
+                    "en_name": record['en_name'],
+                    "time": record['time'],
+                    "description": record['description'],
+                    "level": record['level'],
+                    "tag": {"id": record['tag_id'], "name": record['tag_name']} if record['tag_id'] is not None else None
+                }
+                data.append(item)
+            
+            return data, total
+    except Exception as e:
+        print(f"Error in model_list: {str(e)}")
+        import traceback
+        traceback.print_exc()
+        return [], 0
 
 
 # 有血缘关系的数据资源列表
 def model_resource_list(skip_count, page_size, name_filter=None, id=None, 
                         category=None, time=None):
     """
-    获取有血缘关系的数据资源列表
+    获取数据模型相关的数据资源列表
     
     Args:
-        skip_count: 跳过的记录数量
-        page_size: 每页记录数量
+        skip_count: 跳过的数量
+        page_size: 页面大小
         name_filter: 名称过滤条件
-        id: 数据资源ID
-        category: 类过滤条件
+        id: 数据模型ID
+        category: 类过滤条件
         time: 时间过滤条件
         
     Returns:
-        tuple: (数据列表, 总记录数)
-    """
-    # 构建查询条件
-    params = {'id': id}
-    where_clause = []
-    
-    if name_filter:
-        where_clause.append("n.name =~ $name_filter")
-        params['name_filter'] = f"(?i).*{name_filter}.*"
-    
-    if category:
-        where_clause.append("n.category = $category")
-        params['category'] = category
-    
-    if time:
-        where_clause.append("n.time >= $time")
-        params['time'] = time
-    
-    # 转换为字符串形式
-    where_str = " AND ".join(where_clause)
-    if where_str:
-        where_str = "WHERE " + where_str
-    
-    # 获取数据总数
-    count_query = f"""
-    MATCH (search:data_resource) WHERE id(search) = $id
-    MATCH (search)-[:connection]->(mn:meta_node)<-[:connection]-(n:data_resource)
-    {where_str}
-    RETURN count(DISTINCT n) as count
-    """
-    
-    # 使用正确的session方式执行查询
-    driver = connect_graph()
-    if not driver:
-        return [], 0
-        
-    with driver.session() as session:
-        count_result = session.run(count_query, **params)
-        count = count_result.single()["count"]
-        
-        # 获取分页数据
-        params['skip'] = skip_count
-        params['limit'] = page_size
-        
-        data_query = f"""
-        MATCH (search:data_resource) WHERE id(search) = $id
-        MATCH (search)-[:connection]->(mn:meta_node)<-[:connection]-(n:data_resource)
-        {where_str}
-        WITH DISTINCT n, mn
-        RETURN 
-            id(n) as id, 
-            n.name as name, 
-            n.en_name as en_name, 
-            n.category as category, 
-            n.description as description, 
-            n.time as time,
-            collect({{id: id(mn), name: mn.name}}) as common_meta
-        ORDER BY n.time DESC
-        SKIP $skip
-        LIMIT $limit
+        tuple: (数据资源列表, 总数量)
+    """
+    try:
+        # 构建基础查询
+        base_query = """
+        MATCH (n:data_model)
+        WHERE id(n) = $nodeId
+        MATCH (n)-[:children]->(m:data_resource)
         """
         
-        result = session.run(data_query, **params)
-        data = result.data()
+        # 计算总数量
+        count_query = base_query + """
+        RETURN COUNT(m) as count
+        """
         
-    return data, count
+        with connect_graph().session() as session:
+            # 执行计数查询
+            count_result = session.run(count_query, nodeId=id)
+            count_record = count_result.single()
+            total = count_record['count'] if count_record else 0
+            
+            # 使用分页和筛选条件构建主查询
+            main_query = base_query + """
+            MATCH (m)-[:label]->(l)
+            WHERE id(n) = $nodeId and labels(m) <> ['meta_node']
+            RETURN m.name as name, 
+                   m.en_name as en_name,
+                   id(m) as id,
+                   l.name as label,
+                   m.time as time,
+                   m.description as description,
+                   m.category as category 
+            ORDER BY m.time DESC
+            SKIP $skip LIMIT $limit
+            """
+            
+            # 执行主查询
+            result = session.run(main_query, nodeId=id, skip=skip_count, limit=page_size)
+            
+            # 处理结果
+            data = []
+            for record in result:
+                item = {
+                    "name": record['name'],
+                    "en_name": record['en_name'],
+                    "id": record['id'],
+                    "label": record['label'],
+                    "time": record['time'],
+                    "description": record['description'],
+                    "category": record['category']
+                }
+                data.append(item)
+            
+            return data, total
+    except Exception as e:
+        print(f"Error in model_resource_list: {str(e)}")
+        import traceback
+        traceback.print_exc()
+        return [], 0
 
 
 # 数据模型血缘图谱
 def model_kinship_graph(nodeid, meta=False):
     """
-    获取数据模型血缘图谱
+    生成数据模型的血缘关系图谱
     
     Args:
-        nodeid: 节点ID(字符串)
-        meta: 是否返回元数据
+        nodeid: 节点ID
+        meta: 是否包含元数据
         
     Returns:
-        图谱数据
+        dict: 包含节点和连线信息的图谱数据
     """
-    # 确保nodeid是字符串类型
-    node_id = str(nodeid) if nodeid is not None else None
+    result = {}
     
-    if meta:
-        query = """
-        MATCH p = (n:data_model)-[r:component|resource*..3]-(m) 
-        WHERE elementId(n) = $nodeId
-        WITH p, relationships(p) as rels
-        RETURN p
-        limit 300
+    with connect_graph().session() as session:
+        # 查询起始模型节点
+        start_node_query = """
+        MATCH (n:data_model)
+        WHERE id(n) = $nodeId
+        RETURN n.name as name, n.en_name as en_name
         """
-    else:
-        query = """
-        MATCH p = (n:data_model)-[r:resource*..3]-(m) 
-        WHERE elementId(n) = $nodeId and labels(m) <> ['meta_node']
-        WITH p, relationships(p) as rels
-        RETURN p
-        limit 300
+        
+        start_result = session.run(start_node_query, nodeId=nodeid)
+        start_record = start_result.single()
+        
+        if not start_record:
+            return {"nodes": [], "lines": []}
+        
+        # 查询与模型关联的数据资源
+        resource_query = """
+        MATCH (n:data_model)
+        WHERE id(n) = $nodeId
+        MATCH p = (n)-[:children]->(resource:data_resource)
+        RETURN resource
         """
-    
-    # 使用正确的session方式执行查询
-    driver = connect_graph()
-    if not driver:
-        return {"nodes": [], "edges": []}
         
-    with driver.session() as session:
-        result = session.run(query, nodeId=node_id)
+        resource_result = session.run(resource_query, nodeId=nodeid)
         
-        nodes = set()
-        relationships = set()
-        nodes_by_id = {}
+        nodes = [{"id": str(nodeid), "text": start_record['name'], "type": "model"}]
+        lines = []
         
-        for record in result:
-            path = record["p"]
+        # 处理资源节点
+        for record in resource_result:
+            if 'resource' in record:
+                resource = record['resource']
+                resource_id = str(resource.id)
+                resource_name = resource.get('name', '')
+                resource_en_name = resource.get('en_name', '')
+                
+                # 创建资源节点
+                resource_node = {
+                    "id": resource_id,
+                    "text": resource_name,
+                    "type": "resource"
+                }
+                nodes.append(resource_node)
+                
+                # 创建资源到模型的关系
+                line = {
+                    "from": str(nodeid),
+                    "to": resource_id,
+                    "text": "resource"
+                }
+                lines.append(line)
+
+        # 处理元数据节点
+        if meta:
+            meta_query = """
+            MATCH (n:data_model)
+            WHERE id(n) = $nodeId and labels(m) <> ['meta_node']
+            MATCH p = (n)-[:meta]->(meta:meta_node)
+            RETURN meta
+            """
+            meta_result = session.run(meta_query, nodeId=nodeid)
             
-            for node in path.nodes:
-                node_id_str = str(node.element_id) if hasattr(node, 'element_id') else str(node.id)
-                if node_id_str not in nodes:
-                    node_type = list(node.labels)[0].split('_')[1]
-                    node_data = {
-                        "id": node_id_str,
-                        "text": node.get("name", ""),
-                        "type": node_type
+            for record in meta_result:
+                if 'meta' in record:
+                    meta_node = record['meta']
+                    meta_id = str(meta.id)
+                    meta_name = meta.get('name', '')
+                    meta_en_name = meta.get('en_name', '')
+                    
+                    # 创建元数据节点
+                    meta_node = {
+                        "id": meta_id,
+                        "text": meta_name,
+                        "type": "meta"
                     }
+                    nodes.append(meta_node)
                     
-                    nodes.add(node_id_str)
-                    nodes_by_id[node_id_str] = node_data
-            
-            for rel in path.relationships:
-                start_id = str(rel.start_node.element_id) if hasattr(rel.start_node, 'element_id') else str(rel.start_node.id)
-                end_id = str(rel.end_node.element_id) if hasattr(rel.end_node, 'element_id') else str(rel.end_node.id)
-                relationship_id = f"{start_id}-{end_id}"
-                if relationship_id not in relationships:
-                    relationship_data = {
-                        "from": start_id,
-                        "to": end_id,
-                        "text": type(rel).__name__
+                    # 创建模型到元数据的标签关系
+                    tag_line = {
+                        "from": str(nodeid),
+                        "to": meta_id,
+                        "text": "component"
                     }
-                    relationships.add(relationship_id)
-        
-        # 转换为所需格式
-        return {
-            "nodes": list(nodes_by_id.values()),
-            "edges": [{"from": rel.split("-")[0], "to": rel.split("-")[1], "text": ""} 
-                    for rel in relationships]
+                    lines.append(tag_line)
+
+        # 构建结果
+        result = {
+            "nodes": nodes,
+            "lines": lines
         }
+        return result
 
 
 # 数据模型影响图谱
 def model_impact_graph(nodeid, meta=False):
     """
-    获取数据模型影响图谱
+    生成数据模型的影响关系图谱
     
     Args:
-        nodeid: 节点ID(字符串)
-        meta: 是否返回元数据
+        nodeid: 节点ID
+        meta: 是否包含元数据
         
     Returns:
-        图谱数据
+        dict: 包含节点和连线信息的图谱数据
     """
-    # 确保nodeid是字符串类型
-    node_id = str(nodeid) if nodeid is not None else None
+    result = {}
     
-    if meta:
-        query = """
-        MATCH p = (n:data_model)-[r:use*..3]-(m) 
-        WHERE elementId(n) = $nodeId
-        WITH p, relationships(p) as rels
-        RETURN p
-        limit 300
+    with connect_graph().session() as session:
+        # 查询起始模型节点
+        start_node_query = """
+        MATCH (n:data_model)
+        WHERE id(n) = $nodeId
+        RETURN n.name as name, n.en_name as en_name
         """
-    else:
-        query = """
-        MATCH p = (n:data_model)-[r:use*..3]-(m) 
-        WHERE elementId(n) = $nodeId
-        WITH p, relationships(p) as rels
-        RETURN p
-        limit 300
+        
+        start_result = session.run(start_node_query, nodeId=nodeid)
+        start_record = start_result.single()
+        
+        if not start_record:
+            return {"nodes": [], "lines": []}
+        
+        # 查询影响模型的数据资源
+        resource_query = """
+        MATCH (n:data_model)
+        WHERE id(n) = $nodeId
+        MATCH p = (n)-[:children]->(resource:data_resource)
+        RETURN resource
         """
-    
-    # 使用正确的session方式执行查询
-    driver = connect_graph()
-    if not driver:
-        return {"nodes": [], "edges": []}
         
-    with driver.session() as session:
-        result = session.run(query, nodeId=node_id)
+        resource_result = session.run(resource_query, nodeId=nodeid)
         
-        nodes = set()
-        relationships = set()
-        nodes_by_id = {}
+        nodes = [{"id": str(nodeid), "text": start_record['name'], "type": "model"}]
+        lines = []
         
-        for record in result:
-            path = record["p"]
+        # 处理资源节点
+        for record in resource_result:
+            if 'resource' in record:
+                resource = record['resource']
+                resource_id = str(resource.id)
+                resource_name = resource.get('name', '')
+                resource_en_name = resource.get('en_name', '')
+                
+                # 创建资源节点
+                resource_node = {
+                    "id": resource_id,
+                    "text": resource_name,
+                    "type": "resource"
+                }
+                nodes.append(resource_node)
+                
+                # 创建资源到模型的关系
+                line = {
+                    "from": str(nodeid),
+                    "to": resource_id,
+                    "text": "resource"
+                }
+                lines.append(line)
+
+        # 处理元数据节点
+        if meta:
+            meta_query = """
+            MATCH (n:data_model)
+            WHERE id(n) = $nodeId and labels(m) <> ['meta_node']
+            MATCH p = (n)-[:meta]->(meta:meta_node)
+            RETURN meta
+            """
+            meta_result = session.run(meta_query, nodeId=nodeid)
             
-            for node in path.nodes:
-                node_id_str = str(node.element_id) if hasattr(node, 'element_id') else str(node.id)
-                if node_id_str not in nodes:
-                    node_type = list(node.labels)[0].split('_')[1]
-                    node_data = {
-                        "id": node_id_str,
-                        "text": node.get("name", ""),
-                        "type": node_type
+            for record in meta_result:
+                if 'meta' in record:
+                    meta_node = record['meta']
+                    meta_id = str(meta.id)
+                    meta_name = meta.get('name', '')
+                    meta_en_name = meta.get('en_name', '')
+                    
+                    # 创建元数据节点
+                    meta_node = {
+                        "id": meta_id,
+                        "text": meta_name,
+                        "type": "meta"
                     }
+                    nodes.append(meta_node)
                     
-                    nodes.add(node_id_str)
-                    nodes_by_id[node_id_str] = node_data
-            
-            for rel in path.relationships:
-                start_id = str(rel.start_node.element_id) if hasattr(rel.start_node, 'element_id') else str(rel.start_node.id)
-                end_id = str(rel.end_node.element_id) if hasattr(rel.end_node, 'element_id') else str(rel.end_node.id)
-                relationship_id = f"{start_id}-{end_id}"
-                if relationship_id not in relationships:
-                    relationship_data = {
-                        "from": start_id,
-                        "to": end_id,
-                        "text": type(rel).__name__
+                    # 创建模型到元数据的标签关系
+                    tag_line = {
+                        "from": str(nodeid),
+                        "to": meta_id,
+                        "text": "component"
                     }
-                    relationships.add(relationship_id)
-        
-        # 转换为所需格式
-        return {
-            "nodes": list(nodes_by_id.values()),
-            "edges": [{"from": rel.split("-")[0], "to": rel.split("-")[1], "text": ""} 
-                    for rel in relationships]
+                    lines.append(tag_line)
+
+        # 构建结果
+        result = {
+            "nodes": nodes,
+            "lines": lines
         }
+        return result
 
 
 # 数据模型全部图谱
 def model_all_graph(nodeid, meta=False):
     """
-    获取数据模型全部图谱
+    生成数据模型的所有关系图谱
     
     Args:
-        nodeid: 节点ID(字符串)
-        meta: 是否返回元数据
+        nodeid: 节点ID
+        meta: 是否包含元数据
         
     Returns:
-        图谱数据
+        dict: 包含节点和连线信息的图谱数据
     """
-    # 确保nodeid是字符串类型
-    node_id = str(nodeid) if nodeid is not None else None
+    result = {}
     
-    if meta:
-        query = """
-        MATCH p = (n:data_model)-[r*..3]-(m) 
-        WHERE elementId(n) = $nodeId
-        WITH p, relationships(p) as rels
-        RETURN p
-        limit 300
+    with connect_graph().session() as session:
+        # 查询起始模型节点
+        start_node_query = """
+        MATCH (n:data_model)
+        WHERE id(n) = $nodeId
+        RETURN n.name as name, n.en_name as en_name
         """
-    else:
-        query = """
-        MATCH p = (n:data_model)-[r*..3]-(m) 
-        WHERE elementId(n) = $nodeId and labels(m) <> ['meta_node']
-        WITH p, relationships(p) as rels
-        RETURN p
-        limit 300
+        
+        start_result = session.run(start_node_query, nodeId=nodeid)
+        start_record = start_result.single()
+        
+        if not start_record:
+            return {"nodes": [], "lines": []}
+        
+        # 查询与模型关联的数据资源
+        resource_query = """
+        MATCH (n:data_model)
+        WHERE id(n) = $nodeId
+        MATCH p = (n)-[:children]->(resource:data_resource)
+        RETURN resource
         """
-    
-    # 使用正确的session方式执行查询
-    driver = connect_graph()
-    if not driver:
-        return {"nodes": [], "edges": []}
         
-    with driver.session() as session:
-        result = session.run(query, nodeId=node_id)
+        resource_result = session.run(resource_query, nodeId=nodeid)
         
-        nodes = set()
-        relationships = set()
-        nodes_by_id = {}
+        # 查询与模型关联的元数据
+        meta_query = """
+        MATCH (n:data_model)
+        WHERE id(n) = $nodeId and labels(m) <> ['meta_node']
+        MATCH p = (n)-[:meta]->(meta:meta_node)
+        RETURN meta
+        """
+        
+        nodes = [{"id": str(nodeid), "text": start_record['name'], "type": "model"}]
+        lines = []
         
-        for record in result:
-            path = record["p"]
+        # 处理资源节点
+        for record in resource_result:
+            if 'resource' in record:
+                resource = record['resource']
+                resource_id = str(resource.id)
+                resource_name = resource.get('name', '')
+                resource_en_name = resource.get('en_name', '')
+                
+                # 创建资源节点
+                resource_node = {
+                    "id": resource_id,
+                    "text": resource_name,
+                    "type": "resource"
+                }
+                nodes.append(resource_node)
+                
+                # 创建资源到模型的关系
+                line = {
+                    "from": str(nodeid),
+                    "to": resource_id,
+                    "text": "resource"
+                }
+                lines.append(line)
+
+        # 处理元数据节点
+        if meta:
+            meta_result = session.run(meta_query, nodeId=nodeid)
             
-            for node in path.nodes:
-                node_id_str = str(node.element_id) if hasattr(node, 'element_id') else str(node.id)
-                if node_id_str not in nodes:
-                    node_type = list(node.labels)[0].split('_')[1]
-                    node_data = {
-                        "id": node_id_str,
-                        "text": node.get("name", ""),
-                        "type": node_type
+            for record in meta_result:
+                if 'meta' in record:
+                    meta_node = record['meta']
+                    meta_id = str(meta.id)
+                    meta_name = meta.get('name', '')
+                    meta_en_name = meta.get('en_name', '')
+                    
+                    # 创建元数据节点
+                    meta_node = {
+                        "id": meta_id,
+                        "text": meta_name,
+                        "type": "meta"
                     }
+                    nodes.append(meta_node)
                     
-                    nodes.add(node_id_str)
-                    nodes_by_id[node_id_str] = node_data
-            
-            for rel in path.relationships:
-                start_id = str(rel.start_node.element_id) if hasattr(rel.start_node, 'element_id') else str(rel.start_node.id)
-                end_id = str(rel.end_node.element_id) if hasattr(rel.end_node, 'element_id') else str(rel.end_node.id)
-                relationship_id = f"{start_id}-{end_id}"
-                if relationship_id not in relationships:
-                    relationship_data = {
-                        "from": start_id,
-                        "to": end_id,
-                        "text": type(rel).__name__
+                    # 创建模型到元数据的标签关系
+                    tag_line = {
+                        "from": str(nodeid),
+                        "to": meta_id,
+                        "text": "component"
                     }
-                    relationships.add(relationship_id)
-        
-        # 转换为所需格式
-        return {
-            "nodes": list(nodes_by_id.values()),
-            "edges": [{"from": rel.split("-")[0], "to": rel.split("-")[1], "text": ""} 
-                    for rel in relationships]
+                    lines.append(tag_line)
+
+        # 构建结果
+        result = {
+            "nodes": nodes,
+            "lines": lines
         }
+        return result
 
 
 # 更新数据模型

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

@@ -0,0 +1,822 @@
+from typing import Dict, Any
+from app import db
+from datetime import datetime
+import os
+import boto3
+from botocore.config import Config
+import logging
+import requests
+import json
+import re
+import uuid
+from PIL import Image
+from io import BytesIO
+import pytesseract
+import base64
+from openai import OpenAI
+
+
+# 测试用的解析数据接口。没有实际使用。      
+def parse_data(data: Dict[str, Any]) -> Dict[str, Any]:
+    """
+    解析数据的主函数
+    
+    Args:
+        data: 要解析的数据
+        
+    Returns:
+        解析后的数据
+    """
+    # TODO: 实现数据解析逻辑
+    return {
+        'status': 'success',
+        'message': 'Data parsed successfully',
+        'data': data
+    } 
+
+# 名片解析数据模型
+class BusinessCard(db.Model):
+    __tablename__ = 'business_cards'
+    
+    id = db.Column(db.Integer, primary_key=True)
+    name_zh = db.Column(db.String(100), nullable=False)
+    name_en = db.Column(db.String(100))
+    title_zh = db.Column(db.String(100))
+    title_en = db.Column(db.String(100))
+    mobile = db.Column(db.String(50))
+    phone = db.Column(db.String(50))
+    email = db.Column(db.String(100))
+    hotel_zh = db.Column(db.String(200))
+    hotel_en = db.Column(db.String(200))
+    address_zh = db.Column(db.Text)
+    address_en = db.Column(db.Text)
+    postal_code_zh = db.Column(db.String(20))
+    postal_code_en = db.Column(db.String(20))
+    brand_zh = db.Column(db.String(100))
+    brand_en = db.Column(db.String(100))
+    affiliation_zh = db.Column(db.String(200))
+    affiliation_en = db.Column(db.String(200))
+    image_path = db.Column(db.String(255))  # MinIO中存储的路径
+    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))
+    status = db.Column(db.String(20), default='active')
+    
+    def to_dict(self):
+        return {
+            'id': self.id,
+            'name_zh': self.name_zh,
+            'name_en': self.name_en,
+            'title_zh': self.title_zh,
+            'title_en': self.title_en,
+            'mobile': self.mobile,
+            'phone': self.phone,
+            'email': self.email,
+            'hotel_zh': self.hotel_zh,
+            'hotel_en': self.hotel_en,
+            'address_zh': self.address_zh,
+            'address_en': self.address_en,
+            'postal_code_zh': self.postal_code_zh,
+            'postal_code_en': self.postal_code_en,
+            'brand_zh': self.brand_zh,
+            'brand_en': self.brand_en,
+            'affiliation_zh': self.affiliation_zh,
+            'affiliation_en': self.affiliation_en,
+            'image_path': self.image_path,
+            '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,
+            'status': self.status
+        }
+
+
+# 名片解析功能模块
+
+# 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')
+# 备用API端点
+DEEPSEEK_API_URL_BACKUP = 'https://api.deepseek.com/v1/completions'
+
+# OCR配置
+# 设置pytesseract路径(如果需要)
+# pytesseract.pytesseract.tesseract_cmd = r'/path/to/tesseract'
+# OCR语言设置,支持多语言
+OCR_LANG = os.environ.get('OCR_LANG', 'chi_sim+eng')
+
+def get_minio_client():
+    """获取MinIO客户端连接"""
+    try:
+        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客户端可能需要
+        )
+        
+        # 确保存储桶存在
+        if MINIO_BUCKET not in [bucket['Name'] for bucket in minio_client.list_buckets()['Buckets']]:
+            minio_client.create_bucket(Bucket=MINIO_BUCKET)
+            
+        return minio_client
+    except Exception as e:
+        logging.error(f"MinIO连接错误: {str(e)}")
+        return None
+
+def extract_text_from_image(image_data):
+    """
+    使用OCR从图像中提取文本,然后通过DeepSeek API解析名片信息
+    
+    Args:
+        image_data (bytes): 图像的二进制数据
+        
+    Returns:
+        dict: 提取的信息(姓名、职位、公司等)
+    
+    Raises:
+        Exception: 当OCR或API调用失败或配置错误时抛出异常
+    """
+    try:
+        # 步骤1: 使用OCR从图像中提取文本
+        ocr_text = ocr_extract_text(image_data)
+        if not ocr_text or ocr_text.strip() == "":
+            error_msg = "OCR无法从图像中提取文本"
+            logging.error(error_msg)
+            raise Exception(error_msg)
+        
+        logging.info(f"OCR提取的文本: {ocr_text[:200]}..." if len(ocr_text) > 200 else ocr_text)
+        
+        # 步骤2: 使用DeepSeek API解析文本中的信息
+        return parse_text_with_deepseek(ocr_text)
+    
+    except Exception as e:
+        error_msg = f"从图像中提取和解析文本失败: {str(e)}"
+        logging.error(error_msg, exc_info=True)
+        raise Exception(error_msg)
+
+
+def ocr_extract_text(image_data):
+    """
+    使用OCR从图像中提取文本
+    
+    Args:
+        image_data (bytes): 图像的二进制数据
+        
+    Returns:
+        str: 提取的文本
+    """
+    try:
+        # 将二进制数据转换为PIL图像
+        image = Image.open(BytesIO(image_data))
+        
+        # 使用pytesseract进行OCR文本提取
+        text = pytesseract.image_to_string(image, lang=OCR_LANG)
+        
+        # 清理提取的文本
+        text = text.strip()
+        logging.info(f"OCR成功从图像中提取文本,长度: {len(text)}")
+        print(text)
+        
+        return text
+    except Exception as e:
+        error_msg = f"OCR提取文本失败: {str(e)}"
+        logging.error(error_msg, exc_info=True)
+        raise Exception(error_msg)
+
+
+def parse_text_with_deepseek(text):
+    """
+    使用DeepSeek API解析文本中的名片信息
+    
+    Args:
+        text (str): 要解析的文本
+        
+    Returns:
+        dict: 解析的名片信息
+    """
+    # 准备请求DeepSeek API
+    if not DEEPSEEK_API_KEY:
+        error_msg = "未配置DeepSeek API密钥"
+        logging.error(error_msg)
+        raise Exception(error_msg)
+    
+    # 构建API请求的基本信息
+    headers = {
+        "Authorization": f"Bearer {DEEPSEEK_API_KEY}",
+        "Content-Type": "application/json"
+    }
+    
+    # 构建提示语,包含OCR提取的文本
+    prompt = f"""请从以下名片文本中提取详细信息,需分别识别中英文内容。
+以JSON格式返回,包含以下字段:
+- name_zh: 中文姓名
+- name_en: 英文姓名
+- title_zh: 中文职位/头衔
+- title_en: 英文职位/头衔
+- hotel_zh: 中文酒店/公司名称
+- hotel_en: 英文酒店/公司名称
+- mobile: 手机号码
+- phone: 固定电话
+- email: 电子邮箱
+- address_zh: 中文地址
+- address_en: 英文地址
+
+名片文本:
+{text}
+"""
+    
+    # 使用模型名称
+    model_name = 'deepseek-chat'
+    
+    try:
+        # 尝试调用DeepSeek API
+        logging.info(f"尝试通过DeepSeek API解析文本")
+        payload = {
+            "model": model_name,
+            "messages": [
+                {"role": "system", "content": "你是一个专业的名片信息提取助手。请用JSON格式返回结果,不要有多余的文字说明。"},
+                {"role": "user", "content": prompt}
+            ],
+            "temperature": 0.1
+        }
+        
+        logging.info(f"向DeepSeek API发送请求")
+        response = requests.post(DEEPSEEK_API_URL, headers=headers, json=payload, timeout=30)
+        
+        # 检查响应状态
+        response.raise_for_status()
+        
+        # 解析API响应
+        result = response.json()
+        content = result.get("choices", [{}])[0].get("message", {}).get("content", "{}")
+        
+        # 尝试解析JSON内容
+        try:
+            # 找到内容中的JSON部分(有时模型会在JSON前后添加额外文本)
+            json_content = extract_json_from_text(content)
+            extracted_data = json.loads(json_content)
+            logging.info(f"成功解析DeepSeek API返回的JSON")
+        except json.JSONDecodeError:
+            logging.warning(f"无法解析JSON,尝试直接从文本提取信息")
+            # 如果无法解析JSON,尝试直接从文本中提取关键信息
+            extracted_data = extract_fields_from_text(content)
+        
+        # 确保所有必要的字段都存在
+        required_fields = ['name', 'title', 'company', 'phone', 'email', 'address']
+        for field in required_fields:
+            if field not in extracted_data:
+                extracted_data[field] = ""
+        
+        logging.info(f"成功从DeepSeek API获取解析结果")
+        return extracted_data
+        
+    except requests.exceptions.HTTPError as e:
+        error_msg = f"DeepSeek API调用失败: {str(e)}"
+        logging.error(error_msg)
+        
+        if hasattr(e, 'response') and e.response:
+            logging.error(f"错误状态码: {e.response.status_code}")
+            logging.error(f"错误内容: {e.response.text}")
+        
+        raise Exception(error_msg)
+    except Exception as e:
+        error_msg = f"解析文本过程中发生错误: {str(e)}"
+        logging.error(error_msg, exc_info=True)
+        raise Exception(error_msg)
+
+
+def extract_json_from_text(text):
+    """
+    从文本中提取JSON部分
+    
+    Args:
+        text (str): 包含JSON的文本
+        
+    Returns:
+        str: 提取的JSON字符串
+    """
+    # 尝试找到最外层的花括号对
+    start_idx = text.find('{')
+    if start_idx == -1:
+        return "{}"
+    
+    # 使用简单的括号匹配算法找到对应的闭合括号
+    count = 0
+    for i in range(start_idx, len(text)):
+        if text[i] == '{':
+            count += 1
+        elif text[i] == '}':
+            count -= 1
+            if count == 0:
+                return text[start_idx:i+1]
+    
+    # 如果没有找到闭合括号,返回从开始位置到文本结尾
+    return text[start_idx:]
+
+
+def extract_fields_from_text(text):
+    """
+    从文本中直接提取名片字段信息
+    
+    Args:
+        text (str): 要分析的文本
+        
+    Returns:
+        dict: 提取的字段
+    """
+    # 初始化结果字典
+    result = {
+        'name_zh': '',
+        'name_en': '',
+        'title_zh': '',
+        'title_en': '',
+        'mobile': '',
+        'phone': '',
+        'email': '',
+        'hotel_zh': '',
+        'hotel_en': '',
+        'address_zh': '',
+        'address_en': '',
+        'postal_code_zh': '',
+        'postal_code_en': '',
+        'brand_zh': '',
+        'brand_en': '',
+        'affiliation_zh': '',
+        'affiliation_en': ''
+    }
+    
+    # 提取中文姓名
+    name_zh_match = re.search(r'["\'](姓名)["\'][\s\{:]*["\']?(中文)["\']?[\s\}:]*["\']([^"\']+)["\']', text, re.IGNORECASE)
+    if name_zh_match:
+        result['name_zh'] = name_zh_match.group(3)
+    
+    # 提取英文姓名
+    name_en_match = re.search(r'["\'](姓名)["\'][\s\{:]*["\']?(英文)["\']?[\s\}:]*["\']([^"\']+)["\']', text, re.IGNORECASE)
+    if name_en_match:
+        result['name_en'] = name_en_match.group(3)
+    
+    # 提取中文头衔
+    title_zh_match = re.search(r'["\'](头衔|职位)["\'][\s\{:]*["\']?(中文)["\']?[\s\}:]*["\']([^"\']+)["\']', text, re.IGNORECASE)
+    if title_zh_match:
+        result['title_zh'] = title_zh_match.group(3)
+    
+    # 提取英文头衔
+    title_en_match = re.search(r'["\'](头衔|职位)["\'][\s\{:]*["\']?(英文)["\']?[\s\}:]*["\']([^"\']+)["\']', text, re.IGNORECASE)
+    if title_en_match:
+        result['title_en'] = title_en_match.group(3)
+    
+    # 提取手机
+    mobile_match = re.search(r'["\'](手机)["\'][\s:]*["\']([^"\']+)["\']', text, re.IGNORECASE)
+    if mobile_match:
+        result['mobile'] = mobile_match.group(2)
+    
+    # 提取电话
+    phone_match = re.search(r'["\'](电话)["\'][\s:]*["\']([^"\']+)["\']', text, re.IGNORECASE)
+    if phone_match:
+        result['phone'] = phone_match.group(2)
+    
+    # 提取邮箱
+    email_match = re.search(r'["\'](邮箱)["\'][\s:]*["\']([^"\']+)["\']', text, re.IGNORECASE)
+    if email_match:
+        result['email'] = email_match.group(2)
+    
+    # 提取中文酒店名称
+    hotel_zh_match = re.search(r'["\'](地址)["\'][\s\{:]*["\']?(中文)["\']?[\s\{:]*["\']?(酒店名称)["\']?[\s\}:]*["\']([^"\']+)["\']', text, re.IGNORECASE)
+    if hotel_zh_match:
+        result['hotel_zh'] = hotel_zh_match.group(4)
+    
+    # 提取英文酒店名称
+    hotel_en_match = re.search(r'["\'](地址)["\'][\s\{:]*["\']?(英文)["\']?[\s\{:]*["\']?(酒店名称)["\']?[\s\}:]*["\']([^"\']+)["\']', text, re.IGNORECASE)
+    if hotel_en_match:
+        result['hotel_en'] = hotel_en_match.group(4)
+    
+    # 提取中文详细地址
+    address_zh_match = re.search(r'["\'](地址)["\'][\s\{:]*["\']?(中文)["\']?[\s\{:]*["\']?(详细地址)["\']?[\s\}:]*["\']([^"\']+)["\']', text, re.IGNORECASE)
+    if address_zh_match:
+        result['address_zh'] = address_zh_match.group(4)
+    
+    # 提取英文详细地址
+    address_en_match = re.search(r'["\'](地址)["\'][\s\{:]*["\']?(英文)["\']?[\s\{:]*["\']?(详细地址)["\']?[\s\}:]*["\']([^"\']+)["\']', text, re.IGNORECASE)
+    if address_en_match:
+        result['address_en'] = address_en_match.group(4)
+    
+    # 提取中文邮政编码
+    postal_code_zh_match = re.search(r'["\'](地址)["\'][\s\{:]*["\']?(中文)["\']?[\s\{:]*["\']?(邮政编码)["\']?[\s\}:]*["\']([^"\']+)["\']', text, re.IGNORECASE)
+    if postal_code_zh_match:
+        result['postal_code_zh'] = postal_code_zh_match.group(4)
+    
+    # 提取英文邮政编码
+    postal_code_en_match = re.search(r'["\'](地址)["\'][\s\{:]*["\']?(英文)["\']?[\s\{:]*["\']?(邮政编码)["\']?[\s\}:]*["\']([^"\']+)["\']', text, re.IGNORECASE)
+    if postal_code_en_match:
+        result['postal_code_en'] = postal_code_en_match.group(4)
+    
+    # 提取中文品牌名称
+    brand_zh_match = re.search(r'["\'](公司)["\'][\s\{:]*["\']?(中文)["\']?[\s\{:]*["\']?(品牌名称)["\']?[\s\}:]*["\']([^"\']+)["\']', text, re.IGNORECASE)
+    if brand_zh_match:
+        result['brand_zh'] = brand_zh_match.group(4)
+    
+    # 提取英文品牌名称
+    brand_en_match = re.search(r'["\'](公司)["\'][\s\{:]*["\']?(英文)["\']?[\s\{:]*["\']?(品牌名称)["\']?[\s\}:]*["\']([^"\']+)["\']', text, re.IGNORECASE)
+    if brand_en_match:
+        result['brand_en'] = brand_en_match.group(4)
+    
+    # 提取中文隶属关系
+    affiliation_zh_match = re.search(r'["\'](公司)["\'][\s\{:]*["\']?(中文)["\']?[\s\{:]*["\']?(隶属关系)["\']?[\s\}:]*["\']([^"\']+)["\']', text, re.IGNORECASE)
+    if affiliation_zh_match:
+        result['affiliation_zh'] = affiliation_zh_match.group(4)
+    
+    # 提取英文隶属关系
+    affiliation_en_match = re.search(r'["\'](公司)["\'][\s\{:]*["\']?(英文)["\']?[\s\{:]*["\']?(隶属关系)["\']?[\s\}:]*["\']([^"\']+)["\']', text, re.IGNORECASE)
+    if affiliation_en_match:
+        result['affiliation_en'] = affiliation_en_match.group(4)
+    
+    return result
+
+def parse_text_with_qwen25VLplus(image_data):
+    """
+    使用阿里云的 Qwen 2.5 VL Plus 模型解析图像中的名片信息
+    
+    Args:
+        image_data (bytes): 图像的二进制数据
+        
+    Returns:
+        dict: 解析的名片信息
+    """
+    # 阿里云 Qwen API 配置
+    QWEN_API_KEY = os.environ.get('QWEN_API_KEY', 'sk-8f2320dafc9e4076968accdd8eebd8e9')
+    
+    try:
+        # 将图片数据转为 base64 编码
+        base64_image = base64.b64encode(image_data).decode('utf-8')
+        
+        # 初始化 OpenAI 客户端,配置为阿里云 API
+        client = OpenAI(
+            api_key=QWEN_API_KEY,
+            base_url="https://dashscope.aliyuncs.com/compatible-mode/v1",
+        )
+        
+        # 构建提示语
+        prompt = """请分析这张名片,提取以下信息,需分别识别中英文内容:
+1. 中文姓名 (name_zh)
+2. 英文姓名 (name_en)
+3. 中文职位/头衔 (title_zh)
+4. 英文职位/头衔 (title_en)
+5. 中文酒店/公司名称 (hotel_zh)
+6. 英文酒店/公司名称 (hotel_en)
+7. 手机号码 (mobile)
+8. 固定电话 (phone)
+9. 电子邮箱 (email)
+10. 中文地址 (address_zh)
+11. 英文地址 (address_en)
+12. 中文邮政编码 (postal_code_zh)
+13. 英文邮政编码 (postal_code_en)
+
+请以JSON格式返回结果,不要有多余的文字说明。每个字段如果名片中没有相应信息,返回空字符串。"""
+        
+        # 调用 Qwen 2.5 VL Plus API
+        logging.info("发送请求到 Qwen 2.5 VL Plus 模型")
+        completion = client.chat.completions.create(
+            model="qwen-vl-plus",
+            messages=[
+                {
+                    "role": "user",
+                    "content": [
+                        {"type": "text", "text": prompt},
+                        {"type": "image_url", "image_url": {"url": f"data:image/jpeg;base64,{base64_image}"}}
+                    ]
+                }
+            ]
+        )
+        
+        # 解析响应
+        response_content = completion.choices[0].message.content
+        logging.info(f"成功从 Qwen 模型获取响应: {response_content[:100]}...")
+        
+        # 尝试从响应中提取 JSON
+        try:
+            json_content = extract_json_from_text(response_content)
+            extracted_data = json.loads(json_content)
+            logging.info("成功解析 Qwen 响应中的 JSON")
+        except json.JSONDecodeError:
+            logging.warning("无法解析 JSON,尝试从文本中提取信息")
+            extracted_data = extract_fields_from_text(response_content)
+        
+        # 确保所有必要字段存在
+        required_fields = [
+            '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'
+        ]
+        
+        for field in required_fields:
+            if field not in extracted_data:
+                extracted_data[field] = ""
+        
+        return extracted_data
+        
+    except Exception as e:
+        error_msg = f"Qwen 2.5 VL Plus 模型解析失败: {str(e)}"
+        logging.error(error_msg, exc_info=True)
+        raise Exception(error_msg)
+
+def process_business_card(image_file):
+    """
+    处理名片图片并提取信息
+    
+    Args:
+        image_file (FileStorage): 上传的名片图片文件
+        
+    Returns:
+        dict: 处理结果,包含提取的信息和状态
+    """
+    minio_path = None
+    
+    try:
+        # 读取图片数据
+        image_data = image_file.read()
+        image_file.seek(0)  # 重置文件指针以便后续读取
+        
+        try:
+            # 优先使用 Qwen 2.5 VL Plus 模型直接从图像提取信息
+            try:
+                logging.info("尝试使用 Qwen 2.5 VL Plus 模型解析名片")
+                extracted_data = parse_text_with_qwen25VLplus(image_data)
+                logging.info("成功使用 Qwen 2.5 VL Plus 模型解析名片")
+            except Exception as qwen_error:
+                logging.warning(f"Qwen 模型解析失败,错误原因: {str(qwen_error)}")
+                # extracted_data = extract_text_from_image(image_data)
+        except Exception as e:
+            return {
+                'success': False,
+                'message': f"名片解析失败: {str(e)}",
+                'data': None
+            }
+        
+        try:
+            # 生成唯一的文件名
+            file_ext = os.path.splitext(image_file.filename)[1].lower()
+            if not file_ext:
+                file_ext = '.jpg'  # 默认扩展名
+            
+            unique_filename = f"{uuid.uuid4().hex}{file_ext}"
+            minio_path = f"{unique_filename}"
+            
+            # 尝试上传到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}")
+            else:
+                minio_path = None
+                logging.warning("MinIO客户端未初始化,图片未上传")
+        except Exception as e:
+            logging.error(f"上传图片到MinIO失败: {str(e)}", exc_info=True)
+            minio_path = None
+        
+        try:
+            # 保存到数据库
+            business_card = BusinessCard(
+                name_zh=extracted_data.get('name_zh', ''),
+                name_en=extracted_data.get('name_en', ''),
+                title_zh=extracted_data.get('title_zh', ''),
+                title_en=extracted_data.get('title_en', ''),
+                mobile=extracted_data.get('mobile', ''),
+                phone=extracted_data.get('phone', ''),
+                email=extracted_data.get('email', ''),
+                hotel_zh=extracted_data.get('hotel_zh', ''),
+                hotel_en=extracted_data.get('hotel_en', ''),
+                address_zh=extracted_data.get('address_zh', ''),
+                address_en=extracted_data.get('address_en', ''),
+                postal_code_zh=extracted_data.get('postal_code_zh', ''),
+                postal_code_en=extracted_data.get('postal_code_en', ''),
+                brand_zh=extracted_data.get('brand_zh', ''),
+                brand_en=extracted_data.get('brand_en', ''),
+                affiliation_zh=extracted_data.get('affiliation_zh', ''),
+                affiliation_en=extracted_data.get('affiliation_en', ''),
+                image_path=minio_path,  # 存储相对路径
+                status='active',
+                updated_by='system'
+            )
+            
+            db.session.add(business_card)
+            db.session.commit()
+            
+            logging.info(f"名片信息已保存到数据库,ID: {business_card.id}")
+            
+            return {
+                'success': True,
+                'message': '名片解析成功',
+                'data': business_card.to_dict()
+            }
+        except Exception as e:
+            db.session.rollback()
+            error_msg = f"保存名片信息到数据库失败: {str(e)}"
+            logging.error(error_msg, exc_info=True)
+            
+            # 即使数据库操作失败,仍返回提取的信息
+            return {
+                'success': False,
+                'message': error_msg,
+                'data': {
+                    'id': None,
+                    'name_zh': extracted_data.get('name_zh', ''),
+                    'name_en': extracted_data.get('name_en', ''),
+                    'title_zh': extracted_data.get('title_zh', ''),
+                    'title_en': extracted_data.get('title_en', ''),
+                    'mobile': extracted_data.get('mobile', ''),
+                    'phone': extracted_data.get('phone', ''),
+                    'email': extracted_data.get('email', ''),
+                    'hotel_zh': extracted_data.get('hotel_zh', ''),
+                    'hotel_en': extracted_data.get('hotel_en', ''),
+                    'address_zh': extracted_data.get('address_zh', ''),
+                    'address_en': extracted_data.get('address_en', ''),
+                    'postal_code_zh': extracted_data.get('postal_code_zh', ''),
+                    'postal_code_en': extracted_data.get('postal_code_en', ''),
+                    'brand_zh': extracted_data.get('brand_zh', ''),
+                    'brand_en': extracted_data.get('brand_en', ''),
+                    'affiliation_zh': extracted_data.get('affiliation_zh', ''),
+                    'affiliation_en': extracted_data.get('affiliation_en', ''),
+                    'image_path': minio_path,  # 返回相对路径
+                    'created_at': datetime.now().strftime('%Y-%m-%d %H:%M:%S'),
+                    'updated_at': None,
+                    'updated_by': 'system',
+                    'status': 'active'
+                }
+            }
+            
+    except Exception as e:
+        db.session.rollback()
+        error_msg = f"名片处理失败: {str(e)}"
+        logging.error(error_msg, exc_info=True)
+        
+        return {
+            'success': False,
+            'message': error_msg,
+            'data': None
+        }
+
+def update_business_card(card_id, data):
+    """
+    更新名片信息
+    
+    Args:
+        card_id (int): 名片记录ID
+        data (dict): 包含要更新的字段的字典
+        
+    Returns:
+        dict: 包含操作结果和更新后的名片信息
+    """
+    try:
+        # 查找要更新的名片记录
+        card = BusinessCard.query.get(card_id)
+        
+        if not card:
+            return {
+                'success': False,
+                'message': f'未找到ID为{card_id}的名片记录',
+                'data': None
+            }
+        
+        # 更新名片信息
+        card.name_zh = data.get('name_zh', card.name_zh)
+        card.name_en = data.get('name_en', card.name_en)
+        card.title_zh = data.get('title_zh', card.title_zh)
+        card.title_en = data.get('title_en', card.title_en)
+        card.mobile = data.get('mobile', card.mobile)
+        card.phone = data.get('phone', card.phone)
+        card.email = data.get('email', card.email)
+        card.hotel_zh = data.get('hotel_zh', card.hotel_zh)
+        card.hotel_en = data.get('hotel_en', card.hotel_en)
+        card.address_zh = data.get('address_zh', card.address_zh)
+        card.address_en = data.get('address_en', card.address_en)
+        card.postal_code_zh = data.get('postal_code_zh', card.postal_code_zh)
+        card.postal_code_en = data.get('postal_code_en', card.postal_code_en)
+        card.brand_zh = data.get('brand_zh', card.brand_zh)
+        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.updated_by = data.get('updated_by', 'user')  # 可以根据实际情况修改为当前用户
+        
+        # 保存更新
+        db.session.commit()
+        
+        return {
+            'success': True,
+            'message': '名片信息已更新',
+            'data': card.to_dict()
+        }
+    
+    except Exception as e:
+        db.session.rollback()
+        error_msg = f"更新名片信息失败: {str(e)}"
+        logging.error(error_msg, exc_info=True)
+        
+        return {
+            'success': False,
+            'message': error_msg,
+            'data': None
+        }
+
+def get_business_cards():
+    """
+    获取所有名片记录列表
+    
+    Returns:
+        dict: 包含操作结果和名片列表
+    """
+    try:
+        # 查询所有名片记录
+        cards = BusinessCard.query.all()
+        
+        # 将所有记录转换为字典格式
+        cards_data = [card.to_dict() for card in cards]
+        
+        return {
+            'success': True,
+            'message': '获取名片列表成功',
+            'data': cards_data
+        }
+    
+    except Exception as e:
+        error_msg = f"获取名片列表失败: {str(e)}"
+        logging.error(error_msg, exc_info=True)
+        
+        return {
+            'success': False,
+            'message': error_msg,
+            'data': []
+        }
+
+def update_business_card_status(card_id, status):
+    """
+    更新名片状态(激活/禁用)
+    
+    Args:
+        card_id (int): 名片记录ID
+        status (str): 新状态,'active'或'inactive'
+        
+    Returns:
+        dict: 包含操作结果和更新后的名片信息
+    """
+    try:
+        # 查找要更新的名片记录
+        card = BusinessCard.query.get(card_id)
+        
+        if not card:
+            return {
+                'success': False,
+                'message': f'未找到ID为{card_id}的名片记录',
+                'data': None
+            }
+        
+        # 验证状态值
+        if status not in ['active', 'inactive']:
+            return {
+                'success': False,
+                'message': f'无效的状态值: {status},必须为 active 或 inactive',
+                'data': None
+            }
+        
+        # 更新状态
+        card.status = status
+        card.updated_at = datetime.now()
+        card.updated_by = 'system'  # 可以根据实际情况修改为当前用户
+        
+        # 保存更新
+        db.session.commit()
+        
+        return {
+            'success': True,
+            'message': f'名片状态已更新为: {status}',
+            'data': card.to_dict()
+        }
+    
+    except Exception as e:
+        db.session.rollback()
+        error_msg = f"更新名片状态失败: {str(e)}"
+        logging.error(error_msg, exc_info=True)
+        
+        return {
+            'success': False,
+            'message': error_msg,
+            'data': None
+        }