回顾
接上篇,还记得我们昨天/auth/listUser这个接口内容,在返回的时候data中是null吗,要知道,其实这不是本意,这是一个已知的User的Moudel的反序列化的一个bug。但是因为昨天时间有些晚,再加上还没有带过大家去一步一步剖析bug产生的原因,bug的解决方案的一整套步骤,所以把解决bug放在了今天。bug如图:
想要的效果图:
这里只是因为太长,所以用raw来展示示例来方便大家观看。
BUG判断
返回时发现用户模型中的属性值为null,则可能是由于属性没有被正确反序列化或初始化。我们准备从以下是几个可能的原因和解决方法:
- 确保正确设置属性的可序列化:确保User模型中的属性被正确定义为可序列化。
- 确保属性值被正确初始化:在User模型的构造函数中,确保为每个属性设置了一个初始值,以确保在返回时不会为null。如果你使用了
__init__方法,请检查构造函数中是否为每个属性赋予了默认值。 - 检查数据源:如果用户数据是从数据库等数据源中获取的,确保数据源中存在并正确包含了用户模型的属性值。
- 检查反序列化的过程:确保你使用的反序列化方式正确读取和设置了属性。根据代码,是使用
AbandonJSONResponse.model_to_dict()进行反序列化,那么请确保此方法正确读取了模型中的属性。
确保正确设置属性的可序列化
观察abandon-server/src/app/models/user.py文件,我们得出以下结论:
根据User模型代码,它是可以被序列化的。User模型继承自src.app.models.Base类,这说明它应该是一个合法的SQLAlchemy模型类。属性被定义为列(Column),这意味着它们将被正确映射到数据库表的字段。
因此,此原因排除。
确保属性值被正确初始化
观察abandon-server/src/app/models/user.py文件中的init方法,我们得出以下结论:
根据代码,模型的__init__方法中给各个属性赋予了初始值。因此,在属性初始化方面应该是正确的。
检查数据源
根据代码,我已经提前确保数据库连接没有问题;已经确保查询语句没有写错;那我是否可以看一下查询语句查询出来的结果,来确保数据源的问题?
说干就干,我们在这里,采用打断点的方式来检查,具体方式:在abandon-server/src/app/dao/auth/user_dao.py文件中对应的list_users中打上断点并检验,如图:
如此一看,是否已经恍然大悟!
原来,我们查询出来的结果,其实是一个包含了四个user信息的list,当然我们在此刻,进行一些其他属性的获取的时候,都是可以的。
但是!如果我们直接把这个list丢到返回中一起返回,就会变成null!因为此刻list中的内容,其实是一个类地址,而不是类属性!
因此!我们可以从一些其他地方佐证一下我们自己的猜想!例如:
- 我的SQL语句,如果不是查询User,而是查询User.name,那是否会正常返回?
- 如果我们把查询出来的user值,逐一循环遍历把值拿出来,是否也是一种反序列化?
验证猜想
- 尝试查询User.name
更改我们的查询语句,进行尝试:
可以看到,查询出来的确实是每个User的名字。
- 尝试将查询出来的值反序列化
第一步我们需要把查询更改回User,而不是User.name
第二步编辑listUser接口,for循环反序列化
第三步我们再用postman发起请求
结果:可以看到postman的返回依旧是null,但是此刻控制台已经打印出来完成的user信息!
上手更改代码
首先,我们是有反序列化的工具代码的,在abandon-server/src/app/customized/customized_response.py中的model_to_dict函数,以前没有注释,在此给注释完善了一下:
@staticmethod
def model_to_dict(obj, *ignore: str):
"""
将数据库模型对象转换为字典形式
:param obj: 数据库模型对象
:param ignore: 要忽略的属性名称列表
:return: 字典形式的数据
"""
if getattr(obj, '__table__', None) is None:
# 检查对象是否是一个数据库模型对象,如果不是,直接返回对象本身
return obj
data = dict()
for c in obj.__table__.columns:
if c.name in ignore:
# 如果属性名在要忽略的属性列表中,则不进行转换
continue
val = getattr(obj, c.name)
if isinstance(val, datetime):
# 如果属性值是datetime类型,则将属性值转换为指定格式的字符串
data[c.name] = val.strftime("%Y-%m-%d %H:%M:%S")
else:
# 否则,直接使用属性值
data[c.name] = val
好的,现在我们更改接口中的核心方法:
@router.get("/listUser")
async def list_users():
try:
users = await UserDao.list_users() # 获取用户列表
user_list: List[dict] = [AbandonJSONResponse.model_to_dict(user, "password") for user in users]
# 使用列表推导式遍历用户列表,将每个用户转换为字典形式,并过滤掉密码字段
# 最终得到一个包含所有用户的字典列表
return AbandonJSONResponse.success(dict(user_list=user_list))
except Exception as e:
return AbandonJSONResponse.failed(str(e))
验证
至此!BUG排查以及解决的过程也都体现在大家面前,共勉!