ソースを参照

add 添加日志模块

YueYunyun 4 ヶ月 前
コミット
4033131d82

+ 2 - 2
SourceCode/DataMiddleware/app/data_process/process.py

@@ -64,7 +64,7 @@ class Process:
             5. 返回结构体item的json字符串,压缩成一行。
             6. 数据如下:
                 """
-            msg += json.dumps(item.to_ai_json())
+            msg += utils.to_str(item.to_ai_json())
             api_key = utils.get_config_value("fastgpt.api_key_process")
             self._logger.info(f"开始调用AI:\n {msg}")
             json_data = self._ai.call_ai(msg, api_key)
@@ -109,7 +109,7 @@ class Process:
         6. 数据如下:
         """
         item_ai_json = SubProjectItemModel.list_to_ai_json(items)
-        text += json.dumps(item_ai_json,ensure_ascii=False, separators=(',', ':'))
+        text += utils.to_str(item_ai_json)
         return text
 
     def call_ai_batch(self, message:str) ->list[SubProjectItemModel]:

+ 27 - 24
SourceCode/DataMiddleware/app/init.sql

@@ -52,30 +52,28 @@ CREATE TABLE IF NOT EXISTS sub_project_item (
     send_status TINYINT DEFAULT 0,
     send_time DATETIME,
     remark TEXT,
-    is_del TINYINT DEFAULT 0 COMMENT '是否删除',
-    delete_by VARCHAR(255) COMMENT '删除人',
-    deleted_at DATETIME COMMENT '删除时间',
-    create_by VARCHAR(255) COMMENT '创建人',
-    created_at DATETIME DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
-    update_by VARCHAR(255) COMMENT '更新人',
-    updated_at DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间'
+     is_del TINYINT DEFAULT 0,
+    delete_by VARCHAR(255),
+    deleted_at DATETIME,
+    update_by VARCHAR(255),
+    updated_at DATETIME,
+    create_by VARCHAR(255),
+    created_at DATETIME
 ) CHARACTER SET = utf8mb4 COLLATE = utf8mb4_unicode_ci;
 
