Compare commits

..

No commits in common. "master" and "aio-dev" have entirely different histories.

25 changed files with 153 additions and 2924 deletions

View File

@ -1,36 +0,0 @@
name: Gitea Action for docker build
run-name: ${{ gitea.actor }} is testing out Gitea Actions 🚀
on:
push:
branches: [master]
paths-ignore:
- 'requirements.txt'
- '*RuntimeDockerfile'
- '.gitignore'
- 'README.md'
- 'pyproject.toml'
- '.gitea/workflows/**'
jobs:
build-app:
runs-on: ubuntu-latest
steps:
- run: echo "This job is now running on a ${{ runner.os }} server hosted by Gitea!"
- name: Check out repository code
uses: https://gitea.com/actions/checkout@v4
- name: Login to hub.airpig.cn
uses: https://gitea.com/docker/login-action@v3
with:
registry: hub.airpig.cn
username: admin
password: Chenweijia113!
- run: |
var=${{ gitea.repository }}
repo=${var##*/}
version=$(grep -oP '(?<=version = ")(.*)(?=")' "pyproject.toml")
new_version="v${version}"
docker build -t hub.airpig.cn/library/$repo:$new_version .
docker push hub.airpig.cn/library/$repo:$new_version
docker rmi hub.airpig.cn/library/$repo:$new_version
- run: echo "This job's status is ${{ job.status }}."

View File

@ -1,32 +0,0 @@
name: Gitea Action for docker build runtime
run-name: ${{ gitea.actor }} is testing out Gitea Actions 🚀
on:
push:
branches: [master]
paths:
- 'requirements.txt'
- 'poetry.lock'
- '*RuntimeDockerfile'
jobs:
build-runtime:
runs-on: ubuntu-latest
steps:
- run: echo "This job is now running on a ${{ runner.os }} server hosted by Gitea!"
- name: Check out repository code
uses: https://gitea.com/actions/checkout@v4
- name: Login to hub.airpig.cn
uses: https://gitea.com/docker/login-action@v3
with:
registry: hub.airpig.cn
username: admin
password: Chenweijia113!
- run: |
var=${{ gitea.repository }}
repo=${var##*/}"-runtime"
version=$(grep -oP '(?<=version = ")(.*)(?=")' "pyproject.toml")
new_version="v${version}"
docker build -t hub.airpig.cn/library/$repo:$new_version -f AlpineRuntimeDockerfile .
docker push hub.airpig.cn/library/$repo:$new_version
docker rmi hub.airpig.cn/library/$repo:$new_version
- run: echo "This job's status is ${{ job.status }}."

View File

@ -1,6 +1,6 @@
variables:
PROJECT_NAME: fastapi_app_template
DOCKER_IMAGE_DOMAIN: hub.airpig.cn
DOCKER_IMAGE_DOMAIN: 192.168.2.237:8088
LATEST_VERSION: latest
K8S_NS: default
DEPLOYMENT_NAME: fastapi-app-template

View File

@ -1,30 +0,0 @@
FROM python:3.11-alpine as builder
RUN pip install poetry -i https://mirrors.aliyun.com/pypi/simple/
WORKDIR /venv
COPY poetry.lock pyproject.toml /venv/
RUN poetry config virtualenvs.options.no-pip true \
&& poetry config virtualenvs.options.no-setuptools true \
&& poetry config virtualenvs.in-project true \
&& poetry install
FROM python:3.11-alpine as release
COPY --from=builder /venv /venv
ENV PATH="/venv/.venv/bin:${PATH}"
RUN chmod a+x /venv/.venv/bin/activate \
&& source /venv/.venv/bin/activate
WORKDIR /app
COPY . /app
ENV FAST_API_ENV=prod
CMD ["/usr/local/bin/uvicorn", "main:fast_api_app", "--reload", "--host", "0.0.0.0", "--port", "80"]

View File

@ -1,10 +1,29 @@
FROM busybox
# 安装依赖阶段
FROM python:3.8.6-slim as build
RUN mkdir -p /data /app
RUN mkdir /install
WORKDIR /data
WORKDIR /install
COPY . /data
COPY requirements.txt .
ENTRYPOINT [ "/bin/cp", "-r", "/data/*", "/app"]
RUN sed -i s@/deb.debian.org/@/mirrors.aliyun.com/@g /etc/apt/sources.list
RUN apt-get update \
&& apt-get install gcc -y \
&& apt-get clean
RUN pip install -r requirements.txt -i https://mirrors.aliyun.com/pypi/simple/ --prefix=/install
# 应用启动
FROM python:3.8.6-slim
COPY --from=build /install /usr/local
COPY . /app
WORKDIR /app
ENV FAST_API_ENV=dev
CMD ["/usr/local/bin/uvicorn", "main:fast_api_app", "--reload", "--host", "0.0.0.0", "--port", "21021"]

35
Pipfile
View File

@ -1,35 +0,0 @@
[[source]]
url = "http://mirrors.cloud.aliyuncs.com/pypi/simple/"
verify_ssl = false
name = "pip_conf_index_global"
[packages]
aiofiles = "==22.1.0"
aioredis = "==2.0.1"
aiomysql = "==0.1.1"
bcrypt = "==4.0.1"
email-validator = ">=2.0.0"
fastapi = "==0.111.0"
fastapi-plugins = "==0.13.0"
fastapi-sqlalchemy = "==0.2.1"
pydantic = ">=2.0.0"
pydantic-settings = "==2.2.1"
python-multipart = ">=0.0.7"
pytest = "==7.2.1"
requests = "==2.28.2"
sqlacodegen = "==2.3.0"
sqlalchemy = "==2.0.1"
uvicorn = "==0.20.0"
pyjwt = "==2.6.0"
passlib = "==1.7.4"
pillow = "==9.4.0"
captcha = "==0.4"
jinja2 = "==3.1.2"
pycryptodome = "==3.17"
qiniu = "==7.10.0"
pytz = "==2022.7.1"
[dev-packages]
[requires]
python_version = "3.10"

View File

@ -1,7 +1,7 @@
# FastAPI App 文档
## 0 开发说明
Python(3.10+) and pip(20.2.4+)
Python(3.8.6+) and pip(20.2.4+)
### 开发命名规范
> * 避免采用的名字
> 不要使用字符l小写字母elO大写字母ohI大写字母eye作为单字符变量名。
@ -32,12 +32,9 @@ database=test
## 2 开发环境下安装依赖和运行项目
``` bash
pip install poetry -i https://pypi.tuna.tsinghua.edu.cn/simple/ \
&& poetry source add --priority=primary mirrors https://pypi.tuna.tsinghua.edu.cn/simple/ \
&& poetry config virtualenvs.path /install \
&& poetry install
pip install -r requirements.txt
./start.bat (windows)
./start.sh (mac/linux)
./start.sh (mac)
```
@ -58,31 +55,22 @@ sqlacodegen.exe --tables permission_info --outfile .\Desktop\fastapi_app\models\
logs *日志文件目录
src *源码目录
|--- api *接口目录
|--- service *逻辑目录
|--- biz *逻辑目录
|--- dtos *接口参数和返回值目录
|--- models *数据model目录
|--- middleware *中间件目录
|--- router *路由目录 ----- TODO
|--- utils *工具类目录
|--- captcha *验证码
|--- common *常规
|--- exception *异常处理
|--- file_upload *文件上传
|--- captcha_tools *验证码
|--- common_tools *常规
|--- exception_tools *异常处理
|--- file_upload_tools *文件上传
|--- qiniu_tools *七牛
|--- sms *短信
|--- sms_tools *短信
static *静态文件目录
test *测试目录
.gitignore *git忽略文件
.gitlab-ci.yml * CI/CD文件
main.py *入口文件
config.py *配置入口文件
Dockerfile *dockerfile容器代码
RuntimeDockerfile *dockerfile容器运行环境
requirements.txt * pip依赖包安装文件
pyproject.toml * poetry依赖包安装文件
poetry.lock * poetry依赖包安装文件
Pipfile * pipenv安装文件
Pipfile.lock * pipenv安装文件
README.md * 项目说明文件
Dockerfile *dockerfile容器文件
requirements.txt *依赖包安装文件
start.bat *Windows开发环境下启动文件
start.sh *Unix开发环境下启动文件

View File

@ -1,42 +0,0 @@
# 安装依赖阶段
FROM python:3.11-slim as build
RUN mkdir /install
WORKDIR /install
# RUN sed -i s@/deb.debian.org/@/mirrors.aliyun.com/@g /etc/apt/sources.list.d/debian.sources
# RUN apt-get update \
# && apt-get install gcc -y \
# && apt-get clean
# COPY requirements.txt .
# RUN pip install -r requirements.txt -i https://mirrors.aliyun.com/pypi/simple/ --prefix=/install
COPY poetry.lock .
COPY pyproject.toml .
RUN pip install poetry -i https://mirrors.aliyun.com/pypi/simple/ \
&& poetry source add --priority=primary mirrors https://pypi.tuna.tsinghua.edu.cn/simple/ \
&& poetry config virtualenvs.path /install \
&& poetry install
# 应用启动
FROM python:3.11-slim
# COPY --from=build /install /usr/local/
COPY --from=build /install/*/lib/python3.11/ /usr/local/lib/python3.11/
COPY --from=build /install/*/bin/ /usr/local/bin/
# 删除不需要的文件和工具,精简镜像
RUN apt-get purge -y --auto-remove \
&& rm -rf /var/lib/apt/lists/* \
&& rm -rf /usr/share/doc \
&& rm -rf /usr/share/man \
&& rm -rf /usr/local/bin/pip
RUN mkdir -p /app
WORKDIR /app
# COPY . /app
ENV FAST_API_ENV=prod
CMD ["/usr/local/bin/uvicorn", "main:fast_api_app", "--reload", "--host", "0.0.0.0", "--port", "80"]

View File

@ -5,17 +5,16 @@ lifetime_seconds=3600
[mysql]
username=root
password=123456
host=127.0.0.1
host=192.168.2.94
port=3306
database=test
database=admgs_v2
[redis]
redis_host=127.0.0.1
redis_host=192.168.2.94
redis_port=6379
redis_password=
[rabbitmq]
rabbitmq_host=rabbitmq
rabbitmq_host=192.168.2.94
rabbitmq_user=root
rabbitmq_password=123123

View File

@ -4,18 +4,17 @@ lifetime_seconds=3600
[mysql]
username=root
password=Chenweijia113!
host=mysql
password=123456
host=192.168.2.94
port=3306
database=test
database=admgs_v2
[redis]
redis_host=redis
redis_host=192.168.2.94
redis_port=6379
redis_password=Chenweijia113!
[rabbitmq]
rabbitmq_host=rabbitmq
rabbitmq_host=192.168.2.94
rabbitmq_user=root
rabbitmq_password=123123

View File

@ -3,7 +3,7 @@ from configparser import ConfigParser
from typing import Optional
from fastapi_plugins import RedisSettings
from pydantic_settings import BaseSettings
from pydantic import BaseSettings
class ReConfigParser(ConfigParser):
@ -34,6 +34,7 @@ class MySQLConfig(BaseSettings):
class RedisConfig(RedisSettings):
redis_url: str = None
redis_host: Optional[str] = 'localhost'
redis_port: Optional[int] = 6379
redis_password: str = None
@ -59,7 +60,7 @@ def init_config():
# common_config = CommonConfig(**dict(config.items('common')))
mysql_config = MySQLConfig(**dict(config.items('mysql')))
redis_config = RedisConfig(**dict(config.items('redis')))
# rabbitmq_config = RabbitmqConfig(**dict(config.items("rabbitmq")))
rabbitmq_config = RabbitmqConfig(**dict(config.items("rabbitmq")))
# return common_config, mysql_config, redis_config, rabbitmq_config
return mysql_config, redis_config
except Exception as e:

50
main.py
View File

@ -1,26 +1,23 @@
import logging.config as logging_config
import os
from contextlib import asynccontextmanager
import fastapi_plugins
from fastapi import FastAPI, Request
from fastapi.exceptions import HTTPException, RequestValidationError
# from fastapi.middleware.wsgi import WSGIMiddleware
from fastapi.middleware.wsgi import WSGIMiddleware
from fastapi_sqlalchemy import DBSessionMiddleware
from authx.exceptions import AuthXException
from config import init_config
# from src.middleware.flask import flask_app
from src.utils.exception import http_exception_handler, request_validation_error_handler, authx_exception_handler
# 请求限制
import redis.asyncio as aio_redis
from fastapi_limiter import FastAPILimiter
from src.utils.exception import (http_exception_handler,
request_validation_error_handler)
def create_app():
mysql_config, redis_config = init_config()
app = FastAPI()
@asynccontextmanager
async def lifespan(app: FastAPI):
@app.on_event("startup")
async def startup_event():
# 创建日志文件夹和临时文件上传文件夹
if not os.path.exists("files"):
os.mkdir("files")
@ -30,34 +27,31 @@ def create_app():
os.mkdir("logs")
logging_config.fileConfig('conf/log.ini')
# 初始化配置文件
# Redis 缓存初始化
await fastapi_plugins.redis_plugin.init_app(app, redis_config)
await fastapi_plugins.redis_plugin.init()
# 请求限制
await FastAPILimiter.init(redis=aio_redis.from_url("redis://localhost:6379", encoding="utf8"))
yield
# 应用关闭时关闭redis连接
await fastapi_plugins.redis_plugin.terminate()
await FastAPILimiter.close()
app = FastAPI(lifespan=lifespan)
mysql_config, redis_config = init_config()
# 添加sqlalchemy数据库中间件
# once the middleware is applied, any route can then access the database session from the global ``db``
app.add_middleware(DBSessionMiddleware, db_url=mysql_config.sqlalchemy_db_uri)
# Redis 缓存初始化
await fastapi_plugins.redis_plugin.init_app(app, redis_config)
await fastapi_plugins.redis_plugin.init()
@app.on_event("shutdown")
async def shutdown_event():
await fastapi_plugins.redis_plugin.terminate()
# 添加异常处理
app.add_exception_handler(HTTPException, http_exception_handler)
app.add_exception_handler(RequestValidationError, request_validation_error_handler)
# AuthX异常处理
app.add_exception_handler(AuthXException, authx_exception_handler)
# 可以在这里挂载Flask的应用复用之前项目的相关代码
# app.mount("/v1", WSGIMiddleware(flask_app))
# 在这里添加API route
from src.api import example,auth_example
app.include_router(example.router, tags=["API示例"], prefix="/v1/example")
app.include_router(auth_example.router, tags=["认证示例"], prefix="/v1/auth_example")
from src.api import example
app.include_router(example.router, tags=["API示例"], prefix="/example")
return app

2478
poetry.lock generated

File diff suppressed because it is too large Load Diff

View File

@ -1,34 +0,0 @@
[tool.poetry]
name = "fastapi-template"
version = "0.1.5"
description = ""
authors = ["chenwj113 <chenwj113@gmail.com>"]
license = "MIT"
readme = "README.md"
[tool.poetry.dependencies]
python = "^3.10"
fastapi = "0.111.0"
sqlalchemy = "2.0.0"
aioredis = "2.0.1"
aiomysql = "0.1.1"
fastapi-plugins = "0.13.0"
fastapi-sqlalchemy = "^0.2.1"
passlib = "^1.7.4"
pytz = "^2024.1"
qiniu = "^7.13.2"
pillow = "^10.4.0"
captcha = "^0.6.0"
fastapi-limiter = "^0.1.6"
authx = "^1.3.0"
itsdangerous = "^2.2.0"
[[tool.poetry.source]]
name = "mirrors"
url = "https://pypi.tuna.tsinghua.edu.cn/simple/"
priority = "primary"
[build-system]
requires = ["poetry-core"]
build-backend = "poetry.core.masonry.api"

View File

@ -1,75 +1,24 @@
--index-url https://pypi.tuna.tsinghua.edu.cn/simple
aiojobs==1.2.1 ; python_version >= "3.10" and python_version < "4.0"
aiomysql==0.1.1 ; python_version >= "3.10" and python_version < "4.0"
aioredis==2.0.1 ; python_version >= "3.10" and python_version < "4.0"
annotated-types==0.7.0 ; python_version >= "3.10" and python_version < "4.0"
anyio==4.4.0 ; python_version >= "3.10" and python_version < "4.0"
async-timeout==4.0.3 ; python_version >= "3.10" and python_version < "4.0"
authx==1.3.0 ; python_version >= "3.10" and python_version < "4.0"
captcha==0.6.0 ; python_version >= "3.10" and python_version < "4.0"
certifi==2024.6.2 ; python_version >= "3.10" and python_version < "4.0"
cffi==1.17.0 ; python_version >= "3.10" and python_version < "4.0" and platform_python_implementation != "PyPy"
charset-normalizer==3.3.2 ; python_version >= "3.10" and python_version < "4.0"
click==8.1.7 ; python_version >= "3.10" and python_version < "4.0"
colorama==0.4.6 ; python_version >= "3.10" and python_version < "4.0" and (sys_platform == "win32" or platform_system == "Windows")
cryptography==43.0.0 ; python_version >= "3.10" and python_version < "4.0"
dnspython==2.6.1 ; python_version >= "3.10" and python_version < "4.0"
ecdsa==0.19.0 ; python_version >= "3.10" and python_version < "4.0"
email-validator==2.1.1 ; python_version >= "3.10" and python_version < "4.0"
exceptiongroup==1.2.1 ; python_version >= "3.10" and python_version < "3.11"
fastapi-cli==0.0.4 ; python_version >= "3.10" and python_version < "4.0"
fastapi-limiter==0.1.6 ; python_version >= "3.10" and python_version < "4.0"
fastapi-plugins==0.13.0 ; python_version >= "3.10" and python_version < "4.0"
fastapi-sqlalchemy==0.2.1 ; python_version >= "3.10" and python_version < "4.0"
fastapi==0.111.0 ; python_version >= "3.10" and python_version < "4.0"
greenlet==3.0.3 ; python_version >= "3.10" and python_version < "4.0" and (platform_machine == "aarch64" or platform_machine == "ppc64le" or platform_machine == "x86_64" or platform_machine == "amd64" or platform_machine == "AMD64" or platform_machine == "win32" or platform_machine == "WIN32")
h11==0.14.0 ; python_version >= "3.10" and python_version < "4.0"
hiredis==2.3.2 ; python_version >= "3.10" and python_version < "4.0"
httpcore==1.0.5 ; python_version >= "3.10" and python_version < "4.0"
httptools==0.6.1 ; python_version >= "3.10" and python_version < "4.0"
httpx==0.27.0 ; python_version >= "3.10" and python_version < "4.0"
idna==3.7 ; python_version >= "3.10" and python_version < "4.0"
itsdangerous==2.2.0 ; python_version >= "3.10" and python_version < "4.0"
jinja2==3.1.4 ; python_version >= "3.10" and python_version < "4.0"
markdown-it-py==3.0.0 ; python_version >= "3.10" and python_version < "4.0"
markupsafe==2.1.5 ; python_version >= "3.10" and python_version < "4.0"
mdurl==0.1.2 ; python_version >= "3.10" and python_version < "4.0"
orjson==3.10.4 ; python_version >= "3.10" and python_version < "4.0"
passlib==1.7.4 ; python_version >= "3.10" and python_version < "4.0"
pillow==10.4.0 ; python_version >= "3.10" and python_version < "4.0"
pyasn1==0.6.0 ; python_version >= "3.10" and python_version < "4.0"
pycparser==2.22 ; python_version >= "3.10" and python_version < "4.0" and platform_python_implementation != "PyPy"
pydantic-core==2.18.4 ; python_version >= "3.10" and python_version < "4.0"
pydantic-settings==2.3.1 ; python_version >= "3.10" and python_version < "4.0"
pydantic==2.7.3 ; python_version >= "3.10" and python_version < "4.0"
pygments==2.18.0 ; python_version >= "3.10" and python_version < "4.0"
pyjwt[crypto]==2.9.0 ; python_version >= "3.10" and python_version < "4.0"
pymysql==1.1.1 ; python_version >= "3.10" and python_version < "4.0"
python-dateutil==2.9.0.post0 ; python_version >= "3.10" and python_version < "4.0"
python-dotenv==1.0.1 ; python_version >= "3.10" and python_version < "4.0"
python-jose==3.3.0 ; python_version >= "3.10" and python_version < "4.0"
python-json-logger==2.0.7 ; python_version >= "3.10" and python_version < "4.0"
python-multipart==0.0.9 ; python_version >= "3.10" and python_version < "4.0"
pytz==2024.1 ; python_version >= "3.10" and python_version < "4.0"
pyyaml==6.0.1 ; python_version >= "3.10" and python_version < "4.0"
qiniu==7.13.2 ; python_version >= "3.10" and python_version < "4.0"
redis==5.0.5 ; python_version >= "3.10" and python_version < "4.0"
redis[hiredis]==5.0.5 ; python_version >= "3.10" and python_version < "4.0"
requests==2.32.3 ; python_version >= "3.10" and python_version < "4.0"
rich==13.7.1 ; python_version >= "3.10" and python_version < "4.0"
rsa==4.9 ; python_version >= "3.10" and python_version < "4"
shellingham==1.5.4 ; python_version >= "3.10" and python_version < "4.0"
six==1.16.0 ; python_version >= "3.10" and python_version < "4.0"
sniffio==1.3.1 ; python_version >= "3.10" and python_version < "4.0"
sqlalchemy==2.0.0 ; python_version >= "3.10" and python_version < "4.0"
starlette==0.37.2 ; python_version >= "3.10" and python_version < "4.0"
tenacity==8.3.0 ; python_version >= "3.10" and python_version < "4.0"
typer==0.12.3 ; python_version >= "3.10" and python_version < "4.0"
typing-extensions==4.12.2 ; python_version >= "3.10" and python_version < "4.0"
ujson==5.10.0 ; python_version >= "3.10" and python_version < "4.0"
urllib3==2.2.2 ; python_version >= "3.10" and python_version < "4.0"
uvicorn[standard]==0.30.1 ; python_version >= "3.10" and python_version < "4.0"
uvloop==0.19.0 ; (sys_platform != "win32" and sys_platform != "cygwin") and platform_python_implementation != "PyPy" and python_version >= "3.10" and python_version < "4.0"
watchfiles==0.22.0 ; python_version >= "3.10" and python_version < "4.0"
websockets==12.0 ; python_version >= "3.10" and python_version < "4.0"
aiofiles==22.1.0
aioredis==2.0.1
aiomysql==0.1.1
bcrypt==4.0.1
email-validator==1.3.1
fastapi==0.89.1
fastapi-plugins==0.11.0
FastAPI-SQLAlchemy==0.2.1
pydantic==1.10.4
# pydantic-sqlalchemy==0.0.9
python-multipart==0.0.5
pytest==7.2.1
requests==2.28.2
sqlacodegen==2.3.0
SQLAlchemy==2.0.1
uvicorn==0.20.0
PyJWT==2.6.0
passlib==1.7.4
Pillow==9.4.0
captcha==0.4
jinja2==3.1.2
pycryptodome==3.17
qiniu==7.10.0
pytz==2022.7.1

View File

@ -1,16 +0,0 @@
from fastapi import APIRouter, HTTPException, Depends
# 校验授权token
from src.middleware.auth import security
router = APIRouter()
@router.get('/', dependencies=[Depends(security.access_token_required)])
def index():
return {"message": "Hello World"}
@router.get('/login')
def login(username: str, password: str):
if username == "test" and password == "test":
token = security.create_access_token(uid=username)
return {"access_token": token}
raise HTTPException(401, detail={"message": "Bad credentials"})

View File

@ -5,20 +5,16 @@ import aioredis
from fastapi import APIRouter, Depends, Query
from fastapi.security import OAuth2PasswordBearer
from fastapi_plugins import depends_redis
from fastapi_sqlalchemy import db
from sqlalchemy.sql import text
from fastapi_limiter.depends import RateLimiter
from pydantic import BaseModel
from src.service.example import get_user_by_id
from src.biz.example import get_user_by_id
from src.dtos import response
from src.dtos.example import UserExampleListPagesResult
router = APIRouter()
# 并发数限制 每5秒最多100次请求
@router.get("/", dependencies=[Depends(RateLimiter(times=100, seconds=5))])
@router.get("/")
def index():
return {"msg": "This is Index Page"}
@ -63,11 +59,6 @@ async def get_user_list_pages(page: int = Query(..., description="当前页码")
res = response(data=data, message="Ok!")
return res
# 数据库查询
@router.get('/get_db_version')
async def get_db_version():
result = db.session.execute(text("SELECT version()")).first()
return dict(result=result.tuple()[0])
# Redis 缓存查询
@router.get("/ping")

View File

@ -1,4 +1,4 @@
from src.service import execute_sql
from src.biz import execute_sql
def get_user_by_id(user_id):

View File

@ -52,9 +52,9 @@ class SendCaptchaSuccess(BaseResponse):
class BadRequestError(ErrorModel):
code: int = 400
message: str = "BAD REQUEST"
details: str = "请求参数错误"
code = 400
message = "BAD REQUEST"
details = "请求参数错误"
class BadRequestResponse(BaseResponse):
@ -62,9 +62,9 @@ class BadRequestResponse(BaseResponse):
class ServerInternalError(ErrorModel):
code: int = 500
message: str = "INTERNAL SERVER ERROR"
details: str = "服务器内部错误"
code = 500
message = "INTERNAL SERVER ERROR"
details = "服务器内部错误"
class ServerInternalResponse(BaseResponse):

View File

@ -1,13 +0,0 @@
from authx import AuthX, AuthXConfig
from datetime import timedelta
config = AuthXConfig()
config.JWT_ALGORITHM = "HS256"
config.JWT_SECRET_KEY = "SECRET_KEY"
config.JWT_TOKEN_LOCATION = ["headers", "query", "cookies", "json"]
config.JWT_HEADER_NAME = "X-Token"
config.JWT_HEADER_TYPE = ""
# access token expires in 24 hours
config.JWT_ACCESS_TOKEN_EXPIRES = timedelta(hours=24)
security = AuthX(config=config)

View File

@ -0,0 +1,22 @@
# coding: utf-8
from sqlalchemy import Column, DateTime, Integer, text
from sqlalchemy.dialects.mysql import VARCHAR
from sqlalchemy.ext.declarative import declarative_base
Base = declarative_base()
metadata = Base.metadata
class DevicesPlace(Base):
__tablename__ = 'devices_place'
__table_args__ = {'comment': '设备:地址表'}
id = Column(Integer, primary_key=True)
mid = Column(VARCHAR(10), nullable=False, index=True, comment='设备管理ID')
customer_account = Column(VARCHAR(12), nullable=False, index=True, comment='甲方')
region_id = Column(Integer, nullable=False, server_default=text("'0'"), comment='区域id')
building_id = Column(Integer, nullable=False, server_default=text("'0'"), comment='所在楼建筑的id0为待定')
floor = Column(Integer, nullable=False, server_default=text("'0'"), comment='楼层0为待定可为负数')
place = Column(VARCHAR(50), comment='位置(通常是房间号)')
ctime = Column(DateTime, nullable=False, index=True, server_default=text("CURRENT_TIMESTAMP"), comment='记录创建时间')
utime = Column(DateTime, nullable=False, index=True, server_default=text("CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP"), comment='记录更新时间')

View File

@ -1,10 +1,9 @@
import logging
from collections import defaultdict
from fastapi import Request, status
from fastapi.exceptions import HTTPException, RequestValidationError
from fastapi.responses import JSONResponse
import authx.exceptions as authx_exceptions
from src.dtos import BaseResponse, ErrorModel
logger = logging.getLogger("uvicorn.error")
@ -12,32 +11,35 @@ logger = logging.getLogger("uvicorn.error")
def request_validation_error_handler(req: Request, exc: RequestValidationError):
"""
请求校验错误处理
请求响应校验错误处理
:param req:
:param exc:
:return:
"""
errors = exc.errors()
logger.error(f"{req.method} {req.url} , errors: {errors}")
group_err = defaultdict(list)
for err in errors:
group_err[err["type"]].append(err)
details = ""
for err_type, err_list in group_err.items():
match err_type:
case "missing" | "value_error.missing" :
details += f"缺少字段:{', '.join([err['loc'][-1] for err in err_list])};"
case "value_error.jsondecode":
details += "请求参数JSON编码有误;"
case "value_error":
details += "; ".join([err["msg"] for err in err_list])
case "type_error":
details += "; ".join([err["msg"].replace("value", f"{err['loc'][1]}") for err in err_list])
case "type_error.none.not_allowed":
details += f"字段:{', '.join([err['loc'][-1] for err in err_list])} 不能为空;"
case _:
pass
err_model = ErrorModel(code=status.HTTP_400_BAD_REQUEST, message="接口请求校验异常", details=details)
response_errors = [error for error in errors if error["loc"][0] == "response"]
if len(response_errors) > 0:
message = "接口响应异常 "
else:
message = "接口请求异常 "
json_err = [err["msg"] for err in errors if "value_error.jsondecode" == err["type"]]
if json_err:
message += "请求参数JSON编码有误;"
value_err = [err["msg"] for err in errors if "value_error" == err["type"]]
if value_err:
message += "; ".join(value_err)
missing_err = [err["loc"][-1] for err in errors if "value_error.missing" == err["type"]]
if missing_err:
message += "缺少字段:{} ".format(", ".join(missing_err))
type_err = [err["msg"].replace("value", f"{err['loc'][1]}") for err in errors if "type_error" == err["type"]]
if type_err:
message += "; ".join(type_err)
not_none_err = [err["loc"][-1] for err in errors if "type_error.none.not_allowed" == err["type"]]
if not_none_err:
message += "字段:{} 不能为空".format(", ".join(not_none_err))
err_model = ErrorModel(code=status.HTTP_400_BAD_REQUEST, message=message, details=message)
res = BaseResponse(error=err_model)
return JSONResponse(status_code=200, content=res.dict())
@ -52,23 +54,4 @@ def http_exception_handler(req: Request, exc: HTTPException):
logger.error(f"{req.method} {req.url} , exception:{exc.detail}")
err = ErrorModel(code=exc.status_code, message=exc.detail)
res = BaseResponse(result=None, error=err)
return JSONResponse(status_code=exc.status_code, content=res.model_dump())
def authx_exception_handler(req: Request, exc: authx_exceptions.AuthXException):
"""
AuthX Exception 错误处理
:param req:
:param exc:
:return:
"""
logger.error(f"{req.method} {req.url} , exception:{exc}")
match type(exc):
case authx_exceptions.MissingTokenError:
err = ErrorModel(code=status.HTTP_401_UNAUTHORIZED, message="请求头缺失X-Token")
case authx_exceptions.JWTDecodeError:
err = ErrorModel(code=status.HTTP_401_UNAUTHORIZED, message="X-Token解析失败")
case _:
err = ErrorModel(code=status.HTTP_401_UNAUTHORIZED, message="未知的错误导致认证失败")
res = BaseResponse(result=None, error=err)
return JSONResponse(status_code=err.code, content=res.model_dump())
return JSONResponse(status_code=exc.status_code, content=res.dict())

View File

@ -1,2 +1,2 @@
set FAST_API_ENV=dev
uvicorn main:fast_api_app --reload --host 0.0.0.0 --port 8088
uvicorn main:fast_api_app --reload --host 0.0.0.0 --port 21021

2
start.sh Executable file → Normal file
View File

@ -1,2 +1,2 @@
export FAST_API_ENV=dev
uvicorn main:fast_api_app --reload --host 0.0.0.0 --port 8088
uvicorn main:app --reload --host 0.0.0.0 --port 21021