SongZihuan преди 2 години
родител
ревизия
228685e2aa
променени са 10 файла, в които са добавени 272 реда и са изтрити 4 реда
  1. 1 1
      .gitignore
  2. 0 0
      static/HuanMail.ico
  3. 98 0
      templates/base.html
  4. 2 0
      templates/index/index.html
  5. 69 0
      web/__init__.py
  6. 21 3
      web/configure.py
  7. 9 0
      web/index.py
  8. 59 0
      web/logger.py
  9. 10 0
      web/login.py
  10. 3 0
      web/moment.py

+ 1 - 1
.gitignore

@@ -29,7 +29,7 @@ share/python-wheels/
 MANIFEST
 
 # PyInstaller
-#  Usually these files are written by a python script from a template
+#  Usually these files are written by a python script from a templates
 #  before PyInstaller builds the exe, so as to inject date/other infos into it.
 *.manifest
 *.spec

+ 0 - 0
HuanMail.ico → static/HuanMail.ico


+ 98 - 0
templates/base.html

@@ -0,0 +1,98 @@
+{% macro render_field(field) %}
+    <div class="form-group form-floating my-3">
+        {% if not field.errors %}
+            {{ field(class="form-control", placeholder=field.label.text) | safe }}
+        {% else %}
+            {{ field(class="form-control", placeholder=field.label.text, value="") | safe }}
+        {% endif %}
+
+        {{ field.label }}
+        {% for error in field.errors %}
+            <div class="invalid-feedback"> {{ error }} </div>
+        {% endfor %}
+    </div>
+{% endmacro %}
+
+{% macro render_text_field(field) %}
+    <div class="form-group form-floating my-3">
+        {% if not field.errors %}
+            {{ field(class="form-control", placeholder=field.label.text, style="height: 40vh") | safe }}
+        {% else %}
+            {{ field(class="form-control", placeholder=field.label.text, style="height: 40vh", value="") | safe }}
+        {% endif %}
+
+        {{ field.label }}
+        {% for error in field.errors %}
+            <div class="invalid-feedback"> {{ error }} </div>
+        {% endfor %}
+    </div>
+{% endmacro %}
+
+{% macro render_select_field(field) %}
+    <div class="form-group my-3">
+        {{ field(class="form-select") | safe }}
+        {% for error in field.errors %}
+            <div class="invalid-feedback d-block"> {{ error }} </div>
+        {% endfor %}
+    </div>
+{% endmacro %}
+
+
+{% macro show_time(time) %}
+    {{ moment(datetime.utcfromtimestamp(datetime.timestamp(time))).format('YYYY-MM-DD HH:mm:ss') }}
+{% endmacro %}
+
+<!DOCTYPE html>
+<html lang="zh">
+<head>
+    <meta charset="UTF-8">
+    <meta name="viewport" content="width=device-width, initial-scale=1">
+
+    {% block icon %}
+        <link rel="icon" href="{{ url_for('static', filename=conf["LOGO"]) }}" type="image/x-icon"/>
+    {% endblock %}
+
+    {% block font %}
+        <link rel="preconnect" href="https://fonts.googleapis.com">
+        <link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
+        <link href="https://fonts.googleapis.com/css2?family=Noto+Sans+SC:wght@100;400&display=swap" rel="stylesheet">
+    {% endblock %}
+
+    {% block style %}
+        <link href="https://cdn.jsdelivr.net/npm/bootstrap@5.2.2/dist/css/bootstrap.min.css" rel="stylesheet" integrity="sha384-Zenh87qX5JnK2Jl0vWa8Ck2rdkQ2Bzep5IDxbcnCeuOxjzrPF/et3URy9Bv1WTRi" crossorigin="anonymous">
+        <style>
+            html {
+                font-family: 'Noto Sans SC', sans-serif;
+            }
+        </style>
+    {% endblock %}
+
+    {% block javascript %}
+        <script src="https://cdn.jsdelivr.net/npm/bootstrap@5.2.2/dist/js/bootstrap.bundle.min.js" integrity="sha384-OERcA2EqjJCMA+/3y+gxIOqMEjwtxJY7qPCqsdltbNJuaOe923+mo//f6V8Qbsw3" crossorigin="anonymous"></script>
+        {{ moment.include_moment() }}
+        {{ moment.lang("zh-CN") }}
+    {% endblock %}
+
+    <title>{% block title %} {% endblock %} - {{ conf["WEBSITE_NAME"] }} </title>
+</head>
+
+<body>
+    {% block nav %}
+        <div class="container mt-2">
+            <a class="h3" href="/" style="text-decoration:none;color:#333;"> {{ conf["WEBSITE_TITLE"] }} </a>
+            <a href="{{ url_for("base.index_page") }}" class="btn btn-outline-danger float-end mx-2"> 退出登录 </a>
+        </div>
+    {% endblock %}
+
+    <section class="container mt-4 mb-2">
+    {% for message in get_flashed_messages() %}
+        <div class="alert alert-info fade show">
+            <button type="button" class="btn-close" data-bs-dismiss="alert"></button>
+            {{ message }}
+        </div>
+    {% endfor %}
+    </section>
+
+    {% block content %} {% endblock %}
+</body>
+</html>

