Gin 基础1(路由、MVC、ORM)

1. 开发环境、最简单的服务启动;

github 地址:https://github.com/gin-gonic/gin

# 创建模块文件夹
mkdir -p /Users/hualaoshuan/go/go-gin/api.gin.test.com/topic
cd /Users/hualaoshuan/go/go-gin/api.gin.test.com/topic

# 创建 go.mod
go mod init topic.gin.test.com

# 安装 gin,在当前目录执行
go get github.com/gin-gonic/gin
  • 文件 /Users/hualaoshuan/go/go-gin/api.gin.test.com/topic/main.go
package main

import (
	"github.com/gin-gonic/gin"
	"net/http"
)

type Topic struct {
	TopicID int
	TopicTitle string

}

func main() {
	// 赋值
	//m := make(map[string]interface{})
	//m["username"] = "huahua"
	// 创建路由
	router := gin.Default()
	router.GET("/", func(context *gin.Context) {
		// context.Writer.Write([]byte("hello"))
		// context.JSON(http.StatusOK, m)
		// context.JSON(http.StatusOK, gin.H{})
		context.JSON(http.StatusOK, Topic{101, "话题"})
		
		
	})
	router.Run()	// 默认 8080
}

2. API的URL规则设计、带参数的路由;

  • 文件 /Users/hualaoshuan/go/go-gin/api.gin.test.com/topic/main.go
  • 以下程序可执行,但不规范
  • 目前不支持正则,也不支持固定路径和参数路径共存
package main

import (
	"github.com/gin-gonic/gin"
	"net/http"
)

func main() {
	router := gin.Default()
	// GET /topic/{topic_id} 获取帖子明细
	router.GET("/topic/:topic_id", func(c *gin.Context) {
		c.String(http.StatusOK, "获取帖子id为:%s", c.Param("topic_id"))
	})

	// GET /topic/{user_name} 获取用户发布的帖子列表

	// GET /topic/top 获取最热帖子列表
	router.GET("/topic/top", func(c *gin.Context) {
		c.String(http.StatusOK, "帖子列表:%s")
	})
	
	router.Run()
}
  • 重新设计 api 有版本信息:/v1/topics
  • 尽可能使用复数,且含义明确,名词最佳:/v1/topics,/v1/getusers 不推荐
  • 使用GET参数规划数据展现规则
    • /v1/users:显示全部或默认条数
    • /v1/users?limit=10:只显示10条
    • /v1/topics?username=hua,显示 hua 的帖子
package main

import (
	"github.com/gin-gonic/gin"
)

func main() {
	router := gin.Default()

	router.GET("/v1/topics", func(c *gin.Context) {
		// 默认参数设置,比如 abc 参数默认为 1
		// c.DefaultQuery("abc",1)
		if c.Query("username") == ""{
			c.String(200,"获取帖子列表")
		} else {
			// /v1/topics?username=hua
			c.String(200,"获取用户名=%s的帖子列表",c.Query("username"))
		}
	})
	router.GET("/v1/topics/:topic_id", func(c *gin.Context) {
		c.String(200,"获取topicid=%s的帖子",c.Param("topic_id"))
	})

	router.Run()
}

3. 是否要用 MVC模式、路由分组;

常见的 MVC
在这里插入图片描述
实际开发:

  • 重点业务重点部署(比如 User 相关 api)

在这里插入图片描述

  • 路由分组,文件 /Users/hualaoshuan/go/go-gin/api.gin.test.com/topic/main.go
package main

import (
	"github.com/gin-gonic/gin"
)

func main()  {
	router:=gin.Default()

	v1:=router.Group("/v1/topics")
	{	// 代码块
		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", func(c *gin.Context) {
			c.String(200,"获取topicid=%s的帖子",c.Param("topic_id"))
		})
	}
	router.Run()
}

4. 简单Dao层代码封装、使用中间件模拟"鉴权";

一些需求:

  • GET /v1/topics 默认显示所有话题列表
  • GET /v1/topics?username=hua 显示用户发表的帖子
  • GET /v1/topics/123 显示帖子 ID 为 123 的详细内容
  • POST /v1/topics 外加JSON参数,即可进行帖子的新增 (是需要登录的)
  • DELETE /v1/topics/123 删除帖子 (也要登录)
  • 简单的封装 POST /v1/topics?token=xxxxxx(判断登录)