-CREATE TABLE IF NOT EXISTS user (
+CREATE TABLE IF NOT EXISTS sys_user (
     id INT AUTO_INCREMENT PRIMARY KEY,
-    username VARCHAR(255) UNIQUE NOT NULL COMMENT '用户名',
-    name VARCHAR(255) COMMENT '用户昵称',
-    email VARCHAR(255) COMMENT '邮箱',
-    phone VARCHAR(50) COMMENT '电话',
+    user_name VARCHAR(255) UNIQUE NOT NULL COMMENT '用户名',
+    user_nickname VARCHAR(255) COMMENT '用户昵称',
+    role VARCHAR(50) COMMENT '角色',
     password VARCHAR(255) NOT NULL COMMENT '密码哈希',
-    last_login DATETIME COMMENT '最后登录时间',
     is_del TINYINT DEFAULT 0 COMMENT '是否删除',
     delete_by VARCHAR(255) COMMENT '删除人',
     deleted_at DATETIME COMMENT '删除时间',
     create_by VARCHAR(255) COMMENT '创建人',
     created_at DATETIME DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
     update_by VARCHAR(255) COMMENT '更新人',
-    updated_at DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',
+    INDEX idx_user_name (user_name)
     INDEX idx_username (username)
 ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='用户表';
 
@@ -91,7 +89,21 @@ VALUES (4, 'demo02', 'Demo02', 'demo02@example.com', '13312345002', '$2b$12$woIv
 INSERT INTO `user` (`id`, `username`, `name`, `email`, `phone`, `password`, `last_login`, `is_del`, `delete_by`, `deleted_at`, `create_by`, `created_at`, `update_by`, `updated_at`)
 VALUES (5, 'demo03', 'Demo03', 'demo03@example.com', '13312345002', '$2b$12$woIvmKHq.ndxT5NqzqChl.VF7TWKRbP1KbtAerXNKb/1pQgvRo/iG', NULL, 0, NULL, NULL, 'admin', '2025-03-10 13:56:57', 'admin', '2025-03-10 16:27:47');
 
-
+-- 创建日志表
+CREATE TABLE IF NOT EXISTS sys_log (
+    id INT AUTO_INCREMENT PRIMARY KEY,
+    username VARCHAR(255) NOT NULL COMMENT '用户名',
+    operation_type VARCHAR(50) NOT NULL COMMENT '操作类型',
+    operation_desc VARCHAR(1000) COMMENT '操作描述',
+    operation_result TINYINT COMMENT '操作结果(0:失败, 1:成功)',
+    operation_module VARCHAR(100) COMMENT '操作模块',
+    operation_data TEXT COMMENT '操作数据',
+    data_changes TEXT COMMENT '数据变更记录',
+    operation_ip VARCHAR(50) COMMENT '操作IP',
+    created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
+    INDEX idx_username (username),
+    INDEX idx_created_at (created_at)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='操作日志表';
 
 
 -- -- 修改 project 表的时间字段名
@@ -110,12 +122,3 @@ VALUES (5, 'demo03', 'Demo03', 'demo03@example.com', '13312345002', '$2b$12$woIv
 -- ALTER TABLE sub_project_item
 -- CHANGE COLUMN update_time updated_at DATETIME;
 
-ALTER TABLE sub_project_item
-DROP  updated_at,
-ADD COLUMN  is_del TINYINT DEFAULT 0,
-ADD COLUMN  deleted_by VARCHAR(255),
-ADD COLUMN  deleted_at DATETIME,
-ADD COLUMN  create_by VARCHAR(255),
-ADD COLUMN  created_at DATETIME,
-ADD COLUMN  update_by VARCHAR(255),
-ADD COLUMN  updated_at DATETIME;

+ 39 - 0
SourceCode/DataMiddleware/app/models/log_data.py

@@ -0,0 +1,39 @@
+from datetime import datetime
+from typing import Optional
+
+class LogModel:
+    def __init__(self,
+                 username: str,
+                 operation_type: str,
+                 operation_desc: str,
+                 operation_result: Optional[int] = None,
+                 operation_module: Optional[str] = None,
+                 operation_data: Optional[str] = None,
+                 operation_ip: Optional[str] = None,
+                 data_changes: Optional[str] = None,
+                 log_id: Optional[int] = None,
+                 created_at: Optional[datetime] = None):
+        self.id = log_id
+        self.username = username
+        self.operation_type = operation_type
+        self.operation_desc = operation_desc
+        self.operation_result = operation_result
+        self.operation_module = operation_module
+        self.operation_data = operation_data
+        self.operation_ip = operation_ip
+        self.data_changes = data_changes
+        self.created_at = created_at or datetime.now()
+
+    def to_dict(self) -> dict:
+        return {
+            'id': self.id,
+            'username': self.username,
+            'operation_type': self.operation_type,
+            'operation_desc': self.operation_desc,
+            'operation_result': self.operation_result,
+            'operation_module': self.operation_module,
+            'operation_data': self.operation_data,
+            'operation_ip': self.operation_ip,
+            'data_changes': self.data_changes,
+            'created_at': self.created_at.strftime('%Y-%m-%d %H:%M:%S') if self.created_at else None
+        }

+ 35 - 0
SourceCode/DataMiddleware/app/models/project_data.py

@@ -22,6 +22,13 @@ class ProjectModel:
         self.update_by = update_by
         self.updated_at = updated_at
 
+    def to_dict(self) -> dict:
+        return {
+            'id': self.id,
+            'project_name': self.project_name,
+            'description': self.description,
+        }
+
 class SubProjectModel:
 
     def __init__(self,
@@ -73,6 +80,18 @@ class SubProjectModel:
     def add_item(self, item: 'SubProjectItemModel'):
         self.items.append(item)
 
+    def to_dict(self) -> dict:
+        return {
+            "id": self.id,
+            "project_id": self.project_id,
+            "sub_project_name": self.sub_project_name,
+            "file_paths": self.file_paths,
+            "work_catalog": self.work_catalog,
+            "work_content": self.work_content,
+            "standard_version": self.standard_version,
+            "status": self.status,
+        }
+
 class SubProjectItemModel:
     def __init__(self,
                  project_id: int = None,
@@ -109,6 +128,22 @@ class SubProjectItemModel:
     def __str__(self):
         return f"ProjectItemModel(id={self.id}, project_no={self.sub_project_id}, device_name={self.device_name}, device_model={self.device_model}, device_unit={self.device_unit}, device_count={self.device_count}, standard_no={self.standard_no}, standard_version={self.standard_version})"
 
+    def to_dict(self) -> dict:
+        return {
+            "id": self.id,
+            "project_id": self.project_id,
+            "sub_project_id": self.sub_project_id,
+            "device_name": self.device_name,
+            "device_model": self.device_model,
+            "device_unit": self.device_unit,
+            "device_count": self.device_count,
+            "standard_no": self.standard_no,
+            "standard_version": self.standard_version,
+            "process_status": self.process_status,
+            "process_time": self.process_time,
+            "send_status": self.send_status,
+            "send_time": self.send_time,
+        }
     def to_ai_json(self):
         return {
             "i": self.id,

+ 0 - 7
SourceCode/DataMiddleware/app/models/user_data.py

@@ -62,11 +62,4 @@ class UserModel:
             'name': self.name,
             'email': self.email,
             'phone': self.phone,
-            'last_login': self.last_login.strftime('%Y-%m-%d %H:%M:%S') if self.last_login else None,
-            'created_at': self.created_at.strftime('%Y-%m-%d %H:%M:%S') if self.created_at else None,
-            'create_by': self.create_by,
-            'update_by': self.update_by,
-            'updated_at': self.updated_at.strftime('%Y-%m-%d %H:%M:%S') if self.updated_at else None,
-            'delete_by': self.delete_by,
-            'deleted_at': self.deleted_at.strftime('%Y-%m-%d %H:%M:%S') if self.deleted_at else None
         }

+ 114 - 0
SourceCode/DataMiddleware/app/services/log_helper.py

@@ -0,0 +1,114 @@
+from enum import Enum
+from typing import Optional
+from flask import request, session
+from services.service_log import LogService
+
+class OperationType(Enum):
+    """操作类型枚举"""
+    LOGIN = "登录"
+    LOGOUT = "注销"
+    CREATE = "新增"
+    UPDATE = "修改"
+    DELETE = "删除"
+    PROCESS_TASK = "开始任务"
+    PROCESS = "处理数据"
+    SEND = "发送数据"
+
+class OperationModule(Enum):
+    """操作模块枚举"""
+    ACCOUNT = "账户"
+    PROJECT = "项目"
+    SUB_PROJECT = "工程"
+    SUB_PROJECT_DETAIL = "工程明细"
+
+class LogHelper:
+    _log_service = LogService()
+
+    @staticmethod
+    def get_client_ip() -> str:
+        """获取客户端IP地址"""
+        if request:
+            if 'X-Forwarded-For' in request.headers:
+                return request.headers['X-Forwarded-For'].split(',')[0]
+            return request.remote_addr
+        return ''
+    @staticmethod
+    def log_success(operation_type: OperationType,
+                    operation_module: Optional[OperationModule],
+                    operation_desc: str,
+                    operation_data: Optional[str] = None,
+                    data_changes: Optional[str] = None,
+                    username: Optional[str] = None)-> None:
+        """添加成功操作日志
+        Args:
+            operation_type: 操作类型(枚举值)
+            operation_desc: 操作描述
+            operation_module: 操作模块(枚举值,可选)
+            operation_data: 操作数据(可选)
+            data_changes: 数据变更记录(可选)
+            username: 用户名(可选,默认从session获取)
+        """
+        LogHelper.add_log(
+            operation_type=operation_type,
+            operation_desc=operation_desc,
+            operation_module=operation_module,
+            operation_result=1,
+            operation_data=operation_data,
+            data_changes=data_changes,
+            username=username
+        )
+    @staticmethod
+    def log_fail(operation_type: OperationType,
+                 operation_module: Optional[OperationModule],
+                 operation_desc: str,
+                 operation_data: Optional[str] = None,
+                 data_changes: Optional[str] = None,
+                 username: Optional[str] = None)-> None:
+        """添加失败操作日志
+        Args:
+            operation_type: 操作类型(枚举值)
+            operation_desc: 操作描述
+            operation_module: 操作模块(枚举值,可选)
+            operation_data: 操作数据(可选)
+            data_changes: 数据变更记录(可选)
+            username: 用户名(可选,默认从session获取)
+        """
+        LogHelper.add_log(
+            operation_type=operation_type,
+            operation_desc=operation_desc,
+            operation_module=operation_module,
+            operation_result=0,
+            operation_data=operation_data,
+            data_changes=data_changes,
+            username=username
+        )
+
+    @staticmethod
+    def add_log(operation_type: OperationType,
+                operation_desc: str,
+                operation_module: Optional[OperationModule] = None,
+                operation_result: Optional[int] = None,
+                operation_data: Optional[str] = None,
+                data_changes: Optional[str] = None,
+                username: Optional[str] = None) -> None:
+        """添加操作日志
+
+        Args:
+            operation_type: 操作类型(枚举值)
+            operation_desc: 操作描述
+            operation_module: 操作模块(枚举值,可选)
+            operation_result: 操作结果(0:失败, 1:成功)
+            operation_data: 操作数据(可选)
+            data_changes: 数据变更记录(可选)
+            username: 用户名(可选,默认从session获取)
+        """
+        LogHelper._log_service.add_operation_log(
+            username=username or session.get('username'),
+            operation_type=operation_type.value,
+            operation_desc=operation_desc,
+            operation_module=operation_module.value if operation_module else None,
+            operation_result=operation_result,
+            operation_data=operation_data,
+            operation_ip=LogHelper.get_client_ip(),
+            data_changes=data_changes
+        )

+ 58 - 0
SourceCode/DataMiddleware/app/services/service_log.py

@@ -0,0 +1,58 @@
+import utils
+from datetime import datetime
+from typing import List, Optional, Tuple
+from models.log_data import LogModel
+from stores.log_store import LogStore
+
+class LogService:
+    def __init__(self):
+        self._log_store = LogStore()
+        self._logger = utils.get_logger()
+
+    def add_operation_log(self, username: str, operation_type: str, operation_desc: str,
+                         operation_result: Optional[int] = None,
+                         operation_module: Optional[str] = None,
+                         operation_data: Optional[str] = None,
+                         operation_ip: Optional[str] = None,
+                         data_changes: Optional[str] = None) -> None:
+        """
+        添加操作日志
+        :param username: 用户名
+        :param operation_type: 操作类型
+        :param operation_desc: 操作描述
+        :param operation_result: 操作结果(0:失败, 1:成功)
+        :param operation_module: 操作模块
+        :param operation_data: 操作数据
+        :param operation_ip: 操作IP
+        :param data_changes: 数据变更记录
+        """
+        try:
+            log = LogModel(
+                username=username,
+                operation_type=operation_type,
+                operation_desc=operation_desc,
+                operation_result=operation_result,
+                operation_module=operation_module,
+                operation_data=operation_data,
+                operation_ip=operation_ip,
+                data_changes=data_changes,
+                created_at=datetime.now()
+            )
+            self._log_store.insert_log(log)
+        except Exception as e:
+            self._logger.error(f"----添加操作日志失败-----: {e}")
+
+
+
+    def get_logs_paginated(self, page: int, per_page: int, username: str = None, operation_type: str =None, operation_module: str =None,
+                          start_date: datetime = None, end_date: datetime = None) -> Tuple[List[LogModel], int]:
+        """
+        分页获取日志列表
+        :param page: 页码
+        :param per_page: 每页数量
+        :param username: 用户名筛选
+        :param start_date: 开始时间
+        :param end_date: 结束时间
+        :return: 日志列表和总数
+        """
+        return self._log_store.query_logs_paginated(page, per_page, username, operation_type,operation_module,start_date, end_date)

+ 85 - 33
SourceCode/DataMiddleware/app/services/service_project.py

@@ -1,9 +1,9 @@
+import data_process,data_send
 import threading,utils,os
 
 from models.project_data import ProjectModel,SubProjectModel, SubProjectItemModel
 from stores.project_store import ProjectStore
-import data_process,data_send
-
+from services.log_helper import LogHelper,OperationType,OperationModule
 
 class ProjectService:
 
@@ -20,25 +20,31 @@ class ProjectService:
 
     def add_project(self, project:ProjectModel):
         try:
-            project.create_by = 'admin' if project.create_by is None else project.create_by
             self._project_store.insert_project(project)
+            LogHelper.log_success(OperationType.CREATE,OperationModule.PROJECT, f"添加项目成功",utils.to_str(project.to_dict()))
             return True, ''
         except Exception as e:
             self._logger.error(f"添加项目失败:{e}")
+            LogHelper.log_fail(OperationType.CREATE, OperationModule.PROJECT, f"添加项目失败 错误:{project.project_name} {str(e)}",utils.to_str(project.to_dict()))
             return False, '添加项目失败'
     def update_project(self, project:ProjectModel):
         project = self._project_store.query_project_by_id(project.id)
+        log_data = utils.to_str(project.to_dict())
         if project:
             self._project_store.update_project(project)
+            LogHelper.log_success(OperationType.UPDATE, OperationModule.PROJECT, f"更新项目成功", log_data, utils.to_str(project.to_dict()))
             return True, ''
         else:
+            LogHelper.log_fail( OperationType.UPDATE, OperationModule.PROJECT, f"更新项目失败 错误:{project.project_name}项目不存在", log_data)
             return False, '项目不存在'
     def delete_project(self, project_id:int):
         project = self._project_store.query_project_by_id(project_id)
         if project:
             self._project_store.delete_project(project_id)
+            LogHelper.log_success(OperationType.DELETE, OperationModule.PROJECT, f"删除项目成功", utils.to_str(project.to_dict()))
             return True,''
         else:
+            LogHelper.log_fail(OperationType.DELETE, OperationModule.PROJECT, f"删除项目失败 错误:{project.project_name}项目不存在",utils.to_str(project.to_dict()))
             return False, '项目不存在'
 
     def get_all_sub_project_paginated(self, project_id: int, page: int, per_page: int, keyword: str = None, status: int = None) ->(list[SubProjectModel], int):
@@ -54,16 +60,19 @@ class ProjectService:
         return project
 
     def save_sub_project(self, sub_project:SubProjectModel, project_files: list, delete_old: bool):
+        log_data=""
         if sub_project.id:
             sub_data = self._project_store.query_sub_project(sub_project.id)
+            log_data = utils.to_str(sub_data.to_dict())
             if not sub_data:
+                LogHelper.log_fail(OperationType.UPDATE, OperationModule.SUB_PROJECT, f"修改工程失败 错误:{sub_project.sub_project_name} 工程不存在", log_data)
                 return False, '子项目不存在'
             sub_data.sub_project_name = sub_project.sub_project_name
             sub_data.standard_version = sub_project.standard_version
             sub_data.work_catalog = sub_project.work_catalog
             sub_data.work_content = sub_project.work_content
             sub_data.status = 0
-            self.update_sub_project(sub_data)
+            self._project_store.update_sub_project(sub_data)
         else:
             sub_data = SubProjectModel(
                 project_id=sub_project.project_id,
@@ -74,19 +83,26 @@ class ProjectService:
                 file_paths="",
                 status=0
             )
-            new_id =  self.add_sub_project(sub_data)
+            new_id =  self._project_store.insert_sub_project(sub_data)
             sub_data.id = new_id
         try:
             # 处理文件上传逻辑
             if project_files:
                 file_paths = self._process_file_upload(sub_data, project_files, delete_old)
-                self.update_sub_project_file_paths(sub_data.id, file_paths)
+                self._update_sub_project_file_paths(sub_data.id, file_paths)
+                sub_data.file_paths = file_paths
             elif delete_old:
                 return False,"请上传项目数据文件"
+            if sub_project.id:
+                LogHelper.log_success(OperationType.UPDATE ,OperationModule.SUB_PROJECT, f"修改工程成功", log_data, utils.to_str(sub_data.to_dict()))
+            else:
+                LogHelper.log_success(OperationType.CREATE ,OperationModule.SUB_PROJECT, f"添加工程成功", utils.to_str(sub_data.to_dict()))
             return True, ''
         except ValueError as ve:
+            LogHelper.log_fail(OperationType.UPDATE, OperationModule.SUB_PROJECT, f"{'修改' if sub_project.id else '添加'}工程失败 错误:{sub_project.sub_project_name} {str(ve)}", utils.to_str(sub_data.to_dict()))
             return False,str(ve)
         except Exception as e:
+            LogHelper.log_fail(OperationType.UPDATE, OperationModule.SUB_PROJECT, f"{'修改' if sub_project.id else '添加'}工程失败 错误:{sub_project.sub_project_name} {str(e)}", utils.to_str(sub_data.to_dict()))
             return False,f'服务器错误: {str(e)}'
 
     def _process_file_upload(self,sub_project: SubProjectModel, files: list,  delete_old: bool) -> str:
@@ -97,10 +113,24 @@ class ProjectService:
         self._logger.info(f"保存处理文件,项目ID:{sub_project.project_id},子项目ID:{sub_project.id}")
         if delete_old:
             if os.path.exists(sub_project_dir):
+                delete_paths = []
                 for filename in os.listdir(sub_project_dir):
                     file_path = os.path.join(sub_project_dir, filename)
                     if os.path.isfile(file_path):
-                        os.remove(file_path)
+                        delete_dir = os.path.join(sub_project_dir, 'delete')
+                        os.makedirs(delete_dir, exist_ok=True)
+                        # 处理文件名冲突
+                        base_name = os.path.basename(file_path)
+                        target_path = os.path.join(delete_dir, base_name)
+                        counter = 1
+                        while os.path.exists(target_path):
+                            name, ext = os.path.splitext(base_name)
+                            target_path = os.path.join(delete_dir, f"{name}_{counter}{ext}")
+                            counter += 1
+                        os.rename(file_path, target_path)
+                        delete_paths.append(target_path)
+                if len(delete_paths) > 0:
+                    LogHelper.log_success(OperationType.DELETE, OperationModule.SUB_PROJECT, f"删除子工程文件:{sub_project.sub_project_name}", utils.to_str(delete_paths))
         file_paths =[] if delete_old or not sub_project.file_paths else sub_project.file_paths.split(',')
         for file in files:
             if not file.filename:
@@ -115,13 +145,7 @@ class ProjectService:
             file_paths.append(file_path)
         return ','.join(file_paths)
 
-    def add_sub_project(self, sub_project:SubProjectModel):
-       return self._project_store.insert_sub_project(sub_project)
-
-    def update_sub_project(self, sub_project:SubProjectModel):
-        self._project_store.update_sub_project(sub_project)
-
-    def update_sub_project_file_paths(self, sub_project_id: int, paths: str):
+    def _update_sub_project_file_paths(self, sub_project_id: int, paths: str):
         self._project_store.update_sub_project_file_path(sub_project_id, paths)
     def start_sub_project_task(self, sub_project_id: int) -> (bool, str):
         data = self._project_store.query_sub_project(sub_project_id)
@@ -132,6 +156,7 @@ class ProjectService:
                 return False, '正在分析处理中'
             if data.status == 32 or data.status == 33:
                 return False, '正在上传数据中'
+            LogHelper.log_success(OperationType.PROCESS_TASK, OperationModule.SUB_PROJECT, f"启动工程任务", utils.to_str(data.to_dict()))
             thread = threading.Thread(target = self._process_and_send_sub_project, args=(data,))
             thread.start()
             return True, ''
@@ -153,6 +178,7 @@ class ProjectService:
                 return False, '正在分析处理中'
             if data.status == 32 or data.status == 33:
                 return False, '正在上传数据中'
+            LogHelper.log_success(OperationType.PROCESS, OperationModule.SUB_PROJECT, f"处理工程数据", utils.to_str(data.to_dict()))
             thread = threading.Thread(target=self._process_sub_project, args=(data,))
             thread.start()
             return True, ''
@@ -174,6 +200,7 @@ class ProjectService:
                 return False, '正在分析处理中'
             if data.status == 32 or data.status == 33:
                 return False, '正在上传数据中'
+            LogHelper.log_success(OperationType.SEND, OperationModule.SUB_PROJECT, f"发送工程数据", utils.to_str(data.to_dict()))
             thread = threading.Thread(target=self._send_sub_project_data, args=(data,))
             thread.start()
 
@@ -188,8 +215,10 @@ class ProjectService:
         project = self._project_store.query_sub_project(sub_project_id)
         if project:
             self._project_store.delete_sub_project(sub_project_id)
+            LogHelper.log_success(OperationType.DELETE, OperationModule.SUB_PROJECT, f"删除工程成功", utils.to_str(project.to_dict()))
             return True,''
         else:
+            LogHelper.log_fail(OperationType.DELETE, OperationModule.SUB_PROJECT, f"删除工程", utils.to_str(project.to_dict()))
             return False, '项目不存在'
 
     def get_sub_project_item_list_by_sub_project_paginated(self, project_id: int, page: int, per_page: int, keyword: str = None, process_status:int = None, send_status:int=None) -> (list[SubProjectItemModel], int):
@@ -202,41 +231,62 @@ class ProjectService:
 
     def add_sub_project_item(self, item:SubProjectItemModel):
         project = self._project_store.query_sub_project(item.sub_project_id)
-        project_item = SubProjectItemModel(
-            project_id=project.project_id,
-            sub_project_id=item.sub_project_id,
-            device_name=item.device_name,
-            device_model=item.device_model,
-            device_unit=item.device_unit,
-            standard_version=project.standard_version,
-            standard_no=item.standard_no,
-        )
-        return self._project_store.insert_sub_project_item(project_item)
+        try:
+            project_item = SubProjectItemModel(
+                project_id=project.project_id,
+                sub_project_id=item.sub_project_id,
+                device_name=item.device_name,
+                device_model=item.device_model,
+                device_unit=item.device_unit,
+                standard_version=project.standard_version,
+                standard_no=item.standard_no,
+            )
+            new_id= self._project_store.insert_sub_project_item(project_item)
+            if new_id:
+                LogHelper.log_success(OperationType.CREATE, OperationModule.SUB_PROJECT_DETAIL, f"新增工程明细成功", utils.to_str(project_item.to_dict()))
+                return new_id
+        except Exception as e:
+            LogHelper.log_fail(OperationType.CREATE, OperationModule.SUB_PROJECT_DETAIL, f"新增工程明细失败 错误:{str(e)}", utils.to_str(item.to_dict()))
+            return None
 
     def update_sub_project_item(self, item:SubProjectItemModel):
         project_item = self._project_store.query_sub_project_item_by_id(item.id)
         if project_item:
-            project_item.device_name = item.device_name
-            project_item.device_model = item.device_model
-            project_item.device_unit = item.device_unit
-            project_item.device_count = float(item.device_count)
-            project_item.standard_no = item.standard_no
-            self._project_store.update_sub_project_item(project_item)
-            return True
+            try:
+                log_data = utils.to_str(project_item.to_dict())
+                project_item.device_name = item.device_name
+                project_item.device_model = item.device_model
+                project_item.device_unit = item.device_unit
+                project_item.device_count = float(item.device_count)
+                project_item.standard_no = item.standard_no
+                self._project_store.update_sub_project_item(project_item)
+                LogHelper.log_success(OperationType.UPDATE, OperationModule.SUB_PROJECT_DETAIL, f"修改工程明细成功", log_data, utils.to_str(project_item.to_dict()))
+                return True
+            except Exception as e:
+                LogHelper.log_fail(OperationType.UPDATE, OperationModule.SUB_PROJECT_DETAIL, f"修改工程明细失败 错误:{str(e)}", utils.to_str(item.to_dict()))
+                return False
         else:
+            LogHelper.log_fail(OperationType.UPDATE, OperationModule.SUB_PROJECT_DETAIL, f"修改工程明细失败 错误:明细不存在", utils.to_str(item.to_dict()))
             return False
     def delete_sub_project_item(self, item_id:int):
         project_item = self._project_store.query_sub_project_item_by_id(item_id)
         if project_item:
-            self._project_store.delete_sub_project_item_by_id(project_item.id)
-            return True
+           try:
+               self._project_store.delete_sub_project_item_by_id(project_item.id)
+               LogHelper.log_success(OperationType.DELETE, OperationModule.SUB_PROJECT_DETAIL, f"删除工程明细成功", utils.to_str(project_item.to_dict()))
+               return True
+           except Exception as e:
+               LogHelper.log_fail(OperationType.DELETE, OperationModule.SUB_PROJECT_DETAIL, f"删除工程明细失败 错误:{str(e)}", str(item_id))
         else:
+            LogHelper.log_fail(OperationType.DELETE, OperationModule.SUB_PROJECT_DETAIL, f"删除工程明细失败 错误:明细不存在", str(item_id))
             return False
 
     def start_process_item(self, item_id:int):
         project_item = self._project_store.query_sub_project_item_by_id(item_id)
         if not project_item:
+            LogHelper.log_fail(OperationType.PROCESS, OperationModule.SUB_PROJECT_DETAIL, f"处理工程明细失败 错误:明细不存在", str(item_id))
             return False, '项目不存在'
+        LogHelper.log_success(OperationType.PROCESS, OperationModule.SUB_PROJECT_DETAIL, f"开始处理工程明细", utils.to_str(project_item.to_dict()))
         thread = threading.Thread(target=self._process_sub_project_item, args=(project_item,))
         thread.start()
         return True,""
@@ -251,7 +301,9 @@ class ProjectService:
     def start_send_item(self, item_id:int):
         project_item = self._project_store.query_sub_project_item_by_id(item_id)
         if not project_item:
+            LogHelper.log_fail(OperationType.SEND, OperationModule.SUB_PROJECT_DETAIL, f"发送工程明细失败 错误:明细不存在", str(item_id))
             return False, '项目不存在'
+        LogHelper.log_success(OperationType.SEND, OperationModule.SUB_PROJECT_DETAIL, f"开始发送工程明细", utils.to_str(project_item.to_dict()))
         thread = threading.Thread(target=self._send_sub_project_item, args=(project_item,))
         thread.start()
         return True,""

+ 9 - 3
SourceCode/DataMiddleware/app/services/service_user.py

@@ -1,9 +1,9 @@
-from datetime import datetime
 from typing import Optional, Tuple
 
+import utils
 from models.user_data import UserModel
 from stores.user_store import UserStore
-
+from services.log_helper import LogHelper,OperationType,OperationModule
 class UserService:
     def __init__(self):
         self._user_store = UserStore()
@@ -21,12 +21,14 @@ class UserService:
 
         # 验证密码
         if not user.verify_password(password):
+            LogHelper.log_fail(OperationType.LOGIN, OperationModule.ACCOUNT, "登录失败,密码错误",username=user.username)
             return None, "密码错误"
 
         # 更新最后登录时间
         try:
             self._user_store.update_last_login(user.id)
             user.update_last_login()
+            LogHelper.log_success(OperationType.LOGIN, OperationModule.ACCOUNT, "登录成功",username=user.username)
             return user, "登录成功"
         except Exception as e:
             return None, f"登录失败:{str(e)}"
@@ -46,7 +48,7 @@ class UserService:
         # 检查手机号是否已被其他用户使用
         if phone and phone != user.phone and self._user_store.check_phone_exists(phone, user_id):
             return False, "手机号已被使用"
-
+        log_data = utils.to_str(user.to_dict())
         # 更新用户信息
         user.email = email if email is not None else user.email
         user.phone = phone if phone is not None else user.phone
@@ -55,6 +57,7 @@ class UserService:
 
         try:
             self._user_store.update_user(user)
+            LogHelper.log_success(OperationType.UPDATE, OperationModule.ACCOUNT, "更新成功",log_data,utils.to_str(user.to_dict()))
             return True, "更新成功"
         except Exception as e:
             return False, f"更新失败:{str(e)}"
@@ -72,6 +75,7 @@ class UserService:
 
         # 验证旧密码
         if not user.verify_password(old_password):
+            LogHelper.log_fail(OperationType.UPDATE, OperationModule.ACCOUNT, "密码更新失败,旧密码错误")
             return False, "旧密码错误"
 
         # 设置新密码
@@ -79,6 +83,7 @@ class UserService:
 
         try:
             self._user_store.update_user_password(user_id, user.get_password())
+            LogHelper.log_success(OperationType.UPDATE, OperationModule.ACCOUNT, "密码更新成功")
             return True, "密码更新成功"
         except Exception as e:
             return False, f"密码更新失败:{str(e)}"
@@ -91,6 +96,7 @@ class UserService:
 
         try:
             self._user_store.delete_user(user_id, delete_by)
+            LogHelper.log_success(OperationType.DELETE, OperationModule.ACCOUNT, "用户删除成功", utils.to_str(user.to_dict()))
             return True, "删除成功"
         except Exception as e:
             return False, f"删除失败:{str(e)}"

+ 89 - 0
SourceCode/DataMiddleware/app/stores/log_store.py

@@ -0,0 +1,89 @@
+from datetime import datetime
+from typing import List, Optional, Tuple
+from utils.mysql_helper import MySQLHelper
+from models.log_data import LogModel
+
+class LogStore:
+    def __init__(self):
+        self._db_helper = MySQLHelper()
+
+    def insert_log(self, log_data: LogModel):
+        sql = "INSERT INTO sys_log (username, operation_type, operation_desc, operation_result, operation_data, operation_ip, operation_module, data_changes, created_at) VALUES (%s, %s, %s, %s, %s, %s, %s, %s, %s)"
+        params = (
+            log_data.username,
+            log_data.operation_type,
+            log_data.operation_desc,
+            log_data.operation_result,
+            log_data.operation_data,
+            log_data.operation_ip,
+            log_data.operation_module,
+            log_data.data_changes,
+            log_data.created_at
+        )
+        with self._db_helper:
+            self._db_helper.execute_non_query(sql, params)
+
+    def query_logs_paginated(self, page: int, per_page: int, username: str = None, operation_type: str = None, operation_module: str = None, start_date: datetime = None, end_date: datetime = None) -> Tuple[List[LogModel], int]:
+        offset = (page - 1) * per_page
+        sql_count = "SELECT COUNT(*) as count FROM sys_log WHERE 1=%s"
+        sql = "SELECT id, username, operation_type, operation_desc, operation_result, operation_data, operation_ip, operation_module, data_changes, created_at FROM sys_log WHERE 1=%s"
+        params_count = (1,)
+        params = (1,)
+
+        if username:
+            sql_count += " AND username = %s"
+            sql += " AND username = %s"
+            params_count += (username,)
+            params += (username,)
+
+        if operation_type:
+            sql_count += " AND operation_type = %s"
+            sql += " AND operation_type = %s"
+            params_count += (operation_type,)
+            params += (operation_type,)
+
+        if operation_module:
+            sql_count += " AND operation_module = %s"
+            sql += " AND operation_module = %s"
+            params_count += (operation_module,)
+            params += (operation_module,)
+
+        if start_date:
+            sql_count += " AND created_at >= %s"
+            sql += " AND created_at >= %s"
+            params_count += (start_date,)
+            params += (start_date,)
+
+        if end_date:
+            sql_count += " AND created_at <= %s"
+            sql += " AND created_at <= %s"
+            params_count += (end_date,)
+            params += (end_date,)
+
+        sql += " ORDER BY created_at DESC LIMIT %s OFFSET %s"
+        params += (per_page, offset)
+
+        with self._db_helper:
+            result_count = self._db_helper.fetch_one(sql_count, params_count)
+            count = result_count["count"] if result_count else 0
+            result = self._db_helper.execute_query(sql, params)
+            data = []
+            if not result:
+                return data, count
+
+            for item in result:
+                data.append(
+                    LogModel(
+                        log_id=item["id"],
+                        username=item["username"],
+                        operation_type=item["operation_type"],
+                        operation_desc=item["operation_desc"],
+                        operation_result=item["operation_result"],
+                        operation_data=item["operation_data"],
+                        operation_ip=item["operation_ip"],
+                        operation_module=item["operation_module"],
+                        data_changes=item["data_changes"],
+                        created_at=item["created_at"]
+                    )
+                )
+            return data, count

+ 13 - 13
SourceCode/DataMiddleware/app/stores/user_store.py

@@ -9,8 +9,8 @@ class UserStore:
 
     def query_user_all_paginated(self, page: int, per_page: int, keyword: str = None):
         offset = (page - 1) * per_page
-        sql_count = "SELECT COUNT(*) as count FROM user WHERE is_del=%s"
-        sql = "SELECT id,username,email,phone,name,last_login,delete_by,deleted_at,create_by,created_at,update_by,updated_at FROM user WHERE is_del=%s"
+        sql_count = "SELECT COUNT(*) as count FROM sys_user WHERE is_del=%s"
+        sql = "SELECT id,username,email,phone,name,last_login,delete_by,deleted_at,create_by,created_at,update_by,updated_at FROM sys_user WHERE is_del=%s"
         params_count = (0,)
         params = (0,)
         if keyword:
@@ -47,7 +47,7 @@ class UserStore:
             return data, count
 
     def query_user_by_id(self, user_id: int) -> UserModel | None:
-        sql = "SELECT id,username,password,email,phone,name,last_login,delete_by,deleted_at,create_by,created_at,update_by,updated_at FROM user WHERE is_del=0 AND id = %s"
+        sql = "SELECT id,username,password,email,phone,name,last_login,delete_by,deleted_at,create_by,created_at,update_by,updated_at FROM sys_user WHERE is_del=0 AND id = %s"
         params = (user_id,)
         with self._db_helper:
             result = self._db_helper.fetch_one(sql, params)
@@ -71,7 +71,7 @@ class UserStore:
             return user
     def query_user(self, keyword: str) -> UserModel | None:
         # 构建基础SQL
-        base_sql = "SELECT id,username,password,email,phone,name,last_login,delete_by,deleted_at,create_by,created_at,update_by,updated_at FROM user WHERE is_del=0"
+        base_sql = "SELECT id,username,password,email,phone,name,last_login,delete_by,deleted_at,create_by,created_at,update_by,updated_at FROM sys_user WHERE is_del=0"
         
         # 根据关键字类型构建查询条件
         if utils.is_email(keyword):
@@ -103,7 +103,7 @@ class UserStore:
             user.set_password(result["password"])
             return user
     def query_user_by_username(self, username: str) -> UserModel | None:
-        sql = "SELECT id,username,email,phone,name FROM user WHERE is_del=0 AND username = %s"
+        sql = "SELECT id,username,email,phone,name FROM sys_user WHERE is_del=0 AND username = %s"
         params = (username,)
         with self._db_helper:
             result = self._db_helper.fetch_one(sql, params)
@@ -119,7 +119,7 @@ class UserStore:
             return user
 
     def insert_user(self, user_data: UserModel):
-        sql = "INSERT INTO user (username,password,email,phone,name,create_by,created_at,update_by,updated_at) VALUES (%s,%s,%s,%s,%s,%s,%s,%s,%s)"
+        sql = "INSERT INTO sys_user (username,password,email,phone,name,create_by,created_at,update_by,updated_at) VALUES (%s,%s,%s,%s,%s,%s,%s,%s,%s)"
         params = (
             user_data.username,
             user_data._password,
@@ -135,7 +135,7 @@ class UserStore:
             self._db_helper.execute_non_query(sql, params)
 
     def update_user(self, user_data: UserModel):
-        sql = "UPDATE user SET email=%s,phone=%s,name=%s,update_by=%s,updated_at=%s WHERE id=%s"
+        sql = "UPDATE sys_user SET email=%s,phone=%s,name=%s,update_by=%s,updated_at=%s WHERE id=%s"
         params = (
             user_data.email,
             user_data.phone,
@@ -148,25 +148,25 @@ class UserStore:
             self._db_helper.execute_non_query(sql, params)
 
     def update_user_password(self, user_id: int, password: str):
-        sql = "UPDATE user SET password=%s,updated_at=%s WHERE id=%s"
+        sql = "UPDATE sys_user SET password=%s,updated_at=%s WHERE id=%s"
         params = (password, datetime.now(), user_id)
         with self._db_helper:
             self._db_helper.execute_non_query(sql, params)
 
     def update_last_login(self, user_id: int):
-        sql = "UPDATE user SET last_login=%s WHERE id=%s"
+        sql = "UPDATE sys_user SET last_login=%s WHERE id=%s"
         params = (datetime.now(), user_id)
         with self._db_helper:
             self._db_helper.execute_non_query(sql, params)
 
     def delete_user(self, user_id: int, delete_by: str=""):
-        sql = "UPDATE user SET is_del=1,delete_by=%s,deleted_at=%s WHERE id=%s"
+        sql = "UPDATE sys_user SET is_del=1,delete_by=%s,deleted_at=%s WHERE id=%s"
         params = (delete_by, datetime.now(), user_id)
         with self._db_helper:
             self._db_helper.execute_non_query(sql, params)
 
     def check_username_exists(self, username: str, exclude_user_id: int = None) -> bool:
-        sql = "SELECT COUNT(*) as count FROM user WHERE is_del=0 AND username = %s"
+        sql = "SELECT COUNT(*) as count FROM sys_user WHERE is_del=0 AND username = %s"
         params = [username]
         if exclude_user_id:
             sql += " AND id != %s"
@@ -176,7 +176,7 @@ class UserStore:
             return result["count"] > 0 if result else False
 
     def check_email_exists(self, email: str, exclude_user_id: int = None) -> bool:
-        sql = "SELECT COUNT(*) as count FROM user WHERE is_del=0 AND email = %s"
+        sql = "SELECT COUNT(*) as count FROM sys_user WHERE is_del=0 AND email = %s"
         params = [email]
         if exclude_user_id:
             sql += " AND id != %s"
@@ -186,7 +186,7 @@ class UserStore:
             return result["count"] > 0 if result else False
 
     def check_phone_exists(self, phone: str, exclude_user_id: int = None) -> bool:
-        sql = "SELECT COUNT(*) as count FROM user WHERE is_del=0 AND phone = %s"
+        sql = "SELECT COUNT(*) as count FROM sys_user WHERE is_del=0 AND phone = %s"
         params = [phone]
         if exclude_user_id:
             sql += " AND id != %s"

+ 2 - 0
SourceCode/DataMiddleware/app/ui/__init__.py

@@ -12,10 +12,12 @@ def create_app():
     # 注册蓝图或其他初始化操作
     from .views_project import project_bp
     from .views_auth import auth_bp
+    from .views_log import log_bp
     web_app.secret_key="1qwe2iwb3vber"
 
     web_app.register_blueprint(project_bp)
     web_app.register_blueprint(auth_bp)
+    web_app.register_blueprint(log_bp)
 
     # 注册自定义过滤器
     web_app.jinja_env.filters['basename'] = basename_filter

+ 6 - 2
SourceCode/DataMiddleware/app/ui/templates/base/user_info.html

@@ -1,9 +1,13 @@
 <div class="user-info">
+    {% set username = session.get('username', '') %}
     <div class="user-avatar" onclick="toggleUserMenu()">
-        <div class="avatar-circle">{{ session.get('username', '')[:1].upper() }}</div>
-        <span class="username">{{ session.get('username', '') }}</span>
+        <div class="avatar-circle">{{ username[:1].upper() }}</div>
+        <span class="username">{{ username }}</span>
     </div>
     <div class="user-menu" id="userMenu">
+        {% if username == 'admin' %}
+        <div class="menu-item" onclick="goTo(`{{ url_for('log.log_list') }}`)">操作日志</div>
+        {% endif %}
         <div class="menu-item" onclick="showChangePasswordModal()">修改密码</div>
         <div class="menu-item" onclick="logout()">注销登录</div>
     </div>

+ 148 - 0
SourceCode/DataMiddleware/app/ui/templates/log/list.html

@@ -0,0 +1,148 @@
+<!DOCTYPE html>
+<meta name="viewport" content="width=device-width, initial-scale=1.0" />
+<html lang="zh">
+	<head>
+		<meta charset="UTF-8" />
+		<title>日志管理</title>
+		<link rel="stylesheet" href="{{ url_for('static', filename='styles.css') }}" />
+		<script src="{{ url_for('static', filename='utils.js') }}"></script>
+		<script>
+			function searchData() {
+				const operation_type = document.getElementById('operation_type').value
+				const operation_module = document.getElementById('operation_module').value
+				const username = document.getElementById('username').value
+				const startDate = document.getElementById('startDate').value
+				const endDate = document.getElementById('endDate').value
+				window.location.href = `{{ url_for('log.log_list') }}?u=${username}&ot=${operation_type}&om=${operation_module}&s=${startDate}&e=${endDate}`
+			}
+			function reSearchData() {
+				window.location.href = `{{ url_for('log.log_list') }}`
+			}
+			function jumpToPage(page) {
+				window.location.href = `{{ url_for('log.log_list') }}?u={{username}}&ot={{operation_type}}&om={{operation_module}}&s={{start_date}}&e={{end_date}}&pp={{per_page}}&p=${page}`
+			}
+		</script>
+	</head>
+	<body>
+		<div class="box">
+			<div class="box_header">
+				<h3 class="box_title">日志管理</h3>
+				{% include 'base/user_info.html' %}
+			</div>
+			<div class="box_header">
+				<div></div>
+				<div class="search_box">
+					<select class="form-control" id="operation_type">
+						<option value="">全部操作类型</option>
+						{% for op in operation_type_list %}
+						{% if op == operation_type %}
+						<option value="{{ op }}" selected>{{ op }}</option>
+						{% else %}
+						<option value="{{ op }}" >{{ op }}</option>
+						{% endif %}
+						{% endfor %}
+					</select>
+					<select class="form-control" id="operation_module">
+						<option value="">全部模块</option>
+						{% for op in operation_module_list %}
+						{% if op == operation_module %}
+						<option value="{{ op }}" selected>{{ op }}</option>
+						{% else %}
+						<option value="{{ op }}" >{{ op }}</option>
+						{% endif %}
+						{% endfor %}
+					</select>
+					<input type="text" id="username" class="form-control" placeholder="请输入用户名" value="{{ username }}" />
+					<input type="date" id="startDate" class="form-control" placeholder="开始时间" value="{{ start_date }}" />
+					<input type="date" id="endDate" class="form-control" placeholder="结束时间" value="{{ end_date }}" />
+					<button type="button" class="btn btn-info btn-large" onclick="searchData()">查询</button>
+					<button type="button" class="btn btn-warning btn-large" onclick="reSearchData()">重置</button>
+				</div>
+			</div>
+
+			<div class="box-body">
+				<table class="table">
+					<thead>
+						<tr>
+							<th width="80px">ID</th>
+							<th width="240px">用户名</th>
+							<th width="180px">操作类型</th>
+							<th width="180px">操作模块</th>
+							<th>操作描述</th>
+							<th width="120px">操作结果</th>
+							<th width="180px">操作IP</th>
+							<th width="180px">操作时间</th>
+						</tr>
+					</thead>
+					<tbody>
+						{% if log_list %} {% for log in log_list %}
+						<tr>
+							<td>{{ log.id }}</td>
+							<td>{{ log.username }}</td>
+							<td>
+								{% if log.operation_type == '登录' %}
+								<span class="label label-info">登录</span>
+								{% elif log.operation_type == '新增' %}
+								<span class="label label-info">新增</span>
+								{% elif log.operation_type == '修改' %}
+								<span class="label label-info">修改</span>
+								{% elif log.operation_type == '删除' %}
+								<span class="label label-danger">删除</span>
+								{% else %}
+								<span class="label label-success">{{ log.operation_type}}</span>
+								{% endif %}
+							</td>
+							<td>{{ log.operation_module }}</td>
+							<td title="{{log.operation_data if log.operation_data else ''}}{{'\n'+log.data_changes if log.data_changes else '' }}">
+								{{ log.operation_desc }}
+							</td>
+							<td>
+								{% if log.operation_result == 1 %}
+								<span class="label label-success">成功</span>
+								{% else %}
+								<span class="label label-error">失败</span>
+								{% endif %}
+							</td>
+							<td>{{ log.operation_ip }}</td>
+							<td>{{ log.created_at }}</td>
+						</tr>
+						{% endfor %} {% else %}
+						<tr>
+							<td colspan="8">暂无数据</td>
+						</tr>
+						{% endif %}
+					</tbody>
+				</table>
+				<div class="pagination">
+					<div class="pagination-info">
+						{% set total_pages = (total_count|int + per_page|int - 1)//per_page %}
+						<span class="page"> 总共 {{ total_count }} 条数据,每页 {{ per_page }} 条,当前第 {{ page }} 页 / 共 {{ total_pages }} 页 </span>
+					</div>
+					<div class="pagination-links">
+						{% if page > 1 %}
+						<span class="page page-link" onclick="jumpToPage(1)">首页</span>
+						{% endif %} {% if page > 1 %}
+						<span class="page page-link" onclick="jumpToPage(`{{page - 1}}`)">上一页</span>
+						{% endif %} {% set start_page = [1, page - 2]|max %} {% set end_page = [total_pages, page + 2]|min %} {% if start_page > 1 %}
+						<span class="page page-link" onclick="jumpToPage(1)">1</span>
+						{% if start_page > 2 %}
+						<span class="page">...</span>
+						{% endif %} {% endif %} {% for p in range(start_page, end_page + 1) %} {% if p == page %}
+						<span class="page page-link active" onclick="jumpToPage(`{{ p }}`)">{{ p }}</span>
+						{% else %}
+						<span class="page page-link" onclick="jumpToPage(`{{ p }}`)">{{ p }}</span>
+						{% endif %} {% endfor %} {% if end_page < total_pages %} {% if end_page < total_pages - 1 %}
+						<span class="page">...</span>
+						{% endif %}
+						<span class="page page-link" onclick="jumpToPage(`{{ total_pages }}`)">{{ total_pages }}</span>
+						{% endif %} {% if page < total_pages %}
+						<span class="page page-link" onclick="jumpToPage(`{{ page+1 }}`)">下一页</span>
+						{% endif %} {% if page < total_pages %}
+						<span class="page page-link" onclick="jumpToPage(`{{ total_pages }}`)">末页</span>
+						{% endif %}
+					</div>
+				</div>
+			</div>
+		</div>
+	</body>
+</html>

+ 4 - 0
SourceCode/DataMiddleware/app/ui/views_auth.py

@@ -1,4 +1,6 @@
 from flask import Blueprint, render_template, request, redirect, url_for, jsonify, session
+
+from services.log_helper import LogHelper,OperationType,OperationModule
 from services.service_user import UserService
 
 UserService = UserService()
@@ -54,6 +56,8 @@ def update_user_password():
 @auth_bp.route('/logout', methods=['GET','POST'])
 def logout():
     # 清除session中的用户信息
+    username = session.get('username')
     session.pop('user_id', None)
     session.pop('username', None)
+    LogHelper.log_success(OperationType.LOGIN, OperationModule.ACCOUNT, '用户退出登录',username=username)
     return redirect(url_for('auth.login'))

+ 63 - 0
SourceCode/DataMiddleware/app/ui/views_log.py

@@ -0,0 +1,63 @@
+from datetime import datetime
+from flask import Blueprint, render_template, request, redirect, url_for, jsonify, send_from_directory, session
+
+import utils
+from app.ui.views_base import BaseView
+from app.services.service_log import LogService
+from app.services.log_helper import OperationType,OperationModule
+
+LogService = LogService()
+
+log_bp = Blueprint('log', __name__, template_folder='templates')
+
+@log_bp.route('/log_list')
+@BaseView.login_required
+def log_list():
+    page = request.args.get('p', 1, type=int)
+    per_page = request.args.get('pp', 10, type=int)
+    username = request.args.get('u', '')
+    operation_type = request.args.get('ot', '')
+    operation_module = request.args.get('om', '')
+    start_date = request.args.get('s', '')
+    end_date = request.args.get('e', '')
+
+    try:
+        start_date_obj = datetime.strptime(start_date, '%Y-%m-%d') if start_date else None
+        end_date_obj = datetime.strptime(end_date + ' 23:59:59', '%Y-%m-%d %H:%M:%S') if end_date else None
+
+        logs, total_count = LogService.get_logs_paginated(
+            page=page,
+            per_page=per_page,
+            username=username if username else None,
+            operation_type=operation_type if operation_type else None,
+            operation_module=operation_module if operation_module else None,
+            start_date=start_date_obj,
+            end_date=end_date_obj
+        )
+
+        return render_template('log/list.html',
+                             log_list=logs,
+                             username=username,
+                             start_date=start_date,
+                             end_date=end_date,
+                             page=page,
+                             per_page=per_page,
+                             total_count=total_count,
+                             operation_type=operation_type,
+                             operation_module=operation_module,
+                             operation_module_list=[op.value for op in OperationModule],
+                             operation_type_list=[op.value for op in OperationType])
+    except Exception as e:
+        utils.get_logger().error(f"Error in log_list: {str(e)}")
+        return render_template('log/list.html',
+                             log_list=[],
+                             username=username,
+                             start_date=start_date,
+                             end_date=end_date,
+                             page=1,
+                             per_page=per_page,
+                             total_count=0,
+                             operation_type=operation_type,
+                             operation_module=operation_module,
+                             operation_module_list=[op.value for op in OperationModule],
+                             operation_type_list=[op.value for op in OperationType])

+ 0 - 24
SourceCode/DataMiddleware/app/ui/views_project.py

@@ -123,30 +123,6 @@ def save_sub_project():
     except Exception as e:
         return jsonify({'success': False, 'error': str(e)}), 400
 
-@project_bp.route('/update_sub_project', methods=['POST'])
-@BaseView.login_required
-def update_sub_project():
-    try:
-        req = request.get_json()
-        sub_project_id = req.get('id')
-        project_name = req.get('project_name')
-        standard_version = req.get('standard_version')
-        work_catalog = req.get('work_catalog')
-        work_content = req.get('work_content')
-        project_data = ProjectService.get_sub_project_by_id(sub_project_id)
-        if not project_data:
-            return jsonify({'success': False, 'error': '项目不存在'}), 404
-        data = SubProjectModel(sub_id=sub_project_id,
-                                sub_project_name=project_name,
-                                standard_version=standard_version,
-                                work_catalog=work_catalog,
-                                work_content=work_content,
-                                status=project_data.status)
-        ProjectService.update_sub_project(data)
-        return jsonify({'success': True})
-    except Exception as e:
-        return jsonify({'success': False, 'error': str(e)}), 400
-
 @project_bp.route('/start_sub_project_task/<project_id>', methods=['POST'])
 @BaseView.login_required
 def start_sub_project_task(project_id:int):

+ 7 - 0
SourceCode/DataMiddleware/app/utils/__init__.py

@@ -139,6 +139,13 @@ def to_array(s: str, split: str = ",") -> list[str]:
     """
     return StringHelper.to_array(s, split)
 
+def to_str(data:dict|list|tuple):
+    """
+    将对象转成字符串
+    :param data:
+    :return:
+    """
+    return StringHelper.to_str(data)
 def is_email(email: str) -> bool:
     """
     判断字符串是否为有效的电子邮件地址。

+ 4 - 0
SourceCode/DataMiddleware/app/utils/string_helper.py

@@ -1,3 +1,4 @@
+import json
 class StringHelper:
 
     @staticmethod
@@ -75,6 +76,9 @@ class StringHelper:
         return " ".join(s.split())
 
     @staticmethod
+    def to_str(data:dict|list|tuple):
+        return json.dumps(data, ensure_ascii=False)
+    @staticmethod
     def is_email(s: str) -> bool:
         """
         验证字符串是否为有效的邮箱地址。