持续创作,加速成长!这是我参与「掘金日新计划 · 6 月更文挑战」的第5天,点击查看活动详情
大家好~我是小方,欢迎大家关注笋货测试笔记体完记得俾个like呀
接上篇
上篇我们只是优化了部分代码,剩下的优化点,我们今天全都搞定~
新增自定义异常处理器
- 自定义http异常处理器
注意看上图,我们的注册接口是post请求,但是我直接用了get请求,fastapi自带返回了错误新,但是不满足我们的统一返参结构,所以要加上一个自定义http异常处理器处理该情况~
app/init.py加入以下代码
@fun.exception_handler(StarletteHTTPException)
async def http_exception_handler(request: Request, exc: StarletteHTTPException):
res = ResponseDto(code=201, msg=HTTP_CODE_MSG.get(exc.status_code, exc.detail))
return JSONResponse(content=res.dict())
另外,我在config.py文件加了下面http状态码的map,不多,有兴趣的小伙伴可行添加~
测试一下~!
- 自定义参数校验异常处理器
有时候请求的入参跟我们定义的模型入参不一致时,fastapi默认会返回一个detail信息,告诉你具体哪里错了,但是默认的返参结构不满足们的统一返参结构,得改!
app/init.py加入以下代码
@fun.exception_handler(RequestValidationError)
async def validation_exception_handler(request: Request, exc: RequestValidationError):
message = ""
for error in exc.errors():
message += str(error.get('loc')[-1]) + ":" + str(error.get("msg"))+","
res = ResponseDto(code=201, msg=f"请求参数非法! {message[:-1]}")
return JSONResponse(content=res.dict())
测试一下~
- 手动捕获异常处理器
上篇我们讲到虽然成功捕获了异常,但是返参并不是我们想要的结构,改!
app/init.py加入以下代码
@fun.exception_handler(NormalException)
async def unexpected_exception_error(request: Request, exc: NormalException):
res = ResponseDto(code=110, msg=exc.detail)
return JSONResponse(content=res.dict())
测试一下,重复邮箱号进行注册
- 捕获异常进阶
上面我们加了手动捕获异常处理器,但是如果我们想要添加日志,记录报错的请求参数该怎么做呢?请看下面~加上这段代码即可
重新请求一下,发现接口出现堵塞了
大概原因是
request请求被消耗掉了,导致异步程序卡死在await request.json()???最后参考无敌哥的pity,成功解决~
历史踩坑记,请看:testerhome.com/topics/2890…
完整代码
# -*- coding: utf-8 -*-
# @Time : 2022/5/3 08:56
# @Author : junjie
# @File : __init__.py
import json
from fastapi import FastAPI, Request
from config import Text, HTTP_CODE_MSG
from starlette.exceptions import HTTPException as StarletteHTTPException
from starlette.types import Message
from fastapi.exceptions import RequestValidationError
from app.models.base import ResponseDto
from fastapi.responses import JSONResponse
from app.utils.exception_utils import NormalException
from app.utils.logger import Log
fun = FastAPI(title=Text.TITLE, version=Text.VERSION, description=Text.DESCRIPTION)
async def set_body(request: Request, body: bytes):
async def receive() -> Message:
return {"type": "http.request", "body": body}
request._receive = receive
async def get_body(request: Request) -> bytes:
body = await request.body()
await set_body(request, body)
return body
# 自定义http异常处理器
@fun.exception_handler(StarletteHTTPException)
async def http_exception_handler(request: Request, exc: StarletteHTTPException):
res = ResponseDto(code=201, msg=HTTP_CODE_MSG.get(exc.status_code, exc.detail))
return JSONResponse(content=res.dict())
# 自定义参数校验异常处理器
@fun.exception_handler(RequestValidationError)
async def validation_exception_handler(request: Request, exc: RequestValidationError):
message = ""
for error in exc.errors():
message += str(error.get('loc')[-1]) + ":" + str(error.get("msg"))+","
res = ResponseDto(code=101, msg=f"请求参数非法! {message[:-1]}")
return JSONResponse(content=res.dict())
# 手动捕获异常处理器
@fun.exception_handler(NormalException)
async def unexpected_exception_error(request: Request, exc: NormalException):
log_msg = f"数据工厂捕获到异常, 请求路径: {request.url.path}\n"
params = request.query_params
headers = request.headers
if params: log_msg += f"路径参数: {params}\n"
if headers.get('content-type') == 'application/json':
body = json.dumps(await request.json(), ensure_ascii=False)
log_msg += f"请求参数: {body}"
Log().info(log_msg)
res = ResponseDto(code=110, msg=exc.detail)
return JSONResponse(content=res.dict())
# 全局捕获异常
@fun.middleware("http")
async def errors_handling(request: Request, call_next):
log_msg = f"数据工厂捕获到系统错误, 请求路径:{request.url.path}\n"
params = request.query_params
body = await request.body()
headers = request.headers
if params:
log_msg += f"路径参数: {params}\n"
if body and headers.get('content-type') == 'application/json':
try:
body = json.dumps(json.loads(body), ensure_ascii=False)
log_msg += f"请求参数: {body}\n"
except:
res = ResponseDto(code=102, msg='解析json失败')
return JSONResponse(content=res.dict())
try:
await set_body(request, await request.body())
return await call_next(request)
except Exception as exc:
import traceback
log_msg += f"错误信息: {traceback.format_exc()}"
Log().error(log_msg)
res = ResponseDto(code=500, msg=str(exc.args[0]))
return JSONResponse(content=res.dict())
最终正常打印日志
注册路由优化
新建app/router/routers.py(或者将底下的init.py改一下名即可)加入以下代码
from app.routers.user import user
data = [
(user.router, '/api/user', ["用户模块"])
]
app/init.py引入
main.py相关的代码也清空掉
重启服务,打开
http://127.0.0.1:8080/docs#/
初始化建表优化
import os, importlib, sys
from app.models import Base, engine
from config import FilePath
def get_curd_path():
"""获取curd目录下所有的xxxDao.py"""
curd_path_list = []
for file in os.listdir(FilePath.CURD_PATH):
# 拼接目录
file_path = os.path.join(FilePath.CURD_PATH, file)
# 判断过滤, 取有效目录
if os.path.isdir(file_path) and '__pycache__' not in file:
from collections import defaultdict
path_dict = defaultdict(list)
# 获取目录下所有的xxxDao.py
for py_file in os.listdir(file_path):
if py_file.endswith('py') and 'init' not in py_file:
path_dict[file].append(py_file.split('.')[0])
curd_path_list.append(path_dict)
return curd_path_list
dao_path_list = get_curd_path()
for path in dao_path_list:
for file_path,pys in path.items():
# 拼接对应的curd目录
son_dao_path = os.path.join(FilePath.CURD_PATH, file_path)
# 导包时, 默认在这个路径下查找
sys.path.append(son_dao_path)
for py in pys:
# 动态导包进去
importlib.import_module(py)
Base.metadata.create_all(engine)
config.py加入这两个路径
其实原理就是动态导包进去,将curd目录下的xxxDao导入进去,然后进行
Base.metadata.create_all(engine)就可以批量创建表了
删掉表测试一下
重启服务后,刷新表
搞定~
总结
结合本篇和上篇,我们已经将所有的优化点处理完毕:
- 抛异常时,没有处理,直接服务器错误了 [✓]
- schema校验不通过时,返参太难看了[✓]
- 初始化建表时,需要在
main.py文件引入,不太方便[✓] - 返参没有统一的格式 [✓]
- 编写UserDao时,需要手动try...except```比较繁琐... [✓]
- user表的密码明文存储,不太安全 [✓]
- 注册router时,需要一个个include_router,太麻烦了[✓]
- ...
好了,今天先到这里了,我们下期见哈~
- 项目地址