diff --git a/app.py b/app.py new file mode 100644 index 0000000000000000000000000000000000000000..8295ef967a8ba0befd5eedaa2ce91b8ee34f925e --- /dev/null +++ b/app.py @@ -0,0 +1,19 @@ +#!/usr/bin/env python3 +# -*- coding: utf-8 -*- +""" +# @Version : Python 3.11.4 +# @Software : Sublime Text 4 +# @Author : StudentCWZ +# @Email : StudentCWZ@outlook.com +# @Date : 2023/10/28 13:49 +# @File : app.py +# @Description : +""" + +from application import create_app + +app = create_app() + + +if __name__ == "__main__": + app.run() diff --git a/application/__init__.py b/application/__init__.py new file mode 100644 index 0000000000000000000000000000000000000000..ea183bfa2467f02c576c6a37c74e5c0c2b38b9eb --- /dev/null +++ b/application/__init__.py @@ -0,0 +1,34 @@ +#!/usr/bin/env python3 +# -*- coding: utf-8 -*- +""" +# @Version : Python 3.11.4 +# @Software : Sublime Text 4 +# @Author : StudentCWZ +# @Email : StudentCWZ@outlook.com +# @Date : 2023/10/28 12:11 +# @File : __init__.py +# @Description : +""" + +import os + +from flask import Flask + +from application.extensions import init_plugs +from application.views import init_views +from application.script import init_script + + +def create_app() -> Flask: + app = Flask(os.path.abspath(os.path.join(os.path.dirname(__file__), "."))) + + # 注册各种插件 + init_plugs(app) + + # 注册路由 + init_views(app) + + # 注册命令 + init_script(app) + + return app diff --git a/application/config/config.yaml b/application/config/config.yaml new file mode 100644 index 0000000000000000000000000000000000000000..483103214f3885677c598db86a2aed59352aed24 --- /dev/null +++ b/application/config/config.yaml @@ -0,0 +1,9 @@ +System: + Env: public + +MySQL: + USER: root + PASSWORD: localhost123 + HOST: localhost + PORT: 3306 + DB: elp diff --git a/application/config/production_config.yaml b/application/config/production_config.yaml new file mode 100644 index 0000000000000000000000000000000000000000..485af783c13337767936627fe783a918afafc243 --- /dev/null +++ b/application/config/production_config.yaml @@ -0,0 +1,2 @@ +System: + Env: public123 \ No newline at end of file diff --git a/application/dao/__init__.py b/application/dao/__init__.py new file mode 100644 index 0000000000000000000000000000000000000000..7aa926b858d05a405635493e8ce47bb4bd7f8ac2 --- /dev/null +++ b/application/dao/__init__.py @@ -0,0 +1,13 @@ +#!/usr/bin/env python3 +# -*- coding: utf-8 -*- +""" +# @Version : Python 3.11.4 +# @Software : Sublime Text 4 +# @Author : StudentCWZ +# @Email : StudentCWZ@outlook.com +# @Date : 2023/10/28 22:31 +# @File : __init__.py +# @Description : +""" + +from .user import UserDao diff --git a/application/dao/user.py b/application/dao/user.py new file mode 100644 index 0000000000000000000000000000000000000000..ed1571c60ca31c711124153ea0678e72bd3f4821 --- /dev/null +++ b/application/dao/user.py @@ -0,0 +1,31 @@ +#!/usr/bin/env python3 +# -*- coding: utf-8 -*- +""" +# @Version : Python 3.11.4 +# @Software : Sublime Text 4 +# @Author : StudentCWZ +# @Email : StudentCWZ@outlook.com +# @Date : 2023/10/28 23:56 +# @File : user.py +# @Description : +""" + +from application.extensions.init_sqlalchemy import db +from application.models.user import User + + +class UserDao: + @classmethod + def get_all(cls): + return User.query.all() + + @classmethod + def get_by_id(cls, user_id: int): + return User.query.get(user_id) + + @classmethod + def create(cls, username: str, password: str, email: str): + new_user = User(username=username, email=email, password=password) + db.session.add(new_user) + db.session.commit() + return new_user diff --git a/application/extensions/__init__.py b/application/extensions/__init__.py new file mode 100644 index 0000000000000000000000000000000000000000..f2d5b8964cb735ef09939018b5e288366cbdbcbf --- /dev/null +++ b/application/extensions/__init__.py @@ -0,0 +1,31 @@ +#!/usr/bin/env python3 +# -*- coding: utf-8 -*- +""" +# @Version : Python 3.11.4 +# @Software : Sublime Text 4 +# @Author : StudentCWZ +# @Email : StudentCWZ@outlook.com +# @Date : 2023/10/28 12:50 +# @File : __init__.py +# @Description : +""" + +from flask import Flask + +from .init_config import init_config +from .init_logger import init_logger +from .init_sqlalchemy import init_database +from .init_bcrypt import init_bcrypt +from .init_migrate import init_migrate +from .init_apispec import init_apispec +from .init_marshmallow import init_marshmallow + + +def init_plugs(app: Flask) -> None: + init_config(app) + init_logger(app) + init_database(app) + init_bcrypt(app) + init_migrate(app) + init_apispec(app) + init_marshmallow(app) diff --git a/application/extensions/init_apispec.py b/application/extensions/init_apispec.py new file mode 100644 index 0000000000000000000000000000000000000000..d6bb6f85c56724e579e6052839e32781da4a6cb5 --- /dev/null +++ b/application/extensions/init_apispec.py @@ -0,0 +1,27 @@ +#!/usr/bin/env python3 +# -*- coding: utf-8 -*- +""" +# @Version : Python 3.11.4 +# @Software : Sublime Text 4 +# @Author : StudentCWZ +# @Email : StudentCWZ@outlook.com +# @Date : 2023/10/29 12:01 +# @File : init_apispec.py +# @Description : +""" + +from flask import Flask +from flask_apispec import FlaskApiSpec + +# 初始化 flask-apispec +apispec = FlaskApiSpec() + + +def init_apispec(app: Flask) -> None: + """ + Initialize the ApiSpec extension + + :param app: flask.Flask application instance + :return: None + """ + apispec.init_app(app) diff --git a/application/extensions/init_bcrypt.py b/application/extensions/init_bcrypt.py new file mode 100644 index 0000000000000000000000000000000000000000..34ade7c299954a962a8b721f1d6cf991661c6676 --- /dev/null +++ b/application/extensions/init_bcrypt.py @@ -0,0 +1,26 @@ +#!/usr/bin/env python3 +# -*- coding: utf-8 -*- +""" +# @Version : Python 3.11.4 +# @Software : Sublime Text 4 +# @Author : StudentCWZ +# @Email : StudentCWZ@outlook.com +# @Date : 2023/10/28 22:06 +# @File : init_bcrypt.py +# @Description : +""" + +from flask import Flask +from flask_bcrypt import Bcrypt + +bcrypt = Bcrypt() + + +def init_bcrypt(app: Flask) -> None: + """ + Initialize the bcrypt extension + + :param app: flask.Flask application instance + :return: None + """ + bcrypt.init_app(app) diff --git a/application/extensions/init_config.py b/application/extensions/init_config.py new file mode 100644 index 0000000000000000000000000000000000000000..c3952dd98dc691cfe5cbc9f0e734bd6ace3cdae3 --- /dev/null +++ b/application/extensions/init_config.py @@ -0,0 +1,31 @@ +#!/usr/bin/env python3 +# -*- coding: utf-8 -*- +""" +# @Version : Python 3.11.4 +# @Software : Sublime Text 4 +# @Author : StudentCWZ +# @Email : StudentCWZ@outlook.com +# @Date : 2023/10/28 12:53 +# @File : init_config.py +# @Description : flask-dynaconf extension +""" + +from flask import Flask + +from dynaconf import FlaskDynaconf + + +def init_config(app: Flask) -> None: + """ + Initialize the dynaconf extension + + :param app: flask.Flask application instance + :return: None + """ + FlaskDynaconf( + app=app, + loaders=[ + "application.utils.loaders.consul_loader", + "application.utils.loaders.yaml_loader", + ], + ) diff --git a/application/extensions/init_logger.py b/application/extensions/init_logger.py new file mode 100644 index 0000000000000000000000000000000000000000..62e96a5c01e9cce12368402778293cc4d85d43ef --- /dev/null +++ b/application/extensions/init_logger.py @@ -0,0 +1,25 @@ +#!/usr/bin/env python3 +# -*- coding: utf-8 -*- +""" +# @Version : Python 3.11.4 +# @Software : Sublime Text 4 +# @Author : StudentCWZ +# @Email : StudentCWZ@outlook.com +# @Date : 2023/10/28 18:16 +# @File : init_logger.py +# @Description : +""" + +from flask import Flask + +from application.lib import FlaskLoguru + + +def init_logger(app: Flask) -> None: + """ + Initialize the dynaconf extension + + :param app: flask.Flask application instance + :return: None + """ + FlaskLoguru(app) diff --git a/application/extensions/init_marshmallow.py b/application/extensions/init_marshmallow.py new file mode 100644 index 0000000000000000000000000000000000000000..8923008f17a77621c681399c8f5862e74c8abcb2 --- /dev/null +++ b/application/extensions/init_marshmallow.py @@ -0,0 +1,27 @@ +#!/usr/bin/env python3 +# -*- coding: utf-8 -*- +""" +# @Version : Python 3.11.4 +# @Software : Sublime Text 4 +# @Author : StudentCWZ +# @Email : StudentCWZ@outlook.com +# @Date : 2023/10/29 12:36 +# @File : init_marshmallow.py +# @Description : +""" + +from flask import Flask + +from flask_marshmallow import Marshmallow + +ma = Marshmallow() + + +def init_marshmallow(app: Flask) -> None: + """ + Initialize the database extension + + :param app: flask.Flask application instance + :return: None + """ + ma.init_app(app) \ No newline at end of file diff --git a/application/extensions/init_migrate.py b/application/extensions/init_migrate.py new file mode 100644 index 0000000000000000000000000000000000000000..2f0457bec33ea9acc9f95885a9fe9b2fde998969 --- /dev/null +++ b/application/extensions/init_migrate.py @@ -0,0 +1,28 @@ +#!/usr/bin/env python3 +# -*- coding: utf-8 -*- +""" +# @Version : Python 3.11.4 +# @Software : Sublime Text 4 +# @Author : StudentCWZ +# @Email : StudentCWZ@outlook.com +# @Date : 2023/10/29 01:19 +# @File : init_migrate.py +# @Description : +""" + +from flask import Flask +from flask_migrate import Migrate + +from application.extensions.init_sqlalchemy import db + +migrate = Migrate() + + +def init_migrate(app: Flask) -> None: + """ + Initialize the database extension + + :param app: flask.Flask application instance + :return: None + """ + migrate.init_app(app, db) diff --git a/application/extensions/init_sqlalchemy.py b/application/extensions/init_sqlalchemy.py new file mode 100644 index 0000000000000000000000000000000000000000..1e47102837636cf6cf059975fc0b7fedd58c1bdc --- /dev/null +++ b/application/extensions/init_sqlalchemy.py @@ -0,0 +1,29 @@ +#!/usr/bin/env python3 +# -*- coding: utf-8 -*- +""" +# @Version : Python 3.11.4 +# @Software : Sublime Text 4 +# @Author : StudentCWZ +# @Email : StudentCWZ@outlook.com +# @Date : 2023/10/28 22:01 +# @File : init_sqlalchemy.py +# @Description : +""" + +from flask import Flask +from flask_sqlalchemy import SQLAlchemy + +from application.utils import dsn + +db = SQLAlchemy() + + +def init_database(app: Flask) -> None: + """ + Initialize the database extension + + :param app: flask.Flask application instance + :return: None + """ + app.config.setdefault('SQLALCHEMY_DATABASE_URI', dsn(app)) + db.init_app(app) diff --git a/application/lib/__init__.py b/application/lib/__init__.py new file mode 100644 index 0000000000000000000000000000000000000000..47b682010eb906adb3bf26c4e5050300f1692253 --- /dev/null +++ b/application/lib/__init__.py @@ -0,0 +1,14 @@ +#!/usr/bin/env python3 +# -*- coding: utf-8 -*- +""" +# @Version : Python 3.11.4 +# @Software : Sublime Text 4 +# @Author : StudentCWZ +# @Email : StudentCWZ@outlook.com +# @Date : 2023/10/28 12:19 +# @File : __init__.py +# @Description : +""" + +from .config import ConsulConfig, LocalConfig +from .flask_loguru import FlaskLoguru diff --git a/application/lib/config/__init__.py b/application/lib/config/__init__.py new file mode 100644 index 0000000000000000000000000000000000000000..0df1d019c84f4b0e9d560ad4b7b82916eb25d69d --- /dev/null +++ b/application/lib/config/__init__.py @@ -0,0 +1,14 @@ +#!/usr/bin/env python3 +# -*- coding: utf-8 -*- +""" +# @Version : Python 3.11.4 +# @Software : Sublime Text 4 +# @Author : StudentCWZ +# @Email : StudentCWZ@outlook.com +# @Date : 2023/10/28 15:06 +# @File : __init__.py.py +# @Description : +""" + +from .consul import ConsulConfig +from .local import LocalConfig diff --git a/application/lib/config/consul.py b/application/lib/config/consul.py new file mode 100644 index 0000000000000000000000000000000000000000..3eee5c2ffbf825d4b50032dafb72f08274dcc8c3 --- /dev/null +++ b/application/lib/config/consul.py @@ -0,0 +1,29 @@ +#!/usr/bin/env python3 +# -*- coding: utf-8 -*- +""" +# @Version : Python 3.11.4 +# @Software : Sublime Text 4 +# @Author : StudentCWZ +# @Email : StudentCWZ@outlook.com +# @Date : 2023/10/28 15:06 +# @File : consul.py +# @Description : +""" + +from typing import Any + +import consul +import yaml + + +class ConsulConfig: + def __init__(self, host='localhost', port=8500, token=None, dc='dc1'): + self.token = token + self.dc = dc + self.client = consul.Consul(host=host, port=port, token=token, dc=dc) + + def get(self, key: str) -> Any: + _, data = self.client.kv.get(key=key, token=self.token, dc=self.dc) + if data is None: + raise KeyError(f'Key {key} not found in Consul.') + return yaml.load(data['Value'], Loader=yaml.FullLoader) diff --git a/application/lib/config/local.py b/application/lib/config/local.py new file mode 100644 index 0000000000000000000000000000000000000000..0ff170244047b40d4e1372485d6d75934a742dc6 --- /dev/null +++ b/application/lib/config/local.py @@ -0,0 +1,29 @@ +#!/usr/bin/env python3 +# -*- coding: utf-8 -*- +""" +# @Version : Python 3.11.4 +# @Software : Sublime Text 4 +# @Author : StudentCWZ +# @Email : StudentCWZ@outlook.com +# @Date : 2023/10/28 15:07 +# @File : local.py +# @Description : +""" + +import os +from typing import Any + +import yaml + + +class LocalConfig: + def __init__(self, config_dir: str): + self.config_dir = config_dir + + def load(self, filename: str) -> Any: + filepath = os.path.join(self.config_dir, filename) + if not os.path.exists(filepath): + raise FileNotFoundError(f"No such file or directory: '{filepath}'") + with open(filepath, 'r') as file: + cfg = yaml.safe_load(file) + return cfg diff --git a/application/lib/flask_loguru/__init__.py b/application/lib/flask_loguru/__init__.py new file mode 100644 index 0000000000000000000000000000000000000000..a382bc2675d1887c209ade81cfbb400b6d8068eb --- /dev/null +++ b/application/lib/flask_loguru/__init__.py @@ -0,0 +1,13 @@ +#!/usr/bin/env python3 +# -*- coding: utf-8 -*- +""" +# @Version : Python 3.11.4 +# @Software : Sublime Text 4 +# @Author : StudentCWZ +# @Email : StudentCWZ@outlook.com +# @Date : 2023/10/28 17:43 +# @File : __init__.py +# @Description : +""" + +from .logger import FlaskLoguru diff --git a/application/lib/flask_loguru/format.py b/application/lib/flask_loguru/format.py new file mode 100644 index 0000000000000000000000000000000000000000..ca5fe95d59eedb9482d6d589b29956b1eb1907dc --- /dev/null +++ b/application/lib/flask_loguru/format.py @@ -0,0 +1,31 @@ +#!/usr/bin/env python3 +# -*- coding: utf-8 -*- +""" +# @Version : Python 3.11.4 +# @Software : Sublime Text 4 +# @Author : StudentCWZ +# @Email : StudentCWZ@outlook.com +# @Date : 2023/10/28 18:21 +# @File : format.py +# @Description : +""" + +import json + + +def serialize(record: dict) -> str: + time_stamp = record["time"] + time_stamp = time_stamp.strftime("%Y-%m-%d %H:%M:%S") + subset = { + "time": time_stamp, + "message": record["message"], + "level": record["level"].name.lower(), + "tag": "{}:{}".format(record["file"].path, record["line"]), + "field": {"data": record["extra"].get("data", {})}, + } + return json.dumps(subset, ensure_ascii=False) + + +def patching(record: dict) -> str: + record["extra"]["serialized"] = serialize(record) + return "{extra[serialized]}\n" diff --git a/application/lib/flask_loguru/logger.py b/application/lib/flask_loguru/logger.py new file mode 100644 index 0000000000000000000000000000000000000000..6b015d54d3ce4befad7bfeb36dcdd6c5eab87108 --- /dev/null +++ b/application/lib/flask_loguru/logger.py @@ -0,0 +1,58 @@ +#!/usr/bin/env python3 +# -*- coding: utf-8 -*- +""" +# @Version : Python 3.11.4 +# @Software : Sublime Text 4 +# @Author : StudentCWZ +# @Email : StudentCWZ@outlook.com +# @Date : 2023/10/28 17:44 +# @File : logger.py +# @Description : +""" + +import sys + +from flask import Flask, request, g +from loguru import logger + +from .format import patching + + +class FlaskLoguru: + def __init__(self, app=None): + if app is not None: + self.init_app(app) + + def init_app(self, app: Flask): + logger.remove() + logger.add(sys.stderr, format=patching) + if not hasattr(app, "extensions"): + app.extensions = {} + + app.extensions.setdefault("loguru", {}) + app.extensions["loguru"][self] = logger + + @app.before_request + def before_request(): + data = dict( + url=request.url, + method=request.method, + ip=request.remote_addr, + request_body=request.get_json(), + ) + g.logger = logger.bind(data=data) + g.logger.info('Request started') + + @app.after_request + def after_request(response): + g.logger.info('Request completed') + return response + + @app.teardown_request + def teardown_request(exception=None): + if exception: + data = dict( + exception=str(exception) + ) + g.logger = logger.bind(data=data) + g.logger.exception('An error occurred during the request') diff --git a/application/models/__init__.py b/application/models/__init__.py new file mode 100644 index 0000000000000000000000000000000000000000..2f7985a9b38400e61022d284c9347ef4590b0f2e --- /dev/null +++ b/application/models/__init__.py @@ -0,0 +1,13 @@ +#!/usr/bin/env python3 +# -*- coding: utf-8 -*- +""" +# @Version : Python 3.11.4 +# @Software : Sublime Text 4 +# @Author : StudentCWZ +# @Email : StudentCWZ@outlook.com +# @Date : 2023/10/28 22:12 +# @File : __init__.py.py +# @Description : +""" + +from .user import User diff --git a/application/models/user.py b/application/models/user.py new file mode 100644 index 0000000000000000000000000000000000000000..0a846001ff0cb0fed5d360df9f20d9a17f03c43f --- /dev/null +++ b/application/models/user.py @@ -0,0 +1,49 @@ +#!/usr/bin/env python3 +# -*- coding: utf-8 -*- +""" +# @Version : Python 3.11.4 +# @Software : Sublime Text 4 +# @Author : StudentCWZ +# @Email : StudentCWZ@outlook.com +# @Date : 2023/10/28 22:12 +# @File : user.py +# @Description : +""" + +from sqlalchemy.ext.hybrid import hybrid_property + +from application.extensions.init_bcrypt import bcrypt +from application.extensions.init_sqlalchemy import db + + +class User(db.Model): + """Basic user model""" + + id = db.Column(db.Integer, primary_key=True) + username = db.Column(db.String(80), unique=True, nullable=False) + email = db.Column(db.String(80), unique=True, nullable=False) + _password = db.Column("password", db.String(255), nullable=False) + active = db.Column(db.Boolean, default=True) + + @hybrid_property + def password(self): + return self._password + + @password.setter + def password(self, password: str): + self._password = bcrypt.generate_password_hash(password).decode('utf-8') + + def check_password(self, password: str): + # 判断传过来的密码是否与数据库存的密码一致 + return bcrypt.check_password_hash(self._password, password) + + def to_dict(self) -> dict: + """object to dict""" + return { + 'id': self.id, + 'username': self.username, + 'email': self.email, + } + + def __repr__(self) -> str: + return f'' diff --git a/application/schemas/__init__.py b/application/schemas/__init__.py new file mode 100644 index 0000000000000000000000000000000000000000..11aa3135a76f65cf6d88b3b0beabfd3ef2b1ded2 --- /dev/null +++ b/application/schemas/__init__.py @@ -0,0 +1,14 @@ +#!/usr/bin/env python3 +# -*- coding: utf-8 -*- +""" +# @Version : Python 3.11.4 +# @Software : Sublime Text 4 +# @Author : StudentCWZ +# @Email : StudentCWZ@outlook.com +# @Date : 2023/10/28 22:31 +# @File : __init__.py.py +# @Description : +""" + +from .user import CreateUserItem +from .user import UserSchema diff --git a/application/schemas/user.py b/application/schemas/user.py new file mode 100644 index 0000000000000000000000000000000000000000..1a13d2a55e530df6e13f0bf6ba88c1d98b3a25e6 --- /dev/null +++ b/application/schemas/user.py @@ -0,0 +1,49 @@ +#!/usr/bin/env python3 +# -*- coding: utf-8 -*- +""" +# @Version : Python 3.11.4 +# @Software : Sublime Text 4 +# @Author : StudentCWZ +# @Email : StudentCWZ@outlook.com +# @Date : 2023/10/29 00:07 +# @File : user.py +# @Description : +""" + +from pydantic import BaseModel, EmailStr, field_validator +from application.extensions.init_marshmallow import ma +from application.extensions.init_sqlalchemy import db +from application.models import User +from marshmallow import validates, ValidationError + + +class CreateUserItem(BaseModel): + username: str + password: str + email: EmailStr + + @classmethod + @field_validator("username") + def name_must_contain_only_characters(cls, value): + if not value.isalpha(): + raise ValueError("Username must contain only characters.") + return value + + +class UserSchema(ma.SQLAlchemyAutoSchema): + + id = ma.Int(dump_only=True) + username = ma.Str(required=True) + password = ma.String(load_only=True, required=True) + email = ma.Email(required=True) + + class Meta: + model = User + sqla_session = db.session + load_instance = True + exclude = ("_password",) + + @validates('username') + def validate_username(self, username): + if len(username) < 3: + raise ValidationError('Username must be at least 3 characters.') \ No newline at end of file diff --git a/application/script/__init__.py b/application/script/__init__.py new file mode 100644 index 0000000000000000000000000000000000000000..fad2e9a39c58d2b1f170f21c1e6f958f0bd4bc6c --- /dev/null +++ b/application/script/__init__.py @@ -0,0 +1,20 @@ +#!/usr/bin/env python3 +# -*- coding: utf-8 -*- +""" +# @Version : Python 3.11.4 +# @Software : Sublime Text 4 +# @Author : StudentCWZ +# @Email : StudentCWZ@outlook.com +# @Date : 2023/10/29 11:21 +# @File : __init__.py.py +# @Description : +""" + + +from flask import Flask + +from .cmdline import init + + +def init_script(app: Flask) -> None: + app.cli.add_command(init) diff --git a/application/script/cmdline.py b/application/script/cmdline.py new file mode 100644 index 0000000000000000000000000000000000000000..d80507cbd61173644d5b39d3b3a8927b47e8f444 --- /dev/null +++ b/application/script/cmdline.py @@ -0,0 +1,28 @@ +#!/usr/bin/env python3 +# -*- coding: utf-8 -*- +""" +# @Version : Python 3.11.4 +# @Software : Sublime Text 4 +# @Author : StudentCWZ +# @Email : StudentCWZ@outlook.com +# @Date : 2023/10/29 11:21 +# @File : cmdline.py +# @Description : +""" + +import click +from flask.cli import with_appcontext + + +@click.command("init") +@with_appcontext +def init(): + """Create a new admin user""" + from application.extensions.init_sqlalchemy import db + from application.models import User + + click.echo("create user") + user = User(username="StudentCWZ", email="StudentCWZ@outlook.com", password="qwe!2345", active=True) + db.session.add(user) + db.session.commit() + click.echo("created user admin") diff --git a/application/services/__init__.py b/application/services/__init__.py new file mode 100644 index 0000000000000000000000000000000000000000..0c7559401ae467b2845bb535141c46a0b2cb9b65 --- /dev/null +++ b/application/services/__init__.py @@ -0,0 +1,13 @@ +#!/usr/bin/env python3 +# -*- coding: utf-8 -*- +""" +# @Version : Python 3.11.4 +# @Software : Sublime Text 4 +# @Author : StudentCWZ +# @Email : StudentCWZ@outlook.com +# @Date : 2023/10/28 22:29 +# @File : __init__.py +# @Description : +""" + +from .user import UserService diff --git a/application/services/user.py b/application/services/user.py new file mode 100644 index 0000000000000000000000000000000000000000..ce1983b63d87efbc4b619e8c0dfc6cce735b4318 --- /dev/null +++ b/application/services/user.py @@ -0,0 +1,28 @@ +#!/usr/bin/env python3 +# -*- coding: utf-8 -*- +""" +# @Version : Python 3.11.4 +# @Software : Sublime Text 4 +# @Author : StudentCWZ +# @Email : StudentCWZ@outlook.com +# @Date : 2023/10/29 00:03 +# @File : user.py +# @Description : +""" + +from application.dao import UserDao + + +class UserService: + + @classmethod + def get_all_users(cls): + return UserDao.get_all() + + @classmethod + def get_user_by_id(cls, user_id: int): + return UserDao.get_by_id(user_id) + + @classmethod + def create_user(cls, username: str, password: str, email: str): + return UserDao.create(username, password, email) diff --git a/application/utils/__init__.py b/application/utils/__init__.py new file mode 100644 index 0000000000000000000000000000000000000000..97761095c3460d774a08f8df73a4933a059a8ede --- /dev/null +++ b/application/utils/__init__.py @@ -0,0 +1,13 @@ +#!/usr/bin/env python3 +# -*- coding: utf-8 -*- +""" +# @Version : Python 3.11.4 +# @Software : Sublime Text 4 +# @Author : StudentCWZ +# @Email : StudentCWZ@outlook.com +# @Date : 2023/10/28 12:15 +# @File : __init__.py +# @Description : +""" + +from .dsn import dsn diff --git a/application/utils/dsn/__init__.py b/application/utils/dsn/__init__.py new file mode 100644 index 0000000000000000000000000000000000000000..57116622d6545692a031cc7ddfa1d7da138b7387 --- /dev/null +++ b/application/utils/dsn/__init__.py @@ -0,0 +1,13 @@ +#!/usr/bin/env python3 +# -*- coding: utf-8 -*- +""" +# @Version : Python 3.11.4 +# @Software : Sublime Text 4 +# @Author : StudentCWZ +# @Email : StudentCWZ@outlook.com +# @Date : 2023/10/29 00:36 +# @File : __init__.py +# @Description : +""" + +from .dsn import dsn diff --git a/application/utils/dsn/dsn.py b/application/utils/dsn/dsn.py new file mode 100644 index 0000000000000000000000000000000000000000..0f7b544ca5ffba9322928c9b9f0997b614dff25b --- /dev/null +++ b/application/utils/dsn/dsn.py @@ -0,0 +1,27 @@ +#!/usr/bin/env python3 +# -*- coding: utf-8 -*- +""" +# @Version : Python 3.11.4 +# @Software : Sublime Text 4 +# @Author : StudentCWZ +# @Email : StudentCWZ@outlook.com +# @Date : 2023/10/29 00:36 +# @File : dsn.py +# @Description : +""" + +from flask import Flask + + +def dsn(app: Flask) -> str: + """ + Initialize the MySQL dsn extension + + :param app: flask.Flask application instance + :return: dsn + """ + mysql_cfg = app.config.get('MySQL') + if mysql_cfg is None: + raise KeyError('Key MySQL error') + return (f'mysql+pymysql://{app.config.MySQL.USER}:' + f'{app.config.MySQL.PASSWORD}@{app.config.MySQL.HOST}:{app.config.MySQL.PORT}/{app.config.MySQL.DB}') diff --git a/application/utils/loaders/__init__.py b/application/utils/loaders/__init__.py new file mode 100644 index 0000000000000000000000000000000000000000..c80b422d8814ac8929baa845e54561007e77bb18 --- /dev/null +++ b/application/utils/loaders/__init__.py @@ -0,0 +1,11 @@ +#!/usr/bin/env python3 +# -*- coding: utf-8 -*- +""" +# @Version : Python 3.11.4 +# @Software : Sublime Text 4 +# @Author : StudentCWZ +# @Email : StudentCWZ@outlook.com +# @Date : 2023/10/28 12:16 +# @File : __init__.py +# @Description : +""" \ No newline at end of file diff --git a/application/utils/loaders/consul_loader.py b/application/utils/loaders/consul_loader.py new file mode 100644 index 0000000000000000000000000000000000000000..58297fc738b1b5a78f6746ebec0626ca4ce33d96 --- /dev/null +++ b/application/utils/loaders/consul_loader.py @@ -0,0 +1,86 @@ +#!/usr/bin/env python3 +# -*- coding: utf-8 -*- +""" +# @Version : Python 3.11.4 +# @Software : Sublime Text 4 +# @Author : StudentCWZ +# @Email : StudentCWZ@outlook.com +# @Date : 2023/10/28 12:16 +# @File : consul_loader.py +# @Description : +""" + +import os +from typing import NamedTuple, Union + +import requests +from dynaconf.base import LazySettings +from dynaconf.utils.parse_conf import parse_conf_data + +from application.lib import ConsulConfig + +IDENTIFIER = "consul_loader" + + +class SourceMetadata(NamedTuple): + loader: str + identifier: str + env: str + merged: bool = False + + +def load( + obj: LazySettings, + env: str = None, + silent: bool = True, + key: str = None, + validate=False, +) -> Union[bool, None]: + consul_host = os.environ.get('CONSUL_HOST', 'localhost') + consul_port = os.environ.get('CONSUL_PORT', 8500) + consul_dc = os.environ.get('CONSUL_DC', 'dc1') + consul_token = os.getenv('CONSUL_TOKEN') + consul_key = os.getenv('CONSUL_KEY') + + # 没有对应环境变量,会进入下一个加载器 + if consul_key is None: + return + + # 实例 ConsulConfig + client = ConsulConfig(host=consul_host, port=consul_port, token=consul_token, dc=consul_dc) + # 捕获异常 + try: + data = client.get(key=consul_key) + except requests.exceptions.ConnectionError: + print(2) + # 连接错误后,则会进入下一个加载器 + return + except Exception as e: + # 发生未知错误,才会抛出异常 + raise RuntimeError(f'Unknown error: {e}') + + # 基于 key 获取所需配置 + if key is not None: + data = data[key] + + if env is None: + return + + try: + # 获取 consul 注册中心的配置信息 + result = { + key: parse_conf_data(value, tomlfy=True, box_settings=obj) + for key, value in data.items() + } + except Exception as e: + if silent: + return False + raise e + else: + result['Consul'] = True + # 将 result 配置写入 dynaconf 内置配置中 + obj.update( + result, + loader_identifier=IDENTIFIER, + validate=validate, + ) diff --git a/application/utils/loaders/yaml_loader.py b/application/utils/loaders/yaml_loader.py new file mode 100644 index 0000000000000000000000000000000000000000..7234afda059b5f3d04886e10bef6025251e1b7b4 --- /dev/null +++ b/application/utils/loaders/yaml_loader.py @@ -0,0 +1,74 @@ +#!/usr/bin/env python3 +# -*- coding: utf-8 -*- +""" +# @Version : Python 3.11.4 +# @Software : Sublime Text 4 +# @Author : StudentCWZ +# @Email : StudentCWZ@outlook.com +# @Date : 2023/10/28 13:01 +# @File : yaml_loader.py +# @Description : +""" + +import os +from pathlib import Path +from typing import Union + +from dynaconf.base import LazySettings +from dynaconf.utils.parse_conf import parse_conf_data + +from application.lib import LocalConfig + +IDENTIFIER = "yaml_loader" + + +def load( + obj: LazySettings, + env: str = None, + silent: bool = True, + key: str = None, + validate=False, +) -> Union[bool, None]: + # 判断是否已经加载 consul 配置,如果加载,则不再加载本地配置 + if obj.get('Consul', False): + return + # 获取本地配置文件目录 + config_dir = os.path.join(str(Path(__file__).parent.parent.parent), 'config') + # 实例化 LocalConfig 对象 + local_cfg = LocalConfig(config_dir) + + # 判断当前是否为生产环境,基于不同环境读取本地不同配置文件 + if env == 'PRODUCTION': + filename = f'{env.lower()}_config.yaml' + else: + filename = 'config.yaml' + + # 基于 key 获取所需配置 + if key is None: + cfg = local_cfg.load(filename) + else: + cfg = local_cfg.load(filename)[key] + + try: + # 将 cfg 配置写入 dynaconf 内置配置中 + if cfg.get(env) is None: + result = { + key: parse_conf_data(value, tomlfy=True, box_settings=obj) + for key, value in cfg.items() + } + else: + result = { + key: parse_conf_data(value, tomlfy=True, box_settings=obj) + for key, value in cfg[env].items() + } + except Exception as e: + if silent: + return False + raise e + else: + if result: + obj.update( + result, + loader_identifier=IDENTIFIER, + validate=validate, + ) diff --git a/application/views/__init__.py b/application/views/__init__.py new file mode 100644 index 0000000000000000000000000000000000000000..327d917dd15436f9f82df4972ae0c7f4918ec072 --- /dev/null +++ b/application/views/__init__.py @@ -0,0 +1,19 @@ +#!/usr/bin/env python3 +# -*- coding: utf-8 -*- +""" +# @Version : Python 3.11.4 +# @Software : Sublime Text 4 +# @Author : StudentCWZ +# @Email : StudentCWZ@outlook.com +# @Date : 2023/10/28 22:29 +# @File : __init__.py.py +# @Description : +""" + +from flask import Flask + +from .user import register_user_views + + +def init_views(app: Flask) -> None: + register_user_views(app) diff --git a/application/views/user/__init__.py b/application/views/user/__init__.py new file mode 100644 index 0000000000000000000000000000000000000000..17d3c96c6ecfa009d3d496e79c477a1ed6b8260f --- /dev/null +++ b/application/views/user/__init__.py @@ -0,0 +1,19 @@ +#!/usr/bin/env python3 +# -*- coding: utf-8 -*- +""" +# @Version : Python 3.11.4 +# @Software : Sublime Text 4 +# @Author : StudentCWZ +# @Email : StudentCWZ@outlook.com +# @Date : 2023/10/29 00:18 +# @File : __init__.py.py +# @Description : +""" + +from flask import Flask + +from .user import user_api + + +def register_user_views(app: Flask): + app.register_blueprint(user_api) diff --git a/application/views/user/user.py b/application/views/user/user.py new file mode 100644 index 0000000000000000000000000000000000000000..fd754593894cd57a0711026798361e3c64c53c05 --- /dev/null +++ b/application/views/user/user.py @@ -0,0 +1,47 @@ +#!/usr/bin/env python3 +# -*- coding: utf-8 -*- +""" +# @Version : Python 3.11.4 +# @Software : Sublime Text 4 +# @Author : StudentCWZ +# @Email : StudentCWZ@outlook.com +# @Date : 2023/10/29 00:08 +# @File : user.py +# @Description : +""" + +from flask import Blueprint, request, jsonify +from application.services import UserService +from marshmallow import ValidationError + +from application.schemas import UserSchema + +user_api = Blueprint('user_api', __name__) + + +@user_api.route('/users', methods=['GET']) +def get_users(): + users = UserService.get_all_users() + return jsonify([user.to_dict() for user in users]) + + +@user_api.route('/users/', methods=['GET']) +def get_user(user_id): + user = UserService.get_user_by_id(user_id) + if user is None: + return jsonify({'error': 'User not found'}), 404 + return jsonify(user.to_dict()) + + +@user_api.route('/users', methods=['POST']) +def create_user(): + user_schema = UserSchema() + try: + data = user_schema.load(request.json) + except ValidationError as e: + return jsonify(e.messages), 400 + username = data.username + password = data.password + email = data.email + user = UserService.create_user(username, password, email) + return jsonify(user_schema.dump(user)), 201 diff --git a/requirements.txt b/requirements.txt new file mode 100644 index 0000000000000000000000000000000000000000..ecab6312f77f4cbd771fea5af99c85e69d656a5e --- /dev/null +++ b/requirements.txt @@ -0,0 +1,36 @@ +alembic==1.12.1 +annotated-types==0.6.0 +apispec==6.3.0 +bcrypt==4.0.1 +blinker==1.6.3 +certifi==2023.7.22 +charset-normalizer==3.3.1 +click==8.1.7 +dnspython==2.4.2 +dynaconf==3.2.3 +email-validator==2.1.0.post1 +Flask==3.0.0 +flask-apispec==0.11.4 +Flask-Bcrypt==1.0.1 +Flask-Migrate==4.0.5 +Flask-SQLAlchemy==3.1.1 +idna==3.4 +itsdangerous==2.1.2 +Jinja2==3.1.2 +loguru==0.7.2 +Mako==1.2.4 +MarkupSafe==2.1.3 +marshmallow==3.20.1 +packaging==23.2 +pydantic==2.4.2 +pydantic_core==2.10.1 +PyMySQL==1.1.0 +python-consul==1.1.0 +PyYAML==6.0.1 +requests==2.31.0 +six==1.16.0 +SQLAlchemy==2.0.22 +typing_extensions==4.8.0 +urllib3==2.0.7 +webargs==8.3.0 +Werkzeug==3.0.1