这是我参与「第五届青训营 」伴学笔记创作活动的第 1 天
Day1
作业一
_, err := fmt.Scanf("%d", &guess)
作业二
选用搜狗翻译,收获最大的是学会了如何自动生成json字段结构体以及繁杂的curl请求。值得一提的是,json字段的代码生成,是使用一个具体的json实例来推测字段类型的,因此会出现精度不够的情况,比如float64被推测为int。
type DictRequest struct {
// json结构体代码生成
}
type DictResponse struct {
// json结构体代码生成
}
func query(word string) {
client := &http.Client{}
request := DictRequest{...}
buf, err := json.Marshal(request)
if err != nil {
log.Fatal(err)
}
var data = bytes.NewReader(buf)
req, err := http.NewRequest("POST", "https://fanyi.sogou.com/api/transpc/text/result", data)
if err != nil {
log.Fatal(err)
}
// 繁杂地设置header
resp, err := client.Do(req)
if err != nil {
log.Fatal(err)
}
defer resp.Body.Close()
bodyText, err := ioutil.ReadAll(resp.Body)
if err != nil {
log.Fatal(err)
}
if resp.StatusCode != 200 {
log.Fatal("bad StatusCode:", resp.StatusCode, "body", string(bodyText))
}
var dictResponse DictResponse
err = json.Unmarshal(bodyText, &dictResponse)
if err != nil {
log.Fatal(err)
}
fmt.Println(dictResponse) // 字段太多,懒得筛选了
}
func main() {
if len(os.Args) != 2 {
fmt.Fprintf(os.Stderr, `usage: simpleDict WORD
example: simpleDict hello`)
os.Exit(1)
}
word := os.Args[1]
query(word)
}
作业三
go语言非常大的一个优势是,使用go程能够较容易地写出并发代码。
var wt sync.WaitGroup
wt.Add(2)
go func() {
defer wt.Done()
query_caiyun(word)
}()
go func() {
defer wt.Done()
query_sogou(word)
}()
wt.Wait()
Day2
并发编程
Channel
有无缓冲: make(chan int[, 2])
Mutex
var mutex sync.Mutex
Mutex.Lock()
x += 1
Mutex.Unlock()
WaitGroup
var wt sync.WaitGroup
wt.Add(5)
for i := 0; i < 5; i++{
go func(){
defer wt.Done()
...
}
}
wt.Wait()
依赖管理
Gopath
Go Vendor
go mudule
- go mod init,初始化,创建go.mod文件
- go get example.org/pkg
- go mod tidy
测试
单元测试
- 目标:将函数、模块的输出结果与期望值进行校对;
- 规则
- 所有测试文件以 _test.go 结尾;
- func TestXxxYyy(t *testing.T) 函数名驼峰;
- 初始化逻辑放到 TestMain 中: func TestMain(m *testing.M);
- go test [flags] [packages]运行
- assert包
- 覆盖率
Mock测试
- 幂等
- 稳定
基准测试
- 确定瓶颈,优化代码;
- 例子:rand有一个全局锁,高并发场景像能查;fastrand牺牲部分一致性,但有更高的性能。
项目实战
跟着老师的思路过了一遍,课后作业暂时没有完成。
需求背景
需求
- 展示话题(标题、文字描述)和回帖列表;
- 暂不考虑前端页面实现,仅仅实现一个本地web服务;
- 话题和回帖数据用文件存储。
实体关系
1个话题可以对应多个帖子,但一个帖子只能属于一个话题。
思路
数据层(Repository)
数据可以存储在文件中(目前),也可以存在数据库中。
// Topic
{
"id":1,
"title":"青训营开始了!",
"content":"欢迎踊跃报名!",
"create_time":1000000007
}
// Post
{
"id":1,
"parent_id":1,
"content":"欢迎欢迎热烈欢迎!",
"create_time":1000000007
}
知道了数据的json格式,可以通过自动代码生成定义相关的结构体。由于数据存储在文件中,相关索引需要自己设立以提高查询速度,目前创建的两个map分别是话题ID到话题的映射,以及话题ID到帖子列表的映射。
var (
topicIndexMap map[int64]*Topic
postIndexMap map[int64][]*Post
)
func initTopicIndexMap(filePath string) error{
}
func initPostIndexMap(filePath string) error {
open, err := os.Open(filePath + "post")
if err != nil {
return err
}
scanner := bufio.NewScanner(open)
postTmpMap := make(map[int64][]*Post)
for scanner.Scan() {
text := scanner.Text()
var post Post
if err := json.Unmarshal([]byte(text), &post); err != nil {
return err
}
posts, ok := postTmpMap[post.ParentId]
if !ok {
postTmpMap[post.ParentId] = []*Post{&post}
continue
}
posts = append(posts, &post)
postTmpMap[post.ParentId] = posts
}
postIndexMap = postTmpMap
return nil
}
逻辑层(service)
抽象出一个页面实体,包含一个话题,以及该话题下的所有帖子。
type PageInfo struct {
Topic *repository.Topic
PostList []*repository.Post
}
server端收到一个页面请求ID,也即话题ID(页面ID和话题ID一一对应),需要返回话题信息和该话题下所有帖子的信息。话题信息和帖子列表信息存在两个不同的map中,可以并发读取。
func (f *QueryPageInfoFlow) prepareInfo() error {
//获取topic信息
var wg sync.WaitGroup
wg.Add(2)
go func() {
defer wg.Done()
topic := repository.NewTopicDaoInstance().QueryTopicById(f.topicId)
f.topic = topic
}()
//获取post列表
go func() {
defer wg.Done()
posts := repository.NewPostDaoInstance().QueryPostsByParentId(f.topicId)
f.posts = posts
}()
wg.Wait()
return nil
}
视图层(controller)
将所有信息,例如code,msg,data(包括topic和postlist)整合成一个结构变量,返回给client。
测试结果
使用 jshon 工具对输出结果格式化