chenkun 4 weeks ago
parent
commit
d15ba8a567

+ 10 - 6
src/app/sample/models/base_info.py

@@ -18,12 +18,16 @@ class StandardBaseInfo(BaseModel):
     implementation_date = Column(Date, nullable=True, comment="实施日期")
     drafting_unit = Column(String(255), nullable=True, comment="主编单位")
     approving_department = Column(String(255), nullable=True, comment="批准部门")
-    participating_units = Column(String(255), nullable=True, comment="参编单位,可多条,建议用JSON数组或拆分多个字段;此处为第一个参编单位")
-    document_type = Column(String(50), nullable=True, comment="文件类型,枚举:法律法规、国家标准、行业标准、企业标准、规范性文件、技术规范")
-    professional_field = Column(String(50), nullable=True, comment="专业领域,枚举:法律、参考规范、行政法规、部门规章")
-    engineering_phase = Column(String(50), nullable=True, comment="工程阶段,待补充具体枚举值,如:规划、设计、施工、运维等")
-    validity = Column(String(20), nullable=True, comment="时效性,枚举:现行、已废止、被替代")
-    reference_basis = Column(String(500), nullable=True, comment="参考依据")
+    participating_units_1 = Column(String(255), nullable=True, comment="参编单位_1")
+    participating_units_2 = Column(String(255), nullable=True, comment="参编单位_2")
+    participating_units_3 = Column(String(255), nullable=True, comment="参编单位_3")
+    participating_units_4 = Column(String(255), nullable=True, comment="参编单位_4")
+    document_type = Column(String(50), nullable=True, comment="文件类型")
+    professional_field = Column(String(50), nullable=True, comment="专业领域")
+    engineering_phase = Column(String(50), nullable=True, comment="工程阶段")
+    validity = Column(String(20), nullable=True, comment="时效性")
+    reference_basis_1 = Column(String(500), nullable=True, comment="参考依据_1")
+    reference_basis_2 = Column(String(500), nullable=True, comment="参考依据_2")
     source_url = Column(String(500), nullable=True, comment="文件来源网址,如:http://www.xxx.com/xxx/xxx/xxx.pdf")
     note = Column(String(500), nullable=True, comment="文件备注")
 

+ 6 - 6
src/app/services/image_service.py

@@ -87,8 +87,8 @@ class ImageService:
             cursor.close()
             conn.close()
 
-    async def update_category(self, category_id: str, data: Dict[str, Any], user_id: str) -> Tuple[bool, str]:
-        """更新图片分类"""
+    async def edit_category(self, category_id: str, data: Dict[str, Any], user_id: str) -> Tuple[bool, str]:
+        """编辑图片分类"""
         conn = get_db_connection()
         if not conn:
             return False, "数据库连接失败"