文件 /Users/hualaoshuan/go/go-gin/api.gin.test.com/topic/src/TopicDao.go

package src

import (
	"github.com/gin-gonic/gin"
	"net/http"
)

// 登录判断,必须登录
func MustLogin() (gin.HandlerFunc) {
	return func(c *gin.Context) {
		if _, status := c.GetQuery("token"); !status {
			c.String(http.StatusUnauthorized, "缺少token参数")
			c.Abort()
		} else {
			c.Next()	// 不写也会往下走
		}

	}
}

// 有返回,调用 GetTopic 加 "()"
func GetTopic() (gin.HandlerFunc) {
	return func(c *gin.Context) {
		c.String(200,"获取topicid=%s的帖子",c.Param("topic_id"))
	}
}

// 之后是处理数据库相关操作
func GetTopicDetail(c *gin.Context) {
	c.String(200,"获取topicid=%s的帖子",c.Param("topic_id"))
}

func NewTopic(c *gin.Context)  {
	//判断登录
	c.String(200,"新增帖子")
}
func DelTopic(c *gin.Context)  {
	//判断登录
	c.String(200,"删除帖子")
}

func GetTopicList() {

}

文件 /Users/hualaoshuan/go/go-gin/api.gin.test.com/topic/main.go

package main

import (
	"github.com/gin-gonic/gin"
	. "topic.gin.test.com/src"
)

func main() {

	router:=gin.Default()

	v1:=router.Group("/v1/topics")
	{	// 代码块
		v1.GET("", func(c *gin.Context) {
			if c.Query("username")==""{
				c.String(200,"获取帖子列表")
			}else {
				c.String(200,"获取用户名=%s的帖子列表",c.Query("username"))
			}
		})

		// 注意这里是不能加小括号的。否则变成执行 GetTopicDetail 函数了
		v1.GET("/:topic_id", GetTopicDetail)

		v1.Use(MustLogin())
		{
			v1.POST("",NewTopic)
			v1.DELETE("/:topic_id", DelTopic)
		}
	}
	router.Run()
}

5. 创建Model、参数绑定 Model 的初步使用;

文件 /Users/hualaoshuan/go/go-gin/api.gin.test.com/topic/src/TopicModel.go

package src

type Topic struct {
	TopicID int `json:"id"`		// 映射
	TopicTitle string `json:"title"`
}

// 创建实体
func CreateTopic(id int, title string) Topic {
	return Topic{id, title}
}

// 参数绑定
type TopicQuery struct {
	UserName string `json:"username" form:"username"`
	Page int `json:"page" form:"page" binding:"required"`	// binding 必须有
	PageSize int `json:"pagesize" form:"pagesize"`
}

文件 /Users/hualaoshuan/go/go-gin/api.gin.test.com/topic/src/TopicDao.go

package src

import (
	"github.com/gin-gonic/gin"
	"net/http"
)

// 登录判断,必须登录
func MustLogin() (gin.HandlerFunc) {
	return func(c *gin.Context) {
		if _, status := c.GetQuery("token"); !status {
			c.String(http.StatusUnauthorized, "缺少token参数")
			c.Abort()
		} else {
			c.Next()	// 不写也会往下走
		}

	}
}

// 有返回,调用 GetTopic 加 "()"
func GetTopic() (gin.HandlerFunc) {
	return func(c *gin.Context) {
		c.String(200,"获取topicid=%s的帖子",c.Param("topic_id"))
	}
}

// 之后是处理数据库相关操作
func GetTopicDetail(c *gin.Context) {
	// c.String(200,"获取topicid=%s的帖子",c.Param("topic_id"))
	c.JSON(200, CreateTopic(101, "帖子标题"))
}

func NewTopic(c *gin.Context)  {
	//判断登录
	c.String(200,"新增帖子")
}
func DelTopic(c *gin.Context)  {
	//判断登录
	c.String(200,"删除帖子")
}

