yue 3 月之前
父節點
當前提交
430915ed66

+ 1 - 1
SourceCode/IntelligentRailwayCosting/app/executor/task_runner.py

@@ -21,7 +21,7 @@ class TaskRunner:
     _lock = threading.Lock()
     _lock = threading.Lock()
     _task_processor = None
     _task_processor = None
     _task_empty_wait_count = 0
     _task_empty_wait_count = 0
-    _task_empty_wait_max_count = 20
+    _task_empty_wait_max_count = 5
 
 
     @classmethod
     @classmethod
     def run_task(cls, task: ProjectTaskDto):
     def run_task(cls, task: ProjectTaskDto):

+ 17 - 26
SourceCode/IntelligentRailwayCosting/app/flask_app/__init__.py

@@ -1,21 +1,10 @@
-from flask import Flask
-from flask.json.provider import JSONProvider
+import logging, tools.utils as utils
+from flask import Flask, render_template
 from routes.auth import login_manager
 from routes.auth import login_manager
 from routes import register_api
 from routes import register_api
 from views import register_views
 from views import register_views
 
 
 
 
-class CustomJSONProvider(JSONProvider):
-    def default(self, obj):
-        try:
-            return super().default(obj)
-        except TypeError:
-            return str(obj)
-
-    def encode(self, obj):
-        return super().encode(obj).encode("utf-8").decode("unicode_escape")
-
-
 def create_app():
 def create_app():
     app = Flask(__name__, static_folder="../views/static")
     app = Flask(__name__, static_folder="../views/static")
     app.secret_key = "1qwe2iwb3vber"
     app.secret_key = "1qwe2iwb3vber"
@@ -33,24 +22,26 @@ def create_app():
     app.config["MAX_CONTENT_LENGTH"] = 16 * 1024 * 1024
     app.config["MAX_CONTENT_LENGTH"] = 16 * 1024 * 1024
     app.config["PRESERVE_CONTEXT_ON_EXCEPTION"] = False
     app.config["PRESERVE_CONTEXT_ON_EXCEPTION"] = False
     app.config["REQUEST_BODY_LIMIT"] = "16MB"
     app.config["REQUEST_BODY_LIMIT"] = "16MB"
-    app.json_provider_class = CustomJSONProvider
-
-    login_manager.init_app(app)
 
 
     # 注册所有蓝图
     # 注册所有蓝图
     register_api(app)
     register_api(app)
     register_views(app)
     register_views(app)
+    login_manager.init_app(app)
+
+    if utils.get_logger_level() == logging.DEBUG:
+        for rule in app.url_map.iter_rules():
+            # route = {
+            #     "endpoint": rule.endpoint,
+            #     "methods": sorted(rule.methods),
+            #     "path": str(rule),
+            # }
+            print(
+                f"URL [{str(rule)}] ==> endpoint: {rule.endpoint}, methods: {sorted(rule.methods)}"
+            )
+    else:
+        log = logging.getLogger("werkzeug")
+        log.setLevel(logging.ERROR)
 
 
-    # 注册自定义过滤器
-    for rule in app.url_map.iter_rules():
-        route = {
-            "endpoint": rule.endpoint,
-            "methods": sorted(rule.methods),
-            "path": str(rule),
-        }
-        print(
-            f"URL [{str(rule)}] ==> endpoint: {rule.endpoint}, methods: {sorted(rule.methods)}"
-        )
     return app
     return app
 
 
 
 

+ 4 - 2
SourceCode/IntelligentRailwayCosting/app/routes/__init__.py

@@ -1,9 +1,10 @@
 from routes.auth import auth_api
 from routes.auth import auth_api
-from routes.excel_test import test_api
+from routes.excel_test import excel_test_api
 from routes.log import log_api
 from routes.log import log_api
 from routes.project import project_api
 from routes.project import project_api
 from routes.project_task import project_task_api
 from routes.project_task import project_task_api
 from routes.project_quota import project_quota_api
 from routes.project_quota import project_quota_api
+from routes.error import error_api
 
 
 
 
 def register_api(app):
 def register_api(app):
