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

本文介绍了使用 Go Gin 框架构建 RESTful API 的过程,包括初始化开发环境、设计 API 路由、实现 MVC 结构、路由分组、数据验证、数据库操作及 ORM 使用。内容涵盖从简单的 GET 请求到复杂的 POST 参数绑定,再到 Gorm 的使用和数据映射,逐步深入 Go Web 开发。
摘要由CSDN通过智能技术生成

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)

}
在使用 Gin 框架实现 MVC 架构时,可以按照以下步骤进行操作: 1. 定义模型(Model):创建一个结构体来表示数据模型,通常与数据库表对应。例如,可以创建一个名为 `User` 的结构体来表示用户信息。 ```go type User struct { ID uint `json:"id"` Name string `json:"name"` Age int `json:"age"` Gender string `json:"gender"` Email string `json:"email"` Password string `json:"password"` } ``` 2. 定义控制器(Controller):创建处理业务逻辑的控制器,包括处理请求和响应的函数。例如,可以创建一个名为 `UserController` 的控制器来处理用户相关的操作。 ```go package controllers import ( "github.com/gin-gonic/gin" "net/http" ) type UserController struct{} func (uc *UserController) GetUser(c *gin.Context) { // 处理获取用户的逻辑 // 从数据库中查询用户信息并返回给客户端 c.JSON(http.StatusOK, gin.H{ "message": "Get user", }) } func (uc *UserController) CreateUser(c *gin.Context) { // 处理创建用户的逻辑 // 从请求中获取用户信息并保存到数据库 c.JSON(http.StatusOK, gin.H{ "message": "Create user", }) } // 其他控制器函数... ``` 3. 定义路由(Router):在主函数或路由文件中定义路由,将请求与对应的控制器函数关联起来。 ```go package main import ( "your-app/controllers" "github.com/gin-gonic/gin" ) func main() { r := gin.Default() userController := &controllers.UserController{} r.GET("/users/:id", userController.GetUser) r.POST("/users", userController.CreateUser) // 其他路由... r.Run(":8080") } ``` 以上代码演示了使用 Gin 框架实现了一个简单的 MVC 架构。你可以根据实际需求,继续添加其他控制器和路由,并在控制器中处理相应的业务逻辑。这样,当客户端发送请求时,Gin 框架将调用相应的控制器函数来处理请求并返回响应。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值