Compare commits

...

23 Commits

Author SHA1 Message Date
chenwj113 fc5d6b8953 fix: 更新Dockerfile文件
Gitea Action for docker build runtime / build-runtime (push) Successful in 5m53s Details
2024-09-02 10:11:46 +08:00
chenwj113 6d69a18b03 fix: 运行时环境替换为alpine
Gitea Action for docker build / build-app (push) Successful in 1m23s Details
Gitea Action for docker build runtime / build-runtime (push) Successful in 4m18s Details
2024-08-16 18:37:51 +08:00
chenwj113 61f91fa932 fix: 去除gitea action的pyproject.toml触发path
Gitea Action for docker build / build-app (push) Successful in 1m1s Details
2024-08-15 18:35:00 +08:00
chenweijia ee14c5591a bugfix: 修复gitea action构建脚本的bug
Gitea Action for docker build / build-app (push) Successful in 25s Details
Gitea Action for docker build runtime / build-runtime (push) Successful in 4m17s Details
2024-08-15 17:11:05 +08:00
chenweijia dce678ca95 fix: 更改gitea action获取版本号的方式
Gitea Action for docker build runtime / build-runtime (push) Successful in 4m27s Details
Gitea Action for docker build / build-app (push) Successful in 33s Details
2024-08-15 17:03:16 +08:00
chenweijia 8982dd82d8 fix: 添加用户验证功能
Gitea Action for docker build / build-app (push) Successful in 47s Details
Gitea Action for docker build runtime / build-runtime (push) Successful in 3m46s Details
2024-08-15 13:33:43 +08:00
chenweijia 8459e0c447 fix: 增加一个请求限制功能
Gitea Action for docker build / build-app (push) Successful in 34s Details
Gitea Action for docker build runtime / build-runtime (push) Successful in 5m20s Details
2024-08-14 14:20:19 +08:00
chenweijia bd9dac23be fix: api示例增加数据库操作示例
Gitea Action for docker build / build-app (push) Successful in 34s Details
2024-08-13 14:57:18 +08:00
chenweijia c6767778e2 fix: 更新依赖包信息
Gitea Action for docker build / build-app (push) Successful in 52s Details
Gitea Action for docker build runtime / build-runtime (push) Successful in 6m20s Details
2024-08-13 11:33:39 +08:00
chenwj113 088bee0199 fix:
Gitea Action for docker build runtime / build-runtime (push) Successful in 1m2s Details
2024-06-12 10:13:11 +08:00
chenwj113 b23786319e fix:
Gitea Action for docker build runtime / build-runtime (push) Failing after 3h4m10s Details
2024-06-11 17:51:50 +08:00
chenwj113 2d4e8397ef fix:更新Dockerfile
Gitea Action for docker build runtime / build-runtime (push) Successful in 1m56s Details
2024-06-11 17:36:34 +08:00
chenwj113 9bf965587e fix: 更改Dockerfile安装命令
Gitea Action for docker build runtime / build-runtime (push) Successful in 34s Details
2024-06-11 17:19:24 +08:00
chenwj113 598bb654c7 bugfix: 修复Dockerfile错误
Gitea Action for docker build / build-app (push) Successful in 37s Details
Gitea Action for docker build runtime / build-runtime (push) Successful in 2m40s Details
2024-06-11 17:06:19 +08:00
chenwj113 2ee8beb276 fix: 更新依赖包安装方式
Gitea Action for docker build / build-app (push) Successful in 1m20s Details
Gitea Action for docker build runtime / build-runtime (push) Failing after 1m7s Details
2024-06-11 16:59:21 +08:00
chenwj113 59d4f098ea bugfix:
Gitea Action for docker build / build-app (push) Successful in 43s Details
Gitea Action for docker build runtime / build-runtime (push) Successful in 4m55s Details
2024-06-02 14:35:50 +08:00
chenwj113 d93ec2b0f3 fix: 修复依赖包错误
Gitea Action for docker build runtime / build-runtime (push) Failing after 1m32s Details
2024-06-02 13:54:15 +08:00
chenwj113 6e60009711 fix: 修改RuntimeDockerfile
Gitea Action for docker build runtime / build-runtime (push) Failing after 2m12s Details
2024-06-02 13:49:39 +08:00
chenwj113 0ef5ce6fd8 bugfix:
Gitea Action for docker build runtime / build-runtime (push) Has been cancelled Details
2024-06-02 13:44:26 +08:00
chenwj113 3f0827e64f fix:
Gitea Action for docker build / build-app (push) Successful in 52s Details
Gitea Action for docker build runtime / build-runtime (push) Failing after 48s Details
2024-06-02 13:26:34 +08:00
chenwj113 7dfc9ff205 fix: 修改目录结构
Gitea Action for docker build runtime / build-runtime (push) Waiting to run Details
Gitea Action for docker build / build-app (push) Has been cancelled Details
2024-06-02 12:46:05 +08:00
chenwj113 dad5d2aaa5 fix: cicd文件增加判断null命令 2024-06-02 12:10:55 +08:00
chenwj113 c3fda4c8b5 feat: 新增gitea action功能
Gitea Action for docker build / build-app (push) Successful in 4m48s Details
Gitea Action for docker build runtime / build-runtime (push) Has been cancelled Details
2024-06-02 12:01:40 +08:00
25 changed files with 2924 additions and 153 deletions