@@ -108,11 +108,11 @@ class ImageService:
             """
             cursor.execute(sql, (type_name, remark, user_id, category_id))
             conn.commit()
-            return True, "更新成功"
+            return True, "编辑成功"
         except Exception as e:
-            logger.exception(f"更新图片分类失败: {e}")
+            logger.exception(f"编辑图片分类失败: {e}")
             conn.rollback()
-            return False, f"更新失败: {str(e)}"
+            return False, f"编辑失败: {str(e)}"
         finally:
             cursor.close()
             conn.close()
@@ -146,7 +146,7 @@ class ImageService:
             cursor.close()
             conn.close()
 
-    async def get_images(self, category_id: Optional[str] = None, page: int = 1, page_size: int = 10) -> Dict[str, Any]:
+    async def get_image_list(self, category_id: Optional[str] = None, page: int = 1, page_size: int = 10) -> Dict[str, Any]:
         """获取图片列表"""
         conn = get_db_connection()
         if not conn:

+ 71 - 78
src/app/services/sample_service.py

@@ -44,6 +44,52 @@ class SampleService:
         except Exception as e:
             logger.error(f"初始化 Milvus 集合失败: {e}")
 
+    def _format_document_row(self, item: Dict[str, Any]) -> Dict[str, Any]:
+        """格式化文档行数据,处理URL、日期映射和文件名显示"""
+        if not item:
+            return item
+            
+        # 1. 处理 URL 转换
+        for key in ['file_url', 'md_url', 'json_url']:
+            if item.get(key):
+                item[key] = self.minio_manager.get_full_url(item[key])
+        
+        # 2. 映射字段以适配前端通用显示
+        source_type = item.get('source_type')
+        if source_type == 'basis':
+            # 标准规范特有映射
+            if 'standard_number' in item:
+                item['standard_no'] = item.get('standard_number')
+        elif source_type == 'work':
+            # 施工方案特有映射
+            item['issuing_authority'] = item.get('compiling_unit')
+            item['release_date'] = item.get('compiling_date')
+        elif source_type == 'job':
+            # 办公制度特有映射
+            item['issuing_authority'] = item.get('issuing_department')
+            item['release_date'] = item.get('publish_date')
+        
+        # 3. 格式化时间
+        date_keys = [
+            'created_time', 'updated_time', 'release_date', 
+            'publish_date', 'compiling_date', 'implementation_date',
+            'effective_start_date', 'effective_end_date'
+        ]
+        for key in date_keys:
+            val = item.get(key)
+            if val and hasattr(val, 'isoformat'):
+                item[key] = val.isoformat()
+            elif val is not None:
+                item[key] = str(val)
+        
+        # 4. 增加格式化文件名供前端显示
+        if item.get('conversion_status') == 2:
+            title = item.get('title', 'document')
+            item['md_display_name'] = f"{title}.md"
+            item['json_display_name'] = f"{title}.json"
+            
+        return item
+
     async def get_upload_url(self, filename: str, content_type: str) -> Tuple[bool, str, Dict[str, Any]]:
         """获取 MinIO 预签名上传 URL"""
         try:
@@ -296,37 +342,7 @@ class SampleService:
             
             logger.info(f"Executing SQL: {sql} with params: {params}")
             cursor.execute(sql, tuple(params))
-            items = []
-            for row in cursor.fetchall():
-                item = row
-                # 处理 URL 转换
-                for key in ['file_url', 'md_url', 'json_url']:
-                    if item.get(key):
-                        item[key] = self.minio_manager.get_full_url(item[key])
-                
-                # 映射字段以适配前端通用显示
-                source_type = item.get('source_type')
-                if source_type == 'work':
-                    item['issuing_authority'] = item.get('compiling_unit')
-                    item['release_date'] = item.get('compiling_date')
-                elif source_type == 'job':
-                    item['issuing_authority'] = item.get('issuing_department')
-                    item['release_date'] = item.get('publish_date')
-                
-                # 格式化时间
-                for key in ['created_time', 'updated_time', 'release_date', 'publish_date', 'compiling_date']:
-                    if item.get(key) and hasattr(item[key], 'isoformat'):
-                        item[key] = item[key].isoformat()
-                    elif item.get(key) is not None:
-                        item[key] = str(item[key])
-                
-                # 增加格式化文件名供前端显示
-                if item.get('conversion_status') == 2:
-                    title = item.get('title', 'document')
-                    item['md_display_name'] = f"{title}.md"
-                    item['json_display_name'] = f"{title}.json"
-                
-                items.append(item)
+            items = [self._format_document_row(row) for row in cursor.fetchall()]
             
             # 总数
             count_sql = f"SELECT COUNT(*) as count FROM {from_sql} {where_sql}"
@@ -386,39 +402,9 @@ class SampleService:
                     # 将子表字段合并到 doc 中,方便前端使用
                     # 注意:如果字段名冲突,子表字段会覆盖主表字段(除了 id)
                     sub_data.pop('id', None)
-                    
-                    # 特殊处理一些前端需要的映射字段
-                    if source_type == 'basis':
-                        doc['standard_no'] = sub_data.get('standard_number')
-                    elif source_type == 'work':
-                        doc['issuing_authority'] = sub_data.get('compiling_unit')
-                        doc['release_date'] = sub_data.get('compiling_date')
-                    elif source_type == 'job':
-                        doc['issuing_authority'] = sub_data.get('issuing_department')
-                        doc['release_date'] = sub_data.get('publish_date')
-
                     doc.update(sub_data)
             
-            # 格式化时间
-            for key in ['created_time', 'updated_time', 'release_date', 'publish_date', 'compiling_date', 'implementation_date']:
-                val = doc.get(key)
-                if val and hasattr(val, 'isoformat'):
-                    doc[key] = val.isoformat()
-                elif val is not None:
-                    doc[key] = str(val)
-            
-            # 处理 URL 转换
-            for key in ['file_url', 'md_url', 'json_url']:
-                if doc.get(key):
-                    doc[key] = self.minio_manager.get_full_url(doc[key])
-
-            # 增加格式化文件名供前端显示
-            if doc.get('conversion_status') == 2:
-                title = doc.get('title', 'document')
-                doc['md_display_name'] = f"{title}.md"
-                doc['json_display_name'] = f"{title}.json"
-            
-            return doc
+            return self._format_document_row(doc)
         except Exception as e:
             logger.exception("获取文档详情失败")
             return None
@@ -674,6 +660,7 @@ class SampleService:
                     s.id, s.chinese_name as title, s.standard_number as standard_no, 
                     s.issuing_authority, s.release_date, s.document_type, 
                     s.professional_field, s.validity, s.note, 
+                    s.participating_units, s.reference_basis,
                     s.created_by, u1.username as creator_name, s.created_time,
                     s.updated_by, u2.username as updater_name, s.updated_time,
                     m.file_url, m.conversion_status, m.md_url, m.json_url
@@ -925,7 +912,8 @@ class SampleService:
                     INSERT INTO {table_name} (
                         id, chinese_name, english_name, standard_number, issuing_authority, 
                         release_date, implementation_date, drafting_unit, approving_department,
-                        participating_units, document_type, professional_field, engineering_phase,
+                        participating_units,
+                        document_type, professional_field, engineering_phase,
                         validity, reference_basis, source_url, note,
                         created_by, updated_by, created_time, updated_time
                     ) VALUES (%s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, NOW(), NOW())
@@ -933,7 +921,8 @@ class SampleService:
                 params = (
                     doc_id, data.get('title'), data.get('english_name'), data.get('standard_no'), 
                     data.get('issuing_authority'), self._to_date(data.get('release_date')), self._to_date(data.get('implementation_date')),
-                    data.get('drafting_unit'), data.get('approving_department'), data.get('participating_units'),
+                    data.get('drafting_unit'), data.get('approving_department'),
+                    data.get('participating_units'),
                     data.get('document_type'), data.get('professional_field'), data.get('engineering_phase'),
                     data.get('validity', '现行'), data.get('reference_basis'), data.get('source_url'), data.get('note'),
                     user_id, user_id
@@ -988,9 +977,9 @@ class SampleService:
             cursor.close()
             conn.close()
 
-    async def edit_basic_info(self, type: str, info_id: str, data: Dict[str, Any], updater_id: str) -> Tuple[bool, str]:
+    async def edit_basic_info(self, type: str, doc_id: str, data: Dict[str, Any], updater_id: str) -> Tuple[bool, str]:
         """编辑基本信息"""
-        logger.info(f"Editing basic info for type {type}, id {info_id}: {data}")
+        logger.info(f"Editing basic info for type {type}, id {doc_id}: {data}")
         conn = get_db_connection()
         if not conn:
             return False, "数据库连接失败"
@@ -1012,7 +1001,7 @@ class SampleService:
                 SET title = %s, file_url = %s, file_extension = %s, updated_by = %s, updated_time = NOW()
                 WHERE id = %s
                 """,
