社区话题页面实战 | 青训营

77 阅读4分钟

这是我在青训营的第6篇笔记,这篇笔记中我将在课程示例代码的基础上添加新功能,完成回帖。

课程笔记:

  • 需求描述(社区话题页面):

    • 展示话题(标题,文字描述)和回帖列表
    • 暂不考虑前端实现,仅仅实现一个本地web服务
    • 话题和回帖数据用文件存储
  • 需求用例(Topic、Post)

  • ER图(Entity Relationship Diagram)

    • Topic(id; title; content; creat_time)
    • Post(id; topic_id; content; create_time)
  • 分层结构

    • 数据层: 数据模型,外部数据的增删查改
    • 逻辑层: 业务Entity, 处理核心业务业务的逻辑输出
    • 视图层: 视图view,处理和外部的交互逻辑
  • Repository需要实现:

    • 要实现对Topic和Post两个json数据两个基本查询操作,1)根据话题id查询到这个Topic,2)通过话题id查询到该话题的所有回帖
    • 通过索引来实现查询,通过将数据行映射成为内存的Map
        var(    // 建立Topic和Post的索引
            topicIndexMap map[int64]*Topic
            postIndexMap  map[int64][]*Post
        )
    
  • Service层实现,

    • 实体包括两部分,Topic和PostList
        type PageInfo struct {
            Topic    *repository.Topic
            PostList []*repository.Post
        }
    
    • Service层实现逻辑:首先进行参数校验(需要对传入的Topic id进行非法校验), 然后通过Repostory层拿数据,最后组装成实体,代码流程编排:
        func NewPostDaoInstance() *PostDao {		// 根据话题id查询回帖列表
            postOnce.Do(
            func() {
                postDao = &PostDao{}
            })
            return postDao
        }
        func (*PostDao) QueryPostsByParentId(parentId int64) []*Post {
            return postIndexMap[parentId]
        }
    
        func NewTopicDaoInstance() *TopicDao {		// 根据话题id查询话题
            topicOnce.Do(
            func() {
            topicDao = &TopicDao{}
            })
        return topicDao
        }
        func (*TopicDao) QueryTopicById(id int64) *Topic {
            return topicIndexMap[id]
        }
    
    
  • Controller层

    • 构建View对象
    • 业务逻辑码
        type PageData struct {
            Code int64       `json:"code"`
            Msg  string      `json:"msg"`
            Data interface{} `json:"data"`
        }
        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,
            }
    
        }
    
  • Router (搭建外部框架)

    • 初始化数据索引
    • 初始化引擎配置
    • 构建路由
    • 启动服务

课后实践

  • 新增需求:

    • 支持发布帖子
    • 本地id生成需要保证不重复、唯一性
    • Append文件,更新索引,注意Map的并发安全问题
  • 功能实现

    1. 获取数据:
      发布话题首先要获取用户要发布的内容,包括标题和内容,如果是回复的话,需要有回复话题的id还有内容。
    2. 处理数据:
      在获取完数据后,需要对数据进行封装处理,使其成为一个结构体,需要给它设置一个id,还有时间戳,在这里关于id唯一性的问题,放弃了使用时间戳作为id的想法,因为短时间内的大量回帖可能会导致相同的id。
    3. 获取唯一id
      这里的思路是新发的帖子id为现有帖子最大id+1
    4. 保存数据
      将结构体序列化为JSON,追加到文件末尾,同时更新索引。
  • 实现
    首先在server.go中定义两个路由,一个用于发布话题,一个用于回帖,两个都是用POST方法,然后交给controller处理。

	r.POST("/community/page/publish/topic", func(context *gin.Context) {		// 接受POST请求用来发布新的Topic

		title := context.PostForm("title")
		content := context.PostForm("content")
	
		data, err2 := cotroller.PublishTopic(title, content)
		if err2 != nil {
			return
		}
	
		context.JSON(200, data)
	})

	r.POST("/community/page/publish/post", func(context *gin.Context) {		// 用于发布回帖
		pid, _ := strconv.ParseInt(context.PostForm("pid"), 10, 64)
		content := context.PostForm("content")

		data, err2 := cotroller.PublishPost(pid, content)
		if err2 != nil {
			return
		}

		context.JSON(200, data)
	})

publish.go内容如下:

package cotroller

import (
	"qu/repository"
	"time"
)

func PublishTopic(title string, content string) (topic repository.Topic, err error) {

	id := repository.NewTopicDaoInstance().FindMaxId() + 1
	time := time.Now().Unix()
	topic, err = repository.NewTopicDaoInstance().CreateTopic(id, title, content, time)
	if err != nil {
		return topic, err
	}
	return topic, nil
}
func PublishPost(pid int64, content string) (post repository.Post, err error) {

	id := repository.NewPostDaoInstance().FindMaxId() + 1
	time := time.Now().Unix()
	post, err = repository.NewPostDaoInstance().CreatePost(id, pid, content, time)
	if err != nil {
		return post, err
	}
	return post, nil
}

获取到用户的数据后,我们要对其进行封装,首先要找到id的最大值,在读文件时,我们将所有话题的信息存储到了topicIndexMap中,这是一个map的数据结构,key为id,value为话题信息的结构体Topic 遍历所有的key,找到最大值,再加一就是我们新插入值的id

func (*TopicDao) FindMaxId() int64 {  
id := int64(math.MinInt64)  
    for k, _ := range topicIndexMap {  
        if k > id {  
        id = k  
        }  
    }  
return id  
}

结语:

关于并发,这里可能理解的不太正确,但是尝试用postman进行高并发请求测试,结果证明采用的方法是可以满足id唯一的。