GO语言工程实践课后作业

39 阅读4分钟

这篇文章是青训营第二节课Go语言工程实践的课后作业记录,在课上学习了一个简单的go语言后端开发流程,主要内容是查询某个话题下的所有帖子,课后作业要求在次基础上新增添加帖子的功能,下面我记录一下我在这节课学到的东西,以及添加帖子功能的实现

需求描述

  • 主题:社区话题页面
  • 实现展示话题(标题、文字描述)和回帖列表
  • 暂不考虑前端实现,仅仅实现一个本地web服务
  • 话题和回帖数据用文件存储

image.png

image.png

分层结构

image.png 这张图就是一个典型的分层架构,主要分为三个层次:数据层逻辑层视图层,并描述了每一层的组件及其交互关系。

1. 数据层repository:

  • File(文件) : 数据的存储方式,可能是本地文件,也可能是数据库的一种抽象。
  • Repository(仓储) : 用于处理数据的访问逻辑,从 File 中读取或写入数据,并与模型(Model)交互。

2. 逻辑层service:

  • Entity(实体) : 描述核心业务逻辑中的对象,通常是业务相关的核心数据和逻辑。
  • Service(服务) : 核心的业务逻辑处理单元,负责业务规则的实现。
    它会调用数据层的 Repository 获取或存储数据,同时生成实体 Entity,供视图层使用。

3. 视图层controller:

  • View(视图) : 用于呈现数据给用户的内容,可能是页面、UI组件等。
  • Controller(控制器) : 作为视图层和逻辑层之间的桥梁,接收客户端的请求,调用 Service 获取或操作数据,并将结果传递给视图。
  • Client(客户端) : 用户端,如浏览器或其他形式的客户端,直接与 Controller 交互。

依赖

这个小项目会使用到gin框架,Gin 是一个用 Go 语言编写的轻量级、性能优越的 Web 框架,Gin 提供路由分组功能,支持 GET、POST、PUT、DELETE 等 HTTP 方法。还支持 URL 参数解析和动态路由。 使用go get -u github.com/gin-gonic/gin命令安装好gin框架。

repository实现

这个小项目使用文件作为数据来源,没有使用数据库,可以先看看这两个文件内容,

image.png

image.png 文件内容就是json数据,我们要实现的repository功能就包括查询这两个文件的内容,比如对于topic来说,要根据topicId查出它的对应内容,对于post来说,要根据parent_id查询对于包括的posts内容,一个parent_id可能有多个post。 因此从这两个文件内容就可以先定义出post和topic的结构体

type Post struct {
	Id         int64  `json:"id"`
	ParentId   int64  `json:"parent_id"`
	Content    string `json:"content"`
	CreateTime int64  `json:"create_time"`
}
type Topic struct {
	Id         int64  `json:"id"`
	Title      string `json:"title"`
	Content    string `json:"content"`
	Createtime int64  `json:"create_time"`
}

查询功能我就不再赘述了,我说一下插入功能的实现。为了保证id具有唯一性,这里我先计算目前的帖子个数length,然后加一赋值给data.Id,然后打开post文件,使用追加模式,这样可以直接把新的json数据追加到末尾,注意这里为了避免并发访问map时造成问题,用了一个锁mu,需要写文件时先加锁,写完文件更新一下map用defer来解锁,这样能保证在更新map的时候其它进程不能访问,避免并发造成的问题。

func InsertPost(filepath string, data Post) error {
	var mu sync.RWMutex
	var length int
	for key, _ := range postIndexMap {
		length += len(postIndexMap[key])
	}
	data.Id = int64(length) + 1
	data_json, err := json.Marshal(data)
	if err != nil {
		return err
	}
	file, err := os.OpenFile(filepath+"post", os.O_APPEND|os.O_CREATE|os.O_WRONLY, 0644)
	if err != nil {
		return err
	}
	defer file.Close()
	// 追加写入,并在末尾添加换行符
	mu.Lock()
	defer mu.Unlock() // 解锁
	if _, err := file.Write(append(data_json, '\n')); err != nil {
		return err
	}
	Init(filepath)

	return nil

}

service层

service层我就实现了一个接口,return repository.InsertPost(filepath, data)

controller层

controller也没什么特别的,注意这个data的类型,就是Post结构体,因此在主程序解析body时要做类型转换。

func Inserts(data repository.Post) error {
	filepath := "./data/"
	return service.Insert(filepath, data)
}

主程序

添加上一个Post请求,用一个Post结构体变量来接受解析出来的body,也就是需要插入的数据。到这里这个小作业就完成了。

r.POST("/community/page/insert", func(c *gin.Context) {
    // 定义一个结构体变量 jsonData 来接收请求的 JSON 数据
    var jsonData repository.Post

    // 使用 ShouldBindJSON 方法将请求体中的 JSON 数据绑定到 jsonData 结构体
    if err := c.ShouldBindJSON(&jsonData); err != nil {
        // 如果绑定失败,返回 400 错误和错误详情
        c.JSON(400, gin.H{
            "error":   "Invalid JSON data",
            "details": err.Error(),
        })
        return
    }

    // 调用 controller 中的 Inserts 方法插入数据
    if err := controller.Inserts(jsonData); err != nil {
        // 如果插入数据失败,返回 500 错误
        c.JSON(500, gin.H{
            "error": "Failed to insert data",
        })
        return
    }

    // 如果插入成功,返回 200 状态码和成功消息
    c.JSON(200, gin.H{
        "message": "Data inserted successfully",
    })
})