Ver código fonte

update 重构路由及页面,登录

YueYunyun 6 meses atrás
pai
commit
84c21efa28
40 arquivos alterados com 874 adições e 176 exclusões
  1. 24 0
      SourceCode/DataMiddleware/app/__init__.py
  2. 3 3
      SourceCode/DataMiddleware/app/main.py
  3. 10 0
      SourceCode/DataMiddleware/app/models/user_data.py
  4. 18 0
      SourceCode/DataMiddleware/app/routes/__init__.py
  5. 57 0
      SourceCode/DataMiddleware/app/routes/route_auth.py
  6. 92 0
      SourceCode/DataMiddleware/app/routes/route_base.py
  7. 4 0
      SourceCode/DataMiddleware/app/routes/route_log.py
  8. 4 0
      SourceCode/DataMiddleware/app/routes/route_project.py
  9. 3 0
      SourceCode/DataMiddleware/app/routes/route_sub_project.py
  10. 3 0
      SourceCode/DataMiddleware/app/routes/route_sub_project_item.py
  11. 4 0
      SourceCode/DataMiddleware/app/routes/route_user.py
  12. 3 3
      SourceCode/DataMiddleware/app/services/service_user.py
  13. 25 26
      SourceCode/DataMiddleware/app/stores/project_store.py
  14. 20 20
      SourceCode/DataMiddleware/app/ui/__init__.py
  15. 0 104
      SourceCode/DataMiddleware/app/ui_1/templates/auth/login.html
  16. 0 19
      SourceCode/DataMiddleware/app/ui_1/templates/base/base.html
  17. 0 0
      SourceCode/DataMiddleware/app/user_session/__init__.py
  18. 80 0
      SourceCode/DataMiddleware/app/user_session/current_user.py
  19. 124 0
      SourceCode/DataMiddleware/app/user_session/user_session.py
  20. 8 0
      SourceCode/DataMiddleware/app/views/__init__.py
  21. 9 0
      SourceCode/DataMiddleware/app/views/log.py
  22. 14 0
      SourceCode/DataMiddleware/app/views/login.py
  23. 18 0
      SourceCode/DataMiddleware/app/views/project.py
  24. 0 0
      SourceCode/DataMiddleware/app/views/static/bootstrap/bootstrap.css
  25. 0 0
      SourceCode/DataMiddleware/app/views/static/bootstrap/bootstrap.js
  26. 0 0
      SourceCode/DataMiddleware/app/views/static/jquery/jquery.js
  27. 36 0
      SourceCode/DataMiddleware/app/views/static/layout.css
  28. 33 0
      SourceCode/DataMiddleware/app/views/static/login/index.css
  29. 59 0
      SourceCode/DataMiddleware/app/views/static/login/index.js
  30. 10 0
      SourceCode/DataMiddleware/app/views/static/styles.css
  31. 0 0
      SourceCode/DataMiddleware/app/views/static/utils.js
  32. 27 0
      SourceCode/DataMiddleware/app/views/templates/auth/login.html
  33. 18 0
      SourceCode/DataMiddleware/app/views/templates/base/base.html
  34. 132 0
      SourceCode/DataMiddleware/app/views/templates/base/layout.html
  35. 0 0
      SourceCode/DataMiddleware/app/views/templates/log/index.html
  36. 10 0
      SourceCode/DataMiddleware/app/views/templates/project/index.html
  37. 0 0
      SourceCode/DataMiddleware/app/views/templates/project/sub_project.html
  38. 0 0
      SourceCode/DataMiddleware/app/views/templates/project/sub_project_item.html
  39. 24 0
      SourceCode/DataMiddleware/app/views/views_base.py
  40. 2 1
      SourceCode/DataMiddleware/requirements.txt

+ 24 - 0
SourceCode/DataMiddleware/app/__init__.py

@@ -0,0 +1,24 @@
+from flask import Flask
+from routes.route_auth import login_manager
+from routes import register_api
+from views import register_views
+
+def create_app():
+    app = Flask(__name__, static_folder='views/static')
+    app.secret_key = "1qwe2iwb3vber"
+    
+    login_manager.init_app(app)
+    
+    # 注册所有蓝图
+    register_api(app)
+    register_views(app)
+    
+    # 注册自定义过滤器
+    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

+ 3 - 3
SourceCode/DataMiddleware/app/main.py

@@ -1,5 +1,5 @@
-import threading
-import utils, ui, data_process
+import utils
+from  app import create_app
 
 logger = utils.get_logger()
 
@@ -7,7 +7,7 @@ def main():
     logger.info("程序启动中...")
     # thread = threading.Thread(target=debug_test)
     # thread.start()
-    app = ui.create_app()
+    app = create_app()
     app.run(host='0.0.0.0',port=5123)  # 指定HTTP端口为5123
 
 def debug_test():

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

@@ -10,6 +10,7 @@ class UserModel:
                  phone: Optional[str] = None,
                  name: Optional[str] = None,
                  user_id: int = None,
+                 roles=None,
                  last_login: datetime = None,
                  created_at: datetime = None,
                  create_by: Optional[str] = None,
@@ -17,12 +18,19 @@ class UserModel:
                  updated_at: datetime = None,
                  delete_by: Optional[str] = None,
                  deleted_at: datetime = None):
+        if roles is None:
+            roles = []
         self.id = user_id
         self.username = username
         self.email = email
         self.phone = phone
         self.name = name
         self._password = None
+        if not roles:
+            roles = []
+        if isinstance(roles, str):
+            roles = roles.split(',')
+        self.roles=roles
         self.last_login = last_login
         self.created_at = created_at or datetime.now()
         self.create_by = create_by
@@ -55,6 +63,7 @@ class UserModel:
     def update_last_login(self):
         self.last_login = datetime.now()
 
+
     def to_dict(self) -> dict:
         return {
             'id': self.id,
@@ -62,4 +71,5 @@ class UserModel:
             'name': self.name,
             'email': self.email,
             'phone': self.phone,
+            'roles':[]
         }

+ 18 - 0
SourceCode/DataMiddleware/app/routes/__init__.py

@@ -0,0 +1,18 @@
+from routes.route_auth import auth_api
+from routes.route_user import user_api
+from routes.route_log import log_api
+from routes.route_project import project_api
+from routes.route_sub_project import sub_project_api
+from routes.route_sub_project_item import sub_project_item_api
+
+def register_api(app):
+    url_prefix = '/api'
+    # API蓝图注册
+    app.register_blueprint(auth_api, url_prefix=f"{url_prefix}/auth")
+    app.register_blueprint(user_api, url_prefix=f"{url_prefix}/user")
+    app.register_blueprint(log_api, url_prefix=f"{url_prefix}/log")
+    app.register_blueprint(project_api, url_prefix=f"{url_prefix}/project")
+    app.register_blueprint(sub_project_api, url_prefix=f"{url_prefix}/sub_project")
+    app.register_blueprint(sub_project_item_api, url_prefix=f"{url_prefix}/sub_item")
+    
+   

+ 57 - 0
SourceCode/DataMiddleware/app/routes/route_auth.py

@@ -0,0 +1,57 @@
+from flask import Blueprint, request, jsonify, session
+from flask_login import LoginManager, login_user, logout_user
+from services.service_user import UserService
+from services.log_helper import LogHelper, OperationType, OperationModule
+from routes.route_base import ResponseBase
+from user_session.user_session import UserSession
+from user_session.current_user import CurrentUser
+
+UserService = UserService()
+
+login_manager = LoginManager()
+login_manager.login_view = 'auth_api.login'
+login_manager.login_message = '请先登录'
+
+@login_manager.user_loader
+def load_user(user_id):
+    user,msg = UserService.get_user_by_id(user_id)
+    if user:
+        current_user = UserSession.get_current_user()
+        return current_user
+    return None
+
+auth_api = Blueprint('auth_api', __name__)
+
+@auth_api.route('/login', methods=['POST'])
+def login():
+    keyword = request.json.get('keyword')
+    password = request.json.get('password')
+
+    # 调用UserService进行用户验证
+    user, message = UserService.login(keyword, password)
+    
+    if user:
+        # 创建CurrentUser对象并登录
+        current_user = CurrentUser(user_id=user.id)
+        login_user(current_user)
+        UserSession.set_user(user.id, user.username, user.name, user.roles)
+        # 记录日志
+        LogHelper.log_success(OperationType.LOGIN, OperationModule.ACCOUNT, '用户登录成功', username=user.username)
+
+        return jsonify(ResponseBase.success({
+            'user_id': user.id,
+            'username': user.username
+        }))
+    else:
+        # 登录失败,返回错误信息
+        return jsonify(ResponseBase.error(message))
+
+@auth_api.route('/logout', methods=['POST'])
+def logout():
+    username = session.get('username')
+    if username:
+        logout_user()
+        UserSession.clear_user()
+        LogHelper.log_success(OperationType.LOGIN, OperationModule.ACCOUNT, '用户退出登录', username=username)
+        return jsonify(ResponseBase.success())
+    return jsonify(ResponseBase.error('用户未登录'))

+ 92 - 0
SourceCode/DataMiddleware/app/routes/route_base.py

@@ -0,0 +1,92 @@
+from typing import Dict, Any, Optional, List
+from flask import jsonify
+
+class ResponseBase:
+    """统一的API响应结构"""
+    @staticmethod
+    def success(data: Optional[Any] = None, message: str = "操作成功") -> Dict:
+        """成功响应
+        Args:
+            data: 响应数据
+            message: 响应消息
+        Returns:
+            Dict: 统一的成功响应格式
+        """
+        response = {
+            "success": True,
+            "code": 200,
+            "message": message
+        }
+        if data is not None:
+            response["data"] = data
+        return response
+
+    @staticmethod
+    def error(message: str = "操作失败", code: int = 400, data: Optional[Any] = None) -> Dict:
+        """错误响应
+        Args:
+            message: 错误消息
+            code: 错误码
+            data: 错误详细信息
+        Returns:
+            Dict: 统一的错误响应格式
+        """
+        response = {
+            "success": False,
+            "code": code,
+            "message": message
+        }
+        if data is not None:
+            response["data"] = data
+        return response
+
+    @staticmethod
+    def json_response(success: bool = True, code: int = 200, message: str = "", data: Optional[Any] = None) -> Dict:
+        """自定义响应
+        Args:
+            success: 是否成功
+            code: 状态码
+            message: 响应消息
+            data: 响应数据
+        Returns:
+            Dict: 统一的响应格式
+        """
+        response = {
+            "success": success,
+            "code": code,
+            "message": message
+        }
+        if data is not None:
+            response["data"] = data
+        return response
+
+class TableResponse(ResponseBase):
+    """表格数据响应结构"""
+    @staticmethod
+    def success(rows: List[Dict] = None, total: int = 0, message: str = "操作成功") -> Dict:
+        """表格数据成功响应
+        Args:
+            rows: 表格数据行列表
+            total: 数据总条数
+            message: 响应消息
+        Returns:
+            Dict: 统一的表格数据响应格式
+        """
+        return ResponseBase.success(
+            data={
+                "rows": rows or [],
+                "total": total
+            },
+            message=message
+        )
+
+    @staticmethod
+    def error(message: str = "操作失败", code: int = 400) -> Dict:
+        """表格数据错误响应
+        Args:
+            message: 错误消息
+            code: 错误码
+        Returns:
+            Dict: 统一的错误响应格式
+        """
+        return ResponseBase.error(message=message, code=code)

+ 4 - 0
SourceCode/DataMiddleware/app/routes/route_log.py

@@ -0,0 +1,4 @@
+from flask import Blueprint, request, jsonify, session
+
+
+log_api = Blueprint('log_api', __name__)

+ 4 - 0
SourceCode/DataMiddleware/app/routes/route_project.py

@@ -0,0 +1,4 @@
+from flask import Blueprint, request, jsonify, session
+
+
+project_api = Blueprint('project_api', __name__)

+ 3 - 0
SourceCode/DataMiddleware/app/routes/route_sub_project.py

@@ -0,0 +1,3 @@
+from flask import Blueprint, request, jsonify, session
+
+sub_project_api = Blueprint('sub_project_api', __name__)

