作业任务
完成一个简易后端,要求实现以下功能:
- 发布帖子
- 查询帖子详情
技术栈: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
项目的文档,包括安装说明、使用指南等。
由于本次作业只要求实现后端的内容,所以只需要 controller,service,repository,以及一些根目录下的文件
代码实现
/repository
首先定义 topic,user 和 post 数据对象,用于数据库存储
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
}