【青训营】douyin项目的坑点和领悟

151 阅读3分钟

这是我的第5篇笔记

bug

  • 想在判断user是否存在,找不到时返回err,又少写语句

    • DB.Where("name=?", name).Error,无论如何都不会返回err

    • DB.Debug().First(&o, "name=?", "asd").Error,这样才会返回err,当找不到时也会

    • 上面方案都不行,改为DB.Model(&UserLogin{}).Where("name=?", name).Count(&c),比较好

      • 因为不需要err,也不要不存在时返回err
  • 绑定query的bug,绑定query应该用form标签,否则不能把query的数据绑定到结构体里

  • 用密匙进行token签名时,一定要传入切片

  • 所有数字都是float64,除非用反射拿到对应的类型

    • 在用jwt4.MapClaims拿token.Claims里面的MapClaims值时,因为这只是map,所以所有数字为float64,如果是结构体就用反射拿到类型
    • 所以拿id时注意转为int64
    • 不要忘了c.Set(mw.IdentityKey, id),不然可能不太好取值,当然,直接返回也是可以的
  • create_time gorm自动生成,比如2022-06-01 23:48:20

    • gorm设置为Local或者Asia%2fShanghai(%2f是gorm要求的),即中国

      • 问题,把时间字符串转为时间戳 出错,多了8小时

        • Parse函数返回UTC,或者在layout和timestr都有时区,返回timestr的时区

          • 这很麻烦,
        • ParseInLocation函数则只返回传入的时区,

          • 可以是time.Local,系统时间
          • 也可以是自定义,如chinaZone, err := time.LoadLocation("Asia/Shanghai")
          • 时区文件可以在go的root目录下的 lib\time\zoneinfo.zip 查看
    • 当时把用Parse解析的拿到请求了,所以错了,这个是UTC时间戳,多了8小时

      • 现在用ParseInLocation就没问题了
      • 教训:解析字符串为时间用Parse函数和ParseInLocation函数要格外注意,其他倒不用注意

麻烦事

  • 当用jwt中间件时

    • 要额外自定义Unauthorized,LoginResponse

      • LoginResponse返回id和token

        • token就是LoginResponse参数message

        • 但是id就不好拿了,id可以用设置IdentityKey为"id",这个可以通过ExtractClaims,c.Keys,c.Get()拿取,但是,这3个都是在Keys这个map里

          • 而且只有在middlewareImpl调用时,才会 c.Set("JWT_PAYLOAD", claims), c.Set(mw.IdentityKey, identity)
          • 而且middlewareImpl是GinJWTMiddleware的handler方法,是要创建GinJWTMiddleware对象后才能通过调用其MiddlewareFunc()方法返回一个函数,这个函数只调用middlewareImpl方法,这是用来做中间件方法--handler的
          • 还可以通过GetClaimsFromJWT()拿取,但这个要用GinJWTMiddleware对象调用,在设置LoginResponse时都还没生成呢
          • 于是我在Authenticator里用CheckUser返回的userID来调用c.Set(constants.IdentityKey, userID),因为Authenticator是最先调用的,所以在LoginResponse用没问题,当用middlewareImpl时也只是重新设为userID而已
    • 不能从表单拿token,太恶心了,因为推送视频的request的token在表单上,为什么??。。。

    • 有一个大大的bug

      • id和token的id不对称也会被验证成功,解决方法:使用Authorizator判断
      • Authorizator是对user的再次验证,func(data interface{}, c *gin.Context) bool
      • data是Identity的value,即id
      • 如果request上传了user_id,必须在这里判断这个user_id是否等于Identity
      •  Authorizator: func(data interface{}, c *gin.Context) bool {
             //如果没有userId则不用进行这种隐私校验
             //本来,如果没有配置这个函数,也是直接返回true
             qid := c.Query(constants.UserIdQuery)
             if qid == "" {
                 return true
             }
             queryId, err := strconv.ParseInt(qid, 10, 64)
             if err != nil {
                 return false
             }
         ​
             //Authorizator,c.keys["id"]=%!d(float64=1) float64
             //这是因为MapClaims只是map,因此从token字符串解析的是float64
             id, ok := data.(float64)
             if !ok {
                 return false
             }
             tokenId := int64(id)
         ​
             return tokenId == queryId
         },
        

领悟

  • 事务

    • 做多个修改,要么都成功,要么都失败
  • service使用Flow,让Flow这个结构体保存参数,不要一直在函数里传

    • 当然,这是要对参数做很多处理(可能由这个参数又参数其他参数),所以保存在Flow里,减少传参,还有方便调用
    • 当然,如果对参数做的处理极少,那么可以不要Flow,反而更好
  • 查询数据库时,可以选择把对象地址给dao层,这样就不用再返回这个对象了

    • 最好还是只在service和dao层之间这么做,不要在handler和service之间也这么做

\