这是我参与「第三届青训营 -后端场」笔记创作活动的的第一篇笔记
1、需求背景
社区话题页面
2、需求描述
2.1、展示话题(标题,文字描述)和回帖列表
2.2、仅仅实现一个本地web服务(不用考虑前端页面实现)
2.3、话题和回帖数据用文件储存
3、需求用例
3.1、浏览消费者
每一个访问浏览的人都可以查看话题和话题对应的评论列表
4、ER图-Entity Relationship Diagram
通过ER图能够更清晰的看出两者之间的关联
5、分层结构
结构分层能够使你的项目更加整洁,方便后期维护(问题排查、新增函数、实现新功能等等)
数据层:数据Model,外部数据的增删改查
逻辑层:业务Entity,处理核心业务逻辑输出
视图层:视图view,处理和外部的交互逻辑
6、组件工具
6.1、Gin高性能go web框架
6.2、Go Mod
go mod init
go get gopkg.in/gin-gonic/gin.v1@v1.3.0
7、Repository
7.1、数据格式
json数据格式
7.2、Repository查询
两个基本的查询操作:topic:通过id查询 post:通过topicid查询
7.3、实现方式:map索引
7.4、初始化话题数据索引
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
}
7.5、初始化回复数据索引
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
}
postTmpMap[post.TopicId] = append(postTmpMap[post.TopicId], &post)
}
PostIndexMap = postTmpMap
return nil
}
8、Service
8.1、实体
//文章详情结构体
type PageInfo struct {
Topic *repository.Topic
PostList []*repository.Post
}
8.2、流程
package service
import (
"errors"
"myproject/repository"
"sync"
)
//操作流程对象
type QueryPageInfoFlow struct {
PageInfo PageInfo
}
//执行操作
func (f *QueryPageInfoFlow) Do(topicId int64) (*PageInfo, error) {
//参数校验
if err := f.checkParam(topicId); err != nil {
return nil, err
}
//准备数据
if err := f.prepareInfo(topicId); err != nil {
return nil, err
}
//组装实体
if err := f.packPageInfo(); err != nil {
return nil, err
}
return &f.PageInfo, nil
}
//参数校验的方法
func (f *QueryPageInfoFlow) checkParam(topicId int64) error {
if _, ok := repository.TopicIndexMap[topicId]; !ok {
return errors.New("topic is not exist")
}
return nil
}
//准备数据的方法
func (f *QueryPageInfoFlow) prepareInfo(topicId int64) error {
var wg sync.WaitGroup
wg.Add(2)
//获取topic信息
go func() {
topicDao := repository.NewTopicDaoInstance()
f.PageInfo.Topic = topicDao.QueryTopicById(topicId)
defer wg.Done()
}()
//获取post列表
go func() {
defer wg.Done()
postDao := repository.NewPostDaoInstance()
f.PageInfo.PostList = postDao.QueryPostByTopicId(topicId)
}()
wg.Wait()
return nil
}
//组装数据的方法
func (f *QueryPageInfoFlow) packPageInfo() error {
return nil
}
tips:个人觉得准备数据和组装数据可以合并在一起
8.3、可用性
采用并行处理
9、Controller
9.1、构建业务对象
type PageData struct {
Code int64 `json:"Code,omitempty"` //业务错误码:-1错误,0正确
Msg string `json:"Msg,omitempty"`
Data interface{} `json:"Data,omitempty"`
}
9.2、定义相关函数
func QueryPageInfo(topicIdstr string) *PageData {
f = new(service.QueryPageInfoFlow)
topicId, err := strconv.ParseInt(topicIdstr, 10, 64)
if err != nil {
return &PageData{-1, "failed", nil}
}
pageInfo, err := f.Do(topicId)
if err != nil {
return &PageData{-1, "failed", nil}
}
return &PageData{0, "success", pageInfo}
}
func PublishPost(topicIdstr string, content string) *PageData {
f = new(service.QueryPageInfoFlow)
topicId, err := strconv.ParseInt(topicIdstr, 10, 64)
if err != nil {
return &PageData{-1, "failed", nil}
}
post := repository.Post{
Id: util.CreateId(),
TopicId: topicId,
Content: content,
CreateTime: time.Now(),
}
err = repository.SavePost("./data/", post)
if err != nil {
return &PageData{-1, "failed", nil}
}
pageInfo, err := f.Do(topicId)
if err != nil {
return &PageData{-1, "failed", nil}
}
return &PageData{0, "success", pageInfo}
}
10、Router
10.1、初始化数据索引
10.2、初始化引擎配置
10.3、构建路由
10.4、启动服务
由于此项目业务逻辑较简单,路由较少,所以均放在了main函数里
package main
import (
"github.com/gin-gonic/gin"
"myproject/controller"
"myproject/repository"
"os"
)
func main() {
//初始化数据索引
if err := Init("./data/"); err != nil {
os.Exit(-1)
}
//insertdata()插入测试数据
//初始化引擎配置
r := gin.Default()
//构建路由
r.GET("/community/page/get/:id", func(c *gin.Context) {
topicId := c.Param("id")
data := controller.QueryPageInfo(topicId)
c.JSON(200, data)
})
r.POST("/community/post/do", func(c *gin.Context) {
topicId, _ := c.GetPostForm("topic_id")
content, _ := c.GetPostForm("content")
data := controller.PublishPost(topicId, content)
c.JSON(200, data)
})
//启动服务默认8080端口
err := r.Run()
if err != nil {
return
}
}
//初始化数据索引
func Init(filePath string) error {
if err := repository.InitTopicIndexMap(filePath); err != nil {
return err
}
if err := repository.InitPostIndexMap(filePath); err != nil {
return err
}
return nil
}
11、效果演示
11.1、查询topic
11.2、发布post
12、总结
根据老师给出的框架,对相关逻辑和函数的完善和实现,最终实现了基本的话题查询和话题评论两个功能。通过本项目,我们可以学到一般项目的开发流程和框架设计。本项目的难点在于各结构体方法的设计,需要注意的是map的并发安全,可以用sync的锁来解决这个问题。
最后奉上源码:github.com/zzyzzyzzyzz…
如有不足,欢迎指正!