一、创建后端项目
1、定义项目
项目名称:backend,把后端设置成项目的根目录

后端概述:通过接口去操作数据库(增删改查)
- 通过fastapi定义接口 api.py
- json格式数据(basemodel) api_data.py
- orm操作数据库 db_data.py
- 按照模块划分(用户模块,项目模块,用例模块)
- apps(包)
- user
- project
- common公共文件 (包)
- setting 定义数据库连接信息
- auth.py 授权,加密
- main.py 项目整体执行入口 (py文件)

2、数据库操作
await
增:类.create()
查:类.all 类.filter(xxx=xxx)
改:1、类.filter(xxx=xxx).update(xxx=xxx)
2、res = 类.filter(xxx=xxx)
res.name = xxx
res.save()
删:类.filter(xxx=xxx),delete()
3、项目设置
common目录里面添加setting.py,新建的数据库文件需要加入到Installed_Models
python
MySQL_DB = {
'host': 'localhost',
'port': '3306',
'user': 'root',
'password': 'root',
'database': 'asher_pt',
}
# 需要关联的数据库模型 所在的模块
Installed_Models = [
'apps.user.db_data', # 从最外层的包开始写,一直写到py文件的名称 不需要带后缀
]
TORTOISE_ORM = {
'connections': {
'default': {
'engine': 'tortoise.backends.mysql',
'credentials': MySQL_DB
},
},
'apps': {
'models': {
'models': ['aerich.models', *Installed_Models],
'default_connection': 'default',
},
}
}
项目根目录下添加static文件

创建user包,里面包含,api.py,api_data.py,db_data.py
db_data.py创建数据库
python
from tortoise import Model, fields
# 初始化命令
# aerich init -t main.TORTOISE_ORM
# aerich init-db
# 迁移命令
# aerich migrate
# aerich upgrade
class UserGroup(Model):
"""
用户组(user_group)
"""
id = fields.IntField(pk=True)
name = fields.CharField(min_length=3, max_length=20,description="用户组名称")
class Meta:
table = "user_group"
table_description = "用户组表"
class User(Model):
"""
用户表(user)
"""
id = fields.IntField(pk=True,description="用户id")
username = fields.CharField(min_length=3,max_length=10,description="用户名")
password= fields.CharField(max_length=100,description="密码")
email = fields.CharField(min_length=6,max_length=30,description="邮箱")
mobile = fields.CharField(min_length=11,max_length=11,description="手机号")
is_active = fields.BooleanField(default=False,description="是否激活")
is_admin = fields.BooleanField(default=False,description="是否是管理员")
register_time = fields.DatetimeField(auto_now=True,description="注册时间")
update_time= fields.DatetimeField(auto_now=True,description="更新时间")
user_group = fields.ForeignKeyField("models.UserGroup",related_name="users",description="用户组")
class Meta:
table = "user"
table_description = "用户表"
class UserLoginRecord(Model):
"""
用户登录记录表(user_login_record)
"""
id = fields.IntField(pk=True,description="用户登录记录id")
user_id = fields.IntField(description="用户id")
login_time = fields.DatetimeField(auto_now_add=True,description="登录时间")
logout_time = fields.DatetimeField(auto_now=True,description="登出时间")
is_logout= fields.BooleanField(default=False,description="是否登出")
uuid = fields.UUIDField(description="登录记录的uuid")
class Meta:
table = "user_login_record"
table_description = "用户登录记录表"
common目录里面添加auth.py,用来加解密,账号的加密用了bcrypt
按照第三方库:pip install passlib[bcrypt] bcrypt==4.0.1 pyjwt python-multipart
python
#密码加解密处理
# 用户的密码进行加密,密码校验
# ==========================【密码加密、校验】=======================================
pwd_context = CryptContext(schemes=["bcrypt"], deprecated="auto") # bcrypt 加密的方式
# 密码加密
def password_hash(password): # 对密码进行加密的方法,传入明文密码,返回加密的密码
return pwd_context.hash(password)
# 密码验证
def verify_password(pwd, pwd_hash): # 校验明文密码跟加密之后的密码是不是配对的
return pwd_context.verify(pwd, pwd_hash)
5、token的生成和解析
-
生成密钥
pythonimport secrets #生成密钥 key = secrets.token_hex(32) #32字节 1个字节是8位二进制 32*8=256位 256位密钥 print(key) -
进行token的加密与解密,token的加密有了HS256
python#生成token def create_token(user_info:dict): user_info["exp"] = int(time.time()) +TOKEN_EXPIRE return jwt.encode(user_info, SECRETER_KEY, algorithm=ALGORITHM) # token解密 def verify_token(token): try: res = jwt.decode(token, SECRETER_KEY, algorithms=ALGORITHM) # print( res) #能够解析出数据,不代表就是有效的,如果退出登录,token应该失效,需要进行数据库验证 # from common.base_db import BaseDb # from common.settings import MySQL_DB # uuid = res["uuid"] # db = BaseDb(MySQL_DB) # sql = "select * from user_login_record where uuid = %s" # db_res = db(sql,args=(uuid,)) # print(db_res) # if db_res[0]["is_logout"]: # raise HTTPException(status_code=401, detail="用户未登录") # return res except Exception as e: print(e) raise HTTPException(status_code=401, detail="token失效了呀") if __name__ == '__main__': user_info = {"username":"admin","password":"123456"} token = create_token(user_info) print(token) time.sleep(10) res = verify_token(token) print(res)
6、定义接口
接口定义,如果是json参数需要通过pydantic进行定义入参
python
from pydantic import BaseModel
class UserRegisterReq(BaseModel):
"""
用户注册请求数据模型
"""
username: str
password: str
email: str
mobile: str
user_group_id: int
请求模型需要放在,接口方法的入参里面
async def register_user(user_req: UserRegisterReq):
python
router = APIRouter(prefix="/users",tags=["users"])
@router.post("/register",summary="用户注册接口")
async def register_user(user_req: UserRegisterReq):
"""
用户注册接口
:param user_req:
:return:
"""
#检查用户组是否存在
user_group= await UserGroup.get_or_none(id=user_req.user_group_id)
if not user_group:
raise HTTPException(status_code=400,detail="用户组不存在")
#检查手机号是否存在
existing_mobile = await User.get_or_none(mobile=user_req.mobile)
if existing_mobile:
raise HTTPException(status_code=400,detail="手机号已存在")
#检查邮箱是否存在
existing_email = await User.get_or_none(email=user_req.email)
if existing_email:
raise HTTPException(status_code=400,detail="邮箱已存在")
#创建用户
try:
#密码加密
hashed_password = password_hash(user_req.password)
#创建用户对象
user = await User.create(
username=user_req.username,
password=hashed_password,
email=user_req.email,
mobile=user_req.mobile,
user_group=user_group
)
#构造返回的用户信息
user_info = dict(
id=user.id,
username=user.username,
email=user.email,
mobile=user.mobile,
is_active=user.is_active,
is_admin=user.is_admin,
register_time=str(user.register_time),
update_time=str(user.update_time),
user_group_id=user.user_group_id
)
return {"code": 200, "message": "用户注册成功", "data": user_info}
except IntegrityError as e:
raise HTTPException(status_code=400,detail="用户名或者邮箱或者手机号已存在")
except Exception as e:
raise HTTPException(status_code=500,detail=f"用户注册失败:{e})")
响应模型需要放在,router实例化里面
@router.post("/login",summary="用户登录接口",response_model=UserLoginResp)
Q方法解决filter只能查询一个参数的痛点
user = await User.get_or_none(
Q(username=login_req.account) |
Q(mobile=login_req.account) |
Q(email=login_req.account)
)
python
@router.post("/login",summary="用户登录接口",response_model=UserLoginResp)
async def login_user(login_req: UserLoginReq):
"""
用户登录接口
username可以是手机号,邮箱或用户名
:param login_req:
:return:
"""
#根据手机号,邮箱,用户名查询用户
user = await User.get_or_none(
Q(username=login_req.account) |
Q(mobile=login_req.account) |
Q(email=login_req.account)
)
#检查用户是否存在
if not user:
raise HTTPException(status_code=400,detail="用户不存在")
#检查用户是否被禁用
if not user.is_active:
raise HTTPException(status_code=400,detail="用户已被禁用")
#验证密码
if not verify_password(login_req.password, user.password):
raise HTTPException(status_code=400,detail="密码错误")
#将用户之前所以的登录记录标记为登出
await UserLoginRecord.filter(user_id=user.id,is_logout=False).update(is_logout=True)
#生成token
login_uuid = str(uuid.uuid4())
token_data = {
"user_id": user.id,
"username": user.username,
"email": user.email,
"mobile": user.mobile,
"is_active": user.is_active,
"is_admin": user.is_admin,
"user_group_id": user.user_group_id,
"uuid": login_uuid,
}
token = create_token(token_data)
#创建新的登录记录
await UserLoginRecord.create(
user_id = user.id,
uuid = login_uuid
)
#构造返回的用户信息
user_info_resp = UserInfoResp(
id=user.id,
username=user.username,
email=user.email,
mobile=user.mobile,
is_active=user.is_active,
is_admin=user.is_admin,
register_time=str(user.register_time),
update_time=str(user.update_time),
user_group_id=user.user_group_id,
)
#返回登录成功信息
return UserLoginResp(
msg="登录成功",
token=token,
data=user_info_resp
)
退出登录接口
python
class UserLogoutResp(BaseModel):
"""
用户退出登录响应数据模型
"""
msg: str
python
@router.post("/logout",summary="用户登出接口",response_model=UserLogoutResp)
async def logout_user(user_info: dict = Depends(get_user_info)):
"""
用户退出登录
:param user_info:
:return:
"""
#获取当前用户最新一次登录记录
login_record = await UserLoginRecord.filter(user_id=user_info.get("user_id")).order_by("-login_time").first()
#检查登录记录是否存在
if not login_record:
raise HTTPException(status_code=400,detail="用户登录记录不存在")
#检查用户是否已经退出登录
if login_record.is_logout:
raise HTTPException(status_code=400,detail="用户已经退出登录")
#更新登录记录,设置为已退出登录状态
await UserLoginRecord.filter(uuid=login_record.uuid).update(is_logout=True)
return UserLogoutResp(msg="用户退出登录成功")
目前还无法真正退出登录,因为verify_token验证token是否有效,如果token有效就不是退出状态,所以我们还需要将token置为无效,需要在verify_token里面添加将token置为失效的逻辑,添加连接和操作数据库的类
python
import pymysql
class BaseDb:
def __init__(self, database: dict):
self.connect = pymysql.Connect(**database, cursorclass=pymysql.cursors.DictCursor)
self.cursor = self.connect.cursor()
def select_data(self, sql, args=None):
self.cursor.execute(sql, args)
res = self.cursor.fetchall()
return res
def change_data(self, sql, args=None):
num = self.cursor.execute(sql, args)
self.connect.commit()
return f"【新增or修改了{num}条数据】"
def __call__(self, sql: str, args=None):
sql_new = sql.lower().strip() # "select * from bhh_XiangShuiXiaoShou"
start = sql_new.split(" ")[0] # ["select","*",xxx]
if start in ["select", 'use', 'show']:
res = self.select_data(sql, args)
return res
elif start in ["delete", "alter", "update", "insert", "drop"]:
res = self.change_data(sql, args)
return res
python
# token解密
def verify_token(token):
try:
res = jwt.decode(token, SECRETER_KEY, algorithms=ALGORITHM)
#能够解析出数据,不代表就是有效,如果已经退出登录,也应该失效,需要查询
from common.base_db import BaseDb
from common.settings import MySQL_DB
db = BaseDb(MySQL_DB)
uuid = res["uuid"]
print(f"uuid:{uuid}")
db_res = db(f"select * from user_login_record where uuid='{uuid}'")[0]
print(f"db_res:{db_res}")
print(db_res["is_logout"])
if db_res["is_logout"]:
raise HTTPException(status_code=401, detail="token失效了呀123")
新建project
1、新建数据库,添加project文件,添加db_data.py文件
python
from tortoise import Model, fields
# 初始化命令
# aerich init -t main.TORTOISE_ORM
# aerich init-db
# 迁移命令
# aerich migrate
# aerich upgrade
class Project(Model):
"""
项目表
"""
id = fields.IntField(pk=True)
name = fields.CharField(min_length=1,max_length=10,description="项目名称")
create_time= fields.DatetimeField(auto_now_add=True, description="创建时间")
update_time = fields.DatetimeField(auto_now=True, description="更新时间")
user = fields.ForeignKeyField(model_name="models.User",related_name="project", description="创建人")
group_id = fields.IntField(description="项目组id")
class Meta:
table = "project"
table_description = "项目表"
class ProjectModule(Model):
"""
项目模块表(project_module)
"""
id = fields.IntField(pk=True)
name = fields.CharField(min_length=3, max_length=10, description="项目模块名称")
create_time = fields.DatetimeField(auto_now_add=True, description="创建时间")
update_time = fields.DatetimeField(auto_now = True, description="更新时间")
project = fields.ForeignKeyField(model_name="models.Project",related_name="modules",description="所属项目")
class Meta:
table = "project_module"
table_description = "项目模块表"
class ProjectEnv(Model):
"""
项目环境表(project_env)
"""
id = fields.IntField(pk=True)
name = fields.CharField(min_length=3, max_length=10, description="环境名称")
host = fields.CharField(min_length=16, max_length=50, description="环境地址")
global_var = fields.JSONField(default=dict,description="环境变量")
create_time = fields.DatetimeField(auto_now_add=True,description="创建时间")
update_time = fields.DatetimeField(auto_now=True,description="更新时间")
project = fields.ForeignKeyField(model_name="models.Project",related_name="environments", description="所属项目")
class Meta:
table = "project_env"
table_description = "项目环境表"
定义好之后再在setting.py里面添加

再执行命令
python
# 迁移命令
# aerich migrate
# aerich upgrade
注意:在定义请求,响应模型的时候数据规范
python
from pydantic import BaseModel, Field
# 示例1:必填字段(用 ...)
class ProjectUpdate(BaseModel):
new_name: str = Field(..., min_length=3, max_length=10, description="新的项目名称")
# 示例2:可选字段(默认值为空字符串)
class ProjectUpdateOptional(BaseModel):
new_name: str = Field("", min_length=3, max_length=10, description="新的项目名称")
# 示例3:仅类型注解(默认可选,值为 None)
class ProjectUpdateDefault(BaseModel):
new_name: str # 等价于 new_name: str | None = None