+ 3 - 0
SourceCode/DataMiddleware/app/routes/route_sub_project_item.py

@@ -0,0 +1,3 @@
+from flask import Blueprint, request, jsonify, session
+
+sub_project_item_api = Blueprint('sub_project_item_api', __name__)

+ 4 - 0
SourceCode/DataMiddleware/app/routes/route_user.py

@@ -0,0 +1,4 @@
+from flask import Blueprint, request, jsonify, session
+
+
+user_api = Blueprint('user_api', __name__)

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

@@ -101,17 +101,17 @@ class UserService:
         except Exception as e:
             return False, f"删除失败:{str(e)}"
 
-    def get_user_by_id(self, user_id: int) -> Tuple[Optional[dict], str]:
+    def get_user_by_id(self, user_id: int) -> Tuple[Optional[UserModel], str]:
         user = self._user_store.query_user_by_id(user_id)
         if not user:
             return None, "用户不存在"
-        return user.to_dict(), "获取成功"
+        return user, "获取成功"
 
     def get_users_paginated(self, page: int = 1, per_page: int = 10,
                           keyword: Optional[str] = None) -> Tuple[list, int, str]:
         try:
             users, total = self._user_store.query_user_all_paginated(page, per_page, keyword)
-            return [user.to_dict() for user in users], total, "获取成功"
+            return [user for user in users], total, "获取成功"
         except Exception as e:
             return [], 0, f"获取失败:{str(e)}"
 

+ 25 - 26
SourceCode/DataMiddleware/app/stores/project_store.py

@@ -1,10 +1,8 @@
 from datetime import datetime
 
-from flask import session
 from utils.mysql_helper import MySQLHelper
-
 from models.project_data import ProjectModel, SubProjectModel, SubProjectItemModel
-
+from user_session.user_session import UserSession
 
 class ProjectStore:
 
@@ -12,15 +10,16 @@ class ProjectStore:
         self._db_helper = MySQLHelper()
 
     @staticmethod
-    def _get_current_user():
-        return session.get('username', 'system')
+    def _get_current_user_name():
+        current_user = UserSession.get_current_user()
+        return current_user.username
 
     def filter_data(self, sql:str, params:tuple):
-        current_user = self._get_current_user()
-        if current_user == 'system' or current_user == 'admin':
+        current_user_name = self._get_current_user_name()
+        if current_user_name == 'system' or current_user_name == 'admin':
             return sql, params
         sql += " AND create_by=%s"
-        params += (current_user,)
+        params += (current_user_name,)
         return sql, params
 
     def query_project_all_paginated(self, page: int, per_page: int, keyword: str = None):
@@ -81,29 +80,29 @@ class ProjectStore:
             )
 
     def insert_project(self, project_data: ProjectModel):
-        current_user = self._get_current_user()
+        current_user_name = self._get_current_user_name()
         sql = "INSERT INTO project (project_name,description,create_by,created_at,update_by,updated_at) VALUES (%s,%s,%s,%s,%s,%s)"
-        params = (project_data.project_name,project_data.description, current_user, datetime.now(), current_user, datetime.now())
+        params = (project_data.project_name,project_data.description, current_user_name, datetime.now(), current_user_name, datetime.now())
        
         with self._db_helper:
             self._db_helper.execute_non_query(sql, params)
 
     def update_project(self, project_data: ProjectModel):
-        current_user = self._get_current_user()
+        current_user_name = self._get_current_user_name()
         sql = "UPDATE project SET project_name=%s,description=%s,update_by=%s,updated_at=%s WHERE id=%s"
-        params = (project_data.project_name, project_data.description, current_user, datetime.now(), project_data.id)
+        params = (project_data.project_name, project_data.description, current_user_name, datetime.now(), project_data.id)
         with self._db_helper:
             self._db_helper.execute_non_query(sql, params)
 
     def delete_project(self, project_id: int):
-        current_user = self._get_current_user()
+        current_user_name = self._get_current_user_name()
         sql = "UPDATE project SET is_del=1,project.delete_by=%s,deleted_at=%s WHERE id=%s"
-        params = (current_user, datetime.now(), project_id)
+        params = (current_user_name, datetime.now(), project_id)
         with self._db_helper:
             self._db_helper.execute_non_query(sql, params)
 
     def insert_sub_project(self, sub_project: SubProjectModel) -> int:
-        current_user = self._get_current_user()
+        current_user_name = self._get_current_user_name()
         sql = "INSERT INTO sub_project (project_id,sub_project_name,work_catalog,work_content,standard_version,file_paths,created_at,create_by) VALUES (%s,%s,%s,%s,%s,%s,%s,%s)"
         params = (
             sub_project.project_id,
@@ -113,7 +112,7 @@ class ProjectStore:
             sub_project.standard_version,
             sub_project.file_paths,
             datetime.now(),
-            current_user,
+            current_user_name,
         )
         with self._db_helper:
             new_id = self._db_helper.execute_non_query(sql, params)
@@ -121,7 +120,7 @@ class ProjectStore:
         return new_id
 
     def update_sub_project(self, sub_project: SubProjectModel):
-        current_user = sub_project.create_by or self._get_current_user()
+        current_user_name = sub_project.create_by or self._get_current_user_name()
         sql = "UPDATE sub_project SET sub_project_name = %s, work_catalog = %s, work_content = %s, standard_version = %s ,status = %s,updated_at = %s,update_by = %s WHERE id = %s"
         params = (
             sub_project.sub_project_name,
@@ -130,16 +129,16 @@ class ProjectStore:
             sub_project.standard_version,
             sub_project.status,
             datetime.now(),
-            current_user,
+            current_user_name,
             sub_project.id,
         )
         with self._db_helper:
             self._db_helper.execute_non_query(sql, params)
 
     def delete_sub_project(self, sub_project_id: int) -> bool:
-        current_user = self._get_current_user()
+        current_user_name = self._get_current_user_name()
         sql = "UPDATE sub_project SET is_del=1,delete_by=%s,deleted_at=%s WHERE id = %s"
-        params = (current_user, datetime.now(), sub_project_id,)
+        params = (current_user_name, datetime.now(), sub_project_id,)
         with self._db_helper:
             self._db_helper.execute_non_query(sql, params)
             return True
