实践:发布话题和回帖之GO语言工程实践 | 青训营

108 阅读5分钟

发布话题和回帖之GO语言工程实践

本文章为青训营GO语言工程实践课后作业

需求描述

  1. 发布话题和回帖
  2. 本地ID生成需要保证不重复,唯一性
  3. Append文件,更新索引

框架(Gin)

官方介绍

Gin 是一个用 Go (Golang) 编写的 HTTP web 框架。 它是一个类似于 martini 但拥有更好性能的 API 框架, 优于 httprouter,速度提高了近 40 倍。如果你需要极好的性能,使用 Gin 吧。

引入Gin框架

1.下载并安装 gin:

$ go get -u github.com/gin-gonic/gin

2.将 gin 引入到代码中:

import "github.com/gin-gonic/gin"

源码介绍

本项目相关代码已上传至github github.com/shenxiaoyuc…

目录结构

image.png

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框架获取请求参数的各种方式详解

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

func (*TopicDao) FindMaxId() int64 {  
id := int64(math.MinInt64)  
    for k, _ := range topicIndexMap {  
        if k > id {  
        id = k  
        }  
    }  
return id  
}

若是发表回复的话,由于postIndexMap是一个树形的结构,不方便我们查找最大值,所以从文件读取数据时,我们将所有回复信息都保存到了一个数组中,然后再遍历数组构造Map,有了这个数组,就可以很简单的实现查找最大值

有了id后,就可以对用户输入的信息进行封装了,由于逻辑比较简单,我们直接在controller层实现了,

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  
}

数据封装好需要存储到文件中,并更新索引

封装数据

    newTopic := Topic{  
    Id: id,  
    Title: title,  
    Content: content,  
    CreateTime: createTime,  
}  

更新索引

topicIndexMap[newTopic.Id] = &newTopic  

保存到文件

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,因此在读文件时,我们需要加一个判断,若当前行为空字符串"",直接结束文件读取