Go语言入门 - 工程实践(学习笔记和课后练习) | 青训营笔记

190 阅读4分钟

这是我参与「第三届青训营 -后端场」笔记创作活动的的第2篇笔记。

学习笔记

记录学习过程中方便以后Go语言开发用来查看的笔记。

并发

通道

Channel类似一个管道,方便并发核心单元通讯。

操作符:<-,箭头指向数据传输方向

创建make(chan 元素类型,[缓冲大小])

Lock

声明lock sync.Mutex

使用

lock.Lock()
/* code */
lock.Unlock()

WaitGroup

方法Add(delta int)计数器+delta,Done()计数器-1,Wait()阻塞直到计数器为0。

依赖管理

依赖顺序Go Module->Go Vendor->GOPATH

Go Modulego.mod文件管理依赖包版本,go get/mod指令管理依赖包。

go get example.org/pkg@update(默认)/none(删除依赖)/v1.1.2(tag版本)/23dfdd5(特定commit)/master(最新commit)

go mod init(初始化go.mod)/download(下载到本地缓存)/tidy(增加需要的依赖,删除不需要的依赖)

编译时会选择最低的兼容版本。

测试

单元测试

规则

  1. 测试文件以_test.go结尾
  2. 测试函数func TestXxx(t *testing.T)
  3. 初始化逻辑放到TestMain

assert

import增加github.com/stretchr/testify/assert

覆盖率

go test xxx_test.go xxx.go --cover

项目实践

这节课的项目实践是一个简单的社区话题页面的后端处理逻辑。

项目地址:github.com/Moonlight-Z…

项目clone到本地后默认处于master分支,master分支貌似需要本地MySQL数据库开放3306端口,我不太熟悉数据库,所以切换到了v0.1分支上,使用以下命令:

git clone https://github.com/Moonlight-Zhao/go-project-example.git
cd go-project-example
git checkout v0.1

没有接触过相关的业务,所以梳理代码用了比较长的时间。

  1. 先从server.go开始,这里用到了一个web框架gin,这个框架可以查看手册学习一下大概的使用方法,这里用到了ParamGetPostForm获取参数,之后变调用了下一层cotroller

  2. cotroller的代码有query_page_info.go,用来请求获得页面信息,以及publish_post.go,用来给帖子回帖。cotroller层的代码会调用service层。

  3. service的功能是具体实现请求页面和发布的功能,包括检查参数(内容长度不超过500),为新回帖创建实体存储数据等,这里还用到了一个工具idworker,为每个帖子和回复生成不重复的ID。

  4. 在请求页面和发布时,会调用最底层repository,这一层的db_init.go实现了初始化数据库,在v0.1分支上是实现文件系统管理内容。这一层还有post.go文件,实现新回帖时对文件系统的操作,保存数据。

梳理完代码,就可以尝试使用一下功能了,因为是老师给定的代码,所以不需要调试了,直接go run server.go

image.png

可以看到监听了8080端口,那么另起一个终端,获取数据。

image.png

初始一共只有2个帖子,URL后面用1和2分别获取2个帖子的内容,这里用到了jq,让json的数据更直观,在archlinux可以用sudo pacman -S jq安装。直接打开浏览器也一样可以看到。

image.png

网页查看的时候可以安装JSON-handle插件让内容更直观,这个插件是群里的小伙伴推荐的,感谢!

我们也可以测试一下回帖,可以看到返回了success

image.png

GET一下,可以看到回帖了:

image.png

课后实践

课后作业要求增加发帖功能,保证ID唯一,Append文件更新索引,注意Map的并发安全问题。

既然梳理清楚了代码,仿照回帖功能做发帖功能即可。

  1. 修改server.go,在main函数中增加发帖的请求:
r.POST("/community/topic/do", func(c *gin.Context) {
    title, _ := c.GetPostForm("title")
    content, _ := c.GetPostForm("content")
    data := cotroller.PublishTopic(title, content)
    c.JSON(200, data)
})
  1. 上面我们需要在cotroller层调用一个新函数PublishTopic,在cotroller层新增一个文件publish_topic.go来实现它:
package cotroller

import (
	"github.com/Moonlight-Zhao/go-project-example/service"
)


func PublishTopic(title, content string) *PageData {
	//获取service层结果
	topicId, err := service.PublishTopic(title, content)
	if err != nil {
		return &PageData{
			Code: -1,
			Msg:  err.Error(),
		}
	}
	return &PageData{
		Code: 0,
		Msg:  "success",
		Data: map[string]int64{
			"topic_id": topicId,
		},
	}

}
  1. 这一层又调用了service层的新函数PublishTopic,在service层新增一个文件publish_topic.go来实现它:
package service

import (
	"errors"
	"time"
	"unicode/utf16"
	"github.com/Moonlight-Zhao/go-project-example/repository"
)

func PublishTopic(title, content string) (int64, error) {
	return NewPublishTopicFlow(title, content).Do()
}

func NewPublishTopicFlow(title, content string) *PublishTopicFlow {
	return &PublishTopicFlow{
        title: title,
		content: content,
	}
}

type PublishTopicFlow struct {
    title string
	content string
	topicId int64
}

func (f *PublishTopicFlow) Do() (int64, error) {
	if err := f.checkParam(); err != nil {
		return 0, err
	}
	if err := f.publish(); err != nil {
		return 0, err
	}
	return f.topicId, nil
}

func (f *PublishTopicFlow) checkParam() error {
	if len(utf16.Encode([]rune(f.content))) >= 500 {
		return errors.New("content length must be less than 500")
	}
	return nil
}

func (f *PublishTopicFlow) publish() error {
	topic := &repository.Topic{
        Title:      f.title,
		Content:    f.content,
		CreateTime: time.Now().Unix(),
	}
	id, err := idGen.NextId()
	if err != nil {
		return err
	}
	topic.Id = id
	if err := repository.NewTopicDaoInstance().InsertTopic(topic); err != nil {
		return err
	}
	f.topicId = topic.Id
	return nil
}
  1. 这一层又调用了repository的新函数InsertTopic,在这一层的topic.go文件中实现它:
func (*TopicDao) InsertTopic(topic *Topic) error {
	f, err := os.OpenFile("./data/topic", os.O_APPEND|os.O_WRONLY|os.O_CREATE, 0600)
	if err != nil {
		return err
	}

	defer f.Close()
	marshal, _ := json.Marshal(topic)
	if _, err = f.WriteString(string(marshal)+"\n"); err != nil {
		return err
	}

	rwMutex.Lock()
	_, ok := topicIndexMap[topic.Id]
	if !ok {
		topicIndexMap[topic.Id] = topic
	}
	rwMutex.Unlock()
	return nil
}

OK,不出意外的话,处理一下import就可以尝试运行了,发个贴看看:

image.png

收到了success,查看一下有没有保存好新帖子,注意使用返回的topic_id

image.png

帖子发出去了,我们给新帖子回复一下:

image.png

查看回帖有没有保存好:

image.png

也可以查看项目中data文件夹看到发布的新帖子:

image.png

哎呀,没有换行,不过作业里面可没要求这一条,累了,歇歇再搞,初学后端,如果有错误的地方和表述不正确的地方,请多指正,谢谢~