-                (data.get('title'), file_url, file_extension, updater_id, info_id)
+                (data.get('title'), file_url, file_extension, updater_id, doc_id)
             )
 
             # 2. 更新子表 (移除 file_url)
@@ -1022,8 +1011,10 @@ class SampleService:
                 SET chinese_name = %s, standard_number = %s, issuing_authority = %s, release_date = %s, 
                     document_type = %s, professional_field = %s, validity = %s, 
                     english_name = %s, implementation_date = %s, drafting_unit = %s, 
-                    approving_department = %s, participating_units = %s, engineering_phase = %s, 
-                    reference_basis = %s, source_url = %s, note = %s,
+                    approving_department = %s, engineering_phase = %s, 
+                    participating_units = %s,
+                    reference_basis = %s, 
+                    source_url = %s, note = %s,
                     updated_by = %s, updated_time = NOW() 
                 WHERE id = %s
                 """
@@ -1031,9 +1022,11 @@ class SampleService:
                     data.get('title'), data.get('standard_no'), data.get('issuing_authority'), self._to_date(data.get('release_date')), 
                     data.get('document_type'), data.get('professional_field'), data.get('validity'),
                     data.get('english_name'), self._to_date(data.get('implementation_date')), data.get('drafting_unit'),
-                    data.get('approving_department'), data.get('participating_units'), data.get('engineering_phase'),
-                    data.get('reference_basis'), data.get('source_url'), data.get('note'),
-                    updater_id, info_id
+                    data.get('approving_department'), data.get('engineering_phase'),
+                    data.get('participating_units'),
+                    data.get('reference_basis'),
+                    data.get('source_url'), data.get('note'),
+                    updater_id, doc_id
                 )
             elif type == 'work':
                 sql = f"""
