这是我参与「第五届青训营」伴学笔记创作活动的第8天。
需求描述
- 实现一个展示话题(标题,文字描述)和回帖列表的后端http接口
- 暂时不考虑前端页面实现,仅仅实现一个本地web服务
- 话题和回帖数据使用本地文件来存储
ER关系
- |================|......................|===============|
- |----------Topic----------- |......................|-----------Post----------|
- |------- id -----------------|......................|------- id ---------------|
- |------- title -------------- |.......................|-------topic id ---------|
- |------- content -----------|......................|------- content -------- |
- |------- create_time -------|......................|------- create_time-----|
分层结构
- 数据层(Repository): 数据Model,外部数据的增删改查
- 逻辑层(Service): 业务Entity,处理核心业务逻辑输出,不关心底层的数据存储
- 视图层(Controller): 视图View,处理和外部的交互逻辑,对上游负责,包装一些数据格式
组件工具
- Gin web框架:github.com/gin-gonic/g…
- Go mod
- go mod init
- go get gopkg.in/gin-gonic/gin.v1@v1.3.0
索引设计
var (
topicIndexMap map[int64]*Topic //topic索引
postIndexMap map[int64][]*Post //发布索引
)
初始化topic数据索引
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) //生成map
for scanner.Scan() { //以迭代器的方式进行遍历
text := scanner.Text() //获取下一个数据
var topic Topic
if err := json.Unmarshal([]byte(text), &topic); err != nil { //使用json包来进行反序列化
return err
}
topicTmpMap[topic.Id] = &topic
}
topicIndexMap = topicTmpMap
return nil //返回无错误
}
初始化post数据索引
func initPostIndexMap(filePath string) error{
open, err := os.Open(filePath + "post")
if err != nil {
return err
}
scanner := bufio.NewScanner(open)
postTmpMap := make(map[int64][]*Post)
for scanner.Scan() {
text := scanner.Text()
var post Post
if err := json.Unmarshal([]byte(text), &post); err != nil {
return err
}
posts, ok := postTmpMap[post.ParentId]
if !ok {
postTmpMap[post.ParentId] = []*Post{&post}
continue
}
posts = append(posts, &post)
postTmpMap[post.ParentId] = posts
}
postIndexMap = postTmpMap
return nil
}
查询操作
Dao的初始化
type TopicDao struct {
}
var (
topicDao *TopicDao
topicOnce sync.Once
)
func NewTopicDaoInstance() *TopicDao {
//sync.Once : 高并发场景下只执行一次 单例模式
topicOnce.Do(
func() {
topicDao = &TopicDao{}
})
return topicDao
}
Service层
实体
type PageInfo struct { //页面信息
Topic *repository.Topic
PostList []*repository.Post
}
type QueryPageInfoFlow struct { //控制信息流转
topicId int64
pageInfo *PageInfo
topic *repository.Topic
posts []*repository.Post
}
流程
参数校验
func (f *QueryPageInfoFlow) checkParam() error {
if f.topicId <= 0 {
return errors.New("topic id must be larger than 0")
}
return nil
}
准备数据
func (f *QueryPageInfoFlow) prepareInfo() error {
//获取topic信息
var wg sync.WaitGroup
wg.Add(2) //开双协程来获取数据
go func() {
defer wg.Done()
topic := repository.NewTopicDaoInstance().QueryTopicById(f.topicId)
f.topic = topic
}()
//获取post列表
go func() {
defer wg.Done()
posts := repository.NewPostDaoInstance().QueryPostsByParentId(f.topicId)
f.posts = posts
}()
wg.Wait()
return nil
}
组装数据
func (f *QueryPageInfoFlow) packPageInfo() error {
f.pageInfo = &PageInfo{
Topic: f.topic,
PostList: f.posts,
}
return nil
}
Controller层
实体构造
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,
}
}
搭建逻辑框架
- 初始化数据索引
- 初始化引擎配置
- 构建路由
- 启动服务
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
}
}