@@ -391,7 +390,7 @@ class ProjectStore:
             )
             return data
     def insert_sub_project_item(self, project_item: SubProjectItemModel):
-        current_user = self._get_current_user()
+        current_user_name = self._get_current_user_name()
         sql = "INSERT INTO sub_project_item (project_id,sub_project_id,device_name,device_model,device_unit,device_count,standard_version,standard_no,process_status,send_status,create_by,created_at) VALUES (%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s)"
         params = (
             project_item.project_id,
@@ -404,13 +403,13 @@ class ProjectStore:
             project_item.standard_no,
             project_item.process_status,
             project_item.send_status,
-            current_user,
+            current_user_name,
             datetime.now(),
         )
         with self._db_helper:
            return self._db_helper.execute_non_query(sql, params)
     def update_sub_project_item(self, project_item: SubProjectItemModel) -> bool:
-        current_user = self._get_current_user()
+        current_user_name = self._get_current_user_name()
         sql = "UPDATE sub_project_item SET device_name= %s,device_model= %s,device_unit= %s,device_count= %s,standard_no = %s,update_by,updated_at = %s WHERE id = %s"
         params = (
             project_item.device_name,
@@ -418,7 +417,7 @@ class ProjectStore:
             project_item.device_unit,
             project_item.device_count,
             project_item.standard_no,
-            current_user,
+            current_user_name,
             datetime.now(),
             project_item.id
         )
@@ -426,9 +425,9 @@ class ProjectStore:
             self._db_helper.execute_non_query(sql, params)
             return True
     def delete_sub_project_item_by_id(self, item_id: int):
-        current_user = self._get_current_user()
+        current_user_name = self._get_current_user_name()
         sql = "Update sub_project_item SET is_del=1,delete_by=%s,deleted_at=%s WHERE id = %s"
-        params = (current_user, datetime.now(),item_id )
+        params = (current_user_name, datetime.now(),item_id )
         with self._db_helper:
             self._db_helper.execute_non_query(sql, params)
             return True

+ 20 - 20
SourceCode/DataMiddleware/app/ui/__init__.py

@@ -1,25 +1,25 @@
-import os,utils
-from flask import Flask
+# import os,utils
+# from flask import Flask
 
-def basename_filter(path):
-    return os.path.basename(path)
+# def basename_filter(path):
+#     return os.path.basename(path)
 
-def create_app():
-    # current_dir = os.path.dirname(os.path.abspath(__file__))
-    # template_path = os.path.join(current_dir, 'templates')
-    # web_app = Flask(__name__, template_folder=template_path)
-    web_app = Flask(__name__)
-    # 注册蓝图或其他初始化操作
-    from .views_project import project_bp
-    from .views_auth import auth_bp
-    from .views_log import log_bp
-    web_app.secret_key="1qwe2iwb3vber"
+# def create_app():
+#     # current_dir = os.path.dirname(os.path.abspath(__file__))
+#     # template_path = os.path.join(current_dir, 'templates')
+#     # web_app = Flask(__name__, template_folder=template_path)
+#     web_app = Flask(__name__)
+#     # 注册蓝图或其他初始化操作
+#     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.register_blueprint(project_bp)
+#     web_app.register_blueprint(auth_bp)
+#     web_app.register_blueprint(log_bp)
 
-    # 注册自定义过滤器
-    web_app.jinja_env.filters['basename'] = basename_filter
+#     # 注册自定义过滤器
+#     web_app.jinja_env.filters['basename'] = basename_filter
 
-    return web_app
+#     return web_app

+ 0 - 104
SourceCode/DataMiddleware/app/ui_1/templates/auth/login.html

@@ -1,104 +0,0 @@
-<!DOCTYPE html>
-<html lang="zh">
-<head>
-    <meta charset="UTF-8">
-    <meta name="viewport" content="width=device-width, initial-scale=1.0">
-    <title>登录 - 数据中台</title>
-    <link href="{{ url_for('static', filename='bootstrap/bootstrap.css') }}" rel="stylesheet">
-    <style>
-        body {
-            background-color: #f8f9fa;
-            height: 100vh;
-            display: flex;
-            align-items: center;
-            justify-content: center;
-        }
-        .container {
-            width: 100%;
-            display: flex;
-            justify-content: center;
-        }
-        .login-container {
-            background-color: white;
-            padding: 2rem;
-            border-radius: 10px;
-            box-shadow: 0 0 20px rgba(0, 0, 0, 0.1);
-            width: 100%;
-            max-width: 400px;
-        }
-        .login-title {
-            text-align: center;
-            color: #333;
-            margin-bottom: 2rem;
-        }
-        .form-floating {
-            margin-bottom: 1rem;
-        }
-        .btn-login {
-            width: 100%;
-            padding: 0.8rem;
-            font-size: 1.1rem;
-            background-color: #0d6efd;
-            border: none;
-        }
-        .btn-login:hover {
-            background-color: #0b5ed7;
-        }
-        .alert {
-            margin-bottom: 1rem;
-        }
-    </style>
-</head>
-<body>
-    <div class="container">
-        <div class="login-container">
-            <h2 class="login-title">数据转换推送系统</h2>
-            {% with messages = get_flashed_messages(with_categories=true) %}
-                {% if messages %}
-                    {% for category, message in messages %}
-                        <div class="alert alert-{{ category if category != 'error' else 'danger' }} alert-dismissible fade show" role="alert">
-                            {{ message }}
-                            <button type="button" class="btn-close" data-bs-dismiss="alert" aria-label="Close"></button>
-                        </div>
-                    {% endfor %}
-                {% endif %}
-            {% endwith %}
-            <form method="POST" action="{{ url_for('auth.login') }}" class="needs-validation" novalidate>
-                <div class="form-floating">
-                    <input type="text" class="form-control" id="keyword" name="keyword" placeholder="用户名/邮箱/手机号" required>
-                    <label for="keyword">用户名/邮箱/手机号</label>
-                    <div class="invalid-feedback">
-                        请输入用户名、邮箱或手机号
-                    </div>
-                </div>
-                <div class="form-floating">
-                    <input type="password" class="form-control" id="password" name="password" placeholder="密码" required>
-                    <label for="password">密码</label>
-                    <div class="invalid-feedback">
-                        请输入密码
-                    </div>
-                </div>
-                <button type="submit" class="btn btn-primary btn-login mt-3">登录</button>
-            </form>
-        </div>
-    </div>
-
-    <script src="{{ url_for('static', filename='bootstrap/bootstrap.js') }}"></script>
-    <script>
-        // 表单验证
-        (function () {
-            'use strict'
-            var forms = document.querySelectorAll('.needs-validation')
-            Array.prototype.slice.call(forms).forEach(function (form) {
-                form.addEventListener('submit', function (event) {
-                    if (!form.checkValidity()) {
-                        event.preventDefault()
-                        event.stopPropagation()
-                    }
-                    form.classList.add('was-validated')
-                }, false)
-            })
-        })()
-    </script>
-</body>
-</html>

