From 437b7eea4706a1aced5f5a24c4b0377a56014844 Mon Sep 17 00:00:00 2001 From: dayezi <1372755472@qq.com> Date: Tue, 16 Apr 2024 16:33:31 +0800 Subject: [PATCH 01/31] =?UTF-8?q?=F0=9F=90=B3=20chore(config):=20=E4=BF=AE?= =?UTF-8?q?=E6=94=B9=E9=85=8D=E7=BD=AE=E6=96=87=E4=BB=B6=E4=B8=BAconfig.ym?= =?UTF-8?q?l?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .gitignore | 2 + backend/app/settings/config.py | 263 +++++++++++++++++++++------------ backend/poetry.lock | 89 ++++++++++- backend/pyproject.toml | 1 + frontend/package.json | 2 +- 5 files changed, 259 insertions(+), 98 deletions(-) diff --git a/.gitignore b/.gitignore index d3aef28..adcbd1a 100644 --- a/.gitignore +++ b/.gitignore @@ -1,4 +1,6 @@ # backend +*.yml +*.toml .env.* .env *.pyc diff --git a/backend/app/settings/config.py b/backend/app/settings/config.py index 6089f86..83e25d4 100644 --- a/backend/app/settings/config.py +++ b/backend/app/settings/config.py @@ -1,112 +1,183 @@ -import os -from dotenv import load_dotenv from pathlib import Path -from typing import List +from ruamel.yaml import YAML -from pydantic_settings import BaseSettings +config_path = Path.joinpath(Path(__file__).parent.parent.parent, "config.yml") +yaml = YAML() -env_path = Path.joinpath(Path(__file__).parent.parent.parent, ".env") -load_dotenv(dotenv_path=env_path, verbose=True, override=True) +class Settings: + DEBUG: bool = True + DATETIME_FORMAT: str = "%Y-%m-%d %H:%M:%S" -class Settings(BaseSettings): - PROJECT_NAME: str - VERSION: str - APP_TITLE: str - APP_DESCRIPTION: str + def __init__(self): + with open(config_path, "rb") as f: + self.data = yaml.load(f) - HOST: str - PORT: int - RELOAD: bool + def _save(self): + with open(config_path, "w", encoding="utf-8") as f: + yaml.dump(self.data, f) - CORS_ORIGINS: List = ["*"] - CORS_ALLOW_CREDENTIALS: bool = True - CORS_ALLOW_METHODS: List = ["*"] - CORS_ALLOW_HEADERS: List = ["*"] + @property + def APP_TITLE(self) -> str: + return self.data["app"]["title"] - DEBUG: bool = True + @APP_TITLE.setter + def APP_TITLE(self, value: str): + self.data["app"]["title"] = value - PROJECT_ROOT: str = os.path.abspath(os.path.join(os.path.dirname(__file__), os.pardir)) - BASE_DIR: str = os.path.abspath(os.path.join(PROJECT_ROOT, os.pardir)) - LOGS_ROOT: str = os.path.join(BASE_DIR, "app/logs") - SECRET_KEY: str # openssl rand -hex 32 - JWT_ALGORITHM: str # HS256 - JWT_ACCESS_TOKEN_EXPIRE_MINUTES: int - JWT_REFRESH_TOKEN_EXPIRE_MINUTES: int - - TORTOISE_ORM: dict = { - "connections": { - # "sqlite": { - # "engine": "tortoise.backends.sqlite", - # "credentials": {"file_path": f"{BASE_DIR}/db.sqlite3"}, - # }, - "mysql": { - "engine": "tortoise.backends.mysql", - "credentials": { - "host": os.getenv("DB_HOST"), - "port": os.getenv("DB_PORT"), - "user": os.getenv("DB_USERNAME"), - "password": os.getenv("DB_PASSWORD"), - "database": os.getenv("DB_NAME"), - } - } - }, - "apps": { - "models": { - "models": ["app.models"], - "default_connection": "mysql", - }, - }, - "use_tz": False, - "timezone": "Asia/Shanghai", - } - DATETIME_FORMAT: str = "%Y-%m-%d %H:%M:%S" + @property + def APP_DESCRIPTION(self): + return self.data["app"]["description"] - APP_LOG_CONFIG: object = { - "version": 1, - "disable_existing_loggers": False, - "formatters": { - "default": { - "()": "uvicorn.logging.DefaultFormatter", - "fmt": "%(levelprefix)s %(message)s", - "use_colors": "null" - }, - "access": { - "()": "uvicorn.logging.AccessFormatter", - "fmt": "%(asctime)s - %(levelprefix)s %(client_addr)s - \"%(request_line)s\" %(status_code)s" - } - }, - "handlers": { - "default": { - "formatter": "default", - "class": "logging.StreamHandler", - "stream": "ext://sys.stderr" - }, - "access": { - "formatter": "access", - "class": "logging.StreamHandler", - "stream": "ext://sys.stdout" - } - }, - "loggers": { - "uvicorn": { - "handlers": [ - "default" - ], - "level": "INFO" + @property + def VERSION(self): + return self.data["app"]["version"] + + @property + def HOST(self) -> str: + return self.data["server"]["host"] + + @property + def PORT(self) -> int: + return self.data["server"]["port"] + + @property + def RELOAD(self) -> bool: + return self.data["server"]["reload"] + + @property + def CORS_ORIGINS(self) -> list[str]: + return self.data["server"]["cors_origins"] + + @property + def CORS_ALLOW_CREDENTIALS(self) -> bool: + return self.data["server"]["cors_allow_credentials"] + + @property + def CORS_ALLOW_METHODS(self) -> list[str]: + return self.data["server"]["cors_allow_methods"] + + @property + def CORS_ALLOW_HEADERS(self) -> list[str]: + return self.data["server"]["cors_allow_headers"] + + @property + def SECRET_KEY(self): + return self.data["secret"]["secret_key"] + + @SECRET_KEY.setter + def SECRET_KEY(self, value: str): + self.data["secret"]["secret_key"] = value + + @property + def JWT_ALGORITHM(self): + return self.data["secret"]["jwt_algorithm"] + + @property + def JWT_ACCESS_TOKEN_EXPIRE_MINUTES(self): + return self.data["secret"]["jwt_access_token_expire_min"] + + @property + def JWT_REFRESH_TOKEN_EXPIRE_MINUTES(self): + return self.data["secret"]["jwt_refresh_token_expire_min"] + + @property + def DATABASE_START(self): + return self.data["db"]["start"] + + @DATABASE_START.setter + def DATABASE_START(self, value: str): + self.data["db"]["start"] = value + self._save() + + @property + def DATABASE_HOST(self): + return self.data["db"]["host"] + + @DATABASE_HOST.setter + def DATABASE_HOST(self, value: int): + self.data["db"]["host"] = value + self._save() + + @property + def DATABASE_PORT(self) -> int: + """ + 数据库端口 + """ + return self.data["db"]["port"] + + @DATABASE_PORT.setter + def DATABASE_PORT(self, value: int): + self.data["db"]["port"] = value + self._save() + + @property + def DATABASE_USERNAME(self) -> str: + """ + 数据库用户名 + """ + return self.data["db"]["username"] + + @DATABASE_USERNAME.setter + def DATABASE_USERNAME(self, value: str): + self.data["db"]["username"] = value + self._save() + + @property + def DATABASE_PASSWORD(self) -> str: + """ + 数据库密码 + """ + return self.data["db"]["password"] + + @DATABASE_PASSWORD.setter + def DATABASE_PASSWORD(self, value: str): + self.data["db"]["password"] = value + self._save() + + @property + def DATABASE_NAME(self) -> str: + """ + 数据库名 + """ + return self.data["db"]["name"] + + @DATABASE_NAME.setter + def DATABASE_NAME(self, value: str): + self.data["db"]["name"] = value + self._save() + + @property + def TORTOISE_ORM(self): + return { + "connections": { + "default": { + "engine": "tortoise.backends.mysql", + "credentials": { + "host": self.DATABASE_HOST, + "port": self.DATABASE_PORT, + "user": self.DATABASE_USERNAME, + "password": self.DATABASE_PASSWORD, + "database": self.DATABASE_NAME, + } + } }, - "uvicorn.error": { - "level": "INFO" + "apps": { + "models": { + "models": ["app.models"], + "default_connection": "default", + }, }, - "uvicorn.access": { - "handlers": [ - "access" - ], - "level": "INFO", - "propagate": False - } + "use_tz": False, + "timezone": "Asia/Shanghai", } - } + + @property + def APP_LOG_CONFIG(self) -> dict: + return self.data["log"] settings = Settings() + +if __name__ == '__main__': + print(settings.data) diff --git a/backend/poetry.lock b/backend/poetry.lock index ae980b9..744e31d 100644 --- a/backend/poetry.lock +++ b/backend/poetry.lock @@ -892,6 +892,93 @@ type = "legacy" url = "https://pypi.tuna.tsinghua.edu.cn/simple" reference = "tsinghua" +[[package]] +name = "ruamel-yaml" +version = "0.18.6" +description = "ruamel.yaml is a YAML parser/emitter that supports roundtrip preservation of comments, seq/map flow style, and map key order" +optional = false +python-versions = ">=3.7" +files = [ + {file = "ruamel.yaml-0.18.6-py3-none-any.whl", hash = "sha256:57b53ba33def16c4f3d807c0ccbc00f8a6081827e81ba2491691b76882d0c636"}, + {file = "ruamel.yaml-0.18.6.tar.gz", hash = "sha256:8b27e6a217e786c6fbe5634d8f3f11bc63e0f80f6a5890f28863d9c45aac311b"}, +] + +[package.dependencies] +"ruamel.yaml.clib" = {version = ">=0.2.7", markers = "platform_python_implementation == \"CPython\" and python_version < \"3.13\""} + +[package.extras] +docs = ["mercurial (>5.7)", "ryd"] +jinja2 = ["ruamel.yaml.jinja2 (>=0.2)"] + +[package.source] +type = "legacy" +url = "https://pypi.tuna.tsinghua.edu.cn/simple" +reference = "tsinghua" + +[[package]] +name = "ruamel-yaml-clib" +version = "0.2.8" +description = "C version of reader, parser and emitter for ruamel.yaml derived from libyaml" +optional = false +python-versions = ">=3.6" +files = [ + {file = "ruamel.yaml.clib-0.2.8-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:b42169467c42b692c19cf539c38d4602069d8c1505e97b86387fcf7afb766e1d"}, + {file = "ruamel.yaml.clib-0.2.8-cp310-cp310-macosx_13_0_arm64.whl", hash = "sha256:07238db9cbdf8fc1e9de2489a4f68474e70dffcb32232db7c08fa61ca0c7c462"}, + {file = "ruamel.yaml.clib-0.2.8-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_24_x86_64.whl", hash = "sha256:fff3573c2db359f091e1589c3d7c5fc2f86f5bdb6f24252c2d8e539d4e45f412"}, + {file = "ruamel.yaml.clib-0.2.8-cp310-cp310-manylinux_2_24_aarch64.whl", hash = "sha256:aa2267c6a303eb483de8d02db2871afb5c5fc15618d894300b88958f729ad74f"}, + {file = "ruamel.yaml.clib-0.2.8-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:840f0c7f194986a63d2c2465ca63af8ccbbc90ab1c6001b1978f05119b5e7334"}, + {file = "ruamel.yaml.clib-0.2.8-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:024cfe1fc7c7f4e1aff4a81e718109e13409767e4f871443cbff3dba3578203d"}, + {file = "ruamel.yaml.clib-0.2.8-cp310-cp310-win32.whl", hash = "sha256:c69212f63169ec1cfc9bb44723bf2917cbbd8f6191a00ef3410f5a7fe300722d"}, + {file = "ruamel.yaml.clib-0.2.8-cp310-cp310-win_amd64.whl", hash = "sha256:cabddb8d8ead485e255fe80429f833172b4cadf99274db39abc080e068cbcc31"}, + {file = "ruamel.yaml.clib-0.2.8-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:bef08cd86169d9eafb3ccb0a39edb11d8e25f3dae2b28f5c52fd997521133069"}, + {file = "ruamel.yaml.clib-0.2.8-cp311-cp311-macosx_13_0_arm64.whl", hash = "sha256:b16420e621d26fdfa949a8b4b47ade8810c56002f5389970db4ddda51dbff248"}, + {file = "ruamel.yaml.clib-0.2.8-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_24_x86_64.whl", hash = "sha256:25c515e350e5b739842fc3228d662413ef28f295791af5e5110b543cf0b57d9b"}, + {file = "ruamel.yaml.clib-0.2.8-cp311-cp311-manylinux_2_24_aarch64.whl", hash = "sha256:1707814f0d9791df063f8c19bb51b0d1278b8e9a2353abbb676c2f685dee6afe"}, + {file = "ruamel.yaml.clib-0.2.8-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:46d378daaac94f454b3a0e3d8d78cafd78a026b1d71443f4966c696b48a6d899"}, + {file = "ruamel.yaml.clib-0.2.8-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:09b055c05697b38ecacb7ac50bdab2240bfca1a0c4872b0fd309bb07dc9aa3a9"}, + {file = "ruamel.yaml.clib-0.2.8-cp311-cp311-win32.whl", hash = "sha256:53a300ed9cea38cf5a2a9b069058137c2ca1ce658a874b79baceb8f892f915a7"}, + {file = "ruamel.yaml.clib-0.2.8-cp311-cp311-win_amd64.whl", hash = "sha256:c2a72e9109ea74e511e29032f3b670835f8a59bbdc9ce692c5b4ed91ccf1eedb"}, + {file = "ruamel.yaml.clib-0.2.8-cp312-cp312-macosx_10_9_universal2.whl", hash = "sha256:ebc06178e8821efc9692ea7544aa5644217358490145629914d8020042c24aa1"}, + {file = "ruamel.yaml.clib-0.2.8-cp312-cp312-macosx_13_0_arm64.whl", hash = "sha256:edaef1c1200c4b4cb914583150dcaa3bc30e592e907c01117c08b13a07255ec2"}, + {file = "ruamel.yaml.clib-0.2.8-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:d176b57452ab5b7028ac47e7b3cf644bcfdc8cacfecf7e71759f7f51a59e5c92"}, + {file = "ruamel.yaml.clib-0.2.8-cp312-cp312-manylinux_2_24_aarch64.whl", hash = "sha256:1dc67314e7e1086c9fdf2680b7b6c2be1c0d8e3a8279f2e993ca2a7545fecf62"}, + {file = "ruamel.yaml.clib-0.2.8-cp312-cp312-musllinux_1_1_i686.whl", hash = "sha256:3213ece08ea033eb159ac52ae052a4899b56ecc124bb80020d9bbceeb50258e9"}, + {file = "ruamel.yaml.clib-0.2.8-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:aab7fd643f71d7946f2ee58cc88c9b7bfc97debd71dcc93e03e2d174628e7e2d"}, + {file = "ruamel.yaml.clib-0.2.8-cp312-cp312-win32.whl", hash = "sha256:5c365d91c88390c8d0a8545df0b5857172824b1c604e867161e6b3d59a827eaa"}, + {file = "ruamel.yaml.clib-0.2.8-cp312-cp312-win_amd64.whl", hash = "sha256:1758ce7d8e1a29d23de54a16ae867abd370f01b5a69e1a3ba75223eaa3ca1a1b"}, + {file = "ruamel.yaml.clib-0.2.8-cp36-cp36m-manylinux_2_5_x86_64.manylinux1_x86_64.whl", hash = "sha256:a5aa27bad2bb83670b71683aae140a1f52b0857a2deff56ad3f6c13a017a26ed"}, + {file = "ruamel.yaml.clib-0.2.8-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:c58ecd827313af6864893e7af0a3bb85fd529f862b6adbefe14643947cfe2942"}, + {file = "ruamel.yaml.clib-0.2.8-cp37-cp37m-macosx_12_0_arm64.whl", hash = "sha256:f481f16baec5290e45aebdc2a5168ebc6d35189ae6fea7a58787613a25f6e875"}, + {file = "ruamel.yaml.clib-0.2.8-cp37-cp37m-manylinux_2_24_aarch64.whl", hash = "sha256:77159f5d5b5c14f7c34073862a6b7d34944075d9f93e681638f6d753606c6ce6"}, + {file = "ruamel.yaml.clib-0.2.8-cp37-cp37m-manylinux_2_5_x86_64.manylinux1_x86_64.whl", hash = "sha256:7f67a1ee819dc4562d444bbafb135832b0b909f81cc90f7aa00260968c9ca1b3"}, + {file = "ruamel.yaml.clib-0.2.8-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:4ecbf9c3e19f9562c7fdd462e8d18dd902a47ca046a2e64dba80699f0b6c09b7"}, + {file = "ruamel.yaml.clib-0.2.8-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:87ea5ff66d8064301a154b3933ae406b0863402a799b16e4a1d24d9fbbcbe0d3"}, + {file = "ruamel.yaml.clib-0.2.8-cp37-cp37m-win32.whl", hash = "sha256:75e1ed13e1f9de23c5607fe6bd1aeaae21e523b32d83bb33918245361e9cc51b"}, + {file = "ruamel.yaml.clib-0.2.8-cp37-cp37m-win_amd64.whl", hash = "sha256:3f215c5daf6a9d7bbed4a0a4f760f3113b10e82ff4c5c44bec20a68c8014f675"}, + {file = "ruamel.yaml.clib-0.2.8-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:1b617618914cb00bf5c34d4357c37aa15183fa229b24767259657746c9077615"}, + {file = "ruamel.yaml.clib-0.2.8-cp38-cp38-macosx_12_0_arm64.whl", hash = "sha256:a6a9ffd280b71ad062eae53ac1659ad86a17f59a0fdc7699fd9be40525153337"}, + {file = "ruamel.yaml.clib-0.2.8-cp38-cp38-manylinux_2_24_aarch64.whl", hash = "sha256:305889baa4043a09e5b76f8e2a51d4ffba44259f6b4c72dec8ca56207d9c6fe1"}, + {file = "ruamel.yaml.clib-0.2.8-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.whl", hash = "sha256:700e4ebb569e59e16a976857c8798aee258dceac7c7d6b50cab63e080058df91"}, + {file = "ruamel.yaml.clib-0.2.8-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:e2b4c44b60eadec492926a7270abb100ef9f72798e18743939bdbf037aab8c28"}, + {file = "ruamel.yaml.clib-0.2.8-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:e79e5db08739731b0ce4850bed599235d601701d5694c36570a99a0c5ca41a9d"}, + {file = "ruamel.yaml.clib-0.2.8-cp38-cp38-win32.whl", hash = "sha256:955eae71ac26c1ab35924203fda6220f84dce57d6d7884f189743e2abe3a9fbe"}, + {file = "ruamel.yaml.clib-0.2.8-cp38-cp38-win_amd64.whl", hash = "sha256:56f4252222c067b4ce51ae12cbac231bce32aee1d33fbfc9d17e5b8d6966c312"}, + {file = "ruamel.yaml.clib-0.2.8-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:03d1162b6d1df1caa3a4bd27aa51ce17c9afc2046c31b0ad60a0a96ec22f8001"}, + {file = "ruamel.yaml.clib-0.2.8-cp39-cp39-macosx_12_0_arm64.whl", hash = "sha256:bba64af9fa9cebe325a62fa398760f5c7206b215201b0ec825005f1b18b9bccf"}, + {file = "ruamel.yaml.clib-0.2.8-cp39-cp39-manylinux_2_24_aarch64.whl", hash = "sha256:a1a45e0bb052edf6a1d3a93baef85319733a888363938e1fc9924cb00c8df24c"}, + {file = "ruamel.yaml.clib-0.2.8-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.whl", hash = "sha256:da09ad1c359a728e112d60116f626cc9f29730ff3e0e7db72b9a2dbc2e4beed5"}, + {file = "ruamel.yaml.clib-0.2.8-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:184565012b60405d93838167f425713180b949e9d8dd0bbc7b49f074407c5a8b"}, + {file = "ruamel.yaml.clib-0.2.8-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:a75879bacf2c987c003368cf14bed0ffe99e8e85acfa6c0bfffc21a090f16880"}, + {file = "ruamel.yaml.clib-0.2.8-cp39-cp39-win32.whl", hash = "sha256:84b554931e932c46f94ab306913ad7e11bba988104c5cff26d90d03f68258cd5"}, + {file = "ruamel.yaml.clib-0.2.8-cp39-cp39-win_amd64.whl", hash = "sha256:25ac8c08322002b06fa1d49d1646181f0b2c72f5cbc15a85e80b4c30a544bb15"}, + {file = "ruamel.yaml.clib-0.2.8.tar.gz", hash = "sha256:beb2e0404003de9a4cab9753a8805a8fe9320ee6673136ed7f04255fe60bb512"}, +] + +[package.source] +type = "legacy" +url = "https://pypi.tuna.tsinghua.edu.cn/simple" +reference = "tsinghua" + [[package]] name = "ruff" version = "0.0.281" @@ -1053,4 +1140,4 @@ reference = "tsinghua" [metadata] lock-version = "2.0" python-versions = "^3.11" -content-hash = "938a1e35aebd8b07f3ccee08cbeb73019d91db27b2ad22f5a44e3b6da30e44c3" +content-hash = "467dd11b74429edc6890f20b80e672d5c0142ec0d7498b93568be9eee28e3659" diff --git a/backend/pyproject.toml b/backend/pyproject.toml index cef2912..9ca0eae 100644 --- a/backend/pyproject.toml +++ b/backend/pyproject.toml @@ -23,6 +23,7 @@ pydantic-settings = "^2.0.3" argon2-cffi = "^23.1.0" python-dotenv = "^1.0.1" cryptography = "^42.0.5" +ruamel-yaml = "^0.18.6" [tool.black] line-length = 120 diff --git a/frontend/package.json b/frontend/package.json index a142387..aea8138 100644 --- a/frontend/package.json +++ b/frontend/package.json @@ -1,6 +1,6 @@ { "name": "material-manager", - "version": "0.0.1", + "version": "0.0.2", "private": true, "type": "module", "scripts": { From 04d559c73060789f6347cd351c9563722ba1115c Mon Sep 17 00:00:00 2001 From: dayezi <1372755472@qq.com> Date: Tue, 16 Apr 2024 22:26:19 +0800 Subject: [PATCH 02/31] =?UTF-8?q?=E2=9C=A8=20feat(superAdmin):=20=E6=96=B0?= =?UTF-8?q?=E5=A2=9E=E8=B6=85=E7=AE=A1=E8=8F=9C=E5=8D=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 1. 新增超管页面 2. 初步移除应用启动时加载数据库,修改为启动后自定义配置启动 3. 设置超管用户信息为本地储存 4. 新增数据库配置依赖,数据库未配置时大部分api将返回402 5. 新增数据库测试链接,配置数据库api --- backend/app/__init__.py | 11 ++--- backend/app/api/v1/__init__.py | 14 ++++--- backend/app/api/v1/admin/__init__.py | 12 ++++++ backend/app/api/v1/admin/admin.py | 30 ++++++++++++++ backend/app/api/v1/base/base.py | 11 +++-- backend/app/api/v1/users/users.py | 3 +- backend/app/core/dependency.py | 45 ++++++++++++--------- backend/app/core/init_app.py | 27 ------------- backend/app/core/init_db.py | 47 ++++++++++++++++++++++ backend/app/schemas/admin.py | 14 +++++++ backend/app/schemas/base.py | 1 - backend/app/settings/config.py | 18 ++++++++- backend/poetry.lock | 22 +++++++++- backend/pyproject.toml | 1 + frontend/src/router/modules/superAdmin.ts | 20 +++++++++ frontend/src/views/superAdmin/Settings.vue | 13 ++++++ 16 files changed, 219 insertions(+), 70 deletions(-) create mode 100644 backend/app/api/v1/admin/__init__.py create mode 100644 backend/app/api/v1/admin/admin.py create mode 100644 backend/app/core/init_db.py create mode 100644 backend/app/schemas/admin.py create mode 100644 frontend/src/router/modules/superAdmin.ts create mode 100644 frontend/src/views/superAdmin/Settings.vue diff --git a/backend/app/__init__.py b/backend/app/__init__.py index 5b1bbfc..a169111 100644 --- a/backend/app/__init__.py +++ b/backend/app/__init__.py @@ -2,10 +2,7 @@ from app.core.exceptions import SettingNotFound from app.core.init_app import ( - init_menus, - init_superuser, make_middlewares, - register_db, register_exceptions, register_routers, ) @@ -24,7 +21,6 @@ def create_app() -> FastAPI: openapi_url="/openapi.json", middleware=make_middlewares(), ) - register_db(app) register_exceptions(app) register_routers(app, prefix="/api") return app @@ -33,7 +29,6 @@ def create_app() -> FastAPI: app = create_app() -@app.on_event("startup") -async def startup_event(): - await init_superuser() - await init_menus() +# @app.on_event("startup") +# async def startup_event(): +# await init_menus() diff --git a/backend/app/api/v1/__init__.py b/backend/app/api/v1/__init__.py index 9a32a88..0ae1d87 100644 --- a/backend/app/api/v1/__init__.py +++ b/backend/app/api/v1/__init__.py @@ -1,6 +1,6 @@ from fastapi import APIRouter -from app.core.dependency import DependPermisson +from app.core.dependency import DependPermission from .apis import apis_router from .base import base_router @@ -8,12 +8,14 @@ from .roles import roles_router from .users import users_router from .material import material_router +from .admin import admin_router v1_router = APIRouter() v1_router.include_router(base_router, prefix="/base") -v1_router.include_router(users_router, prefix="/user", dependencies=[DependPermisson]) -v1_router.include_router(roles_router, prefix="/role", dependencies=[DependPermisson]) -v1_router.include_router(menus_router, prefix="/menu", dependencies=[DependPermisson]) -v1_router.include_router(apis_router, prefix="/api", dependencies=[DependPermisson]) -v1_router.include_router(material_router, prefix="/material", dependencies=[DependPermisson]) +v1_router.include_router(users_router, prefix="/user", dependencies=[DependPermission]) +v1_router.include_router(roles_router, prefix="/role", dependencies=[DependPermission]) +v1_router.include_router(menus_router, prefix="/menu", dependencies=[DependPermission]) +v1_router.include_router(apis_router, prefix="/api", dependencies=[DependPermission]) +v1_router.include_router(material_router, prefix="/material", dependencies=[DependPermission]) +v1_router.include_router(admin_router, prefix="/admin", dependencies=[DependPermission]) diff --git a/backend/app/api/v1/admin/__init__.py b/backend/app/api/v1/admin/__init__.py new file mode 100644 index 0000000..678f1b6 --- /dev/null +++ b/backend/app/api/v1/admin/__init__.py @@ -0,0 +1,12 @@ +# coding=utf-8 +# @FileName :__init__.py.py +# @Time :2024/4/16 下午9:30 +# @Author :dayezi +from fastapi import APIRouter + +from .admin import router + +admin_router = APIRouter() +admin_router.include_router(router, tags=["管理员模块"]) + +__all__ = ["admin_router"] diff --git a/backend/app/api/v1/admin/admin.py b/backend/app/api/v1/admin/admin.py new file mode 100644 index 0000000..feab97d --- /dev/null +++ b/backend/app/api/v1/admin/admin.py @@ -0,0 +1,30 @@ +# coding=utf-8 +# @FileName :admin.py +# @Time :2024/4/16 下午9:31 +# @Author :dayezi +from fastapi import APIRouter + +from app.core.init_db import test_db, set_db +from app.log import logger +from app.schemas import Success, Fail +from app.schemas.admin import DbInfo + +router = APIRouter() + + +@router.post("/test_db", summary="测试数据库连接") +async def test_db_conn(data: DbInfo): + if test_db(data): + return Success(msg="数据库链接成功!") + else: + return Fail(msg="数据库链接失败!") + + +@router.post("/set_db", summary="设置数据库连接") +async def set_db_conn(data: DbInfo): + try: + set_db(data) + return Success(msg="数据库设置成功!") + except Exception as e: + logger.error(e) + return Fail(msg="数据库设置失败!") diff --git a/backend/app/api/v1/base/base.py b/backend/app/api/v1/base/base.py index db2ed02..1a62f73 100644 --- a/backend/app/api/v1/base/base.py +++ b/backend/app/api/v1/base/base.py @@ -1,4 +1,4 @@ -from datetime import datetime, timedelta +from datetime import timedelta from fastapi import APIRouter from jwt.exceptions import ExpiredSignatureError @@ -9,7 +9,7 @@ from app.models.admin import Api, Menu, Role, User from app.schemas.base import Fail, Success, FailAuth from app.schemas.login import * -from app.schemas.users import UpdatePassword +from app.schemas.users import UpdatePassword, BaseUser from app.settings import settings from app.utils.jwtt import create_access_token, decode_access_token from app.utils.password import get_password_hash, verify_password @@ -19,7 +19,10 @@ @router.post("/accessToken", summary="获取token") async def login_access_token(credentials: CredentialsSchema): - user: User = await user_controller.authenticate(credentials) + if credentials.username == "admin": + user: BaseUser = BaseUser.parse_obj(settings.SUPER_USER) + else: + user: User = await user_controller.authenticate(credentials) await user_controller.update_last_login(user.id) access_token_expires = timedelta(minutes=settings.JWT_ACCESS_TOKEN_EXPIRE_MINUTES) refresh_token_expires = timedelta(minutes=settings.JWT_REFRESH_TOKEN_EXPIRE_MINUTES) @@ -29,7 +32,7 @@ async def login_access_token(credentials: CredentialsSchema): data = JWTOut( username=user.username, depart=user.depart, - roles=["admin"], + roles=user.roles, accessToken=create_access_token( data=JWTPayload( user_id=user.id, diff --git a/backend/app/api/v1/users/users.py b/backend/app/api/v1/users/users.py index fcce163..9b83e98 100644 --- a/backend/app/api/v1/users/users.py +++ b/backend/app/api/v1/users/users.py @@ -5,7 +5,6 @@ from tortoise.expressions import Q from app.controllers.user import UserController -from app.core.dependency import DependPermisson from app.schemas.base import Success, SuccessExtra from app.schemas.users import * @@ -48,7 +47,7 @@ async def create_user( ): user_controller = UserController() user = await user_controller.get_by_email(user_in.email) - if user: + if (user is None) | (user_in.username == "admin"): raise HTTPException( status_code=400, detail="The user with this email already exists in the system.", diff --git a/backend/app/core/dependency.py b/backend/app/core/dependency.py index 8fd952c..8fe1786 100644 --- a/backend/app/core/dependency.py +++ b/backend/app/core/dependency.py @@ -8,28 +8,35 @@ from app.settings import settings +class DataBaseControl: + @classmethod + async def has_db(cls) -> bool: + if settings.DATABASE_START is not None: + return True + else: + raise HTTPException(status_code=402, detail="数据库未配置!") + + class AuthControl: @classmethod - async def is_authed(cls, authorization: str = Header(..., description="token验证")) -> Optional["User"]: - try: - token = authorization.split(" ")[1] - if token == "dev": - user = await User.filter().first() - user_id = user.id - else: + async def is_authed(cls, authorization: str = Header(..., description="token验证"), + db: bool = Depends(DataBaseControl.has_db)) -> Optional["User"]: + if db: + try: + token = authorization.split(" ")[1] decode_data = jwt.decode(token, settings.SECRET_KEY, algorithms=settings.JWT_ALGORITHM) user_id = decode_data.get("user_id") - user = await User.filter(id=user_id).first() - if not user: - raise HTTPException(status_code=401, detail="Authentication failed") - CTX_USER_ID.set(int(user_id)) - return user - except jwt.DecodeError: - raise HTTPException(status_code=401, detail="无效的Token") - except jwt.ExpiredSignatureError: - raise HTTPException(status_code=401, detail="登录已过期") - except Exception as e: - raise HTTPException(status_code=500, detail=f"{repr(e)}") + user = await User.filter(id=user_id).first() + if not user: + raise HTTPException(status_code=401, detail="Authentication failed") + CTX_USER_ID.set(int(user_id)) + return user + except jwt.DecodeError: + raise HTTPException(status_code=401, detail="无效的Token") + except jwt.ExpiredSignatureError: + raise HTTPException(status_code=401, detail="登录已过期") + except Exception as e: + raise HTTPException(status_code=500, detail=f"{repr(e)}") class PermissionControl: @@ -51,4 +58,4 @@ async def has_permission(cls, request: Request, current_user: User = Depends(Aut DependAuth = Depends(AuthControl.is_authed) -DependPermisson = Depends(PermissionControl.has_permission) +DependPermission = Depends(PermissionControl.has_permission) diff --git a/backend/app/core/init_app.py b/backend/app/core/init_app.py index 4c73bb7..b28472b 100644 --- a/backend/app/core/init_app.py +++ b/backend/app/core/init_app.py @@ -1,10 +1,8 @@ from fastapi import FastAPI from fastapi.middleware import Middleware from fastapi.middleware.cors import CORSMiddleware -from tortoise.contrib.fastapi import register_tortoise from app.api import api_router -from app.controllers.user import UserCreate, user_controller from app.core.exceptions import ( DoesNotExist, DoesNotExistHandle, @@ -38,16 +36,6 @@ def make_middlewares(): return middleware -def register_db(app: FastAPI, db_url=None): - register_tortoise( - app, - # db_url='sqlite://db.sqlite3', - # modules={'models':['app.models', "aerich.models"]}, - config=settings.TORTOISE_ORM, - generate_schemas=True, - ) - - def register_exceptions(app: FastAPI): app.add_exception_handler(DoesNotExist, DoesNotExistHandle) app.add_exception_handler(HTTPException, HttpExcHandle) @@ -60,21 +48,6 @@ def register_routers(app: FastAPI, prefix: str = "/api"): app.include_router(api_router, prefix=prefix) -async def init_superuser(): - user = await user_controller.model.exists() - if not user: - await user_controller.create( - UserCreate( - username="admin", - depart="管理部", - email="admin@admin.com", - password="admin123456", - is_active=True, - is_superuser=True, - ) - ) - - async def init_menus(): menus = await Menu.exists() if not menus: diff --git a/backend/app/core/init_db.py b/backend/app/core/init_db.py new file mode 100644 index 0000000..2dd4ea0 --- /dev/null +++ b/backend/app/core/init_db.py @@ -0,0 +1,47 @@ +# coding=utf-8 +# @FileName :init_db.py +# @Time :2024/4/16 上午2:53 +# @Author :dayezi +from tortoise import Tortoise, run_async +import pymysql +from app.settings import settings +from app.log import logger +from app.schemas.admin import DbInfo + + +async def tortoise_init(): + await Tortoise.init(config=settings.TORTOISE_ORM) + await Tortoise.generate_schemas(safe=True) + + +def test_db(db_info: DbInfo) -> bool: + try: + conn = pymysql.connect( + host=db_info.db_host, + user=db_info.db_user, + password=db_info.db_password, + db=db_info.db_name, + port=db_info.db_port) + with conn.cursor() as cursor: + cursor.execute("SELECT 1") + result = cursor.fetchone() + if result[0] != 1: + logger.info("数据库测试失败,失败原因:数据库连接失败") + return False + logger.info("数据库测试成功") + conn.close() + return True + except Exception as e: + logger.info(f"数据库测试失败,失败原因:{e}") + return False + + +def set_db(db_info: DbInfo): + settings.DATABASE_START = db_info.db_start + settings.DATABASE_HOST = db_info.db_host + settings.DATABASE_PORT = db_info.db_port + settings.DATABASE_USERNAME = db_info.db_user + settings.DATABASE_PASSWORD = db_info.db_password + settings.DATABASE_NAME = db_info.db_name + + run_async(tortoise_init()) diff --git a/backend/app/schemas/admin.py b/backend/app/schemas/admin.py new file mode 100644 index 0000000..53bdb27 --- /dev/null +++ b/backend/app/schemas/admin.py @@ -0,0 +1,14 @@ +# coding=utf-8 +# @FileName :admin.py +# @Time :2024/4/16 下午9:29 +# @Author :dayezi +from pydantic import BaseModel, Field + + +class DbInfo(BaseModel): + db_start: str = Field(..., description="数据库启动类型") + db_name: str = Field(..., description="数据库名称") + db_host: str = Field(..., description="数据库地址") + db_port: int = Field(3306, description="数据库端口") + db_user: str = Field(..., description="数据库用户名") + db_password: str = Field(..., description="数据库密码") diff --git a/backend/app/schemas/base.py b/backend/app/schemas/base.py index 0f15809..398db57 100644 --- a/backend/app/schemas/base.py +++ b/backend/app/schemas/base.py @@ -62,4 +62,3 @@ def __init__( content = {"code": code, "msg": msg} content.update(kwargs) super().__init__(content=content, status_code=code) - diff --git a/backend/app/settings/config.py b/backend/app/settings/config.py index 83e25d4..b3f5812 100644 --- a/backend/app/settings/config.py +++ b/backend/app/settings/config.py @@ -82,7 +82,7 @@ def JWT_REFRESH_TOKEN_EXPIRE_MINUTES(self): return self.data["secret"]["jwt_refresh_token_expire_min"] @property - def DATABASE_START(self): + def DATABASE_START(self) -> str: return self.data["db"]["start"] @DATABASE_START.setter @@ -147,6 +147,19 @@ def DATABASE_NAME(self, value: str): self.data["db"]["name"] = value self._save() + @property + def SUPER_USER_PWD(self) -> str: + return self.data["superUser"]["password"] + + @SUPER_USER_PWD.setter + def SUPER_USER_PWD(self, value: str): + self.data["superUser"]["password"] = value + self._save() + + @property + def SUPER_USER(self) -> dict: + return self.data["superUser"] + @property def TORTOISE_ORM(self): return { @@ -180,4 +193,5 @@ def APP_LOG_CONFIG(self) -> dict: settings = Settings() if __name__ == '__main__': - print(settings.data) + settings.DATABASE_START = "mysql" + print(settings.DATABASE_START) diff --git a/backend/poetry.lock b/backend/poetry.lock index 744e31d..2d366a3 100644 --- a/backend/poetry.lock +++ b/backend/poetry.lock @@ -841,6 +841,26 @@ type = "legacy" url = "https://pypi.tuna.tsinghua.edu.cn/simple" reference = "tsinghua" +[[package]] +name = "pymysql" +version = "1.1.0" +description = "Pure Python MySQL Driver" +optional = false +python-versions = ">=3.7" +files = [ + {file = "PyMySQL-1.1.0-py3-none-any.whl", hash = "sha256:8969ec6d763c856f7073c4c64662882675702efcb114b4bcbb955aea3a069fa7"}, + {file = "PyMySQL-1.1.0.tar.gz", hash = "sha256:4f13a7df8bf36a51e81dd9f3605fede45a4878fe02f9236349fd82a3f0612f96"}, +] + +[package.extras] +ed25519 = ["PyNaCl (>=1.4.0)"] +rsa = ["cryptography"] + +[package.source] +type = "legacy" +url = "https://pypi.tuna.tsinghua.edu.cn/simple" +reference = "tsinghua" + [[package]] name = "pypika-tortoise" version = "0.1.6" @@ -1140,4 +1160,4 @@ reference = "tsinghua" [metadata] lock-version = "2.0" python-versions = "^3.11" -content-hash = "467dd11b74429edc6890f20b80e672d5c0142ec0d7498b93568be9eee28e3659" +content-hash = "3d714358777bbd90acca80e04d9120467d705cf14e2287fd7496bc861a761a66" diff --git a/backend/pyproject.toml b/backend/pyproject.toml index 9ca0eae..7144931 100644 --- a/backend/pyproject.toml +++ b/backend/pyproject.toml @@ -24,6 +24,7 @@ argon2-cffi = "^23.1.0" python-dotenv = "^1.0.1" cryptography = "^42.0.5" ruamel-yaml = "^0.18.6" +pymysql = "^1.1.0" [tool.black] line-length = 120 diff --git a/frontend/src/router/modules/superAdmin.ts b/frontend/src/router/modules/superAdmin.ts new file mode 100644 index 0000000..9c46627 --- /dev/null +++ b/frontend/src/router/modules/superAdmin.ts @@ -0,0 +1,20 @@ +export default { + path: "/superAdmin", + meta: { + title: "超管", + rank: 7, + icon: "fluent:shield-person-20-regular" + }, + children: [ + { + path: "/superAdmin/settings", + name: "Settings", + component: () => import("@/views/superAdmin/Settings.vue"), + meta: { + title: "设置", + icon: "fluent:person-edit-48-regular", + keepAlive: true + } + } + ] +} satisfies RouteConfigsTable; diff --git a/frontend/src/views/superAdmin/Settings.vue b/frontend/src/views/superAdmin/Settings.vue new file mode 100644 index 0000000..c133205 --- /dev/null +++ b/frontend/src/views/superAdmin/Settings.vue @@ -0,0 +1,13 @@ + + + + + From 62ed4502747eee83d8199e68990127deb269fe1f Mon Sep 17 00:00:00 2001 From: dayezi <1372755472@qq.com> Date: Wed, 17 Apr 2024 18:14:37 +0800 Subject: [PATCH 03/31] =?UTF-8?q?=E2=9C=A8=20feat(superAdmin):=20=E6=96=B0?= =?UTF-8?q?=E5=A2=9E=E6=95=B0=E6=8D=AE=E5=BA=93=E9=85=8D=E7=BD=AE=E9=A1=B5?= =?UTF-8?q?=E9=9D=A2?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 1. 新增超管数据库配置页面 2. 去除超管api的验证依赖 3. 修改数据库信息校验模型的各个字段 4. 新增前端禁止访问通知 5. 重新格式化日志对象 6. 表单数据清除默认方法的应用 --- backend/app/__init__.py | 2 + backend/app/api/v1/__init__.py | 2 +- backend/app/api/v1/base/base.py | 2 +- backend/app/core/dependency.py | 5 +- backend/app/core/init_db.py | 24 +-- backend/app/log/log.py | 30 ++-- backend/app/schemas/admin.py | 12 +- backend/app/schemas/users.py | 6 +- frontend/src/api/admin.ts | 9 + frontend/src/router/modules/superAdmin.ts | 9 + frontend/src/utils/http/index.ts | 3 + frontend/src/utils/http/types.d.ts | 1 + frontend/src/views/admin/MaterialMeta.vue | 13 +- frontend/src/views/superAdmin/Logs.vue | 15 ++ frontend/src/views/superAdmin/Settings.vue | 185 ++++++++++++++++++++- 15 files changed, 269 insertions(+), 49 deletions(-) create mode 100644 frontend/src/api/admin.ts create mode 100644 frontend/src/views/superAdmin/Logs.vue diff --git a/backend/app/__init__.py b/backend/app/__init__.py index a169111..3159e61 100644 --- a/backend/app/__init__.py +++ b/backend/app/__init__.py @@ -6,6 +6,7 @@ register_exceptions, register_routers, ) +from app.log import logger try: from app.settings.config import settings @@ -23,6 +24,7 @@ def create_app() -> FastAPI: ) register_exceptions(app) register_routers(app, prefix="/api") + logger.success("应用初始化成功") return app diff --git a/backend/app/api/v1/__init__.py b/backend/app/api/v1/__init__.py index 0ae1d87..4e18a10 100644 --- a/backend/app/api/v1/__init__.py +++ b/backend/app/api/v1/__init__.py @@ -18,4 +18,4 @@ v1_router.include_router(menus_router, prefix="/menu", dependencies=[DependPermission]) v1_router.include_router(apis_router, prefix="/api", dependencies=[DependPermission]) v1_router.include_router(material_router, prefix="/material", dependencies=[DependPermission]) -v1_router.include_router(admin_router, prefix="/admin", dependencies=[DependPermission]) +v1_router.include_router(admin_router, prefix="/admin") diff --git a/backend/app/api/v1/base/base.py b/backend/app/api/v1/base/base.py index 1a62f73..34deaa5 100644 --- a/backend/app/api/v1/base/base.py +++ b/backend/app/api/v1/base/base.py @@ -23,7 +23,7 @@ async def login_access_token(credentials: CredentialsSchema): user: BaseUser = BaseUser.parse_obj(settings.SUPER_USER) else: user: User = await user_controller.authenticate(credentials) - await user_controller.update_last_login(user.id) + await user_controller.update_last_login(user.id) access_token_expires = timedelta(minutes=settings.JWT_ACCESS_TOKEN_EXPIRE_MINUTES) refresh_token_expires = timedelta(minutes=settings.JWT_REFRESH_TOKEN_EXPIRE_MINUTES) expire = datetime.now() + access_token_expires diff --git a/backend/app/core/dependency.py b/backend/app/core/dependency.py index 8fe1786..67c824d 100644 --- a/backend/app/core/dependency.py +++ b/backend/app/core/dependency.py @@ -6,6 +6,7 @@ from app.core.ctx import CTX_USER_ID from app.models import Role, User from app.settings import settings +from app.log import logger class DataBaseControl: @@ -14,7 +15,8 @@ async def has_db(cls) -> bool: if settings.DATABASE_START is not None: return True else: - raise HTTPException(status_code=402, detail="数据库未配置!") + logger.error("数据库未配置!") + raise HTTPException(status_code=403, detail="数据库未配置!禁止访问!") class AuthControl: @@ -50,6 +52,7 @@ async def has_permission(cls, request: Request, current_user: User = Depends(Aut if not roles: raise HTTPException(status_code=403, detail="The user is not bound to a role") apis = [await role.apis for role in roles] + logger.debug(f"{current_user.username} 访问了 {method} {path} 接口") permission_apis = list(set((api.method, api.path) for api in sum(apis, []))) # path = "/api/v1/auth/userinfo" # method = "GET" diff --git a/backend/app/core/init_db.py b/backend/app/core/init_db.py index 2dd4ea0..5493017 100644 --- a/backend/app/core/init_db.py +++ b/backend/app/core/init_db.py @@ -17,11 +17,11 @@ async def tortoise_init(): def test_db(db_info: DbInfo) -> bool: try: conn = pymysql.connect( - host=db_info.db_host, - user=db_info.db_user, - password=db_info.db_password, - db=db_info.db_name, - port=db_info.db_port) + host=db_info.host, + user=db_info.username, + password=db_info.password, + db=db_info.database, + port=db_info.port) with conn.cursor() as cursor: cursor.execute("SELECT 1") result = cursor.fetchone() @@ -32,16 +32,16 @@ def test_db(db_info: DbInfo) -> bool: conn.close() return True except Exception as e: - logger.info(f"数据库测试失败,失败原因:{e}") + logger.error(f"数据库测试失败,失败原因:{e}") return False def set_db(db_info: DbInfo): - settings.DATABASE_START = db_info.db_start - settings.DATABASE_HOST = db_info.db_host - settings.DATABASE_PORT = db_info.db_port - settings.DATABASE_USERNAME = db_info.db_user - settings.DATABASE_PASSWORD = db_info.db_password - settings.DATABASE_NAME = db_info.db_name + settings.DATABASE_START = db_info.start + settings.DATABASE_HOST = db_info.host + settings.DATABASE_PORT = db_info.port + settings.DATABASE_USERNAME = db_info.username + settings.DATABASE_PASSWORD = db_info.password + settings.DATABASE_NAME = db_info.database run_async(tortoise_init()) diff --git a/backend/app/log/log.py b/backend/app/log/log.py index 89ef8e8..ff9c9c7 100644 --- a/backend/app/log/log.py +++ b/backend/app/log/log.py @@ -5,21 +5,23 @@ from app.settings import settings -class Loggin: - def __init__(self) -> None: - debug = settings.DEBUG - if debug: - self.level = "DEBUG" - else: - self.level = "INFO" +class Logger: - def setup_logger(self): - loguru_logger.remove() - loguru_logger.add(sink=sys.stdout, level=self.level) + def __init__(self): + self.logger = loguru_logger + self.logger.remove() + self.logger.add( + sink=sys.stderr, + level=settings.DEBUG, + format="{time:YYYY-MM-DD HH:mm:ss.SSS} | {level: <8} |" + " {name}:{function}:{line} | {message}" + ) - # logger.add("my_project.log", level=level, rotation="100 MB") # Output log messages to a file - return loguru_logger + def get_logger(self): + return self.logger -loggin = Loggin() -logger = loggin.setup_logger() +logger = Logger().get_logger() + +if __name__ == '__main__': + logger.info("nihao") diff --git a/backend/app/schemas/admin.py b/backend/app/schemas/admin.py index 53bdb27..94557b1 100644 --- a/backend/app/schemas/admin.py +++ b/backend/app/schemas/admin.py @@ -6,9 +6,9 @@ class DbInfo(BaseModel): - db_start: str = Field(..., description="数据库启动类型") - db_name: str = Field(..., description="数据库名称") - db_host: str = Field(..., description="数据库地址") - db_port: int = Field(3306, description="数据库端口") - db_user: str = Field(..., description="数据库用户名") - db_password: str = Field(..., description="数据库密码") + start: str = Field(..., description="数据库启动类型") + database: str = Field(..., description="数据库名称") + host: str = Field(..., description="数据库地址") + port: int = Field(3306, description="数据库端口") + username: str = Field(..., description="数据库用户名") + password: str = Field(..., description="数据库密码") diff --git a/backend/app/schemas/users.py b/backend/app/schemas/users.py index d16f660..8c99cf3 100644 --- a/backend/app/schemas/users.py +++ b/backend/app/schemas/users.py @@ -11,9 +11,9 @@ class BaseUser(BaseModel): depart: Optional[str] = None is_active: Optional[bool] = True is_superuser: Optional[bool] = False - created_at: Optional[datetime] - updated_at: Optional[datetime] - last_login: Optional[datetime] + created_at: Optional[datetime] = None + updated_at: Optional[datetime] = None + last_login: Optional[datetime] = None roles: Optional[list] = [] diff --git a/frontend/src/api/admin.ts b/frontend/src/api/admin.ts new file mode 100644 index 0000000..67a85cb --- /dev/null +++ b/frontend/src/api/admin.ts @@ -0,0 +1,9 @@ +import { http } from "@/utils/http"; +import { baseUrlApi } from "./utils"; +import type { BaseResult } from "@/api/type"; + +export const testDB = async (data: object) => { + return http.request("post", baseUrlApi("admin/test_db"), { + data + }); +}; diff --git a/frontend/src/router/modules/superAdmin.ts b/frontend/src/router/modules/superAdmin.ts index 9c46627..a5fa17d 100644 --- a/frontend/src/router/modules/superAdmin.ts +++ b/frontend/src/router/modules/superAdmin.ts @@ -15,6 +15,15 @@ export default { icon: "fluent:person-edit-48-regular", keepAlive: true } + }, + { + path: "/superAdmin/logs", + name: "Logs", + component: () => import("@/views/superAdmin/Logs.vue"), + meta: { + title: "日志", + icon: "fluent:person-edit-48-regular" + } } ] } satisfies RouteConfigsTable; diff --git a/frontend/src/utils/http/index.ts b/frontend/src/utils/http/index.ts index 828fb15..4c5586a 100644 --- a/frontend/src/utils/http/index.ts +++ b/frontend/src/utils/http/index.ts @@ -151,6 +151,9 @@ class PureHttp { if (error.response == null) { errorNotification("服务器超时故障,请重新刷新~"); } + if (error.response.status == 403) { + errorNotification(error.response.data.msg); + } if (error.response.status == 401) { removeToken(); resetRouter(); diff --git a/frontend/src/utils/http/types.d.ts b/frontend/src/utils/http/types.d.ts index 44c600d..9e684cc 100644 --- a/frontend/src/utils/http/types.d.ts +++ b/frontend/src/utils/http/types.d.ts @@ -16,6 +16,7 @@ export type RequestMethods = Extract< export interface PureHttpError extends AxiosError { isCancelRequest?: boolean; + response?: PureHttpResponse; } export interface PureHttpResponse extends AxiosResponse { diff --git a/frontend/src/views/admin/MaterialMeta.vue b/frontend/src/views/admin/MaterialMeta.vue index d97059c..cf85d2f 100644 --- a/frontend/src/views/admin/MaterialMeta.vue +++ b/frontend/src/views/admin/MaterialMeta.vue @@ -80,13 +80,11 @@ const pagination = reactive({ // 表格页面大小改变回调 const pageSizeChange = (size: number) => { pagination.pageSize = size; - console.log(pagination); if (!!area.value) onSearch(); }; // 表格翻页回调 const pageCurrentChange = (page: number) => { pagination.currentPage = page; - console.log(pagination); if (!!area.value) onSearch(); }; // 表格列 @@ -207,12 +205,9 @@ const addForm = reactive({ number: 1 }); // 清除添加物资表单数据 -const clearAddForm = () => { - const keys = Object.keys(addForm); - - for (const key of keys) { - addForm[key] = typeof addForm[key] === "string" ? "" : 1; - } +const clearAddForm = (formEl: FormInstance | undefined) => { + if (!formEl) return; + formEl.resetFields(); }; // 添加物资表单数据校验 const rules = reactive>({ @@ -346,7 +341,7 @@ const submitForm = async (formEl: FormInstance | undefined) => { :close-on-click-modal="false" :close-on-press-escape="false" :destroy-on-close="true" - @close="clearAddForm" + @close="clearAddForm(addFormRef)" >