实践是学习 Golang 后端开发必不可少的步骤,那么本文介绍青训营GO语言工程实践课的一个课后作业:社区话题页面。
需求分析
描述
社区话题页面
- 展示话题(标题、文字描述)和回帖列表
- 不考虑前端页面的实现,仅实现一个本地web服务
- 话题和回帖数据用文件存储
- 支持发布帖子:本地id生成需要保证不重复,唯一性 append 文件,更新索引
需求用例
E-R 图
erDiagram
Topic ||--o{ Post:""
Topic {
string id
string title
string content
int64 create_time
}
Post {
string id
string title
string content
int64 create_time
}
分层结构
- 数据层:数据模型,对数据进行增删查改。
- 逻辑层:业务实体,处理核心业务逻辑输出。
- 控制层,展现视图,处理和外部的交互逻辑。
实现
代码结构
考虑用 Hertz 进行实现。
先用命令行工具 hz 生成基础代码:
mkdir community_topic-backend
cd community_topic-backend
hz new -module community_topic_backend
那么生成的模板代码里 biz/handler 对应分层中的 controller,也就是响应用户请求, biz/service 对应 service 中的具体实现, biz/dal 对应分层中的dal,即数据的具体访问与存储。
具体代码结构:
❯ tree
.
├── biz
│ ├── dal
│ │ ├── db_init.go
│ │ ├── post.go
│ │ └── topic.go
│ ├── handler
│ │ ├── publish_post.go
│ │ └── query_page_info.go
│ ├── router
│ │ └── register.go
│ └── service
│ ├── publish_post.go
│ └── query_page_info.go
├── build.sh
├── data
│ ├── post
│ └── topic
├── go.mod
├── go.sum
├── main.go
├── output
│ ├── bin
│ │ └── community_topic_backend
│ └── bootstrap.sh
├── router.go
├── router_gen.go
└── script
└── bootstrap.sh
10 directories, 20 files
分析
路由定义在最上层的 router.go 里:
// Code generated by hertz generator.
package main
import (
handler "community_topic_backend/biz/handler"
"github.com/cloudwego/hertz/pkg/app/server"
)
// customizeRegister registers customize routers.
func customizedRegister(r *server.Hertz) {
r.GET("/community/page/get/:id", handler.QueryPageInfoHandler)
r.POST("/community/post/do", handler.PublishPostHandler)
}
查询页面帖子对应 biz/handler 和 biz/service 里的 query_page_info.go。
发帖对应 biz/handler 和 biz/service 里的 publish_post.go。
biz/dal/db_init.go 定义话题(topic)和话题对应帖子(post)的全局字典存储结构,topicIndexMap map[int64]*Topic 和postIndexMap map[int64][]*Post,并使用sync.Mutex锁机制保证两个变量使用的并发安全; biz/dal/topic.go 和 biz/dal/post.go 定义 topic 和 post 实体的数据结构及方法。
将data中的话题和帖子数据分别读取并存储到两个字典中便于使用。
初始化 http 服务、路由等位于 main.go:
// Code generated by hertz generator.
package main
import (
"community_topic_backend/biz/dal"
"os"
"github.com/cloudwego/hertz/pkg/app/server"
)
func main() {
if err := Init("./data/"); err != nil {
os.Exit(-1)
}
h := server.Default()
register(h)
h.Spin()
}
func Init(filePath string) error {
if err := dal.Init(filePath); err != nil {
return err
}
return nil
}
运行测试:
编译:
./build.sh
测试:
获取 page 的话题列表
❯ curl http://127.0.0.1:8888/community/page/get/1
{"code":0,"msg":"success","data":{"Topic":{"id":1,"title":"青训营来啦!","content":"小姐姐,快到碗里来~","create_time":1650437625},"PostList":[{"id":1,"parent_id":1,"content":"小姐姐快来1","create_time":1650437616},{"id":2,"parent_id":1,"content":"小姐姐快来2","create_time":1650437617},{"id":3,"parent_id":1,"content":"小姐姐快来3","create_time":1650437618},{"id":4,"parent_id":1,"content":"小姐姐快来4","create_time":1650437619},{"id":5,"parent_id":1,"content":"小姐姐快来5","create_time":1650437620}]}}%
上传
curl -X POST -F 'topic_id=2' -F 'content="Test content for uploading"' http://127.0.0.1:8888/community/post/do
cat ./data/post
{"id":1,"parent_id":1,"content":"小姐姐快来1","create_time":1650437616}
{"id":2,"parent_id":1,"content":"小姐姐快来2","create_time":1650437617}
{"id":3,"parent_id":1,"content":"小姐姐快来3","create_time":1650437618}
{"id":4,"parent_id":1,"content":"小姐姐快来4","create_time":1650437619}
{"id":5,"parent_id":1,"content":"小姐姐快来5","create_time":1650437620}
{"id":6,"parent_id":2,"content":"小哥哥快来1","create_time":1650437621}
{"id":7,"parent_id":2,"content":"小哥哥快来2","create_time":1650437622}
{"id":8,"parent_id":2,"content":"小哥哥快来3","create_time":1650437623}
{"id":9,"parent_id":2,"content":"小哥哥快来4","create_time":1650437624}
{"id":10,"parent_id":2,"content":"小哥哥快来5","create_time":1650437625}
{"id":2879109067299483648,"parent_id":2,"content":"Test content for uploading","create_time":1693063966}