+ 0 - 19
SourceCode/DataMiddleware/app/ui_1/templates/base/base.html

@@ -1,19 +0,0 @@
-   <!DOCTYPE html>
-   <html lang="zh">
-   <head>
-       <meta charset="UTF-8">
-       <title>{% block title %}{% endblock %}-IWB</title>
-       <link rel="stylesheet" href="{{ url_for('static', filename='styles.css') }}">
-       <link rel="stylesheet" href="{{ url_for('static', filename='bootstrap/bootstrap.css') }}"></link>
-       {% block head %}{% endblock %}
-   </head>
-   <body>
-       <div class="container">
-           {% block content %}{% endblock %}
-       </div>
-       <script src="{{ url_for('static', filename='jquery.js') }}"></script>
-       <script src="{{ url_for('static', filename='bootstrap/bootstrap.js') }}"></script>
-       <script src="{{ url_for('static', filename='utils.js') }}"></script>
-       {% block scripts %}{% endblock %}
-   </body>
-   </html>

+ 0 - 0
SourceCode/DataMiddleware/app/ui_1/__init__.py → SourceCode/DataMiddleware/app/user_session/__init__.py


+ 80 - 0
SourceCode/DataMiddleware/app/user_session/current_user.py

@@ -0,0 +1,80 @@
+from dataclasses import dataclass
+from typing import Optional, List
+from flask_login import UserMixin
+
+@dataclass
+class CurrentUser(UserMixin):
+    """当前用户信息结构体"""
+    user_id: Optional[int] = None
+    username: Optional[str] = None
+    name: Optional[str] = None
+    roles: List[str] = None
+
+    def __post_init__(self):
+        """初始化角色列表"""
+        if self.roles is None:
+            self.roles = []
+
+    def get_id(self):
+        """实现Flask-Login要求的get_id方法"""
+        return str(self.user_id) if self.user_id else None
+
+    @property
+    def is_authenticated(self) -> bool:
+        """检查用户是否已认证
+
+        Returns:
+            bool: 如果用户已认证返回True,否则返回False
+        """
+        return self.user_id is not None and self.username is not None
+    
+    @property
+    def is_admin(self) -> bool:
+        """检查用户是否为管理员
+
+        Returns:
+            bool: 如果用户是管理员返回True,否则返回False
+        """
+        return self.username == 'admin' or 'admin' in self.roles
+    
+    @property
+    def is_super_admin(self) -> bool:
+        """检查用户是否为超级管理员
+
+        Returns:
+            bool: 如果用户是超级管理员返回True,否则返回False
+        """
+        return 'super_admin' in self.roles
+    
+    def has_role(self, role: str) -> bool:
+        """检查用户是否拥有指定角色
+
+        Args:
+            role (str): 角色名称
+
+        Returns:
+            bool: 如果用户拥有指定角色返回True,否则返回False
+        """
+        return role in self.roles
+    
+    def has_any_role(self, roles: List[str]) -> bool:
+        """检查用户是否拥有指定角色列表中的任意一个角色
+
+        Args:
+            roles (List[str]): 角色名称列表
+
+        Returns:
+            bool: 如果用户拥有指定角色列表中的任意一个角色返回True,否则返回False
+        """
+        return any(role in self.roles for role in roles)
+    
+    def has_all_roles(self, roles: List[str]) -> bool:
+        """检查用户是否拥有指定角色列表中的所有角色
+
+        Args:
+            roles (List[str]): 角色名称列表
+
+        Returns:
+            bool: 如果用户拥有指定角色列表中的所有角色返回True,否则返回False
+        """
+        return all(role in self.roles for role in roles)

+ 124 - 0
SourceCode/DataMiddleware/app/user_session/user_session.py