@@ -14,4 +15,5 @@ def register_api(app):
     app.register_blueprint(project_api, url_prefix=f"{url_prefix}/project")
     app.register_blueprint(project_api, url_prefix=f"{url_prefix}/project")
     app.register_blueprint(project_task_api, url_prefix=f"{url_prefix}/task")
     app.register_blueprint(project_task_api, url_prefix=f"{url_prefix}/task")
     app.register_blueprint(project_quota_api, url_prefix=f"{url_prefix}/quota")
     app.register_blueprint(project_quota_api, url_prefix=f"{url_prefix}/quota")
-    app.register_blueprint(test_api, url_prefix=f"{url_prefix}/v1")
+    app.register_blueprint(excel_test_api, url_prefix=f"{url_prefix}/excel_test")
+    app.register_blueprint(error_api, url_prefix=f"{url_prefix}/error")

+ 23 - 0
SourceCode/IntelligentRailwayCosting/app/routes/error.py

@@ -0,0 +1,23 @@
+from flask import Blueprint, redirect, url_for
+from core.api import ResponseBase
+
+error_api = Blueprint('error_api', __name__)
+
+
+@error_api.route('/redirect/<error_message>', methods=['GET'])
+def redirect_to_error(error_message: str):
+    """
+    API错误重定向到全局错误页面
+    """
+    return redirect(url_for('error.error_with_message', error_message=error_message))
+
+
+def handle_api_error(e: Exception, module_name: str = ''):
+    """
+    统一处理API错误
+    :param e: 异常
+    :param module_name: 模块名称
+    :return: API错误响应
+    """
+    error_message = f'{module_name}操作失败:{str(e)}'
+    return ResponseBase.error(error_message)

+ 4 - 4
SourceCode/IntelligentRailwayCosting/app/routes/excel_test.py

@@ -4,13 +4,13 @@ import string
 
 
 from core.dtos import ExcelParseResultDto, ExcelParseResultDataDto
 from core.dtos import ExcelParseResultDto, ExcelParseResultDataDto
 
 
-test_api = Blueprint("test_api", __name__)
+excel_test_api = Blueprint("test_api", __name__)
 
 
 test_data_dic = {}
 test_data_dic = {}
 task_count_dic = {}
 task_count_dic = {}
 
 
 
 
-@test_api.route("/task_submit", methods=["POST"])
+@excel_test_api.route("/task_submit", methods=["POST"])
 def test_submit():
 def test_submit():
     data = request.get_json()
     data = request.get_json()
     # print("[task_submit] 接受数据:",end="")
     # print("[task_submit] 接受数据:",end="")
@@ -21,7 +21,7 @@ def test_submit():
     return jsonify(result)
     return jsonify(result)
 
 
 
 
-@test_api.route("/task_status", methods=["POST"])
+@excel_test_api.route("/task_status", methods=["POST"])
 def test_query():
 def test_query():
     data = request.get_json()
     data = request.get_json()
     # print("[task_status] 接受数据:",end="")
     # print("[task_status] 接受数据:",end="")
@@ -31,7 +31,7 @@ def test_query():
     return jsonify(result)
     return jsonify(result)
 
 
 
 
-@test_api.route("/cancel_task", methods=["POST"])
+@excel_test_api.route("/cancel_task", methods=["POST"])
 def test_cancel():
 def test_cancel():
     data = request.get_json()
     data = request.get_json()
     # print("[cancel_task] 接受数据:",end="")
     # print("[cancel_task] 接受数据:",end="")

+ 6 - 3
SourceCode/IntelligentRailwayCosting/app/routes/project.py

@@ -25,7 +25,8 @@ def get_page_list():
         projects, total_count = project_srvice.get_projects_paginated(page, per_page, keyword, start_date, end_date)
         projects, total_count = project_srvice.get_projects_paginated(page, per_page, keyword, start_date, end_date)
         return TableResponse.success(projects, total_count)
         return TableResponse.success(projects, total_count)
     except Exception as e:
     except Exception as e:
-        return ResponseBase.error(f'获取项目失败:{str(e)}')
+        from routes.error import handle_api_error
+        return handle_api_error(e, '项目列表')
 
 
 @project_api.route('/budget/<project_id>', methods=['POST'])
 @project_api.route('/budget/<project_id>', methods=['POST'])
 @Permission.authorize
 @Permission.authorize