func GetTopicList(c *gin.Context) {
	//if c.Query("username")==""{
	//	c.String(200,"获取帖子列表")
	//}else {
	//	c.String(200,"获取用户名=%s的帖子列表",c.Query("username"))
	//}

	query := TopicQuery{}
	err := c.BindQuery(&query)
	if err != nil{
		// 自定义错误信息
		c.String(400,"参数错误:%s",err.Error())
	}else{
		c.JSON(200,query)
	}
}

文件 /Users/hualaoshuan/go/go-gin/api.gin.test.com/topic/main.go

package main

import (
	"github.com/gin-gonic/gin"
	. "topic.gin.test.com/src"
)

func main() {

	router:=gin.Default()

	v1:=router.Group("/v1/topics")
	{	// 代码块
		v1.GET("", GetTopicList)

		// 注意这里是不能加小括号的。否则变成执行 GetTopicDetail 函数了
		v1.GET("/:topic_id", GetTopicDetail)

		v1.Use(MustLogin())
		{
			v1.POST("",NewTopic)
			v1.DELETE("/:topic_id", DelTopic)
		}

	}
	router.Run()

}

6. 内置验证器的初步使用、POST参数绑定;

在这里插入图片描述

type Topic struct {
	TopicID int `json:"id"`
	TopicTitle string `json:"title"  binding:"required" `
}


// 验证器来源于一个第三方库
// https://github.com/go-playground/validator

// 文档
// https://godoc.org/gopkg.in/go-playground/validator.v8#hdr-Baked_In_Validators_and_Tags

文件 /Users/hualaoshuan/go/go-gin/api.gin.test.com/topic/src/TopicModel.go

package src

type Topic struct {
	//TopicID int `json:"id"`		// 映射
	//TopicTitle string `json:"title" binding:"required"`	// require 必须传参
	TopicID int `json:"id"`
	TopicTitle string `json:"title" binding:"min=4,max=20"`	// 最小4,最大20字符
	TopicShortTitle string `json:"stitle" binding:"required,nefield=TopicTitle"`	// 短标题,nefield(not equal),和xx字段不能相等
	UserIP string `json:"ip" binding:"ipv4"`
	TopicScore int `json:"score" binding:"omitempty,gt=5"`	// 帖子积分,omitempty 可空,填了必须大于5
}

// 创建实体
func CreateTopic(id int, title string) Topic {
	// return Topic{id, title}	// 只有两个字段,多字段建议写字段
	return Topic{TopicID:id, TopicTitle:title}
}

// 参数绑定
type TopicQuery struct {
	UserName string `json:"username" form:"username"`
	Page int `json:"page" form:"page" binding:"required"`	// binding 必须有
	PageSize int `json:"pagesize" form:"pagesize"`

}

文件 /Users/hualaoshuan/go/go-gin/api.gin.test.com/topic/src/TopicDao.go

package src

import (
	"github.com/gin-gonic/gin"
	"net/http"
)

// 登录判断,必须登录
func MustLogin() (gin.HandlerFunc) {
	return func(c *gin.Context) {
		if _, status := c.GetQuery("token"); !status {
			c.String(http.StatusUnauthorized, "缺少token参数")
			c.Abort()
		} else {
			c.Next()	// 不写也会往下走
		}

	}
}

// 有返回,调用 GetTopic 加 "()"
func GetTopic() (gin.HandlerFunc) {
	return func(c *gin.Context) {
		c.String(200,"获取topicid=%s的帖子",c.Param("topic_id"))
	}
}

// 之后是处理数据库相关操作
func GetTopicDetail(c *gin.Context) {
	// c.String(200,"获取topicid=%s的帖子",c.Param("topic_id"))
	c.JSON(200, CreateTopic(101, "帖子标题"))
}

//
func NewTopic(c *gin.Context)  {
	//判断登录
	// c.String(200,"新增帖子")
	// post http://localhost:8080/v1/topics?token=123
	//{
	//	"title":"帖子标题",
	//	"stitle" : "小标题",
	//	"ip": "192.168.2.1",
	//	"score": 6
	//}
	topic:=Topic{}
	err:=c.BindJSON(&topic)	// 寻找 json 参数
	if err!=nil{
		c.String(400,"参数错误:%s",err.Error())
	}else{
		c.JSON(200,topic)
	}
}
func DelTopic(c *gin.Context)  {
	//判断登录
	c.String(200,"删除帖子")
}

