# Go语言之路 **Repository Path**: thirtyleo/GoRoad ## Basic Information - **Project Name**: Go语言之路 - **Description**: Go语言基础文档中有文档,这里是从gin框架直接起步! - **Primary Language**: Unknown - **License**: Not specified - **Default Branch**: master - **Homepage**: None - **GVP Project**: No ## Statistics - **Stars**: 1 - **Forks**: 1 - **Created**: 2020-10-20 - **Last Updated**: 2022-05-25 ## Categories & Tags **Categories**: Uncategorized **Tags**: None ## README # Go语言Gin框架基础 ## Go教程文档 [http://www.topgoer.com/](http://www.topgoer.com/ ) 视频: [https://www.bilibili.com/video/BV1Mi4y147fF?t=13&p=5](https://www.bilibili.com/video/BV1Mi4y147fF?t=13&p=5 ) ## 何为gin - Gin是一个golang的微框架,封装比较优雅,API友好,源码注释比较明确,具有快速灵活,容错方便等特点 - 对于golang而言,web框架的依赖要远比Python,Java之类的要小。自身的`net/http`足够简单,性能也非常不错 - 借助框架开发,不仅可以省去很多常用的封装带来的时间,也有助于团队的编码风格和形成规范 ## gin安装 > go get -u github.com/gin-gonic/gin ## 入门案例 ```go package main import ( "net/http" "github.com/gin-gonic/gin" ) func main() { // 1.创建路由 router:=gin.Default() // 2.绑定路由规则,执行的函数 // gin.Context,封装了request和response router.GET("/v1/topic", func(c *gin.Context) { if c.Query("username") ==""{ c.String(http.StatusOK,"获取帖子列表") }else { username :=c.Query("username") fmt.Print("username= ",username) c.String(200,"获取用户名为%s的帖子的列表",c.Query("username")) } }) // 3.监听端口,默认在8080 // Run("里面不指定端口号默认为8080") router.Run(":8000") } ``` ## 模块封装 在项目根目录下创建dao文件夹,将一些逻辑函数封装在这里! 创建TopicDao.go ```go package dao import "github.com/gin-gonic/gin" /*获取帖子列表*/ func GetTopicDetail(c *gin.Context) { c.String(200,"获取topic id=%s的帖子",c.Param("topic_id")) } /*新增帖子*/ func AddTopic(c *gin.Context) { c.String(200,"新增帖子!") } /*删除帖子*/ func DeleteTopic(c *gin.Context) { c.String(200,"删除帖子!") } /*必须登录*/ func MustLogin() gin.HandlerFunc { return func(c *gin.Context) { //go语言中可以用下划线表示可忽略参数 if _,status:=c.GetQuery("token");!status{ c.String(401,"缺少token参数") c.Abort() } } } ``` > 函数名首字母要大写,你不大写GoLang会强制纠正你!我们知道go语言里,定义函数的关键字是func,紧跟着是函数名,参数列表与返回值! 主函数里使用 ```go router:=gin.Default() v1 :=router.Group("/a1/topic") v1.GET("", func(c *gin.Context) { if c.Query("username")==""{ c.String(200,"获取帖子列表") }else { c.String(200,"获取用户名为%s的帖子的列表",c.Query("username")) } }) v1.GET("/:topic_id", GetTopicDetail) //使用将中间件添加到组中,请参阅GitHub中的示例代码。 v1.Use(MustLogin())//dao层定义的返回值是HandlerFunc,所以这里必须要加括号 v1.POST("", AddTopic) v1.DELETE("/:id", DeleteTopic) router.Run() ``` 如果MustLogin函数执行不通过,那么下面的post和delete方法也不会再调用,因为被Abort了! 可以使用postman测试一下!![1603196104645](readme.assets/1603196104645.png) 我这里呢就不再测试去掉Abort的结果了,有兴趣的伙伴可以试试,会调用post的方法哦! 我们再添加token参数,如下图: ![1603196202514](readme.assets/1603196202514.png) 结果响应成功! ## 实体封装 在项目根目录下创建model文件夹,在该文件夹下创建Topic.go ```go type Topic struct { /* Id */ TopicId int `json:"topic_id"` /* 标题 */ TopicTitle string `json:"topic_title"` } /* 创建Topic对象 */ func CreateTopic(id int, title string) Topic { return Topic{id,title} } ``` > 这里的`是左上角Esc下面的那个键,输入法切换至英语 `json:"topic_id"`,可以理解成一种字段映射,无太大意义! dao层使用: ```go /*获取帖子列表*/ func GetTopicDetail(c *gin.Context) { //c.String(200,"获取topic id=%s的帖子",c.Param("topic_id")) c.JSON(200,Model.CreateTopic(101,"帖子标题")) } ``` main函数调用: ```go v1.GET("/:topic_id", GetTopicDetail) ``` ```json { topic_id: 101, topic_title: "帖子标题" } ``` **拓展:** ```go /* 查询绑定 */ type TopicQuery struct { /** json:相当于绑定一个别名 form:对应输入参数 binding:required表示此参数必有,否则报错 */ UserName string `json:"username" form:"username" binding:"required"` Page int `json:"page" form:"page"` Size int `json:"size" form:"size"` } ``` dao层使用: ```go /*获取帖子列表*/ func GetTopicList(c *gin.Context) { query:=Model.TopicQuery{} err:=c.BindQuery(&query) if err != nil { c.String(400,"参数错误%s",err.Error()) }else { c.JSON(200,query) } } ``` main函数使用: ```go router.GET("v2/topic/list", GetTopicList) ``` 各位朋友可以逐次测试,去掉form,看看能不能接收到参数,下图是正常请求! ![1603199837173](readme.assets/1603199837173.png) 实体中UserName不能为空,若为空则报错!![1603199909147](readme.assets/1603199909147.png) ## 内置验证器的使用 模型: ```go /*内置验证器的使用*/ type TopicPlus struct { /* Id */ TopicId int `json:"topic_id"` /* 标题 */ TopicTitle string `json:"topic_title" binding:"min=4,max=20"` /* 短标题 nefield不能与要求的字段重复 */ TopicShortTitle string `json:"topic_s_title" binding:"required,nefield=TopicTitle"` /* 用户IP*/ UserIP string `json:"ip" binding:"ipv4"` /* 分数 要大于5 */ TopicScore int `json:"score" binding:"gt=5"` } ``` > binding里面有很多内置的方法,你只需要像Java那样去配置就可以 dao层: ```go /*新增帖子*/ func AddTopic(c *gin.Context) { query:=Model.TopicPlus{} err:=c.BindJSON(&query) //绑定的是json if err != nil { c.String(400,"参数错误%s",err.Error()) }else { c.JSON(200,query) } } ``` 然后启动测试: ![1603252792479](readme.assets/1603252792479.png) 上图是按照实体里面绑定的规则来写的参数,实际你们可以试着打破这个规则,系统会有非常完美的提示! ## 正则验证Json参数 官方链接: [https://github.com/gin-gonic/gin](https://github.com/gin-gonic/gin ) 相关博客: [https://blog.csdn.net/sinat_37390744/article/details/108809009?utm_medium=distribute.pc_relevant.none-task-blog-BlogCommendFromMachineLearnPai2-4.channel_param&depth_1-utm_source=distribute.pc_relevant.none-task-blog-BlogCommendFromMachineLearnPai2-4.channel_param](https://blog.csdn.net/sinat_37390744/article/details/108809009?utm_medium=distribute.pc_relevant.none-task-blog-BlogCommendFromMachineLearnPai2-4.channel_param&depth_1-utm_source=distribute.pc_relevant.none-task-blog-BlogCommendFromMachineLearnPai2-4.channel_param ) 先写个实体: ```go /*内置验证器的使用*/ type TopicPlus struct { /* Id */ TopicId int `json:"topic_id"` /* 标题 */ TopicTitle string `json:"topic_title" binding:"min=4,max=20"` /* 短标题 nefield不能与要求的字段重复 */ TopicShortTitle string `json:"topic_s_title" binding:"required,nefield=TopicTitle"` /* 用户IP*/ UserIP string `json:"ip" binding:"ipv4"` /* 分数 要大于5 */ TopicScore int `json:"score" binding:"gt=5"` /* 链接 */ TopicUrlParam string `json:"url" binding:"omitempty,topicUrl"` } ``` > 关键在于binding:"omitempty,topicUrl",这里topicUrl要与验证规则进行绑定 新建validator包,定义规则匹配类 ```go package validator import ( "github.com/go-playground/validator/v10" "regexp" ) //这是v10的写法,v8需要参照上面的博客 func TopicUrl(fl validator.FieldLevel) bool { if url,ok:=fl.Field().Interface().(string);ok{ if matched,_ := regexp.MatchString(`\w{4,10}`,url); matched{ return true } } return false } ``` 在main函数里,将上面两个配置进行关联: ```go if v, ok := binding.Validator.Engine().(*validator.Validate); ok { v.RegisterValidation("topicUrl", TopicUrl) } ``` 以上是摘自官方和网络,有关自定义参数验证器的方法! ## 批量提交 相信不论是你学习什么语言,但凡提交必然会有批量提交的需求,Go同样不例外,那么Go的批量提交怎么去做呢? Model: ```go /*批量新增*/ type Topics struct { /* 定义一个切片 gt 大于 lt 小于*/ //dive是使Topic里面定义的规则生效! TopicList []Topic `json:"topics" binding:"gt=0,lt=3,dive"` TopicSize int `json:"size"` } ``` dao: ```go /*批量新增帖子*/ func AddTopics(c *gin.Context) { query:=Model.Topics{} err:=c.BindJSON(&query) if err != nil { c.String(400,"参数错误%s",err.Error()) }else { c.JSON(200,query) } } ``` main.go ```go v2 := router.Group("/a2/topic") { v2.POST("", AddTopics) } ``` postman进行测验: ![1603258148653](readme.assets/1603258148653.png) > 这样其实有一个问题,我们定义在Topic里面的参数约束会失效,那么我们怎么来解决呢?配置dive,他就会在添加的时候,对子层进行校验,如果子层的子层还有校验,同理添加! ## Gorm简单入门 中文文档: [http://gorm.book.jasperxu.com/database.html#dbc](http://gorm.book.jasperxu.com/database.html#dbc ) ```go func main() { //连接数据库 db, _ := gorm.Open("mysql", "root:123456@tcp(127.0.0.1:3306)/leotemp?charset=utf8&loc=Local") //一个坑,不设置这个参数,gorm会把表名转义后加个s,导致找不到数据库的表 db.SingularTable(true) rows, _ := db.Raw("select id,className from class").Rows() for rows.Next(){ var id int var name string rows.Scan(&id,&name) fmt.Println(id,name) } defer db.Close() } ``` 建表: ```sql CREATE TABLE `class` ( `id` int(10) NOT NULL COMMENT 'ID', `className` varchar(255) DEFAULT NULL COMMENT '班级名称', PRIMARY KEY (`id`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8; ``` > [http://gorm.book.jasperxu.com/]( http://gorm.book.jasperxu.com/ ) 建议您在百忙中看完!只需一小时,过个大概! ## DB封装 建表: ```sql CREATE TABLE `topic_class` ( `class_id` int(11) NOT NULL COMMENT 'id', `class_name` varchar(255) DEFAULT NULL COMMENT '名称', `class_remark` text CHARACTER SET utf8mb4 COMMENT '备注', PRIMARY KEY (`class_id`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8; ``` model: ```go type TopicClass struct { ClassId int `json:"class_id" gorm:"primary_key"` ClassName string `json:"class_name"` ClassRemark string `json:"class_remark"` } ``` 封装DB,先创建DataBase文件夹 ```go package DataBase import ( "fmt" "github.com/jinzhu/gorm" ) /** 封装数据库,需要完善! */ var DataHelper *gorm.DB var err error func init() { DataHelper,err = gorm.Open("mysql", "root:123456@tcp(127.0.0.1:3306)/leotemp?charset=utf8&loc=Local") if err != nil{ fmt.Println(err) //defer db.Close() } //一个坑,不设置这个参数,gorm会把表名转义后加个s,导致找不到数据库的表 DataHelper.SingularTable(true) DataHelper.LogMode(true) } ``` dao: ```go /*获取帖子列表*/ func GetTopicDetail(c *gin.Context) { //c.String(200,"获取topic id=%s的帖子",c.Param("topic_id")) //c.JSON(200,Model.CreateTopic(101,"帖子标题")) tid := c.Param("topic_id") topics:=Model.TopicClass{} DataHelper.Find(&topics,tid) c.JSON(200,topics) } ``` main: ```go v1 := router.Group("/a1/topic") v1.GET("/:topic_id", GetTopicDetail) ``` > 上面是最基础的代码,但是他存在一个问题就是连接开启后没有关闭,如果你手动关闭的话,那么访问完之后想要再次访问就不行了!