@@ -0,0 +1,124 @@
+from flask import session
+from typing import Optional, Tuple
+from .current_user import CurrentUser
+
+
+class UserSession:
+    """用户会话管理类"""
+
+    @staticmethod
+    def set_user(user_id: int, username: str, name: str, roles: Optional[list] = None) -> None:
+        """设置用户登录状态
+
+        Args:
+            user_id (int): 用户ID
+            username (str): 用户名
+            name (str): 用户姓名
+            roles (Optional[list], optional): 用户角色列表. Defaults to None.
+        """
+        session['user_id'] = user_id
+        session['username'] = username
+        session['name'] = name
+        session['roles'] = roles or []
+
+    @staticmethod
+    def get_current_user() -> CurrentUser:
+        """获取当前登录用户信息
+
+        Returns:
+            CurrentUser: 返回当前用户信息结构体
+        """
+        return CurrentUser(
+            user_id=session.get('user_id'),
+            username=session.get('username'),
+            name=session.get('name'),
+            roles=session.get('roles', [])
+        )
+    
+    @staticmethod
+    def get_current_username() -> Optional[str]:
+        """获取当前登录用户名
+
+        Returns:
+            Optional[str]: 返回用户名,未登录则返回None
+        """
+        return session.get('username')
+    
+    @staticmethod
+    def get_current_user_id() -> Optional[int]:
+        """获取当前登录用户ID
+
+        Returns:
+            Optional[int]: 返回用户ID,未登录则返回None
+        """
+        return session.get('user_id')
+    
+    @staticmethod
+    def get_current_name() -> Optional[str]:
+        """获取当前登录用户姓名
+
+        Returns:
+            Optional[str]: 返回用户姓名,未登录则返回None
+        """
+        return session.get('name')
+
+    
+    @staticmethod
+    def clear_user() -> None:
+        """清除用户登录状态"""
+        session.pop('user_id', None)
+        session.pop('username', None)
+        session.pop('name', None)
+        session.pop('roles', None)
+    
+    @staticmethod
+    def is_logged_in() -> bool:
+        """检查用户是否已登录
+
+        Returns:
+            bool: 如果用户已登录返回True,否则返回False
+        """
+        return 'user_id' in session and 'username' in session
+    
+    @staticmethod
+    def is_admin() -> bool:
+        """检查当前用户是否为管理员
+
+        Returns:
+            bool: 如果当前用户是管理员返回True,否则返回False
+        """
+        return session.get('username') == 'admin'
+    
+    @staticmethod
+    def get_current_roles() -> list:
+        """获取当前用户角色列表
+
+        Returns:
+            list: 返回用户角色列表,未登录则返回空列表
+        """
+        return session.get('roles', [])
+
+    @staticmethod
+    def has_role(role: str) -> bool:
+        """检查当前用户是否拥有指定角色
+
+        Args:
+            role (str): 角色名称
+
+        Returns:
+            bool: 如果用户拥有指定角色返回True,否则返回False
+        """
+        return role in session.get('roles', [])
+
+    @staticmethod
+    def has_all_roles(roles: list) -> bool:
+        """检查当前用户是否拥有指定角色列表中的所有角色
+
+        Args:
+            roles (list): 角色名称列表
+
+        Returns:
+            bool: 如果用户拥有指定角色列表中的所有角色返回True,否则返回False
+        """
+        user_roles = session.get('roles', [])
+        return all(role in user_roles for role in roles)

+ 8 - 0
SourceCode/DataMiddleware/app/views/__init__.py

@@ -0,0 +1,8 @@
+from views.login import auth_bp
+from views.log import log_bp
+from views.project import project_bp
+
+def register_views(app):
+    app.register_blueprint(auth_bp)
+    app.register_blueprint(log_bp)
+    app.register_blueprint(project_bp)

+ 9 - 0
SourceCode/DataMiddleware/app/views/log.py

@@ -0,0 +1,9 @@
+from flask import Blueprint, render_template
+from views.views_base import BaseView
+
+log_bp = Blueprint('log', __name__, template_folder='templates')
+
+@log_bp.route('/log', methods=['GET'])
+@BaseView.login_required
+def login():
+    return render_template('log/index.html')

+ 14 - 0
SourceCode/DataMiddleware/app/views/login.py

@@ -0,0 +1,14 @@
+from flask import Blueprint, render_template
+from flask_login import logout_user
+from user_session.user_session import UserSession
+
+auth_bp = Blueprint('auth', __name__, template_folder='templates')
+
+@auth_bp.route('/login', methods=['GET'])
+def login():
+    # 如果用户已登录,重定向到首页
+    # if 'user_id' in session:
+    #     return redirect(url_for('project.index'))
+    logout_user()
+    UserSession.clear_user()
+    return render_template('auth/login.html')

+ 18 - 0
SourceCode/DataMiddleware/app/views/project.py

@@ -0,0 +1,18 @@
+from flask import Blueprint, render_template
+from views.views_base import BaseView
+
+project_bp = Blueprint('project', __name__, template_folder='templates')
+
+@project_bp.route('/', methods=['GET'])
+@BaseView.login_required
+def index():
+   return render_template('project/index.html')
+@project_bp.route('/sub', methods=['GET'])
+@BaseView.login_required
+def sub():
+   return render_template('project/sub_project.html')
+
+@project_bp.route('/sub-item', methods=['GET'])
+@BaseView.login_required
+def sub_item():
+   return render_template('project/sub_project_item.html')

+ 0 - 0
SourceCode/DataMiddleware/app/ui_1/static/bootstrap/bootstrap.css → SourceCode/DataMiddleware/app/views/static/bootstrap/bootstrap.css


+ 0 - 0
SourceCode/DataMiddleware/app/ui_1/static/bootstrap/bootstrap.js → SourceCode/DataMiddleware/app/views/static/bootstrap/bootstrap.js


+ 0 - 0
SourceCode/DataMiddleware/app/ui_1/static/jquery/jquery.js → SourceCode/DataMiddleware/app/views/static/jquery/jquery.js


+ 36 - 0
SourceCode/DataMiddleware/app/views/static/layout.css

@@ -0,0 +1,36 @@
+:root {
+    --sky-blue: #87CEEB;
+    --header-height: 56px;
+    --footer-height: 56px;
+}
+
+body {
+    display: flex;
+    flex-direction: column;
+    min-height: 100vh;
+    margin: 0;
+}
+
+.navbar {
+    background-color: var(--sky-blue) !important;
+    height: var(--header-height);
+}
+
+.container-fluid.py-4 {
+    flex: 1;
+    min-height: calc(100vh - var(--header-height) - var(--footer-height));
+}
+
+.footer {
+    background-color: var(--sky-blue) !important;
+    height: var(--footer-height);
+    display: flex;
+    align-items: center;
+}
+
+.navbar-dark .navbar-nav .nav-link,
+.navbar-dark .navbar-brand,
+.text-white,
+.footer {
+    color: #fff !important;
+}

+ 33 - 0
SourceCode/DataMiddleware/app/views/static/login/index.css

@@ -0,0 +1,33 @@
+	body {
+		background-color: #f8f9fa;
+		height: 100vh;
+		display: flex;
+		align-items: center;
+		justify-content: center;
+	}
+	.container, .container-fluid{
+		justify-content: center;
+		display: flex;
+	}
+	.login-container {
+		background-color: white;
+		padding: 2rem;
+		border-radius: 10px;
+		box-shadow: 0 0 20px rgba(0, 0, 0, 0.1);
+		width: 100%;
+		max-width: 400px;
+		margin-top: -10%;
+	}
+	.login-title {
+		text-align: center;
+		color: #333;
+		margin-bottom: 2rem;
+	}
+	.form-floating {
+		margin-bottom: 1rem;
+	}
+	.btn-login {
+		width: 100%;
+		padding: 0.8rem;
+		font-size: 1.1rem;
+	}

