这篇文章是青训营第二节课Go语言工程实践的课后作业记录,在课上学习了一个简单的go语言后端开发流程,主要内容是查询某个话题下的所有帖子,课后作业要求在次基础上新增添加帖子的功能,下面我记录一下我在这节课学到的东西,以及添加帖子功能的实现
需求描述
- 主题:社区话题页面
- 实现展示话题(标题、文字描述)和回帖列表
- 暂不考虑前端实现,仅仅实现一个本地web服务
- 话题和回帖数据用文件存储
分层结构
这张图就是一个典型的分层架构,主要分为三个层次:数据层、逻辑层和视图层,并描述了每一层的组件及其交互关系。
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实现
这个小项目使用文件作为数据来源,没有使用数据库,可以先看看这两个文件内容,
文件内容就是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",
})
})