Go语言 基于gin框架从0开始构建一个bbs server(五)- 帖子详情,帖子列表分页,id失真

915 阅读3分钟

源码

帖子详情

这个比较简单了,之前做过多次。

注册路由:

v1.GET("/post/:id", controllers.GetPostDetailHandler)

controller:

// 获取帖子详情
func GetPostDetailHandler(c *gin.Context) {
   postIdStr := c.Param("id")
   postId, err := strconv.ParseInt(postIdStr, 10, 64)
   // 校验参数是否正确
   if err != nil {
      zap.L().Error("GetPostDetailHandler", zap.Error(err))
      ResponseError(c, CodeInvalidParam)
      return
   }
   data, err := logic.GetPostDetail(postId)
   if err != nil {
      zap.L().Error("GetPostDetailHandler", zap.Error(err))
      ResponseError(c, CodeServerBusy)
      return
   }
   ResponseSuccess(c, data)
}

logic

func GetPostDetail(id int64) (model *models.Post, err error) {
   return mysql.GetPostDetail(id)

}

dao:

func GetPostDetail(id int64) (post *models.Post, err error) {
   post = new(models.Post)
   sqlStr := "select post_id,title,content,author_id,community_id,create_time,update_time " +
      " from post where post_id=?"
   err = db.Get(post, sqlStr, id)
   if err != nil {
      // 空数据的时候 不算错误 只是没有板块而已
      if err == sql.ErrNoRows {
         zap.L().Warn("no community ")
         err = nil
      }
   }
   return post, err
}

看下效果

image.png

优化帖子详情接口

上述的帖子接口其实还有些问题 主要是 返回的数据里面只有 作者id和板块id 这样的可读性太差

我们来稍微修改一下即可

新增一个结构体:


type ApiPostDetail struct {
   AuthorName string `json:"author_name"`
   *Community `json:"_community"`
   *Post      `json:"_post"`
}

再改下我们的logic 就合乎要求了:

func GetPostDetail(id int64) (apiPostDetail *models.ApiPostDetail, err error) {
   //先查帖子实体
   post, err := mysql.GetPostDetail(id)
   //再查 作者 名称
   username, err := mysql.GetUserNameById(post.AuthorId)
   if err != nil {
      zap.L().Warn("no author ")
      err = nil
   }
   //再查板块实体
   community, err := GetCommunityById(post.CommunityId)
   if err != nil {
      zap.L().Warn("no community ")
      err = nil
   }
   apiPostDetail = new(models.ApiPostDetail)
   apiPostDetail.AuthorName = username

   apiPostDetail.Community = community
   apiPostDetail.Post = post

   return apiPostDetail, err

}

image.png

分页展示帖子列表

分页查询其实也不难,主要就是sql 语句那边稍微改一下,然后logic层多了一个for 循环来拼接我们的查询结果

v1.GET("/postlist", controllers.GetPostListHandler)
func GetPostListHandler(c *gin.Context) {
   pageSizeStr := c.Query("pageSize")
   pageNumStr := c.Query("pageNum")

   pageSize, err := strconv.ParseInt(pageSizeStr, 10, 64)
   if err != nil {
      ResponseError(c, CodeInvalidParam)
      return
   }

   pageNum, err := strconv.ParseInt(pageNumStr, 10, 64)
   if err != nil {
      ResponseError(c, CodeInvalidParam)
      return
   }

   if pageNum < 1 {
      ResponseErrorWithMsg(c, CodeInvalidParam, "页码不可小于1")
      return
   }

   data, err := logic.GetPostList(pageSize, pageNum)
   if err != nil {
      zap.L().Error("GetPostDetailHandler", zap.Error(err))
      ResponseError(c, CodeServerBusy)
      return
   }
   ResponseSuccess(c, data)
}
func GetPostList(pageSize int64, pageNum int64) (apiPostDetailList []*models.ApiPostDetail, err error) {

   var offset int64
   offset = pageSize * (pageNum - 1)
   postList, err := mysql.GetPostList(offset, pageSize)
   if err != nil {
      return nil, err
   }
   apiPostDetailList = make([]*models.ApiPostDetail, 0, 2)
   for _, post := range postList {
      //再查 作者 名称
      username, err := mysql.GetUserNameById(post.AuthorId)
      if err != nil {
         zap.L().Warn("no author ")
         err = nil
         return nil, err
      }
      //再查板块实体
      community, err := GetCommunityById(post.CommunityId)
      if err != nil {
         zap.L().Warn("no community ")
         err = nil
         return nil, err
      }
      apiPostDetail := new(models.ApiPostDetail)
      apiPostDetail.AuthorName = username
      apiPostDetail.Community = community
      apiPostDetail.Post = post
      apiPostDetailList = append(apiPostDetailList, apiPostDetail)
   }
   return apiPostDetailList, nil
}
func GetPostList(offset int64, pageSize int64) (posts []*models.Post, err error) {
   zap.L().Info("GetPostList", zap.String("offset", strconv.FormatInt(offset, 10)), zap.String("pageSize", strconv.FormatInt(pageSize, 10)))
   sqlStr := "select post_id,title,content,author_id,community_id,create_time,update_time " +
      " from post limit ?,?"
   posts = make([]*models.Post, 0, pageSize)
   err = db.Select(&posts, sqlStr, offset, pageSize)
   if err != nil {
      return nil, err
   }
   return posts, err

}

前端id 失真

上述的代码 我们都是用雪花算法 来生成 一个int64的 id,并且在json中 我们返回的是一个数字 并不是一个字符串 这会导致一个问题,在go语言中 int64的最大值如下:

max: 9223372036854775807

然而前端的类型中 将数字 视为 number类型 最大值是2的53次方 9007199254740992

明显这个int64 比这个值要大

实际中 前端在拿到这个数字 解析出来就会出现精度丢失的问题了。

所以 通常而言,作为服务端 对于id类型的 我们还是在json中返回成字符串比较好。

同样的 前端在给我们传递值也一样最好约定成字符串 ,这样服务端拿到值以后 自己做相应的转换是最保险的

在go语言中

image.png

我们只要在设置tag的地方 标识一下string就可以了

当然你也可以对你的struct来定义 序列化和反序列化的方法,也可以解决这个问题。但是显然效率是不如这个tag的写法的