+ 2 - 0
templates/index/index.html

@@ -0,0 +1,2 @@
+{% extends "base.html" %}
+{% block title %} 首页 {% endblock %}

+ 69 - 0
web/__init__.py

@@ -1,6 +1,75 @@
 from flask import Flask
+from flask.logging import default_handler
+import logging
+import logging.handlers
+import os
+import sys
+
+from .configure import conf
+from .logger import Logger
+from .login import login
+from .moment import moment
+
+
+if conf["DEBUG_PROFILE"]:
+    from werkzeug.middleware.profiler import ProfilerMiddleware
 
 
 class HuamMailFlask(Flask):
     def __init__(self, import_name):
         super(HuamMailFlask, self).__init__(import_name)
+        self.update_configure()
+        self.profile_setting()
+        self.logging_setting()
+        self.blueprint()
+
+        moment.init_app(self)
+        login.init_app(self)
+
+        @self.context_processor
+        def inject_base():
+            """ app默认模板变量 """
+            return {"conf": conf}
+
+        self.error_page([400, 401, 403, 404, 405, 408, 410, 413, 414, 423, 500, 501, 502])
+
+    def blueprint(self):
+        from .index import index
+        self.register_blueprint(index, url_prefix="/")
+
+    def profile_setting(self):
+        if conf["DEBUG_PROFILE"]:
+            self.wsgi_app = ProfilerMiddleware(self.wsgi_app, sort_by=("cumtime",))
+
+    def logging_setting(self):
+        self.logger.removeHandler(default_handler)
+        self.logger.setLevel(conf["LOG_LEVEL"])
+        self.logger.propagate = False  # 不传递给更高级别的处理器处理日志
+
+        if len(conf["LOG_HOME"]) > 0:
+            handle = logging.handlers.TimedRotatingFileHandler(
+                os.path.join(conf["LOG_HOME"], f"flask.log"), backupCount=10)
+            handle.setFormatter(logging.Formatter(conf["LOG_FORMAT"]))
+            self.logger.addHandler(handle)
+
+        if conf["LOG_STDERR"]:
+            handle = logging.StreamHandler(sys.stderr)
+            handle.setFormatter(logging.Formatter(conf["LOG_FORMAT"]))
+            self.logger.addHandler(handle)
+
+    def update_configure(self):
+        """ 更新配置 """
+        self.config.update(conf)
+
+    def error_page(self, error_code):
+        # for i in error_code:
+        #     def create_error_handle(status):  # 创建一个 status 变量给 error_handle
+        #         def error_handle(e):
+        #             Logger.print_load_page_log(status)
+        #             data = render_template('error.html', error_code=status, error_info=e)
+        #             return Response(response=data, status=status)
+        #
+        #         return error_handle
+        #
+        #     self.errorhandler(i)(create_error_handle(i))
+        pass

+ 21 - 3
web/configure.py

