社区话题页面小项目入门---个人理解笔记| 青训营笔记

105 阅读5分钟

这是我参与「第五届青训营 」伴学笔记创作活动的第 2 天

社区话题页面小项目入门---个人理解笔记

课程链接:Go 语言工程实践之测试 - 掘金 (juejin.cn)

项目地址:Moonlight-Zhao/go-project-example at v1 (github.com)

该项目需要实现实现一个展示话题(标题,文字描述)和回帖列表的后端http接口

在理解这个go后端项目结构的时候,我不断尝试着与springboot项目进行对比分析。加深记忆。

项目结构分析

image-20230126153516497.png

以上是项目源码结构:

整体分为三层,repository数据层,service逻辑层,controller视图层, 与java对springboot项目结构类似。

dao

数据层关联底层数据模型,即这里的repository包,与Springboot中的dao层是一致的,里面的db_init.go 用于初始化连接数据库,post.go、topic.go、user.go是数据实体,用于映射数据库中的表,同时定义sql方法,对service层提供接口。

数据库连接配置

dsn 依次写明的参数,用户、密码、主机地址、端口号、数据库名字、其他参数,

一般本地连接我们只需要修改用户名和密码,其他参数不用修改。

然后使用gorm.Open(mysql.Open(dsn), &gorm.Config{})函数连接到mysql数据库,还是比较方便的。

// 文件加载就连接
func Init() error {
   var err error
   //配置连接参数
   dsn := "root:666666@tcp(127.0.0.1:3306)/community?charset=utf8mb4&parseTime=True&loc=Local"
   //开启连接
   db, err = gorm.Open(mysql.Open(dsn), &gorm.Config{})
   return err
}

数据库表实体映射:

以user.go为例:

/*
数据库User表映射,字段全大写public
*/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"`
}
​
// 表名映射
func (User) TableName() string {
    return "user"
}
​
type UserDao struct {
}

先用结构体定义数据库表对应的字段名,用tag指定映射的数据库表的字段名,TableName()方法映射表名

然后可以定义UserDao,结构体,用来定义对应的dao方法,类似于Springboot中的用xml文件定义dao方法。例如:

type UserDao struct {
}
// Dao方法
func (*UserDao) QueryUserById(id int64) (*User, error) {
    var user User
    // 根据id查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
}

service

Servcie逻辑层处理核心业务逻辑,即这里的service包,接受controller层的参数,调用dao层的方法,实现话题和回帖列表的业务需求,并将数据返回给视图层; 这里的Publish_Post 用于提交回帖,query_page_info_test.go 用于查询组装所有话题页面用到的用户、主题、回帖数据

Controller

Controller视图层负责处理和外部的交互逻辑,即这里的handle包,controller接受前端请求的参数,同时调用service层的方法,实现与前端交互。对于这里的简单需求,我们这里只用简单的封装成json数据返回即可。publish_post.go文件定义了PageData结构体,就是我们返回的统一的json数据格式。

type PageData struct {
   Code int64       `json:"code"` //返回码
   Msg  string      `json:"msg"`  //错误信息
   Data interface{} `json:"data"` //返回数据
}

例如:QueryPageInfo方法,接受前端请求的主题id,字符串转化为整数,在调用service方法QueryPageInfo,得到数据封装在PageData以json格式返回给前端。

func QueryPageInfo(topicIdStr string) *PageData {
   //参数转换,转换为64位大小的10进制数,
   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,
   }
​
}

读到这,我很好奇,前端怎么访问到这个方法?换句话来说,那里指定了前端的访问路径,后端才响应这个方法?对于springboot,一切通过注解解决,通过在controller层的方法上添加@RequestMapping("/admin")就能将该处理方法映射到/admin路径,前端即可访问此路径来响应该方法。那么go有是如何实现的呢?其实这就是通过路由器router来实现。

router

gin通过Default()获取默认路由

//获取默认router
r := gin.Default()

获取路由后,通过路由的GET、POST、PUT、DELETE方法配置路由,第一个参数是请求路径,第二个参数是请求路径对应的响应方法。如此达到路径匹配响应方法的配置。

//查看页面接口
r.GET("/community/page/get/:id", func(c *gin.Context) {
   topicId := c.Param("id")
   data := handler.QueryPageInfo(topicId)
   c.JSON(200, data)
})
//提交评论接口
r.POST("/community/post/do", func(c *gin.Context) {
   uid, _ := c.GetPostForm("uid")
   topicId, _ := c.GetPostForm("topic_id")
   content, _ := c.GetPostForm("content")
   data := handler.PublishPost(uid, topicId, content)
   c.JSON(200, data)
})

在这个项目中,路由的配置放在了main函数中,因为这是小小项目,前后端接口较少,所以直接图方便放在了server.go里。日后如果项目比较大,接口比较多,一般新建一个文件夹router专门用于controller方法与路径的关联的 路由配置。此外,路由还有路由分组的功能,如: Router.Group("manage-api"),方便我们分组管理路由结构,我下次再学习记录一下。

util

这里的util里面只有一个文件,logger,即日志。里面使用了Zap.logger的日志功能。

总结

通过这个小小项目,我认识了go的后端的项目结构。我在学习了解的时候不断与springboot项目比较分析,对比优劣,加深了我的理解与记忆。就目前来看,路由器这个功能对我来说是比较新奇的点,她统一管理了请求路径与响应方法之间的映射,但是我感觉还是springboot中的@RequestMapping注解比较简便一点。