+ 59 - 0
SourceCode/DataMiddleware/app/views/static/login/index.js

@@ -0,0 +1,59 @@
+	// 表单验证和提交
+	;(function () {
+		'use strict'
+		const form = document.getElementById('loginForm')
+		form.addEventListener('submit', async function (event) {
+			event.preventDefault()
+			if (!form.checkValidity()) {
+				event.stopPropagation()
+				form.classList.add('was-validated')
+				return
+			}
+
+			const formData = {
+				keyword: document.getElementById('keyword').value,
+				password: document.getElementById('password').value,
+			}
+
+
+
+			try {
+				const response = await fetch('/api/auth/login', {
+					method: 'POST',
+					headers: {
+						'Content-Type': 'application/json',
+					},
+					body: JSON.stringify(formData),
+				})
+				const data = await response.json()
+				if (data.success) {
+					// 获取URL参数中的redirect_url
+					const urlParams = new URLSearchParams(window.location.search);
+					const redirectUrl = urlParams.get('redirect_url');
+					if (redirectUrl) {
+						window.location.href =  redirectUrl;
+					} else {
+						window.location.href = `${window.location.protocol}//${window.location.host}/`
+					}
+				} else {
+					showAlert('danger', data.error || '登录失败,请检查用户名和密码')
+				}
+			} catch (error) {
+				console.error('登录请求失败:', error)
+				showAlert('danger', '网络错误,请稍后重试')
+			}
+		})
+
+		function showAlert(type, message) {
+			const alertContainer = document.getElementById('alert-container')
+			const alertDiv = document.createElement('div')
+			alertDiv.className = `alert alert-${type} alert-dismissible fade show`
+			alertDiv.setAttribute('role', 'alert')
+			alertDiv.innerHTML = `
+                ${message}
+                <button type="button" class="btn-close" data-bs-dismiss="alert" aria-label="Close"></button>
+            `
+			alertContainer.innerHTML = ''
+			alertContainer.appendChild(alertDiv)
+		}
+	})()

+ 10 - 0
SourceCode/DataMiddleware/app/views/static/styles.css

@@ -0,0 +1,10 @@
+*{
+    margin: 0;
+    padding: 0;
+    box-sizing: border-box;
+}
+
+body > .container, body > .container-fluid{
+    padding: 0;
+    margin: 0;
+}

+ 0 - 0
SourceCode/DataMiddleware/app/ui_1/views/__init__.py → SourceCode/DataMiddleware/app/views/static/utils.js


+ 27 - 0
SourceCode/DataMiddleware/app/views/templates/auth/login.html

@@ -0,0 +1,27 @@
+{% extends "base/base.html" %}
+{% block title %}登录{% endblock %}
+{% block head %}
+<link rel="stylesheet" href="{{ url_for('static', filename='login/index.css') }}" />
+{% endblock %}
+{% block content %}
+<div class="login-container">
+	<h2 class="login-title">数据转换推送系统</h2>
+	<div id="alert-container"></div>
+	<form id="loginForm" class="needs-validation" novalidate>
+		<div class="form-floating">
+			<input type="text" class="form-control" id="keyword" name="keyword" placeholder="用户名/邮箱/手机号" required />
+			<label for="keyword">用户名/邮箱/手机号</label>
+			<div class="invalid-feedback">请输入用户名、邮箱或手机号</div>
+		</div>
+		<div class="form-floating">
+			<input type="password" class="form-control" id="password" name="password" placeholder="密码" required />
+			<label for="password">密码</label>
+			<div class="invalid-feedback">请输入密码</div>
+		</div>
+		<button type="submit" class="btn btn-primary btn-login mt-3">登录</button>
+	</form>
+</div>
+{% endblock %}
+{% block scripts %}
+<script src="{{ url_for('static', filename='login/index.js') }}"></script>
+{% endblock %}

+ 18 - 0
SourceCode/DataMiddleware/app/views/templates/base/base.html

@@ -0,0 +1,18 @@
+<!DOCTYPE html>
+<html lang="zh">
+	<head>
+		<meta charset="UTF-8" />
+		<meta name="viewport" content="width=device-width, initial-scale=1.0" />
+		<title>{% block title %}{% endblock %}-IWB</title>
+		<link rel="stylesheet" href="{{ url_for('static', filename='styles.css') }}" />
+		<link rel="stylesheet" href="{{ url_for('static', filename='bootstrap/bootstrap.css') }}" />
+		{% block head %}{% endblock %}
+	</head>
+	<body>
+		<div class="container-fluid">{% block content %}{% endblock %}</div>
+		<script src="{{ url_for('static', filename='jquery/jquery.js') }}"></script>
+		<script src="{{ url_for('static', filename='bootstrap/bootstrap.js') }}"></script>
+		<script src="{{ url_for('static', filename='utils.js') }}"></script>
+		{% block scripts %}{% endblock %}
+	</body>
+</html>

+ 132 - 0
SourceCode/DataMiddleware/app/views/templates/base/layout.html

