项目搭建 | 青训营笔记

63 阅读2分钟

项目需求

  1. 展示话题(标题,文字描述)和回帖列表;
  2. 暂不考虑前端页面实现,仅仅实现一个本地 web 服务;
  3. 话题和回帖数据用文件存储。

分层结构

  • 主要分为以下几层:
    • 数据层 (repository):数据 Model,外部数据的增删改查;
    • 逻辑层 (service):业务 Entity,处理核心业务逻辑输出;
    • 视图层 (controller):视图 view,处理和外部的交互逻辑。

组件

  • gorm:用于操作数据库的CRUD
  • gin:负责网络部分的路由,网络信息的发送和接收

项目搭建

  • 在项目构建之初,我们用 go mod init 进行初始化,然后通过 go get gopkg.in/gin-gonic/g… 来远程拉取代码包及其依赖包,并自动完成编译和安装。

  • 根据之前的需求分析对数据层 Repository 的数据库部分进行代码编写,应该实现两个基本的查询操作:

    • 根据话题 Id 查询话题:QueryTopicById

type Topic struct {
   Id         int64     `gorm:"column:id"`
   UserId     int64     `gorm:"column:user_id"`
   Title      string    `gorm:"column:title"`
   Content    string    `gorm:"column:content"`
   CreateTime time.Time `gorm:"column:create_time"`
}

func (Topic) TableName() string {
   return "topic"
}

type TopicDao struct {
}

var topicDao *TopicDao
var topicOnce sync.Once

func NewTopicDaoInstance() *TopicDao {
   topicOnce.Do(
      func() {
         topicDao = &TopicDao{}
      })
   return topicDao
}

func (*TopicDao) QueryTopicById(id int64) (*Topic, error) {
   var topic Topic
   err := db.Where("id = ?", id).Find(&topic).Error
   if err != nil {
      util.Logger.Error("find topic by id err:" + err.Error())
      return nil, err
   }
   return &topic, nil
}

*   根据话题 Id 查询所有帖子数据:QueryPostByParentId
type Post struct {
   Id         int64     `gorm:"column:id"`
   ParentId   int64     `gorm:"parent_id"`
   UserId     int64     `gorm:"column:user_id"`
   Content    string    `gorm:"column:content"`
   DiggCount  int32     `gorm:"column:digg_count"`
   CreateTime time.Time `gorm:"column:create_time"`
}

func (Post) TableName() string {
   return "post"
}

type PostDao struct {
}

var postDao *PostDao
var postOnce sync.Once

func NewPostDaoInstance() *PostDao {
   postOnce.Do(
      func() {
         postDao = &PostDao{}
      })
   return postDao
}

func (*PostDao) QueryPostById(id int64) (*Post, error) {
   var post Post
   err := db.Where("id = ?", id).Find(&post).Error
   if err == gorm.ErrRecordNotFound {
      return nil, nil
   }
   if err != nil {
      util.Logger.Error("find post by id err:" + err.Error())
      return nil, err
   }
   return &post, nil
}

func (*PostDao) QueryPostByParentId(parentId int64) ([]*Post, error) {
   var posts []*Post
   err := db.Where("parent_id = ?", parentId).Find(&posts).Error
   if err != nil {
      util.Logger.Error("find posts by parent_id err:" + err.Error())
      return nil, err
   }
   return posts, nil
}
func (*PostDao) CreatePost(post *Post) error {
   if err := db.Create(post).Error; err != nil {
      util.Logger.Error("insert post err:" + err.Error())
      return err
   }
   return nil
}
  • 在数据库中查询到数据后,将其传输到逻辑层 Service 进行处理:参数校验 checkParam -> 准备数据 prepareInfo -> 组装实体 packPageInfo ;
    • query_page
type TopicInfo struct {
   Topic *repository.Topic
   User  *repository.User
}

type PostInfo struct {
   Post *repository.Post
   User *repository.User
}

type PageInfo struct {
   TopicInfo *TopicInfo
   PostList  []*PostInfo
}

func QueryPageInfo(topicId int64) (*PageInfo, error) {
   return NewQueryPageInfoFlow(topicId).Do()
}

func NewQueryPageInfoFlow(topId int64) *QueryPageInfoFlow {
   return &QueryPageInfoFlow{
      topicId: topId,
   }
}

type QueryPageInfoFlow struct {
   topicId  int64
   pageInfo *PageInfo

   topic   *repository.Topic
   posts   []*repository.Post
   userMap map[int64]*repository.User
}

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
}

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)
   var topicErr, postErr error
   go func() {
      defer wg.Done()
      topic, err := repository.NewTopicDaoInstance().QueryTopicById(f.topicId)
      if err != nil {
         topicErr = err
         return
      }
      f.topic = topic
   }()
   //获取post列表
   go func() {
      defer wg.Done()
      posts, err := repository.NewPostDaoInstance().QueryPostByParentId(f.topicId)
      if err != nil {
         postErr = err
         return
      }
      f.posts = posts
   }()
   wg.Wait()
   if topicErr != nil {
      return topicErr
   }
   if postErr != nil {
      return postErr
   }
   //获取用户信息
   uids := []int64{f.topic.Id}
   for _, post := range f.posts {
      uids = append(uids, post.Id)
   }
   userMap, err := repository.NewUserDaoInstance().MQueryUserById(uids)
   if err != nil {
      return err
   }
   f.userMap = userMap
   return nil
}

func (f *QueryPageInfoFlow) packPageInfo() error {
   //topic info
   userMap := f.userMap
   topicUser, ok := userMap[f.topic.UserId]
   if !ok {
      return errors.New("has no topic user info")
   }
   //post list
   postList := make([]*PostInfo, 0)
   for _, post := range f.posts {
      postUser, ok := userMap[post.UserId]
      if !ok {
         return errors.New("has no post user info for " + fmt.Sprint(post.UserId))
      }
      postList = append(postList, &PostInfo{
         Post: post,
         User: postUser,
      })
   }
   f.pageInfo = &PageInfo{
      TopicInfo: &TopicInfo{
         Topic: f.topic,
         User:  topicUser,
      },
      PostList: postList,
   }
   return nil
}
  • publish_post

func PublishPost(topicId, userId int64, content string) (int64, error) {
   return NewPublishPostFlow(topicId, userId, content).Do()
}

func NewPublishPostFlow(topicId, userId int64, content string) *PublishPostFlow {
   return &PublishPostFlow{
      userId:  userId,
      content: content,
      topicId: topicId,
   }
}

type PublishPostFlow struct {
   userId  int64
   content string
   topicId int64

   postId int64
}

func (f *PublishPostFlow) Do() (int64, error) {
   if err := f.checkParam(); err != nil {
      return 0, err
   }
   if err := f.publish(); err != nil {
      return 0, err
   }
   return f.postId, nil
}

func (f *PublishPostFlow) checkParam() error {
   if f.userId <= 0 {
      return errors.New("userId id must be larger than 0")
   }
   if utf8.RuneCountInString(f.content) >= 500 {
      return errors.New("content length must be less than 500")
   }
   return nil
}

func (f *PublishPostFlow) publish() error {
   post := &repository.Post{
      ParentId:   f.topicId,
      UserId:     f.userId,
      Content:    f.content,
      CreateTime: time.Now(),
   }
   if err := repository.NewPostDaoInstance().CreatePost(post); err != nil {
      return err
   }
   f.postId = post.Id
   return nil
}