@@ -36,7 +37,8 @@ def get_budget_info(project_id:str):
             return ResponseBase.error(msg)
             return ResponseBase.error(msg)
         return ResponseBase.success(data)
         return ResponseBase.success(data)
     except Exception as e:
     except Exception as e:
-        return ResponseBase.error(f'获取项目概算信息失败:{str(e)}')
+        from routes.error import handle_api_error
+        return handle_api_error(e, '项目概算信息')
 
 
 @project_api.route('/chapter/<project_id>', methods=['POST'])
 @project_api.route('/chapter/<project_id>', methods=['POST'])
 @Permission.authorize
 @Permission.authorize
@@ -48,7 +50,8 @@ def get_chapter_items(project_id:str):
             return ResponseBase.error(msg)
             return ResponseBase.error(msg)
         return ResponseBase.success(data)
         return ResponseBase.success(data)
     except Exception as e:
     except Exception as e:
-        return ResponseBase.error(f'获取项目章节条目失败:{str(e)}')
+        from routes.error import handle_api_error
+        return handle_api_error(e, '项目章节条目')
 
 
 # @project_api.route('/budget-item/<budget_id>/<project_id>', methods=['POST'])
 # @project_api.route('/budget-item/<budget_id>/<project_id>', methods=['POST'])
 # @Permission.authorize
 # @Permission.authorize

+ 6 - 6
SourceCode/IntelligentRailwayCosting/app/stores/__init__.py

@@ -1,10 +1,10 @@
-# from stores.railway_costing_mysql import LogStore, ProjectQuotaStore, ProjectTaskStore
+from stores.railway_costing_mysql import LogStore, ProjectQuotaStore, ProjectTaskStore
 
 
-from stores.railway_costing_sqlserver import (
-    LogStore,
-    ProjectQuotaStore,
-    ProjectTaskStore,
-)
+# from stores.railway_costing_sqlserver import (
+#     LogStore,
+#     ProjectQuotaStore,
+#     ProjectTaskStore,
+# )
 
 
 from .user import UserStore
 from .user import UserStore
 from .project import ProjectStore
 from .project import ProjectStore

+ 1 - 1
SourceCode/IntelligentRailwayCosting/app/stores/project.py

@@ -130,4 +130,4 @@ class ProjectStore:
                 .filter(ProjectModel.project_id == project_id)
                 .filter(ProjectModel.project_id == project_id)
                 .first()
                 .first()
             )
             )
-            return ProjectDto.from_model(data)
+            return ProjectDto.from_model(data) if data else None

+ 10 - 10
SourceCode/IntelligentRailwayCosting/app/tools/db_helper/sqlserver_helper.py

@@ -41,7 +41,7 @@ class SQLServerHelper(DBHelper):
         )
         )
 
 
     def _build_connection_string(
     def _build_connection_string(
-        self, database: str, config: Optional[Dict[str, str]] = None
+            self, database: str, config: Optional[Dict[str, str]] = None
     ) -> str:
     ) -> str:
         """构建连接字符串"""
         """构建连接字符串"""
         conn_config = self._default_config.copy()
         conn_config = self._default_config.copy()
@@ -65,7 +65,7 @@ class SQLServerHelper(DBHelper):
             f"SERVER={conn_config['server']}",
             f"SERVER={conn_config['server']}",
             f"DATABASE={conn_config['database'] if 'database' in conn_config else database}",
             f"DATABASE={conn_config['database'] if 'database' in conn_config else database}",
             "CHARSET=UTF-8",
             "CHARSET=UTF-8",
-            "TDS_Version=7.3",
+            f"TDS_VERSION={conn_config['tds_version'] if 'tds_version' in conn_config else '7.0'}",
         ]
         ]
         conn_parts.extend(auth_params)
         conn_parts.extend(auth_params)
 
 
@@ -76,7 +76,7 @@ class SQLServerHelper(DBHelper):
         return conn_url
         return conn_url
 
 
     def get_engine(
     def get_engine(
-        self, database: str, config: Optional[Dict[str, str]] = None
+            self, database: str, config: Optional[Dict[str, str]] = None
     ) -> Engine:
     ) -> Engine:
         """获取或创建数据库引擎"""
         """获取或创建数据库引擎"""
         conn_str = self._build_connection_string(database, config)
         conn_str = self._build_connection_string(database, config)
