青训营X豆包MarsCode 技术训练营第五课 | GO语言工程实践课后作业

35 阅读5分钟

作业任务

完成一个简易后端,要求实现以下功能:

  1. 发布帖子
  2. 查询帖子详情

技术栈:gin + gorm

作业结构

web项目结构概述

在介绍小作业的结构前,让我们先来了解一下一个go语言项目由哪些组成

/cmd

主要负责程序的启动,初始化,停止等,可以存放多个main.go

cmd
├── ctl
│   └── main.go
├── server
│   └── main.go
└── task
    └── main.go

/internal

存放项目的内部私有代码和库,不允许在项目外部使用,如果其他项目中导入 internal 目录下的内容,Go在编译时会报错

internal 内部可以增加额外的包结构来区分组件间共享和私有的内部代码

internal
├── app
│   ├── ctl
│   ├── server
│   └── task
└── pkg

/pkg

包含可供外部使用的库代码,提供给其他项目使用的公共 API。

/configs

此目录存放配置文件或默认配置

/test

用于存放测试文件和测试数据

/web

包含与 Web 相关的文件,如前端代码、模板和静态资源。

/service

实现项目的业务逻辑,与处理请求的代码分开。

/controller

处理 HTTP 请求的函数和路由定义。

/middleware

中间件逻辑,比如身份验证、日志记录、跨域处理等。

/docs

项目的文档,包括安装说明、使用指南等。

由于本次作业只要求实现后端的内容,所以只需要 controllerservicerepository,以及一些根目录下的文件

代码实现

/repository

首先定义 topicuserpost 数据对象,用于数据库存储

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"`
}
​
type Post struct {
    Id         int64     `gorm:"column:id"`
    ParentId   int64     `gorm:"column: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"`
}
​
type User struct {
    Id         int64     `gorm:"column:id"`
    Name       string    `gorm:"column:name"`
    Avatar     string    `gorm:"column:avatar"`
    Level      int       `gorm:"column:level"`
    CreateTime time.Time `gorm:"column:create_time"`
    ModifyTime time.Time `gorm:"column:modify_time"`
}
​

再实现对应的增删改查逻辑

注意:只实现了本次作业需要的

user

type UserDao struct {
}
​
var userDao *UserDao
var userOnce sync.Once
​
func NewUserDaoInstance() *UserDao {
    userOnce.Do(
        func() {
            userDao = &UserDao{}
        })
    return userDao
}
​
func (*UserDao) QueryUserById(id int64) (*User, error) {
    var user User
    err := db.Where("id = ?", id).Find(&user).Error
    if err == gorm.ErrRecordNotFound {
        return nil, nil
    }
    if err != nil {
        //util.Logger.Error("find user by id err:" + err.Error())
        return nil, err
    }
    return &user, nil
}
​
func (*UserDao) MQueryUserById(ids []int64) (map[int64]*User, error) {
    var users []*User
    err := db.Where("id in (?)", ids).Find(&users).Error
    if err != nil {
        log.Println("find user by id err")
    }
    userMap := make(map[int64]*User)
    for _, user := range users {
        userMap[user.Id] = user
    }
    return userMap, nil
}

post:

type PostDao struct {
}
​
var postDao *PostDao
var postOnce sync.Once
​
func NewPostDaoInstance() *PostDao {
    postOnce.Do(
        func() {
            postDao = &PostDao{}
        })
    return postDao
}
​
func (*PostDao) QueryByPostId(id int64) (*Post, error) {
    post := &Post{}
    err := db.Where("id = ?", id).First(&post).Error
    if err == gorm.ErrRecordNotFound {
        log.Println("QueryByPostId err")
        return nil, err
    }
    if err != nil {
        log.Println("QueryByPostId err")
        return nil, err
    } else {
        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 {
        return nil, err
        log.Println("QueryPostByParentId err")
    }
    return posts, nil
}
​
func (*PostDao) CreatePost(post *Post) error {
    if err := db.Create(post).Error; err != nil {
        return err
    }
    return nil
}

topic:

type TopicDao struct {
}
​
var topicDao *TopicDao
var topicOnce sync.Once
​
func NewTopicDaoInstance() *TopicDao {
    topicOnce.Do(
        func() {
            topicDao = &TopicDao{}
        })
    return topicDao
}
​
func (*TopicDao) QueryByTopicId(id int64) (*Topic, error) {
    topic := &Topic{}
    if err := db.Where("id = ?", id).First(topic).Error; err != nil {
        log.Println("QueryByTopicId err")
        return nil, err
    }
    return topic, nil
}

/service

这里实现具体的业务逻辑

发布帖子

package service
​
import (
    "errors"
    "time"
    "unicode/utf8"
    "youthcamp/lesson02/project/repository"
)
​
// PublishPost 发布帖子
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
}

查询帖子详情

package service
​
import (
    "errors"
    "fmt"
    "sync"
    "youthcamp/lesson02/project/repository"
)
​
type TopicInfo struct {
    Topic *repository.Topic
    User  *repository.User
}
​
type PostInfo struct {
    Post *repository.Post
    User *repository.User
}
​
type PageInfo struct {
    TopicInfo *TopicInfo
    PostInfo  []*PostInfo
}
​
// QueryPageInfo 查询帖子详情
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("topicId must be larger than 0")
    }
    return nil
}
​
// 通过topicID初始化topic,post列表和userMap
func (f *QueryPageInfoFlow) prepareInfo() error {
    var wg sync.WaitGroup
    wg.Add(2)
    var topicErr, postErr error
    go func() {
        defer wg.Done()
        topic, err := repository.NewTopicDaoInstance().QueryByTopicId(f.topicId)
        if err != nil {
            topicErr = err
            return
        }
        f.topic = topic
    }()
​
    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.topicId}
    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
}
​
// /初始化pageinfo
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,
        },
        PostInfo: postList,
    }
    return nil
}
​

/controller

这里实现具体的前后端交互

发布帖子

package controller
​
import (
    "strconv"
    "youthcamp/lesson02/project/service"
)
​
func PublishPost(uidStr string, topicIdStr string, content string) *PageData {
    uid, _ := strconv.ParseInt(uidStr, 10, 64)
    topicId, _ := strconv.ParseInt(topicIdStr, 10, 64)
    postId, err := service.PublishPost(topicId, uid, content)
    if err != nil {
        return &PageData{
            Code: -1,
            Msg:  err.Error(),
        }
    }
    return &PageData{
        Code: 0,
        Msg:  "success",
        Data: map[string]int64{
            "post_id": postId,
        },
    }
​
}

/查询帖子详情

package controller
​
import (
    "strconv"
    "youthcamp/lesson02/project/service"
)
​
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(),
        }
    }
    //获取service层结果
    pageInfo, err := service.QueryPageInfo(topicId)
    if err != nil {
        return &PageData{
            Code: -1,
            Msg:  err.Error(),
        }
    }
​
    return &PageData{
        Code: 0,
        Msg:  "success",
        Data: pageInfo,
    }
}

main文件

package main
​
import (
    "github.com/gin-gonic/gin"
    "youthcamp/lesson02/project/controller"
    "youthcamp/lesson02/project/repository"
)
​
func main() {
​
    if err := Init(); err != nil {
        panic(err)
    }
​
    r := gin.Default()
​
    r.GET("/ping", func(c *gin.Context) {
        c.JSON(200, gin.H{
            "msg": "pong",
        })
    })
​
    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) {
        uid, _ := c.GetPostForm("uid")
        content, _ := c.GetPostForm("content")
        topicId, _ := c.GetPostForm("topicId")
        data := controller.PublishPost(topicId, uid, content)
        c.JSON(200, data)
    })
​
    r.Run(":8080")
​
}
​
func Init() error {
    if err := repository.InitDB(); err != nil {
        return err
    }
    return nil
}