这是我参与「第五届青训营 」伴学笔记创作活动的第 2 天
社区话题页面小项目入门---个人理解笔记
课程链接:Go 语言工程实践之测试 - 掘金 (juejin.cn)
项目地址:Moonlight-Zhao/go-project-example at v1 (github.com)
该项目需要实现实现一个展示话题(标题,文字描述)和回帖列表的后端http接口
在理解这个go后端项目结构的时候,我不断尝试着与springboot项目进行对比分析。加深记忆。
项目结构分析
以上是项目源码结构:
整体分为三层,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注解比较简便一点。