组成部分
在实际项目中,往往会有多个接口,在我们实际开发过程中,通常会根据各个功能模块,将开发的各个部分分别存放在不同的文件夹下。本案例中通过图书管理系统,来演示这部分内容。由于代码量的问题,这里仅演示登录和图书展示两个界面相关的代码。
整体布局如下图:
系统配置(configs)
系统配置中主要用来存放一些资源配置的代码,例如数据库连接或者生成token的配置信息。
#configs.db.py
from sqlalchemy import create_engine
from sqlalchemy.orm import declarative_base
# 数据库相关的系统配置项
DBURL = 'mysql+pymysql://root:123456@127.0.0.1:3306/libraryDB'
ENGINE = create_engine(DBURL)
Base = declarative_base(bind=ENGINE)
from passlib.context import CryptContext
SECRET_KEY = "09d25e094faa6ca2556c818166b7a9563b93f7099f6f0f4caa6cf63b88e8d3e7"
ALGORITHM = "HS256"
ACCESS_TOKEN_EXPIRE_MINUTES = 30
CONTEXT = CryptContext(schemes=['bcrypt'], deprecated="auto")
通用工具(utils)
用来存放一些系统中常用的工具函数,生成token,或者依赖函数。
#utils.db.py
from sqlalchemy.orm import Session
from configs.db import ENGINE
def get_db():
db = Session(ENGINE)
try:
yield db
finally:
db.close()
#utils.token.py
from jose import JWTError,jwt
from typing import Union
from datetime import timedelta,datetime
from configs.token import CONTEXT,SECRET_KEY,ALGORITHM
def hash_pwd(pwd:str) -> str:
return CONTEXT.hash(pwd)
def verify_pwd(p_pwd:str,h_pwd:str) -> bool:
return CONTEXT.verify(p_pwd,h_pwd)
def create_token(data:dict,expires_delta:Union[timedelta,None]=None) -> str:
to_encode = data.copy()
# 追加过期时间
if expires_delta:
expire = datetime.utcnow() + expires_delta
else:
expire = datetime.utcnow() + timedelta(minutes=15)
to_encode.update({"exp": expire})
# 注意此处的to_encode中的键,解token后也通过这些键来取值!
# 利用密钥和加密算法生成token
encoded_jwt = jwt.encode(to_encode, SECRET_KEY, algorithm=ALGORITHM)
return encoded_jwt
数据表模型(models)
用来存放应用中用到的不同的数据表模型。
#models.user.py
from sqlalchemy import Table,Column,String,Integer,DateTime
from sqlalchemy.orm import mapper
from configs.db import Base
user_table = Table(
"user",
Base.metadata,
Column("id", Integer, primary_key=True, autoincrement=True),
Column("username", String(20),comment="用户名"),
Column("hash_pwd", String(200),comment="加密后的密码"),
Column("token", String(255),comment="token"),
Column("last_time", DateTime(),comment="最后登录时间"),
comment = "用户表"
)
# 新的一种混合方式创建数据表模型类
class User(object):
def __init__(self,**kwargs):
for i in kwargs:
self.__dict__[i] = kwargs.get(i)
mapper(User,user_table)
#models.book.py
from sqlalchemy import Table,Column,String,Integer,DateTime
from sqlalchemy.orm import mapper
from configs.db import Base
book_table = Table(
"book",
Base.metadata,
Column("id", Integer, primary_key=True, autoincrement=True),
Column("title", String(50),comment="书名"),
Column("author", Integer,comment="作者,作者表的id"),
Column("publisher", Integer, comment="出版社,出版社表的id"),
Column("code", String(20), comment="ISBN码"),
comment = "图书表"
)
# 新的一种混合方式创建数据表模型类
class Book(object):
def __init__(self,**kwargs):
for i in kwargs:
self.__dict__[i] = kwargs.get(i)
mapper(Book,book_table)
数据逻辑(services)
存放各个数据库表对应的增删改查的逻辑函数。
#services.user.py
from sqlalchemy.orm import Session
from datetime import datetime
from typing import Union
from models.user import User
from schemas.user import UserIn
def insert_user(db:Session,username:str,hash_pwd:str):
token = ''
last_time = datetime.utcnow()
user = User(username=username,hash_pwd=hash_pwd,token=token,last_time=last_time)
db.add(user)
db.commit()
return True
def update_user(db:Session,id:int,token:Union[str,None]=None,last_time:Union[datetime,None]=None):
if token:
db.query(User).filter(User.id == id).update({"token":token})
if last_time:
db.query(User).filter(User.id == id).update({"last_time":last_time})
db.commit()
return True
def read_user(db:Session, username:str) -> UserIn:
user = db.query(User).filter(User.username == username).one()
if user:
return UserIn(**user.dict())
#services.book.py
from sqlalchemy.orm import Session
from datetime import datetime
from typing import Union
from models.book import Book
from schemas.book import BookIn
def insert_book(db:Session,book:BookIn):
book = Book(title=book.title,author=book.author,publisher=book.publisher,code=book.code)
db.add(book)
db.commit()
return True
def read_books(db:Session) -> list:
books = db.query(Book).all()
if books:
result = []
for book in books:
result.append({
"id":book.id,
"title":book.title,
"author":book.author,
"publisher":book.publisher,
"code":book.code
})
return result
数据验证模型(schemas)
根据services里面的参数,可以提炼出数据验证模型。
#services.user.py
from pydantic import BaseModel
from typing import Union
from datetime import datetime
class UserIn(BaseModel):
username:str
pwd:str
class UserUp(BaseModel):
id:int
token:Union[str,None]=None
last_time:Union[datetime,None]=None
#services.book.py
from pydantic import BaseModel
class BookIn(BaseModel):
title:str
author:int
publisher:int
code:str
子路由(routers)
子路由是APIRouter类的实例对象,使用与FastAPI类似。用来存放各个功能模块的接口。
#routers.user.py
from fastapi import APIRouter,Depends
from sqlalchemy.orm import Session
from schemas.user import UserIn,UserUp
from utils.db import get_db
from utils.token import hash_pwd
from services.user import insert_user,update_user
user = APIRouter()
@user.post("/")
def add_user(user:UserIn,db:Session=Depends(get_db)):
h_pwd = hash_pwd(user.pwd)
insert_user(db=db,username=user.username,hash_pwd=h_pwd)
return {
"code":"0000",
"msg":"OK"
}
@user.put("/")
def modify_user(user:UserUp,db:Session=Depends(get_db)):
update_user(db=db,token=user.token,last_time=user.last_time,id=user.id)
return {
"code": "0000",
"msg": "OK"
}
#routers.book.py
from fastapi import APIRouter,Depends
from sqlalchemy.orm import Session
from services.book import insert_book,read_books
from schemas.book import BookIn
from utils.db import get_db
book = APIRouter()
@book.post("/")
def add_book(book:BookIn,db:Session=Depends(get_db)):
insert_book(db=db,book=book)
return {
"code":"0000",
"msg":"OK"
}
@book.get("/")
def get_books(db:Session=Depends(get_db)):
data = read_books(db=db)
return {
"code": "0000",
"msg": "OK",
"data": data
}
总路由(main)
汇总所有的子路由到一个总的路由中。
#main.py
from fastapi import FastAPI
from routers.user import user
from routers.book import book
from configs.db import Base
app = FastAPI()
#启动时,初始化创建数据库表
@app.on_event("startup")
def initdb():
Base.metadata.create_all()
#prefix定义该子路由的路径前缀
app.include_router(user,prefix="/user",tags=["用户相关"])
app.include_router(book,prefix="/book",tags=["图书相关"])
if __name__ == '__main__':
import uvicorn
uvicorn.run(app="main:app",host="127.0.0.1",port=8080,reload=True)
接口呈现界面: