测开平台(后端开发)

一、创建后端项目

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的生成和解析

  1. 生成密钥

    python 复制代码
    import secrets
    #生成密钥
    key = secrets.token_hex(32)  #32字节 1个字节是8位二进制 32*8=256位 256位密钥
    
    print(key)
  2. 进行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
相关推荐
Hello.Reader2 小时前
Flink SQL 集合运算UNION / INTERSECT / EXCEPT 以及 IN / EXISTS 在流式场景下怎么用?
数据库·sql·flink
_Minato_2 小时前
数据库知识整理——数据库控制功能
数据库·经验分享·笔记·软考·计算机系统
TDengine (老段)2 小时前
TDengine 数据订阅架构设计与最佳实践
大数据·数据库·时序数据库·tdengine·涛思数据
Jtti3 小时前
MySQL磁盘不足会导致服务直接崩溃吗?
数据库·mysql
蜂蜜黄油呀土豆3 小时前
分布式基础知识:分布式事务完整解析(背景、模式、协议、优缺点)
数据库·微服务·分布式事务·架构设计·分布式系统·2pc/3pc·tcc/saga
写代码的【黑咖啡】3 小时前
MySQL 主从同步与读写分离详解
数据库·mysql
我是高手高手高高手3 小时前
TP8 增加数据时在数据回滚事务时没错误数据却没有插入(表数据插入不了)startTrans() rollback()Db::transaction
数据库
小光学长3 小时前
基于web的影视网站设计与实现14yj533o(程序+源码+数据库+调试部署+开发环境)带论文文档1万字以上,文末可获取,系统界面在最后面。
java·前端·数据库
菜鸟小九3 小时前
redis基础(数据结构)
数据结构·数据库·redis