这是我参与「第五届青训营 」伴学笔记创作活动的第 3 天
一、本节课重点内容
1. 需求背景
2. 组件工具
3. 测试运行
二、详细知识点介绍
1. 需求背景
需求描述
- 社区话题页面
- 展示话题(标题、文字描述)和回帖列表
- 暂不考虑前端页面实现,仅仅实现一个本地web服务
- 话题和回帖数据用文件存储
需求用例
主要包括用户浏览话题内容和回帖列表,想象每个实体的属性有哪些以及它们之间的联系
ER图
用来描述现实世界的概念模型,有了模型实体、属性以及之间的联系,对后续的开发提供比较清晰的思路
分层结构
代码结构采用分层结构设计,包括repository数据层、service逻辑层、controller视图层。数据层关联底层数据模型,封装外部数据的增删改查,对逻辑层屏蔽底层数据差异,即不管底层是文件还是数据库还是微服务,同时对service逻辑层透明,接口模型不变。service逻辑层处理核心业务逻辑,计算打包业务实体entity并上送给视图层。controller视图层处理和外部的交互逻辑,以view视图形式返回给客户端
2. 组件工具
高性能web框架gin v1.3.0版,主要涉及路由分发,使用go module依赖管理,按reposity、service、controller逐步实现
Repository
根据之前的ER图先定义Topic和Post结构体如下:
为了实现高效查询,使用map实现内存索引,在服务对外暴露之前,利用文件元数据初始化全局内存索引,可以实现时间复杂度为O(1)的查找操作
迭代遍历数据行,转为结构体存储到map中
func initTopicIndexMap(filePath string) error {
open, err := os.Open(filePath + "topic")
if err != nil {
return err
}
scanner := bufio.NewScanner(open)
topicTmpMap := make(map[int64]*Topic)
for scanner.Scan() {
text := scanner.Text()
var topic Topic
if err := json.Unmarshal([]byte(text), &topic); err != nil {
return err
}
topicTmpMap[topic.Id] = &topic
}
topicIndexMap = topicTmpMap
return nil
}
查询则直接查询key获得value即可
Service
Service主要包括PageInfo结构体,它包含Topic和PostList
type PageInfo struct {
Topic *repository.Topic
PostList []*repository.Post
}
Service实现流程包括参数校验、准备数据、组装实体三部分
func (f *QueryPageInfoFlow) Do() (*PageInfo, error) {
if err := f.checkParam(); err != nil {
return nil, err
}
if err := f.prepareInfo(); err != nil {
return nil, err
}
if err := f.packPageInfo(); err != nil {
return nil, err
}
return f.pageInfo, nil
}
prepreinfo方法中,话题和回帖信息的获取都依赖tipicid,这样两者就可并行执行,提高效率,实际开发中要思考流程是否可并发,从而提高并发
Controller
定义PageData结构体作为view对象,通过code和msg打包业务状态信息,data承载业务实体信息
type PageData struct {
Code int64 `json:"code"`
Msg string `json:"msg"`
Data interface{} `json:"data"`
}
func QueryPageInfo(topicIdStr string) *PageData {
topicId, err := strconv.ParseInt(topicIdStr, 10, 64)
if err != nil {
return &PageData{
Code: -1,
Msg: err.Error(),
}
}
pageInfo, err := service.QueryPageInfo(topicId)
if err != nil {
return &PageData{
Code: -1,
Msg: err.Error(),
}
}
return &PageData{
Code: 0,
Msg: "success",
Data: pageInfo,
}
}
Routerr
最后是web服务的引擎配置,path映射到具体controller,通过path变量传递话题id。过程包括初始化数据索引、初始化引擎配置、构建路由、启动服务
func main() {
if err := Init("./data/"); err != nil {
os.Exit(-1)
}
r := gin.Default()
r.GET("/community/page/get/:id", func(c *gin.Context) {
topicId := c.Param("id")
data := cotroller.QueryPageInfo(topicId)
c.JSON(200, data)
})
err := r.Run()
if err != nil {
return
}
}
三、测试运行
本地go run运行,并使用curl请求服务暴露的接口
四、课后个人总结
在课程中跟着视频从项目拆解,代码落地设计,最后测试运行一步步实现。在现实中,遇到复杂的项目时,也可以通过大拆小的思路,将大需求拆解为小需求的思路来分析问题,遇到问题,各个击破,同时也要做好充分的测试。