@@ -0,0 +1,132 @@
+{% extends "base/base.html" %} {% block head %}
+
+<link rel="stylesheet" href="{{ url_for('static', filename='layout.css') }}" />
+{% block page_head %}{% endblock %} {% endblock %} {% block content %}
+<nav class="navbar navbar-expand-lg navbar-dark">
+	<div class="container-fluid">
+		<a class="navbar-brand" href="#">IWB</a>
+		<button class="navbar-toggler" type="button" data-bs-toggle="collapse" data-bs-target="#navbarNav" aria-controls="navbarNav" aria-expanded="false" aria-label="Toggle navigation">
+			<span class="navbar-toggler-icon"></span>
+		</button>
+		<div class="collapse navbar-collapse" id="navbarNav">
+			<ul class="navbar-nav me-auto mb-2 mb-lg-0">
+				<li class="nav-item">
+					<a class="nav-link active" aria-current="page" href="#">首页</a>
+				</li>
+			</ul>
+			<div class="d-flex align-items-center">
+				<div class="dropdown">
+					<a href="#" class="d-flex align-items-center text-white text-decoration-none dropdown-toggle" id="userDropdown" data-bs-toggle="dropdown" aria-expanded="false">
+						<div class="rounded-circle me-2 d-flex align-items-center justify-content-center bg-primary text-white" style="width: 32px; height: 32px">{{ current_user.name[0].upper() if current_user and current_user.is_authenticated and current_user.name else '?' }}</div>
+						<span>{{ current_user.name if current_user and current_user.is_authenticated and current_user.name else '未登录' }}</span>
+					</a>
+					<ul class="dropdown-menu dropdown-menu-end" aria-labelledby="userDropdown">
+						<li><a class="dropdown-item" href="#" data-bs-toggle="modal" data-bs-target="#changePasswordModal">修改密码</a></li>
+						<li><hr class="dropdown-divider" /></li>
+						<li><a class="dropdown-item" href="#" onclick="logout()">注销登录</a></li>
+					</ul>
+				</div>
+			</div>
+		</div>
+	</div>
+</nav>
+
+<div class="container-fluid py-4">{% block page_content %}{% endblock %}</div>
+
+<footer class="footer mt-auto py-3">
+	<div class="container text-center">
+		<span>© 2024 IWB. All rights reserved.</span>
+	</div>
+</footer>
+
+<!-- 修改密码模态框 -->
+<div class="modal fade" id="changePasswordModal" tabindex="-1" aria-labelledby="changePasswordModalLabel" aria-hidden="true">
+	<div class="modal-dialog">
+		<div class="modal-content">
+			<div class="modal-header">
+				<h5 class="modal-title" id="changePasswordModalLabel">修改密码</h5>
+				<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
+			</div>
+			<div class="modal-body">
+				<form id="changePasswordForm">
+					<div class="mb-3">
+						<label for="oldPassword" class="form-label">当前密码</label>
+						<input type="password" class="form-control" id="oldPassword" required />
+					</div>
+					<div class="mb-3">
+						<label for="newPassword" class="form-label">新密码</label>
+						<input type="password" class="form-control" id="newPassword" required />
+					</div>
+					<div class="mb-3">
+						<label for="confirmPassword" class="form-label">确认新密码</label>
+						<input type="password" class="form-control" id="confirmPassword" required />
+					</div>
+				</form>
+			</div>
+			<div class="modal-footer">
+				<button type="button" class="btn btn-secondary" data-bs-dismiss="modal">取消</button>
+				<button type="button" class="btn btn-primary" onclick="changePassword()">保存</button>
+			</div>
+		</div>
+	</div>
+</div>
+{% endblock %} {% block scripts %}
+<script>
+	function changePassword() {
+		const oldPassword = document.getElementById('oldPassword').value
+		const newPassword = document.getElementById('newPassword').value
+		const confirmPassword = document.getElementById('confirmPassword').value
+
+		if (newPassword !== confirmPassword) {
+			alert('新密码与确认密码不匹配')
+			return
+		}
+
+		fetch('/api/user/change-password', {
+			method: 'POST',
+			headers: {
+				'Content-Type': 'application/json',
+			},
+			body: JSON.stringify({
+				old_password: oldPassword,
+				new_password: newPassword,
+			}),
+		})
+			.then((response) => response.json())
+			.then((data) => {
+				if (data.success) {
+					alert('密码修改成功')
+					$('#changePasswordModal').modal('hide')
+					document.getElementById('changePasswordForm').reset()
+				} else {
+					alert(data.message || '密码修改失败')
+				}
+			})
+			.catch((error) => {
+				console.error('Error:', error)
+				alert('发生错误,请稍后重试')
+			})
+	}
+
+	function logout() {
+		fetch('/api/auth/logout', {
+			method: 'POST',
+			headers: {
+				'Content-Type': 'application/json',
+			},
+		})
+			.then((response) => response.json())
+			.then((data) => {
+				if (data.success) {
+					window.location.href = '/login'
+				} else {
+					alert(data.message || '注销登录失败')
+				}
+			})
+			.catch((error)=> {
+				console.error('Error:', error)
+				alert('发生错误,请稍后重试')
+			})
+	}
+</script>
+{% endblock %}

+ 0 - 0
SourceCode/DataMiddleware/app/ui_1/views/login.py → SourceCode/DataMiddleware/app/views/templates/log/index.html


+ 10 - 0
SourceCode/DataMiddleware/app/views/templates/project/index.html

@@ -0,0 +1,10 @@
+{% extends "base/layout.html" %}
+
+{% block title %}项目{% endblock %}
+
+{% block page_content %}
+    <div class="container">
+        <h1>项目管理</h1>
+        <!-- 这里可以添加项目相关的内容 -->
+    </div>
+{% endblock %}

+ 0 - 0
SourceCode/DataMiddleware/app/ui_1/views/project.py → SourceCode/DataMiddleware/app/views/templates/project/sub_project.html


+ 0 - 0
SourceCode/DataMiddleware/app/views/templates/project/sub_project_item.html


+ 24 - 0
SourceCode/DataMiddleware/app/views/views_base.py

@@ -0,0 +1,24 @@
+import os
+from flask import session, redirect, url_for, request
+from functools import wraps
+from user_session.user_session import UserSession
+
+class BaseView:
+    @staticmethod
+    def login_required(f):
+        @wraps(f)
+        def decorated_function(*args, **kwargs):
+            current_user = UserSession.get_current_user()
+            print(current_user)
+            if not current_user.is_authenticated :
+                return redirect(url_for('auth.login', next=request.url))
+            return f(*args, **kwargs)
+        return decorated_function
+
+    @staticmethod
+    def get_current_user():
+        return session.get('user_id'), session.get('username')
+
+    @staticmethod
+    def basename_filter(path):
+        return os.path.basename(path)

+ 2 - 1
SourceCode/DataMiddleware/requirements.txt

@@ -15,4 +15,5 @@ tabulate==0.9.0
 
 pyxnat~=1.6.3
 httplib2~=0.22.0
-bcrypt~=4.3.0
+bcrypt~=4.3.0
+Flask-Login~=0.6.3