GO语言工程实践课后作业 | 青训营
这是我在字节跳动青训营学习的第7天,也是我参加《第六届青训营笔记伴读》的第三篇笔记
需求描述
-
发布话题和回帖
-
本地ID生成需要保证不重复,唯一性
-
Append文件,更新索引
框架(Gin)
官方介绍
Gin 是一个用 Go (Golang) 编写的 HTTP web 框架。 它是一个类似于 martini 但拥有更好性能的 API 框架, 优于 httprouter,速度提高了近 40 倍。如果你需要极好的性能,使用 Gin 吧。
引入Gin框架
1.下载并安装 gin:
shell
$ go get -u github.com/gin-gonic/gin
2.将 gin 引入到代码中:
arduino
import "github.com/gin-gonic/gin"
源码介绍
本项目相关代码已上传至github message_board
目录结构
controller视图层
主要负责处理用户输入的数据,并相应相应数据,包含两个文件
-
publish.go
用户发布数据的接口,包括发布话题,以及回复
-
query_page_info.go
用户查看数据的接口,可查看话题和对应的回复
data数据库
存储数据的文件,因为只是一个简单的项目,所以并没有用到数据库,而是使用文件存储json
格式的文件,包含两个文件
post
: 所有回复的信息topic
:所有话题的信息
repository 数据层
负责数据库的增删改查,为上层提供了简单易用的接口,包含三个文件
db_init.go
从文件中读取出所有的数据,并存储到对应的Map
中,便于后续的查找post.go
- 实现了通过话题id查找对应回复
- 实现了新增回复
- 实现了查找id的最大值(用于实现唯一id)
topic.go
- 实现了通过id查找对应话题
- 实现了新增话题
- 实现了查找id的最大值(用于实现唯一id)
service
处理业务逻辑,对数据层的数据进行处理,并将结果返回给视图层,只包含一个文件
query_page_info.go
: 查找话题及相应回复
server.go
- 程序入口,定义路由,处理路由,调用接口响应请求;
功能实现
实现思路
1.获取数据
发布话题首先要获取用户要发布的内容,包括标题和内容,如果是回复的话,需要有回复话题的id还有内容,我在设置路由时,获取到了用户的输入,校验完数据后传给controller
进行处理
2.处理数据
获取完数据后,需要对数据进行封装处理,使其成为一个结构体,需要给它设置一个id,还有时间戳, 时间戳可以通过time.now.Unix()
获取
3.获取唯一id
我的实现思路是,找到所有id中的最大值,然后加一,
4.保存数据
将结构体序列化为JSON格式,追加到文件末尾,同时更新索引
- 若是发表话题,直接新增索引就可以
- 若是发表回复,需要先判断,当前话题有没有回复,没有的话需要新创建一个数组
具体实现
在server.go
定义了两个路由,一个是发布话题/commuity/page/publish/topic
,一个是发表回复/commuity/page/publish/topic
,两个都是post方法,因为没有实现前端,所以这里使用postman进行接口测试,这两个路由主要是接受用户输入的信息,然后交给controller
处理,这里涉及到gin获取post
参数的方式
我认为,用户发表话题前端大概率是表单方式提交,所以这里默认请求参数为Form表单,使用PostForm
方法可以获取对应参数,方法参数为表单控件的名称
此处参考了掘金的一篇文章Gin框架获取请求参数的各种方式详解
go
r.POST("/community/page/publish/topic", func(context *gin.Context) {
title := context.PostForm("title")
content := context.PostForm("content")
data, err2 := cotroller.PublishTopic(title, content)
if err2 != nil {
return
}
context.JSON(200, data)
})
获取到用户的数据后,我们要对其进行封装,首先要找到id的最大值,在读文件时,我们将所有话题的信息存储到了topicIndexMap
中,这是一个map的数据结构,key为id
,value为话题信息的结构体Topic
遍历所有的key,找到最大值,再加一就是我们新插入值的id
go
func (*TopicDao) FindMaxId() int64 {
id := int64(math.MinInt64)
for k, _ := range topicIndexMap {
if k > id {
id = k
}
}
return id
}
若是发表回复的话,由于postIndexMap
是一个树形的结构,不方便我们查找最大值,所以从文件读取数据时,我们将所有回复信息都保存到了一个数组中,然后再遍历数组构造Map
,有了这个数组,就可以很简单的实现查找最大值
有了id
后,就可以对用户输入的信息进行封装了,由于逻辑比较简单,我们直接在controller层实现了,
go
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
}
数据封装好需要存储到文件中,并更新索引
封装数据
go
newTopic := Topic{
Id: id,
Title: title,
Content: content,
CreateTime: createTime,
}
更新索引
go
topicIndexMap[newTopic.Id] = &newTopic
保存到文件
go
f, err := os.OpenFile("./data/topic", os.O_APPEND|os.O_WRONLY|os.O_CREATE, 0600) //打开文件
if err != nil {
return newTopic, err
}
defer f.Close() //在函数执行完之后关闭文件
marshal, _ := json.Marshal(newTopic) //序列化
if _, err = f.WriteString(string(marshal) + "\n"); err != nil { //写入文件
return newTopic, err
}
保存文件时,由于默认每条数据后会换行,会导致下次读数据时读到空行报错Initunexpected end of JSON input
,因此在读文件时,我们需要加一个判断,若当前行为空字符串""
,直接结束文件读取
总结
麻雀虽小但五脏俱全,学到的东西也很多,对go语言的熟练度++,跟java中springboot相似,甚至更简单,但对于并发,还是不太懂,并不知道哪里会有map的并发安全问题(我好像就没用到并发)