@@ -87,7 +87,7 @@ class SQLServerHelper(DBHelper):
         return engine
         return engine
 
 
     def execute_query(
     def execute_query(
-        self, database: str, query: str, params: Optional[Dict[str, Any]] = None
+            self, database: str, query: str, params: Optional[Dict[str, Any]] = None
     ) -> List[Tuple]:
     ) -> List[Tuple]:
         """执行查询并返回结果"""
         """执行查询并返回结果"""
         with self.session_scope(database) as session:
         with self.session_scope(database) as session:
@@ -95,7 +95,7 @@ class SQLServerHelper(DBHelper):
             return [tuple(row) for row in result.fetchall()]
             return [tuple(row) for row in result.fetchall()]
 
 
     def execute_non_query(
     def execute_non_query(
-        self, database: str, query: str, params: Optional[Dict[str, Any]] = None
+            self, database: str, query: str, params: Optional[Dict[str, Any]] = None
     ) -> int:
     ) -> int:
         """执行非查询操作(如INSERT, UPDATE, DELETE)"""
         """执行非查询操作(如INSERT, UPDATE, DELETE)"""
         with self.session_scope(database) as session:
         with self.session_scope(database) as session:
@@ -103,7 +103,7 @@ class SQLServerHelper(DBHelper):
             return result.rowcount
             return result.rowcount
 
 
     def execute_scalar(
     def execute_scalar(
-        self, database: str, query: str, params: Optional[Dict[str, Any]] = None
+            self, database: str, query: str, params: Optional[Dict[str, Any]] = None
     ) -> Any:
     ) -> Any:
         """执行查询并返回第一行第一列的值"""
         """执行查询并返回第一行第一列的值"""
         with self.session_scope(database) as session:
         with self.session_scope(database) as session:
@@ -112,10 +112,10 @@ class SQLServerHelper(DBHelper):
             return row[0] if row else None
             return row[0] if row else None
 
 
     def execute_procedure(
     def execute_procedure(
-        self,
-        database: str,
-        procedure_name: str,
-        params: Optional[Dict[str, Any]] = None,
+            self,
+            database: str,
+            procedure_name: str,
+            params: Optional[Dict[str, Any]] = None,
     ) -> List[Tuple]:
     ) -> List[Tuple]:
         """执行存储过程"""
         """执行存储过程"""
         params = params or {}
         params = params or {}

+ 4 - 0
SourceCode/IntelligentRailwayCosting/app/tools/utils/__init__.py

@@ -20,6 +20,10 @@ def get_logger():
     return LoggerHelper.get_logger()
     return LoggerHelper.get_logger()
 
 
 
 
+def get_logger_level():
+    return LoggerHelper().get_log_level()
+
+
 def clean_log_file(day: int):
 def clean_log_file(day: int):
     """
     """
     清理指定天数之前的日志文件。
     清理指定天数之前的日志文件。

+ 3 - 3
SourceCode/IntelligentRailwayCosting/app/tools/utils/logger_helper.py

@@ -39,7 +39,7 @@ class LoggerHelper:
         """
         """
         初始化日志记录器,包括设置日志级别、创建处理器和格式化器,并将它们组合起来
         初始化日志记录器,包括设置日志级别、创建处理器和格式化器,并将它们组合起来
         """
         """
-        log_level = self._get_log_level()
+        log_level = self.get_log_level()
         self._logger = logging.getLogger("app_logger")
         self._logger = logging.getLogger("app_logger")
         self._logger.setLevel(log_level)
         self._logger.setLevel(log_level)
 
 
@@ -71,7 +71,7 @@ class LoggerHelper:
         self._logger.addHandler(file_handler)
         self._logger.addHandler(file_handler)
         self._logger.addHandler(console_handler)
         self._logger.addHandler(console_handler)
 
 
-    def _get_log_level(self):
+    def get_log_level(self):
         try:
         try:
             # 尝试将字符串转换为 logging 模块中的日志级别常量
             # 尝试将字符串转换为 logging 模块中的日志级别常量
             log_level = getattr(logging, self._log_level_string.upper())
             log_level = getattr(logging, self._log_level_string.upper())
