这是我参与「第三届青训营-后端场」笔记创作活动的第2篇笔记。

青训营话题页

需求

  1. 实现一个展示话题(标题,文字描述)和回帖列表的后端 http 接口;
  2. 本地文件存储数据 组件及技术点
    • 了解go web框架的简单使用
    • 了解分层设计的概念
  • 文件操作:读文件pkg.go.dev/io

课堂例子

因为这个例子中并没有采用数据库,因此采用文件来存储数据,每一条数据都是一个json字符串,如下图

image.png

image.png

通过定义Post、Topic结构体来作为实体化对象

type Post struct {
   Id         int64  `json:"id"`
   ParentId   int64  `json:"parent_id"`
   Content    string `json:"content"`
   CreateTime int64  `json:"create_time"`
}
复制代码
type Topic struct {
   Id         int64  `json:"id"`
   Title      string `json:"title"`
   Content    string `json:"content"`
   CreateTime int64  `json:"create_time"`
}
复制代码

因为数据是存储在文件里,如果每次操作都打开文件会影响程序的运行时间,增加开销,因此我们在程序初始化时就应该把数据读取出来并且建立索引,这样之后就可以直接对索引操作,速度会明显提升:

image.png

通过创建两个map来作为索引,map的时间复杂度为O(1)

  • TopicIndexMap是根据话题id号来作为key,value为Topic实体
  • postIndexMap是根据所属话题id号来作为key,value为post切片

读取文件的数据,并建立索引:

func initTopicIndexMap(filePath string) error {
   open, err := os.Open(filePath + "topic")
   if err != nil {
      return err
   }
   scanner := bufio.NewScanner(open)
   topicTmpMap := make(map[int64]*Topic)
   for scanner.Scan() {
      text := scanner.Text()
      var topic Topic
      if err := json.Unmarshal([]byte(text), &topic); err != nil {
         return err
      }
      topicTmpMap[topic.Id] = &topic
   }
   TopicIndexMap = topicTmpMap
   return nil
}

func initPostIndexMap(filePath string) error {
   open, err := os.Open(filePath + "post")
   if err != nil {
      return err
   }
   scanner := bufio.NewScanner(open)
   postTmpMap := make(map[int64][]*Post)
   for scanner.Scan() {
      text := scanner.Text()
      var post Post
      //将扫描到的帖子数据序列化为一个json对象
      if err := json.Unmarshal([]byte(text), &post); err != nil {
         return err
      }
      posts, ok := postTmpMap[post.ParentId]
      //如果话题id不存在于postTmpMap中
      if !ok {
         //空索引实现
         postTmpMap[post.ParentId] = []*Post{&post}
         continue
      }
      //如果话题id存在,则直接将每个post追加到post切片中
      posts = append(posts, &post)
      //建立索引
      postTmpMap[post.ParentId] = posts
   }
   postIndexMap = postTmpMap
   return nil
}
复制代码

再从main函数出发:

gin框架路由初始化,添加一个GET路由请求处理,解析请求参数,通过请求参数的topicId来获取话题信息和话题所有的帖子信息 image.png

在控制层的cotroller中,定义了QueryPageInfo函数用于查询页面数据

func QueryPageInfo(topicIdStr string) *PageData {
   //转化为整型
   topicId, err := strconv.ParseInt(topicIdStr, 10, 64)
   if err != nil {
      return &PageData{
         Code: -1,
         Msg:  err.Error(),
      }
   }
   //在服务层中获取数据
   pageInfo, err := service.QueryPageInfo(topicId)
   if err != nil {
      return &PageData{
         Code: -1,
         Msg:  err.Error(),
      }
   }
   return &PageData{
      Code: 0,
      Msg:  "success",
      Data: pageInfo,
   }

}
复制代码

通过服务层来获取数据,又因为获取topic信息和post列表是互不干扰的,因此可开启两个协程来加速获取

image.png

获取数据后便可以作为响应返回:

image.png

开启gin,监听3000端口:

image.png

运行,postman发起请求,请求成功,成功获取数据

image.png

image.png

课后作业

  1. 支持对话题发布回帖。

  2. 回帖 id 生成需要保证不重复、唯一性。

  3. 新加回帖追加到本地文件,同时需要更新索引,注意 Map 的并发安全问题。

    思路:前端发起Post请求,将帖子信息(帖子内容,话题id)通过表单发送给后端,后端解析表单参数,再根据当前时间戳来生成唯一帖子id(在非高并发的情况下,id是唯一的,也可以通过雪花算法来生成唯一id),然后对参数处理,生成对应的Post实体,写入文件中,并更新索引。

主要代码:

这里是添加一个POST请求的路由处理,解析表单参数,传给控制层 image.png ReleasePost函数对数据进行处理,并且生成当前时间戳作为帖子发布时间

image.png

服务层:

func ReleasePost(parent_id int64, content string, create_time int64) error {
   return NewReleasePostFlow(parent_id, content, create_time).Do()
}

func NewReleasePostFlow(parent_id int64, content string, create_time int64) *ReleasePostFlow {
   return &ReleasePostFlow{
      ParentId:   parent_id,
      Content:    content,
      CreateTime: create_time,
   }
}

type ReleasePostFlow struct {
   ParentId   int64
   Content    string
   CreateTime int64
}

func (f *ReleasePostFlow) Do() error {
   if err := f.checkParam(); err != nil {
      return err
   }
   if err := f.addPost(); err != nil {
      return err
   }
   return nil
}

//检验参数
func (f *ReleasePostFlow) checkParam() error {
   //检验帖子内容是否为空
   if f.Content == "" {
      return errors.New("content is not empty!  ")
   }
   //检验话题id是否存在
   _, ok := repository.TopicIndexMap[f.ParentId]
   if !ok {
      return errors.New("topic id not exist")
   }

   return nil
}

func (f *ReleasePostFlow) addPost() error {
   //使用当前时间戳来生成唯一ID,也可以使用雪花算法生成帖子id
   id := f.CreateTime + 10
   //生成Post实例化
   Post := repository.Post{
      Id:         id,
      ParentId:   f.ParentId,
      Content:    f.Content,
      CreateTime: f.CreateTime,
   }
   err := repository.AddPost(Post)
   if err != nil {
      return err
   }
   return nil
}
复制代码

在数据层中对文件进行操作,写入成功后更新索引

image.png

运行,postman模拟发起请求:

image.png 请求成功!文件中也成功写入!

image.png 再通过GET请求获取,也成功获取新添加的帖子,说明索引更新成功!

image.png