View File

@ -0,0 +1,36 @@
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

@ -0,0 +1,32 @@
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: 192.168.2.237:8088
DOCKER_IMAGE_DOMAIN: hub.airpig.cn
LATEST_VERSION: latest
K8S_NS: default
DEPLOYMENT_NAME: fastapi-app-template

30
AlpineRuntimeDockerfile Normal file
View File

@ -0,0 +1,30 @@
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,29 +1,10 @@
# 安装依赖阶段
FROM python:3.8.6-slim as build
FROM busybox
RUN mkdir /install
RUN mkdir -p /data /app
WORKDIR /install
WORKDIR /data
COPY requirements.txt .
COPY . /data
RUN sed -i s@/deb.debian.org/@/mirrors.aliyun.com/@g /etc/apt/sources.list
ENTRYPOINT [ "/bin/cp", "-r", "/data/*", "/app"]
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 Normal file
View File

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

42
RuntimeDockerfile Normal file
View File

@ -0,0 +1,42 @@
# 安装依赖阶段
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,16 +5,17 @@ lifetime_seconds=3600
[mysql]
username=root
password=123456
host=192.168.2.94
host=127.0.0.1
port=3306
database=admgs_v2
database=test
[redis]
redis_host=192.168.2.94
redis_host=127.0.0.1
redis_port=6379
redis_password=
[rabbitmq]
rabbitmq_host=192.168.2.94
rabbitmq_host=rabbitmq
rabbitmq_user=root
rabbitmq_password=123123

View File

@ -4,17 +4,18 @@ lifetime_seconds=3600
[mysql]
username=root
password=123456
host=192.168.2.94
password=Chenweijia113!
host=mysql
port=3306
database=admgs_v2
database=test
[redis]
redis_host=192.168.2.94
redis_host=redis
redis_port=6379
redis_password=Chenweijia113!
[rabbitmq]
rabbitmq_host=192.168.2.94
rabbitmq_host=rabbitmq
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 import BaseSettings
from pydantic_settings import BaseSettings
class ReConfigParser(ConfigParser):
@ -34,7 +34,6 @@ class MySQLConfig(BaseSettings):
class RedisConfig(RedisSettings):
redis_url: str = None
redis_host: Optional[str] = 'localhost'
redis_port: Optional[int] = 6379
redis_password: str = None
@ -60,7 +59,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:

48
main.py
View File

@ -1,23 +1,26 @@
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)
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
def create_app():
app = FastAPI()
mysql_config, redis_config = init_config()
@app.on_event("startup")
async def startup_event():
@asynccontextmanager
async def lifespan(app: FastAPI):
# 创建日志文件夹和临时文件上传文件夹
if not os.path.exists("files"):
os.mkdir("files")
@ -27,31 +30,34 @@ def create_app():
os.mkdir("logs")
logging_config.fileConfig('conf/log.ini')
# 初始化配置文件
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 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)
# 添加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)
# 添加异常处理
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
app.include_router(example.router, tags=["API示例"], prefix="/example")
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")
return app

2478
poetry.lock generated Normal file

File diff suppressed because it is too large Load Diff

34
pyproject.toml Normal file
View File

@ -0,0 +1,34 @@
[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,24 +1,75 @@
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
--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"

16
src/api/auth_example.py Normal file
View File

@ -0,0 +1,16 @@
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,16 +5,20 @@ 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.biz.example import get_user_by_id
from src.service.example import get_user_by_id
from src.dtos import response
from src.dtos.example import UserExampleListPagesResult
router = APIRouter()
@router.get("/")
# 并发数限制 每5秒最多100次请求
@router.get("/", dependencies=[Depends(RateLimiter(times=100, seconds=5))])
def index():
return {"msg": "This is Index Page"}
@ -59,6 +63,11 @@ 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

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

13
src/middleware/auth.py Normal file
View File

@ -0,0 +1,13 @@
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

@ -1,22 +0,0 @@
# 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,4 +1,4 @@
from src.biz import execute_sql
from src.service import execute_sql
def get_user_by_id(user_id):

View File

@ -1,9 +1,10 @@
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")
@ -11,35 +12,32 @@ 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}")
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)
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)
res = BaseResponse(error=err_model)
return JSONResponse(status_code=200, content=res.dict())
@ -54,4 +52,23 @@ 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.dict())
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())

View File

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

2
start.sh Normal file → Executable file
View File

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