@@ -99,7 +99,7 @@ class LoggerHelper:
             return
             return
         for filename in os.listdir(cls._log_file_path):
         for filename in os.listdir(cls._log_file_path):
             if filename != cls._log_file_name and filename.startswith(
             if filename != cls._log_file_name and filename.startswith(
-                cls._log_file_name
+                    cls._log_file_name
             ):
             ):
                 try:
                 try:
                     file_path = os.path.join(cls._log_file_path, filename)
                     file_path = os.path.join(cls._log_file_path, filename)

+ 16 - 0
SourceCode/IntelligentRailwayCosting/app/views/__init__.py

@@ -1,9 +1,25 @@
 from views.login import auth_bp
 from views.login import auth_bp
 from views.project import project_bp
 from views.project import project_bp
 from views.log import log_bp
 from views.log import log_bp
+from views.error import error_bp
 
 
 
 
 def register_views(app):
 def register_views(app):
+    # 为特定蓝图注册错误处理器
+    def handle_blueprint_exception(e):
+        import traceback
+        from flask import render_template
+
+        error_message = str(e)
+        traceback.print_exc()  # 打印异常堆栈信息到控制台
+        return render_template(
+            "base/error.html", page_active="project", error_message=error_message
+        )
+
+    # 只为主要蓝图注册错误处理
+    for bp in [auth_bp, project_bp, log_bp, error_bp]:
+        bp.errorhandler(Exception)(handle_blueprint_exception)
     app.register_blueprint(auth_bp)
     app.register_blueprint(auth_bp)
     app.register_blueprint(project_bp)
     app.register_blueprint(project_bp)
     app.register_blueprint(log_bp)
     app.register_blueprint(log_bp)
+    app.register_blueprint(error_bp)

+ 24 - 0
SourceCode/IntelligentRailwayCosting/app/views/error.py

@@ -0,0 +1,24 @@
+from flask import Blueprint, render_template
+from core.user_session import Permission
+
+error_bp = Blueprint("error", __name__, template_folder="templates")
+
+
+@error_bp.route("/error", methods=["GET"])
+@Permission.authorize
+def global_error():
+    """
+    全局错误页面
+    """
+    return render_template("base/error.html", page_active="project", error_message=None)
+
+
+@error_bp.route("/error/<error_message>", methods=["GET"])
+@Permission.authorize
+def error_with_message(error_message: str):
+    """
+    带有错误信息的全局错误页面
+    """
+    return render_template(
+        "base/error.html", page_active="project", error_message=error_message
+    )

+ 26 - 5
SourceCode/IntelligentRailwayCosting/app/views/project.py

@@ -1,7 +1,11 @@
-from flask import Blueprint, render_template
+from flask import Blueprint, render_template, redirect, url_for
+
+import tools.utils as utils
+from core.dtos import ProjectDto
 from core.user_session import Permission
 from core.user_session import Permission
 from stores import BudgetStore, ProjectStore, ProjectTaskStore
 from stores import BudgetStore, ProjectStore, ProjectTaskStore
 
 
+logger = utils.get_logger()
 project_bp = Blueprint("project", __name__, template_folder="templates")
 project_bp = Blueprint("project", __name__, template_folder="templates")
 project_store = ProjectStore()
 project_store = ProjectStore()
 budget_store = BudgetStore()
 budget_store = BudgetStore()
@@ -18,7 +22,11 @@ def index():
 @Permission.authorize
 @Permission.authorize
 def budget_info(project_id: str):
 def budget_info(project_id: str):
     project = project_store.get(project_id)
     project = project_store.get(project_id)
-    budgets = budget_store.get_budget_info(project_id)
+    try:
+        budgets = budget_store.get_budget_info(project_id)
+    except Exception as e:
+        logger.error(f"访问项目概算页面失败[{project_id}]: {str(e)}")
+        return redirect(url_for("project.project_error", project_id=project_id))
     return render_template(
     return render_template(
         "project/budget_info.html",
         "project/budget_info.html",
         page_active="project",
         page_active="project",
@@ -27,12 +35,16 @@ def budget_info(project_id: str):
     )
     )
 
 
 
 
-@project_bp.route("/quota_info/<project_id>/<task_id>", methods=["GET"])
+@project_bp.route("/quota_info/<project_id>/<int:task_id>", methods=["GET"])
 @Permission.authorize
 @Permission.authorize