@@ -5,10 +5,26 @@ from typing import Dict
 
 
 conf: Dict[str, any] = {
-    "DEBUG_PROFILE": False,
-    "SMTP_JSON": "smtp.json",
-    "IMAP_JSON": "imap.json",
     "SECRET_KEY": "HuanMail-R-Salt",
+    "WEBSITE_NAME": "HuanMail",
+    "WEBSITE_TITLE": "HuanMail-在线邮件系统",
+
+    "IMAP_HOST": "localhost",
+    "IMAP_PORT": 143,
+    "IMAP_SSL": False,
+    "IMAP_START_SSL": False,
+
+    "SMTP_HOST": "localhost",
+    "SMTP_PORT": 25,
+    "SMTP_SSL": False,
+    "SMTP_START_SSL": False,
+
+    "REDIS_HOST": "localhost",
+    "REDIS_PORT": 6379,
+    "REDIS_NAME": "localhost",
+    "REDIS_PASSWD": "123456",
+    "REDIS_DATABASE": 0,
+
     "LOG_HOME": "",
     "LOG_FORMAT": "[%(levelname)s]:%(name)s:%(asctime)s "
                   "(%(filename)s:%(lineno)d %(funcName)s) "
@@ -16,6 +32,8 @@ conf: Dict[str, any] = {
                   "%(message)s",
     "LOG_LEVEL": logging.INFO,
     "LOG_STDERR": True,
+    "DEBUG_PROFILE": False,
+
     "LOGO": "HuanMail.ico",
 }
 

+ 9 - 0
web/index.py

@@ -0,0 +1,9 @@
+from flask import Blueprint, render_template
+
+
+index = Blueprint("base", __name__)
+
+
+@index.route("/")
+def index_page():
+    return render_template("index/index.html")

+ 59 - 0
web/logger.py

@@ -0,0 +1,59 @@
+from flask import request, current_app
+from flask_login import current_user
+
+
+class Logger:
+    @staticmethod
+    def __get_log_request_info():
+        return (f"user: '{current_user.email}' "
+                f"url: '{request.url}' blueprint: '{request.blueprint}' "
+                f"args: {request.args} form: {request.form} "
+                f"accept_encodings: '{request.accept_encodings}' "
+                f"accept_charsets: '{request.accept_charsets}' "
+                f"accept_mimetypes: '{request.accept_mimetypes}' "
+                f"accept_languages: '{request.accept_languages}'")
+
+    @staticmethod
+    def print_load_page_log(page: str):
+        current_app.logger.debug(
+            f"[{request.method}] Load - '{page}' " + Logger.__get_log_request_info())
+
+    @staticmethod
+    def print_form_error_log(opt: str):
+        current_app.logger.warning(
+            f"[{request.method}] '{opt}' - Bad form " + Logger.__get_log_request_info())
+
+    @staticmethod
+    def print_sys_opt_fail_log(opt: str):
+        current_app.logger.error(
+            f"[{request.method}] System {opt} - fail " + Logger.__get_log_request_info())
+
+    @staticmethod
+    def print_sys_opt_success_log(opt: str):
+        current_app.logger.warning(
+            f"[{request.method}] System {opt} - success " + Logger.__get_log_request_info())
+
+    @staticmethod
+    def print_user_opt_fail_log(opt: str):
+        current_app.logger.debug(
+            f"[{request.method}] User {opt} - fail " + Logger.__get_log_request_info())
+
+    @staticmethod
+    def print_user_opt_success_log(opt: str):
+        current_app.logger.debug(
+            f"[{request.method}] User {opt} - success " + Logger.__get_log_request_info())
+
+    @staticmethod
+    def print_user_opt_error_log(opt: str):
+        current_app.logger.warning(
+            f"[{request.method}] User {opt} - system fail " + Logger.__get_log_request_info())
+
+    @staticmethod
+    def print_import_user_opt_success_log(opt: str):
+        current_app.logger.info(
+            f"[{request.method}] User {opt} - success " + Logger.__get_log_request_info())
+
+    @staticmethod
+    def print_user_not_allow_opt_log(opt: str):
+        current_app.logger.info(
+            f"[{request.method}] User '{opt}' - reject " + Logger.__get_log_request_info())

+ 10 - 0
web/login.py

@@ -0,0 +1,10 @@
+from flask_login import LoginManager
+
+
+login = LoginManager()
+login.login_view = "auth.login_page"
+
+
+@login.user_loader
+def user_loader(user_id: int):
+    return None

+ 3 - 0
web/moment.py

@@ -0,0 +1,3 @@
+from flask_moment import Moment
+
+moment = Moment()