这是我在青训营的第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的并发安全问题
-
功能实现
- 获取数据:
发布话题首先要获取用户要发布的内容,包括标题和内容,如果是回复的话,需要有回复话题的id还有内容。 - 处理数据:
在获取完数据后,需要对数据进行封装处理,使其成为一个结构体,需要给它设置一个id,还有时间戳,在这里关于id唯一性的问题,放弃了使用时间戳作为id的想法,因为短时间内的大量回帖可能会导致相同的id。 - 获取唯一id
这里的思路是新发的帖子id为现有帖子最大id+1 - 保存数据
将结构体序列化为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唯一的。