mirror of
https://github.com/fastapi-practices/fastapi_best_architecture.git
synced 2025-08-26 13:26:04 +08:00
Refactor the backend architecture (#299)
* define the basic architecture * Update script and deployment file locations * Update the route registration * Fix CI download dependencies * Updated ruff to 0.3.3 * Update app subdirectory naming * Update the model import * fix pre-commit pdm lock * Update the service directory naming * Add CRUD method documents * Fix the issue of circular import * Update the README document * Update the SQL statement for create tables * Update docker scripts and documentation * Fix docker scripts * Update the backend README.md * Add the security folder and move the redis client * Update the configuration item * Fix environment configuration reads * Update the default configuration * Updated README description * Updated the user registration API * Fix test cases * Update the celery configuration * Update and fix celery configuration * Updated the celery structure * Update celery tasks and api * Add celery flower * Update the import style * Update contributors
This commit is contained in:
2
.github/workflows/ci.yml
vendored
2
.github/workflows/ci.yml
vendored
@ -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
|
||||
|
12
.gitignore
vendored
12
.gitignore
vendored
@ -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/
|
||||
|
@ -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$
|
||||
|
130
README.md
130
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
|
||||
1. Enter the `backend` directory
|
||||
|
||||
```shell
|
||||
cd backend
|
||||
```
|
||||
|
||||
2. Install the 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
|
||||
3. Create a database `fba` with utf8mb4 encoding.
|
||||
4. Install and start Redis
|
||||
5. Create a `.env` file in the `backend` directory.
|
||||
|
||||
```shell
|
||||
cd backend/app/
|
||||
touch .env
|
||||
```
|
||||
|
||||
5. Copy `.env.example` to `.env`
|
||||
|
||||
```shell
|
||||
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
|
||||
|
||||
8. Start celery worker, beat and flower
|
||||
|
||||
```shell
|
||||
celery -A tasks worker --loglevel=INFO
|
||||
# Optional, if you don't need to use the scheduled task
|
||||
celery -A tasks beat --loglevel=INFO
|
||||
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. Execute the `backend/app/main.py` file to start the service
|
||||
10. Browser access: http://127.0.0.1:8000/api/v1/docs
|
||||
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
|
||||
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/app/
|
||||
```
|
||||
cd backend/
|
||||
|
||||
5. Execute the test command
|
||||
|
||||
```shell
|
||||
pytest -vs --disable-warnings
|
||||
```
|
||||
|
||||
@ -203,8 +206,9 @@ Execute unittests via pytest
|
||||
|
||||
## Contributors
|
||||
|
||||
<span style="margin: 0 5px;" ><a href="https://github.com/wu-clan" ><img src="https://images.weserv.nl/?url=avatars.githubusercontent.com/u/52145145?v=4&h=60&w=60&fit=cover&mask=circle&maxage=7d" /></a></span>
|
||||
<span style="margin: 0 5px;" ><a href="https://github.com/downdawn" ><img src="https://images.weserv.nl/?url=avatars.githubusercontent.com/u/41266749?v=4&h=60&w=60&fit=cover&mask=circle&maxage=7d" /></a></span>
|
||||
<a href="https://github.com/fastapi-practices/fastapi_best_architecture/graphs/contributors">
|
||||
<img src="https://contrib.rocks/image?repo=fastapi-practices/fastapi_best_architecture"/>
|
||||
</a>
|
||||
|
||||
## 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)
|
||||
|
@ -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,33 +81,32 @@ mvc 架构作为常规设计模式,在 python web 中也很常见,但是三
|
||||
|
||||
### 后端
|
||||
|
||||
1. 安装依赖项
|
||||
1. 进入 `backend` 目录
|
||||
|
||||
```shell
|
||||
cd backend
|
||||
```
|
||||
|
||||
2. 安装依赖包
|
||||
|
||||
```shell
|
||||
pip install -r requirements.txt
|
||||
```
|
||||
|
||||
2. 创建一个数据库 `fba`,选择 utf8mb4 编码
|
||||
3. 安装并启动 Redis
|
||||
4. 在 `backend/app/` 目录下创建一个 `.env` 文件
|
||||
3. 创建一个数据库 `fba`,选择 utf8mb4 编码
|
||||
4. 安装并启动 Redis
|
||||
5. 在 `backend` 目录下创建 `.env` 文件
|
||||
|
||||
```shell
|
||||
cd backend/app/
|
||||
touch .env
|
||||
```
|
||||
|
||||
5. 复制 `.env.example` 到 `.env`
|
||||
|
||||
```shell
|
||||
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
|
||||
|
||||
@ -112,50 +114,55 @@ mvc 架构作为常规设计模式,在 python web 中也很常见,但是三
|
||||
alembic upgrade head
|
||||
```
|
||||
|
||||
8. 启动 celery worker 和 beat
|
||||
8. 启动 celery worker, beat 和 flower
|
||||
|
||||
```shell
|
||||
celery -A tasks worker --loglevel=INFO
|
||||
# 可选,如果您不需要使用计划任务
|
||||
celery -A tasks beat --loglevel=INFO
|
||||
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. 执行 `backend/app/main.py` 文件启动服务
|
||||
10. 浏览器访问:http://127.0.0.1:8000/api/v1/docs
|
||||
|
||||
---
|
||||
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目录
|
||||
4. 进入 `backend` 目录,执行测试命令
|
||||
|
||||
```shell
|
||||
cd backend/app/
|
||||
```
|
||||
cd backend/
|
||||
|
||||
5. 执行测试命令
|
||||
|
||||
```shell
|
||||
pytest -vs --disable-warnings
|
||||
```
|
||||
|
||||
@ -196,8 +199,9 @@ mvc 架构作为常规设计模式,在 python web 中也很常见,但是三
|
||||
|
||||
## 贡献者
|
||||
|
||||
<span style="margin: 0 5px;" ><a href="https://github.com/wu-clan" ><img src="https://images.weserv.nl/?url=avatars.githubusercontent.com/u/52145145?v=4&h=60&w=60&fit=cover&mask=circle&maxage=7d" /></a></span>
|
||||
<span style="margin: 0 5px;" ><a href="https://github.com/downdawn" ><img src="https://images.weserv.nl/?url=avatars.githubusercontent.com/u/41266749?v=4&h=60&w=60&fit=cover&mask=circle&maxage=7d" /></a></span>
|
||||
<a href="https://github.com/fastapi-practices/fastapi_best_architecture/graphs/contributors">
|
||||
<img src="https://contrib.rocks/image?repo=fastapi-practices/fastapi_best_architecture"/>
|
||||
</a>
|
||||
|
||||
## 特别鸣谢
|
||||
|
||||
@ -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)
|
||||
|
4
backend/.dockerignore
Normal file
4
backend/.dockerignore
Normal file
@ -0,0 +1,4 @@
|
||||
__pycache__/
|
||||
venv/
|
||||
.venv/
|
||||
.pdm-python
|
@ -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'
|
12
backend/.gitignore
vendored
Normal file
12
backend/.gitignore
vendored
Normal file
@ -0,0 +1,12 @@
|
||||
__pycache__/
|
||||
.env
|
||||
venv/
|
||||
.venv/
|
||||
.mypy_cache/
|
||||
log/
|
||||
alembic/versions/
|
||||
static/media/
|
||||
.ruff_cache/
|
||||
.pytest_cache/
|
||||
.pdm-python
|
||||
celerybeat-schedule.*
|
@ -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
|
60
backend/README.md
Normal file
60
backend/README.md
Normal file
@ -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/<your username>/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.
|
@ -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)
|
||||
|
15
backend/app/admin/api/router.py
Normal file
15
backend/app/admin/api/router.py
Normal file
@ -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)
|
13
backend/app/admin/api/v1/auth/__init__.py
Normal file
13
backend/app/admin/api/v1/auth/__init__.py
Normal file
@ -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')
|
@ -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)
|
@ -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})
|
@ -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)
|
@ -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')
|
||||
|
@ -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()
|
||||
|
@ -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()
|
||||
|
11
backend/app/admin/api/v1/monitor/__init__.py
Normal file
11
backend/app/admin/api/v1/monitor/__init__.py
Normal file
@ -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')
|
@ -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')),
|
@ -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')),
|
@ -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')
|
||||
|
@ -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)
|
||||
|
||||
|
@ -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 规则才能真正拥有访问权限,适合配置全局接口访问策略<br>
|
||||
- 推荐添加基于角色的访问权限, 需配合添加 g 策略才能真正拥有访问权限,适合配置全局接口访问策略<br>
|
||||
**格式**: 角色 role + 访问路径 path + 访问方法 method
|
||||
|
||||
- 如果添加基于用户的访问权限, 不需配合添加 g 规则就能真正拥有权限,适合配置指定用户接口访问策略<br>
|
||||
- 如果添加基于用户的访问权限, 不需配合添加 g 策略就能真正拥有权限,适合配置指定用户接口访问策略<br>
|
||||
**格式**: 用户 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 规则中添加基于用户组的访问权限, 才能真正拥有访问权限<br>
|
||||
- 如果在 p 策略中添加了基于角色的访问权限, 则还需要在 g 策略中添加基于用户组的访问权限, 才能真正拥有访问权限<br>
|
||||
**格式**: 用户 uuid + 角色 role
|
||||
|
||||
- 如果在 p 策略中添加了基于用户的访问权限, 则不添加相应的 g 规则能直接拥有访问权限<br>
|
||||
但是拥有的不是用户角色的所有权限, 而只是单一的对应的 p 规则所添加的访问权限
|
||||
- 如果在 p 策略中添加了基于用户的访问权限, 则不添加相应的 g 策略能直接拥有访问权限<br>
|
||||
但是拥有的不是用户角色的所有权限, 而只是单一的对应的 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,
|
@ -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()
|
||||
|
@ -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()
|
||||
|
@ -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()
|
||||
|
@ -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)
|
||||
|
@ -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()
|
||||
|
@ -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()
|
33
backend/app/admin/conf.py
Normal file
33
backend/app/admin/conf.py
Normal file
@ -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()
|
@ -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
|
||||
|
@ -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
|
||||
|
@ -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)
|
||||
)
|
@ -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()
|
@ -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
|
||||
|
@ -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
|
||||
|
@ -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)
|
||||
)
|
@ -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
|
||||
|
@ -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
|
||||
|
@ -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)
|
@ -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)
|
||||
|
||||
|
14
backend/app/admin/model/__init__.py
Normal file
14
backend/app/admin/model/__init__.py
Normal file
@ -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
|
@ -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):
|
@ -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):
|
@ -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):
|
@ -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):
|
@ -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):
|
@ -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):
|
@ -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):
|
@ -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):
|
@ -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):
|
@ -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',
|
@ -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):
|
@ -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',
|
@ -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):
|
@ -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):
|
@ -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):
|
@ -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):
|
@ -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):
|
@ -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):
|
@ -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):
|
@ -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):
|
@ -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):
|
@ -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):
|
@ -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):
|
@ -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):
|
@ -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):
|
@ -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()
|
@ -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()
|
@ -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()
|
@ -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()
|
@ -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()
|
@ -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()
|
@ -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()
|
@ -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()
|
@ -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()
|
@ -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()
|
@ -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()
|
@ -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()
|
11
backend/app/admin/tests/api_v1/test_auth.py
Normal file
11
backend/app/admin/tests/api_v1/test_auth.py
Normal file
@ -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
|
@ -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
|
||||
|
25
backend/app/admin/tests/utils/db_mysql.py
Normal file
25
backend/app/admin/tests/utils/db_mysql.py
Normal file
@ -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()
|
@ -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}'}
|
@ -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=['任务管理'])
|
@ -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)
|
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user