@@ -1050,7 +1043,7 @@ class SampleService:
                     data.get('issuing_authority'), self._to_date(data.get('release_date')),
                     data.get('plan_summary'), data.get('compilation_basis'), data.get('plan_category'),
                     data.get('level_1_classification'), data.get('level_2_classification'), data.get('level_3_classification'), data.get('level_4_classification'),
-                    data.get('note'), updater_id, info_id
+                    data.get('note'), updater_id, doc_id
                 )
                 
             elif type == 'job':
@@ -1064,7 +1057,7 @@ class SampleService:
                 params = (
                     data.get('title'), data.get('issuing_authority'), data.get('document_type'), self._to_date(data.get('release_date')), 
                     self._to_date(data.get('effective_start_date')), self._to_date(data.get('effective_end_date')), data.get('note'),
-                    updater_id, info_id
+                    updater_id, doc_id
                 )
             else:
                 return False, "不支持的类型"
@@ -1081,7 +1074,7 @@ class SampleService:
             cursor.close()
             conn.close()
 
-    async def delete_basic_info(self, type: str, info_id: str) -> Tuple[bool, str]:
+    async def delete_basic_info(self, type: str, doc_id: str) -> Tuple[bool, str]:
         """删除基本信息"""
         conn = get_db_connection()
         if not conn:
@@ -1094,13 +1087,13 @@ class SampleService:
                 return False, "无效的类型"
             
             # 1. 删除主表记录 (由于设置了 ON DELETE CASCADE,子表记录会自动删除)
-            cursor.execute("DELETE FROM t_samp_document_main WHERE id = %s", (info_id,))
+            cursor.execute("DELETE FROM t_samp_document_main WHERE id = %s", (doc_id,))
             
             # 同步删除任务管理中心的数据
             try:
-                await task_service.delete_task(info_id)
+                await task_service.delete_task(doc_id)
             except Exception as task_err:
-                logger.error(f"同步删除任务中心数据失败 (ID: {info_id}): {task_err}")
+                logger.error(f"同步删除任务中心数据失败 (ID: {doc_id}): {task_err}")
 
             conn.commit()
             return True, "删除成功"

