学了这一段时间的go语言,应该说是有所收获的,对基础的语法和一些简单的程序设计已经有了一定的基础,但同时也发现,自己在go的工程实践方面尚不能很好地完成设计和编码,不能够很好地通过需求进行代码实现,归根究底还是对开发的模式不够熟悉,因此自己通过这次的工程实践——社区帖子发布这一课后作业,对开发的思路有了一定的认识,通过阅读高手的代码思路,卒或有所闻。还是需要好好消化的。闲话少叙,进入正文!
本文的参考材料有:
(Go语言入门 - 工程实践|青训营笔记 - 掘金 (juejin.cn))
(GO语言工程实践课后作业 | 青训营 - 掘金 (juejin.cn))
需求介绍
- 在工程实践所给的社区互动demo基础上,增加回帖功能和发布话题功能
- 保证本地id唯一性
- 用append添加文件,要更新索引
框架引入
这里用到的是Gin框架,Gin框架是用Go语言写的后端web框架,简洁、轻量、支持高并发,具有极高的性能。在项目中我们要追求高性能的话,很适合用这个框架。
安装gin框架即在命令行中打入以下命令
go get -u github.com/gin-gonic/gin
在代码中引入Gin框架则是用一下代码
import "github.com/gin-gonic/gin"
实现思路
原项目地址:(github.com/Moonlight-Z…)
整个项目分为三部分
- cotroller层(视图层)
- repository层(数据处理层)
- server.go(程序入口)
下面对每个层进行逐一解析。
1. cotroller层
这部分主要包括两个文件:publish.go和query_page_info.go
- publish.go: 主要获取话题发布的数据和回帖的数据输入,并处理相关数据。
- query_page_info.go:获取用户查看数据的接口,用户可以通过此查看帖子数据。
2.repository层
这部分负责数据库的操作,同时负责为上层提供相应的接口,主要包括下列3个文件。
- db_init.go:负责数据的初始化,并存取到相应的map中,方便之后的操作。
- post.go:实现通过话题id查找回复;查找唯一id;实现新增回帖。
- topic.go:支持查找话题,通过话题id查找帖子内容,查找唯一id。
3.server.go
提供程序入口,定义和处理路由,调用接口和响应。
首先我们要先了解程序的执行流程,从层次上讲:入口->cotroller层->repository层->service层,在这个demo中,要增加发布帖子和回帖功能,需要在原先demo基础上于cotroller增加publish.go,在post.go和topic.go进行完善,在server.go部分增加路由定义和处理。
总体思路
分为以下几个部分。
1.数据获得
发布帖子需要获得用户的发布的标题和内容,如果是回帖还要有回复的话题的id和内容,这一部分需要再server.go部分设置新的路由,获取用户输入的数据,校验完以后传回cotroller层进行处理。
2.数据处理
在新增的publish层予以定义两个函数:话题发布和帖子回复发布函数,为每一个新的发布都设置一个新的id,发布先后用时间戳来区分,这里需要注意并发性的设计,因为为了防止在对同一个话题同时回复和保证id的唯一性,因此在这两部分要进行并发互斥处理。
3.数据保存
将数据各部分转为json格式,追加至data末尾,同时更新索引。
代码详解
原先demo可以通过上面所附的地址进行下载,这里只对新增的部分代码进行解析。
1.server.go
r.POST("/community/page/publish/topic", func(ctx *gin.Context) {
//发布话题
title := ctx.PostForm("title")
content := ctx.PostForm("content")
data, err1 := cotroller.TopicPublish(title, content)
if err1 != nil {
return
}
ctx.JSON(200, data)
})
r.POST("/community/page/publish/post", func(ctx *gin.Context) {
//发布回帖
parentid, _ := strconv.ParseInt(ctx.PostForm("parentid"), 10, 64)
content := ctx.PostForm("content")
data, err1 := cotroller.PostPublish(parentid, content)
if err1 != nil {
return
}
ctx.JSON(200, data)
})
这里新增了发布话题和发布回帖的新路由,通过postform方法获取每个key对应的值,在话题路由中,用TopicPublish函数处理数据;在回帖路由中,获得id中用到字符串转换的方法,通过PostPublish函数处理回帖数据,最后根据请求状态码是否为200判断路由成功与否。
2.publish.go
这个是为处理发布帖子新增的一个文件,其中包含两个处理函数如下:
package cotroller
import (
"time"
"github.com/Moonlight-Zhao/go-project-example/repository"
)
func TopicPublish(title string, content string) (topic repository.Topic, err error) {
id := repository.NewTopicDaoInstance().WeiyiId()
time := time.Now().Unix() //获得秒
topic, err = repository.NewTopicDaoInstance().TopicCreate(id, title, content, time)
if err != nil {
return topic, err
}
return topic, nil
}
func PostPublish(parentid int64, content string) (post repository.Post, err error) {
id := repository.NewPostDaoInstance().WeiyiId()
time := time.Now().Unix() //获得秒
post, err = repository.NewPostDaoInstance().PostCreate(id, parentid, content, time)
if err != nil {
return post, err
}
return post, nil
}
总体思路都是先找到唯一的id,(根据时间戳先后),再通过repository层的话题创建函数和回复创建函数获得话题和帖子回复。
3.topic.go和post.go
话题创建函数如下。
var lock = &sync.Mutex{}
func (*TopicDao) TopicCreate(id int64, title string, content string, create int64) (topic Topic, err error) {
lock.Lock()
defer lock.Unlock()
Topic_new := Topic{
Id: id,
Title: title,
Content: content,
CreateTime: create,
}
topicIndexMap[Topic_new.Id] = &Topic_new
file, err := os.OpenFile("./data/topic", os.O_APPEND|os.O_WRONLY|os.O_CREATE, 0600)
if err != nil {
return Topic_new, err
}
defer file.Close()
marshal, _ := json.Marshal(Topic_new)
if _, err = file.WriteString(string(marshal) + "\n"); err != nil {
return Topic_new, err
}
return Topic_new, nil
}
这里首先要注意并发性设置,因为要防止同时发布话题的情况,所以在一开始加了互斥锁。
更新索引部分
topicIndexMap[Topic_new.Id] = &Topic_new
保存到文件
file, err := os.OpenFile("./data/topic", os.O_APPEND|os.O_WRONLY|os.O_CREATE, 0600)
if err != nil {
return Topic_new, err
}
defer file.Close()
marshal, _ := json.Marshal(Topic_new)
if _, err = file.WriteString(string(marshal) + "\n"); err != nil {//序列化操作
return Topic_new, err
}
return Topic_new, nil
回帖创建函数结构大体同话题创建,这里不再赘述。
还想指出的一个地方是在post.go与topic.go中查询唯一id的一个方法。在查找唯一id的方法中,参考了参考资料中的方法,查找当前最大id后加1。
话题发布查找id的方法如下,这里需要加上互斥锁,通过遍历所有key找到最大值再加一即可。
func (*TopicDao) WeiyiId() int64 {
lock.Lock()
defer lock.Unlock()
idmax := int64(math.MinInt64)
for no, _ := range topicIndexMap {
if no > idmax {
idmax = no
}
}
return idmax + 1
}
回帖发布查找id的方法如下,同样要加上互斥锁。
func (*PostDao) WeiyiId() int64 {
lock1.Lock()
defer lock1.Unlock()
idmax := int64(math.MinInt64)
for no := 0; no < len(posts); no++ {
if posts[no].Id > idmax {
idmax = posts[no].Id
}
}
return idmax + 1
}
这里的posts是一个数组,我们将所有信息存到一个数组中,再遍历创建一个map,然后按照topic中的方法查询就可以。
posts []*Post
总结
总的来说这项作业是有很大收获的,虽然只是在原先demo的基础上增加功能和模仿,但在理清思路,查询资料以及开发上于我而言有很大增益,go学习能力有所增益,希望在接下来的学习中能够更上一层楼。