func GetTopicList(c *gin.Context) {
	//if c.Query("username")==""{
	//	c.String(200,"获取帖子列表")
	//}else {
	//	c.String(200,"获取用户名=%s的帖子列表",c.Query("username"))
	//}

	query := TopicQuery{}
	err := c.BindQuery(&query)
	if err != nil{
		// 自定义错误信息
		c.String(400,"参数错误:%s",err.Error())
	}else{
		c.JSON(200,query)
	}
}

文件 /Users/hualaoshuan/go/go-gin/api.gin.test.com/topic/main.go

package main

import (
	"github.com/gin-gonic/gin"
	. "topic.gin.test.com/src"
)

func main() {

	router:=gin.Default()

	v1:=router.Group("/v1/topics")
	{	// 代码块
		v1.GET("", GetTopicList)

		// 注意这里是不能加小括号的。否则变成执行 GetTopicDetail 函数了
		v1.GET("/:topic_id", GetTopicDetail)

		v1.Use(MustLogin())
		{
			v1.POST("",NewTopic)
			v1.DELETE("/:topic_id", DelTopic)
		}

	}
	router.Run()

}

在这里插入图片描述

7. 自定义验证器结合正则验证 JSON 参数;

8. 批量提交帖子数据的验证;

文件 /Users/hualaoshuan/go/go-gin/api.gin.test.com/topic/src/TopicModel.go

package src

// 单个 Topic 实体
type Topic struct {
	//TopicID int `json:"id"`		// 映射
	//TopicTitle string `json:"title" binding:"required"`	// require 必须传参
	TopicID int `json:"id"`
	TopicTitle string `json:"title" binding:"min=4,max=20"`	// 最小4,最大20字符
	TopicShortTitle string `json:"stitle" binding:"required,nefield=TopicTitle"`	// 短标题,nefield(not equal),和xx字段不能相等
	UserIP string `json:"ip" binding:"ipv4"`
	TopicScore int `json:"score" binding:"omitempty,gt=5"`	// 帖子积分,omitempty 可空,填了必须大于5
}

// 多条
type Topics struct {
	TopicList []Topic `json:"topics" binding:"gt=0,lt=3,dive"`	// 切片,dive进一步验证单条实体
	TopicListSize int `json:"size"`
}

// 创建实体
func CreateTopic(id int, title string) Topic {
	// return Topic{id, title}	// 只有两个字段,多字段建议写字段
	return Topic{TopicID:id, TopicTitle:title}
}

// 参数绑定
type TopicQuery struct {
	UserName string `json:"username" form:"username"`
	Page int `json:"page" form:"page" binding:"required"`	// binding 必须有
	PageSize int `json:"pagesize" form:"pagesize"`

}

文件 /Users/hualaoshuan/go/go-gin/api.gin.test.com/topic/src/TopicDao.go

package src

import (
	"github.com/gin-gonic/gin"
	"net/http"
)

// 登录判断,必须登录
func MustLogin() (gin.HandlerFunc) {
	return func(c *gin.Context) {
		if _, status := c.GetQuery("token"); !status {
			c.String(http.StatusUnauthorized, "缺少token参数")
			c.Abort()
		} else {
			c.Next()	// 不写也会往下走
		}

	}
}

// 有返回,调用 GetTopic 加 "()"
func GetTopic() (gin.HandlerFunc) {
	return func(c *gin.Context) {
		c.String(200,"获取topicid=%s的帖子",c.Param("topic_id"))
	}
}

// 之后是处理数据库相关操作
func GetTopicDetail(c *gin.Context) {
	// c.String(200,"获取topicid=%s的帖子",c.Param("topic_id"))
	c.JSON(200, CreateTopic(101, "帖子标题"))
}

// 单帖新增
func NewTopic(c *gin.Context)  {
	//判断登录
	topic:=Topic{}
	err:=c.BindJSON(&topic)	// 寻找 json 参数
	if err!=nil{
		c.String(400,"参数错误:%s",err.Error())
	}else{
		c.JSON(200,topic)
	}
}

