14-解决users反序列化的bug

173 阅读5分钟

回顾

接上篇,还记得我们昨天/auth/listUser这个接口内容,在返回的时候data中是null吗,要知道,其实这不是本意,这是一个已知的User的Moudel的反序列化的一个bug。但是因为昨天时间有些晚,再加上还没有带过大家去一步一步剖析bug产生的原因,bug的解决方案的一整套步骤,所以把解决bug放在了今天。bug如图:

image.png

想要的效果图:

这里只是因为太长,所以用raw来展示示例来方便大家观看。

image.png

BUG判断

返回时发现用户模型中的属性值为null,则可能是由于属性没有被正确反序列化或初始化。我们准备从以下是几个可能的原因和解决方法:

  1. 确保正确设置属性的可序列化:确保User模型中的属性被正确定义为可序列化。
  2. 确保属性值被正确初始化:在User模型的构造函数中,确保为每个属性设置了一个初始值,以确保在返回时不会为null。如果你使用了__init__方法,请检查构造函数中是否为每个属性赋予了默认值。
  3. 检查数据源:如果用户数据是从数据库等数据源中获取的,确保数据源中存在并正确包含了用户模型的属性值。
  4. 检查反序列化的过程:确保你使用的反序列化方式正确读取和设置了属性。根据代码,是使用AbandonJSONResponse.model_to_dict()进行反序列化,那么请确保此方法正确读取了模型中的属性。
确保正确设置属性的可序列化

观察abandon-server/src/app/models/user.py文件,我们得出以下结论:

根据User模型代码,它是可以被序列化的。User模型继承自src.app.models.Base类,这说明它应该是一个合法的SQLAlchemy模型类。属性被定义为列(Column),这意味着它们将被正确映射到数据库表的字段。

image.png

因此,此原因排除。

确保属性值被正确初始化

观察abandon-server/src/app/models/user.py文件中的init方法,我们得出以下结论:

根据代码,模型的__init__方法中给各个属性赋予了初始值。因此,在属性初始化方面应该是正确的。

image.png

检查数据源

根据代码,我已经提前确保数据库连接没有问题;已经确保查询语句没有写错;那我是否可以看一下查询语句查询出来的结果,来确保数据源的问题?

说干就干,我们在这里,采用打断点的方式来检查,具体方式:在abandon-server/src/app/dao/auth/user_dao.py文件中对应的list_users中打上断点并检验,如图:

image.png

image.png

如此一看,是否已经恍然大悟!

原来,我们查询出来的结果,其实是一个包含了四个user信息的list,当然我们在此刻,进行一些其他属性的获取的时候,都是可以的。

但是!如果我们直接把这个list丢到返回中一起返回,就会变成null!因为此刻list中的内容,其实是一个类地址,而不是类属性!

因此!我们可以从一些其他地方佐证一下我们自己的猜想!例如:

  1. 我的SQL语句,如果不是查询User,而是查询User.name,那是否会正常返回?
  2. 如果我们把查询出来的user值,逐一循环遍历把值拿出来,是否也是一种反序列化?
验证猜想
  1. 尝试查询User.name

更改我们的查询语句,进行尝试:

image.png

image.png

可以看到,查询出来的确实是每个User的名字。

  1. 尝试将查询出来的值反序列化

第一步我们需要把查询更改回User,而不是User.name

第二步编辑listUser接口,for循环反序列化

image.png

第三步我们再用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))
验证

image.png

至此!BUG排查以及解决的过程也都体现在大家面前,共勉!