diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml
index e6b8882..00c70b6 100644
--- a/.github/workflows/ci.yml
+++ b/.github/workflows/ci.yml
@@ -26,7 +26,7 @@ jobs:
- name: Install dependencies
run: |
python -m pip install --upgrade pip
- pip install -r requirements.txt
+ pip install -r ./backend/requirements.txt
- name: pre-commit
uses: pre-commit/action@v3.0.0
diff --git a/.gitignore b/.gitignore
index 690b435..66f8fb5 100644
--- a/.gitignore
+++ b/.gitignore
@@ -1,12 +1,2 @@
-__pycache__/
.idea/
-.env
-venv/
-.venv/
-.mypy_cache/
-backend/app/log/
-backend/app/alembic/versions/
-backend/app/static/media/
-.ruff_cache/
-.pytest_cache/
-.pdm-python
+.vscode/
diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml
index c7d4a06..b0fd1b3 100644
--- a/.pre-commit-config.yaml
+++ b/.pre-commit-config.yaml
@@ -13,18 +13,24 @@ repos:
- id: ruff
args:
- '--config'
- - '.ruff.toml'
+ - 'backend/.ruff.toml'
- '--fix'
- '--unsafe-fixes'
- id: ruff-format
- repo: https://github.com/pdm-project/pdm
- rev: 2.12.2
+ rev: 2.12.4
hooks:
- id: pdm-export
args:
- - -o
- - 'requirements.txt'
+ - '-p'
+ - 'backend'
+ - '-o'
+ - 'backend/requirements.txt'
- '--without-hashes'
- files: ^pdm.lock$
+ files: backend/pdm\.lock$
- id: pdm-lock-check
+ args:
+ - '-p'
+ - 'backend'
+ files: backend/pdm\.lock$
diff --git a/README.md b/README.md
index 59960a3..8c0cc3d 100644
--- a/README.md
+++ b/README.md
@@ -3,12 +3,15 @@
[](https://github.com/fastapi-practices/fastapi_best_architecture/blob/master/LICENSE)
[](https://www.python.org/downloads/)
[](https://github.com/astral-sh/ruff)
+[](https://pydantic.dev)
-> [!Tip]
-> **2024-3-22 (公告)**
+> [!CAUTION]
+> **For 2023-12-22 (announcement)**
>
-> You're looking at the legacy-single-app-pydantic-v2 branch, which has been locked and no longer provides any updates
-> and fixes
+> The master branch has completed the app architecture refactoring, please pay extra attention to sync fork operations
+> to avoid irreparable damage!
+>
+> We have kept and locked the original branch (legacy-single-app-pydantic-v2), which you can get in the branch selector
English | [简体中文](./README.zh-CN.md)
@@ -83,117 +86,117 @@ Luckily, we now have a demo site: [FBA UI](https://fba.xwboy.top/)
* Redis: The latest stable version is recommended
* Nodejs: 14.0+
-### BackEnd
+### Backend
-1. Install dependencies
-
- ```shell
- pip install -r requirements.txt
- ```
-
-2. Create a database `fba`, choose utf8mb4 encoding
-3. Install and start Redis
-4. Create a `.env` file in the `backend/app/` directory
-
- ```shell
- cd backend/app/
- touch .env
- ```
-
-5. Copy `.env.example` to `.env`
+1. Enter the `backend` directory
```shell
+ cd backend
+ ```
+
+2. Install the dependencies
+
+ ```shell
+ pip install -r requirements.txt
+ ```
+
+3. Create a database `fba` with utf8mb4 encoding.
+4. Install and start Redis
+5. Create a `.env` file in the `backend` directory.
+
+ ```shell
+ touch .env
+
cp .env.example .env
```
-6. Modify the configuration file as needed
-7. Database migration [alembic](https://alembic.sqlalchemy.org/en/latest/tutorial.html)
+6. Modify the configuration files `core/conf.py` and `.env` as needed.
+7. database migration [alembic](https://alembic.sqlalchemy.org/en/latest/tutorial.html)
```shell
- cd backend/app/
-
- # Generate migration file
+ # Generate the migration file
alembic revision --autogenerate
-
+
# Execute the migration
alembic upgrade head
- ```
-8. Start celery worker and beat
-
- ```shell
- celery -A tasks worker --loglevel=INFO
- # Optional, if you don't need to use the scheduled task
- celery -A tasks beat --loglevel=INFO
```
-9. Execute the `backend/app/main.py` file to start the service
-10. Browser access: http://127.0.0.1:8000/api/v1/docs
+8. Start celery worker, beat and flower
+
+ ```shell
+ celery -A app.task.celery worker -l info
+
+ # Scheduled tasks (optional)
+ celery -A app.task.celery beat -l info
+
+ # Web monitor (optional)
+ celery -A app.task.celery flower --port=8555 --basic-auth=admin:123456
+ ```
+
+9. [Initialize test data](#test-data) (Optional)
+10. Execute the `main.py` file to start the service
+11. Open a browser and visit: http://127.0.0.1:8000/api/v1/docs
+
+### Front end
+
+Jump to [fastapi_best_architecture_ui](https://github.com/fastapi-practices/fastapi_best_architecture_ui) View details
---
-### Front
-
-Go to [fastapi_best_architecture_ui](https://github.com/fastapi-practices/fastapi_best_architecture_ui) for details
-
-### Docker deploy
+### Docker Deployment
> [!WARNING]
-> Default port conflict:8000,3306,6379,5672
>
-> As a best practice, shut down on-premises services before deployment:mysql,redis,rabbitmq...
+> Default port conflicts: 8000, 3306, 6379, 5672.
+>
+> It is recommended to shut down local services: mysql, redis, rabbitmq... before deployment.
-1. Go to the directory where the ``docker-compose.yml`` file is located and create the environment variable
- file ``.env``
+1. Go to the `deploy/backend/docker-compose` directory, and create the environment variable file `.env`.
```shell
- cd deploy/docker-compose/
+ cd deploy/backend/docker-compose
- cp .env.server ../../backend/app/.env
+ touch .env.server ../../../backend/.env
- # This command is optional
- cp .env.docker .env
+ cp .env.server ../../../backend/.env
```
-2. Modify the configuration file as needed
-3. Execute the one-click boot command
+2. Modify the configuration files `backend/core/conf.py` and `.env` as needed.
+3. Execute the one-click startup command
```shell
docker-compose up -d --build
```
-4. Wait for the command to complete automatically
-5. Visit the browser: http://127.0.0.1:8000/api/v1/docs
+4. Wait for the command to complete.
+5. Open a browser and visit: http://127.0.0.1:8000/api/v1/docs
## Test data
-Initialize the test data using the `backend/sql/init_test_data.sql` file
+Initialize the test data using the `backend/sql/init_test_data.sql` file.
-## Development process
+## Development Process
(For reference only)
-1. Define the database model (model) and remember to perform database migration for each change
-2. Define the data validation model (schema)
-3. Define routes (router) and views (api)
-4. Define the business logic (service)
-5. Write database operations (crud)
+1. define the database model (model)
+2. define the data validation model (schema)
+3. define the view (api) and routing (router)
+4. write business (service)
+5. write database operations (crud)
-## Test
+## Testing
-Execute unittests via pytest
+Execute unit tests through `pytest`.
-1. Create the test database `fba_test`, select utf8mb4 encoding
-2. Using `backend/sql/create_tables.sql` file to create database tables
-3. Initialize the test data using the `backend/sql/init_pytest_data.sql` file
-4. Enter the app directory
-
- ```shell
- cd backend/app/
- ```
-
-5. Execute the test command
+1. create a test database `fba_test` with utf8mb4 encoding
+2. create database tables using the `backend/sql/create_tables.sql` file
+3. initialize the test data using the `backend/sql/init_pytest_data.sql` file
+4. Go to the `backend` directory and execute the test commands.
```shell
+ cd backend/
+
pytest -vs --disable-warnings
```
@@ -203,8 +206,9 @@ Execute unittests via pytest
## Contributors
-
-
+
+
+
## Special thanks
@@ -226,7 +230,7 @@ beans: [:coffee: Sponsor :coffee:](https://wu-clan.github.io/sponsor/)
## License
-This project is licensed under the terms of
+This project is licensed by the terms of
the [MIT](https://github.com/fastapi-practices/fastapi_best_architecture/blob/master/LICENSE) license
[](https://starchart.cc/fastapi-practices/fastapi_best_architecture)
diff --git a/README.zh-CN.md b/README.zh-CN.md
index 645f582..0ee39b0 100644
--- a/README.zh-CN.md
+++ b/README.zh-CN.md
@@ -3,11 +3,14 @@
[](https://github.com/fastapi-practices/fastapi_best_architecture/blob/master/LICENSE)
[](https://www.python.org/downloads/)
[](https://github.com/astral-sh/ruff)
+[](https://pydantic.dev)
-> [!Tip]
+> [!CAUTION]
> **2024-3-22 (公告)**
>
-> 你正在查看 legacy-single-app-pydantic-v2 分支,此分支已被锁定,不再提供任何更新和修复
+> 主分支已完成 app 架构重构,请格外注意 sync fork 操作,以免造成不可挽回的损失!
+>
+> 我们保留并锁定了原始分支(legacy-single-app-pydantic-v2),您可以在分支选择器中找到它
简体中文 | [English](./README.md)
@@ -78,84 +81,88 @@ mvc 架构作为常规设计模式,在 python web 中也很常见,但是三
### 后端
-1. 安装依赖项
-
- ```shell
- pip install -r requirements.txt
- ```
-
-2. 创建一个数据库 `fba`,选择 utf8mb4 编码
-3. 安装并启动 Redis
-4. 在 `backend/app/` 目录下创建一个 `.env` 文件
-
- ```shell
- cd backend/app/
- touch .env
- ```
-
-5. 复制 `.env.example` 到 `.env`
+1. 进入 `backend` 目录
```shell
+ cd backend
+ ```
+
+2. 安装依赖包
+
+ ```shell
+ pip install -r requirements.txt
+ ```
+
+3. 创建一个数据库 `fba`,选择 utf8mb4 编码
+4. 安装并启动 Redis
+5. 在 `backend` 目录下创建 `.env` 文件
+
+ ```shell
+ touch .env
+
cp .env.example .env
```
-6. 按需修改配置文件
+6. 按需修改配置文件 `core/conf.py` 和 `.env`
7. 数据库迁移 [alembic](https://alembic.sqlalchemy.org/en/latest/tutorial.html)
```shell
- cd backend/app/
-
# 生成迁移文件
alembic revision --autogenerate
-
+
# 执行迁移
alembic upgrade head
- ```
-
-8. 启动 celery worker 和 beat
-
- ```shell
- celery -A tasks worker --loglevel=INFO
- # 可选,如果您不需要使用计划任务
- celery -A tasks beat --loglevel=INFO
```
-9. 执行 `backend/app/main.py` 文件启动服务
-10. 浏览器访问:http://127.0.0.1:8000/api/v1/docs
+8. 启动 celery worker, beat 和 flower
----
+ ```shell
+ celery -A app.task.celery worker -l info
+
+ # 定时任务(可选)
+ celery -A app.task.celery beat -l info
+
+ # web 监控(可选)
+ celery -A app.task.celery flower --port=8555 --basic-auth=admin:123456
+ ```
+
+9. [初始化测试数据](#测试数据)(可选)
+10. 执行 `main.py` 文件启动服务
+11. 打开浏览器访问:http://127.0.0.1:8000/api/v1/docs
### 前端
跳转 [fastapi_best_architecture_ui](https://github.com/fastapi-practices/fastapi_best_architecture_ui) 查看详情
+---
+
### Docker 部署
> [!WARNING]
+>
> 默认端口冲突:8000,3306,6379,5672
>
-> 最佳做法是在部署之前关闭本地服务:mysql,redis,rabbitmq...
+> 建议在部署前关闭本地服务:mysql,redis,rabbitmq...
-1. 进入 `docker-compose.yml` 文件所在目录,创建环境变量文件`.env`
+1. 进入 `deploy/backend/docker-compose` 目录,创建环境变量文件`.env`
```shell
- cd deploy/docker-compose/
+ cd deploy/backend/docker-compose
- cp .env.server ../../backend/app/.env
+ touch .env.server ../../../backend/.env
- # 此命令为可选
- cp .env.docker .env
+ cp .env.server ../../../backend/.env
```
-2. 按需修改配置文件
+2. 按需修改配置文件 `backend/core/conf.py` 和 `.env`
3. 执行一键启动命令
```shell
docker-compose up -d --build
```
-4. 等待命令自动完成
-5. 浏览器访问:http://127.0.0.1:8000/api/v1/docs
+4. 等待命令执行完成
+5. 打开浏览器访问:http://127.0.0.1:8000/api/v1/docs
## 测试数据
@@ -165,28 +172,24 @@ mvc 架构作为常规设计模式,在 python web 中也很常见,但是三
(仅供参考)
-1. 定义数据库模型(model),每次变化记得执行数据库迁移
+1. 定义数据库模型(model)
2. 定义数据验证模型(schema)
-3. 定义路由(router)和视图(api)
-4. 定义业务逻辑(service)
+3. 定义视图(api)和路由(router)
+4. 编写业务(service)
5. 编写数据库操作(crud)
## 测试
-通过 pytest 执行单元测试
+通过 `pytest` 执行单元测试
1. 创建测试数据库 `fba_test`,选择 utf8mb4 编码
2. 使用 `backend/sql/create_tables.sql` 文件创建数据库表
3. 使用 `backend/sql/init_pytest_data.sql` 文件初始化测试数据
-4. 进入app目录
-
- ```shell
- cd backend/app/
- ```
-
-5. 执行测试命令
+4. 进入 `backend` 目录,执行测试命令
```shell
+ cd backend/
+
pytest -vs --disable-warnings
```
@@ -196,8 +199,9 @@ mvc 架构作为常规设计模式,在 python web 中也很常见,但是三
## 贡献者
-
-
+
+
+
## 特别鸣谢
@@ -218,6 +222,6 @@ mvc 架构作为常规设计模式,在 python web 中也很常见,但是三
## 许可证
-本项目根据 [MIT](https://github.com/fastapi-practices/fastapi_best_architecture/blob/master/LICENSE) 许可证的条款进行许可
+本项目由 [MIT](https://github.com/fastapi-practices/fastapi_best_architecture/blob/master/LICENSE) 许可证的条款进行许可
[](https://starchart.cc/fastapi-practices/fastapi_best_architecture)
diff --git a/backend/.dockerignore b/backend/.dockerignore
new file mode 100644
index 0000000..f48827b
--- /dev/null
+++ b/backend/.dockerignore
@@ -0,0 +1,4 @@
+__pycache__/
+venv/
+.venv/
+.pdm-python
diff --git a/backend/app/.env.example b/backend/.env.example
similarity index 79%
rename from backend/app/.env.example
rename to backend/.env.example
index 0860edf..75fcf70 100644
--- a/backend/app/.env.example
+++ b/backend/.env.example
@@ -1,19 +1,25 @@
# Env: dev、pro
ENVIRONMENT='dev'
# MySQL
-DB_HOST='127.0.0.1'
-DB_PORT=3306
-DB_USER='root'
-DB_PASSWORD='123456'
+MYSQL_HOST='127.0.0.1'
+MYSQL_PORT=3306
+MYSQL_USER='root'
+MYSQL_PASSWORD='123456'
# Redis
REDIS_HOST='127.0.0.1'
REDIS_PORT=6379
REDIS_PASSWORD=''
REDIS_DATABASE=0
+# Token
+TOKEN_SECRET_KEY='1VkVF75nsNABBjK_7-qz7GtzNy3AMvktc9TCPwKczCk'
+# Opera Log
+OPERA_LOG_ENCRYPT_SECRET_KEY='d77b25790a804c2b4a339dd0207941e4cefa5751935a33735bc73bb7071a005b'
+# Admin
+# OAuth2
+OAUTH2_GITHUB_CLIENT_ID='test'
+OAUTH2_GITHUB_CLIENT_SECRET='test'
+# Task
# Celery
-CELERY_REDIS_HOST='127.0.0.1'
-CELERY_REDIS_PORT=6379
-CELERY_REDIS_PASSWORD=''
CELERY_BROKER_REDIS_DATABASE=1
CELERY_BACKEND_REDIS_DATABASE=2
# Rabbitmq
@@ -21,10 +27,3 @@ RABBITMQ_HOST='127.0.0.1'
RABBITMQ_PORT=5672
RABBITMQ_USERNAME='guest'
RABBITMQ_PASSWORD='guest'
-# Token
-TOKEN_SECRET_KEY='1VkVF75nsNABBjK_7-qz7GtzNy3AMvktc9TCPwKczCk'
-# Opera Log
-OPERA_LOG_ENCRYPT_SECRET_KEY='d77b25790a804c2b4a339dd0207941e4cefa5751935a33735bc73bb7071a005b'
-# OAuth2
-OAUTH2_GITHUB_CLIENT_ID='test'
-OAUTH2_GITHUB_CLIENT_SECRET='test'
diff --git a/backend/.gitignore b/backend/.gitignore
new file mode 100644
index 0000000..5eb36a6
--- /dev/null
+++ b/backend/.gitignore
@@ -0,0 +1,12 @@
+__pycache__/
+.env
+venv/
+.venv/
+.mypy_cache/
+log/
+alembic/versions/
+static/media/
+.ruff_cache/
+.pytest_cache/
+.pdm-python
+celerybeat-schedule.*
diff --git a/.ruff.toml b/backend/.ruff.toml
similarity index 58%
rename from .ruff.toml
rename to backend/.ruff.toml
index 3152a11..4dc4e38 100644
--- a/.ruff.toml
+++ b/backend/.ruff.toml
@@ -1,29 +1,33 @@
line-length = 120
+unsafe-fixes = true
+cache-dir = ".ruff_cache"
target-version = "py310"
-cache-dir = "./.ruff_cache"
-
-[per-file-ignores]
-"backend/app/api/v1/*.py" = ["TCH"]
-"backend/app/models/*.py" = ["TCH003"]
-"backend/app/**/__init__.py" = ["F401"]
-"backend/app/tests/*.py" = ["E402"]
[lint]
select = [
"E",
"F",
"I",
+ "TCH",
+ # W
"W505",
+ # PT
"PT018",
+ # SIM
"SIM101",
"SIM114",
+ # PGH
"PGH004",
+ # PL
"PLE1142",
+ # RUF
"RUF100",
- "F404",
- "TCH",
+ # UP
"UP007"
]
+preview = true
+ignore-init-module-imports = true
+ignore = ["FURB101"]
[lint.flake8-pytest-style]
mark-parentheses = false
@@ -38,5 +42,13 @@ ignore-variadic-names = true
lines-between-types = 1
order-by-type = true
+[lint.per-file-ignores]
+"**/api/v1/*.py" = ["TCH"]
+"**/model/*.py" = ["TCH003"]
+"**/model/__init__.py" = ["F401"]
+"**/tests/*.py" = ["E402"]
+
[format]
+preview = true
quote-style = "single"
+docstring-code-format = true
diff --git a/backend/README.md b/backend/README.md
new file mode 100644
index 0000000..56cc754
--- /dev/null
+++ b/backend/README.md
@@ -0,0 +1,60 @@
+# FBA Project - Backend
+
+## Docker
+
+> [!IMPORTANT]
+> Due to Docker context limitations, you cannot successfully build a image using a Dockerfile in the current directory
+
+1. Make sure you're at the root of the project
+2. Run the following Docker command to build a image:
+
+ ```shell
+ docker build -f backend/backend.dockerfile -t fba_backend_independent .
+ ```
+
+3. Start decker image
+
+ ```shell
+ docker run -d fba_backend_independent -p 8000:8000 --name fba_app
+ ```
+
+## Contributing
+
+1. Prerequisites
+
+ You'll need the following prerequisites:
+ - Any Python version between Python >= 3.10
+ - virtualenv or other virtual environment tool
+ - git
+ - [PDM](https://pdm-project.org/latest/)
+
+2. Installation and setup
+
+ ```shell
+ # Clone your fork and cd into the repo directory
+ git clone https://github.com//fastapi_best_architecture.git
+
+ cd fastapi_best_architecture/backend
+
+ # Install requirements.txt
+ pdm install
+ ```
+
+3. Check out a new branch and make your changes
+
+ ```shell
+ # Checkout a new branch and make your changes
+ git checkout -b your-new-feature-branch
+ # Make your changes...
+ ```
+
+4. Run linting
+
+ ```shell
+ # Run automated code formatting and linting
+ pdm lint
+ ```
+
+5. Commit and push your changes
+
+ Commit your changes, push your branch to GitHub, and create a pull request.
diff --git a/backend/app/api/__init__.py b/backend/__init__.py
similarity index 100%
rename from backend/app/api/__init__.py
rename to backend/__init__.py
diff --git a/backend/app/alembic.ini b/backend/alembic.ini
similarity index 100%
rename from backend/app/alembic.ini
rename to backend/alembic.ini
diff --git a/backend/app/alembic/README b/backend/alembic/README
similarity index 100%
rename from backend/app/alembic/README
rename to backend/alembic/README
diff --git a/backend/app/alembic/env.py b/backend/alembic/env.py
similarity index 89%
rename from backend/app/alembic/env.py
rename to backend/alembic/env.py
index e3a5360..7aee02d 100644
--- a/backend/app/alembic/env.py
+++ b/backend/alembic/env.py
@@ -9,12 +9,12 @@ from alembic import context
from sqlalchemy import engine_from_config, pool
from sqlalchemy.ext.asyncio import AsyncEngine
-sys.path.append('../../')
+sys.path.append('../')
-from backend.app.core import path_conf # noqa: E402
+from backend.core import path_conf
-if not os.path.exists(path_conf.Versions):
- os.makedirs(path_conf.Versions)
+if not os.path.exists(path_conf.ALEMBIC_Versions_DIR):
+ os.makedirs(path_conf.ALEMBIC_Versions_DIR)
# this is the Alembic Config object, which provides
# access to the values within the .ini file in use.
@@ -26,12 +26,12 @@ fileConfig(config.config_file_name)
# add your model's MetaData object here
# for 'autogenerate' support
-from backend.app.models import MappedBase # noqa: E402
+from backend.common.msd.model import MappedBase # noqa: E402
target_metadata = MappedBase.metadata
# other values from the config, defined by the needs of env.py,
-from backend.app.database.db_mysql import SQLALCHEMY_DATABASE_URL # noqa: E402
+from backend.database.db_mysql import SQLALCHEMY_DATABASE_URL # noqa: E402
config.set_main_option('sqlalchemy.url', SQLALCHEMY_DATABASE_URL)
diff --git a/backend/app/alembic/script.py.mako b/backend/alembic/script.py.mako
similarity index 100%
rename from backend/app/alembic/script.py.mako
rename to backend/alembic/script.py.mako
diff --git a/backend/app/api/v1/__init__.py b/backend/app/admin/__init__.py
similarity index 100%
rename from backend/app/api/v1/__init__.py
rename to backend/app/admin/__init__.py
diff --git a/backend/app/common/__init__.py b/backend/app/admin/api/__init__.py
similarity index 100%
rename from backend/app/common/__init__.py
rename to backend/app/admin/api/__init__.py
diff --git a/backend/app/admin/api/router.py b/backend/app/admin/api/router.py
new file mode 100644
index 0000000..b832a47
--- /dev/null
+++ b/backend/app/admin/api/router.py
@@ -0,0 +1,15 @@
+#!/usr/bin/env python3
+# -*- coding: utf-8 -*-
+from fastapi import APIRouter
+
+from backend.app.admin.api.v1.auth import router as auth_router
+from backend.app.admin.api.v1.log import router as log_router
+from backend.app.admin.api.v1.monitor import router as monitor_router
+from backend.app.admin.api.v1.sys import router as sys_router
+
+v1 = APIRouter()
+
+v1.include_router(auth_router)
+v1.include_router(sys_router)
+v1.include_router(log_router)
+v1.include_router(monitor_router)
diff --git a/backend/app/common/exception/__init__.py b/backend/app/admin/api/v1/__init__.py
similarity index 100%
rename from backend/app/common/exception/__init__.py
rename to backend/app/admin/api/v1/__init__.py
diff --git a/backend/app/admin/api/v1/auth/__init__.py b/backend/app/admin/api/v1/auth/__init__.py
new file mode 100644
index 0000000..2bd73d8
--- /dev/null
+++ b/backend/app/admin/api/v1/auth/__init__.py
@@ -0,0 +1,13 @@
+#!/usr/bin/env python3
+# -*- coding: utf-8 -*-
+from fastapi import APIRouter
+
+from backend.app.admin.api.v1.auth.auth import router as auth_router
+from backend.app.admin.api.v1.auth.captcha import router as captcha_router
+from backend.app.admin.api.v1.auth.github import router as github_router
+
+router = APIRouter(prefix='/auth', tags=['授权管理'])
+
+router.include_router(auth_router)
+router.include_router(captcha_router, prefix='/captcha')
+router.include_router(github_router, prefix='/github')
diff --git a/backend/app/api/v1/auth/auth.py b/backend/app/admin/api/v1/auth/auth.py
similarity index 76%
rename from backend/app/api/v1/auth/auth.py
rename to backend/app/admin/api/v1/auth/auth.py
index 02aef04..59f2940 100644
--- a/backend/app/api/v1/auth/auth.py
+++ b/backend/app/admin/api/v1/auth/auth.py
@@ -7,17 +7,17 @@ from fastapi.security import HTTPBasicCredentials
from fastapi_limiter.depends import RateLimiter
from starlette.background import BackgroundTasks
-from backend.app.common.jwt import DependsJwtAuth
-from backend.app.common.response.response_schema import ResponseModel, response_base
-from backend.app.schemas.token import GetSwaggerToken
-from backend.app.schemas.user import AuthLoginParam
-from backend.app.services.auth_service import auth_service
+from backend.app.admin.schema.token import GetSwaggerToken
+from backend.app.admin.schema.user import AuthLoginParam
+from backend.app.admin.service.auth_service import auth_service
+from backend.common.response.response_schema import ResponseModel, response_base
+from backend.common.security.jwt import DependsJwtAuth
router = APIRouter()
@router.post('/login/swagger', summary='swagger 调试专用', description='用于快捷获取 token 进行 swagger 认证')
-async def swagger_user_login(obj: Annotated[HTTPBasicCredentials, Depends()]) -> GetSwaggerToken:
+async def swagger_login(obj: Annotated[HTTPBasicCredentials, Depends()]) -> GetSwaggerToken:
token, user = await auth_service.swagger_login(obj=obj)
return GetSwaggerToken(access_token=token, user=user) # type: ignore
@@ -33,7 +33,7 @@ async def user_login(request: Request, obj: AuthLoginParam, background_tasks: Ba
return await response_base.success(data=data)
-@router.post('/new_token', summary='创建新 token', dependencies=[DependsJwtAuth])
+@router.post('/token/new', summary='创建新 token', dependencies=[DependsJwtAuth])
async def create_new_token(request: Request, refresh_token: Annotated[str, Query(...)]) -> ResponseModel:
data = await auth_service.new_token(request=request, refresh_token=refresh_token)
return await response_base.success(data=data)
diff --git a/backend/app/api/v1/auth/captcha.py b/backend/app/admin/api/v1/auth/captcha.py
similarity index 73%
rename from backend/app/api/v1/auth/captcha.py
rename to backend/app/admin/api/v1/auth/captcha.py
index 267518e..9197a37 100644
--- a/backend/app/api/v1/auth/captcha.py
+++ b/backend/app/admin/api/v1/auth/captcha.py
@@ -5,15 +5,15 @@ from fastapi import APIRouter, Depends, Request
from fastapi_limiter.depends import RateLimiter
from starlette.concurrency import run_in_threadpool
-from backend.app.common.redis import redis_client
-from backend.app.common.response.response_schema import ResponseModel, response_base
-from backend.app.core.conf import settings
+from backend.app.admin.conf import admin_settings
+from backend.common.response.response_schema import ResponseModel, response_base
+from backend.database.db_redis import redis_client
router = APIRouter()
@router.get(
- '/captcha',
+ '',
summary='获取登录验证码',
dependencies=[Depends(RateLimiter(times=5, seconds=10))],
)
@@ -25,6 +25,6 @@ async def get_captcha(request: Request) -> ResponseModel:
img, code = await run_in_threadpool(img_captcha, img_byte=img_type)
ip = request.state.ip
await redis_client.set(
- f'{settings.CAPTCHA_LOGIN_REDIS_PREFIX}:{ip}', code, ex=settings.CAPTCHA_LOGIN_EXPIRE_SECONDS
+ f'{admin_settings.CAPTCHA_LOGIN_REDIS_PREFIX}:{ip}', code, ex=admin_settings.CAPTCHA_LOGIN_EXPIRE_SECONDS
)
return await response_base.success(data={'image_type': img_type, 'image': img})
diff --git a/backend/app/api/v1/auth/github.py b/backend/app/admin/api/v1/auth/github.py
similarity index 53%
rename from backend/app/api/v1/auth/github.py
rename to backend/app/admin/api/v1/auth/github.py
index 851641e..0e4c856 100644
--- a/backend/app/api/v1/auth/github.py
+++ b/backend/app/admin/api/v1/auth/github.py
@@ -3,33 +3,33 @@
from fastapi import APIRouter, BackgroundTasks, Depends, Request
from fastapi_oauth20 import FastAPIOAuth20, GitHubOAuth20
-from backend.app.common.response.response_schema import ResponseModel, response_base
-from backend.app.core.conf import settings
-from backend.app.services.github_service import github_service
+from backend.app.admin.conf import admin_settings
+from backend.app.admin.service.github_service import github_service
+from backend.common.response.response_schema import ResponseModel, response_base
router = APIRouter()
-github_client = GitHubOAuth20(settings.OAUTH2_GITHUB_CLIENT_ID, settings.OAUTH2_GITHUB_CLIENT_SECRET)
-github_oauth2 = FastAPIOAuth20(github_client, settings.OAUTH2_GITHUB_REDIRECT_URI)
+github_client = GitHubOAuth20(admin_settings.OAUTH2_GITHUB_CLIENT_ID, admin_settings.OAUTH2_GITHUB_CLIENT_SECRET)
+github_oauth2 = FastAPIOAuth20(github_client, admin_settings.OAUTH2_GITHUB_REDIRECT_URI)
-@router.get('/github', summary='获取 Github 授权链接')
-async def auth_github() -> ResponseModel:
- auth_url = await github_client.get_authorization_url(redirect_uri=settings.OAUTH2_GITHUB_REDIRECT_URI)
+@router.get('', summary='获取 Github 授权链接')
+async def github_auth2() -> ResponseModel:
+ auth_url = await github_client.get_authorization_url(redirect_uri=admin_settings.OAUTH2_GITHUB_REDIRECT_URI)
return await response_base.success(data=auth_url)
@router.get(
- '/github/callback',
+ '/callback',
summary='Github 授权重定向',
description='Github 授权后,自动重定向到当前地址并获取用户信息,通过用户信息自动创建系统用户',
response_model=None,
)
-async def login_github(
+async def github_login(
request: Request, background_tasks: BackgroundTasks, oauth: FastAPIOAuth20 = Depends(github_oauth2)
) -> ResponseModel:
- token, state = oauth
+ token, _state = oauth
access_token = token['access_token']
user = await github_client.get_userinfo(access_token)
- data = await github_service.add_with_login(request, background_tasks, user)
+ data = await github_service.create_with_login(request, background_tasks, user)
return await response_base.success(data=data)
diff --git a/backend/app/api/v1/log/__init__.py b/backend/app/admin/api/v1/log/__init__.py
similarity index 65%
rename from backend/app/api/v1/log/__init__.py
rename to backend/app/admin/api/v1/log/__init__.py
index 315ba11..e3ee952 100644
--- a/backend/app/api/v1/log/__init__.py
+++ b/backend/app/admin/api/v1/log/__init__.py
@@ -2,8 +2,8 @@
# -*- coding: utf-8 -*-
from fastapi import APIRouter
-from backend.app.api.v1.log.login_log import router as login_log
-from backend.app.api.v1.log.opera_log import router as opera_log
+from backend.app.admin.api.v1.log.login_log import router as login_log
+from backend.app.admin.api.v1.log.opera_log import router as opera_log
router = APIRouter(prefix='/logs')
diff --git a/backend/app/api/v1/log/login_log.py b/backend/app/admin/api/v1/log/login_log.py
similarity index 74%
rename from backend/app/api/v1/log/login_log.py
rename to backend/app/admin/api/v1/log/login_log.py
index 4ec104f..db5393d 100644
--- a/backend/app/api/v1/log/login_log.py
+++ b/backend/app/admin/api/v1/log/login_log.py
@@ -4,14 +4,14 @@ from typing import Annotated
from fastapi import APIRouter, Depends, Query
-from backend.app.common.jwt import DependsJwtAuth
-from backend.app.common.pagination import DependsPagination, paging_data
-from backend.app.common.permission import RequestPermission
-from backend.app.common.rbac import DependsRBAC
-from backend.app.common.response.response_schema import ResponseModel, response_base
-from backend.app.database.db_mysql import CurrentSession
-from backend.app.schemas.login_log import GetLoginLogListDetails
-from backend.app.services.login_log_service import login_log_service
+from backend.app.admin.schema.login_log import GetLoginLogListDetails
+from backend.app.admin.service.login_log_service import login_log_service
+from backend.common.pagination import DependsPagination, paging_data
+from backend.common.response.response_schema import ResponseModel, response_base
+from backend.common.security.jwt import DependsJwtAuth
+from backend.common.security.permission import RequestPermission
+from backend.common.security.rbac import DependsRBAC
+from backend.database.db_mysql import CurrentSession
router = APIRouter()
diff --git a/backend/app/api/v1/log/opera_log.py b/backend/app/admin/api/v1/log/opera_log.py
similarity index 74%
rename from backend/app/api/v1/log/opera_log.py
rename to backend/app/admin/api/v1/log/opera_log.py
index c2866c6..42b8805 100644
--- a/backend/app/api/v1/log/opera_log.py
+++ b/backend/app/admin/api/v1/log/opera_log.py
@@ -4,14 +4,14 @@ from typing import Annotated
from fastapi import APIRouter, Depends, Query
-from backend.app.common.jwt import DependsJwtAuth
-from backend.app.common.pagination import DependsPagination, paging_data
-from backend.app.common.permission import RequestPermission
-from backend.app.common.rbac import DependsRBAC
-from backend.app.common.response.response_schema import ResponseModel, response_base
-from backend.app.database.db_mysql import CurrentSession
-from backend.app.schemas.opera_log import GetOperaLogListDetails
-from backend.app.services.opera_log_service import opera_log_service
+from backend.app.admin.schema.opera_log import GetOperaLogListDetails
+from backend.app.admin.service.opera_log_service import opera_log_service
+from backend.common.pagination import DependsPagination, paging_data
+from backend.common.response.response_schema import ResponseModel, response_base
+from backend.common.security.jwt import DependsJwtAuth
+from backend.common.security.permission import RequestPermission
+from backend.common.security.rbac import DependsRBAC
+from backend.database.db_mysql import CurrentSession
router = APIRouter()
diff --git a/backend/app/admin/api/v1/monitor/__init__.py b/backend/app/admin/api/v1/monitor/__init__.py
new file mode 100644
index 0000000..119bdb0
--- /dev/null
+++ b/backend/app/admin/api/v1/monitor/__init__.py
@@ -0,0 +1,11 @@
+#!/usr/bin/env python3
+# -*- coding: utf-8 -*-
+from fastapi import APIRouter
+
+from backend.app.admin.api.v1.monitor.redis import router as redis_router
+from backend.app.admin.api.v1.monitor.server import router as server_router
+
+router = APIRouter(prefix='/monitors', tags=['监控管理'])
+
+router.include_router(redis_router, prefix='/redis')
+router.include_router(server_router, prefix='/server')
diff --git a/backend/app/api/v1/monitor/redis.py b/backend/app/admin/api/v1/monitor/redis.py
similarity index 62%
rename from backend/app/api/v1/monitor/redis.py
rename to backend/app/admin/api/v1/monitor/redis.py
index 6826a6e..5fb861e 100644
--- a/backend/app/api/v1/monitor/redis.py
+++ b/backend/app/admin/api/v1/monitor/redis.py
@@ -2,16 +2,16 @@
# -*- coding: utf-8 -*-
from fastapi import APIRouter, Depends
-from backend.app.common.jwt import DependsJwtAuth
-from backend.app.common.permission import RequestPermission
-from backend.app.common.response.response_schema import ResponseModel, response_base
-from backend.app.utils.redis_info import redis_info
+from backend.common.response.response_schema import ResponseModel, response_base
+from backend.common.security.jwt import DependsJwtAuth
+from backend.common.security.permission import RequestPermission
+from backend.utils.redis_info import redis_info
router = APIRouter()
@router.get(
- '/redis',
+ '',
summary='redis 监控',
dependencies=[
Depends(RequestPermission('sys:monitor:redis')),
diff --git a/backend/app/api/v1/monitor/server.py b/backend/app/admin/api/v1/monitor/server.py
similarity index 76%
rename from backend/app/api/v1/monitor/server.py
rename to backend/app/admin/api/v1/monitor/server.py
index 8b4c16b..e9f9534 100644
--- a/backend/app/api/v1/monitor/server.py
+++ b/backend/app/admin/api/v1/monitor/server.py
@@ -3,16 +3,16 @@
from fastapi import APIRouter, Depends
from starlette.concurrency import run_in_threadpool
-from backend.app.common.jwt import DependsJwtAuth
-from backend.app.common.permission import RequestPermission
-from backend.app.common.response.response_schema import ResponseModel, response_base
-from backend.app.utils.server_info import server_info
+from backend.common.response.response_schema import ResponseModel, response_base
+from backend.common.security.jwt import DependsJwtAuth
+from backend.common.security.permission import RequestPermission
+from backend.utils.server_info import server_info
router = APIRouter()
@router.get(
- '/server',
+ '',
summary='server 监控',
dependencies=[
Depends(RequestPermission('sys:monitor:server')),
diff --git a/backend/app/api/v1/sys/__init__.py b/backend/app/admin/api/v1/sys/__init__.py
similarity index 57%
rename from backend/app/api/v1/sys/__init__.py
rename to backend/app/admin/api/v1/sys/__init__.py
index 939f1d0..9ec2112 100644
--- a/backend/app/api/v1/sys/__init__.py
+++ b/backend/app/admin/api/v1/sys/__init__.py
@@ -2,14 +2,14 @@
# -*- coding: utf-8 -*-
from fastapi import APIRouter
-from backend.app.api.v1.sys.api import router as api_router
-from backend.app.api.v1.sys.casbin import router as casbin_router
-from backend.app.api.v1.sys.dept import router as dept_router
-from backend.app.api.v1.sys.dict_data import router as dict_data_router
-from backend.app.api.v1.sys.dict_type import router as dict_type_router
-from backend.app.api.v1.sys.menu import router as menu_router
-from backend.app.api.v1.sys.role import router as role_router
-from backend.app.api.v1.sys.user import router as user_router
+from backend.app.admin.api.v1.sys.api import router as api_router
+from backend.app.admin.api.v1.sys.casbin import router as casbin_router
+from backend.app.admin.api.v1.sys.dept import router as dept_router
+from backend.app.admin.api.v1.sys.dict_data import router as dict_data_router
+from backend.app.admin.api.v1.sys.dict_type import router as dict_type_router
+from backend.app.admin.api.v1.sys.menu import router as menu_router
+from backend.app.admin.api.v1.sys.role import router as role_router
+from backend.app.admin.api.v1.sys.user import router as user_router
router = APIRouter(prefix='/sys')
diff --git a/backend/app/api/v1/sys/api.py b/backend/app/admin/api/v1/sys/api.py
similarity index 79%
rename from backend/app/api/v1/sys/api.py
rename to backend/app/admin/api/v1/sys/api.py
index 968666d..556f49a 100644
--- a/backend/app/api/v1/sys/api.py
+++ b/backend/app/admin/api/v1/sys/api.py
@@ -4,21 +4,21 @@ from typing import Annotated
from fastapi import APIRouter, Depends, Path, Query
-from backend.app.common.jwt import DependsJwtAuth
-from backend.app.common.pagination import DependsPagination, paging_data
-from backend.app.common.permission import RequestPermission
-from backend.app.common.rbac import DependsRBAC
-from backend.app.common.response.response_schema import ResponseModel, response_base
-from backend.app.database.db_mysql import CurrentSession
-from backend.app.schemas.api import CreateApiParam, GetApiListDetails, UpdateApiParam
-from backend.app.services.api_service import api_service
+from backend.app.admin.schema.api import CreateApiParam, GetApiListDetails, UpdateApiParam
+from backend.app.admin.service.api_service import api_service
+from backend.common.pagination import DependsPagination, paging_data
+from backend.common.response.response_schema import ResponseModel, response_base
+from backend.common.security.jwt import DependsJwtAuth
+from backend.common.security.permission import RequestPermission
+from backend.common.security.rbac import DependsRBAC
+from backend.database.db_mysql import CurrentSession
router = APIRouter()
@router.get('/all', summary='获取所有接口', dependencies=[DependsJwtAuth])
async def get_all_apis() -> ResponseModel:
- data = await api_service.get_api_list()
+ data = await api_service.get_all()
return await response_base.success(data=data)
diff --git a/backend/app/api/v1/sys/casbin.py b/backend/app/admin/api/v1/sys/casbin.py
similarity index 77%
rename from backend/app/api/v1/sys/casbin.py
rename to backend/app/admin/api/v1/sys/casbin.py
index 120863b..691335a 100644
--- a/backend/app/api/v1/sys/casbin.py
+++ b/backend/app/admin/api/v1/sys/casbin.py
@@ -5,13 +5,7 @@ from uuid import UUID
from fastapi import APIRouter, Depends, Query
-from backend.app.common.jwt import DependsJwtAuth
-from backend.app.common.pagination import DependsPagination, paging_data
-from backend.app.common.permission import RequestPermission
-from backend.app.common.rbac import DependsRBAC
-from backend.app.common.response.response_schema import ResponseModel, response_base
-from backend.app.database.db_mysql import CurrentSession
-from backend.app.schemas.casbin_rule import (
+from backend.app.admin.schema.casbin_rule import (
CreatePolicyParam,
CreateUserRoleParam,
DeleteAllPoliciesParam,
@@ -20,14 +14,20 @@ from backend.app.schemas.casbin_rule import (
GetPolicyListDetails,
UpdatePolicyParam,
)
-from backend.app.services.casbin_service import casbin_service
+from backend.app.admin.service.casbin_service import casbin_service
+from backend.common.pagination import DependsPagination, paging_data
+from backend.common.response.response_schema import ResponseModel, response_base
+from backend.common.security.jwt import DependsJwtAuth
+from backend.common.security.permission import RequestPermission
+from backend.common.security.rbac import DependsRBAC
+from backend.database.db_mysql import CurrentSession
router = APIRouter()
@router.get(
'',
- summary='(模糊条件)分页获取所有权限规则',
+ summary='(模糊条件)分页获取所有权限策略',
dependencies=[
DependsJwtAuth,
DependsPagination,
@@ -35,7 +35,7 @@ router = APIRouter()
)
async def get_pagination_casbin(
db: CurrentSession,
- ptype: Annotated[str | None, Query(description='规则类型, p / g')] = None,
+ ptype: Annotated[str | None, Query(description='策略类型, p / g')] = None,
sub: Annotated[str | None, Query(description='用户 uuid / 角色')] = None,
) -> ResponseModel:
casbin_select = await casbin_service.get_casbin_list(ptype=ptype, sub=sub)
@@ -43,7 +43,7 @@ async def get_pagination_casbin(
return await response_base.success(data=page_data)
-@router.get('/policies', summary='获取所有P权限规则', dependencies=[DependsJwtAuth])
+@router.get('/policies', summary='获取所有P权限策略', dependencies=[DependsJwtAuth])
async def get_all_policies(role: Annotated[int | None, Query(description='角色ID')] = None) -> ResponseModel:
policies = await casbin_service.get_policy_list(role=role)
return await response_base.success(data=policies)
@@ -51,7 +51,7 @@ async def get_all_policies(role: Annotated[int | None, Query(description='角色
@router.post(
'/policy',
- summary='添加P权限规则',
+ summary='添加P权限策略',
dependencies=[
Depends(RequestPermission('casbin:p:add')),
DependsRBAC,
@@ -59,12 +59,12 @@ async def get_all_policies(role: Annotated[int | None, Query(description='角色
)
async def create_policy(p: CreatePolicyParam) -> ResponseModel:
"""
- p 规则:
+ p 策略:
- - 推荐添加基于角色的访问权限, 需配合添加 g 规则才能真正拥有访问权限,适合配置全局接口访问策略
+ - 推荐添加基于角色的访问权限, 需配合添加 g 策略才能真正拥有访问权限,适合配置全局接口访问策略
**格式**: 角色 role + 访问路径 path + 访问方法 method
- - 如果添加基于用户的访问权限, 不需配合添加 g 规则就能真正拥有权限,适合配置指定用户接口访问策略
+ - 如果添加基于用户的访问权限, 不需配合添加 g 策略就能真正拥有权限,适合配置指定用户接口访问策略
**格式**: 用户 uuid + 访问路径 path + 访问方法 method
"""
data = await casbin_service.create_policy(p=p)
@@ -73,7 +73,7 @@ async def create_policy(p: CreatePolicyParam) -> ResponseModel:
@router.post(
'/policies',
- summary='添加多组P权限规则',
+ summary='添加多组P权限策略',
dependencies=[
Depends(RequestPermission('casbin:p:group:add')),
DependsRBAC,
@@ -86,7 +86,7 @@ async def create_policies(ps: list[CreatePolicyParam]) -> ResponseModel:
@router.put(
'/policy',
- summary='更新P权限规则',
+ summary='更新P权限策略',
dependencies=[
Depends(RequestPermission('casbin:p:edit')),
DependsRBAC,
@@ -99,7 +99,7 @@ async def update_policy(old: UpdatePolicyParam, new: UpdatePolicyParam) -> Respo
@router.put(
'/policies',
- summary='更新多组P权限规则',
+ summary='更新多组P权限策略',
dependencies=[
Depends(RequestPermission('casbin:p:group:edit')),
DependsRBAC,
@@ -112,7 +112,7 @@ async def update_policies(old: list[UpdatePolicyParam], new: list[UpdatePolicyPa
@router.delete(
'/policy',
- summary='删除P权限规则',
+ summary='删除P权限策略',
dependencies=[
Depends(RequestPermission('casbin:p:del')),
DependsRBAC,
@@ -125,7 +125,7 @@ async def delete_policy(p: DeletePolicyParam) -> ResponseModel:
@router.delete(
'/policies',
- summary='删除多组P权限规则',
+ summary='删除多组P权限策略',
dependencies=[
Depends(RequestPermission('casbin:p:group:del')),
DependsRBAC,
@@ -138,7 +138,7 @@ async def delete_policies(ps: list[DeletePolicyParam]) -> ResponseModel:
@router.delete(
'/policies/all',
- summary='删除所有P权限规则',
+ summary='删除所有P权限策略',
dependencies=[
Depends(RequestPermission('casbin:p:empty')),
DependsRBAC,
@@ -151,7 +151,7 @@ async def delete_all_policies(sub: DeleteAllPoliciesParam) -> ResponseModel:
return await response_base.fail()
-@router.get('/groups', summary='获取所有G权限规则', dependencies=[DependsJwtAuth])
+@router.get('/groups', summary='获取所有G权限策略', dependencies=[DependsJwtAuth])
async def get_all_groups() -> ResponseModel:
data = await casbin_service.get_group_list()
return await response_base.success(data=data)
@@ -159,7 +159,7 @@ async def get_all_groups() -> ResponseModel:
@router.post(
'/group',
- summary='添加G权限规则',
+ summary='添加G权限策略',
dependencies=[
Depends(RequestPermission('casbin:g:add')),
DependsRBAC,
@@ -167,13 +167,13 @@ async def get_all_groups() -> ResponseModel:
)
async def create_group(g: CreateUserRoleParam) -> ResponseModel:
"""
- g 规则 (**依赖 p 规则**):
+ g 策略 (**依赖 p 策略**):
- - 如果在 p 规则中添加了基于角色的访问权限, 则还需要在 g 规则中添加基于用户组的访问权限, 才能真正拥有访问权限
+ - 如果在 p 策略中添加了基于角色的访问权限, 则还需要在 g 策略中添加基于用户组的访问权限, 才能真正拥有访问权限
**格式**: 用户 uuid + 角色 role
- - 如果在 p 策略中添加了基于用户的访问权限, 则不添加相应的 g 规则能直接拥有访问权限
- 但是拥有的不是用户角色的所有权限, 而只是单一的对应的 p 规则所添加的访问权限
+ - 如果在 p 策略中添加了基于用户的访问权限, 则不添加相应的 g 策略能直接拥有访问权限
+ 但是拥有的不是用户角色的所有权限, 而只是单一的对应的 p 策略所添加的访问权限
"""
data = await casbin_service.create_group(g=g)
return await response_base.success(data=data)
@@ -181,7 +181,7 @@ async def create_group(g: CreateUserRoleParam) -> ResponseModel:
@router.post(
'/groups',
- summary='添加多组G权限规则',
+ summary='添加多组G权限策略',
dependencies=[
Depends(RequestPermission('casbin:g:group:add')),
DependsRBAC,
@@ -194,7 +194,7 @@ async def create_groups(gs: list[CreateUserRoleParam]) -> ResponseModel:
@router.delete(
'/group',
- summary='删除G权限规则',
+ summary='删除G权限策略',
dependencies=[
Depends(RequestPermission('casbin:g:del')),
DependsRBAC,
@@ -207,7 +207,7 @@ async def delete_group(g: DeleteUserRoleParam) -> ResponseModel:
@router.delete(
'/groups',
- summary='删除多组G权限规则',
+ summary='删除多组G权限策略',
dependencies=[
Depends(RequestPermission('casbin:g:group:del')),
DependsRBAC,
@@ -220,7 +220,7 @@ async def delete_groups(gs: list[DeleteUserRoleParam]) -> ResponseModel:
@router.delete(
'/groups/all',
- summary='删除所有G权限规则',
+ summary='删除所有G权限策略',
dependencies=[
Depends(RequestPermission('casbin:g:empty')),
DependsRBAC,
diff --git a/backend/app/api/v1/sys/dept.py b/backend/app/admin/api/v1/sys/dept.py
similarity index 81%
rename from backend/app/api/v1/sys/dept.py
rename to backend/app/admin/api/v1/sys/dept.py
index d9d1af2..614f615 100644
--- a/backend/app/api/v1/sys/dept.py
+++ b/backend/app/admin/api/v1/sys/dept.py
@@ -4,13 +4,13 @@ from typing import Annotated
from fastapi import APIRouter, Depends, Path, Query
-from backend.app.common.jwt import DependsJwtAuth
-from backend.app.common.permission import RequestPermission
-from backend.app.common.rbac import DependsRBAC
-from backend.app.common.response.response_schema import ResponseModel, response_base
-from backend.app.schemas.dept import CreateDeptParam, GetDeptListDetails, UpdateDeptParam
-from backend.app.services.dept_service import dept_service
-from backend.app.utils.serializers import select_as_dict
+from backend.app.admin.schema.dept import CreateDeptParam, GetDeptListDetails, UpdateDeptParam
+from backend.app.admin.service.dept_service import dept_service
+from backend.common.response.response_schema import ResponseModel, response_base
+from backend.common.security.jwt import DependsJwtAuth
+from backend.common.security.permission import RequestPermission
+from backend.common.security.rbac import DependsRBAC
+from backend.utils.serializers import select_as_dict
router = APIRouter()
diff --git a/backend/app/api/v1/sys/dict_data.py b/backend/app/admin/api/v1/sys/dict_data.py
similarity index 78%
rename from backend/app/api/v1/sys/dict_data.py
rename to backend/app/admin/api/v1/sys/dict_data.py
index 09ca0fb..0e00e48 100644
--- a/backend/app/api/v1/sys/dict_data.py
+++ b/backend/app/admin/api/v1/sys/dict_data.py
@@ -4,15 +4,15 @@ from typing import Annotated
from fastapi import APIRouter, Depends, Path, Query
-from backend.app.common.jwt import DependsJwtAuth
-from backend.app.common.pagination import DependsPagination, paging_data
-from backend.app.common.permission import RequestPermission
-from backend.app.common.rbac import DependsRBAC
-from backend.app.common.response.response_schema import ResponseModel, response_base
-from backend.app.database.db_mysql import CurrentSession
-from backend.app.schemas.dict_data import CreateDictDataParam, GetDictDataListDetails, UpdateDictDataParam
-from backend.app.services.dict_data_service import dict_data_service
-from backend.app.utils.serializers import select_as_dict
+from backend.app.admin.schema.dict_data import CreateDictDataParam, GetDictDataListDetails, UpdateDictDataParam
+from backend.app.admin.service.dict_data_service import dict_data_service
+from backend.common.pagination import DependsPagination, paging_data
+from backend.common.response.response_schema import ResponseModel, response_base
+from backend.common.security.jwt import DependsJwtAuth
+from backend.common.security.permission import RequestPermission
+from backend.common.security.rbac import DependsRBAC
+from backend.database.db_mysql import CurrentSession
+from backend.utils.serializers import select_as_dict
router = APIRouter()
diff --git a/backend/app/api/v1/sys/dict_type.py b/backend/app/admin/api/v1/sys/dict_type.py
similarity index 77%
rename from backend/app/api/v1/sys/dict_type.py
rename to backend/app/admin/api/v1/sys/dict_type.py
index ca239a5..5ddc666 100644
--- a/backend/app/api/v1/sys/dict_type.py
+++ b/backend/app/admin/api/v1/sys/dict_type.py
@@ -4,14 +4,14 @@ from typing import Annotated
from fastapi import APIRouter, Depends, Path, Query
-from backend.app.common.jwt import DependsJwtAuth
-from backend.app.common.pagination import DependsPagination, paging_data
-from backend.app.common.permission import RequestPermission
-from backend.app.common.rbac import DependsRBAC
-from backend.app.common.response.response_schema import ResponseModel, response_base
-from backend.app.database.db_mysql import CurrentSession
-from backend.app.schemas.dict_type import CreateDictTypeParam, GetDictTypeListDetails, UpdateDictTypeParam
-from backend.app.services.dict_type_service import dict_type_service
+from backend.app.admin.schema.dict_type import CreateDictTypeParam, GetDictTypeListDetails, UpdateDictTypeParam
+from backend.app.admin.service.dict_type_service import dict_type_service
+from backend.common.pagination import DependsPagination, paging_data
+from backend.common.response.response_schema import ResponseModel, response_base
+from backend.common.security.jwt import DependsJwtAuth
+from backend.common.security.permission import RequestPermission
+from backend.common.security.rbac import DependsRBAC
+from backend.database.db_mysql import CurrentSession
router = APIRouter()
diff --git a/backend/app/api/v1/sys/menu.py b/backend/app/admin/api/v1/sys/menu.py
similarity index 79%
rename from backend/app/api/v1/sys/menu.py
rename to backend/app/admin/api/v1/sys/menu.py
index 449076c..dbb7a91 100644
--- a/backend/app/api/v1/sys/menu.py
+++ b/backend/app/admin/api/v1/sys/menu.py
@@ -4,19 +4,19 @@ from typing import Annotated
from fastapi import APIRouter, Depends, Path, Query, Request
-from backend.app.common.jwt import DependsJwtAuth
-from backend.app.common.permission import RequestPermission
-from backend.app.common.rbac import DependsRBAC
-from backend.app.common.response.response_schema import ResponseModel, response_base
-from backend.app.schemas.menu import CreateMenuParam, GetMenuListDetails, UpdateMenuParam
-from backend.app.services.menu_service import menu_service
-from backend.app.utils.serializers import select_as_dict
+from backend.app.admin.schema.menu import CreateMenuParam, GetMenuListDetails, UpdateMenuParam
+from backend.app.admin.service.menu_service import menu_service
+from backend.common.response.response_schema import ResponseModel, response_base
+from backend.common.security.jwt import DependsJwtAuth
+from backend.common.security.permission import RequestPermission
+from backend.common.security.rbac import DependsRBAC
+from backend.utils.serializers import select_as_dict
router = APIRouter()
@router.get('/sidebar', summary='获取用户菜单展示树', dependencies=[DependsJwtAuth])
-async def get_user_menus(request: Request) -> ResponseModel:
+async def get_user_sidebar_tree(request: Request) -> ResponseModel:
menu = await menu_service.get_user_menu_tree(request=request)
return await response_base.success(data=menu)
diff --git a/backend/app/api/v1/sys/role.py b/backend/app/admin/api/v1/sys/role.py
similarity index 83%
rename from backend/app/api/v1/sys/role.py
rename to backend/app/admin/api/v1/sys/role.py
index 9504cdc..f7c7366 100644
--- a/backend/app/api/v1/sys/role.py
+++ b/backend/app/admin/api/v1/sys/role.py
@@ -4,16 +4,16 @@ from typing import Annotated
from fastapi import APIRouter, Depends, Path, Query, Request
-from backend.app.common.jwt import DependsJwtAuth
-from backend.app.common.pagination import DependsPagination, paging_data
-from backend.app.common.permission import RequestPermission
-from backend.app.common.rbac import DependsRBAC
-from backend.app.common.response.response_schema import ResponseModel, response_base
-from backend.app.database.db_mysql import CurrentSession
-from backend.app.schemas.role import CreateRoleParam, GetRoleListDetails, UpdateRoleMenuParam, UpdateRoleParam
-from backend.app.services.menu_service import menu_service
-from backend.app.services.role_service import role_service
-from backend.app.utils.serializers import select_as_dict, select_list_serialize
+from backend.app.admin.schema.role import CreateRoleParam, GetRoleListDetails, UpdateRoleMenuParam, UpdateRoleParam
+from backend.app.admin.service.menu_service import menu_service
+from backend.app.admin.service.role_service import role_service
+from backend.common.pagination import DependsPagination, paging_data
+from backend.common.response.response_schema import ResponseModel, response_base
+from backend.common.security.jwt import DependsJwtAuth
+from backend.common.security.permission import RequestPermission
+from backend.common.security.rbac import DependsRBAC
+from backend.database.db_mysql import CurrentSession
+from backend.utils.serializers import select_as_dict, select_list_serialize
router = APIRouter()
diff --git a/backend/app/api/v1/sys/user.py b/backend/app/admin/api/v1/sys/user.py
similarity index 86%
rename from backend/app/api/v1/sys/user.py
rename to backend/app/admin/api/v1/sys/user.py
index e9aa754..a8a6d56 100644
--- a/backend/app/api/v1/sys/user.py
+++ b/backend/app/admin/api/v1/sys/user.py
@@ -4,13 +4,7 @@ from typing import Annotated
from fastapi import APIRouter, Depends, Path, Query, Request
-from backend.app.common.jwt import DependsJwtAuth
-from backend.app.common.pagination import DependsPagination, paging_data
-from backend.app.common.permission import RequestPermission
-from backend.app.common.rbac import DependsRBAC
-from backend.app.common.response.response_schema import ResponseModel, response_base
-from backend.app.database.db_mysql import CurrentSession
-from backend.app.schemas.user import (
+from backend.app.admin.schema.user import (
AddUserParam,
AvatarParam,
GetCurrentUserInfoDetail,
@@ -20,14 +14,20 @@ from backend.app.schemas.user import (
UpdateUserParam,
UpdateUserRoleParam,
)
-from backend.app.services.user_service import user_service
-from backend.app.utils.serializers import select_as_dict
+from backend.app.admin.service.user_service import user_service
+from backend.common.pagination import DependsPagination, paging_data
+from backend.common.response.response_schema import ResponseModel, response_base
+from backend.common.security.jwt import DependsJwtAuth
+from backend.common.security.permission import RequestPermission
+from backend.common.security.rbac import DependsRBAC
+from backend.database.db_mysql import CurrentSession
+from backend.utils.serializers import select_as_dict
router = APIRouter()
-@router.post('/register', summary='用户注册')
-async def user_register(obj: RegisterUserParam) -> ResponseModel:
+@router.post('/register', summary='注册用户')
+async def register_user(obj: RegisterUserParam) -> ResponseModel:
await user_service.register(obj=obj)
return await response_base.success()
@@ -49,7 +49,7 @@ async def password_reset(request: Request, obj: ResetPasswordParam) -> ResponseM
@router.get('/me', summary='获取当前用户信息', dependencies=[DependsJwtAuth], response_model_exclude={'password'})
-async def get_current_userinfo(request: Request) -> ResponseModel:
+async def get_current_user(request: Request) -> ResponseModel:
data = GetCurrentUserInfoDetail(**await select_as_dict(request.user))
return await response_base.success(data=data)
@@ -62,7 +62,7 @@ async def get_user(username: Annotated[str, Path(...)]) -> ResponseModel:
@router.put('/{username}', summary='更新用户信息', dependencies=[DependsJwtAuth])
-async def update_userinfo(request: Request, username: Annotated[str, Path(...)], obj: UpdateUserParam) -> ResponseModel:
+async def update_user(request: Request, username: Annotated[str, Path(...)], obj: UpdateUserParam) -> ResponseModel:
count = await user_service.update(request=request, username=username, obj=obj)
if count > 0:
return await response_base.success()
diff --git a/backend/app/admin/conf.py b/backend/app/admin/conf.py
new file mode 100644
index 0000000..8314604
--- /dev/null
+++ b/backend/app/admin/conf.py
@@ -0,0 +1,33 @@
+#!/usr/bin/env python3
+# -*- coding: utf-8 -*-
+from functools import lru_cache
+
+from pydantic_settings import BaseSettings, SettingsConfigDict
+
+from backend.core.path_conf import BasePath
+
+
+class AdminSettings(BaseSettings):
+ """Admin Settings"""
+
+ model_config = SettingsConfigDict(env_file=f'{BasePath}/.env', env_file_encoding='utf-8', extra='ignore')
+
+ # OAuth2:https://github.com/fastapi-practices/fastapi_oauth20
+ OAUTH2_GITHUB_CLIENT_ID: str
+ OAUTH2_GITHUB_CLIENT_SECRET: str
+
+ # OAuth2
+ OAUTH2_GITHUB_REDIRECT_URI: str = 'http://127.0.0.1:8000/api/v1/auth/github/callback'
+
+ # Captcha
+ CAPTCHA_LOGIN_REDIS_PREFIX: str = 'fba_login_captcha'
+ CAPTCHA_LOGIN_EXPIRE_SECONDS: int = 60 * 5 # 过期时间,单位:秒
+
+
+@lru_cache
+def get_admin_settings() -> AdminSettings:
+ """获取 admin 配置"""
+ return AdminSettings()
+
+
+admin_settings = get_admin_settings()
diff --git a/backend/app/common/response/__init__.py b/backend/app/admin/crud/__init__.py
similarity index 100%
rename from backend/app/common/response/__init__.py
rename to backend/app/admin/crud/__init__.py
diff --git a/backend/app/crud/crud_api.py b/backend/app/admin/crud/crud_api.py
similarity index 65%
rename from backend/app/crud/crud_api.py
rename to backend/app/admin/crud/crud_api.py
index e141d6d..723624e 100644
--- a/backend/app/crud/crud_api.py
+++ b/backend/app/admin/crud/crud_api.py
@@ -5,16 +5,31 @@ from typing import Sequence
from sqlalchemy import Select, and_, delete, desc, select
from sqlalchemy.ext.asyncio import AsyncSession
-from backend.app.crud.base import CRUDBase
-from backend.app.models import Api
-from backend.app.schemas.api import CreateApiParam, UpdateApiParam
+from backend.app.admin.model import Api
+from backend.app.admin.schema.api import CreateApiParam, UpdateApiParam
+from backend.common.msd.crud import CRUDBase
class CRUDApi(CRUDBase[Api, CreateApiParam, UpdateApiParam]):
async def get(self, db: AsyncSession, pk: int) -> Api | None:
+ """
+ 获取 API
+
+ :param db:
+ :param pk:
+ :return:
+ """
return await self.get_(db, pk=pk)
async def get_list(self, name: str = None, method: str = None, path: str = None) -> Select:
+ """
+ 获取 API 列表
+
+ :param name:
+ :param method:
+ :param path:
+ :return:
+ """
se = select(self.model).order_by(desc(self.model.created_time))
where_list = []
if name:
@@ -28,20 +43,55 @@ class CRUDApi(CRUDBase[Api, CreateApiParam, UpdateApiParam]):
return se
async def get_all(self, db: AsyncSession) -> Sequence[Api]:
+ """
+ 获取所有 API
+
+ :param db:
+ :return:
+ """
apis = await db.execute(select(self.model))
return apis.scalars().all()
async def get_by_name(self, db: AsyncSession, name: str) -> Api | None:
+ """
+ 通过 name 获取 API
+
+ :param db:
+ :param name:
+ :return:
+ """
api = await db.execute(select(self.model).where(self.model.name == name))
return api.scalars().first()
async def create(self, db: AsyncSession, obj_in: CreateApiParam) -> None:
+ """
+ 创建 API
+
+ :param db:
+ :param obj_in:
+ :return:
+ """
await self.create_(db, obj_in)
async def update(self, db: AsyncSession, pk: int, obj_in: UpdateApiParam) -> int:
+ """
+ 更新 API
+
+ :param db:
+ :param pk:
+ :param obj_in:
+ :return:
+ """
return await self.update_(db, pk, obj_in)
async def delete(self, db: AsyncSession, pk: list[int]) -> int:
+ """
+ 删除 API
+
+ :param db:
+ :param pk:
+ :return:
+ """
apis = await db.execute(delete(self.model).where(self.model.id.in_(pk)))
return apis.rowcount
diff --git a/backend/app/crud/crud_casbin.py b/backend/app/admin/crud/crud_casbin.py
similarity index 66%
rename from backend/app/crud/crud_casbin.py
rename to backend/app/admin/crud/crud_casbin.py
index 9a3308e..bd8d69b 100644
--- a/backend/app/crud/crud_casbin.py
+++ b/backend/app/admin/crud/crud_casbin.py
@@ -5,13 +5,20 @@ from uuid import UUID
from sqlalchemy import Select, and_, delete, or_, select
from sqlalchemy.ext.asyncio import AsyncSession
-from backend.app.crud.base import CRUDBase
-from backend.app.models import CasbinRule
-from backend.app.schemas.casbin_rule import CreatePolicyParam, DeleteAllPoliciesParam, UpdatePolicyParam
+from backend.app.admin.model import CasbinRule
+from backend.app.admin.schema.casbin_rule import CreatePolicyParam, DeleteAllPoliciesParam, UpdatePolicyParam
+from backend.common.msd.crud import CRUDBase
class CRUDCasbin(CRUDBase[CasbinRule, CreatePolicyParam, UpdatePolicyParam]):
- async def get_all_policy(self, ptype: str, sub: str) -> Select:
+ async def get_list(self, ptype: str, sub: str) -> Select:
+ """
+ 获取策略列表
+
+ :param ptype:
+ :param sub:
+ :return:
+ """
se = select(self.model).order_by(self.model.id)
where_list = []
if ptype:
@@ -23,6 +30,13 @@ class CRUDCasbin(CRUDBase[CasbinRule, CreatePolicyParam, UpdatePolicyParam]):
return se
async def delete_policies_by_sub(self, db: AsyncSession, sub: DeleteAllPoliciesParam) -> int:
+ """
+ 删除角色所有P策略
+
+ :param db:
+ :param sub:
+ :return:
+ """
where_list = []
if sub.uuid:
where_list.append(self.model.v0 == sub.uuid)
@@ -31,6 +45,13 @@ class CRUDCasbin(CRUDBase[CasbinRule, CreatePolicyParam, UpdatePolicyParam]):
return result.rowcount
async def delete_groups_by_uuid(self, db: AsyncSession, uuid: UUID) -> int:
+ """
+ 删除用户所有G策略
+
+ :param db:
+ :param uuid:
+ :return:
+ """
result = await db.execute(delete(self.model).where(self.model.v0 == str(uuid)))
return result.rowcount
diff --git a/backend/app/crud/crud_dept.py b/backend/app/admin/crud/crud_dept.py
similarity index 69%
rename from backend/app/crud/crud_dept.py
rename to backend/app/admin/crud/crud_dept.py
index 4192553..3cd5fd4 100644
--- a/backend/app/crud/crud_dept.py
+++ b/backend/app/admin/crud/crud_dept.py
@@ -6,21 +6,45 @@ from sqlalchemy import and_, asc, or_, select
from sqlalchemy.ext.asyncio import AsyncSession
from sqlalchemy.orm import selectinload
-from backend.app.crud.base import CRUDBase
-from backend.app.models import Dept, User
-from backend.app.schemas.dept import CreateDeptParam, UpdateDeptParam
+from backend.app.admin.model import Dept, User
+from backend.app.admin.schema.dept import CreateDeptParam, UpdateDeptParam
+from backend.common.msd.crud import CRUDBase
class CRUDDept(CRUDBase[Dept, CreateDeptParam, UpdateDeptParam]):
async def get(self, db: AsyncSession, dept_id: int) -> Dept | None:
+ """
+ 获取部门
+
+ :param db:
+ :param dept_id:
+ :return:
+ """
return await self.get_(db, pk=dept_id, del_flag=0)
async def get_by_name(self, db: AsyncSession, name: str) -> Dept | None:
+ """
+ 通过 name 获取 API
+
+ :param db:
+ :param name:
+ :return:
+ """
return await self.get_(db, name=name, del_flag=0)
async def get_all(
self, db: AsyncSession, name: str = None, leader: str = None, phone: str = None, status: int = None
) -> Sequence[Dept]:
+ """
+ 获取所有部门
+
+ :param db:
+ :param name:
+ :param leader:
+ :param phone:
+ :param status:
+ :return:
+ """
se = select(self.model).order_by(asc(self.model.sort))
where_list = [self.model.del_flag == 0]
conditions = []
@@ -45,15 +69,44 @@ class CRUDDept(CRUDBase[Dept, CreateDeptParam, UpdateDeptParam]):
return dept.scalars().all()
async def create(self, db: AsyncSession, obj_in: CreateDeptParam) -> None:
+ """
+ 创建部门
+
+ :param db:
+ :param obj_in:
+ :return:
+ """
await self.create_(db, obj_in)
async def update(self, db: AsyncSession, dept_id: int, obj_in: UpdateDeptParam) -> int:
+ """
+ 更新部门
+
+ :param db:
+ :param dept_id:
+ :param obj_in:
+ :return:
+ """
return await self.update_(db, dept_id, obj_in)
async def delete(self, db: AsyncSession, dept_id: int) -> int:
+ """
+ 删除部门
+
+ :param db:
+ :param dept_id:
+ :return:
+ """
return await self.delete_(db, dept_id, del_flag=1)
- async def get_user_relation(self, db: AsyncSession, dept_id: int) -> list[User]:
+ async def get_relation(self, db: AsyncSession, dept_id: int) -> list[User]:
+ """
+ 获取关联
+
+ :param db:
+ :param dept_id:
+ :return:
+ """
result = await db.execute(
select(self.model).options(selectinload(self.model.users)).where(self.model.id == dept_id)
)
@@ -61,6 +114,13 @@ class CRUDDept(CRUDBase[Dept, CreateDeptParam, UpdateDeptParam]):
return user_relation.users
async def get_children(self, db: AsyncSession, dept_id: int) -> list[Dept]:
+ """
+ 获取子部门
+
+ :param db:
+ :param dept_id:
+ :return:
+ """
result = await db.execute(
select(self.model).options(selectinload(self.model.children)).where(self.model.id == dept_id)
)
diff --git a/backend/app/crud/crud_dict_data.py b/backend/app/admin/crud/crud_dict_data.py
similarity index 62%
rename from backend/app/crud/crud_dict_data.py
rename to backend/app/admin/crud/crud_dict_data.py
index 8e5cd50..4199217 100644
--- a/backend/app/crud/crud_dict_data.py
+++ b/backend/app/admin/crud/crud_dict_data.py
@@ -4,16 +4,31 @@ from sqlalchemy import Select, and_, delete, desc, select
from sqlalchemy.ext.asyncio import AsyncSession
from sqlalchemy.orm import selectinload
-from backend.app.crud.base import CRUDBase
-from backend.app.models.sys_dict_data import DictData
-from backend.app.schemas.dict_data import CreateDictDataParam, UpdateDictDataParam
+from backend.app.admin.model import DictData
+from backend.app.admin.schema.dict_data import CreateDictDataParam, UpdateDictDataParam
+from backend.common.msd.crud import CRUDBase
class CRUDDictData(CRUDBase[DictData, CreateDictDataParam, UpdateDictDataParam]):
async def get(self, db: AsyncSession, pk: int) -> DictData | None:
+ """
+ 获取字典数据
+
+ :param db:
+ :param pk:
+ :return:
+ """
return await self.get_(db, pk=pk)
- async def get_all(self, label: str = None, value: str = None, status: int = None) -> Select:
+ async def get_list(self, label: str = None, value: str = None, status: int = None) -> Select:
+ """
+ 获取所有字典数据
+
+ :param label:
+ :param value:
+ :param status:
+ :return:
+ """
se = select(self.model).options(selectinload(self.model.type)).order_by(desc(self.model.sort))
where_list = []
if label:
@@ -27,20 +42,56 @@ class CRUDDictData(CRUDBase[DictData, CreateDictDataParam, UpdateDictDataParam])
return se
async def get_by_label(self, db: AsyncSession, label: str) -> DictData | None:
+ """
+ 通过 label 获取字典数据
+
+ :param db:
+ :param label:
+ :return:
+ """
api = await db.execute(select(self.model).where(self.model.label == label))
return api.scalars().first()
async def create(self, db: AsyncSession, obj_in: CreateDictDataParam) -> None:
+ """
+ 创建数据字典
+
+ :param db:
+ :param obj_in:
+ :return:
+ """
await self.create_(db, obj_in)
async def update(self, db: AsyncSession, pk: int, obj_in: UpdateDictDataParam) -> int:
+ """
+ 更新数据字典
+
+ :param db:
+ :param pk:
+ :param obj_in:
+ :return:
+ """
return await self.update_(db, pk, obj_in)
async def delete(self, db: AsyncSession, pk: list[int]) -> int:
+ """
+ 删除字典数据
+
+ :param db:
+ :param pk:
+ :return:
+ """
apis = await db.execute(delete(self.model).where(self.model.id.in_(pk)))
return apis.rowcount
async def get_with_relation(self, db: AsyncSession, pk: int) -> DictData | None:
+ """
+ 获取字典数据和类型
+
+ :param db:
+ :param pk:
+ :return:
+ """
where = [self.model.id == pk]
dict_data = await db.execute(select(self.model).options(selectinload(self.model.type)).where(*where))
return dict_data.scalars().first()
diff --git a/backend/app/crud/crud_dict_type.py b/backend/app/admin/crud/crud_dict_type.py
similarity index 59%
rename from backend/app/crud/crud_dict_type.py
rename to backend/app/admin/crud/crud_dict_type.py
index 9dbeca5..a34af10 100644
--- a/backend/app/crud/crud_dict_type.py
+++ b/backend/app/admin/crud/crud_dict_type.py
@@ -3,16 +3,31 @@
from sqlalchemy import Select, delete, desc, select
from sqlalchemy.ext.asyncio import AsyncSession
-from backend.app.crud.base import CRUDBase
-from backend.app.models.sys_dict_type import DictType
-from backend.app.schemas.dict_type import CreateDictTypeParam, UpdateDictTypeParam
+from backend.app.admin.model import DictType
+from backend.app.admin.schema.dict_type import CreateDictTypeParam, UpdateDictTypeParam
+from backend.common.msd.crud import CRUDBase
class CRUDDictType(CRUDBase[DictType, CreateDictTypeParam, UpdateDictTypeParam]):
async def get(self, db: AsyncSession, pk: int) -> DictType | None:
+ """
+ 获取字典类型
+
+ :param db:
+ :param pk:
+ :return:
+ """
return await self.get_(db, pk=pk)
- async def get_all(self, *, name: str = None, code: str = None, status: int = None) -> Select:
+ async def get_list(self, *, name: str = None, code: str = None, status: int = None) -> Select:
+ """
+ 获取所有字典类型
+
+ :param name:
+ :param code:
+ :param status:
+ :return:
+ """
se = select(self.model).order_by(desc(self.model.created_time))
where_list = []
if name:
@@ -26,16 +41,45 @@ class CRUDDictType(CRUDBase[DictType, CreateDictTypeParam, UpdateDictTypeParam])
return se
async def get_by_code(self, db: AsyncSession, code: str) -> DictType | None:
+ """
+ 通过 code 获取字典类型
+
+ :param db:
+ :param code:
+ :return:
+ """
dept = await db.execute(select(self.model).where(self.model.code == code))
return dept.scalars().first()
async def create(self, db: AsyncSession, obj_in: CreateDictTypeParam) -> None:
+ """
+ 创建字典类型
+
+ :param db:
+ :param obj_in:
+ :return:
+ """
await self.create_(db, obj_in)
async def update(self, db: AsyncSession, pk: int, obj_in: UpdateDictTypeParam) -> int:
+ """
+ 更新字典类型
+
+ :param db:
+ :param pk:
+ :param obj_in:
+ :return:
+ """
return await self.update_(db, pk, obj_in)
async def delete(self, db: AsyncSession, pk: list[int]) -> int:
+ """
+ 删除字典类型
+
+ :param db:
+ :param pk:
+ :return:
+ """
apis = await db.execute(delete(self.model).where(self.model.id.in_(pk)))
return apis.rowcount
diff --git a/backend/app/crud/crud_login_log.py b/backend/app/admin/crud/crud_login_log.py
similarity index 60%
rename from backend/app/crud/crud_login_log.py
rename to backend/app/admin/crud/crud_login_log.py
index c960bb0..6e82b2f 100644
--- a/backend/app/crud/crud_login_log.py
+++ b/backend/app/admin/crud/crud_login_log.py
@@ -3,13 +3,21 @@
from sqlalchemy import Select, and_, delete, desc, select
from sqlalchemy.ext.asyncio import AsyncSession
-from backend.app.crud.base import CRUDBase
-from backend.app.models import LoginLog
-from backend.app.schemas.login_log import CreateLoginLogParam, UpdateLoginLogParam
+from backend.app.admin.model import LoginLog
+from backend.app.admin.schema.login_log import CreateLoginLogParam, UpdateLoginLogParam
+from backend.common.msd.crud import CRUDBase
class CRUDLoginLog(CRUDBase[LoginLog, CreateLoginLogParam, UpdateLoginLogParam]):
- async def get_all(self, username: str | None = None, status: int | None = None, ip: str | None = None) -> Select:
+ async def get_list(self, username: str | None = None, status: int | None = None, ip: str | None = None) -> Select:
+ """
+ 获取登录日志列表
+
+ :param username:
+ :param status:
+ :param ip:
+ :return:
+ """
se = select(self.model).order_by(desc(self.model.created_time))
where_list = []
if username:
@@ -22,15 +30,35 @@ class CRUDLoginLog(CRUDBase[LoginLog, CreateLoginLogParam, UpdateLoginLogParam])
se = se.where(and_(*where_list))
return se
- async def create(self, db: AsyncSession, obj_in: CreateLoginLogParam):
+ async def create(self, db: AsyncSession, obj_in: CreateLoginLogParam) -> None:
+ """
+ 创建登录日志
+
+ :param db:
+ :param obj_in:
+ :return:
+ """
await self.create_(db, obj_in)
await db.commit()
async def delete(self, db: AsyncSession, pk: list[int]) -> int:
+ """
+ 删除登录日志
+
+ :param db:
+ :param pk:
+ :return:
+ """
logs = await db.execute(delete(self.model).where(self.model.id.in_(pk)))
return logs.rowcount
async def delete_all(self, db: AsyncSession) -> int:
+ """
+ 删除所有登录日志
+
+ :param db:
+ :return:
+ """
logs = await db.execute(delete(self.model))
return logs.rowcount
diff --git a/backend/app/crud/crud_menu.py b/backend/app/admin/crud/crud_menu.py
similarity index 66%
rename from backend/app/crud/crud_menu.py
rename to backend/app/admin/crud/crud_menu.py
index d9c9660..5d59a24 100644
--- a/backend/app/crud/crud_menu.py
+++ b/backend/app/admin/crud/crud_menu.py
@@ -5,20 +5,42 @@ from typing import Sequence
from sqlalchemy import and_, asc, select
from sqlalchemy.orm import selectinload
-from backend.app.crud.base import CRUDBase
-from backend.app.models import Menu
-from backend.app.schemas.menu import CreateMenuParam, UpdateMenuParam
+from backend.app.admin.model import Menu
+from backend.app.admin.schema.menu import CreateMenuParam, UpdateMenuParam
+from backend.common.msd.crud import CRUDBase
class CRUDMenu(CRUDBase[Menu, CreateMenuParam, UpdateMenuParam]):
async def get(self, db, menu_id: int) -> Menu | None:
+ """
+ 获取菜单
+
+ :param db:
+ :param menu_id:
+ :return:
+ """
return await self.get_(db, pk=menu_id)
async def get_by_title(self, db, title: str) -> Menu | None:
+ """
+ 通过 title 获取菜单
+
+ :param db:
+ :param title:
+ :return:
+ """
result = await db.execute(select(self.model).where(and_(self.model.title == title, self.model.menu_type != 2)))
return result.scalars().first()
async def get_all(self, db, title: str | None = None, status: int | None = None) -> Sequence[Menu]:
+ """
+ 获取所有菜单
+
+ :param db:
+ :param title:
+ :param status:
+ :return:
+ """
se = select(self.model).order_by(asc(self.model.sort))
where_list = []
if title:
@@ -31,6 +53,14 @@ class CRUDMenu(CRUDBase[Menu, CreateMenuParam, UpdateMenuParam]):
return menu.scalars().all()
async def get_role_menus(self, db, superuser: bool, menu_ids: list[int]) -> Sequence[Menu]:
+ """
+ 获取角色菜单
+
+ :param db:
+ :param superuser:
+ :param menu_ids:
+ :return:
+ """
se = select(self.model).order_by(asc(self.model.sort))
where_list = [self.model.menu_type.in_([0, 1])]
if not superuser:
@@ -40,16 +70,45 @@ class CRUDMenu(CRUDBase[Menu, CreateMenuParam, UpdateMenuParam]):
return menu.scalars().all()
async def create(self, db, obj_in: CreateMenuParam) -> None:
+ """
+ 创建菜单
+
+ :param db:
+ :param obj_in:
+ :return:
+ """
await self.create_(db, obj_in)
async def update(self, db, menu_id: int, obj_in: UpdateMenuParam) -> int:
+ """
+ 更新菜单
+
+ :param db:
+ :param menu_id:
+ :param obj_in:
+ :return:
+ """
count = await self.update_(db, menu_id, obj_in)
return count
async def delete(self, db, menu_id: int) -> int:
+ """
+ 删除菜单
+
+ :param db:
+ :param menu_id:
+ :return:
+ """
return await self.delete_(db, menu_id)
async def get_children(self, db, menu_id: int) -> list[Menu]:
+ """
+ 获取子菜单
+
+ :param db:
+ :param menu_id:
+ :return:
+ """
result = await db.execute(
select(self.model).options(selectinload(self.model.children)).where(self.model.id == menu_id)
)
diff --git a/backend/app/crud/crud_opera_log.py b/backend/app/admin/crud/crud_opera_log.py
similarity index 61%
rename from backend/app/crud/crud_opera_log.py
rename to backend/app/admin/crud/crud_opera_log.py
index 5c95f1b..45ef61c 100644
--- a/backend/app/crud/crud_opera_log.py
+++ b/backend/app/admin/crud/crud_opera_log.py
@@ -3,13 +3,21 @@
from sqlalchemy import Select, and_, delete, desc, select
from sqlalchemy.ext.asyncio import AsyncSession
-from backend.app.crud.base import CRUDBase
-from backend.app.models import OperaLog
-from backend.app.schemas.opera_log import CreateOperaLogParam, UpdateOperaLogParam
+from backend.app.admin.model import OperaLog
+from backend.app.admin.schema.opera_log import CreateOperaLogParam, UpdateOperaLogParam
+from backend.common.msd.crud import CRUDBase
class CRUDOperaLogDao(CRUDBase[OperaLog, CreateOperaLogParam, UpdateOperaLogParam]):
- async def get_all(self, username: str | None = None, status: int | None = None, ip: str | None = None) -> Select:
+ async def get_list(self, username: str | None = None, status: int | None = None, ip: str | None = None) -> Select:
+ """
+ 获取操作日志列表
+
+ :param username:
+ :param status:
+ :param ip:
+ :return:
+ """
se = select(self.model).order_by(desc(self.model.created_time))
where_list = []
if username:
@@ -23,13 +31,33 @@ class CRUDOperaLogDao(CRUDBase[OperaLog, CreateOperaLogParam, UpdateOperaLogPara
return se
async def create(self, db: AsyncSession, obj_in: CreateOperaLogParam) -> None:
+ """
+ 创建操作日志
+
+ :param db:
+ :param obj_in:
+ :return:
+ """
await self.create_(db, obj_in)
async def delete(self, db: AsyncSession, pk: list[int]) -> int:
+ """
+ 删除操作日志
+
+ :param db:
+ :param pk:
+ :return:
+ """
logs = await db.execute(delete(self.model).where(self.model.id.in_(pk)))
return logs.rowcount
async def delete_all(self, db: AsyncSession) -> int:
+ """
+ 删除所有操作日志
+
+ :param db:
+ :return:
+ """
logs = await db.execute(delete(self.model))
return logs.rowcount
diff --git a/backend/app/crud/crud_role.py b/backend/app/admin/crud/crud_role.py
similarity index 63%
rename from backend/app/crud/crud_role.py
rename to backend/app/admin/crud/crud_role.py
index 4d06b4b..0557573 100644
--- a/backend/app/crud/crud_role.py
+++ b/backend/app/admin/crud/crud_role.py
@@ -5,30 +5,65 @@ from typing import Sequence
from sqlalchemy import Select, delete, desc, select
from sqlalchemy.orm import selectinload
-from backend.app.crud.base import CRUDBase
-from backend.app.models import Menu, Role, User
-from backend.app.schemas.role import CreateRoleParam, UpdateRoleMenuParam, UpdateRoleParam
+from backend.app.admin.model import Menu, Role, User
+from backend.app.admin.schema.role import CreateRoleParam, UpdateRoleMenuParam, UpdateRoleParam
+from backend.common.msd.crud import CRUDBase
class CRUDRole(CRUDBase[Role, CreateRoleParam, UpdateRoleParam]):
async def get(self, db, role_id: int) -> Role | None:
+ """
+ 获取角色
+
+ :param db:
+ :param role_id:
+ :return:
+ """
return await self.get_(db, pk=role_id)
async def get_with_relation(self, db, role_id: int) -> Role | None:
+ """
+ 获取角色和菜单
+
+ :param db:
+ :param role_id:
+ :return:
+ """
role = await db.execute(
select(self.model).options(selectinload(self.model.menus)).where(self.model.id == role_id)
)
return role.scalars().first()
async def get_all(self, db) -> Sequence[Role]:
+ """
+ 获取所有角色
+
+ :param db:
+ :return:
+ """
roles = await db.execute(select(self.model))
return roles.scalars().all()
- async def get_user_all(self, db, user_id: int) -> Sequence[Role]:
+ async def get_user_roles(self, db, user_id: int) -> Sequence[Role]:
+ """
+ 获取用户所有角色
+
+ :param db:
+ :param user_id:
+ :return:
+ """
roles = await db.execute(select(self.model).join(self.model.users).where(User.id == user_id))
return roles.scalars().all()
async def get_list(self, name: str = None, data_scope: int = None, status: int = None) -> Select:
+ """
+ 获取角色列表
+
+ :param name:
+ :param data_scope:
+ :param status:
+ :return:
+ """
se = select(self.model).options(selectinload(self.model.menus)).order_by(desc(self.model.created_time))
where_list = []
if name:
@@ -42,17 +77,47 @@ class CRUDRole(CRUDBase[Role, CreateRoleParam, UpdateRoleParam]):
return se
async def get_by_name(self, db, name: str) -> Role | None:
+ """
+ 通过 name 获取角色
+
+ :param db:
+ :param name:
+ :return:
+ """
role = await db.execute(select(self.model).where(self.model.name == name))
return role.scalars().first()
async def create(self, db, obj_in: CreateRoleParam) -> None:
+ """
+ 创建角色
+
+ :param db:
+ :param obj_in:
+ :return:
+ """
await self.create_(db, obj_in)
async def update(self, db, role_id: int, obj_in: UpdateRoleParam) -> int:
+ """
+ 更新角色
+
+ :param db:
+ :param role_id:
+ :param obj_in:
+ :return:
+ """
rowcount = await self.update_(db, pk=role_id, obj_in=obj_in)
return rowcount
async def update_menus(self, db, role_id: int, menu_ids: UpdateRoleMenuParam) -> int:
+ """
+ 更新角色菜单
+
+ :param db:
+ :param role_id:
+ :param menu_ids:
+ :return:
+ """
current_role = await self.get_with_relation(db, role_id)
# 更新菜单
menus = await db.execute(select(Menu).where(Menu.id.in_(menu_ids.menus)))
@@ -60,6 +125,13 @@ class CRUDRole(CRUDBase[Role, CreateRoleParam, UpdateRoleParam]):
return len(current_role.menus)
async def delete(self, db, role_id: list[int]) -> int:
+ """
+ 删除角色
+
+ :param db:
+ :param role_id:
+ :return:
+ """
roles = await db.execute(delete(self.model).where(self.model.id.in_(role_id)))
return roles.rowcount
diff --git a/backend/app/crud/crud_user.py b/backend/app/admin/crud/crud_user.py
similarity index 65%
rename from backend/app/crud/crud_user.py
rename to backend/app/admin/crud/crud_user.py
index a08679f..37be7ca 100644
--- a/backend/app/crud/crud_user.py
+++ b/backend/app/admin/crud/crud_user.py
@@ -6,35 +6,77 @@ from sqlalchemy.ext.asyncio import AsyncSession
from sqlalchemy.orm import selectinload
from sqlalchemy.sql import Select
-from backend.app.common import jwt
-from backend.app.crud.base import CRUDBase
-from backend.app.models import Role, User
-from backend.app.schemas.user import AddUserParam, AvatarParam, RegisterUserParam, UpdateUserParam, UpdateUserRoleParam
-from backend.app.utils.timezone import timezone
+from backend.app.admin.model import Role, User
+from backend.app.admin.schema.user import (
+ AddUserParam,
+ AvatarParam,
+ RegisterUserParam,
+ UpdateUserParam,
+ UpdateUserRoleParam,
+)
+from backend.common.msd.crud import CRUDBase
+from backend.common.security.jwt import get_hash_password
+from backend.utils.timezone import timezone
class CRUDUser(CRUDBase[User, RegisterUserParam, UpdateUserParam]):
async def get(self, db: AsyncSession, user_id: int) -> User | None:
+ """
+ 获取用户
+
+ :param db:
+ :param user_id:
+ :return:
+ """
return await self.get_(db, pk=user_id)
async def get_by_username(self, db: AsyncSession, username: str) -> User | None:
+ """
+ 通过 username 获取用户
+
+ :param db:
+ :param username:
+ :return:
+ """
user = await db.execute(select(self.model).where(self.model.username == username))
return user.scalars().first()
async def get_by_nickname(self, db: AsyncSession, nickname: str) -> User | None:
+ """
+ 通过 nickname 获取用户
+
+ :param db:
+ :param nickname:
+ :return:
+ """
user = await db.execute(select(self.model).where(self.model.nickname == nickname))
return user.scalars().first()
async def update_login_time(self, db: AsyncSession, username: str) -> int:
+ """
+ 更新用户登录时间
+
+ :param db:
+ :param username:
+ :return:
+ """
user = await db.execute(
update(self.model).where(self.model.username == username).values(last_login_time=timezone.now())
)
return user.rowcount
async def create(self, db: AsyncSession, obj: RegisterUserParam, *, social: bool = False) -> None:
+ """
+ 创建用户
+
+ :param db:
+ :param obj:
+ :param social:
+ :return:
+ """
if not social:
salt = text_captcha(5)
- obj.password = await jwt.get_hash_password(f'{obj.password}{salt}')
+ obj.password = await get_hash_password(f'{obj.password}{salt}')
dict_obj = obj.model_dump()
dict_obj.update({'salt': salt})
else:
@@ -44,8 +86,15 @@ class CRUDUser(CRUDBase[User, RegisterUserParam, UpdateUserParam]):
db.add(new_user)
async def add(self, db: AsyncSession, obj: AddUserParam) -> None:
+ """
+ 后台添加用户
+
+ :param db:
+ :param obj:
+ :return:
+ """
salt = text_captcha(5)
- obj.password = await jwt.get_hash_password(f'{obj.password}{salt}')
+ obj.password = await get_hash_password(f'{obj.password}{salt}')
dict_obj = obj.model_dump(exclude={'roles'})
dict_obj.update({'salt': salt})
new_user = self.model(**dict_obj)
@@ -56,11 +105,27 @@ class CRUDUser(CRUDBase[User, RegisterUserParam, UpdateUserParam]):
db.add(new_user)
async def update_userinfo(self, db: AsyncSession, input_user: User, obj: UpdateUserParam) -> int:
+ """
+ 更新用户信息
+
+ :param db:
+ :param input_user:
+ :param obj:
+ :return:
+ """
user = await db.execute(update(self.model).where(self.model.id == input_user.id).values(**obj.model_dump()))
return user.rowcount
@staticmethod
async def update_role(db: AsyncSession, input_user: User, obj: UpdateUserRoleParam) -> None:
+ """
+ 更新用户角色
+
+ :param db:
+ :param input_user:
+ :param obj:
+ :return:
+ """
# 删除用户所有角色
for i in list(input_user.roles):
input_user.roles.remove(i)
@@ -71,23 +136,63 @@ class CRUDUser(CRUDBase[User, RegisterUserParam, UpdateUserParam]):
input_user.roles.extend(role_list)
async def update_avatar(self, db: AsyncSession, current_user: User, avatar: AvatarParam) -> int:
+ """
+ 更新用户头像
+
+ :param db:
+ :param current_user:
+ :param avatar:
+ :return:
+ """
user = await db.execute(update(self.model).where(self.model.id == current_user.id).values(avatar=avatar.url))
return user.rowcount
async def delete(self, db: AsyncSession, user_id: int) -> int:
+ """
+ 删除用户
+
+ :param db:
+ :param user_id:
+ :return:
+ """
return await self.delete_(db, user_id)
async def check_email(self, db: AsyncSession, email: str) -> User | None:
+ """
+ 检查邮箱是否存在
+
+ :param db:
+ :param email:
+ :return:
+ """
mail = await db.execute(select(self.model).where(self.model.email == email))
return mail.scalars().first()
async def reset_password(self, db: AsyncSession, pk: int, password: str, salt: str) -> int:
+ """
+ 重置用户密码
+
+ :param db:
+ :param pk:
+ :param password:
+ :param salt:
+ :return:
+ """
user = await db.execute(
- update(self.model).where(self.model.id == pk).values(password=await jwt.get_hash_password(password + salt))
+ update(self.model).where(self.model.id == pk).values(password=await get_hash_password(password + salt))
)
return user.rowcount
- async def get_all(self, dept: int = None, username: str = None, phone: str = None, status: int = None) -> Select:
+ async def get_list(self, dept: int = None, username: str = None, phone: str = None, status: int = None) -> Select:
+ """
+ 获取用户列表
+
+ :param dept:
+ :param username:
+ :param phone:
+ :param status:
+ :return:
+ """
se = (
select(self.model)
.options(selectinload(self.model.dept))
@@ -108,22 +213,57 @@ class CRUDUser(CRUDBase[User, RegisterUserParam, UpdateUserParam]):
return se
async def get_super(self, db: AsyncSession, user_id: int) -> bool:
+ """
+ 获取用户超级管理员状态
+
+ :param db:
+ :param user_id:
+ :return:
+ """
user = await self.get(db, user_id)
return user.is_superuser
async def get_staff(self, db: AsyncSession, user_id: int) -> bool:
+ """
+ 获取用户后台登录状态
+
+ :param db:
+ :param user_id:
+ :return:
+ """
user = await self.get(db, user_id)
return user.is_staff
async def get_status(self, db: AsyncSession, user_id: int) -> bool:
+ """
+ 获取用户状态
+
+ :param db:
+ :param user_id:
+ :return:
+ """
user = await self.get(db, user_id)
return user.status
async def get_multi_login(self, db: AsyncSession, user_id: int) -> bool:
+ """
+ 获取用户多点登录状态
+
+ :param db:
+ :param user_id:
+ :return:
+ """
user = await self.get(db, user_id)
return user.is_multi_login
async def set_super(self, db: AsyncSession, user_id: int) -> int:
+ """
+ 设置用户超级管理员
+
+ :param db:
+ :param user_id:
+ :return:
+ """
super_status = await self.get_super(db, user_id)
user = await db.execute(
update(self.model).where(self.model.id == user_id).values(is_superuser=False if super_status else True)
@@ -131,6 +271,13 @@ class CRUDUser(CRUDBase[User, RegisterUserParam, UpdateUserParam]):
return user.rowcount
async def set_staff(self, db: AsyncSession, user_id: int) -> int:
+ """
+ 设置用户后台登录
+
+ :param db:
+ :param user_id:
+ :return:
+ """
staff_status = await self.get_staff(db, user_id)
user = await db.execute(
update(self.model).where(self.model.id == user_id).values(is_staff=False if staff_status else True)
@@ -138,6 +285,13 @@ class CRUDUser(CRUDBase[User, RegisterUserParam, UpdateUserParam]):
return user.rowcount
async def set_status(self, db: AsyncSession, user_id: int) -> int:
+ """
+ 设置用户状态
+
+ :param db:
+ :param user_id:
+ :return:
+ """
status = await self.get_status(db, user_id)
user = await db.execute(
update(self.model).where(self.model.id == user_id).values(status=False if status else True)
@@ -145,6 +299,13 @@ class CRUDUser(CRUDBase[User, RegisterUserParam, UpdateUserParam]):
return user.rowcount
async def set_multi_login(self, db: AsyncSession, user_id: int) -> int:
+ """
+ 设置用户多点登录
+
+ :param db:
+ :param user_id:
+ :return:
+ """
multi_login = await self.get_multi_login(db, user_id)
user = await db.execute(
update(self.model).where(self.model.id == user_id).values(is_multi_login=False if multi_login else True)
@@ -152,6 +313,14 @@ class CRUDUser(CRUDBase[User, RegisterUserParam, UpdateUserParam]):
return user.rowcount
async def get_with_relation(self, db: AsyncSession, *, user_id: int = None, username: str = None) -> User | None:
+ """
+ 获取用户和(部门,角色,菜单)
+
+ :param db:
+ :param user_id:
+ :param username:
+ :return:
+ """
where = []
if user_id:
where.append(self.model.id == user_id)
diff --git a/backend/app/crud/crud_user_social.py b/backend/app/admin/crud/crud_user_social.py
similarity index 56%
rename from backend/app/crud/crud_user_social.py
rename to backend/app/admin/crud/crud_user_social.py
index 669a4ab..6db1077 100644
--- a/backend/app/crud/crud_user_social.py
+++ b/backend/app/admin/crud/crud_user_social.py
@@ -3,22 +3,44 @@
from sqlalchemy import and_, select
from sqlalchemy.ext.asyncio import AsyncSession
-from backend.app.common.enums import UserSocialType
-from backend.app.crud.base import CRUDBase
-from backend.app.models import UserSocial
-from backend.app.schemas.user_social import CreateUserSocialParam, UpdateUserSocialParam
+from backend.app.admin.model import UserSocial
+from backend.app.admin.schema.user_social import CreateUserSocialParam, UpdateUserSocialParam
+from backend.common.enums import UserSocialType
+from backend.common.msd.crud import CRUDBase
class CRUDOUserSocial(CRUDBase[UserSocial, CreateUserSocialParam, UpdateUserSocialParam]):
async def get(self, db: AsyncSession, pk: int, source: UserSocialType) -> UserSocial | None:
+ """
+ 获取用户社交账号绑定
+
+ :param db:
+ :param pk:
+ :param source:
+ :return:
+ """
se = select(self.model).where(and_(self.model.id == pk, self.model.source == source))
user_social = await db.execute(se)
return user_social.scalars().first()
async def create(self, db: AsyncSession, obj_in: CreateUserSocialParam) -> None:
+ """
+ 创建用户社交账号绑定
+
+ :param db:
+ :param obj_in:
+ :return:
+ """
await self.create_(db, obj_in)
async def delete(self, db: AsyncSession, social_id: int) -> int:
+ """
+ 删除用户社交账号绑定
+
+ :param db:
+ :param social_id:
+ :return:
+ """
return await self.delete_(db, social_id)
diff --git a/backend/app/admin/model/__init__.py b/backend/app/admin/model/__init__.py
new file mode 100644
index 0000000..cec0611
--- /dev/null
+++ b/backend/app/admin/model/__init__.py
@@ -0,0 +1,14 @@
+#!/usr/bin/env python3
+# -*- coding: utf-8 -*-
+from backend.common.msd.model import MappedBase # noqa: I001
+from backend.app.admin.model.sys_api import Api
+from backend.app.admin.model.sys_casbin_rule import CasbinRule
+from backend.app.admin.model.sys_dept import Dept
+from backend.app.admin.model.sys_dict_data import DictData
+from backend.app.admin.model.sys_dict_type import DictType
+from backend.app.admin.model.sys_login_log import LoginLog
+from backend.app.admin.model.sys_menu import Menu
+from backend.app.admin.model.sys_opera_log import OperaLog
+from backend.app.admin.model.sys_role import Role
+from backend.app.admin.model.sys_user import User
+from backend.app.admin.model.sys_user_social import UserSocial
diff --git a/backend/app/models/sys_api.py b/backend/app/admin/model/sys_api.py
similarity index 92%
rename from backend/app/models/sys_api.py
rename to backend/app/admin/model/sys_api.py
index 147e0ea..101852c 100644
--- a/backend/app/models/sys_api.py
+++ b/backend/app/admin/model/sys_api.py
@@ -1,11 +1,10 @@
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
-
from sqlalchemy import String
from sqlalchemy.dialects.mysql import LONGTEXT
from sqlalchemy.orm import Mapped, mapped_column
-from backend.app.models.base import Base, id_key
+from backend.common.msd.model import Base, id_key
class Api(Base):
diff --git a/backend/app/models/sys_casbin_rule.py b/backend/app/admin/model/sys_casbin_rule.py
similarity index 95%
rename from backend/app/models/sys_casbin_rule.py
rename to backend/app/admin/model/sys_casbin_rule.py
index ec7fb96..f9e9cbe 100644
--- a/backend/app/models/sys_casbin_rule.py
+++ b/backend/app/admin/model/sys_casbin_rule.py
@@ -4,7 +4,7 @@ from sqlalchemy import String
from sqlalchemy.dialects.mysql import LONGTEXT
from sqlalchemy.orm import Mapped, mapped_column
-from backend.app.models.base import MappedBase, id_key
+from backend.common.msd.model import MappedBase, id_key
class CasbinRule(MappedBase):
diff --git a/backend/app/models/sys_dept.py b/backend/app/admin/model/sys_dept.py
similarity index 96%
rename from backend/app/models/sys_dept.py
rename to backend/app/admin/model/sys_dept.py
index 55a101b..3462fc1 100644
--- a/backend/app/models/sys_dept.py
+++ b/backend/app/admin/model/sys_dept.py
@@ -5,7 +5,7 @@ from typing import Union
from sqlalchemy import ForeignKey, String
from sqlalchemy.orm import Mapped, mapped_column, relationship
-from backend.app.models.base import Base, id_key
+from backend.common.msd.model import Base, id_key
class Dept(Base):
diff --git a/backend/app/models/sys_dict_data.py b/backend/app/admin/model/sys_dict_data.py
similarity index 95%
rename from backend/app/models/sys_dict_data.py
rename to backend/app/admin/model/sys_dict_data.py
index cba78ee..a52cee7 100644
--- a/backend/app/models/sys_dict_data.py
+++ b/backend/app/admin/model/sys_dict_data.py
@@ -4,7 +4,7 @@ from sqlalchemy import ForeignKey, String
from sqlalchemy.dialects.mysql import LONGTEXT
from sqlalchemy.orm import Mapped, mapped_column, relationship
-from backend.app.models.base import Base, id_key
+from backend.common.msd.model import Base, id_key
class DictData(Base):
diff --git a/backend/app/models/sys_dict_type.py b/backend/app/admin/model/sys_dict_type.py
similarity index 94%
rename from backend/app/models/sys_dict_type.py
rename to backend/app/admin/model/sys_dict_type.py
index 98b4134..82f756e 100644
--- a/backend/app/models/sys_dict_type.py
+++ b/backend/app/admin/model/sys_dict_type.py
@@ -4,7 +4,7 @@ from sqlalchemy import String
from sqlalchemy.dialects.mysql import LONGTEXT
from sqlalchemy.orm import Mapped, mapped_column, relationship
-from backend.app.models.base import Base, id_key
+from backend.common.msd.model import Base, id_key
class DictType(Base):
diff --git a/backend/app/models/sys_login_log.py b/backend/app/admin/model/sys_login_log.py
similarity index 93%
rename from backend/app/models/sys_login_log.py
rename to backend/app/admin/model/sys_login_log.py
index 29b53db..62fbbd8 100644
--- a/backend/app/models/sys_login_log.py
+++ b/backend/app/admin/model/sys_login_log.py
@@ -6,8 +6,8 @@ from sqlalchemy import String
from sqlalchemy.dialects.mysql import LONGTEXT
from sqlalchemy.orm import Mapped, mapped_column
-from backend.app.models.base import DataClassBase, id_key
-from backend.app.utils.timezone import timezone
+from backend.common.msd.model import DataClassBase, id_key
+from backend.utils.timezone import timezone
class LoginLog(DataClassBase):
diff --git a/backend/app/models/sys_menu.py b/backend/app/admin/model/sys_menu.py
similarity index 94%
rename from backend/app/models/sys_menu.py
rename to backend/app/admin/model/sys_menu.py
index 8c4aebc..b1d7ef4 100644
--- a/backend/app/models/sys_menu.py
+++ b/backend/app/admin/model/sys_menu.py
@@ -6,8 +6,8 @@ from sqlalchemy import ForeignKey, String
from sqlalchemy.dialects.mysql import LONGTEXT
from sqlalchemy.orm import Mapped, mapped_column, relationship
-from backend.app.models.base import Base, id_key
-from backend.app.models.sys_role_menu import sys_role_menu
+from backend.app.admin.model.sys_role_menu import sys_role_menu
+from backend.common.msd.model import Base, id_key
class Menu(Base):
diff --git a/backend/app/models/sys_opera_log.py b/backend/app/admin/model/sys_opera_log.py
similarity index 94%
rename from backend/app/models/sys_opera_log.py
rename to backend/app/admin/model/sys_opera_log.py
index b002eb3..dd84bba 100644
--- a/backend/app/models/sys_opera_log.py
+++ b/backend/app/admin/model/sys_opera_log.py
@@ -6,8 +6,8 @@ from sqlalchemy import String
from sqlalchemy.dialects.mysql import JSON, LONGTEXT
from sqlalchemy.orm import Mapped, mapped_column
-from backend.app.models.base import DataClassBase, id_key
-from backend.app.utils.timezone import timezone
+from backend.common.msd.model import DataClassBase, id_key
+from backend.utils.timezone import timezone
class OperaLog(DataClassBase):
diff --git a/backend/app/models/sys_role.py b/backend/app/admin/model/sys_role.py
similarity index 85%
rename from backend/app/models/sys_role.py
rename to backend/app/admin/model/sys_role.py
index 0addc31..f9d5938 100644
--- a/backend/app/models/sys_role.py
+++ b/backend/app/admin/model/sys_role.py
@@ -4,9 +4,9 @@ from sqlalchemy import String
from sqlalchemy.dialects.mysql import LONGTEXT
from sqlalchemy.orm import Mapped, mapped_column, relationship
-from backend.app.models.base import Base, id_key
-from backend.app.models.sys_role_menu import sys_role_menu
-from backend.app.models.sys_user_role import sys_user_role
+from backend.app.admin.model.sys_role_menu import sys_role_menu
+from backend.app.admin.model.sys_user_role import sys_user_role
+from backend.common.msd.model import Base, id_key
class Role(Base):
diff --git a/backend/app/models/sys_role_menu.py b/backend/app/admin/model/sys_role_menu.py
similarity index 91%
rename from backend/app/models/sys_role_menu.py
rename to backend/app/admin/model/sys_role_menu.py
index c447cc9..cde1c77 100644
--- a/backend/app/models/sys_role_menu.py
+++ b/backend/app/admin/model/sys_role_menu.py
@@ -2,7 +2,7 @@
# -*- coding: utf-8 -*-
from sqlalchemy import INT, Column, ForeignKey, Integer, Table
-from backend.app.models.base import MappedBase
+from backend.common.msd.model import MappedBase
sys_role_menu = Table(
'sys_role_menu',
diff --git a/backend/app/models/sys_user.py b/backend/app/admin/model/sys_user.py
similarity index 91%
rename from backend/app/models/sys_user.py
rename to backend/app/admin/model/sys_user.py
index 9357553..19055d0 100644
--- a/backend/app/models/sys_user.py
+++ b/backend/app/admin/model/sys_user.py
@@ -6,10 +6,10 @@ from typing import Union
from sqlalchemy import ForeignKey, String
from sqlalchemy.orm import Mapped, mapped_column, relationship
-from backend.app.database.db_mysql import uuid4_str
-from backend.app.models.base import Base, id_key
-from backend.app.models.sys_user_role import sys_user_role
-from backend.app.utils.timezone import timezone
+from backend.app.admin.model.sys_user_role import sys_user_role
+from backend.common.msd.model import Base, id_key
+from backend.database.db_mysql import uuid4_str
+from backend.utils.timezone import timezone
class User(Base):
diff --git a/backend/app/models/sys_user_role.py b/backend/app/admin/model/sys_user_role.py
similarity index 91%
rename from backend/app/models/sys_user_role.py
rename to backend/app/admin/model/sys_user_role.py
index 8e37398..88c41de 100644
--- a/backend/app/models/sys_user_role.py
+++ b/backend/app/admin/model/sys_user_role.py
@@ -2,7 +2,7 @@
# -*- coding: utf-8 -*-
from sqlalchemy import INT, Column, ForeignKey, Integer, Table
-from backend.app.models.base import MappedBase
+from backend.common.msd.model import MappedBase
sys_user_role = Table(
'sys_user_role',
diff --git a/backend/app/models/sys_user_social.py b/backend/app/admin/model/sys_user_social.py
similarity index 96%
rename from backend/app/models/sys_user_social.py
rename to backend/app/admin/model/sys_user_social.py
index af683ab..3389cf7 100644
--- a/backend/app/models/sys_user_social.py
+++ b/backend/app/admin/model/sys_user_social.py
@@ -5,7 +5,7 @@ from typing import Union
from sqlalchemy import ForeignKey, String
from sqlalchemy.orm import Mapped, mapped_column, relationship
-from backend.app.models.base import Base, id_key
+from backend.common.msd.model import Base, id_key
class UserSocial(Base):
diff --git a/backend/app/core/__init__.py b/backend/app/admin/schema/__init__.py
similarity index 100%
rename from backend/app/core/__init__.py
rename to backend/app/admin/schema/__init__.py
diff --git a/backend/app/schemas/api.py b/backend/app/admin/schema/api.py
similarity index 86%
rename from backend/app/schemas/api.py
rename to backend/app/admin/schema/api.py
index aaf3b08..979d607 100644
--- a/backend/app/schemas/api.py
+++ b/backend/app/admin/schema/api.py
@@ -4,8 +4,8 @@ from datetime import datetime
from pydantic import ConfigDict, Field
-from backend.app.common.enums import MethodType
-from backend.app.schemas.base import SchemaBase
+from backend.common.enums import MethodType
+from backend.common.msd.schema import SchemaBase
class ApiSchemaBase(SchemaBase):
diff --git a/backend/app/schemas/casbin_rule.py b/backend/app/admin/schema/casbin_rule.py
similarity index 92%
rename from backend/app/schemas/casbin_rule.py
rename to backend/app/admin/schema/casbin_rule.py
index 698d92f..5ece8a9 100644
--- a/backend/app/schemas/casbin_rule.py
+++ b/backend/app/admin/schema/casbin_rule.py
@@ -2,8 +2,8 @@
# -*- coding: utf-8 -*-
from pydantic import ConfigDict, Field
-from backend.app.common.enums import MethodType
-from backend.app.schemas.base import SchemaBase
+from backend.common.enums import MethodType
+from backend.common.msd.schema import SchemaBase
class CreatePolicyParam(SchemaBase):
diff --git a/backend/app/schemas/dept.py b/backend/app/admin/schema/dept.py
similarity index 85%
rename from backend/app/schemas/dept.py
rename to backend/app/admin/schema/dept.py
index c99b5a7..6389927 100644
--- a/backend/app/schemas/dept.py
+++ b/backend/app/admin/schema/dept.py
@@ -4,8 +4,8 @@ from datetime import datetime
from pydantic import ConfigDict, Field
-from backend.app.common.enums import StatusType
-from backend.app.schemas.base import CustomEmailStr, CustomPhoneNumber, SchemaBase
+from backend.common.enums import StatusType
+from backend.common.msd.schema import CustomEmailStr, CustomPhoneNumber, SchemaBase
class DeptSchemaBase(SchemaBase):
diff --git a/backend/app/schemas/dict_data.py b/backend/app/admin/schema/dict_data.py
similarity index 79%
rename from backend/app/schemas/dict_data.py
rename to backend/app/admin/schema/dict_data.py
index 35d19b6..179ba5d 100644
--- a/backend/app/schemas/dict_data.py
+++ b/backend/app/admin/schema/dict_data.py
@@ -4,9 +4,9 @@ from datetime import datetime
from pydantic import ConfigDict, Field
-from backend.app.common.enums import StatusType
-from backend.app.schemas.base import SchemaBase
-from backend.app.schemas.dict_type import GetDictTypeListDetails
+from backend.app.admin.schema.dict_type import GetDictTypeListDetails
+from backend.common.enums import StatusType
+from backend.common.msd.schema import SchemaBase
class DictDataSchemaBase(SchemaBase):
diff --git a/backend/app/schemas/dict_type.py b/backend/app/admin/schema/dict_type.py
similarity index 85%
rename from backend/app/schemas/dict_type.py
rename to backend/app/admin/schema/dict_type.py
index 84a90e8..61edfc9 100644
--- a/backend/app/schemas/dict_type.py
+++ b/backend/app/admin/schema/dict_type.py
@@ -4,8 +4,8 @@ from datetime import datetime
from pydantic import ConfigDict, Field
-from backend.app.common.enums import StatusType
-from backend.app.schemas.base import SchemaBase
+from backend.common.enums import StatusType
+from backend.common.msd.schema import SchemaBase
class DictTypeSchemaBase(SchemaBase):
diff --git a/backend/app/schemas/login_log.py b/backend/app/admin/schema/login_log.py
similarity index 93%
rename from backend/app/schemas/login_log.py
rename to backend/app/admin/schema/login_log.py
index d8f44a7..14e59f8 100644
--- a/backend/app/schemas/login_log.py
+++ b/backend/app/admin/schema/login_log.py
@@ -4,7 +4,7 @@ from datetime import datetime
from pydantic import ConfigDict
-from backend.app.schemas.base import SchemaBase
+from backend.common.msd.schema import SchemaBase
class LoginLogSchemaBase(SchemaBase):
diff --git a/backend/app/schemas/menu.py b/backend/app/admin/schema/menu.py
similarity index 90%
rename from backend/app/schemas/menu.py
rename to backend/app/admin/schema/menu.py
index d36ba51..a69802c 100644
--- a/backend/app/schemas/menu.py
+++ b/backend/app/admin/schema/menu.py
@@ -4,8 +4,8 @@ from datetime import datetime
from pydantic import ConfigDict, Field
-from backend.app.common.enums import MenuType, StatusType
-from backend.app.schemas.base import SchemaBase
+from backend.common.enums import MenuType, StatusType
+from backend.common.msd.schema import SchemaBase
class MenuSchemaBase(SchemaBase):
diff --git a/backend/app/schemas/opera_log.py b/backend/app/admin/schema/opera_log.py
similarity index 90%
rename from backend/app/schemas/opera_log.py
rename to backend/app/admin/schema/opera_log.py
index 0e6db5a..50ccc46 100644
--- a/backend/app/schemas/opera_log.py
+++ b/backend/app/admin/schema/opera_log.py
@@ -4,8 +4,8 @@ from datetime import datetime
from pydantic import ConfigDict, Field
-from backend.app.common.enums import StatusType
-from backend.app.schemas.base import SchemaBase
+from backend.common.enums import StatusType
+from backend.common.msd.schema import SchemaBase
class OperaLogSchemaBase(SchemaBase):
diff --git a/backend/app/schemas/role.py b/backend/app/admin/schema/role.py
similarity index 82%
rename from backend/app/schemas/role.py
rename to backend/app/admin/schema/role.py
index 5b0cc8f..8901c7f 100644
--- a/backend/app/schemas/role.py
+++ b/backend/app/admin/schema/role.py
@@ -4,9 +4,9 @@ from datetime import datetime
from pydantic import ConfigDict, Field
-from backend.app.common.enums import RoleDataScopeType, StatusType
-from backend.app.schemas.base import SchemaBase
-from backend.app.schemas.menu import GetMenuListDetails
+from backend.app.admin.schema.menu import GetMenuListDetails
+from backend.common.enums import RoleDataScopeType, StatusType
+from backend.common.msd.schema import SchemaBase
class RoleSchemaBase(SchemaBase):
diff --git a/backend/app/schemas/token.py b/backend/app/admin/schema/token.py
similarity index 84%
rename from backend/app/schemas/token.py
rename to backend/app/admin/schema/token.py
index 95c810b..2faf777 100644
--- a/backend/app/schemas/token.py
+++ b/backend/app/admin/schema/token.py
@@ -2,8 +2,8 @@
# -*- coding: utf-8 -*-
from datetime import datetime
-from backend.app.schemas.base import SchemaBase
-from backend.app.schemas.user import GetUserInfoNoRelationDetail
+from backend.app.admin.schema.user import GetUserInfoNoRelationDetail
+from backend.common.msd.schema import SchemaBase
class GetSwaggerToken(SchemaBase):
diff --git a/backend/app/schemas/user.py b/backend/app/admin/schema/user.py
similarity index 90%
rename from backend/app/schemas/user.py
rename to backend/app/admin/schema/user.py
index 0985113..ddb03ab 100644
--- a/backend/app/schemas/user.py
+++ b/backend/app/admin/schema/user.py
@@ -4,10 +4,10 @@ from datetime import datetime
from pydantic import ConfigDict, EmailStr, Field, HttpUrl, model_validator
-from backend.app.common.enums import StatusType
-from backend.app.schemas.base import CustomPhoneNumber, SchemaBase
-from backend.app.schemas.dept import GetDeptListDetails
-from backend.app.schemas.role import GetRoleListDetails
+from backend.app.admin.schema.dept import GetDeptListDetails
+from backend.app.admin.schema.role import GetRoleListDetails
+from backend.common.enums import StatusType
+from backend.common.msd.schema import CustomPhoneNumber, SchemaBase
class AuthSchemaBase(SchemaBase):
diff --git a/backend/app/schemas/user_social.py b/backend/app/admin/schema/user_social.py
similarity index 79%
rename from backend/app/schemas/user_social.py
rename to backend/app/admin/schema/user_social.py
index 232426c..a71b4f1 100644
--- a/backend/app/schemas/user_social.py
+++ b/backend/app/admin/schema/user_social.py
@@ -1,7 +1,7 @@
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
-from backend.app.common.enums import UserSocialType
-from backend.app.schemas.base import SchemaBase
+from backend.common.enums import UserSocialType
+from backend.common.msd.schema import SchemaBase
class UserSocialSchemaBase(SchemaBase):
diff --git a/backend/app/crud/__init__.py b/backend/app/admin/service/__init__.py
similarity index 100%
rename from backend/app/crud/__init__.py
rename to backend/app/admin/service/__init__.py
diff --git a/backend/app/services/api_service.py b/backend/app/admin/service/api_service.py
similarity index 80%
rename from backend/app/services/api_service.py
rename to backend/app/admin/service/api_service.py
index b769db1..655b287 100644
--- a/backend/app/services/api_service.py
+++ b/backend/app/admin/service/api_service.py
@@ -4,11 +4,11 @@ from typing import Sequence
from sqlalchemy import Select
-from backend.app.common.exception import errors
-from backend.app.crud.crud_api import api_dao
-from backend.app.database.db_mysql import async_db_session
-from backend.app.models import Api
-from backend.app.schemas.api import CreateApiParam, UpdateApiParam
+from backend.app.admin.crud.crud_api import api_dao
+from backend.app.admin.model import Api
+from backend.app.admin.schema.api import CreateApiParam, UpdateApiParam
+from backend.common.exception import errors
+from backend.database.db_mysql import async_db_session
class ApiService:
@@ -25,7 +25,7 @@ class ApiService:
return await api_dao.get_list(name=name, method=method, path=path)
@staticmethod
- async def get_api_list() -> Sequence[Api]:
+ async def get_all() -> Sequence[Api]:
async with async_db_session() as db:
apis = await api_dao.get_all(db)
return apis
@@ -51,4 +51,4 @@ class ApiService:
return count
-api_service: ApiService = ApiService()
+api_service = ApiService()
diff --git a/backend/app/services/auth_service.py b/backend/app/admin/service/auth_service.py
similarity index 74%
rename from backend/app/services/auth_service.py
rename to backend/app/admin/service/auth_service.py
index efad1ed..073f4b0 100644
--- a/backend/app/services/auth_service.py
+++ b/backend/app/admin/service/auth_service.py
@@ -4,19 +4,27 @@ from fastapi import Request
from fastapi.security import HTTPBasicCredentials
from starlette.background import BackgroundTask, BackgroundTasks
-from backend.app.common import jwt
-from backend.app.common.enums import LoginLogStatusType
-from backend.app.common.exception import errors
-from backend.app.common.redis import redis_client
-from backend.app.common.response.response_code import CustomErrorCode
-from backend.app.core.conf import settings
-from backend.app.crud.crud_user import user_dao
-from backend.app.database.db_mysql import async_db_session
-from backend.app.models import User
-from backend.app.schemas.token import GetLoginToken, GetNewToken
-from backend.app.schemas.user import AuthLoginParam
-from backend.app.services.login_log_service import LoginLogService
-from backend.app.utils.timezone import timezone
+from backend.app.admin.conf import admin_settings
+from backend.app.admin.crud.crud_user import user_dao
+from backend.app.admin.model import User
+from backend.app.admin.schema.token import GetLoginToken, GetNewToken
+from backend.app.admin.schema.user import AuthLoginParam
+from backend.app.admin.service.login_log_service import LoginLogService
+from backend.common.enums import LoginLogStatusType
+from backend.common.exception import errors
+from backend.common.response.response_code import CustomErrorCode
+from backend.common.security.jwt import (
+ create_access_token,
+ create_new_token,
+ create_refresh_token,
+ get_token,
+ jwt_decode,
+ password_verify,
+)
+from backend.core.conf import settings
+from backend.database.db_mysql import async_db_session
+from backend.database.db_redis import redis_client
+from backend.utils.timezone import timezone
class AuthService:
@@ -26,13 +34,11 @@ class AuthService:
current_user = await user_dao.get_by_username(db, obj.username)
if not current_user:
raise errors.NotFoundError(msg='用户不存在')
- elif not await jwt.password_verify(f'{obj.password}{current_user.salt}', current_user.password):
+ elif not await password_verify(f'{obj.password}{current_user.salt}', current_user.password):
raise errors.AuthorizationError(msg='密码错误')
elif not current_user.status:
raise errors.AuthorizationError(msg='用户已锁定, 登陆失败')
- access_token, _ = await jwt.create_access_token(
- str(current_user.id), multi_login=current_user.is_multi_login
- )
+ access_token, _ = await create_access_token(str(current_user.id), multi_login=current_user.is_multi_login)
await user_dao.update_login_time(db, obj.username)
return access_token, current_user
@@ -43,19 +49,19 @@ class AuthService:
current_user = await user_dao.get_by_username(db, obj.username)
if not current_user:
raise errors.NotFoundError(msg='用户不存在')
- elif not await jwt.password_verify(obj.password + current_user.salt, current_user.password):
+ elif not await password_verify(obj.password + current_user.salt, current_user.password):
raise errors.AuthorizationError(msg='密码错误')
elif not current_user.status:
raise errors.AuthorizationError(msg='用户已锁定, 登陆失败')
- captcha_code = await redis_client.get(f'{settings.CAPTCHA_LOGIN_REDIS_PREFIX}:{request.state.ip}')
+ captcha_code = await redis_client.get(f'{admin_settings.CAPTCHA_LOGIN_REDIS_PREFIX}:{request.state.ip}')
if not captcha_code:
raise errors.AuthorizationError(msg='验证码失效,请重新获取')
if captcha_code.lower() != obj.captcha.lower():
raise errors.CustomError(error=CustomErrorCode.CAPTCHA_ERROR)
- access_token, access_token_expire_time = await jwt.create_access_token(
+ access_token, access_token_expire_time = await create_access_token(
str(current_user.id), multi_login=current_user.is_multi_login
)
- refresh_token, refresh_token_expire_time = await jwt.create_refresh_token(
+ refresh_token, refresh_token_expire_time = await create_refresh_token(
str(current_user.id), access_token_expire_time, multi_login=current_user.is_multi_login
)
await user_dao.update_login_time(db, obj.username)
@@ -85,7 +91,7 @@ class AuthService:
msg='登录成功',
)
background_tasks.add_task(LoginLogService.create, **login_log)
- await redis_client.delete(f'{settings.CAPTCHA_LOGIN_REDIS_PREFIX}:{request.state.ip}')
+ await redis_client.delete(f'{admin_settings.CAPTCHA_LOGIN_REDIS_PREFIX}:{request.state.ip}')
data = GetLoginToken(
access_token=access_token,
refresh_token=refresh_token,
@@ -97,7 +103,7 @@ class AuthService:
@staticmethod
async def new_token(*, request: Request, refresh_token: str) -> GetNewToken:
- user_id = await jwt.jwt_decode(refresh_token)
+ user_id = await jwt_decode(refresh_token)
if request.user.id != user_id:
raise errors.TokenError(msg='刷新 token 无效')
async with async_db_session() as db:
@@ -106,13 +112,13 @@ class AuthService:
raise errors.NotFoundError(msg='用户不存在')
elif not current_user.status:
raise errors.AuthorizationError(msg='用户已锁定,操作失败')
- current_token = await jwt.get_token(request)
+ current_token = await get_token(request)
(
new_access_token,
new_refresh_token,
new_access_token_expire_time,
new_refresh_token_expire_time,
- ) = await jwt.create_new_token(
+ ) = await create_new_token(
str(current_user.id), current_token, refresh_token, multi_login=current_user.is_multi_login
)
data = GetNewToken(
@@ -125,7 +131,7 @@ class AuthService:
@staticmethod
async def logout(*, request: Request) -> None:
- token = await jwt.get_token(request)
+ token = await get_token(request)
if request.user.is_multi_login:
key = f'{settings.TOKEN_REDIS_PREFIX}:{request.user.id}:{token}'
await redis_client.delete(key)
@@ -134,4 +140,4 @@ class AuthService:
await redis_client.delete_prefix(prefix)
-auth_service: AuthService = AuthService()
+auth_service = AuthService()
diff --git a/backend/app/services/casbin_service.py b/backend/app/admin/service/casbin_service.py
similarity index 89%
rename from backend/app/services/casbin_service.py
rename to backend/app/admin/service/casbin_service.py
index 0493571..9336f30 100644
--- a/backend/app/services/casbin_service.py
+++ b/backend/app/admin/service/casbin_service.py
@@ -4,11 +4,8 @@ from uuid import UUID
from sqlalchemy import Select
-from backend.app.common.exception import errors
-from backend.app.common.rbac import rbac
-from backend.app.crud.crud_casbin import casbin_dao
-from backend.app.database.db_mysql import async_db_session
-from backend.app.schemas.casbin_rule import (
+from backend.app.admin.crud.crud_casbin import casbin_dao
+from backend.app.admin.schema.casbin_rule import (
CreatePolicyParam,
CreateUserRoleParam,
DeleteAllPoliciesParam,
@@ -16,12 +13,15 @@ from backend.app.schemas.casbin_rule import (
DeleteUserRoleParam,
UpdatePolicyParam,
)
+from backend.common.exception import errors
+from backend.common.security.rbac import rbac
+from backend.database.db_mysql import async_db_session
class CasbinService:
@staticmethod
async def get_casbin_list(*, ptype: str, sub: str) -> Select:
- return await casbin_dao.get_all_policy(ptype, sub)
+ return await casbin_dao.get_list(ptype, sub)
@staticmethod
async def get_policy_list(*, role: int | None = None) -> list:
@@ -32,12 +32,6 @@ class CasbinService:
data = enforcer.get_policy()
return data
- @staticmethod
- async def get_policy_list_by_role(*, role: str) -> list:
- enforcer = await rbac.enforcer()
- data = enforcer.get_filtered_named_policy('p', 0, role)
- return data
-
@staticmethod
async def create_policy(*, p: CreatePolicyParam) -> bool:
enforcer = await rbac.enforcer()
@@ -140,4 +134,4 @@ class CasbinService:
return count
-casbin_service: CasbinService = CasbinService()
+casbin_service = CasbinService()
diff --git a/backend/app/services/dept_service.py b/backend/app/admin/service/dept_service.py
similarity index 86%
rename from backend/app/services/dept_service.py
rename to backend/app/admin/service/dept_service.py
index 4e9636d..97bb150 100644
--- a/backend/app/services/dept_service.py
+++ b/backend/app/admin/service/dept_service.py
@@ -2,12 +2,12 @@
# -*- coding: utf-8 -*-
from typing import Any
-from backend.app.common.exception import errors
-from backend.app.crud.crud_dept import dept_dao
-from backend.app.database.db_mysql import async_db_session
-from backend.app.models import Dept
-from backend.app.schemas.dept import CreateDeptParam, UpdateDeptParam
-from backend.app.utils.build_tree import get_tree_data
+from backend.app.admin.crud.crud_dept import dept_dao
+from backend.app.admin.model import Dept
+from backend.app.admin.schema.dept import CreateDeptParam, UpdateDeptParam
+from backend.common.exception import errors
+from backend.database.db_mysql import async_db_session
+from backend.utils.build_tree import get_tree_data
class DeptService:
@@ -61,7 +61,7 @@ class DeptService:
@staticmethod
async def delete(*, pk: int) -> int:
async with async_db_session.begin() as db:
- dept_user = await dept_dao.get_user_relation(db, pk)
+ dept_user = await dept_dao.get_relation(db, pk)
if dept_user:
raise errors.ForbiddenError(msg='部门下存在用户,无法删除')
children = await dept_dao.get_children(db, pk)
@@ -71,4 +71,4 @@ class DeptService:
return count
-dept_service: DeptService = DeptService()
+dept_service = DeptService()
diff --git a/backend/app/services/dict_data_service.py b/backend/app/admin/service/dict_data_service.py
similarity index 79%
rename from backend/app/services/dict_data_service.py
rename to backend/app/admin/service/dict_data_service.py
index 9623f41..bc1b54a 100644
--- a/backend/app/services/dict_data_service.py
+++ b/backend/app/admin/service/dict_data_service.py
@@ -2,12 +2,12 @@
# -*- coding: utf-8 -*-
from sqlalchemy import Select
-from backend.app.common.exception import errors
-from backend.app.crud.crud_dict_data import dict_data_dao
-from backend.app.crud.crud_dict_type import dict_type_dao
-from backend.app.database.db_mysql import async_db_session
-from backend.app.models.sys_dict_data import DictData
-from backend.app.schemas.dict_data import CreateDictDataParam, UpdateDictDataParam
+from backend.app.admin.crud.crud_dict_data import dict_data_dao
+from backend.app.admin.crud.crud_dict_type import dict_type_dao
+from backend.app.admin.model import DictData
+from backend.app.admin.schema.dict_data import CreateDictDataParam, UpdateDictDataParam
+from backend.common.exception import errors
+from backend.database.db_mysql import async_db_session
class DictDataService:
@@ -21,7 +21,7 @@ class DictDataService:
@staticmethod
async def get_select(*, label: str = None, value: str = None, status: int = None) -> Select:
- return await dict_data_dao.get_all(label=label, value=value, status=status)
+ return await dict_data_dao.get_list(label=label, value=value, status=status)
@staticmethod
async def create(*, obj: CreateDictDataParam) -> None:
@@ -56,4 +56,4 @@ class DictDataService:
return count
-dict_data_service: DictDataService = DictDataService()
+dict_data_service = DictDataService()
diff --git a/backend/app/services/dict_type_service.py b/backend/app/admin/service/dict_type_service.py
similarity index 77%
rename from backend/app/services/dict_type_service.py
rename to backend/app/admin/service/dict_type_service.py
index 59e208f..ac91537 100644
--- a/backend/app/services/dict_type_service.py
+++ b/backend/app/admin/service/dict_type_service.py
@@ -2,16 +2,16 @@
# -*- coding: utf-8 -*-
from sqlalchemy import Select
-from backend.app.common.exception import errors
-from backend.app.crud.crud_dict_type import dict_type_dao
-from backend.app.database.db_mysql import async_db_session
-from backend.app.schemas.dict_type import CreateDictTypeParam, UpdateDictTypeParam
+from backend.app.admin.crud.crud_dict_type import dict_type_dao
+from backend.app.admin.schema.dict_type import CreateDictTypeParam, UpdateDictTypeParam
+from backend.common.exception import errors
+from backend.database.db_mysql import async_db_session
class DictTypeService:
@staticmethod
async def get_select(*, name: str = None, code: str = None, status: int = None) -> Select:
- return await dict_type_dao.get_all(name=name, code=code, status=status)
+ return await dict_type_dao.get_list(name=name, code=code, status=status)
@staticmethod
async def create(*, obj: CreateDictTypeParam) -> None:
@@ -40,4 +40,4 @@ class DictTypeService:
return count
-dict_type_service: DictTypeService = DictTypeService()
+dict_type_service = DictTypeService()
diff --git a/backend/app/services/github_service.py b/backend/app/admin/service/github_service.py
similarity index 74%
rename from backend/app/services/github_service.py
rename to backend/app/admin/service/github_service.py
index 3fdca28..17af43f 100644
--- a/backend/app/services/github_service.py
+++ b/backend/app/admin/service/github_service.py
@@ -3,24 +3,26 @@
from fast_captcha import text_captcha
from fastapi import BackgroundTasks, Request
-from backend.app.common import jwt
-from backend.app.common.enums import LoginLogStatusType, UserSocialType
-from backend.app.common.exception.errors import AuthorizationError
-from backend.app.common.redis import redis_client
-from backend.app.core.conf import settings
-from backend.app.crud.crud_user import user_dao
-from backend.app.crud.crud_user_social import user_social_dao
-from backend.app.database.db_mysql import async_db_session
-from backend.app.schemas.token import GetLoginToken
-from backend.app.schemas.user import RegisterUserParam
-from backend.app.schemas.user_social import CreateUserSocialParam
-from backend.app.services.login_log_service import LoginLogService
-from backend.app.utils.timezone import timezone
+from backend.app.admin.conf import admin_settings
+from backend.app.admin.crud.crud_user import user_dao
+from backend.app.admin.crud.crud_user_social import user_social_dao
+from backend.app.admin.schema.token import GetLoginToken
+from backend.app.admin.schema.user import RegisterUserParam
+from backend.app.admin.schema.user_social import CreateUserSocialParam
+from backend.app.admin.service.login_log_service import LoginLogService
+from backend.common.enums import LoginLogStatusType, UserSocialType
+from backend.common.exception.errors import AuthorizationError
+from backend.common.security import jwt
+from backend.database.db_mysql import async_db_session
+from backend.database.db_redis import redis_client
+from backend.utils.timezone import timezone
class GithubService:
@staticmethod
- async def add_with_login(request: Request, background_tasks: BackgroundTasks, user: dict) -> GetLoginToken | None:
+ async def create_with_login(
+ request: Request, background_tasks: BackgroundTasks, user: dict
+ ) -> GetLoginToken | None:
async with async_db_session.begin() as db:
github_email = user['email']
if not github_email:
@@ -68,7 +70,7 @@ class GithubService:
msg='登录成功(OAuth2)',
)
background_tasks.add_task(LoginLogService.create, **login_log)
- await redis_client.delete(f'{settings.CAPTCHA_LOGIN_REDIS_PREFIX}:{request.state.ip}')
+ await redis_client.delete(f'{admin_settings.CAPTCHA_LOGIN_REDIS_PREFIX}:{request.state.ip}')
data = GetLoginToken(
access_token=access_token,
refresh_token=refresh_token,
@@ -79,4 +81,4 @@ class GithubService:
return data
-github_service: GithubService = GithubService()
+github_service = GithubService()
diff --git a/backend/app/services/login_log_service.py b/backend/app/admin/service/login_log_service.py
similarity index 81%
rename from backend/app/services/login_log_service.py
rename to backend/app/admin/service/login_log_service.py
index db21c36..ffc5148 100644
--- a/backend/app/services/login_log_service.py
+++ b/backend/app/admin/service/login_log_service.py
@@ -6,17 +6,17 @@ from fastapi import Request
from sqlalchemy import Select
from sqlalchemy.ext.asyncio import AsyncSession
-from backend.app.common.log import log
-from backend.app.crud.crud_login_log import login_log_dao
-from backend.app.database.db_mysql import async_db_session
-from backend.app.models import User
-from backend.app.schemas.login_log import CreateLoginLogParam
+from backend.app.admin.crud.crud_login_log import login_log_dao
+from backend.app.admin.model import User
+from backend.app.admin.schema.login_log import CreateLoginLogParam
+from backend.common.log import log
+from backend.database.db_mysql import async_db_session
class LoginLogService:
@staticmethod
async def get_select(*, username: str, status: int, ip: str) -> Select:
- return await login_log_dao.get_all(username=username, status=status, ip=ip)
+ return await login_log_dao.get_list(username=username, status=status, ip=ip)
@staticmethod
async def create(
@@ -56,4 +56,4 @@ class LoginLogService:
return count
-login_log_service: LoginLogService = LoginLogService()
+login_log_service = LoginLogService()
diff --git a/backend/app/services/menu_service.py b/backend/app/admin/service/menu_service.py
similarity index 87%
rename from backend/app/services/menu_service.py
rename to backend/app/admin/service/menu_service.py
index 8f7013c..01f583d 100644
--- a/backend/app/services/menu_service.py
+++ b/backend/app/admin/service/menu_service.py
@@ -4,15 +4,15 @@ from typing import Any
from fastapi import Request
-from backend.app.common.exception import errors
-from backend.app.common.redis import redis_client
-from backend.app.core.conf import settings
-from backend.app.crud.crud_menu import menu_dao
-from backend.app.crud.crud_role import role_dao
-from backend.app.database.db_mysql import async_db_session
-from backend.app.models import Menu
-from backend.app.schemas.menu import CreateMenuParam, UpdateMenuParam
-from backend.app.utils.build_tree import get_tree_data
+from backend.app.admin.crud.crud_menu import menu_dao
+from backend.app.admin.crud.crud_role import role_dao
+from backend.app.admin.model import Menu
+from backend.app.admin.schema.menu import CreateMenuParam, UpdateMenuParam
+from backend.common.exception import errors
+from backend.core.conf import settings
+from backend.database.db_mysql import async_db_session
+from backend.database.db_redis import redis_client
+from backend.utils.build_tree import get_tree_data
class MenuService:
@@ -96,4 +96,4 @@ class MenuService:
return count
-menu_service: MenuService = MenuService()
+menu_service = MenuService()
diff --git a/backend/app/services/opera_log_service.py b/backend/app/admin/service/opera_log_service.py
similarity index 71%
rename from backend/app/services/opera_log_service.py
rename to backend/app/admin/service/opera_log_service.py
index ecc99c5..870b8dc 100644
--- a/backend/app/services/opera_log_service.py
+++ b/backend/app/admin/service/opera_log_service.py
@@ -2,15 +2,15 @@
# -*- coding: utf-8 -*-
from sqlalchemy import Select
-from backend.app.crud.crud_opera_log import opera_log_dao
-from backend.app.database.db_mysql import async_db_session
-from backend.app.schemas.opera_log import CreateOperaLogParam
+from backend.app.admin.crud.crud_opera_log import opera_log_dao
+from backend.app.admin.schema.opera_log import CreateOperaLogParam
+from backend.database.db_mysql import async_db_session
class OperaLogService:
@staticmethod
async def get_select(*, username: str | None = None, status: int | None = None, ip: str | None = None) -> Select:
- return await opera_log_dao.get_all(username=username, status=status, ip=ip)
+ return await opera_log_dao.get_list(username=username, status=status, ip=ip)
@staticmethod
async def create(*, obj_in: CreateOperaLogParam):
@@ -30,4 +30,4 @@ class OperaLogService:
return count
-opera_log_service: OperaLogService = OperaLogService()
+opera_log_service = OperaLogService()
diff --git a/backend/app/services/role_service.py b/backend/app/admin/service/role_service.py
similarity index 83%
rename from backend/app/services/role_service.py
rename to backend/app/admin/service/role_service.py
index 6bfcd3b..d95e10b 100644
--- a/backend/app/services/role_service.py
+++ b/backend/app/admin/service/role_service.py
@@ -5,14 +5,14 @@ from typing import Sequence
from fastapi import Request
from sqlalchemy import Select
-from backend.app.common.exception import errors
-from backend.app.common.redis import redis_client
-from backend.app.core.conf import settings
-from backend.app.crud.crud_menu import menu_dao
-from backend.app.crud.crud_role import role_dao
-from backend.app.database.db_mysql import async_db_session
-from backend.app.models import Role
-from backend.app.schemas.role import CreateRoleParam, UpdateRoleMenuParam, UpdateRoleParam
+from backend.app.admin.crud.crud_menu import menu_dao
+from backend.app.admin.crud.crud_role import role_dao
+from backend.app.admin.model import Role
+from backend.app.admin.schema.role import CreateRoleParam, UpdateRoleMenuParam, UpdateRoleParam
+from backend.common.exception import errors
+from backend.core.conf import settings
+from backend.database.db_mysql import async_db_session
+from backend.database.db_redis import redis_client
class RoleService:
@@ -33,7 +33,7 @@ class RoleService:
@staticmethod
async def get_user_roles(*, pk: int) -> Sequence[Role]:
async with async_db_session() as db:
- roles = await role_dao.get_user_all(db, user_id=pk)
+ roles = await role_dao.get_user_roles(db, user_id=pk)
return roles
@staticmethod
@@ -82,4 +82,4 @@ class RoleService:
return count
-role_service: RoleService = RoleService()
+role_service = RoleService()
diff --git a/backend/app/services/user_service.py b/backend/app/admin/service/user_service.py
similarity index 93%
rename from backend/app/services/user_service.py
rename to backend/app/admin/service/user_service.py
index 35552c7..e27205b 100644
--- a/backend/app/services/user_service.py
+++ b/backend/app/admin/service/user_service.py
@@ -5,16 +5,11 @@ import random
from fastapi import Request
from sqlalchemy import Select
-from backend.app.common.exception import errors
-from backend.app.common.jwt import get_token, password_verify, superuser_verify
-from backend.app.common.redis import redis_client
-from backend.app.core.conf import settings
-from backend.app.crud.crud_dept import dept_dao
-from backend.app.crud.crud_role import role_dao
-from backend.app.crud.crud_user import user_dao
-from backend.app.database.db_mysql import async_db_session
-from backend.app.models import User
-from backend.app.schemas.user import (
+from backend.app.admin.crud.crud_dept import dept_dao
+from backend.app.admin.crud.crud_role import role_dao
+from backend.app.admin.crud.crud_user import user_dao
+from backend.app.admin.model import User
+from backend.app.admin.schema.user import (
AddUserParam,
AvatarParam,
RegisterUserParam,
@@ -22,6 +17,11 @@ from backend.app.schemas.user import (
UpdateUserParam,
UpdateUserRoleParam,
)
+from backend.common.exception import errors
+from backend.common.security.jwt import get_token, password_verify, superuser_verify
+from backend.core.conf import settings
+from backend.database.db_mysql import async_db_session
+from backend.database.db_redis import redis_client
class UserService:
@@ -147,7 +147,7 @@ class UserService:
@staticmethod
async def get_select(*, dept: int, username: str = None, phone: str = None, status: int = None) -> Select:
- return await user_dao.get_all(dept=dept, username=username, phone=phone, status=status)
+ return await user_dao.get_list(dept=dept, username=username, phone=phone, status=status)
@staticmethod
async def update_permission(*, request: Request, pk: int) -> int:
@@ -225,4 +225,4 @@ class UserService:
return count
-user_service: UserService = UserService()
+user_service = UserService()
diff --git a/backend/app/database/__init__.py b/backend/app/admin/tests/__init__.py
similarity index 100%
rename from backend/app/database/__init__.py
rename to backend/app/admin/tests/__init__.py
diff --git a/backend/app/middleware/__init__.py b/backend/app/admin/tests/api_v1/__init__.py
similarity index 100%
rename from backend/app/middleware/__init__.py
rename to backend/app/admin/tests/api_v1/__init__.py
diff --git a/backend/app/admin/tests/api_v1/test_auth.py b/backend/app/admin/tests/api_v1/test_auth.py
new file mode 100644
index 0000000..4eb9e52
--- /dev/null
+++ b/backend/app/admin/tests/api_v1/test_auth.py
@@ -0,0 +1,11 @@
+#!/usr/bin/env python3
+# -*- coding: utf-8 -*-
+from starlette.testclient import TestClient
+
+from backend.core.conf import settings
+
+
+def test_logout(client: TestClient, token_headers: dict[str, str]) -> None:
+ response = client.post(f'{settings.API_V1_STR}/auth/logout', headers=token_headers)
+ assert response.status_code == 200
+ assert response.json()['code'] == 200
diff --git a/backend/app/tests/conftest.py b/backend/app/admin/tests/conftest.py
similarity index 69%
rename from backend/app/tests/conftest.py
rename to backend/app/admin/tests/conftest.py
index 7bb6a1c..04a2fb5 100644
--- a/backend/app/tests/conftest.py
+++ b/backend/app/admin/tests/conftest.py
@@ -1,19 +1,15 @@
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
-import sys
-
-sys.path.append('../../')
-
from typing import Dict, Generator
import pytest
from starlette.testclient import TestClient
-from backend.app.database.db_mysql import get_db
-from backend.app.main import app
-from backend.app.tests.utils.db_mysql import override_get_db
-from backend.app.tests.utils.get_headers import get_token_headers
+from backend.app.admin.tests.utils.db_mysql import override_get_db
+from backend.app.admin.tests.utils.get_headers import get_token_headers
+from backend.database.db_mysql import get_db
+from backend.main import app
app.dependency_overrides[get_db] = override_get_db
diff --git a/backend/app/schemas/__init__.py b/backend/app/admin/tests/utils/__init__.py
similarity index 100%
rename from backend/app/schemas/__init__.py
rename to backend/app/admin/tests/utils/__init__.py
diff --git a/backend/app/admin/tests/utils/db_mysql.py b/backend/app/admin/tests/utils/db_mysql.py
new file mode 100644
index 0000000..7774031
--- /dev/null
+++ b/backend/app/admin/tests/utils/db_mysql.py
@@ -0,0 +1,25 @@
+#!/usr/bin/env python3
+# -*- coding: utf-8 -*-
+from sqlalchemy.ext.asyncio import AsyncSession
+
+from backend.core.conf import settings
+from backend.database.db_mysql import create_engine_and_session
+
+TEST_SQLALCHEMY_DATABASE_URL = (
+ f'mysql+asyncmy://{settings.MYSQL_USER}:{settings.MYSQL_PASSWORD}@{settings.MYSQL_HOST}:'
+ f'{settings.MYSQL_PORT}/{settings.MYSQL_DATABASE}_test?charset={settings.MYSQL_CHARSET}'
+)
+
+_, test_async_db_session = create_engine_and_session(TEST_SQLALCHEMY_DATABASE_URL)
+
+
+async def override_get_db() -> AsyncSession:
+ """session 生成器"""
+ session = test_async_db_session()
+ try:
+ yield session
+ except Exception as se:
+ await session.rollback()
+ raise se
+ finally:
+ await session.close()
diff --git a/backend/app/tests/utils/get_headers.py b/backend/app/admin/tests/utils/get_headers.py
similarity index 62%
rename from backend/app/tests/utils/get_headers.py
rename to backend/app/admin/tests/utils/get_headers.py
index 5e582cb..8c28102 100644
--- a/backend/app/tests/utils/get_headers.py
+++ b/backend/app/admin/tests/utils/get_headers.py
@@ -1,18 +1,16 @@
-#!/usr/bin/env python3
-# -*- coding: utf-8 -*-
-from typing import Dict
-
+#!/usr/bin/env from typing import Dict
from starlette.testclient import TestClient
-from backend.app.core.conf import settings
+from backend.core.conf import settings
-def get_token_headers(client: TestClient, username: str, password: str) -> Dict[str, str]:
+def get_token_headers(client: TestClient, username: str, password: str) -> dict[str, str]:
data = {
'username': username,
'password': password,
}
- response = client.post(f'{settings.API_V1_STR}/auth/swagger_login', data=data)
+ response = client.post(f'{settings.API_V1_STR}/auth/login/swagger', params=data)
+ response.raise_for_status()
token_type = response.json()['token_type']
access_token = response.json()['access_token']
headers = {'Authorization': f'{token_type} {access_token}'}
diff --git a/backend/app/api/routers.py b/backend/app/api/routers.py
deleted file mode 100644
index fcc423b..0000000
--- a/backend/app/api/routers.py
+++ /dev/null
@@ -1,22 +0,0 @@
-#!/usr/bin/env python3
-# -*- coding: utf-8 -*-
-from fastapi import APIRouter
-
-from backend.app.api.v1.auth import router as auth_router
-from backend.app.api.v1.log import router as log_router
-from backend.app.api.v1.mixed import router as mixed_router
-from backend.app.api.v1.monitor import router as monitor_router
-from backend.app.api.v1.sys import router as sys_router
-from backend.app.api.v1.task import router as task_router
-from backend.app.core.conf import settings
-
-v1 = APIRouter(prefix=settings.API_V1_STR)
-
-# 集合
-v1.include_router(auth_router)
-v1.include_router(sys_router)
-v1.include_router(log_router)
-v1.include_router(monitor_router)
-v1.include_router(mixed_router)
-# 独立
-v1.include_router(task_router, prefix='/tasks', tags=['任务管理'])
diff --git a/backend/app/api/v1/auth/__init__.py b/backend/app/api/v1/auth/__init__.py
deleted file mode 100644
index 06c724c..0000000
--- a/backend/app/api/v1/auth/__init__.py
+++ /dev/null
@@ -1,13 +0,0 @@
-#!/usr/bin/env python3
-# -*- coding: utf-8 -*-
-from fastapi import APIRouter
-
-from backend.app.api.v1.auth.auth import router as auth_router
-from backend.app.api.v1.auth.captcha import router as captcha_router
-from backend.app.api.v1.auth.github import router as github_router
-
-router = APIRouter(prefix='/auth', tags=['授权管理'])
-
-router.include_router(auth_router)
-router.include_router(captcha_router)
-router.include_router(github_router)
diff --git a/backend/app/api/v1/mixed/__init__.py b/backend/app/api/v1/mixed/__init__.py
deleted file mode 100644
index f84e40e..0000000
--- a/backend/app/api/v1/mixed/__init__.py
+++ /dev/null
@@ -1,11 +0,0 @@
-#!/usr/bin/env python3
-# -*- coding: utf-8 -*-
-from fastapi import APIRouter
-
-from backend.app.api.v1.mixed.config import router as config_router
-from backend.app.api.v1.mixed.tests import router as upload_router
-
-router = APIRouter(prefix='/mixes', tags=['杂项'])
-
-router.include_router(config_router)
-router.include_router(upload_router)
diff --git a/backend/app/api/v1/mixed/config.py b/backend/app/api/v1/mixed/config.py
deleted file mode 100644
index 438dca2..0000000
--- a/backend/app/api/v1/mixed/config.py
+++ /dev/null
@@ -1,33 +0,0 @@
-#!/usr/bin/env python3
-# -*- coding: utf-8 -*-
-from fastapi import APIRouter, Depends, Request
-from fastapi.routing import APIRoute
-
-from backend.app.common.permission import RequestPermission
-from backend.app.common.rbac import DependsRBAC
-from backend.app.common.response.response_schema import ResponseModel, response_base
-
-router = APIRouter()
-
-
-@router.get(
- '/routes',
- summary='获取所有路由',
- dependencies=[
- Depends(RequestPermission('sys:route:list')),
- DependsRBAC,
- ],
-)
-async def get_all_route(request: Request) -> ResponseModel:
- data = []
- for route in request.app.routes:
- if isinstance(route, APIRoute):
- data.append(
- {
- 'path': route.path,
- 'name': route.name,
- 'summary': route.summary,
- 'methods': route.methods,
- }
- )
- return await response_base.success(data={'route_list': data})
diff --git a/backend/app/api/v1/mixed/tests.py b/backend/app/api/v1/mixed/tests.py
deleted file mode 100644
index ccf037f..0000000
--- a/backend/app/api/v1/mixed/tests.py
+++ /dev/null
@@ -1,31 +0,0 @@
-#!/usr/bin/env python3
-# -*- coding: utf-8 -*-
-from typing import Annotated
-
-from fastapi import APIRouter, File, Form, UploadFile
-
-from backend.app.common.response.response_schema import ResponseModel, response_base
-from backend.app.tasks import task_demo_async
-
-router = APIRouter(prefix='/tests')
-
-
-@router.post('/send', summary='异步任务演示')
-async def send_task() -> ResponseModel:
- result = task_demo_async.delay()
- return await response_base.success(data=result.id)
-
-
-@router.post('/files', summary='上传文件演示')
-async def create_file(
- file: Annotated[bytes, File()],
- fileb: Annotated[UploadFile, File()],
- token: Annotated[str, Form()],
-) -> ResponseModel:
- return ResponseModel(
- data={
- 'file_size': len(file),
- 'token': token,
- 'fileb_content_type': fileb.content_type,
- }
- )
diff --git a/backend/app/api/v1/monitor/__init__.py b/backend/app/api/v1/monitor/__init__.py
deleted file mode 100644
index 8bb3ae6..0000000
--- a/backend/app/api/v1/monitor/__init__.py
+++ /dev/null
@@ -1,11 +0,0 @@
-#!/usr/bin/env python3
-# -*- coding: utf-8 -*-
-from fastapi import APIRouter
-
-from backend.app.api.v1.monitor.redis import router as redis_router
-from backend.app.api.v1.monitor.server import router as server_router
-
-router = APIRouter(prefix='/monitors', tags=['监控管理'])
-
-router.include_router(redis_router)
-router.include_router(server_router)
diff --git a/backend/app/api/v1/task.py b/backend/app/api/v1/task.py
deleted file mode 100644
index b43968b..0000000
--- a/backend/app/api/v1/task.py
+++ /dev/null
@@ -1,45 +0,0 @@
-#!/usr/bin/env python3
-# -*- coding: utf-8 -*-
-from typing import Annotated
-
-from fastapi import APIRouter, Body, Depends, Path
-
-from backend.app.common.jwt import DependsJwtAuth
-from backend.app.common.permission import RequestPermission
-from backend.app.common.rbac import DependsRBAC
-from backend.app.common.response.response_code import CustomResponseCode
-from backend.app.common.response.response_schema import ResponseModel, response_base
-from backend.app.services.task_service import task_service
-
-router = APIRouter()
-
-
-@router.get('', summary='获取所有可执行任务模块', dependencies=[DependsJwtAuth])
-async def get_all_tasks() -> ResponseModel:
- tasks = task_service.get_task_list()
- return await response_base.success(data=tasks)
-
-
-@router.get('/{pk}', summary='获取任务结果', dependencies=[DependsJwtAuth])
-async def get_task_result(pk: Annotated[str, Path(description='任务ID')]) -> ResponseModel:
- task = task_service.get(pk)
- if not task:
- return await response_base.fail(res=CustomResponseCode.HTTP_204, data=pk)
- return await response_base.success(data=task.result)
-
-
-@router.post(
- '/{module}',
- summary='执行任务',
- dependencies=[
- Depends(RequestPermission('sys:task:run')),
- DependsRBAC,
- ],
-)
-async def run_task(
- module: Annotated[str, Path(description='任务模块')],
- args: Annotated[list | None, Body()] = None,
- kwargs: Annotated[dict | None, Body()] = None,
-) -> ResponseModel:
- task = task_service.run(module=module, args=args, kwargs=kwargs)
- return await response_base.success(data=task.result)
diff --git a/backend/app/celery-start.sh b/backend/app/celery-start.sh
deleted file mode 100644
index a44f2cb..0000000
--- a/backend/app/celery-start.sh
+++ /dev/null
@@ -1,3 +0,0 @@
-#!/usr/bin/env bash
-
-celery -A tasks worker --loglevel=INFO -B
diff --git a/backend/app/common/celery.py b/backend/app/common/celery.py
deleted file mode 100644
index 559992d..0000000
--- a/backend/app/common/celery.py
+++ /dev/null
@@ -1,55 +0,0 @@
-#!/usr/bin/env python3
-# -*- coding: utf-8 -*-
-from celery import Celery
-
-from backend.app.core.conf import settings
-
-__all__ = ['celery_app']
-
-
-def make_celery(main_name: str) -> Celery:
- """
- 创建 celery 应用
-
- :param main_name: __main__ module name
- :return:
- """
- app = Celery(main_name)
- app.autodiscover_tasks(packages=['backend.app'])
-
- # Celery Config
- # https://docs.celeryq.dev/en/stable/userguide/configuration.html
- app.conf.broker_url = (
- (
- f'redis://:{settings.CELERY_REDIS_PASSWORD}@{settings.CELERY_REDIS_HOST}:'
- f'{settings.CELERY_REDIS_PORT}/{settings.CELERY_BROKER_REDIS_DATABASE}'
- )
- if settings.CELERY_BROKER == 'redis'
- else (
- f'amqp://{settings.RABBITMQ_USERNAME}:{settings.RABBITMQ_PASSWORD}@{settings.RABBITMQ_HOST}:'
- f'{settings.RABBITMQ_PORT}'
- )
- )
- app.conf.result_backend = (
- f'redis://:{settings.CELERY_REDIS_PASSWORD}@{settings.CELERY_REDIS_HOST}:'
- f'{settings.CELERY_REDIS_PORT}/{settings.CELERY_BACKEND_REDIS_DATABASE}'
- )
- app.conf.result_backend_transport_options = {
- 'global_keyprefix': settings.CELERY_BACKEND_REDIS_PREFIX,
- 'retry_policy': {
- 'timeout': settings.CELERY_BACKEND_REDIS_TIMEOUT,
- },
- 'result_chord_ordered': settings.CELERY_BACKEND_REDIS_ORDERED,
- }
- app.conf.timezone = settings.DATETIME_TIMEZONE
- app.conf.task_track_started = True
-
- # Celery Schedule Tasks
- # https://docs.celeryq.dev/en/stable/userguide/periodic-tasks.html
- app.conf.beat_schedule = settings.CELERY_BEAT_SCHEDULE
- app.conf.beat_schedule_filename = settings.CELERY_BEAT_SCHEDULE_FILENAME
-
- return app
-
-
-celery_app = make_celery('celery_app')
diff --git a/backend/app/core/path_conf.py b/backend/app/core/path_conf.py
deleted file mode 100644
index 97cede4..0000000
--- a/backend/app/core/path_conf.py
+++ /dev/null
@@ -1,18 +0,0 @@
-#!/usr/bin/env python3
-# -*- coding: utf-8 -*-
-import os
-
-from pathlib import Path
-
-# 获取项目根目录
-# 或使用绝对路径,指到backend目录为止,例如windows:BasePath = D:\git_project\fastapi_mysql\backend
-BasePath = Path(__file__).resolve().parent.parent.parent
-
-# 迁移文件存放路径
-Versions = os.path.join(BasePath, 'app', 'alembic', 'versions')
-
-# 日志文件路径
-LogPath = os.path.join(BasePath, 'app', 'log')
-
-# 离线 IP 数据库路径
-IP2REGION_XDB = os.path.join(BasePath, 'app', 'static', 'ip2region.xdb')
diff --git a/backend/app/models/__init__.py b/backend/app/models/__init__.py
deleted file mode 100644
index 24ecdba..0000000
--- a/backend/app/models/__init__.py
+++ /dev/null
@@ -1,18 +0,0 @@
-#!/usr/bin/env python3
-# -*- coding: utf-8 -*-
-"""
-# 导入所有模型,并将 Base 放在最前面, 以便 Base 拥有它们
-# imported by Alembic
-"""
-from backend.app.models.base import MappedBase
-from backend.app.models.sys_api import Api
-from backend.app.models.sys_casbin_rule import CasbinRule
-from backend.app.models.sys_dept import Dept
-from backend.app.models.sys_dict_data import DictData
-from backend.app.models.sys_dict_type import DictType
-from backend.app.models.sys_login_log import LoginLog
-from backend.app.models.sys_menu import Menu
-from backend.app.models.sys_opera_log import OperaLog
-from backend.app.models.sys_role import Role
-from backend.app.models.sys_user import User
-from backend.app.models.sys_user_social import UserSocial
diff --git a/backend/app/router.py b/backend/app/router.py
new file mode 100644
index 0000000..26adf14
--- /dev/null
+++ b/backend/app/router.py
@@ -0,0 +1,12 @@
+#!/usr/bin/env python3
+# -*- coding: utf-8 -*-
+from fastapi import APIRouter
+
+from backend.app.admin.api.router import v1 as admin_v1
+from backend.app.task.api.router import v1 as task_v1
+from backend.core.conf import settings
+
+route = APIRouter(prefix=settings.API_V1_STR)
+
+route.include_router(admin_v1)
+route.include_router(task_v1)
diff --git a/backend/app/services/task_service.py b/backend/app/services/task_service.py
deleted file mode 100644
index def0c59..0000000
--- a/backend/app/services/task_service.py
+++ /dev/null
@@ -1,36 +0,0 @@
-#!/usr/bin/env python3
-# -*- coding: utf-8 -*-
-from celery.exceptions import BackendGetMetaError, NotRegistered
-from celery.result import AsyncResult
-
-from backend.app.common.celery import celery_app
-from backend.app.common.exception.errors import NotFoundError
-
-
-class TaskService:
- @staticmethod
- def get(pk: str) -> AsyncResult | None:
- try:
- result = celery_app.AsyncResult(pk)
- except (BackendGetMetaError, NotRegistered):
- raise NotFoundError(msg='任务不存在')
- if result.failed():
- return None
- return result
-
- @staticmethod
- def get_task_list() -> dict:
- filtered_tasks = {}
- tasks = celery_app.tasks
- for key, value in tasks.items():
- if not key.startswith('celery.'):
- filtered_tasks[key] = value
- return filtered_tasks
-
- @staticmethod
- def run(*, module: str, args: list | None = None, kwargs: dict | None = None) -> AsyncResult:
- task = celery_app.send_task(module, args, kwargs)
- return task
-
-
-task_service: TaskService = TaskService()
diff --git a/backend/app/task/__init__.py b/backend/app/task/__init__.py
new file mode 100644
index 0000000..f041604
--- /dev/null
+++ b/backend/app/task/__init__.py
@@ -0,0 +1,8 @@
+#!/usr/bin/env python3
+# -*- coding: utf-8 -*-
+import sys
+
+from pathlib import Path
+
+# 导入项目根目录
+sys.path.append(str(Path(__file__).resolve().parent.parent.parent.parent))
diff --git a/backend/app/services/__init__.py b/backend/app/task/api/__init__.py
similarity index 100%
rename from backend/app/services/__init__.py
rename to backend/app/task/api/__init__.py
diff --git a/backend/app/task/api/router.py b/backend/app/task/api/router.py
new file mode 100644
index 0000000..b038e51
--- /dev/null
+++ b/backend/app/task/api/router.py
@@ -0,0 +1,9 @@
+#!/usr/bin/env python3
+# -*- coding: utf-8 -*-
+from fastapi import APIRouter
+
+from backend.app.task.api.v1.task import router as task_router
+
+v1 = APIRouter()
+
+v1.include_router(task_router, prefix='/tasks', tags=['任务管理'])
diff --git a/backend/app/tests/__init__.py b/backend/app/task/api/v1/__init__.py
similarity index 100%
rename from backend/app/tests/__init__.py
rename to backend/app/task/api/v1/__init__.py
diff --git a/backend/app/task/api/v1/task.py b/backend/app/task/api/v1/task.py
new file mode 100644
index 0000000..9187333
--- /dev/null
+++ b/backend/app/task/api/v1/task.py
@@ -0,0 +1,54 @@
+#!/usr/bin/env python3
+# -*- coding: utf-8 -*-
+from typing import Annotated
+
+from fastapi import APIRouter, Body, Depends, Path
+
+from backend.app.task.service.task_service import task_service
+from backend.common.response.response_schema import ResponseModel, response_base
+from backend.common.security.jwt import DependsJwtAuth
+from backend.common.security.permission import RequestPermission
+from backend.common.security.rbac import DependsRBAC
+
+router = APIRouter()
+
+
+@router.get('', summary='获取所有可执行任务模块', dependencies=[DependsJwtAuth])
+async def get_all_tasks() -> ResponseModel:
+ tasks = task_service.get_list()
+ return await response_base.success(data=tasks)
+
+
+@router.get('/current', summary='获取当前正在执行的任务', dependencies=[DependsJwtAuth])
+async def get_current_task() -> ResponseModel:
+ task = task_service.get()
+ return await response_base.success(data=task)
+
+
+@router.get('/{uid}/status', summary='获取任务状态', dependencies=[DependsJwtAuth])
+async def get_task_status(uid: Annotated[str, Path(description='任务ID')]) -> ResponseModel:
+ status = task_service.get_status(uid)
+ return await response_base.success(data=status)
+
+
+@router.get('/{uid}', summary='获取任务结果', dependencies=[DependsJwtAuth])
+async def get_task_result(uid: Annotated[str, Path(description='任务ID')]) -> ResponseModel:
+ task = task_service.get_result(uid)
+ return await response_base.success(data=task)
+
+
+@router.post(
+ '/{name}',
+ summary='执行任务',
+ dependencies=[
+ Depends(RequestPermission('sys:task:run')),
+ DependsRBAC,
+ ],
+)
+async def run_task(
+ name: Annotated[str, Path(description='任务名称')],
+ args: Annotated[list | None, Body(description='任务函数位置参数')] = None,
+ kwargs: Annotated[dict | None, Body(description='任务函数关键字参数')] = None,
+) -> ResponseModel:
+ task = task_service.run(name=name, args=args, kwargs=kwargs)
+ return await response_base.success(data=task)
diff --git a/backend/app/task/celery.py b/backend/app/task/celery.py
new file mode 100644
index 0000000..f1e199c
--- /dev/null
+++ b/backend/app/task/celery.py
@@ -0,0 +1,59 @@
+#!/usr/bin/env python3
+# -*- coding: utf-8 -*-
+from celery import Celery
+
+from backend.app.task.conf import task_settings
+from backend.core.conf import settings
+
+__all__ = ['celery_app']
+
+
+def init_celery() -> Celery:
+ """创建 celery 应用"""
+
+ app = Celery('fba_celery')
+
+ # Celery Config
+ # https://docs.celeryq.dev/en/stable/userguide/configuration.html
+ _redis_broker = (
+ f'redis://:{settings.REDIS_PASSWORD}@{settings.REDIS_HOST}:'
+ f'{settings.REDIS_PORT}/{task_settings.CELERY_BROKER_REDIS_DATABASE}'
+ )
+ _amqp_broker = (
+ f'amqp://{task_settings.RABBITMQ_USERNAME}:{task_settings.RABBITMQ_PASSWORD}@'
+ f'{task_settings.RABBITMQ_HOST}:{task_settings.RABBITMQ_PORT}'
+ )
+ _result_backend = (
+ f'redis://:{settings.REDIS_PASSWORD}@{settings.REDIS_HOST}:'
+ f'{settings.REDIS_PORT}/{task_settings.CELERY_BACKEND_REDIS_DATABASE}'
+ )
+ _result_backend_transport_options = {
+ 'global_keyprefix': f'{task_settings.CELERY_BACKEND_REDIS_PREFIX}_',
+ 'retry_policy': {
+ 'timeout': task_settings.CELERY_BACKEND_REDIS_TIMEOUT,
+ },
+ }
+
+ # Celery Schedule Tasks
+ # https://docs.celeryq.dev/en/stable/userguide/periodic-tasks.html
+ _beat_schedule = task_settings.CELERY_SCHEDULE
+
+ # Update celery settings
+ app.conf.update(
+ broker_url=_redis_broker if task_settings.CELERY_BROKER == 'redis' else _amqp_broker,
+ result_backend=_result_backend,
+ result_backend_transport_options=_result_backend_transport_options,
+ timezone=settings.DATETIME_TIMEZONE,
+ enable_utc=False,
+ task_track_started=True,
+ beat_schedule=_beat_schedule,
+ )
+
+ # Load task modules
+ app.autodiscover_tasks(task_settings.CELERY_TASKS_PACKAGES)
+
+ return app
+
+
+# 创建 celery 实例
+celery_app = init_celery()
diff --git a/backend/app/tests/api_v1/__init__.py b/backend/app/task/celery_task/__init__.py
similarity index 100%
rename from backend/app/tests/api_v1/__init__.py
rename to backend/app/task/celery_task/__init__.py
diff --git a/backend/app/tests/utils/__init__.py b/backend/app/task/celery_task/db_log/__init__.py
similarity index 100%
rename from backend/app/tests/utils/__init__.py
rename to backend/app/task/celery_task/db_log/__init__.py
diff --git a/backend/app/task/celery_task/db_log/tasks.py b/backend/app/task/celery_task/db_log/tasks.py
new file mode 100644
index 0000000..22395e2
--- /dev/null
+++ b/backend/app/task/celery_task/db_log/tasks.py
@@ -0,0 +1,39 @@
+#!/usr/bin/env python3
+# -*- coding: utf-8 -*-
+from sqlalchemy.exc import SQLAlchemyError
+
+from backend.app.admin.service.login_log_service import login_log_service
+from backend.app.admin.service.opera_log_service import opera_log_service
+from backend.app.task.celery import celery_app
+from backend.app.task.conf import task_settings
+
+
+@celery_app.task(
+ name='auto_delete_db_opera_log',
+ bind=True,
+ retry_backoff=True,
+ max_retries=task_settings.CELERY_TASK_MAX_RETRIES,
+)
+async def auto_delete_db_opera_log(self) -> int:
+ """自动删除数据库操作日志"""
+ try:
+ result = await opera_log_service.delete_all()
+ except SQLAlchemyError as exc:
+ raise self.retry(exc=exc)
+ return result
+
+
+@celery_app.task(
+ name='auto_delete_db_login_log',
+ bind=True,
+ retry_backoff=True,
+ max_retries=task_settings.CELERY_TASK_MAX_RETRIES,
+)
+async def auto_delete_db_login_log(self) -> int:
+ """自动删除数据库登录日志"""
+
+ try:
+ result = await login_log_service.delete_all()
+ except SQLAlchemyError as exc:
+ raise self.retry(exc=exc)
+ return result
diff --git a/backend/app/tasks.py b/backend/app/task/celery_task/tasks.py
similarity index 60%
rename from backend/app/tasks.py
rename to backend/app/task/celery_task/tasks.py
index 213d344..deb7256 100644
--- a/backend/app/tasks.py
+++ b/backend/app/task/celery_task/tasks.py
@@ -1,14 +1,11 @@
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
-import sys
import uuid
-sys.path.append('../../')
-
-from backend.app.common.celery import celery_app # noqa: E402
+from backend.app.task.celery import celery_app
-@celery_app.task
+@celery_app.task(name='task_demo_async')
def task_demo_async() -> str:
uid = uuid.uuid4().hex
print(f'异步任务 {uid} 执行成功')
diff --git a/backend/app/task/conf.py b/backend/app/task/conf.py
new file mode 100644
index 0000000..c5df638
--- /dev/null
+++ b/backend/app/task/conf.py
@@ -0,0 +1,69 @@
+#!/usr/bin/env python3
+# -*- coding: utf-8 -*-
+from functools import lru_cache
+from typing import Literal
+
+from celery.schedules import crontab
+from pydantic import model_validator
+from pydantic_settings import BaseSettings, SettingsConfigDict
+
+from backend.core.path_conf import BasePath
+
+
+class TaskSettings(BaseSettings):
+ """Task Settings"""
+
+ model_config = SettingsConfigDict(env_file=f'{BasePath}/.env', env_file_encoding='utf-8', extra='ignore')
+
+ # Env Config
+ ENVIRONMENT: Literal['dev', 'pro']
+
+ # Env Celery
+ CELERY_BROKER_REDIS_DATABASE: int # 仅当使用 redis 作为 broker 时生效, 更适用于测试环境
+ CELERY_BACKEND_REDIS_DATABASE: int
+
+ # Env Rabbitmq
+ # docker run -d --hostname fba-mq --name fba-mq -p 5672:5672 -p 15672:15672 rabbitmq:latest
+ RABBITMQ_HOST: str
+ RABBITMQ_PORT: int
+ RABBITMQ_USERNAME: str
+ RABBITMQ_PASSWORD: str
+
+ # Celery
+ CELERY_BROKER: Literal['rabbitmq', 'redis'] = 'redis'
+ CELERY_BACKEND_REDIS_PREFIX: str = 'fba_celery'
+ CELERY_BACKEND_REDIS_TIMEOUT: float = 5.0
+ CELERY_TASKS_PACKAGES: list[str] = [
+ 'app.task.celery_task',
+ 'app.task.celery_task.db_log',
+ ]
+ CELERY_TASK_MAX_RETRIES: int = 5
+ CELERY_SCHEDULE: dict = {
+ 'exec-every-10-seconds': {
+ 'task': 'task_demo_async',
+ 'schedule': 10,
+ },
+ 'exec-every-sunday': {
+ 'task': 'auto_delete_db_opera_log',
+ 'schedule': crontab(0, 0, day_of_week='6'), # type: ignore
+ },
+ 'exec-every-15-of-month': {
+ 'task': 'auto_delete_db_login_log',
+ 'schedule': crontab(0, 0, day_of_month='15'), # type: ignore
+ },
+ }
+
+ @model_validator(mode='before')
+ def validate_celery_broker(cls, values):
+ if values['ENVIRONMENT'] == 'pro':
+ values['CELERY_BROKER'] = 'rabbitmq'
+ return values
+
+
+@lru_cache
+def get_task_settings() -> TaskSettings:
+ """获取 task 配置"""
+ return TaskSettings()
+
+
+task_settings = get_task_settings()
diff --git a/backend/app/utils/__init__.py b/backend/app/task/service/__init__.py
similarity index 100%
rename from backend/app/utils/__init__.py
rename to backend/app/task/service/__init__.py
diff --git a/backend/app/task/service/task_service.py b/backend/app/task/service/task_service.py
new file mode 100644
index 0000000..ef4745e
--- /dev/null
+++ b/backend/app/task/service/task_service.py
@@ -0,0 +1,46 @@
+#!/usr/bin/env python3
+# -*- coding: utf-8 -*-
+from celery.exceptions import NotRegistered
+from celery.result import AsyncResult
+
+from backend.app.task.celery import celery_app
+from backend.common.exception.errors import NotFoundError
+
+
+class TaskService:
+ @staticmethod
+ def get_list():
+ filtered_tasks = []
+ tasks = celery_app.tasks
+ for key, value in tasks.items():
+ if not key.startswith('celery.'):
+ filtered_tasks.append({key, value})
+ return filtered_tasks
+
+ @staticmethod
+ def get():
+ return celery_app.current_worker_task
+
+ @staticmethod
+ def get_status(uid: str):
+ try:
+ result = AsyncResult(id=uid, app=celery_app)
+ except NotRegistered:
+ raise NotFoundError(msg='任务不存在')
+ return result.status
+
+ @staticmethod
+ def get_result(uid: str):
+ try:
+ result = AsyncResult(id=uid, app=celery_app)
+ except NotRegistered:
+ raise NotFoundError(msg='任务不存在')
+ return result
+
+ @staticmethod
+ def run(*, name: str, args: list | None = None, kwargs: dict | None = None):
+ task = celery_app.send_task(name=name, args=args, kwargs=kwargs)
+ return task
+
+
+task_service = TaskService()
diff --git a/backend/app/tests/api_v1/test_auth.py b/backend/app/tests/api_v1/test_auth.py
deleted file mode 100644
index 9484115..0000000
--- a/backend/app/tests/api_v1/test_auth.py
+++ /dev/null
@@ -1,22 +0,0 @@
-#!/usr/bin/env python3
-# -*- coding: utf-8 -*-
-from starlette.testclient import TestClient
-
-from backend.app.core.conf import settings
-from backend.app.tests.conftest import PYTEST_PASSWORD, PYTEST_USERNAME
-
-
-def test_login(client: TestClient) -> None:
- data = {
- 'username': PYTEST_USERNAME,
- 'password': PYTEST_PASSWORD,
- }
- response = client.post(f'{settings.API_V1_STR}/auth/swagger_login', data=data)
- assert response.status_code == 200
- assert response.json()['token_type'] == 'Bearer'
-
-
-def test_logout(client: TestClient, token_headers: dict[str, str]) -> None:
- response = client.post(f'{settings.API_V1_STR}/auth/logout', headers=token_headers)
- assert response.status_code == 200
- assert response.json()['code'] == 200
diff --git a/backend/app/tests/utils/db_mysql.py b/backend/app/tests/utils/db_mysql.py
deleted file mode 100644
index 90720ff..0000000
--- a/backend/app/tests/utils/db_mysql.py
+++ /dev/null
@@ -1,27 +0,0 @@
-#!/usr/bin/env python3
-# -*- coding: utf-8 -*-
-from sqlalchemy.ext.asyncio import AsyncSession
-
-from backend.app.core.conf import settings
-from backend.app.database.db_mysql import create_engine_and_session
-
-TEST_DB_DATABASE = settings.DB_DATABASE + '_test'
-
-TEST_SQLALCHEMY_DATABASE_URL = (
- f'mysql+asyncmy://{settings.DB_USER}:{settings.DB_PASSWORD}@{settings.DB_HOST}:'
- f'{settings.DB_PORT}/{TEST_DB_DATABASE}?charset={settings.DB_CHARSET}'
-)
-
-test_async_engine, test_async_db_session = create_engine_and_session(TEST_SQLALCHEMY_DATABASE_URL)
-
-
-async def override_get_db() -> AsyncSession:
- """session 生成器"""
- session = test_async_db_session()
- try:
- yield session
- except Exception as se:
- await session.rollback()
- raise se
- finally:
- await session.close()
diff --git a/backend.dockerfile b/backend/backend.dockerfile
similarity index 73%
rename from backend.dockerfile
rename to backend/backend.dockerfile
index c5777de..f68d3cc 100644
--- a/backend.dockerfile
+++ b/backend/backend.dockerfile
@@ -13,14 +13,14 @@ RUN apt-get update \
# 某些包可能存在同步不及时导致安装失败的情况,可更改为官方源:https://pypi.org/simple
RUN pip install --upgrade pip -i https://mirrors.aliyun.com/pypi/simple \
- && pip install --no-cache-dir -r requirements.txt -i https://mirrors.aliyun.com/pypi/simple
+ && pip install -r backend/requirements.txt -i https://mirrors.aliyun.com/pypi/simple
ENV TZ = Asia/Shanghai
RUN mkdir -p /var/log/fastapi_server
-COPY ./deploy/fastapi_server.conf /etc/supervisor/conf.d/
+COPY deploy/backend/fastapi_server.conf /etc/supervisor/conf.d/
EXPOSE 8001
-CMD ["uvicorn", "backend.app.main:app", "--host", "127.0.0.1", "--port", "8000"]
+CMD ["uvicorn", "backend.main:app", "--host", "127.0.0.1", "--port", "8000"]
diff --git a/backend/celery-start.sh b/backend/celery-start.sh
new file mode 100644
index 0000000..e2a021a
--- /dev/null
+++ b/backend/celery-start.sh
@@ -0,0 +1,7 @@
+#!/usr/bin/env bash
+
+# work && beat
+celery -A app.task.celery worker -l info -B
+
+# flower
+celery -A app.task.celery flower --port=8555 --basic-auth=admin:123456
diff --git a/celery.dockerfile b/backend/celery.dockerfile
similarity index 76%
rename from celery.dockerfile
rename to backend/celery.dockerfile
index 33fc188..b8dff8f 100644
--- a/celery.dockerfile
+++ b/backend/celery.dockerfile
@@ -12,16 +12,18 @@ RUN apt-get update \
&& rm -rf /var/lib/apt/lists/*
RUN pip install --upgrade pip -i https://mirrors.aliyun.com/pypi/simple \
- && pip install --no-cache-dir -r requirements.txt -i https://mirrors.aliyun.com/pypi/simple
+ && pip install -r backend/requirements.txt -i https://mirrors.aliyun.com/pypi/simple
ENV TZ = Asia/Shanghai
RUN mkdir -p /var/log/celery
-COPY ./deploy/celery.conf /etc/supervisor/conf.d/
+COPY deploy/backend/celery.conf /etc/supervisor/conf.d/
-WORKDIR /fba/backend/app
+WORKDIR /fba/backend/
RUN chmod +x celery-start.sh
+EXPOSE 8555
+
CMD ["./celery-start.sh"]
diff --git a/backend/common/__init__.py b/backend/common/__init__.py
new file mode 100644
index 0000000..56fafa5
--- /dev/null
+++ b/backend/common/__init__.py
@@ -0,0 +1,2 @@
+#!/usr/bin/env python3
+# -*- coding: utf-8 -*-
diff --git a/backend/app/common/enums.py b/backend/common/enums.py
similarity index 100%
rename from backend/app/common/enums.py
rename to backend/common/enums.py
diff --git a/backend/common/exception/__init__.py b/backend/common/exception/__init__.py
new file mode 100644
index 0000000..56fafa5
--- /dev/null
+++ b/backend/common/exception/__init__.py
@@ -0,0 +1,2 @@
+#!/usr/bin/env python3
+# -*- coding: utf-8 -*-
diff --git a/backend/app/common/exception/errors.py b/backend/common/exception/errors.py
similarity index 97%
rename from backend/app/common/exception/errors.py
rename to backend/common/exception/errors.py
index c13f731..c622d60 100644
--- a/backend/app/common/exception/errors.py
+++ b/backend/common/exception/errors.py
@@ -6,12 +6,13 @@
业务代码执行异常时,可以使用 raise xxxError 触发内部错误,它尽可能实现带有后台任务的异常,但它不适用于**自定义响应状态码**
如果要求使用**自定义响应状态码**,则可以通过 return await response_base.fail(res=CustomResponseCode.xxx) 直接返回
""" # noqa: E501
+
from typing import Any
from fastapi import HTTPException
from starlette.background import BackgroundTask
-from backend.app.common.response.response_code import CustomErrorCode, StandardResponseCode
+from backend.common.response.response_code import CustomErrorCode, StandardResponseCode
class BaseExceptionMixin(Exception):
diff --git a/backend/app/common/exception/exception_handler.py b/backend/common/exception/exception_handler.py
similarity index 95%
rename from backend/app/common/exception/exception_handler.py
rename to backend/common/exception/exception_handler.py
index bcc0ac2..75f869a 100644
--- a/backend/app/common/exception/exception_handler.py
+++ b/backend/common/exception/exception_handler.py
@@ -9,16 +9,16 @@ from starlette.exceptions import HTTPException
from starlette.middleware.cors import CORSMiddleware
from uvicorn.protocols.http.h11_impl import STATUS_PHRASES
-from backend.app.common.exception.errors import BaseExceptionMixin
-from backend.app.common.log import log
-from backend.app.common.response.response_code import CustomResponseCode, StandardResponseCode
-from backend.app.common.response.response_schema import response_base
-from backend.app.core.conf import settings
-from backend.app.schemas.base import (
+from backend.common.exception.errors import BaseExceptionMixin
+from backend.common.log import log
+from backend.common.msd.schema import (
CUSTOM_USAGE_ERROR_MESSAGES,
CUSTOM_VALIDATION_ERROR_MESSAGES,
)
-from backend.app.utils.serializers import MsgSpecJSONResponse
+from backend.common.response.response_code import CustomResponseCode, StandardResponseCode
+from backend.common.response.response_schema import response_base
+from backend.core.conf import settings
+from backend.utils.serializers import MsgSpecJSONResponse
@sync_to_async
@@ -36,7 +36,7 @@ def _get_exception_code(status_code: int):
"""
try:
STATUS_PHRASES[status_code]
- except Exception: # noqa: ignore
+ except Exception:
code = StandardResponseCode.HTTP_400
else:
code = status_code
diff --git a/backend/app/common/log.py b/backend/common/log.py
similarity index 91%
rename from backend/app/common/log.py
rename to backend/common/log.py
index 10c3d6d..49a0813 100644
--- a/backend/app/common/log.py
+++ b/backend/common/log.py
@@ -8,8 +8,8 @@ from typing import TYPE_CHECKING
from loguru import logger
-from backend.app.core import path_conf
-from backend.app.core.conf import settings
+from backend.core import path_conf
+from backend.core.conf import settings
if TYPE_CHECKING:
import loguru
@@ -17,7 +17,7 @@ if TYPE_CHECKING:
class Logger:
def __init__(self):
- self.log_path = path_conf.LogPath
+ self.log_path = path_conf.LOG_DIR
def log(self) -> loguru.Logger:
if not os.path.exists(self.log_path):
diff --git a/backend/common/msd/__init__.py b/backend/common/msd/__init__.py
new file mode 100644
index 0000000..56fafa5
--- /dev/null
+++ b/backend/common/msd/__init__.py
@@ -0,0 +1,2 @@
+#!/usr/bin/env python3
+# -*- coding: utf-8 -*-
diff --git a/backend/app/crud/base.py b/backend/common/msd/crud.py
similarity index 98%
rename from backend/app/crud/base.py
rename to backend/common/msd/crud.py
index 05f7288..8135080 100644
--- a/backend/app/crud/base.py
+++ b/backend/common/msd/crud.py
@@ -6,7 +6,7 @@ from pydantic import BaseModel
from sqlalchemy import and_, delete, select, update
from sqlalchemy.ext.asyncio import AsyncSession
-from backend.app.models.base import MappedBase
+from backend.common.msd.model import MappedBase
ModelType = TypeVar('ModelType', bound=MappedBase)
CreateSchemaType = TypeVar('CreateSchemaType', bound=BaseModel)
diff --git a/backend/app/models/base.py b/backend/common/msd/model.py
similarity index 98%
rename from backend/app/models/base.py
rename to backend/common/msd/model.py
index 500b130..4822fa9 100644
--- a/backend/app/models/base.py
+++ b/backend/common/msd/model.py
@@ -5,7 +5,7 @@ from typing import Annotated
from sqlalchemy.orm import DeclarativeBase, Mapped, MappedAsDataclass, declared_attr, mapped_column
-from backend.app.utils.timezone import timezone
+from backend.utils.timezone import timezone
# 通用 Mapped 类型主键, 需手动添加,参考以下使用方式
# MappedBase -> id: Mapped[id_key]
diff --git a/backend/app/schemas/base.py b/backend/common/msd/schema.py
similarity index 99%
rename from backend/app/schemas/base.py
rename to backend/common/msd/schema.py
index 00b5442..464f959 100644
--- a/backend/app/schemas/base.py
+++ b/backend/common/msd/schema.py
@@ -1,6 +1,5 @@
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
-
from pydantic import BaseModel, ConfigDict, EmailStr, validate_email
from pydantic_extra_types.phone_numbers import PhoneNumber
diff --git a/backend/app/common/pagination.py b/backend/common/pagination.py
similarity index 82%
rename from backend/app/common/pagination.py
rename to backend/common/pagination.py
index 11969b9..6e7f0a7 100644
--- a/backend/app/common/pagination.py
+++ b/backend/common/pagination.py
@@ -53,14 +53,12 @@ class _Page(AbstractPage[T], Generic[T]):
page = params.page
size = params.size
total_pages = math.ceil(total / params.size)
- links = create_links(
- **{
- 'first': {'page': 1, 'size': f'{size}'},
- 'last': {'page': f'{math.ceil(total / params.size)}', 'size': f'{size}'} if total > 0 else None,
- 'next': {'page': f'{page + 1}', 'size': f'{size}'} if (page + 1) <= total_pages else None,
- 'prev': {'page': f'{page - 1}', 'size': f'{size}'} if (page - 1) >= 1 else None,
- }
- ).model_dump()
+ links = create_links(**{
+ 'first': {'page': 1, 'size': f'{size}'},
+ 'last': {'page': f'{math.ceil(total / params.size)}', 'size': f'{size}'} if total > 0 else None,
+ 'next': {'page': f'{page + 1}', 'size': f'{size}'} if (page + 1) <= total_pages else None,
+ 'prev': {'page': f'{page - 1}', 'size': f'{size}'} if (page - 1) >= 1 else None,
+ }).model_dump()
return cls(items=items, total=total, page=params.page, size=params.size, total_pages=total_pages, links=links)
diff --git a/backend/common/response/__init__.py b/backend/common/response/__init__.py
new file mode 100644
index 0000000..56fafa5
--- /dev/null
+++ b/backend/common/response/__init__.py
@@ -0,0 +1,2 @@
+#!/usr/bin/env python3
+# -*- coding: utf-8 -*-
diff --git a/backend/app/common/response/response_code.py b/backend/common/response/response_code.py
similarity index 100%
rename from backend/app/common/response/response_code.py
rename to backend/common/response/response_code.py
diff --git a/backend/app/common/response/response_schema.py b/backend/common/response/response_schema.py
similarity index 94%
rename from backend/app/common/response/response_schema.py
rename to backend/common/response/response_schema.py
index 2fd6cdb..1cdf7b3 100644
--- a/backend/app/common/response/response_schema.py
+++ b/backend/common/response/response_schema.py
@@ -5,8 +5,8 @@ from typing import Any
from pydantic import BaseModel, ConfigDict
-from backend.app.common.response.response_code import CustomResponse, CustomResponseCode
-from backend.app.core.conf import settings
+from backend.common.response.response_code import CustomResponse, CustomResponseCode
+from backend.core.conf import settings
_ExcludeData = set[int | str] | dict[int | str, Any]
@@ -23,10 +23,12 @@ class ResponseModel(BaseModel):
def test():
return ResponseModel(data={'test': 'test'})
+
@router.get('/test')
def test() -> ResponseModel:
return ResponseModel(data={'test': 'test'})
+
@router.get('/test')
def test() -> ResponseModel:
res = CustomResponseCode.HTTP_200
diff --git a/backend/common/security/__init__.py b/backend/common/security/__init__.py
new file mode 100644
index 0000000..56fafa5
--- /dev/null
+++ b/backend/common/security/__init__.py
@@ -0,0 +1,2 @@
+#!/usr/bin/env python3
+# -*- coding: utf-8 -*-
diff --git a/backend/app/common/jwt.py b/backend/common/security/jwt.py
similarity index 95%
rename from backend/app/common/jwt.py
rename to backend/common/security/jwt.py
index 77bad87..dfb224e 100644
--- a/backend/app/common/jwt.py
+++ b/backend/common/security/jwt.py
@@ -10,12 +10,11 @@ from jose import jwt
from passlib.context import CryptContext
from sqlalchemy.ext.asyncio import AsyncSession
-from backend.app.common.exception.errors import AuthorizationError, TokenError
-from backend.app.common.redis import redis_client
-from backend.app.core.conf import settings
-from backend.app.crud.crud_user import user_dao
-from backend.app.models import User
-from backend.app.utils.timezone import timezone
+from backend.app.admin.model import User
+from backend.common.exception.errors import AuthorizationError, TokenError
+from backend.core.conf import settings
+from backend.database.db_redis import redis_client
+from backend.utils.timezone import timezone
pwd_context = CryptContext(schemes=['bcrypt'], deprecated='auto')
@@ -180,6 +179,8 @@ async def get_current_user(db: AsyncSession, data: dict) -> User:
:return:
"""
user_id = data.get('sub')
+ from backend.app.admin.crud.crud_user import user_dao
+
user = await user_dao.get_with_relation(db, user_id=user_id)
if not user:
raise TokenError(msg='Token 无效')
diff --git a/backend/app/common/permission.py b/backend/common/security/permission.py
similarity index 87%
rename from backend/app/common/permission.py
rename to backend/common/security/permission.py
index a6e1d81..3db8b55 100644
--- a/backend/app/common/permission.py
+++ b/backend/common/security/permission.py
@@ -2,8 +2,8 @@
# -*- coding: utf-8 -*-
from fastapi import Request
-from backend.app.common.exception.errors import ServerError
-from backend.app.core.conf import settings
+from backend.common.exception.errors import ServerError
+from backend.core.conf import settings
class RequestPermission:
diff --git a/backend/app/common/rbac.py b/backend/common/security/rbac.py
similarity index 78%
rename from backend/app/common/rbac.py
rename to backend/common/security/rbac.py
index 7373fae..fd41044 100644
--- a/backend/app/common/rbac.py
+++ b/backend/common/security/rbac.py
@@ -5,13 +5,13 @@ import casbin_async_sqlalchemy_adapter
from fastapi import Depends, Request
-from backend.app.common.enums import MethodType, StatusType
-from backend.app.common.exception.errors import AuthorizationError, TokenError
-from backend.app.common.jwt import DependsJwtAuth
-from backend.app.common.redis import redis_client
-from backend.app.core.conf import settings
-from backend.app.database.db_mysql import async_engine
-from backend.app.models import CasbinRule
+from backend.app.admin.model import CasbinRule
+from backend.common.enums import MethodType, StatusType
+from backend.common.exception.errors import AuthorizationError, TokenError
+from backend.common.security.jwt import DependsJwtAuth
+from backend.core.conf import settings
+from backend.database.db_mysql import async_engine
+from backend.database.db_redis import redis_client
class RBAC:
@@ -89,15 +89,11 @@ class RBAC:
user_menu_perms = []
user_forbid_menu_perms = []
for role in user_roles:
- user_menus = role.menus
- if user_menus:
- for menu in user_menus:
- perms = menu.perms
- if perms:
- if menu.status == StatusType.enable:
- user_menu_perms.extend(perms.split(','))
- else:
- user_forbid_menu_perms.extend(perms.split(','))
+ for menu in role.menus:
+ if menu.status == StatusType.enable:
+ user_menu_perms.extend(menu.perms.split(','))
+ else:
+ user_forbid_menu_perms.extend(menu.perms.split(','))
await redis_client.set(
f'{settings.PERMISSION_REDIS_PREFIX}:{user_uuid}:enable', ','.join(user_menu_perms)
)
@@ -116,13 +112,9 @@ class RBAC:
if not user_forbid_menu_perms:
user_forbid_menu_perms = []
for role in user_roles:
- user_menus = role.menus
- if user_menus:
- for menu in user_menus:
- perms = menu.perms
- if perms:
- if menu.status == StatusType.disable:
- user_forbid_menu_perms.extend(perms.split(','))
+ for menu in role.menus:
+ if menu.status == StatusType.disable:
+ user_forbid_menu_perms.extend(menu.perms.split(','))
await redis_client.set(
f'{settings.PERMISSION_REDIS_PREFIX}:{user_uuid}:disable', ','.join(user_forbid_menu_perms)
)
diff --git a/backend/core/__init__.py b/backend/core/__init__.py
new file mode 100644
index 0000000..56fafa5
--- /dev/null
+++ b/backend/core/__init__.py
@@ -0,0 +1,2 @@
+#!/usr/bin/env python3
+# -*- coding: utf-8 -*-
diff --git a/backend/app/core/conf.py b/backend/core/conf.py
similarity index 63%
rename from backend/app/core/conf.py
rename to backend/core/conf.py
index a3a66f3..354a42a 100644
--- a/backend/app/core/conf.py
+++ b/backend/core/conf.py
@@ -6,18 +6,22 @@ from typing import Literal
from pydantic import model_validator
from pydantic_settings import BaseSettings, SettingsConfigDict
+from backend.core.path_conf import BasePath
+
class Settings(BaseSettings):
- model_config = SettingsConfigDict(env_file='.env', env_file_encoding='utf-8')
+ """Global Settings"""
+
+ model_config = SettingsConfigDict(env_file=f'{BasePath}/.env', env_file_encoding='utf-8', extra='ignore')
# Env Config
ENVIRONMENT: Literal['dev', 'pro']
# Env MySQL
- DB_HOST: str
- DB_PORT: int
- DB_USER: str
- DB_PASSWORD: str
+ MYSQL_HOST: str
+ MYSQL_PORT: int
+ MYSQL_USER: str
+ MYSQL_PASSWORD: str
# Env Redis
REDIS_HOST: str
@@ -25,30 +29,12 @@ class Settings(BaseSettings):
REDIS_PASSWORD: str
REDIS_DATABASE: int
- # Env Celery
- CELERY_REDIS_HOST: str
- CELERY_REDIS_PORT: int
- CELERY_REDIS_PASSWORD: str
- CELERY_BROKER_REDIS_DATABASE: int # 仅当使用 redis 作为 broker 时生效, 更适用于测试环境
- CELERY_BACKEND_REDIS_DATABASE: int
-
- # Env Rabbitmq
- # docker run -d --hostname fba-mq --name fba-mq -p 5672:5672 -p 15672:15672 rabbitmq:latest
- RABBITMQ_HOST: str
- RABBITMQ_PORT: int
- RABBITMQ_USERNAME: str
- RABBITMQ_PASSWORD: str
-
# Env Token
TOKEN_SECRET_KEY: str # 密钥 secrets.token_urlsafe(32)
# Env Opera Log
OPERA_LOG_ENCRYPT_SECRET_KEY: str # 密钥 os.urandom(32), 需使用 bytes.hex() 方法转换为 str
- # OAuth2:https://github.com/fastapi-practices/fastapi_oauth20
- OAUTH2_GITHUB_CLIENT_ID: str
- OAUTH2_GITHUB_CLIENT_SECRET: str
-
# FastAPI
API_V1_STR: str = '/api/v1'
TITLE: str = 'FastAPI'
@@ -74,9 +60,6 @@ class Settings(BaseSettings):
('GET', f'{API_V1_STR}/auth/captcha'),
}
- # OAuth2
- OAUTH2_GITHUB_REDIRECT_URI: str = 'http://127.0.0.1:8000/api/v1/auth/github/callback'
-
# Uvicorn
UVICORN_HOST: str = '127.0.0.1'
UVICORN_PORT: int = 8000
@@ -96,9 +79,9 @@ class Settings(BaseSettings):
DATETIME_FORMAT: str = '%Y-%m-%d %H:%M:%S'
# MySQL
- DB_ECHO: bool = False
- DB_DATABASE: str = 'fba'
- DB_CHARSET: str = 'utf8mb4'
+ MYSQL_ECHO: bool = False
+ MYSQL_DATABASE: str = 'fba'
+ MYSQL_CHARSET: str = 'utf8mb4'
# Redis
REDIS_TIMEOUT: int = 5
@@ -107,17 +90,12 @@ class Settings(BaseSettings):
TOKEN_ALGORITHM: str = 'HS256' # 算法
TOKEN_EXPIRE_SECONDS: int = 60 * 60 * 24 * 1 # 过期时间,单位:秒
TOKEN_REFRESH_EXPIRE_SECONDS: int = 60 * 60 * 24 * 7 # 刷新过期时间,单位:秒
- TOKEN_URL_SWAGGER: str = f'{API_V1_STR}/auth/swagger_login'
TOKEN_REDIS_PREFIX: str = 'fba_token'
TOKEN_REFRESH_REDIS_PREFIX: str = 'fba_refresh_token'
TOKEN_EXCLUDE: list[str] = [ # JWT / RBAC 白名单
f'{API_V1_STR}/auth/login',
]
- # Captcha
- CAPTCHA_LOGIN_REDIS_PREFIX: str = 'fba_login_captcha'
- CAPTCHA_LOGIN_EXPIRE_SECONDS: int = 60 * 5 # 过期时间,单位:秒
-
# Log
LOG_STDOUT_FILENAME: str = 'fba_access.log'
LOG_STDERR_FILENAME: str = 'fba_error.log'
@@ -133,11 +111,8 @@ class Settings(BaseSettings):
# Casbin Auth
CASBIN_EXCLUDE: set[tuple[str, str]] = {
- ('POST', f'{API_V1_STR}/auth/swagger_login'),
- ('POST', f'{API_V1_STR}/auth/login'),
('POST', f'{API_V1_STR}/auth/logout'),
- ('POST', f'{API_V1_STR}/auth/register'),
- ('GET', f'{API_V1_STR}/auth/captcha'),
+ ('POST', f'{API_V1_STR}/auth/token/new'),
}
# Role Menu Auth
@@ -152,7 +127,8 @@ class Settings(BaseSettings):
DOCS_URL,
REDOCS_URL,
OPENAPI_URL,
- f'{API_V1_STR}/auth/swagger_login',
+ f'{API_V1_STR}/auth/login/swagger',
+ f'{API_V1_STR}/auth/github/callback',
]
OPERA_LOG_ENCRYPT: int = 1 # 0: AES (性能损耗); 1: md5; 2: ItsDangerous; 3: 不加密, others: 替换为 ******
OPERA_LOG_ENCRYPT_INCLUDE: list[str] = [
@@ -166,30 +142,12 @@ class Settings(BaseSettings):
IP_LOCATION_REDIS_PREFIX: str = 'fba_ip_location'
IP_LOCATION_EXPIRE_SECONDS: int = 60 * 60 * 24 * 1 # 过期时间,单位:秒
- # Celery
- CELERY_BROKER: Literal['rabbitmq', 'redis'] = 'redis'
- CELERY_BACKEND_REDIS_PREFIX: str = 'fba_celery'
- CELERY_BACKEND_REDIS_TIMEOUT: float = 5.0
- CELERY_BACKEND_REDIS_ORDERED: bool = True
- CELERY_BEAT_SCHEDULE_FILENAME: str = './log/celery_beat-schedule'
- CELERY_BEAT_SCHEDULE: dict = {
- 'task_demo_async': {
- 'task': 'tasks.task_demo_async',
- 'schedule': 5.0,
- },
- }
-
- @model_validator(mode='before')
- def validate_celery_broker(cls, values):
- if values['ENVIRONMENT'] == 'pro':
- values['CELERY_BROKER'] = 'rabbitmq'
- return values
-
@lru_cache
-def get_settings():
- """读取配置优化"""
+def get_settings() -> Settings:
+ """获取全局配置"""
return Settings()
+# 创建配置实例
settings = get_settings()
diff --git a/backend/core/path_conf.py b/backend/core/path_conf.py
new file mode 100644
index 0000000..0514dbb
--- /dev/null
+++ b/backend/core/path_conf.py
@@ -0,0 +1,21 @@
+#!/usr/bin/env python3
+# -*- coding: utf-8 -*-
+import os
+
+from pathlib import Path
+
+# 获取项目根目录
+# 或使用绝对路径,指到backend目录为止,例如windows:BasePath = D:\git_project\fastapi_mysql\backend
+BasePath = Path(__file__).resolve().parent.parent
+
+# alembic 迁移文件存放路径
+ALEMBIC_Versions_DIR = os.path.join(BasePath, 'alembic', 'versions')
+
+# 日志文件路径
+LOG_DIR = os.path.join(BasePath, 'log')
+
+# 离线 IP 数据库路径
+IP2REGION_XDB = os.path.join(BasePath, 'static', 'ip2region.xdb')
+
+# 挂载静态目录
+STATIC_DIR = os.path.join(BasePath, 'static')
diff --git a/backend/app/core/registrar.py b/backend/core/registrar.py
similarity index 75%
rename from backend/app/core/registrar.py
rename to backend/core/registrar.py
index 39365ef..34f8dcd 100644
--- a/backend/app/core/registrar.py
+++ b/backend/core/registrar.py
@@ -7,17 +7,18 @@ from fastapi_limiter import FastAPILimiter
from fastapi_pagination import add_pagination
from starlette.middleware.authentication import AuthenticationMiddleware
-from backend.app.api.routers import v1
-from backend.app.common.exception.exception_handler import register_exception
-from backend.app.common.redis import redis_client
-from backend.app.core.conf import settings
-from backend.app.database.db_mysql import create_table
-from backend.app.middleware.jwt_auth_middleware import JwtAuthMiddleware
-from backend.app.middleware.opera_log_middleware import OperaLogMiddleware
-from backend.app.utils.demo_site import demo_site
-from backend.app.utils.health_check import ensure_unique_route_names, http_limit_callback
-from backend.app.utils.openapi import simplify_operation_ids
-from backend.app.utils.serializers import MsgSpecJSONResponse
+from backend.app.router import route
+from backend.common.exception.exception_handler import register_exception
+from backend.core.conf import settings
+from backend.core.path_conf import STATIC_DIR
+from backend.database.db_mysql import create_table
+from backend.database.db_redis import redis_client
+from backend.middleware.jwt_auth_middleware import JwtAuthMiddleware
+from backend.middleware.opera_log_middleware import OperaLogMiddleware
+from backend.utils.demo_site import demo_site
+from backend.utils.health_check import ensure_unique_route_names, http_limit_callback
+from backend.utils.openapi import simplify_operation_ids
+from backend.utils.serializers import MsgSpecJSONResponse
@asynccontextmanager
@@ -85,9 +86,9 @@ def register_static_file(app: FastAPI):
from fastapi.staticfiles import StaticFiles
- if not os.path.exists('./static'):
- os.mkdir('./static')
- app.mount('/static', StaticFiles(directory='static'), name='static')
+ if not os.path.exists(STATIC_DIR):
+ os.mkdir(STATIC_DIR)
+ app.mount('/static', StaticFiles(directory=STATIC_DIR), name='static')
def register_middleware(app: FastAPI):
@@ -110,7 +111,7 @@ def register_middleware(app: FastAPI):
)
# Access log
if settings.MIDDLEWARE_ACCESS:
- from backend.app.middleware.access_middleware import AccessMiddleware
+ from backend.middleware.access_middleware import AccessMiddleware
app.add_middleware(AccessMiddleware)
# CORS: Always at the end
@@ -136,7 +137,7 @@ def register_router(app: FastAPI):
dependencies = [Depends(demo_site)] if settings.DEMO_MODE else None
# API
- app.include_router(v1, dependencies=dependencies)
+ app.include_router(route, dependencies=dependencies)
# Extra
ensure_unique_route_names(app)
diff --git a/backend/database/__init__.py b/backend/database/__init__.py
new file mode 100644
index 0000000..56fafa5
--- /dev/null
+++ b/backend/database/__init__.py
@@ -0,0 +1,2 @@
+#!/usr/bin/env python3
+# -*- coding: utf-8 -*-
diff --git a/backend/app/database/db_mysql.py b/backend/database/db_mysql.py
similarity index 76%
rename from backend/app/database/db_mysql.py
rename to backend/database/db_mysql.py
index f895234..9a7a9cb 100644
--- a/backend/app/database/db_mysql.py
+++ b/backend/database/db_mysql.py
@@ -9,15 +9,15 @@ from fastapi import Depends
from sqlalchemy import URL
from sqlalchemy.ext.asyncio import AsyncSession, async_sessionmaker, create_async_engine
-from backend.app.common.log import log
-from backend.app.core.conf import settings
-from backend.app.models.base import MappedBase
+from backend.common.log import log
+from backend.common.msd.model import MappedBase
+from backend.core.conf import settings
def create_engine_and_session(url: str | URL):
try:
# 数据库引擎
- engine = create_async_engine(url, echo=settings.DB_ECHO, future=True, pool_pre_ping=True)
+ engine = create_async_engine(url, echo=settings.MYSQL_ECHO, future=True, pool_pre_ping=True)
# log.success('数据库连接成功')
except Exception as e:
log.error('❌ 数据库链接失败 {}', e)
@@ -28,8 +28,8 @@ def create_engine_and_session(url: str | URL):
SQLALCHEMY_DATABASE_URL = (
- f'mysql+asyncmy://{settings.DB_USER}:{settings.DB_PASSWORD}@{settings.DB_HOST}:'
- f'{settings.DB_PORT}/{settings.DB_DATABASE}?charset={settings.DB_CHARSET}'
+ f'mysql+asyncmy://{settings.MYSQL_USER}:{settings.MYSQL_PASSWORD}@{settings.MYSQL_HOST}:'
+ f'{settings.MYSQL_PORT}/{settings.MYSQL_DATABASE}?charset={settings.MYSQL_CHARSET}'
)
async_engine, async_db_session = create_engine_and_session(SQLALCHEMY_DATABASE_URL)
diff --git a/backend/app/common/redis.py b/backend/database/db_redis.py
similarity index 93%
rename from backend/app/common/redis.py
rename to backend/database/db_redis.py
index 8d3d8ea..ac134ae 100644
--- a/backend/app/common/redis.py
+++ b/backend/database/db_redis.py
@@ -5,8 +5,8 @@ import sys
from redis.asyncio.client import Redis
from redis.exceptions import AuthenticationError, TimeoutError
-from backend.app.common.log import log
-from backend.app.core.conf import settings
+from backend.common.log import log
+from backend.core.conf import settings
class RedisCli(Redis):
@@ -60,5 +60,5 @@ class RedisCli(Redis):
await self.delete(key)
-# 创建redis连接对象
+# 创建 redis 客户端实例
redis_client = RedisCli()
diff --git a/backend/app/main.py b/backend/main.py
similarity index 88%
rename from backend/app/main.py
rename to backend/main.py
index 2b6a5db..40162e2 100644
--- a/backend/app/main.py
+++ b/backend/main.py
@@ -4,9 +4,9 @@ import uvicorn
from path import Path
-from backend.app.common.log import log
-from backend.app.core.conf import settings
-from backend.app.core.registrar import register_app
+from backend.common.log import log
+from backend.core.conf import settings
+from backend.core.registrar import register_app
app = register_app()
diff --git a/backend/middleware/__init__.py b/backend/middleware/__init__.py
new file mode 100644
index 0000000..56fafa5
--- /dev/null
+++ b/backend/middleware/__init__.py
@@ -0,0 +1,2 @@
+#!/usr/bin/env python3
+# -*- coding: utf-8 -*-
diff --git a/backend/app/middleware/access_middleware.py b/backend/middleware/access_middleware.py
similarity index 87%
rename from backend/app/middleware/access_middleware.py
rename to backend/middleware/access_middleware.py
index f671a01..eb18fc4 100644
--- a/backend/app/middleware/access_middleware.py
+++ b/backend/middleware/access_middleware.py
@@ -3,8 +3,8 @@
from fastapi import Request, Response
from starlette.middleware.base import BaseHTTPMiddleware, RequestResponseEndpoint
-from backend.app.common.log import log
-from backend.app.utils.timezone import timezone
+from backend.common.log import log
+from backend.utils.timezone import timezone
class AccessMiddleware(BaseHTTPMiddleware):
diff --git a/backend/app/middleware/jwt_auth_middleware.py b/backend/middleware/jwt_auth_middleware.py
similarity index 86%
rename from backend/app/middleware/jwt_auth_middleware.py
rename to backend/middleware/jwt_auth_middleware.py
index 84c26d8..849452f 100644
--- a/backend/app/middleware/jwt_auth_middleware.py
+++ b/backend/middleware/jwt_auth_middleware.py
@@ -6,12 +6,12 @@ from fastapi import Request, Response
from starlette.authentication import AuthCredentials, AuthenticationBackend, AuthenticationError
from starlette.requests import HTTPConnection
-from backend.app.common import jwt
-from backend.app.common.exception.errors import TokenError
-from backend.app.common.log import log
-from backend.app.core.conf import settings
-from backend.app.database.db_mysql import async_db_session
-from backend.app.utils.serializers import MsgSpecJSONResponse
+from backend.common.exception.errors import TokenError
+from backend.common.log import log
+from backend.common.security import jwt
+from backend.core.conf import settings
+from backend.database.db_mysql import async_db_session
+from backend.utils.serializers import MsgSpecJSONResponse
class _AuthenticationError(AuthenticationError):
diff --git a/backend/app/middleware/opera_log_middleware.py b/backend/middleware/opera_log_middleware.py
similarity index 93%
rename from backend/app/middleware/opera_log_middleware.py
rename to backend/middleware/opera_log_middleware.py
index 8635ea6..dee4698 100644
--- a/backend/app/middleware/opera_log_middleware.py
+++ b/backend/middleware/opera_log_middleware.py
@@ -7,14 +7,14 @@ from starlette.datastructures import UploadFile
from starlette.middleware.base import BaseHTTPMiddleware
from starlette.requests import Request
-from backend.app.common.enums import OperaLogCipherType
-from backend.app.common.log import log
-from backend.app.core.conf import settings
-from backend.app.schemas.opera_log import CreateOperaLogParam
-from backend.app.services.opera_log_service import OperaLogService
-from backend.app.utils.encrypt import AESCipher, ItsDCipher, Md5Cipher
-from backend.app.utils.request_parse import parse_ip_info, parse_user_agent_info
-from backend.app.utils.timezone import timezone
+from backend.app.admin.schema.opera_log import CreateOperaLogParam
+from backend.app.admin.service.opera_log_service import OperaLogService
+from backend.common.enums import OperaLogCipherType
+from backend.common.log import log
+from backend.core.conf import settings
+from backend.utils.encrypt import AESCipher, ItsDCipher, Md5Cipher
+from backend.utils.request_parse import parse_ip_info, parse_user_agent_info
+from backend.utils.timezone import timezone
class OperaLogMiddleware(BaseHTTPMiddleware):
diff --git a/pdm.lock b/backend/pdm.lock
similarity index 89%
rename from pdm.lock
rename to backend/pdm.lock
index 01104fa..59e1722 100644
--- a/pdm.lock
+++ b/backend/pdm.lock
@@ -2,10 +2,10 @@
# It is not intended for manual editing.
[metadata]
-groups = ["default", "deploy"]
+groups = ["default", "lint", "deploy"]
strategy = ["cross_platform", "inherit_metadata"]
lock_version = "4.4.1"
-content_hash = "sha256:06e039c331218934838e6c6865b0c78b8cb14142c3f90f23f7d36844f9a20a09"
+content_hash = "sha256:1e8b094727ea68450c56c0182cfb4ca42ca2347cea93fffd883feac54a5fe44f"
[[package]]
name = "aiofiles"
@@ -72,7 +72,7 @@ files = [
[[package]]
name = "anyio"
-version = "4.2.0"
+version = "4.3.0"
requires_python = ">=3.8"
summary = "High level compatibility layer for multiple asynchronous event loop implementations"
groups = ["default"]
@@ -83,8 +83,8 @@ dependencies = [
"typing-extensions>=4.1; python_version < \"3.11\"",
]
files = [
- {file = "anyio-4.2.0-py3-none-any.whl", hash = "sha256:745843b39e829e108e518c489b31dc757de7d2131d53fac32bd8df268227bfee"},
- {file = "anyio-4.2.0.tar.gz", hash = "sha256:e1875bb4b4e2de1669f4bc7869b6d3f54231cdced71605e6e64c9be77e3be50f"},
+ {file = "anyio-4.3.0-py3-none-any.whl", hash = "sha256:048e05d0f6caeed70d731f3db756d35dcc1f35747c8c403364a8332c630441b8"},
+ {file = "anyio-4.3.0.tar.gz", hash = "sha256:f75253795a87df48568485fd18cdd2a3fa5c4f7c5be8e5e36637733fce06fed6"},
]
[[package]]
@@ -258,13 +258,13 @@ files = [
[[package]]
name = "certifi"
-version = "2023.11.17"
+version = "2024.2.2"
requires_python = ">=3.6"
summary = "Python package for providing Mozilla's CA Bundle."
groups = ["default"]
files = [
- {file = "certifi-2023.11.17-py3-none-any.whl", hash = "sha256:e036ab49d5b79556f99cfc2d9320b34cfbe5be05c5871b51de9329f0603b0474"},
- {file = "certifi-2023.11.17.tar.gz", hash = "sha256:9b469f3a900bf28dc19b8cfbf8019bf47f7fdd1a65a1d4ffb98fc14166beb4d1"},
+ {file = "certifi-2024.2.2-py3-none-any.whl", hash = "sha256:dc383c07b76109f368f6106eee2b593b04a011ea4d55f652c6ca24a754d1cdd1"},
+ {file = "certifi-2024.2.2.tar.gz", hash = "sha256:0569859f95fc761b18b45ef421b1290a0f65f147e92a1e5eb3e635f9a5e4e66f"},
]
[[package]]
@@ -438,13 +438,13 @@ files = [
[[package]]
name = "dnspython"
-version = "2.5.0"
+version = "2.6.1"
requires_python = ">=3.8"
summary = "DNS toolkit"
groups = ["default"]
files = [
- {file = "dnspython-2.5.0-py3-none-any.whl", hash = "sha256:6facdf76b73c742ccf2d07add296f178e629da60be23ce4b0a9c927b1e02c3a6"},
- {file = "dnspython-2.5.0.tar.gz", hash = "sha256:a0034815a59ba9ae888946be7ccca8f7c157b286f8455b379c692efb51022a15"},
+ {file = "dnspython-2.6.1-py3-none-any.whl", hash = "sha256:5ef3b9680161f6fa89daf8ad451b5f1a33b18ae8a1c6778cdf4b43f08c0a6e50"},
+ {file = "dnspython-2.6.1.tar.gz", hash = "sha256:e8f0f9c23a7b7cb99ded64e6c3a6f3e701d78f50c55e002b839dea7225cff7cc"},
]
[[package]]
@@ -575,6 +575,24 @@ files = [
{file = "filelock-3.13.1.tar.gz", hash = "sha256:521f5f56c50f8426f5e03ad3b281b490a87ef15bc6c526f168290f0c7148d44e"},
]
+[[package]]
+name = "flower"
+version = "2.0.1"
+requires_python = ">=3.7"
+summary = "Celery Flower"
+groups = ["default"]
+dependencies = [
+ "celery>=5.0.5",
+ "humanize",
+ "prometheus-client>=0.8.0",
+ "pytz",
+ "tornado<7.0.0,>=5.0.0",
+]
+files = [
+ {file = "flower-2.0.1-py2.py3-none-any.whl", hash = "sha256:9db2c621eeefbc844c8dd88be64aef61e84e2deb29b271e02ab2b5b9f01068e2"},
+ {file = "flower-2.0.1.tar.gz", hash = "sha256:5ab717b979530770c16afb48b50d2a98d23c3e9fe39851dcf6bc4d01845a02a0"},
+]
+
[[package]]
name = "greenlet"
version = "3.0.3"
@@ -715,7 +733,7 @@ files = [
[[package]]
name = "httpcore"
-version = "1.0.2"
+version = "1.0.4"
requires_python = ">=3.8"
summary = "A minimal low-level HTTP client."
groups = ["default"]
@@ -724,8 +742,8 @@ dependencies = [
"h11<0.15,>=0.13",
]
files = [
- {file = "httpcore-1.0.2-py3-none-any.whl", hash = "sha256:096cc05bca73b8e459a1fc3dcf585148f63e534eae4339559c9b8a8d6399acc7"},
- {file = "httpcore-1.0.2.tar.gz", hash = "sha256:9fc092e4799b26174648e54b74ed5f683132a464e95643b226e00c2ed2fa6535"},
+ {file = "httpcore-1.0.4-py3-none-any.whl", hash = "sha256:ac418c1db41bade2ad53ae2f3834a3a0f5ae76b56cf5aa497d2d033384fc7d73"},
+ {file = "httpcore-1.0.4.tar.gz", hash = "sha256:cb2839ccfcba0d2d3c1131d3c3e26dfc327326fbe7a5dc0dbfe9f6c9151bb022"},
]
[[package]]
@@ -777,15 +795,26 @@ files = [
{file = "httpx-0.25.2.tar.gz", hash = "sha256:8b8fcaa0c8ea7b05edd69a094e63a2094c4efcb48129fb757361bc423c0ad9e8"},
]
+[[package]]
+name = "humanize"
+version = "4.9.0"
+requires_python = ">=3.8"
+summary = "Python humanize utilities"
+groups = ["default"]
+files = [
+ {file = "humanize-4.9.0-py3-none-any.whl", hash = "sha256:ce284a76d5b1377fd8836733b983bfb0b76f1aa1c090de2566fcf008d7f6ab16"},
+ {file = "humanize-4.9.0.tar.gz", hash = "sha256:582a265c931c683a7e9b8ed9559089dea7edcf6cc95be39a3cbc2c5d5ac2bcfa"},
+]
+
[[package]]
name = "identify"
-version = "2.5.33"
+version = "2.5.35"
requires_python = ">=3.8"
summary = "File identification library for Python"
groups = ["default"]
files = [
- {file = "identify-2.5.33-py2.py3-none-any.whl", hash = "sha256:d40ce5fcd762817627670da8a7d8d8e65f24342d14539c59488dc603bf662e34"},
- {file = "identify-2.5.33.tar.gz", hash = "sha256:161558f9fe4559e1557e1bff323e8631f6a0e4837f7497767c1782832f16b62d"},
+ {file = "identify-2.5.35-py2.py3-none-any.whl", hash = "sha256:c4de0081837b211594f8e877a6b4fad7ca32bbfc1a9307fdd61c28bfe923f13e"},
+ {file = "identify-2.5.35.tar.gz", hash = "sha256:10a7ca245cfcd756a554a7288159f72ff105ad233c7c4b9c6f0f4d108f5f6791"},
]
[[package]]
@@ -881,42 +910,42 @@ files = [
[[package]]
name = "markupsafe"
-version = "2.1.4"
+version = "2.1.5"
requires_python = ">=3.7"
summary = "Safely add untrusted strings to HTML/XML markup."
groups = ["default"]
files = [
- {file = "MarkupSafe-2.1.4-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:de8153a7aae3835484ac168a9a9bdaa0c5eee4e0bc595503c95d53b942879c84"},
- {file = "MarkupSafe-2.1.4-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:e888ff76ceb39601c59e219f281466c6d7e66bd375b4ec1ce83bcdc68306796b"},
- {file = "MarkupSafe-2.1.4-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a0b838c37ba596fcbfca71651a104a611543077156cb0a26fe0c475e1f152ee8"},
- {file = "MarkupSafe-2.1.4-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:dac1ebf6983148b45b5fa48593950f90ed6d1d26300604f321c74a9ca1609f8e"},
- {file = "MarkupSafe-2.1.4-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:0fbad3d346df8f9d72622ac71b69565e621ada2ce6572f37c2eae8dacd60385d"},
- {file = "MarkupSafe-2.1.4-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:d5291d98cd3ad9a562883468c690a2a238c4a6388ab3bd155b0c75dd55ece858"},
- {file = "MarkupSafe-2.1.4-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:a7cc49ef48a3c7a0005a949f3c04f8baa5409d3f663a1b36f0eba9bfe2a0396e"},
- {file = "MarkupSafe-2.1.4-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:b83041cda633871572f0d3c41dddd5582ad7d22f65a72eacd8d3d6d00291df26"},
- {file = "MarkupSafe-2.1.4-cp310-cp310-win32.whl", hash = "sha256:0c26f67b3fe27302d3a412b85ef696792c4a2386293c53ba683a89562f9399b0"},
- {file = "MarkupSafe-2.1.4-cp310-cp310-win_amd64.whl", hash = "sha256:a76055d5cb1c23485d7ddae533229039b850db711c554a12ea64a0fd8a0129e2"},
- {file = "MarkupSafe-2.1.4-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:9e9e3c4020aa2dc62d5dd6743a69e399ce3de58320522948af6140ac959ab863"},
- {file = "MarkupSafe-2.1.4-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:0042d6a9880b38e1dd9ff83146cc3c9c18a059b9360ceae207805567aacccc69"},
- {file = "MarkupSafe-2.1.4-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:55d03fea4c4e9fd0ad75dc2e7e2b6757b80c152c032ea1d1de487461d8140efc"},
- {file = "MarkupSafe-2.1.4-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3ab3a886a237f6e9c9f4f7d272067e712cdb4efa774bef494dccad08f39d8ae6"},
- {file = "MarkupSafe-2.1.4-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:abf5ebbec056817057bfafc0445916bb688a255a5146f900445d081db08cbabb"},
- {file = "MarkupSafe-2.1.4-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:e1a0d1924a5013d4f294087e00024ad25668234569289650929ab871231668e7"},
- {file = "MarkupSafe-2.1.4-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:e7902211afd0af05fbadcc9a312e4cf10f27b779cf1323e78d52377ae4b72bea"},
- {file = "MarkupSafe-2.1.4-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:c669391319973e49a7c6230c218a1e3044710bc1ce4c8e6eb71f7e6d43a2c131"},
- {file = "MarkupSafe-2.1.4-cp311-cp311-win32.whl", hash = "sha256:31f57d64c336b8ccb1966d156932f3daa4fee74176b0fdc48ef580be774aae74"},
- {file = "MarkupSafe-2.1.4-cp311-cp311-win_amd64.whl", hash = "sha256:54a7e1380dfece8847c71bf7e33da5d084e9b889c75eca19100ef98027bd9f56"},
- {file = "MarkupSafe-2.1.4-cp312-cp312-macosx_10_9_universal2.whl", hash = "sha256:a76cd37d229fc385738bd1ce4cba2a121cf26b53864c1772694ad0ad348e509e"},
- {file = "MarkupSafe-2.1.4-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:987d13fe1d23e12a66ca2073b8d2e2a75cec2ecb8eab43ff5624ba0ad42764bc"},
- {file = "MarkupSafe-2.1.4-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5244324676254697fe5c181fc762284e2c5fceeb1c4e3e7f6aca2b6f107e60dc"},
- {file = "MarkupSafe-2.1.4-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:78bc995e004681246e85e28e068111a4c3f35f34e6c62da1471e844ee1446250"},
- {file = "MarkupSafe-2.1.4-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:a4d176cfdfde84f732c4a53109b293d05883e952bbba68b857ae446fa3119b4f"},
- {file = "MarkupSafe-2.1.4-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:f9917691f410a2e0897d1ef99619fd3f7dd503647c8ff2475bf90c3cf222ad74"},
- {file = "MarkupSafe-2.1.4-cp312-cp312-musllinux_1_1_i686.whl", hash = "sha256:f06e5a9e99b7df44640767842f414ed5d7bedaaa78cd817ce04bbd6fd86e2dd6"},
- {file = "MarkupSafe-2.1.4-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:396549cea79e8ca4ba65525470d534e8a41070e6b3500ce2414921099cb73e8d"},
- {file = "MarkupSafe-2.1.4-cp312-cp312-win32.whl", hash = "sha256:f6be2d708a9d0e9b0054856f07ac7070fbe1754be40ca8525d5adccdbda8f475"},
- {file = "MarkupSafe-2.1.4-cp312-cp312-win_amd64.whl", hash = "sha256:5045e892cfdaecc5b4c01822f353cf2c8feb88a6ec1c0adef2a2e705eef0f656"},
- {file = "MarkupSafe-2.1.4.tar.gz", hash = "sha256:3aae9af4cac263007fd6309c64c6ab4506dd2b79382d9d19a1994f9240b8db4f"},
+ {file = "MarkupSafe-2.1.5-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:a17a92de5231666cfbe003f0e4b9b3a7ae3afb1ec2845aadc2bacc93ff85febc"},
+ {file = "MarkupSafe-2.1.5-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:72b6be590cc35924b02c78ef34b467da4ba07e4e0f0454a2c5907f473fc50ce5"},
+ {file = "MarkupSafe-2.1.5-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e61659ba32cf2cf1481e575d0462554625196a1f2fc06a1c777d3f48e8865d46"},
+ {file = "MarkupSafe-2.1.5-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:2174c595a0d73a3080ca3257b40096db99799265e1c27cc5a610743acd86d62f"},
+ {file = "MarkupSafe-2.1.5-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:ae2ad8ae6ebee9d2d94b17fb62763125f3f374c25618198f40cbb8b525411900"},
+ {file = "MarkupSafe-2.1.5-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:075202fa5b72c86ad32dc7d0b56024ebdbcf2048c0ba09f1cde31bfdd57bcfff"},
+ {file = "MarkupSafe-2.1.5-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:598e3276b64aff0e7b3451b72e94fa3c238d452e7ddcd893c3ab324717456bad"},
+ {file = "MarkupSafe-2.1.5-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:fce659a462a1be54d2ffcacea5e3ba2d74daa74f30f5f143fe0c58636e355fdd"},
+ {file = "MarkupSafe-2.1.5-cp310-cp310-win32.whl", hash = "sha256:d9fad5155d72433c921b782e58892377c44bd6252b5af2f67f16b194987338a4"},
+ {file = "MarkupSafe-2.1.5-cp310-cp310-win_amd64.whl", hash = "sha256:bf50cd79a75d181c9181df03572cdce0fbb75cc353bc350712073108cba98de5"},
+ {file = "MarkupSafe-2.1.5-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:629ddd2ca402ae6dbedfceeba9c46d5f7b2a61d9749597d4307f943ef198fc1f"},
+ {file = "MarkupSafe-2.1.5-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:5b7b716f97b52c5a14bffdf688f971b2d5ef4029127f1ad7a513973cfd818df2"},
+ {file = "MarkupSafe-2.1.5-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:6ec585f69cec0aa07d945b20805be741395e28ac1627333b1c5b0105962ffced"},
+ {file = "MarkupSafe-2.1.5-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b91c037585eba9095565a3556f611e3cbfaa42ca1e865f7b8015fe5c7336d5a5"},
+ {file = "MarkupSafe-2.1.5-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:7502934a33b54030eaf1194c21c692a534196063db72176b0c4028e140f8f32c"},
+ {file = "MarkupSafe-2.1.5-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:0e397ac966fdf721b2c528cf028494e86172b4feba51d65f81ffd65c63798f3f"},
+ {file = "MarkupSafe-2.1.5-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:c061bb86a71b42465156a3ee7bd58c8c2ceacdbeb95d05a99893e08b8467359a"},
+ {file = "MarkupSafe-2.1.5-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:3a57fdd7ce31c7ff06cdfbf31dafa96cc533c21e443d57f5b1ecc6cdc668ec7f"},
+ {file = "MarkupSafe-2.1.5-cp311-cp311-win32.whl", hash = "sha256:397081c1a0bfb5124355710fe79478cdbeb39626492b15d399526ae53422b906"},
+ {file = "MarkupSafe-2.1.5-cp311-cp311-win_amd64.whl", hash = "sha256:2b7c57a4dfc4f16f7142221afe5ba4e093e09e728ca65c51f5620c9aaeb9a617"},
+ {file = "MarkupSafe-2.1.5-cp312-cp312-macosx_10_9_universal2.whl", hash = "sha256:8dec4936e9c3100156f8a2dc89c4b88d5c435175ff03413b443469c7c8c5f4d1"},
+ {file = "MarkupSafe-2.1.5-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:3c6b973f22eb18a789b1460b4b91bf04ae3f0c4234a0a6aa6b0a92f6f7b951d4"},
+ {file = "MarkupSafe-2.1.5-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ac07bad82163452a6884fe8fa0963fb98c2346ba78d779ec06bd7a6262132aee"},
+ {file = "MarkupSafe-2.1.5-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f5dfb42c4604dddc8e4305050aa6deb084540643ed5804d7455b5df8fe16f5e5"},
+ {file = "MarkupSafe-2.1.5-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:ea3d8a3d18833cf4304cd2fc9cbb1efe188ca9b5efef2bdac7adc20594a0e46b"},
+ {file = "MarkupSafe-2.1.5-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:d050b3361367a06d752db6ead6e7edeb0009be66bc3bae0ee9d97fb326badc2a"},
+ {file = "MarkupSafe-2.1.5-cp312-cp312-musllinux_1_1_i686.whl", hash = "sha256:bec0a414d016ac1a18862a519e54b2fd0fc8bbfd6890376898a6c0891dd82e9f"},
+ {file = "MarkupSafe-2.1.5-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:58c98fee265677f63a4385256a6d7683ab1832f3ddd1e66fe948d5880c21a169"},
+ {file = "MarkupSafe-2.1.5-cp312-cp312-win32.whl", hash = "sha256:8590b4ae07a35970728874632fed7bd57b26b0102df2d2b233b6d9d82f6c62ad"},
+ {file = "MarkupSafe-2.1.5-cp312-cp312-win_amd64.whl", hash = "sha256:823b65d8706e32ad2df51ed89496147a42a2a6e01c13cfb6ffb8b1e92bc910bb"},
+ {file = "MarkupSafe-2.1.5.tar.gz", hash = "sha256:d283d37a890ba4c1ae73ffadf8046435c76e7bc2247bbb63c00bd1a709c6544b"},
]
[[package]]
@@ -977,13 +1006,13 @@ files = [
[[package]]
name = "packaging"
-version = "23.2"
+version = "24.0"
requires_python = ">=3.7"
summary = "Core utilities for Python packages"
groups = ["default"]
files = [
- {file = "packaging-23.2-py3-none-any.whl", hash = "sha256:8c491190033a9af7e1d931d0b5dacc2ef47509b34dd0de67ed209b5203fc88c7"},
- {file = "packaging-23.2.tar.gz", hash = "sha256:048fb0e9405036518eaaf48a55953c750c11e1a1b68e0dd1a9d62ed0c092cfc5"},
+ {file = "packaging-24.0-py3-none-any.whl", hash = "sha256:2ddfb553fdf02fb784c234c7ba6ccc288296ceabec964ad2eae3777778130bc5"},
+ {file = "packaging-24.0.tar.gz", hash = "sha256:eb82c5e3e56209074766e6885bb04b8c38a0c015d0a30036ebe7ece34c9989e9"},
]
[[package]]
@@ -1102,6 +1131,17 @@ files = [
{file = "pre_commit-3.2.2.tar.gz", hash = "sha256:5b808fcbda4afbccf6d6633a56663fed35b6c2bc08096fd3d47ce197ac351d9d"},
]
+[[package]]
+name = "prometheus-client"
+version = "0.20.0"
+requires_python = ">=3.8"
+summary = "Python client for the Prometheus monitoring system."
+groups = ["default"]
+files = [
+ {file = "prometheus_client-0.20.0-py3-none-any.whl", hash = "sha256:cde524a85bce83ca359cc837f28b8c0db5cac7aa653a588fd7e84ba061c329e7"},
+ {file = "prometheus_client-0.20.0.tar.gz", hash = "sha256:287629d00b147a32dcb2be0b9df905da599b2d82f80377083ec8463309a4bb89"},
+]
+
[[package]]
name = "prompt-toolkit"
version = "3.0.43"
@@ -1328,7 +1368,7 @@ files = [
[[package]]
name = "python-dateutil"
-version = "2.8.2"
+version = "2.9.0.post0"
requires_python = "!=3.0.*,!=3.1.*,!=3.2.*,>=2.7"
summary = "Extensions to the standard Python datetime module"
groups = ["default"]
@@ -1336,8 +1376,8 @@ dependencies = [
"six>=1.5",
]
files = [
- {file = "python-dateutil-2.8.2.tar.gz", hash = "sha256:0123cacc1627ae19ddf3c27a5de5bd67ee4586fbdd6440d9748f8abb483d3e86"},
- {file = "python_dateutil-2.8.2-py2.py3-none-any.whl", hash = "sha256:961d03dc3453ebbc59dbdea9e4e11c5651520a876d0f4db161e8674aae935da9"},
+ {file = "python-dateutil-2.9.0.post0.tar.gz", hash = "sha256:37dd54208da7e1cd875388217d5e00ebd4179249f90fb72437e91a35459a0ad3"},
+ {file = "python_dateutil-2.9.0.post0-py2.py3-none-any.whl", hash = "sha256:a8b2bc7bffae282281c8140a97d3aa9c14da0b136dfe83f850eea9a5f7470427"},
]
[[package]]
@@ -1452,7 +1492,7 @@ files = [
[[package]]
name = "rich"
-version = "13.7.0"
+version = "13.7.1"
requires_python = ">=3.7.0"
summary = "Render rich text, tables, progress bars, syntax highlighting, markdown and more to the terminal"
groups = ["default"]
@@ -1461,8 +1501,8 @@ dependencies = [
"pygments<3.0.0,>=2.13.0",
]
files = [
- {file = "rich-13.7.0-py3-none-any.whl", hash = "sha256:6da14c108c4866ee9520bbffa71f6fe3962e193b7da68720583850cd4548e235"},
- {file = "rich-13.7.0.tar.gz", hash = "sha256:5cb5123b5cf9ee70584244246816e9114227e0b98ad9176eede6ad54bf5403fa"},
+ {file = "rich-13.7.1-py3-none-any.whl", hash = "sha256:4edbae314f59eb482f54e9e30bf00d33350aaa94f4bfcd4e9e3110e64d0d7222"},
+ {file = "rich-13.7.1.tar.gz", hash = "sha256:9be308cb1fe2f1f57d67ce99e95af38a1e2bc71ad9813b0e247cf7ffbcc3a432"},
]
[[package]]
@@ -1481,39 +1521,39 @@ files = [
[[package]]
name = "ruff"
-version = "0.1.8"
+version = "0.3.3"
requires_python = ">=3.7"
summary = "An extremely fast Python linter and code formatter, written in Rust."
-groups = ["default"]
+groups = ["lint"]
files = [
- {file = "ruff-0.1.8-py3-none-macosx_10_12_x86_64.macosx_11_0_arm64.macosx_10_12_universal2.whl", hash = "sha256:7de792582f6e490ae6aef36a58d85df9f7a0cfd1b0d4fe6b4fb51803a3ac96fa"},
- {file = "ruff-0.1.8-py3-none-macosx_10_12_x86_64.whl", hash = "sha256:c8e3255afd186c142eef4ec400d7826134f028a85da2146102a1172ecc7c3696"},
- {file = "ruff-0.1.8-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ff78a7583020da124dd0deb835ece1d87bb91762d40c514ee9b67a087940528b"},
- {file = "ruff-0.1.8-py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:bd8ee69b02e7bdefe1e5da2d5b6eaaddcf4f90859f00281b2333c0e3a0cc9cd6"},
- {file = "ruff-0.1.8-py3-none-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:a05b0ddd7ea25495e4115a43125e8a7ebed0aa043c3d432de7e7d6e8e8cd6448"},
- {file = "ruff-0.1.8-py3-none-manylinux_2_17_ppc64.manylinux2014_ppc64.whl", hash = "sha256:e6f08ca730f4dc1b76b473bdf30b1b37d42da379202a059eae54ec7fc1fbcfed"},
- {file = "ruff-0.1.8-py3-none-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:f35960b02df6b827c1b903091bb14f4b003f6cf102705efc4ce78132a0aa5af3"},
- {file = "ruff-0.1.8-py3-none-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:7d076717c67b34c162da7c1a5bda16ffc205e0e0072c03745275e7eab888719f"},
- {file = "ruff-0.1.8-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b6a21ab023124eafb7cef6d038f835cb1155cd5ea798edd8d9eb2f8b84be07d9"},
- {file = "ruff-0.1.8-py3-none-musllinux_1_2_aarch64.whl", hash = "sha256:ce697c463458555027dfb194cb96d26608abab920fa85213deb5edf26e026664"},
- {file = "ruff-0.1.8-py3-none-musllinux_1_2_armv7l.whl", hash = "sha256:db6cedd9ffed55548ab313ad718bc34582d394e27a7875b4b952c2d29c001b26"},
- {file = "ruff-0.1.8-py3-none-musllinux_1_2_i686.whl", hash = "sha256:05ffe9dbd278965271252704eddb97b4384bf58b971054d517decfbf8c523f05"},
- {file = "ruff-0.1.8-py3-none-musllinux_1_2_x86_64.whl", hash = "sha256:5daaeaf00ae3c1efec9742ff294b06c3a2a9db8d3db51ee4851c12ad385cda30"},
- {file = "ruff-0.1.8-py3-none-win32.whl", hash = "sha256:e49fbdfe257fa41e5c9e13c79b9e79a23a79bd0e40b9314bc53840f520c2c0b3"},
- {file = "ruff-0.1.8-py3-none-win_amd64.whl", hash = "sha256:f41f692f1691ad87f51708b823af4bb2c5c87c9248ddd3191c8f088e66ce590a"},
- {file = "ruff-0.1.8-py3-none-win_arm64.whl", hash = "sha256:aa8ee4f8440023b0a6c3707f76cadce8657553655dcbb5fc9b2f9bb9bee389f6"},
- {file = "ruff-0.1.8.tar.gz", hash = "sha256:f7ee467677467526cfe135eab86a40a0e8db43117936ac4f9b469ce9cdb3fb62"},
+ {file = "ruff-0.3.3-py3-none-macosx_10_12_x86_64.macosx_11_0_arm64.macosx_10_12_universal2.whl", hash = "sha256:973a0e388b7bc2e9148c7f9be8b8c6ae7471b9be37e1cc732f8f44a6f6d7720d"},
+ {file = "ruff-0.3.3-py3-none-macosx_10_12_x86_64.whl", hash = "sha256:cfa60d23269d6e2031129b053fdb4e5a7b0637fc6c9c0586737b962b2f834493"},
+ {file = "ruff-0.3.3-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1eca7ff7a47043cf6ce5c7f45f603b09121a7cc047447744b029d1b719278eb5"},
+ {file = "ruff-0.3.3-py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:e7d3f6762217c1da954de24b4a1a70515630d29f71e268ec5000afe81377642d"},
+ {file = "ruff-0.3.3-py3-none-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:b24c19e8598916d9c6f5a5437671f55ee93c212a2c4c569605dc3842b6820386"},
+ {file = "ruff-0.3.3-py3-none-manylinux_2_17_ppc64.manylinux2014_ppc64.whl", hash = "sha256:5a6cbf216b69c7090f0fe4669501a27326c34e119068c1494f35aaf4cc683778"},
+ {file = "ruff-0.3.3-py3-none-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:352e95ead6964974b234e16ba8a66dad102ec7bf8ac064a23f95371d8b198aab"},
+ {file = "ruff-0.3.3-py3-none-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:8d6ab88c81c4040a817aa432484e838aaddf8bfd7ca70e4e615482757acb64f8"},
+ {file = "ruff-0.3.3-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:79bca3a03a759cc773fca69e0bdeac8abd1c13c31b798d5bb3c9da4a03144a9f"},
+ {file = "ruff-0.3.3-py3-none-musllinux_1_2_aarch64.whl", hash = "sha256:2700a804d5336bcffe063fd789ca2c7b02b552d2e323a336700abb8ae9e6a3f8"},
+ {file = "ruff-0.3.3-py3-none-musllinux_1_2_armv7l.whl", hash = "sha256:fd66469f1a18fdb9d32e22b79f486223052ddf057dc56dea0caaf1a47bdfaf4e"},
+ {file = "ruff-0.3.3-py3-none-musllinux_1_2_i686.whl", hash = "sha256:45817af234605525cdf6317005923bf532514e1ea3d9270acf61ca2440691376"},
+ {file = "ruff-0.3.3-py3-none-musllinux_1_2_x86_64.whl", hash = "sha256:0da458989ce0159555ef224d5b7c24d3d2e4bf4c300b85467b08c3261c6bc6a8"},
+ {file = "ruff-0.3.3-py3-none-win32.whl", hash = "sha256:f2831ec6a580a97f1ea82ea1eda0401c3cdf512cf2045fa3c85e8ef109e87de0"},
+ {file = "ruff-0.3.3-py3-none-win_amd64.whl", hash = "sha256:be90bcae57c24d9f9d023b12d627e958eb55f595428bafcb7fec0791ad25ddfc"},
+ {file = "ruff-0.3.3-py3-none-win_arm64.whl", hash = "sha256:0171aab5fecdc54383993389710a3d1227f2da124d76a2784a7098e818f92d61"},
+ {file = "ruff-0.3.3.tar.gz", hash = "sha256:38671be06f57a2f8aba957d9f701ea889aa5736be806f18c0cd03d6ff0cbca8d"},
]
[[package]]
name = "setuptools"
-version = "69.0.3"
+version = "69.2.0"
requires_python = ">=3.8"
summary = "Easily download, build, install, upgrade, and uninstall Python packages"
groups = ["default", "deploy"]
files = [
- {file = "setuptools-69.0.3-py3-none-any.whl", hash = "sha256:385eb4edd9c9d5c17540511303e39a147ce2fc04bc55289c322b9e5904fe2c05"},
- {file = "setuptools-69.0.3.tar.gz", hash = "sha256:be1af57fc409f93647f2e8e4573a142ed38724b8cdd389706a867bb4efcf1e78"},
+ {file = "setuptools-69.2.0-py3-none-any.whl", hash = "sha256:c21c49fb1042386df081cb5d86759792ab89efca84cf114889191cd09aacc80c"},
+ {file = "setuptools-69.2.0.tar.gz", hash = "sha256:0ff4183f8f42cd8fa3acea16c45205521a4ef28f73c6391d8a25e92893134f2e"},
]
[[package]]
@@ -1539,13 +1579,13 @@ files = [
[[package]]
name = "sniffio"
-version = "1.3.0"
+version = "1.3.1"
requires_python = ">=3.7"
summary = "Sniff out which async library your code is running under"
groups = ["default"]
files = [
- {file = "sniffio-1.3.0-py3-none-any.whl", hash = "sha256:eecefdce1e5bbfb7ad2eeaabf7c1eeb404d7757c379bd1f7e5cce9d8bf425384"},
- {file = "sniffio-1.3.0.tar.gz", hash = "sha256:e60305c5e5d314f5389259b7f22aaa33d8f7dee49763119234af3755c55b9101"},
+ {file = "sniffio-1.3.1-py3-none-any.whl", hash = "sha256:2f6da418d1f1e0fddd844478f41680e794e6051915791a034ff65e5f100525a2"},
+ {file = "sniffio-1.3.1.tar.gz", hash = "sha256:f4324edc670a0f49750a81b895f35c3adb843cca46f0530f79fc1babb23789dc"},
]
[[package]]
@@ -1626,26 +1666,46 @@ files = [
{file = "tomli-2.0.1.tar.gz", hash = "sha256:de526c12914f0c550d15924c62d72abc48d6fe7364aa87328337a31007fe8a4f"},
]
+[[package]]
+name = "tornado"
+version = "6.4"
+requires_python = ">= 3.8"
+summary = "Tornado is a Python web framework and asynchronous networking library, originally developed at FriendFeed."
+groups = ["default"]
+files = [
+ {file = "tornado-6.4-cp38-abi3-macosx_10_9_universal2.whl", hash = "sha256:02ccefc7d8211e5a7f9e8bc3f9e5b0ad6262ba2fbb683a6443ecc804e5224ce0"},
+ {file = "tornado-6.4-cp38-abi3-macosx_10_9_x86_64.whl", hash = "sha256:27787de946a9cffd63ce5814c33f734c627a87072ec7eed71f7fc4417bb16263"},
+ {file = "tornado-6.4-cp38-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f7894c581ecdcf91666a0912f18ce5e757213999e183ebfc2c3fdbf4d5bd764e"},
+ {file = "tornado-6.4-cp38-abi3-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:e43bc2e5370a6a8e413e1e1cd0c91bedc5bd62a74a532371042a18ef19e10579"},
+ {file = "tornado-6.4-cp38-abi3-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f0251554cdd50b4b44362f73ad5ba7126fc5b2c2895cc62b14a1c2d7ea32f212"},
+ {file = "tornado-6.4-cp38-abi3-musllinux_1_1_aarch64.whl", hash = "sha256:fd03192e287fbd0899dd8f81c6fb9cbbc69194d2074b38f384cb6fa72b80e9c2"},
+ {file = "tornado-6.4-cp38-abi3-musllinux_1_1_i686.whl", hash = "sha256:88b84956273fbd73420e6d4b8d5ccbe913c65d31351b4c004ae362eba06e1f78"},
+ {file = "tornado-6.4-cp38-abi3-musllinux_1_1_x86_64.whl", hash = "sha256:71ddfc23a0e03ef2df1c1397d859868d158c8276a0603b96cf86892bff58149f"},
+ {file = "tornado-6.4-cp38-abi3-win32.whl", hash = "sha256:6f8a6c77900f5ae93d8b4ae1196472d0ccc2775cc1dfdc9e7727889145c45052"},
+ {file = "tornado-6.4-cp38-abi3-win_amd64.whl", hash = "sha256:10aeaa8006333433da48dec9fe417877f8bcc21f48dda8d661ae79da357b2a63"},
+ {file = "tornado-6.4.tar.gz", hash = "sha256:72291fa6e6bc84e626589f1c29d90a5a6d593ef5ae68052ee2ef000dfd273dee"},
+]
+
[[package]]
name = "typing-extensions"
-version = "4.9.0"
+version = "4.10.0"
requires_python = ">=3.8"
summary = "Backported and Experimental Type Hints for Python 3.8+"
groups = ["default"]
files = [
- {file = "typing_extensions-4.9.0-py3-none-any.whl", hash = "sha256:af72aea155e91adfc61c3ae9e0e342dbc0cba726d6cba4b6c72c1f34e47291cd"},
- {file = "typing_extensions-4.9.0.tar.gz", hash = "sha256:23478f88c37f27d76ac8aee6c905017a143b0b1b886c3c9f66bc2fd94f9f5783"},
+ {file = "typing_extensions-4.10.0-py3-none-any.whl", hash = "sha256:69b1a937c3a517342112fb4c6df7e72fc39a38e7891a5730ed4985b5214b5475"},
+ {file = "typing_extensions-4.10.0.tar.gz", hash = "sha256:b0abd7c89e8fb96f98db18d86106ff1d90ab692004eb746cf6eda2682f91b3cb"},
]
[[package]]
name = "tzdata"
-version = "2023.4"
+version = "2024.1"
requires_python = ">=2"
summary = "Provider of IANA time zone data"
groups = ["default"]
files = [
- {file = "tzdata-2023.4-py2.py3-none-any.whl", hash = "sha256:aa3ace4329eeacda5b7beb7ea08ece826c28d761cda36e747cfbf97996d39bf3"},
- {file = "tzdata-2023.4.tar.gz", hash = "sha256:dd54c94f294765522c77399649b4fefd95522479a664a0cec87f41bebc6148c9"},
+ {file = "tzdata-2024.1-py2.py3-none-any.whl", hash = "sha256:9068bc196136463f5245e51efda838afa15aaeca9903f49050dfa2679db4d252"},
+ {file = "tzdata-2024.1.tar.gz", hash = "sha256:2674120f8d891909751c38abcdfd386ac0a5a1127954fbc332af6b5ceae07efd"},
]
[[package]]
@@ -1751,7 +1811,7 @@ files = [
[[package]]
name = "virtualenv"
-version = "20.25.0"
+version = "20.25.1"
requires_python = ">=3.7"
summary = "Virtual Python Environment builder"
groups = ["default"]
@@ -1761,8 +1821,8 @@ dependencies = [
"platformdirs<5,>=3.9.1",
]
files = [
- {file = "virtualenv-20.25.0-py3-none-any.whl", hash = "sha256:4238949c5ffe6876362d9c0180fc6c3a824a7b12b80604eeb8085f2ed7460de3"},
- {file = "virtualenv-20.25.0.tar.gz", hash = "sha256:bf51c0d9c7dd63ea8e44086fa1e4fb1093a31e963b86959257378aef020e1f1b"},
+ {file = "virtualenv-20.25.1-py3-none-any.whl", hash = "sha256:961c026ac520bac5f69acb8ea063e8a4f071bcc9457b9c1f28f6b085c511583a"},
+ {file = "virtualenv-20.25.1.tar.gz", hash = "sha256:e08e13ecdca7a0bd53798f356d5831434afa5b07b93f0abdf0797b7a06ffe197"},
]
[[package]]
diff --git a/backend/pre_start.sh b/backend/pre_start.sh
new file mode 100644
index 0000000..de4ba74
--- /dev/null
+++ b/backend/pre_start.sh
@@ -0,0 +1,5 @@
+#!/usr/bin/env bash
+
+alembic upgrade head
+
+python ./scripts/init_data.py
diff --git a/pyproject.toml b/backend/pyproject.toml
similarity index 77%
rename from pyproject.toml
rename to backend/pyproject.toml
index a3659dc..c5223b8 100644
--- a/pyproject.toml
+++ b/backend/pyproject.toml
@@ -1,8 +1,7 @@
[project]
name = "fastapi_best_architecture"
version = "0.0.1"
-description = """FastAPI based on the construction of the front and back of the separation of rights control system,
-using a unique pseudo three-tier architecture model design, and as a template library free open source"""
+description = "基于 FastAPI 构建的前后端分离 RBAC 权限控制系统,采用独特的伪三层架构模型设计,内置 fastapi-admin 基本实现"
authors = [
{ name = "Wu Clan", email = "jianhengwu0407@gmail.com" },
]
@@ -41,22 +40,28 @@ dependencies = [
"python-multipart==0.0.6",
"pytz==2023.3",
"redis[hiredis]==5.0.1",
- "ruff==0.1.8",
"SQLAlchemy==2.0.23",
"user-agents==2.2.0",
"uvicorn[standard]==0.24.0",
"XdbSearchIP==1.0.2",
- "fastapi-oauth20>=0.0.1a1",
+ "fastapi_oauth20>=0.0.1a1",
+ "flower==2.0.1",
]
requires-python = ">=3.10"
readme = "README.md"
license = { text = "MIT" }
-[project.optional-dependencies]
+[tool.pdm.dev-dependencies]
+lint = [
+ "ruff>=0.3.3",
+]
deploy = [
- "supervisor==4.2.5",
- "wait-for-it==2.2.2",
+ "supervisor>=4.2.5",
+ "wait-for-it>=2.2.2",
]
[tool.pdm]
package-type = "application"
+
+[tool.pdm.scripts]
+lint = "pre-commit run --all-files"
diff --git a/requirements.txt b/backend/requirements.txt
similarity index 86%
rename from requirements.txt
rename to backend/requirements.txt
index 8922f27..1f13ab1 100644
--- a/requirements.txt
+++ b/backend/requirements.txt
@@ -6,7 +6,7 @@ aiosmtplib==3.0.1
alembic==1.13.0
amqp==5.2.0
annotated-types==0.6.0
-anyio==4.2.0
+anyio==4.3.0
asgiref==3.7.2
async-timeout==4.0.3; python_full_version <= "3.11.2"
asyncmy==0.2.9
@@ -16,7 +16,7 @@ billiard==4.2.0
casbin==1.34.0
casbin-async-sqlalchemy-adapter==1.4.0
celery==5.3.6
-certifi==2023.11.17
+certifi==2024.2.2
cffi==1.16.0
cfgv==3.4.0
click==8.1.7
@@ -26,7 +26,7 @@ click-repl==0.3.0
colorama==0.4.6; sys_platform == "win32" or platform_system == "Windows"
cryptography==41.0.7
distlib==0.3.8
-dnspython==2.5.0
+dnspython==2.6.1
ecdsa==0.18.0
email-validator==2.0.0
exceptiongroup==1.2.0; python_version < "3.11"
@@ -36,14 +36,16 @@ fastapi-limiter==0.1.6
fastapi-oauth20==0.0.1a1
fastapi-pagination==0.12.13
filelock==3.13.1
+flower==2.0.1
greenlet==3.0.3; platform_machine == "win32" or platform_machine == "WIN32" or platform_machine == "AMD64" or platform_machine == "amd64" or platform_machine == "x86_64" or platform_machine == "ppc64le" or platform_machine == "aarch64"
gunicorn==21.2.0
h11==0.14.0
hiredis==2.3.2
-httpcore==1.0.2
+httpcore==1.0.4
httptools==0.6.1
httpx==0.25.2
-identify==2.5.33
+humanize==4.9.0
+identify==2.5.35
idna==3.6
iniconfig==2.0.0
itsdangerous==2.1.2
@@ -51,11 +53,11 @@ kombu==5.3.5
loguru==0.7.2
mako==1.3.2
markdown-it-py==3.0.0
-markupsafe==2.1.4
+markupsafe==2.1.5
mdurl==0.1.2
msgspec==0.18.5
nodeenv==1.8.0
-packaging==23.2
+packaging==24.0
passlib==1.7.4
path==15.1.2
phonenumbers==8.13.27
@@ -63,6 +65,7 @@ pillow==9.5.0
platformdirs==4.2.0
pluggy==1.4.0
pre-commit==3.2.2
+prometheus-client==0.20.0
prompt-toolkit==3.0.43
psutil==5.9.6
pyasn1==0.5.1
@@ -74,32 +77,33 @@ pydantic-settings==2.1.0
pygments==2.17.2
pytest==7.2.2
pytest-pretty==1.2.0
-python-dateutil==2.8.2
+python-dateutil==2.9.0.post0
python-dotenv==1.0.1
python-jose==3.3.0
python-multipart==0.0.6
pytz==2023.3
pyyaml==6.0.1
redis==5.0.1
-rich==13.7.0
+rich==13.7.1
rsa==4.9
-ruff==0.1.8
-setuptools==69.0.3
+ruff==0.3.3
+setuptools==69.2.0
simpleeval==0.9.13
six==1.16.0
-sniffio==1.3.0
+sniffio==1.3.1
sqlalchemy==2.0.23
starlette==0.32.0.post1
supervisor==4.2.5
tomli==2.0.1; python_version < "3.11"
-typing-extensions==4.9.0
-tzdata==2023.4
+tornado==6.4
+typing-extensions==4.10.0
+tzdata==2024.1
ua-parser==0.18.0
user-agents==2.2.0
uvicorn==0.24.0
uvloop==0.19.0; (sys_platform != "cygwin" and sys_platform != "win32") and platform_python_implementation != "PyPy"
vine==5.1.0
-virtualenv==20.25.0
+virtualenv==20.25.1
wait-for-it==2.2.2
watchfiles==0.21.0
wcwidth==0.2.13
diff --git a/backend/scripts/format.sh b/backend/scripts/format.sh
new file mode 100644
index 0000000..816bab2
--- /dev/null
+++ b/backend/scripts/format.sh
@@ -0,0 +1,3 @@
+#!/usr/bin/env bash
+
+ruff format --check
diff --git a/backend/scripts/init_data.py b/backend/scripts/init_data.py
new file mode 100644
index 0000000..a6aa607
--- /dev/null
+++ b/backend/scripts/init_data.py
@@ -0,0 +1,20 @@
+#!/usr/bin/env python3
+# -*- coding: utf-8 -*-
+import logging
+
+from anyio import run
+
+from backend.database.db_mysql import create_table
+
+logging.basicConfig(level=logging.INFO)
+logger = logging.getLogger(__name__)
+
+
+async def init() -> None:
+ logger.info('Creating initial data')
+ await create_table()
+ logger.info('Initial data created')
+
+
+if __name__ == '__main__':
+ run(init) # type: ignore
diff --git a/backend/scripts/lint.sh b/backend/scripts/lint.sh
new file mode 100644
index 0000000..3adf95c
--- /dev/null
+++ b/backend/scripts/lint.sh
@@ -0,0 +1,3 @@
+#!/usr/bin/env bash
+
+pdm lint
diff --git a/backend/scripts/pdm_export.sh b/backend/scripts/pdm_export.sh
new file mode 100644
index 0000000..8c81b06
--- /dev/null
+++ b/backend/scripts/pdm_export.sh
@@ -0,0 +1,3 @@
+#!/usr/bin/env bash
+
+pdm export -p . -o requirements.txt --without-hashes
diff --git a/backend/sql/create_tables.sql b/backend/sql/create_tables.sql
index 572d037..713bfa9 100644
--- a/backend/sql/create_tables.sql
+++ b/backend/sql/create_tables.sql
@@ -1,239 +1,280 @@
-CREATE TABLE alembic_version
+-- sys_api: table
+CREATE TABLE `sys_api`
(
- version_num VARCHAR(32) NOT NULL,
- CONSTRAINT alembic_version_pkc PRIMARY KEY (version_num)
-);
+ `id` int NOT NULL AUTO_INCREMENT COMMENT '主键id',
+ `name` varchar(50) NOT NULL COMMENT 'api名称',
+ `method` varchar(16) NOT NULL COMMENT '请求方法',
+ `path` varchar(500) NOT NULL COMMENT 'api路径',
+ `remark` longtext COMMENT '备注',
+ `created_time` datetime NOT NULL COMMENT '创建时间',
+ `updated_time` datetime DEFAULT NULL COMMENT '更新时间',
+ PRIMARY KEY (`id`),
+ UNIQUE KEY `name` (`name`),
+ KEY `ix_sys_api_id` (`id`)
+) ENGINE = InnoDB
+ DEFAULT CHARSET = utf8mb4
+ COLLATE = utf8mb4_0900_ai_ci;
-CREATE TABLE sys_api
+-- sys_casbin_rule: table
+CREATE TABLE `sys_casbin_rule`
(
- id INTEGER NOT NULL AUTO_INCREMENT,
- name VARCHAR(50) NOT NULL COMMENT 'api名称',
- method VARCHAR(16) NOT NULL COMMENT '请求方法',
- path VARCHAR(500) NOT NULL COMMENT 'api路径',
- remark LONGTEXT COMMENT '备注',
- created_time DATETIME NOT NULL COMMENT '创建时间',
- updated_time DATETIME COMMENT '更新时间',
- PRIMARY KEY (id),
- UNIQUE (name)
-);
+ `id` int NOT NULL AUTO_INCREMENT COMMENT '主键id',
+ `ptype` varchar(255) NOT NULL COMMENT '策略类型: p / g',
+ `v0` varchar(255) NOT NULL COMMENT '角色ID / 用户uuid',
+ `v1` longtext NOT NULL COMMENT 'api路径 / 角色名称',
+ `v2` varchar(255) DEFAULT NULL COMMENT '请求方法',
+ `v3` varchar(255) DEFAULT NULL,
+ `v4` varchar(255) DEFAULT NULL,
+ `v5` varchar(255) DEFAULT NULL,
+ PRIMARY KEY (`id`),
+ KEY `ix_sys_casbin_rule_id` (`id`)
+) ENGINE = InnoDB
+ DEFAULT CHARSET = utf8mb4
+ COLLATE = utf8mb4_0900_ai_ci;
-CREATE INDEX ix_sys_api_id ON sys_api (id);
-
-CREATE TABLE sys_casbin_rule
+-- sys_dept: table
+CREATE TABLE `sys_dept`
(
- id INTEGER NOT NULL COMMENT '主键id' AUTO_INCREMENT,
- ptype VARCHAR(255) NOT NULL COMMENT '策略类型: p 或者 g',
- v0 VARCHAR(255) NOT NULL COMMENT '角色 / 用户uuid',
- v1 LONGTEXT NOT NULL COMMENT 'api路径 / 角色名称',
- v2 VARCHAR(255) COMMENT '请求方法',
- v3 VARCHAR(255),
- v4 VARCHAR(255),
- v5 VARCHAR(255),
- PRIMARY KEY (id)
-);
+ `id` int NOT NULL AUTO_INCREMENT COMMENT '主键id',
+ `name` varchar(50) NOT NULL COMMENT '部门名称',
+ `level` int NOT NULL COMMENT '部门层级',
+ `sort` int NOT NULL COMMENT '排序',
+ `leader` varchar(20) DEFAULT NULL COMMENT '负责人',
+ `phone` varchar(11) DEFAULT NULL COMMENT '手机',
+ `email` varchar(50) DEFAULT NULL COMMENT '邮箱',
+ `status` int NOT NULL COMMENT '部门状态(0停用 1正常)',
+ `del_flag` tinyint(1) NOT NULL COMMENT '删除标志(0删除 1存在)',
+ `parent_id` int DEFAULT NULL COMMENT '父部门ID',
+ `created_time` datetime NOT NULL COMMENT '创建时间',
+ `updated_time` datetime DEFAULT NULL COMMENT '更新时间',
+ PRIMARY KEY (`id`),
+ KEY `ix_sys_dept_parent_id` (`parent_id`),
+ KEY `ix_sys_dept_id` (`id`),
+ CONSTRAINT `sys_dept_ibfk_1` FOREIGN KEY (`parent_id`) REFERENCES `sys_dept` (`id`) ON DELETE SET NULL
+) ENGINE = InnoDB
+ DEFAULT CHARSET = utf8mb4
+ COLLATE = utf8mb4_0900_ai_ci;
-CREATE INDEX ix_sys_casbin_rule_id ON sys_casbin_rule (id);
-
-CREATE TABLE sys_dept
+-- sys_dict_type: table
+CREATE TABLE `sys_dict_type`
(
- id INTEGER NOT NULL AUTO_INCREMENT,
- name VARCHAR(50) NOT NULL COMMENT '部门名称',
- level INTEGER NOT NULL COMMENT '部门层级',
- sort INTEGER NOT NULL COMMENT '排序',
- leader VARCHAR(20) COMMENT '负责人',
- phone VARCHAR(11) COMMENT '手机',
- email VARCHAR(50) COMMENT '邮箱',
- status INTEGER NOT NULL COMMENT '部门状态(0停用 1正常)',
- del_flag BOOL NOT NULL COMMENT '删除标志(0删除 1存在)',
- parent_id INTEGER COMMENT '父部门ID',
- created_time DATETIME NOT NULL COMMENT '创建时间',
- updated_time DATETIME COMMENT '更新时间',
- PRIMARY KEY (id),
- FOREIGN KEY (parent_id) REFERENCES sys_dept (id) ON DELETE SET NULL
-);
+ `id` int NOT NULL AUTO_INCREMENT COMMENT '主键id',
+ `name` varchar(32) NOT NULL COMMENT '字典类型名称',
+ `code` varchar(32) NOT NULL COMMENT '字典类型编码',
+ `status` int NOT NULL COMMENT '状态(0停用 1正常)',
+ `remark` longtext COMMENT '备注',
+ `created_time` datetime NOT NULL COMMENT '创建时间',
+ `updated_time` datetime DEFAULT NULL COMMENT '更新时间',
+ PRIMARY KEY (`id`),
+ UNIQUE KEY `name` (`name`),
+ UNIQUE KEY `code` (`code`),
+ KEY `ix_sys_dict_type_id` (`id`)
+) ENGINE = InnoDB
+ DEFAULT CHARSET = utf8mb4
+ COLLATE = utf8mb4_0900_ai_ci;
-CREATE INDEX ix_sys_dept_id ON sys_dept (id);
-
-CREATE INDEX ix_sys_dept_parent_id ON sys_dept (parent_id);
-
-CREATE TABLE sys_dict_type
+-- sys_dict_data: table
+CREATE TABLE `sys_dict_data`
(
- id INTEGER NOT NULL AUTO_INCREMENT,
- name VARCHAR(32) NOT NULL COMMENT '字典类型名称',
- code VARCHAR(32) NOT NULL COMMENT '字典类型编码',
- status INTEGER NOT NULL COMMENT '状态(0停用 1正常)',
- remark LONGTEXT COMMENT '备注',
- created_time DATETIME NOT NULL COMMENT '创建时间',
- updated_time DATETIME COMMENT '更新时间',
- PRIMARY KEY (id),
- UNIQUE (code),
- UNIQUE (name)
-);
+ `id` int NOT NULL AUTO_INCREMENT COMMENT '主键id',
+ `label` varchar(32) NOT NULL COMMENT '字典标签',
+ `value` varchar(32) NOT NULL COMMENT '字典值',
+ `sort` int NOT NULL COMMENT '排序',
+ `status` int NOT NULL COMMENT '状态(0停用 1正常)',
+ `remark` longtext COMMENT '备注',
+ `type_id` int NOT NULL COMMENT '字典类型关联ID',
+ `created_time` datetime NOT NULL COMMENT '创建时间',
+ `updated_time` datetime DEFAULT NULL COMMENT '更新时间',
+ PRIMARY KEY (`id`),
+ UNIQUE KEY `label` (`label`),
+ UNIQUE KEY `value` (`value`),
+ KEY `type_id` (`type_id`),
+ KEY `ix_sys_dict_data_id` (`id`),
+ CONSTRAINT `sys_dict_data_ibfk_1` FOREIGN KEY (`type_id`) REFERENCES `sys_dict_type` (`id`)
+) ENGINE = InnoDB
+ DEFAULT CHARSET = utf8mb4
+ COLLATE = utf8mb4_0900_ai_ci;
-CREATE INDEX ix_sys_dict_type_id ON sys_dict_type (id);
-
-CREATE TABLE sys_login_log
+-- sys_login_log: table
+CREATE TABLE `sys_login_log`
(
- id INTEGER NOT NULL AUTO_INCREMENT,
- user_uuid VARCHAR(50) NOT NULL COMMENT '用户UUID',
- username VARCHAR(20) NOT NULL COMMENT '用户名',
- status INTEGER NOT NULL COMMENT '登录状态(0失败 1成功)',
- ip VARCHAR(50) NOT NULL COMMENT '登录IP地址',
- country VARCHAR(50) COMMENT '国家',
- region VARCHAR(50) COMMENT '地区',
- city VARCHAR(50) COMMENT '城市',
- user_agent VARCHAR(255) NOT NULL COMMENT '请求头',
- os VARCHAR(50) COMMENT '操作系统',
- browser VARCHAR(50) COMMENT '浏览器',
- device VARCHAR(50) COMMENT '设备',
- msg LONGTEXT NOT NULL COMMENT '提示消息',
- login_time DATETIME NOT NULL COMMENT '登录时间',
- created_time DATETIME NOT NULL COMMENT '创建时间',
- PRIMARY KEY (id)
-);
+ `id` int NOT NULL AUTO_INCREMENT COMMENT '主键id',
+ `user_uuid` varchar(50) NOT NULL COMMENT '用户UUID',
+ `username` varchar(20) NOT NULL COMMENT '用户名',
+ `status` int NOT NULL COMMENT '登录状态(0失败 1成功)',
+ `ip` varchar(50) NOT NULL COMMENT '登录IP地址',
+ `country` varchar(50) DEFAULT NULL COMMENT '国家',
+ `region` varchar(50) DEFAULT NULL COMMENT '地区',
+ `city` varchar(50) DEFAULT NULL COMMENT '城市',
+ `user_agent` varchar(255) NOT NULL COMMENT '请求头',
+ `os` varchar(50) DEFAULT NULL COMMENT '操作系统',
+ `browser` varchar(50) DEFAULT NULL COMMENT '浏览器',
+ `device` varchar(50) DEFAULT NULL COMMENT '设备',
+ `msg` longtext NOT NULL COMMENT '提示消息',
+ `login_time` datetime NOT NULL COMMENT '登录时间',
+ `created_time` datetime NOT NULL COMMENT '创建时间',
+ PRIMARY KEY (`id`),
+ KEY `ix_sys_login_log_id` (`id`)
+) ENGINE = InnoDB
+ DEFAULT CHARSET = utf8mb4
+ COLLATE = utf8mb4_0900_ai_ci;
-CREATE INDEX ix_sys_login_log_id ON sys_login_log (id);
-
-CREATE TABLE sys_menu
+-- sys_menu: table
+CREATE TABLE `sys_menu`
(
- id INTEGER NOT NULL AUTO_INCREMENT,
- title VARCHAR(50) NOT NULL COMMENT '菜单标题',
- name VARCHAR(50) NOT NULL COMMENT '菜单名称',
- level INTEGER NOT NULL COMMENT '菜单层级',
- sort INTEGER NOT NULL COMMENT '排序',
- icon VARCHAR(100) COMMENT '菜单图标',
- path VARCHAR(200) COMMENT '路由地址',
- menu_type INTEGER NOT NULL COMMENT '菜单类型(0目录 1菜单 2按钮)',
- component VARCHAR(255) COMMENT '组件路径',
- perms VARCHAR(100) COMMENT '权限标识',
- status INTEGER NOT NULL COMMENT '菜单状态(0停用 1正常)',
- `show` INTEGER NOT NULL COMMENT '是否显示(0否 1是)',
- cache INTEGER NOT NULL COMMENT '是否缓存(0否 1是)',
- remark LONGTEXT COMMENT '备注',
- parent_id INTEGER COMMENT '父菜单ID',
- created_time DATETIME NOT NULL COMMENT '创建时间',
- updated_time DATETIME COMMENT '更新时间',
- PRIMARY KEY (id),
- FOREIGN KEY (parent_id) REFERENCES sys_menu (id) ON DELETE SET NULL
-);
+ `id` int NOT NULL AUTO_INCREMENT COMMENT '主键id',
+ `title` varchar(50) NOT NULL COMMENT '菜单标题',
+ `name` varchar(50) NOT NULL COMMENT '菜单名称',
+ `level` int NOT NULL COMMENT '菜单层级',
+ `sort` int NOT NULL COMMENT '排序',
+ `icon` varchar(100) DEFAULT NULL COMMENT '菜单图标',
+ `path` varchar(200) DEFAULT NULL COMMENT '路由地址',
+ `menu_type` int NOT NULL COMMENT '菜单类型(0目录 1菜单 2按钮)',
+ `component` varchar(255) DEFAULT NULL COMMENT '组件路径',
+ `perms` varchar(100) DEFAULT NULL COMMENT '权限标识',
+ `status` int NOT NULL COMMENT '菜单状态(0停用 1正常)',
+ `show` int NOT NULL COMMENT '是否显示(0否 1是)',
+ `cache` int NOT NULL COMMENT '是否缓存(0否 1是)',
+ `remark` longtext COMMENT '备注',
+ `parent_id` int DEFAULT NULL COMMENT '父菜单ID',
+ `created_time` datetime NOT NULL COMMENT '创建时间',
+ `updated_time` datetime DEFAULT NULL COMMENT '更新时间',
+ PRIMARY KEY (`id`),
+ KEY `ix_sys_menu_id` (`id`),
+ KEY `ix_sys_menu_parent_id` (`parent_id`),
+ CONSTRAINT `sys_menu_ibfk_1` FOREIGN KEY (`parent_id`) REFERENCES `sys_menu` (`id`) ON DELETE SET NULL
+) ENGINE = InnoDB
+ DEFAULT CHARSET = utf8mb4
+ COLLATE = utf8mb4_0900_ai_ci;
-CREATE INDEX ix_sys_menu_id ON sys_menu (id);
-
-CREATE INDEX ix_sys_menu_parent_id ON sys_menu (parent_id);
-
-CREATE TABLE sys_opera_log
+-- sys_opera_log: table
+CREATE TABLE `sys_opera_log`
(
- id INTEGER NOT NULL AUTO_INCREMENT,
- username VARCHAR(20) COMMENT '用户名',
- method VARCHAR(20) NOT NULL COMMENT '请求类型',
- title VARCHAR(255) NOT NULL COMMENT '操作模块',
- path VARCHAR(500) NOT NULL COMMENT '请求路径',
- ip VARCHAR(50) NOT NULL COMMENT 'IP地址',
- country VARCHAR(50) COMMENT '国家',
- region VARCHAR(50) COMMENT '地区',
- city VARCHAR(50) COMMENT '城市',
- user_agent VARCHAR(255) NOT NULL COMMENT '请求头',
- os VARCHAR(50) COMMENT '操作系统',
- browser VARCHAR(50) COMMENT '浏览器',
- device VARCHAR(50) COMMENT '设备',
- args JSON COMMENT '请求参数',
- status INTEGER NOT NULL COMMENT '操作状态(0异常 1正常)',
- code VARCHAR(20) NOT NULL COMMENT '操作状态码',
- msg LONGTEXT COMMENT '提示消息',
- cost_time FLOAT NOT NULL COMMENT '请求耗时ms',
- opera_time DATETIME NOT NULL COMMENT '操作时间',
- created_time DATETIME NOT NULL COMMENT '创建时间',
- PRIMARY KEY (id)
-);
+ `id` int NOT NULL AUTO_INCREMENT COMMENT '主键id',
+ `username` varchar(20) DEFAULT NULL COMMENT '用户名',
+ `method` varchar(20) NOT NULL COMMENT '请求类型',
+ `title` varchar(255) NOT NULL COMMENT '操作模块',
+ `path` varchar(500) NOT NULL COMMENT '请求路径',
+ `ip` varchar(50) NOT NULL COMMENT 'IP地址',
+ `country` varchar(50) DEFAULT NULL COMMENT '国家',
+ `region` varchar(50) DEFAULT NULL COMMENT '地区',
+ `city` varchar(50) DEFAULT NULL COMMENT '城市',
+ `user_agent` varchar(255) NOT NULL COMMENT '请求头',
+ `os` varchar(50) DEFAULT NULL COMMENT '操作系统',
+ `browser` varchar(50) DEFAULT NULL COMMENT '浏览器',
+ `device` varchar(50) DEFAULT NULL COMMENT '设备',
+ `args` json DEFAULT NULL COMMENT '请求参数',
+ `status` int NOT NULL COMMENT '操作状态(0异常 1正常)',
+ `code` varchar(20) NOT NULL COMMENT '操作状态码',
+ `msg` longtext COMMENT '提示消息',
+ `cost_time` float NOT NULL COMMENT '请求耗时ms',
+ `opera_time` datetime NOT NULL COMMENT '操作时间',
+ `created_time` datetime NOT NULL COMMENT '创建时间',
+ PRIMARY KEY (`id`),
+ KEY `ix_sys_opera_log_id` (`id`)
+) ENGINE = InnoDB
+ DEFAULT CHARSET = utf8mb4
+ COLLATE = utf8mb4_0900_ai_ci;
-CREATE INDEX ix_sys_opera_log_id ON sys_opera_log (id);
-
-CREATE TABLE sys_role
+-- sys_role: table
+CREATE TABLE `sys_role`
(
- id INTEGER NOT NULL AUTO_INCREMENT,
- name VARCHAR(20) NOT NULL COMMENT '角色名称',
- data_scope INTEGER COMMENT '权限范围(1:全部数据权限 2:自定义数据权限)',
- status INTEGER NOT NULL COMMENT '角色状态(0停用 1正常)',
- remark LONGTEXT COMMENT '备注',
- created_time DATETIME NOT NULL COMMENT '创建时间',
- updated_time DATETIME COMMENT '更新时间',
- PRIMARY KEY (id),
- UNIQUE (name)
-);
+ `id` int NOT NULL AUTO_INCREMENT COMMENT '主键id',
+ `name` varchar(20) NOT NULL COMMENT '角色名称',
+ `data_scope` int DEFAULT NULL COMMENT '权限范围(1:全部数据权限 2:自定义数据权限)',
+ `status` int NOT NULL COMMENT '角色状态(0停用 1正常)',
+ `remark` longtext COMMENT '备注',
+ `created_time` datetime NOT NULL COMMENT '创建时间',
+ `updated_time` datetime DEFAULT NULL COMMENT '更新时间',
+ PRIMARY KEY (`id`),
+ UNIQUE KEY `name` (`name`),
+ KEY `ix_sys_role_id` (`id`)
+) ENGINE = InnoDB
+ DEFAULT CHARSET = utf8mb4
+ COLLATE = utf8mb4_0900_ai_ci;
-CREATE INDEX ix_sys_role_id ON sys_role (id);
-
-CREATE TABLE sys_dict_data
+-- sys_role_menu: table
+CREATE TABLE `sys_role_menu`
(
- id INTEGER NOT NULL AUTO_INCREMENT,
- label VARCHAR(32) NOT NULL COMMENT '字典标签',
- value VARCHAR(32) NOT NULL COMMENT '字典值',
- sort INTEGER NOT NULL COMMENT '排序',
- status INTEGER NOT NULL COMMENT '状态(0停用 1正常)',
- remark LONGTEXT COMMENT '备注',
- type_id INTEGER NOT NULL COMMENT '字典类型关联ID',
- created_time DATETIME NOT NULL COMMENT '创建时间',
- updated_time DATETIME COMMENT '更新时间',
- PRIMARY KEY (id),
- FOREIGN KEY (type_id) REFERENCES sys_dict_type (id),
- UNIQUE (label),
- UNIQUE (value)
-);
+ `id` int NOT NULL AUTO_INCREMENT COMMENT '主键ID',
+ `role_id` int NOT NULL COMMENT '角色ID',
+ `menu_id` int NOT NULL COMMENT '菜单ID',
+ PRIMARY KEY (`id`, `role_id`, `menu_id`),
+ UNIQUE KEY `ix_sys_role_menu_id` (`id`),
+ KEY `role_id` (`role_id`),
+ KEY `menu_id` (`menu_id`),
+ CONSTRAINT `sys_role_menu_ibfk_1` FOREIGN KEY (`role_id`) REFERENCES `sys_role` (`id`) ON DELETE CASCADE,
+ CONSTRAINT `sys_role_menu_ibfk_2` FOREIGN KEY (`menu_id`) REFERENCES `sys_menu` (`id`) ON DELETE CASCADE
+) ENGINE = InnoDB
+ DEFAULT CHARSET = utf8mb4
+ COLLATE = utf8mb4_0900_ai_ci;
-CREATE INDEX ix_sys_dict_data_id ON sys_dict_data (id);
-
-CREATE TABLE sys_role_menu
+-- sys_user: table
+CREATE TABLE `sys_user`
(
- id INTEGER NOT NULL COMMENT '主键ID' AUTO_INCREMENT,
- role_id INTEGER NOT NULL COMMENT '角色ID',
- menu_id INTEGER NOT NULL COMMENT '菜单ID',
- PRIMARY KEY (id, role_id, menu_id),
- FOREIGN KEY (menu_id) REFERENCES sys_menu (id) ON DELETE CASCADE,
- FOREIGN KEY (role_id) REFERENCES sys_role (id) ON DELETE CASCADE
-);
+ `id` int NOT NULL AUTO_INCREMENT COMMENT '主键id',
+ `uuid` varchar(50) NOT NULL,
+ `username` varchar(20) NOT NULL COMMENT '用户名',
+ `nickname` varchar(20) NOT NULL COMMENT '昵称',
+ `password` varchar(255) DEFAULT NULL COMMENT '密码',
+ `salt` varchar(5) DEFAULT NULL COMMENT '加密盐',
+ `email` varchar(50) NOT NULL COMMENT '邮箱',
+ `is_superuser` tinyint(1) NOT NULL COMMENT '超级权限(0否 1是)',
+ `is_staff` tinyint(1) NOT NULL COMMENT '后台管理登陆(0否 1是)',
+ `status` int NOT NULL COMMENT '用户账号状态(0停用 1正常)',
+ `is_multi_login` tinyint(1) NOT NULL COMMENT '是否重复登陆(0否 1是)',
+ `avatar` varchar(255) DEFAULT NULL COMMENT '头像',
+ `phone` varchar(11) DEFAULT NULL COMMENT '手机号',
+ `join_time` datetime NOT NULL COMMENT '注册时间',
+ `last_login_time` datetime DEFAULT NULL COMMENT '上次登录',
+ `dept_id` int DEFAULT NULL COMMENT '部门关联ID',
+ `created_time` datetime NOT NULL COMMENT '创建时间',
+ `updated_time` datetime DEFAULT NULL COMMENT '更新时间',
+ PRIMARY KEY (`id`),
+ UNIQUE KEY `uuid` (`uuid`),
+ UNIQUE KEY `nickname` (`nickname`),
+ UNIQUE KEY `ix_sys_user_username` (`username`),
+ UNIQUE KEY `ix_sys_user_email` (`email`),
+ KEY `dept_id` (`dept_id`),
+ KEY `ix_sys_user_id` (`id`),
+ CONSTRAINT `sys_user_ibfk_1` FOREIGN KEY (`dept_id`) REFERENCES `sys_dept` (`id`) ON DELETE SET NULL
+) ENGINE = InnoDB
+ DEFAULT CHARSET = utf8mb4
+ COLLATE = utf8mb4_0900_ai_ci;
-CREATE UNIQUE INDEX ix_sys_role_menu_id ON sys_role_menu (id);
-
-CREATE TABLE sys_user
+-- sys_user_role: table
+CREATE TABLE `sys_user_role`
(
- id INTEGER NOT NULL AUTO_INCREMENT,
- uuid VARCHAR(50) NOT NULL,
- username VARCHAR(20) NOT NULL COMMENT '用户名',
- nickname VARCHAR(20) NOT NULL COMMENT '昵称',
- password VARCHAR(255) NOT NULL COMMENT '密码',
- salt VARCHAR(5) NOT NULL COMMENT '加密盐',
- email VARCHAR(50) NOT NULL COMMENT '邮箱',
- is_superuser BOOL NOT NULL COMMENT '超级权限(0否 1是)',
- is_staff BOOL NOT NULL COMMENT '后台管理登陆(0否 1是)',
- status INTEGER NOT NULL COMMENT '用户账号状态(0停用 1正常)',
- is_multi_login BOOL NOT NULL COMMENT '是否重复登陆(0否 1是)',
- avatar VARCHAR(255) COMMENT '头像',
- phone VARCHAR(11) COMMENT '手机号',
- join_time DATETIME NOT NULL COMMENT '注册时间',
- last_login_time DATETIME COMMENT '上次登录',
- dept_id INTEGER COMMENT '部门关联ID',
- created_time DATETIME NOT NULL COMMENT '创建时间',
- updated_time DATETIME COMMENT '更新时间',
- PRIMARY KEY (id),
- FOREIGN KEY (dept_id) REFERENCES sys_dept (id) ON DELETE SET NULL,
- UNIQUE (nickname),
- UNIQUE (uuid)
-);
+ `id` int NOT NULL AUTO_INCREMENT COMMENT '主键ID',
+ `user_id` int NOT NULL COMMENT '用户ID',
+ `role_id` int NOT NULL COMMENT '角色ID',
+ PRIMARY KEY (`id`, `user_id`, `role_id`),
+ UNIQUE KEY `ix_sys_user_role_id` (`id`),
+ KEY `user_id` (`user_id`),
+ KEY `role_id` (`role_id`),
+ CONSTRAINT `sys_user_role_ibfk_1` FOREIGN KEY (`user_id`) REFERENCES `sys_user` (`id`) ON DELETE CASCADE,
+ CONSTRAINT `sys_user_role_ibfk_2` FOREIGN KEY (`role_id`) REFERENCES `sys_role` (`id`) ON DELETE CASCADE
+) ENGINE = InnoDB
+ DEFAULT CHARSET = utf8mb4
+ COLLATE = utf8mb4_0900_ai_ci;
-CREATE UNIQUE INDEX ix_sys_user_email ON sys_user (email);
-
-CREATE INDEX ix_sys_user_id ON sys_user (id);
-
-CREATE UNIQUE INDEX ix_sys_user_username ON sys_user (username);
-
-CREATE TABLE sys_user_role
+-- sys_user_social: table
+CREATE TABLE `sys_user_social`
(
- id INTEGER NOT NULL COMMENT '主键ID' AUTO_INCREMENT,
- user_id INTEGER NOT NULL COMMENT '用户ID',
- role_id INTEGER NOT NULL COMMENT '角色ID',
- PRIMARY KEY (id, user_id, role_id),
- FOREIGN KEY (role_id) REFERENCES sys_role (id) ON DELETE CASCADE,
- FOREIGN KEY (user_id) REFERENCES sys_user (id) ON DELETE CASCADE
-);
-
-CREATE UNIQUE INDEX ix_sys_user_role_id ON sys_user_role (id);
+ `id` int NOT NULL AUTO_INCREMENT COMMENT '主键id',
+ `source` varchar(20) NOT NULL COMMENT '第三方用户来源',
+ `open_id` varchar(20) DEFAULT NULL COMMENT '第三方用户的 open id',
+ `uid` varchar(20) DEFAULT NULL COMMENT '第三方用户的 ID',
+ `union_id` varchar(20) DEFAULT NULL COMMENT '第三方用户的 union id',
+ `scope` varchar(120) DEFAULT NULL COMMENT '第三方用户授予的权限',
+ `code` varchar(50) DEFAULT NULL COMMENT '用户的授权 code',
+ `user_id` int DEFAULT NULL COMMENT '用户关联ID',
+ `created_time` datetime NOT NULL COMMENT '创建时间',
+ `updated_time` datetime DEFAULT NULL COMMENT '更新时间',
+ PRIMARY KEY (`id`),
+ KEY `user_id` (`user_id`),
+ KEY `ix_sys_user_social_id` (`id`),
+ CONSTRAINT `sys_user_social_ibfk_1` FOREIGN KEY (`user_id`) REFERENCES `sys_user` (`id`) ON DELETE SET NULL
+) ENGINE = InnoDB
+ DEFAULT CHARSET = utf8mb4
+ COLLATE = utf8mb4_0900_ai_ci;
diff --git a/backend/app/static/ip2region.xdb b/backend/static/ip2region.xdb
similarity index 100%
rename from backend/app/static/ip2region.xdb
rename to backend/static/ip2region.xdb
diff --git a/backend/utils/__init__.py b/backend/utils/__init__.py
new file mode 100644
index 0000000..56fafa5
--- /dev/null
+++ b/backend/utils/__init__.py
@@ -0,0 +1,2 @@
+#!/usr/bin/env python3
+# -*- coding: utf-8 -*-
diff --git a/backend/app/utils/build_tree.py b/backend/utils/build_tree.py
similarity index 94%
rename from backend/app/utils/build_tree.py
rename to backend/utils/build_tree.py
index 53917ff..5037bef 100644
--- a/backend/app/utils/build_tree.py
+++ b/backend/utils/build_tree.py
@@ -4,8 +4,8 @@ from typing import Any, Sequence
from asgiref.sync import sync_to_async
-from backend.app.common.enums import BuildTreeType
-from backend.app.utils.serializers import RowData, select_list_serialize
+from backend.common.enums import BuildTreeType
+from backend.utils.serializers import RowData, select_list_serialize
async def get_tree_nodes(row: Sequence[RowData]) -> list[dict[str, Any]]:
diff --git a/backend/app/utils/demo_site.py b/backend/utils/demo_site.py
similarity index 82%
rename from backend/app/utils/demo_site.py
rename to backend/utils/demo_site.py
index 648a793..4792b13 100644
--- a/backend/app/utils/demo_site.py
+++ b/backend/utils/demo_site.py
@@ -2,8 +2,8 @@
# -*- coding: utf-8 -*-
from fastapi import Request
-from backend.app.common.exception import errors
-from backend.app.core.conf import settings
+from backend.common.exception import errors
+from backend.core.conf import settings
async def demo_site(request: Request):
diff --git a/backend/app/utils/encrypt.py b/backend/utils/encrypt.py
similarity index 98%
rename from backend/app/utils/encrypt.py
rename to backend/utils/encrypt.py
index d620d30..91f6db7 100644
--- a/backend/app/utils/encrypt.py
+++ b/backend/utils/encrypt.py
@@ -9,7 +9,7 @@ from cryptography.hazmat.primitives import padding
from cryptography.hazmat.primitives.ciphers import Cipher, algorithms, modes
from itsdangerous import URLSafeSerializer
-from backend.app.common.log import log
+from backend.common.log import log
class AESCipher:
diff --git a/backend/app/utils/health_check.py b/backend/utils/health_check.py
similarity index 95%
rename from backend/app/utils/health_check.py
rename to backend/utils/health_check.py
index 0d759cc..2ca4d7c 100644
--- a/backend/app/utils/health_check.py
+++ b/backend/utils/health_check.py
@@ -5,7 +5,7 @@ from math import ceil
from fastapi import FastAPI, Request, Response
from fastapi.routing import APIRoute
-from backend.app.common.exception import errors
+from backend.common.exception import errors
def ensure_unique_route_names(app: FastAPI) -> None:
diff --git a/backend/app/utils/openapi.py b/backend/utils/openapi.py
similarity index 100%
rename from backend/app/utils/openapi.py
rename to backend/utils/openapi.py
diff --git a/backend/app/utils/re_verify.py b/backend/utils/re_verify.py
similarity index 100%
rename from backend/app/utils/re_verify.py
rename to backend/utils/re_verify.py
diff --git a/backend/app/utils/redis_info.py b/backend/utils/redis_info.py
similarity index 90%
rename from backend/app/utils/redis_info.py
rename to backend/utils/redis_info.py
index 54442a8..9f012ef 100644
--- a/backend/app/utils/redis_info.py
+++ b/backend/utils/redis_info.py
@@ -1,7 +1,7 @@
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
-from backend.app.common.redis import redis_client
-from backend.app.utils.server_info import server_info
+from backend.database.db_redis import redis_client
+from backend.utils.server_info import server_info
class RedisInfo:
diff --git a/backend/app/utils/request_parse.py b/backend/utils/request_parse.py
similarity index 94%
rename from backend/app/utils/request_parse.py
rename to backend/utils/request_parse.py
index 2718e1c..3edc78b 100644
--- a/backend/app/utils/request_parse.py
+++ b/backend/utils/request_parse.py
@@ -7,10 +7,10 @@ from fastapi import Request
from user_agents import parse
from XdbSearchIP.xdbSearcher import XdbSearcher
-from backend.app.common.log import log
-from backend.app.common.redis import redis_client
-from backend.app.core.conf import settings
-from backend.app.core.path_conf import IP2REGION_XDB
+from backend.common.log import log
+from backend.core.conf import settings
+from backend.core.path_conf import IP2REGION_XDB
+from backend.database.db_redis import redis_client
@sync_to_async
diff --git a/backend/app/utils/serializers.py b/backend/utils/serializers.py
similarity index 100%
rename from backend/app/utils/serializers.py
rename to backend/utils/serializers.py
diff --git a/backend/app/utils/server_info.py b/backend/utils/server_info.py
similarity index 88%
rename from backend/app/utils/server_info.py
rename to backend/utils/server_info.py
index a505d51..62dd347 100644
--- a/backend/app/utils/server_info.py
+++ b/backend/utils/server_info.py
@@ -9,7 +9,7 @@ from typing import List
import psutil
-from backend.app.utils.timezone import timezone
+from backend.utils.timezone import timezone
class ServerInfo:
@@ -92,17 +92,15 @@ class ServerInfo:
disk_info = []
for disk in psutil.disk_partitions():
usage = psutil.disk_usage(disk.mountpoint)
- disk_info.append(
- {
- 'dir': disk.mountpoint,
- 'type': disk.fstype,
- 'device': disk.device,
- 'total': ServerInfo.format_bytes(usage.total),
- 'free': ServerInfo.format_bytes(usage.free),
- 'used': ServerInfo.format_bytes(usage.used),
- 'usage': f'{round(usage.percent, 2)} %',
- }
- )
+ disk_info.append({
+ 'dir': disk.mountpoint,
+ 'type': disk.fstype,
+ 'device': disk.device,
+ 'total': ServerInfo.format_bytes(usage.total),
+ 'free': ServerInfo.format_bytes(usage.free),
+ 'used': ServerInfo.format_bytes(usage.used),
+ 'usage': f'{round(usage.percent, 2)} %',
+ })
return disk_info
@staticmethod
diff --git a/backend/app/utils/timezone.py b/backend/utils/timezone.py
similarity index 95%
rename from backend/app/utils/timezone.py
rename to backend/utils/timezone.py
index 23dc956..e9f37a9 100644
--- a/backend/app/utils/timezone.py
+++ b/backend/utils/timezone.py
@@ -4,7 +4,7 @@ import zoneinfo
from datetime import datetime
-from backend.app.core.conf import settings
+from backend.core.conf import settings
class TimeZone:
diff --git a/deploy/backend/celery.conf b/deploy/backend/celery.conf
new file mode 100644
index 0000000..7b1039e
--- /dev/null
+++ b/deploy/backend/celery.conf
@@ -0,0 +1,29 @@
+[program:celery_worker]
+directory=/fba/backend
+command=/usr/local/bin/celery -A app.task.celery worker --loglevel=INFO
+user=root
+autostart=true
+autorestart=true
+startretries=5
+redirect_stderr=true
+stdout_logfile=/var/log/celery/fba_celery_worker.log
+
+[program:celery_beat]
+directory=/fba/backend
+command=/usr/local/bin/celery -A app.task.celery beat --loglevel=INFO
+user=root
+autostart=true
+autorestart=true
+startretries=5
+redirect_stderr=true
+stdout_logfile=/var/log/celery/fba_celery_beat.log
+
+[program:celery_flower]
+directory=/fba/backend
+command=/usr/local/bin/celery -A app.task.celery flower --port=8555 --basic-auth=admin:123456
+user=root
+autostart=true
+autorestart=true
+startretries=5
+redirect_stderr=true
+stdout_logfile=/var/log/celery/fba_celery_flower.log
diff --git a/deploy/docker-compose/.env.docker b/deploy/backend/docker-compose/.env.docker
similarity index 56%
rename from deploy/docker-compose/.env.docker
rename to deploy/backend/docker-compose/.env.docker
index c21edba..f235e50 100644
--- a/deploy/docker-compose/.env.docker
+++ b/deploy/backend/docker-compose/.env.docker
@@ -1,3 +1,3 @@
# Docker
-DOCKER_DB_MAP_PORT=13306
+DOCKER_MYSQL_MAP_PORT=13306
DOCKER_REDIS_MAP_PORT=16379
diff --git a/deploy/docker-compose/.env.server b/deploy/backend/docker-compose/.env.server
similarity index 79%
rename from deploy/docker-compose/.env.server
rename to deploy/backend/docker-compose/.env.server
index 7e208c7..2e79ff0 100644
--- a/deploy/docker-compose/.env.server
+++ b/deploy/backend/docker-compose/.env.server
@@ -1,19 +1,25 @@
# Env: dev、pro
ENVIRONMENT='dev'
# MySQL
-DB_HOST='fba_mysql'
-DB_PORT=3306
-DB_USER='root'
-DB_PASSWORD='123456'
+MYSQL_HOST='fba_mysql'
+MYSQL_PORT=3306
+MYSQL_USER='root'
+MYSQL_PASSWORD='123456'
# Redis
REDIS_HOST='fba_redis'
REDIS_PORT=6379
REDIS_PASSWORD=''
REDIS_DATABASE=0
+# Token
+TOKEN_SECRET_KEY='1VkVF75nsNABBjK_7-qz7GtzNy3AMvktc9TCPwKczCk'
+# Opera Log
+OPERA_LOG_ENCRYPT_SECRET_KEY='d77b25790a804c2b4a339dd0207941e4cefa5751935a33735bc73bb7071a005b'
+# Admin
+# OAuth2
+OAUTH2_GITHUB_CLIENT_ID='test'
+OAUTH2_GITHUB_CLIENT_SECRET='test'
+# Task
# Celery
-CELERY_REDIS_HOST='fba_redis'
-CELERY_REDIS_PORT=6379
-CELERY_REDIS_PASSWORD=''
CELERY_BROKER_REDIS_DATABASE=1
CELERY_BACKEND_REDIS_DATABASE=2
# Rabbitmq
@@ -21,10 +27,3 @@ RABBITMQ_HOST='fba_rabbitmq'
RABBITMQ_PORT=5672
RABBITMQ_USERNAME='guest'
RABBITMQ_PASSWORD='guest'
-# Token
-TOKEN_SECRET_KEY='1VkVF75nsNABBjK_7-qz7GtzNy3AMvktc9TCPwKczCk'
-# Opera Log
-OPERA_LOG_ENCRYPT_SECRET_KEY='d77b25790a804c2b4a339dd0207941e4cefa5751935a33735bc73bb7071a005b'
-# OAuth2
-OAUTH2_GITHUB_CLIENT_ID='test'
-OAUTH2_GITHUB_CLIENT_SECRET='test'
diff --git a/deploy/docker-compose/docker-compose.yml b/deploy/backend/docker-compose/docker-compose.yml
similarity index 85%
rename from deploy/docker-compose/docker-compose.yml
rename to deploy/backend/docker-compose/docker-compose.yml
index b3db8a3..623b6b5 100644
--- a/deploy/docker-compose/docker-compose.yml
+++ b/deploy/backend/docker-compose/docker-compose.yml
@@ -22,8 +22,8 @@ volumes:
services:
fba_server:
build:
- context: ../../
- dockerfile: backend.dockerfile
+ context: ../../../
+ dockerfile: backend/backend.dockerfile
container_name: fba_server
restart: always
depends_on:
@@ -40,13 +40,13 @@ services:
- |
wait-for-it -s fba_mysql:3306 -s fba_redis:6379 -t 300
mkdir -p /var/log/supervisor/
- supervisord -c /fba/deploy/supervisor.conf
+ supervisord -c /fba/deploy/backend/supervisor.conf
supervisorctl restart fastapi_server
fba_mysql:
image: mysql:8.0.29
ports:
- - "${DOCKER_DB_MAP_PORT:-3306}:3306"
+ - "${DOCKER_MYSQL_MAP_PORT:-3306}:3306"
container_name: fba_mysql
restart: always
environment:
@@ -88,7 +88,7 @@ services:
- fba_server
volumes:
- ../nginx.conf:/etc/nginx/nginx.conf:ro
- - fba_static:/www/fba_server/backend/app/static
+ - fba_static:/www/fba_server/backend/static
networks:
- fba_network
@@ -110,8 +110,10 @@ services:
fba_celery:
build:
- context: ../../
- dockerfile: celery.dockerfile
+ context: ../../../
+ dockerfile: backend/celery.dockerfile
+ ports:
+ - "8555:8555"
container_name: fba_celery
restart: always
depends_on:
@@ -124,6 +126,7 @@ services:
- |
wait-for-it -s fba_rabbitmq:5672 -t 300
mkdir -p /var/log/supervisor/
- supervisord -c /fba/deploy/supervisor.conf
+ supervisord -c /fba/deploy/backend/supervisor.conf
supervisorctl restart celery_worker
supervisorctl restart celery_beat
+ supervisorctl restart celery_flower
diff --git a/deploy/fastapi_server.conf b/deploy/backend/fastapi_server.conf
similarity index 67%
rename from deploy/fastapi_server.conf
rename to deploy/backend/fastapi_server.conf
index 76bd9ba..3178bef 100644
--- a/deploy/fastapi_server.conf
+++ b/deploy/backend/fastapi_server.conf
@@ -1,6 +1,6 @@
[program:fastapi_server]
directory=/fba
-command=/usr/local/bin/gunicorn -c /fba/deploy/gunicorn.conf.py main:app
+command=/usr/local/bin/gunicorn -c /fba/deploy/backend/gunicorn.conf.py main:app
user=root
autostart=true
autorestart=true
diff --git a/deploy/gunicorn.conf.py b/deploy/backend/gunicorn.conf.py
similarity index 70%
rename from deploy/gunicorn.conf.py
rename to deploy/backend/gunicorn.conf.py
index 3cd9ff1..db92135 100644
--- a/deploy/gunicorn.conf.py
+++ b/deploy/backend/gunicorn.conf.py
@@ -1,8 +1,8 @@
# 监听内网端口
-bind = '0.0.0.0:8001'
+bind = "0.0.0.0:8001"
# 工作目录
-chdir = '/fba/backend/app'
+chdir = "/fba/backend/"
# 并行工作进程数
workers = 1
@@ -22,25 +22,25 @@ timeout = 120
daemon = False
# 工作模式协程
-worker_class = 'uvicorn.workers.UvicornWorker'
+worker_class = "uvicorn.workers.UvicornWorker"
# 设置最大并发量
worker_connections = 2000
# 设置进程文件目录
-pidfile = '/fba/gunicorn.pid'
+pidfile = "/fba/gunicorn.pid"
# 设置访问日志和错误信息日志路径
-accesslog = '/var/log/fastapi_server/gunicorn_access.log'
-errorlog = '/var/log/fastapi_server/gunicorn_error.log'
+accesslog = "/var/log/fastapi_server/gunicorn_access.log"
+errorlog = "/var/log/fastapi_server/gunicorn_error.log"
# 设置这个值为true 才会把打印信息记录到错误日志里
capture_output = True
# 设置日志记录水平
-loglevel = 'debug'
+loglevel = "debug"
# python程序
-pythonpath = '/usr/local/lib/python3.10/site-packages'
+pythonpath = "/usr/local/lib/python3.10/site-packages"
# 启动 gunicorn -c gunicorn.conf.py main:app
diff --git a/deploy/nginx.conf b/deploy/backend/nginx.conf
similarity index 96%
rename from deploy/nginx.conf
rename to deploy/backend/nginx.conf
index aa8e481..6ca80e0 100644
--- a/deploy/nginx.conf
+++ b/deploy/backend/nginx.conf
@@ -52,7 +52,7 @@ http {
}
location /static/ {
- alias /www/fba_server/backend/app/static;
+ alias /www/fba_server/backend/static;
}
}
}
diff --git a/deploy/supervisor.conf b/deploy/backend/supervisor.conf
similarity index 100%
rename from deploy/supervisor.conf
rename to deploy/backend/supervisor.conf
diff --git a/deploy/celery.conf b/deploy/celery.conf
deleted file mode 100644
index ea68c06..0000000
--- a/deploy/celery.conf
+++ /dev/null
@@ -1,19 +0,0 @@
-[program:celery_worker]
-directory=/fba/backend/app
-command=/usr/local/bin/celery -A tasks worker --loglevel=INFO
-user=root
-autostart=true
-autorestart=true
-startretries=5
-redirect_stderr=true
-stdout_logfile=/var/log/celery/fba_celery_worker.log
-
-[program:celery_beat]
-directory=/fba/backend/app
-command=/usr/local/bin/celery -A tasks beat --loglevel=INFO
-user=root
-autostart=true
-autorestart=true
-startretries=5
-redirect_stderr=true
-stdout_logfile=/var/log/celery/fba_celery_beat.log