// 多帖批量新增
func NewTopics(c *gin.Context)  {
	//判断登录
	topics:=Topics{}
	err:=c.BindJSON(&topics)	// 寻找 json 参数
	if err!=nil{
		c.String(400,"参数错误:%s",err.Error())
	}else{
		c.JSON(200,topics)
	}
}

func DelTopic(c *gin.Context)  {
	//判断登录
	c.String(200,"删除帖子")
}

func GetTopicList(c *gin.Context) {
	query := TopicQuery{}
	err := c.BindQuery(&query)
	if err != nil{
		// 自定义错误信息
		c.String(400,"参数错误:%s",err.Error())
	}else{
		c.JSON(200,query)
	}

}

文件 /Users/hualaoshuan/go/go-gin/api.gin.test.com/topic/main.go

package main

import (
	"github.com/gin-gonic/gin"
	. "topic.gin.test.com/src"
)

func main() {

	router:=gin.Default()

	v1:=router.Group("/v1/topics")	// 单条帖子
	{	// 代码块
		v1.GET("", GetTopicList)

		// 注意这里是不能加小括号的。否则变成执行 GetTopicDetail 函数了
		v1.GET("/:topic_id", GetTopicDetail)

		v1.Use(MustLogin())
		{
			v1.POST("",NewTopic)
			v1.DELETE("/:topic_id", DelTopic)
		}

	}

	// 多条帖子
	v2:=router.Group("/v1/mtopics")
	{
		v2.Use(MustLogin())
		{
			v2.POST("",NewTopics)
		}

	}
	router.Run()

}

9. ORM、Gorm入手、执行原始 SQL;

在这里插入图片描述

# 驱动:https://github.com/go-sql-driver/mysql

# 引入Gorm
#https://github.com/jinzhu/gorm

# 文档:
# http://gorm.io/
# https://gorm.io/docs/


# 安装
# 在项目目录下执行
go get -u github.com/go-sql-driver/mysql
go get -u github.com/jinzhu/gorm

文件 /Users/hualaoshuan/go/go-gin/api.gin.test.com/topic/main.go

package main

import (
	"fmt"
	"gorm.io/driver/mysql"
	"gorm.io/gorm"
)

func main() {
	dsn := "root:asdf@tcp(127.0.0.1:3306)/test?charset=utf8mb4&parseTime=True&loc=Local"
	db, _ := gorm.Open(mysql.Open(dsn), &gorm.Config{})
	rows, _ := db.Raw("select topic_id, topic_title from topics").Rows()
	for rows.Next() {
		var t_id int
		var t_title string
		rows.Scan(&t_id, &t_title)
		fmt.Println(t_id, t_title)
	}
}

10. 结合 Model 进行数据映射、查询;

模型操作:https://gorm.io/docs/models.html

文件 /Users/hualaoshuan/go/go-gin/api.gin.test.com/topic/src/TopicModel.go

package src

type TopicClass struct {
	ClassId int `gorm:"primaryKey"`	// 主键
	ClassName string
	ClassRemark string
	ClassType string `gorm:"Column:classtype"`	//指定字段名
}

文件 /Users/hualaoshuan/go/go-gin/api.gin.test.com/topic/main.go

package main

import (
	"fmt"
	"gorm.io/driver/mysql"
	"gorm.io/gorm"
	. "topic.gin.test.com/src"
)

func main() {
	dsn := "root:asdf@tcp(127.0.0.1:3306)/test?charset=utf8mb4&parseTime=True&loc=Local"
	db, _ := gorm.Open(mysql.Open(dsn), &gorm.Config{
		//NamingStrategy: schema.NamingStrategy{
		//	SingularTable: true, // 使用单数表名
		//},
	})

	// 查询一条数据
	//tc := &TopicClass{} 	// 引用不加"." 就 src.TopicClass{}
	//db.Table("topic_class").First(&tc,2)
	//fmt.Println(tc)

	// 查询一堆数据
	var tcs []TopicClass
	// db.Table("topic_class").Where("classtype=?", 1).Find(&tcs)
	db.Table("topic_class").Where(&TopicClass{ClassType:"1"}).Find(&tcs)
	fmt.Println(tcs)

}

版权声明:本文为博主原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接和本声明。
本文链接:https://blog.csdn.net/hualaoshuan/article/details/124597963