+ 60 - 11
src/app/services/task_service.py

@@ -1,14 +1,18 @@
 
 import logging
-from typing import List, Dict, Any, Tuple
+from typing import List, Dict, Any, Tuple, Optional
 from app.base.async_mysql_connection import get_db_connection
+from app.base.minio_connection import get_minio_manager
 
 logger = logging.getLogger(__name__)
 
 class TaskService:
     """任务管理服务类"""
     
-    async def get_tasks(self, task_type: str) -> List[Dict[str, Any]]:
+    def __init__(self):
+        self.minio_manager = get_minio_manager()
+
+    async def get_task_list(self, task_type: str) -> List[Dict[str, Any]]:
         """获取任务列表
         
         Args:
@@ -35,14 +39,15 @@ class TaskService:
                     ORDER BY d.created_time DESC
                 """
             elif task_type == 'image':
-                # 类型为图片的,从 t_image_info 拿名称
+                # 类型为图片的,从 t_image_info 拿名称和 URL
                 sql = """
                     SELECT 
                         t.id, 
                         t.business_id,
                         t.task_id, 
                         t.type,
-                        i.image_name as name
+                        i.image_name as name,
+                        i.image_url
                     FROM t_task_management t
                     JOIN t_image_info i ON t.business_id COLLATE utf8mb4_unicode_ci = i.id COLLATE utf8mb4_unicode_ci
                     WHERE t.type = 'image'
@@ -52,7 +57,15 @@ class TaskService:
                 return []
                 
             cursor.execute(sql)
-            return cursor.fetchall()
+            tasks = cursor.fetchall()
+            
+            # 如果是图片类型,处理 URL 转换以支持前端预览
+            if task_type == 'image':
+                for item in tasks:
+                    if item.get('image_url'):
+                        item['image_url'] = self.minio_manager.get_full_url(item['image_url'])
+            
+            return tasks
         except Exception as e:
             logger.exception(f"获取任务列表失败 ({task_type}): {e}")
             return []
@@ -60,27 +73,63 @@ class TaskService:
             cursor.close()
             conn.close()
 
-    async def add_task(self, business_id: str, task_type: str, task_id: str = None) -> Tuple[bool, str]:
-        """添加任务记录"""
+    async def add_task(self, business_id: str, task_type: str, task_id: str = None) -> Tuple[bool, str, Optional[int]]:
+        """添加或更新任务记录
+        
+        Returns:
+            Tuple[bool, str, Optional[int]]: (是否成功, 消息, 记录的自增id)
+        """
         conn = get_db_connection()
         if not conn:
-            return False, "数据库连接失败"
+            return False, "数据库连接失败", None
         
         cursor = conn.cursor()
         try:
-            # 使用 business_id 作为外键关联,id 为自增主键
+            # 1. 插入或更新记录
+            # 使用 business_id 作为唯一标识(业务主键),id 为数据库自增主键
             sql = """
                 INSERT INTO t_task_management (business_id, task_id, type)
                 VALUES (%s, %s, %s)
                 ON DUPLICATE KEY UPDATE task_id = VALUES(task_id)
             """
             cursor.execute(sql, (business_id, task_id, task_type))
+            
+            # 2. 获取当前记录的 id (如果是更新,lastrowid 也是有效的)
+            record_id = cursor.lastrowid
+            
+            # 如果是更新且 lastrowid 没拿到,通过 business_id 查一下
+            if not record_id:
+                cursor.execute("SELECT id FROM t_task_management WHERE business_id = %s", (business_id,))
+                res = cursor.fetchone()
+                if res:
+                    record_id = res['id']
+
             conn.commit()
-            return True, "添加成功"
+            return True, "添加成功", record_id
         except Exception as e:
             logger.exception(f"添加任务记录失败: {e}")
             conn.rollback()
-            return False, f"添加失败: {str(e)}"
+            return False, f"添加失败: {str(e)}", None
+        finally:
+            cursor.close()
+            conn.close()
+
+    async def delete_task_by_id(self, id: int) -> Tuple[bool, str]:
+        """根据主键 id 删除任务记录"""
+        conn = get_db_connection()
+        if not conn:
+            return False, "数据库连接失败"
+        
+        cursor = conn.cursor()
+        try:
+            sql = "DELETE FROM t_task_management WHERE id = %s"
+            cursor.execute(sql, (id,))
+            conn.commit()
+            return True, "删除成功"
+        except Exception as e:
+            logger.exception(f"根据id删除任务失败: {e}")
+            conn.rollback()
+            return False, f"删除失败: {str(e)}"
         finally:
             cursor.close()
             conn.close()

+ 6 - 6
src/views/image_view.py

@@ -75,8 +75,8 @@ async def add_category(data: CategoryAdd, credentials: HTTPAuthorizationCredenti
         return ApiResponse(code=500, message=str(e), timestamp=datetime.now(timezone.utc).isoformat()).model_dump()
 
 @router.put("/categories/{category_id}")
-async def update_category(category_id: str, data: CategoryUpdate, credentials: HTTPAuthorizationCredentials = Depends(security)):
-    """更新分类"""
+async def edit_category(category_id: str, data: CategoryUpdate, credentials: HTTPAuthorizationCredentials = Depends(security)):
+    """编辑分类"""
     try:
         payload = verify_token(credentials.credentials)
         if not payload:
@@ -84,14 +84,14 @@ async def update_category(category_id: str, data: CategoryUpdate, credentials: H
         
         user_id = payload.get("sub", "system")
         service = ImageService()
-        success, message = await service.update_category(category_id, data.model_dump(), user_id)
+        success, message = await service.edit_category(category_id, data.model_dump(), user_id)
         
         if success:
             return ApiResponse(code=0, message=message, timestamp=datetime.now(timezone.utc).isoformat()).model_dump()
         else:
             return ApiResponse(code=500, message=message, timestamp=datetime.now(timezone.utc).isoformat()).model_dump()
     except Exception as e:
-        logger.exception("更新分类失败")
+        logger.exception("编辑分类失败")
         return ApiResponse(code=500, message=str(e), timestamp=datetime.now(timezone.utc).isoformat()).model_dump()
 
 @router.delete("/categories/{category_id}")
@@ -116,7 +116,7 @@ async def delete_category(category_id: str, credentials: HTTPAuthorizationCreden
 # --- 图片管理 API ---
 
 @router.get("")
-async def get_images(
+async def get_image_list(
     category_id: Optional[str] = None, 
     page: int = 1, 
     page_size: int = 10, 
@@ -129,7 +129,7 @@ async def get_images(
             return ApiResponse(code=401, message="无效的访问令牌", timestamp=datetime.now(timezone.utc).isoformat()).model_dump()
         
         service = ImageService()
-        data = await service.get_images(category_id, page, page_size)
+        data = await service.get_image_list(category_id, page, page_size)
         return ApiResponse(code=0, message="成功", data=data, timestamp=datetime.now(timezone.utc).isoformat()).model_dump()
     except Exception as e:
         logger.exception("获取图片列表失败")

+ 1 - 1
src/views/sample_view.py

@@ -36,7 +36,7 @@ async def get_tasks(type: str, credentials: HTTPAuthorizationCredentials = Depen
         if not payload:
             return ApiResponse(code=401, message="无效的访问令牌", timestamp=datetime.now(timezone.utc).isoformat()).model_dump()
         
-        tasks = await task_service.get_tasks(type)
+        tasks = await task_service.get_task_list(type)
         return ApiResponse(code=0, message="成功", data=tasks, timestamp=datetime.now(timezone.utc).isoformat()).model_dump()
     except Exception as e:
         logger.exception("获取任务列表失败")