Go语言工程实践——课后实践 | 青训营

72 阅读6分钟

学了这一段时间的go语言,应该说是有所收获的,对基础的语法和一些简单的程序设计已经有了一定的基础,但同时也发现,自己在go的工程实践方面尚不能很好地完成设计和编码,不能够很好地通过需求进行代码实现,归根究底还是对开发的模式不够熟悉,因此自己通过这次的工程实践——社区帖子发布这一课后作业,对开发的思路有了一定的认识,通过阅读高手的代码思路,卒或有所闻。还是需要好好消化的。闲话少叙,进入正文!

本文的参考材料有:

(Go语言入门 - 工程实践|青训营笔记 - 掘金 (juejin.cn))

(GO语言工程实践课后作业 | 青训营 - 掘金 (juejin.cn))

需求介绍

  1. 在工程实践所给的社区互动demo基础上,增加回帖功能和发布话题功能
  2. 保证本地id唯一性
  3. 用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…)

整个项目分为三部分

  1. cotroller层(视图层)
  2. repository层(数据处理层)
  3. 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学习能力有所增益,希望在接下来的学习中能够更上一层楼。