-def quota_info(project_id: str, task_id: str):
+def quota_info(project_id: str, task_id: int):
     project = project_store.get(project_id)
     project = project_store.get(project_id)
-    budgets = budget_store.get_budget_info(project_id)
     task = project_task_store.get_task_dto(task_id)
     task = project_task_store.get_task_dto(task_id)
+    try:
+        budgets = budget_store.get_budget_info(project_id)
+    except Exception as e:
+        logger.error(f"访问定额输入页面失败[{project_id}]: {str(e)}")
+        return redirect(url_for("project.project_error", project_id=project_id))
     return render_template(
     return render_template(
         "project/quota_info.html",
         "project/quota_info.html",
         page_active="project",
         page_active="project",
@@ -40,3 +52,12 @@ def quota_info(project_id: str, task_id: str):
         budgets=budgets,
         budgets=budgets,
         task=task,
         task=task,
     )
     )
+
+
+@project_bp.route("/project_error/<project_id>", methods=["GET"])
+@Permission.authorize
+def project_error(project_id: str):
+    project = project_store.get(project_id)
+    if not project:
+        project = ProjectDto(project_id=project_id, project_name="")
+    return render_template("project/error.html", page_active="project", project=project)

+ 27 - 0
SourceCode/IntelligentRailwayCosting/app/views/templates/base/error.html

@@ -0,0 +1,27 @@
+{% extends "base/layout.html" %} {% block title %}系统错误{% endblock %} {% block page_head_plugins %}
+{% endblock %} {% block page_content %}
+
+<div class="app-body-header h-50px" id="breadcrumb_header">
+    <h3>系统错误</h3>
+    <ol class="breadcrumb breadcrumb-dot text-muted fs-6 fw-semibold ms-5">
+        <li class="breadcrumb-item"><a href="{{ url_for('project.index') }}" class="">首页</a></li>
+        <li class="breadcrumb-item text-muted">系统错误</li>
+    </ol>
+</div>
+<div class="container p-5 fw-bold">
+    <div class="alert alert-danger">
+        <h4 class="alert-heading">发生错误!</h4>
+        <p>系统处理您的请求时发生了错误。</p>
+        {% if error_message %}
+        <hr>
+        <p class="mb-0">错误详情: <span class="text-danger">{{ error_message }}</span></p>
+        {% endif %}
+    </div>
+    <a href="{{ url_for('project.index') }}" class="btn btn-primary mt-5">返回首页</a>
+</div>
+
+{% endblock %} {% block page_scripts %}
+<script>
+    ChangeHeadMenu('#{{page_active}}_menu')
+</script>
+{% endblock %}

+ 31 - 0
SourceCode/IntelligentRailwayCosting/app/views/templates/project/error.html

@@ -0,0 +1,31 @@
+{% extends "base/layout.html" %} {% block title %}项目异常{% endblock %} {% block page_head_plugins %}{% endblock %} {%
+block page_content %}
+
+<div class="app-body-header h-50px" id="breadcrumb_header">
+    <h3>项目异常</h3>
+    <ol class="breadcrumb breadcrumb-dot text-muted fs-6 fw-semibold ms-5">
+        <li class="breadcrumb-item"><a href="{{ url_for('project.index') }}" class="">项目管理</a></li>
+        <li class="breadcrumb-item text-muted">项目异常</li>
+    </ol>
+</div>
+<div class="container p-5 fw-bold">
+    <div class="alert alert-danger">
+        <h4 class="alert-heading">项目异常!</h4>
+        <p>系统查询项目数据库信息异常</p>
+        <hr>
+        <p class="mb-0">项目ID:<span class="text-primary">{{project.project_id}}</span></p>
+        {% if project.project_name %}
+        <p class="mb-0">项目名称:<span class="text-primary">{{project.project_name}}</span></p>
+        {% else %}
+        <p class="mb-0">项目状态:<span class="text-danger">未查询到项目</span></p>
+        {% endif %}
+    </div>
+    <a href="{{ url_for('project.index') }}" class="btn btn-primary mt-5">返回首页</a>
+</div>
+
+{% endblock %} {% block page_scripts %}
+<script src="{{ url_for('static', filename='base/plugins/jstree/jstree.bundle.js') }}"></script>
+<script>
+    ChangeHeadMenu('#{{page_active}}_menu')
+</script>
+{% endblock %}