gin快速学习

安装

go get -u github.com/gin-gonic/gin

快速开始

package main

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

func main() {
   engine := gin.Default() //创建gin引擎
   engine.GET("/ping", func(context *gin.Context) {
      context.JSON(http.StatusOK, gin.H{
         "message": "pong",
      })
   })
   engine.Run(":9090") // 开启服务器,默认监听localhost:8080
}

参数类型

路径参数(路由参数)

设置路径参数有以下两种方法

  • :id:常用于获取明确的、单段路径参数(中间不能有 /)
  • *filepath:常用于路径通配(特别是文件路径)会把后面的路径全部当作一个参数(包括 /)
package main

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

func login(context *gin.Context) {
	context.JSON(200, gin.H{
		"code":    200,
		"message": "登陆成功",
		"id":      context.Param("id"),		// 获取路径参数
	})
}
func download(context *gin.Context) {
	context.JSON(200, gin.H{
		"code":    200,
		"path":    context.Param("filepath"),
		"message": "下载成功",
	})
}

func main() {
	// 创建路由
	// 默认使用了2个中间件Logger(), Recovery()
	r := gin.Default()

	// 定义路由匹配规则 以及对应路由函数
	r.GET("/login/:id", login)
	r.GET("/downloadFile/*filepath", download)

	r.Run(":8000")
}

查询参数

查询参数是一个url的?后面的参数
https://tieba.baidu.com/p/9623159424?frwh=index
例如frwh=index就是一个查询参数

package main

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

func login(context *gin.Context) {
	username := context.Query("username")    // 获取单个查询参数 没有则返回空
	hobby := context.QueryArray("hobby")     // 获取多个查询参数 没有则返回空
	age := context.DefaultQuery("age", "18") // 获取单个查询参数 没有则返回默认值
	phone, ok := context.GetQuery("phone")   // 获取单个查询参数 返回值多一个bool值 需要判断
	if !ok {
		context.String(200, "没有注册手机号")
	}
	context.JSON(200, gin.H{
		"code":     200,
		"message":  "登陆成功",
		"username": username,
		"hobby":    hobby,
		"age":      age,
		"phone":    phone,
	})
}

func main() {
	// 创建路由
	// 默认使用了2个中间件Logger(), Recovery()
	r := gin.Default()

	// 定义路由匹配规则 以及对应路由函数
	r.GET("/login", login)

	r.Run(":8000")
}

请求体参数

表单的内容类型一般有application/jsonapplication/x-www-form-urlencodedapplication/xmlmultipart/form-data
Form表单格式

package main

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

func login(context *gin.Context) {
	username := context.PostForm("username")    // 获取单个请求体参数 没有则返回空
	hobby := context.PostFormArray("hobby")     // 获取多个请求体参数 没有则返回空
	age := context.DefaultPostForm("age", "18") // 获取单个请求体参数 没有则返回默认值
	phone, ok := context.GetPostForm("phone")   // 获取单个请求体参数 返回值多一个bool值 需要判断
	if !ok {
		context.String(200, "没有注册手机号")
	}
	context.JSON(200, gin.H{
		"code":     200,
		"message":  "登陆成功",
		"username": username,
		"hobby":    hobby,
		"age":      age,
		"phone":    phone,
	})
}

func main() {
	// 创建路由
	// 默认使用了2个中间件Logger(), Recovery()
	r := gin.Default()

	// 定义路由匹配规则 以及对应路由函数
	r.POST("/login", login)

	r.Run(":8000")
}

ShouldBind格式
ShouldBind格式 可处理一切数据

package main

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

type Book struct {
	Title  string  `json:"title-json" form:"title-form"` // 最终显示的是json字段
	Author string  `json:"author-json" form:"author-form"`
	Price  float64 `json:"price-json" form:"price-form"`
}

func login(context *gin.Context) {
	// 实例化结构体
	book := Book{}

	// 给结构体对象传值 因为要修改 使用指针
	context.ShouldBind(&book)
	context.JSON(200, gin.H{
		"code":    200,
		"message": "登陆成功",
		"data":    book,
	})
}

func main() {
	// 创建路由
	// 默认使用了2个中间件Logger(), Recovery()
	r := gin.Default()

	// 定义路由匹配规则 以及对应路由函数
	r.POST("/login", login)

	r.Run(":8000")
}

关于结构体标签
json标签的值:当传入数据为json格式时 标签值为传入的key值
form标签的值:当传入数据为form-data时 标签值为传入的字段名
无论传入的数据时json还是form-data 写入结构体的key都为json标签值

路由管理

路由分组

支持嵌套

package main

import (
	"github.com/gin-gonic/gin"
)
func sIndexHandler(c *gin.Context) {
	c.JSON(200, gin.H{
		"code": 200,
		"data": "sIndexHandler",
		"msg":  "success",
	})
}

func sHomeHandler(c *gin.Context) {
	c.JSON(200, gin.H{
		"code": 200,
		"data": "sHomeHandler",
		"msg":  "success",
	})
}

func liveIndexHandler(c *gin.Context) {
	c.JSON(200, gin.H{
		"code": 200,
		"data": "liveIndexHandler",
		"msg":  "success",
	})
}

func liveHomeHandler(c *gin.Context) {
	c.JSON(200, gin.H{
		"code": 200,
		"data": "liveHomeHandler",
		"msg":  "success",
	})
}

func ashopBedHandler(c *gin.Context) {
	c.JSON(200, gin.H{
		"code": 200,
		"data": "ashopBedHandler",
		"msg":  "success",
	})
}

func ashopFoodHandler(c *gin.Context) {
	c.JSON(200, gin.H{
		"code": 200,
		"data": "ashopFoodHandler",
		"msg":  "success",
	})
}
func main() {
	// 创建路由
	// 默认使用了2个中间件Logger(), Recovery()
	r := gin.Default()

	// 路由分组
	shop := r.Group("/shopping")
	{
		shop.GET("/index", sIndexHandler) // /shopping/index
		shop.GET("/home", sHomeHandler)   // /shopping/home
	}

	blog := r.Group("/blog")
	{
		blog.GET("/index", liveIndexHandler)
		blog.GET("/home", liveHomeHandler)
	}

	// 路由组也是支持嵌套的
	app01 := r.Group("/app01")
	{
		app01.GET("/index", sIndexHandler) // /app01/index
		ashop := app01.Group("/shopping")
		{
			ashop.GET("/bed", ashopBedHandler)   // /app01/shopping/bed
			ashop.GET("/food", ashopFoodHandler) // /app01/shopping/food
		}
	}

	r.Run(":8000")
}

404路由

当访问路由的不存在时 访问该路由

package main

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

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

	// 注册处理器
	e.NoRoute(func(context *gin.Context) { // 这里只是演示,不要在生产环境中直接返回HTML代码
		context.String(http.StatusNotFound, "<h1>404 Page Not Found</h1>")
	})
	log.Fatalln(e.Run(":8080"))
}

重定向

package main

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

func main() {
	e := gin.Default()
	e.GET("/", Index)
	e.GET("/hello", Hello)
	e.NoRoute(func(c *gin.Context) {
		c.Redirect(http.StatusMovedPermanently, "/hello")
	})
	log.Fatalln(e.Run(":8080"))
}

func Index(c *gin.Context) {
	c.Redirect(http.StatusMovedPermanently, "https://www.baidu.com")
}

func Hello(c *gin.Context) {
	c.String(http.StatusOK, "hello")
}

文件上传

单文件上传

// 创建template/index.html
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Upload File</title>
</head>
<body>
<h2>Upload a File</h2>
<form action="http://localhost:8080/upload" method="post" enctype="multipart/form-data">
    <input type="file" name="file" required />
    <br><br>
    <input type="submit" value="Upload File" />
</form>
</body>
</html>

// main.go
package main

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

func file(c *gin.Context) {
	c.HTML(200, "index.html", nil)
}

func main() {
	e := gin.Default()
	e.LoadHTMLGlob("./template/*")
	e.GET("/", file)
	e.POST("/upload", uploadFile)
	log.Fatalln(e.Run(":8080"))
}

func uploadFile(ctx *gin.Context) {
	// 获取文件
	file, err := ctx.FormFile("file")

	if err != nil {
		ctx.String(http.StatusBadRequest, "%+v", err)
		return
	}
	// 保存在本地
	err = ctx.SaveUploadedFile(file, "./static/"+file.Filename)
	if err != nil {
		ctx.String(http.StatusBadRequest, "%+v", err)
		return
	}
	// 返回结果
	ctx.String(http.StatusOK, "upload %s size:%d byte successfully!", file.Filename, file.Size)
}

多文件上传

// index.html
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Upload Multiple Files</title>
</head>
<body>
<h2>Upload Multiple Files</h2>
<form action="http://localhost:8080/uploadFiles" method="post" enctype="multipart/form-data">
    <input type="file" name="files" multiple />
    <br><br>
    <input type="submit" value="Upload" />
</form>
</body>
</html>


// main.go
package main

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

func file(c *gin.Context) {
	c.HTML(200, "index.html", nil)
}

func main() {
	e := gin.Default()
	e.LoadHTMLGlob("./template/*")
	e.GET("/", file)
	e.POST("/uploadFiles", uploadFiles)
	log.Fatalln(e.Run(":8080"))
}

func uploadFiles(ctx *gin.Context) {
	// 获取gin解析好的multipart表单
	form, _ := ctx.MultipartForm()
	// 根据键值取得对应的文件列表
	files := form.File["files"]
	// 遍历文件列表,保存到本地
	for _, file := range files {
		err := ctx.SaveUploadedFile(file, "./static/"+file.Filename)
		if err != nil {
			ctx.String(http.StatusBadRequest, "upload failed")
			return
		}
	}
	// 返回结果
	ctx.String(http.StatusOK, "upload %d files successfully!", len(files))
}

中间件

package main

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

func index(c *gin.Context) {
	fmt.Println("index request")

	c.JSON(http.StatusOK, gin.H{
		"data": "data",
	})
}

func index2(c *gin.Context) {
	fmt.Println("index2 request")

	c.JSON(http.StatusOK, gin.H{
		"data": "data",
	})
}

func M1() gin.HandlerFunc {
	return func(context *gin.Context) {
		fmt.Println("M1 request")
		// 中断中间件执行 原路返回 不继续执行下面 拦截命令
		//context.Abort()

		// 拦截后返回的状态码
		//context.AbortWithStatus(404)

		// 拦截后显示的json
		//context.AbortWithStatusJSON(200, gin.H{
		//	"msg": "人机别访问我",
		//})

		context.Next()
		fmt.Println("M1 response")
	}
}

func M2() gin.HandlerFunc {
	return func(context *gin.Context) {
		fmt.Println("M2 request")
		context.Next() // 位于请求响应处理之间
		fmt.Println("M2 response")
	}
}

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

	// 注册全局中间件
	router.Use(M1())

	// 注册局部中间件 常注册在分组位置 也可以注册在单个路由上
	r := router.Group("/api", M2())
	{
		r.GET("index2", index2)
	}
	
	router.GET("index", index)

	router.Run()
}

静态资源配置

Static("访问路径""实际保存文件路径")
func main() {
   router := gin.Default()
   // 加载静态文件目录
   router.Static("/static", "./static")

   // 加载静态文件
   router.StaticFile("/favicon", "./static/favicon.ico")

   router.Run(":8080")
}

跨域配置

func CorsMiddle() gin.HandlerFunc {
   return func(c *gin.Context) {
      method := c.Request.Method
      origin := c.Request.Header.Get("Origin")
      if origin != "" {
         // 生产环境中的服务端通常都不会填 *,应当填写指定域名
         c.Header("Access-Control-Allow-Origin", origin)
         // 允许使用的HTTP METHOD
         c.Header("Access-Control-Allow-Methods", "POST, GET, OPTIONS, PUT, DELETE, UPDATE")
         // 允许使用的请求头
         c.Header("Access-Control-Allow-Headers", "Origin, X-Requested-With, Content-Type, Accept, Authorization")
         // 允许客户端访问的响应头
         c.Header("Access-Control-Expose-Headers", "Content-Length, Access-Control-Allow-Origin, Access-Control-Allow-Headers, Cache-Control, Content-Language, Content-Type")
         // 是否需要携带认证信息 Credentials 可以是 cookies、authorization headers 或 TLS client certificates
         // 设置为true时,Access-Control-Allow-Origin不能为 *
         c.Header("Access-Control-Allow-Credentials", "true")
      }
      // 放行OPTION请求,但不执行后续方法
      if method == "OPTIONS" {
         c.AbortWithStatus(http.StatusNoContent)
      }
      // 放行
      c.Next()
   }
}

数据校验

在使用时,官方建议在整个程序的生命周期中,只存在一个验证器实例,这样会有利于其缓存一些数据

结构体验证

package main

import (
	"fmt"
	"github.com/go-playground/validator/v10"
)

// 默认规则认证
type User struct {
	Name  string `validate:"required"`        // required 代表必须传值
	Age   int    `validate:"required,gte=18"` // gte代表大于等于
	Email string `validate:"required,email"`
}

type Person struct {
	Name    string `validate:"contains=jack"` //名字包含jack
	Age     int    `validate:"gte=18"`        //大于等于18岁
	Address string `validate:"required"`      //以市结尾
}

func main() {
	// 正常实例化应该为ShouldBind传入 此处为模拟
	user := &User{
		Name:  "xyh",
		Age:   20,
		Email: "3340@qq.com",
	}
	person := &Person{
		Name:    "jack",
		Age:     20,
		Address: "北京市",
	}

	val := validator.New()

	// 校验user过程
	err := val.Struct(user)
	fmt.Println("err:::", err)

	err2 := validator.New().Struct(person)
	fmt.Println("err2:::", err2)
}

map验证

func TestMap(t *testing.T) {
   user := map[string]interface{}{
      "name":    "jak",
      "age":     17,
      "address": "滔博市",
   }
   rules := map[string]interface{}{
      "name":    "contains=jacklove",
      "age":     "gte=18",
   }

   validate := validator.New()

   validateMap := validate.ValidateMap(user, rules)
   fmt.Println(validateMap)
}

切片验证

func TestSlice(t *testing.T) {
   userList := make([]User, 0)
   user := User{
      Name:    "jacklove",
      Age:     17,
      Address: "滔博市",
   }
   userList = append(userList, user)
   err := validator.New().Var(userList, "dive") //“dive”即深层验证的意思,当元素为结构体时,会自动进行结构体验证
   fmt.Println(err)
}

变量验证

func TestVar(t *testing.T) {
   name := "jack"
   err := validator.New().Var(name, "max=5,contains=a,min=1,endswith=l") //最大长度为5,最小长度为1,包含字母a,以字母l结尾
   fmt.Println(err)
}

字段验证

type Password struct {
   FirstPassword  string `validate:"eqfield=SecondPassword"` //验证两次输入的密码是否相等
   SecondPassword string
}

type RegisterUser struct {
   Username string `validate:"necsfield=Password.FirstPassword"` //在注册时为了安全考虑,禁止密码和用户名一致
   Password Password
}

func TestCrossStructFieldValidate(t *testing.T) {
   validate = validator.New()
   // 失败
   fmt.Println(validate.Struct(RegisterUser{
      Username: "gopher",
      Password: Password{
         FirstPassword:  "gopher",
         SecondPassword: "gophers",
      },
   }))
   // 成功
   fmt.Println(validate.Struct(RegisterUser{
      Username: "gophers",
      Password: Password{
         FirstPassword:  "gopher",
         SecondPassword: "gopher",
      },
   }))
}

自定义校验

type Student struct {
	Name string `validate:"required"`
	Url  string `validate:"required,is666"` // 自定义对is666的判断
}

func is666(fl validator.FieldLevel) bool {
	value := fl.Field().String()
	fmt.Println("value::", value)

	ret := value == "invalid-ur"
	return ret
}

func main() {
	std := &Student{
		Name: "xyh",
		Url:  "invalid-url",
	}
	fmt.Println("std:::", std)
	val := validator.New()
	val.RegisterValidation("is666", is666)

	err := val.Struct(std)
	fmt.Println("err::", err)
}

跨字段验证

type User struct {
	Password        string `validate:"required"`
	PasswordConfirm string `validate:"required,eqfield=Password"`
}

func main() {
	user := &User{
		Password:        "password",
		PasswordConfirm: "worth-password",
	}
	val := validator.New()
	err := val.Struct(user)
	fmt.Println("err:::", err)
}

Gorm

安装

go get -u gorm.io/gorm
go get -u gorm.io/driver/mysql

连接数据库

package main

import (
	"gorm.io/driver/mysql"
	"gorm.io/gorm"
	"gorm.io/gorm/logger"
	"log"
	"os"
)

func main() {
	// dsn := "用户:密码@tcp(ip:port)/数据库名?charset=utf8mb4&parseTime=True&loc=Local"
	dsn := "root:1234@tcp(127.0.0.1:3306)/relearngin?charset=utf8mb4&parseTime=True&loc=Local"
	newLogger := logger.New(
		log.New(os.Stdout, "\r\n", log.LstdFlags),
		logger.Config{
			LogLevel: logger.Info, // 日志级别
		},
	)
	db, _ := gorm.Open(mysql.Open(dsn), &gorm.Config{
		Logger: newLogger,
	})

}

创建映射结构体

创建models目录 分别创建四个go文件
创建学生表

package models

import (
	"gorm.io/gorm"
	"time"
)

type Student struct {
	gorm.Model
	Sname  string `gorm:"type:varchar(32);unique;not null"`
	Sno    int
	Sage   int
	Spwd   string `gorm:"type:varchar(100);not null"`
	Gender string `gorm:"default:male"`
	Birth  *time.Time
	Remark string `gorm:"type:varchar(255)"`

	// 定义多对一关系
	ClassID int
	Class   Class `gorm:"foreignKey:ClassID;constraint:OnDelete:CASCADE"`

	// 多对多
	// many2many是第三张表的类型  student2courses是第三张表的表名
	Courses []Course `gorm:"many2many:student2courses;constraint:OnDelete:CASCADE"`
}

// 定义迁移到数据库的表名
func (s Student) TableName() string {
	return "students"
}

创建班级表

package models

import "gorm.io/gorm"

type Class struct {
	gorm.Model
	Cname string `gorm:"type:varchar(32);unique;not null"`
	Cnum  int

	// 定义外键关系 一对多
	TeacherID int
	// constraint:OnDelete:CASCADE 创建级联 主表删除子表一起删除
	Teacher Teacher `gorm:"foreignKey:TeacherID;constraint:OnDelete:CASCADE"`
}

// 定义迁移到数据库的表名
func (c Class) TableName() string {
	return "class"
}

创建教师表

package models

import (
	"gorm.io/gorm"
	"time"
)

type Teacher struct {
	gorm.Model
	Tname  string `gorm:"type:varchar(32);unique;not null"`
	Tno    int
	Tpwd   string `gorm:"type:varchar(100);not null"`
	Tage   int
	Birth  *time.Time
	Remark string `gorm:"type:varchar(255)"`
}

// 在实现的方法中,它返回了字符串teacher,在数据库迁移的时候,gorm 会创建名为teacher的表
func (t Teacher) TableName() string {
	return "teacher"
}

创建课程表

package models

import "gorm.io/gorm"

type Course struct {
	gorm.Model
	Coname string `gorm:"type:varchar(32);unique;not null"`
	Credit int
	Period int

	// 多对一
	TeacherID int
	Teacher   Teacher `gorm:"foreignKey:TeacherID;constraint:OnDelete:CASCADE"`
}

func (c Course) TableName() string {
	return "courses"
}

迁移数据库

func main() {
	dsn := "root:1234@tcp(127.0.0.1:3306)/relearngin?charset=utf8mb4&parseTime=True&loc=Local"
	newLogger := logger.New(
		log.New(os.Stdout, "\r\n", log.LstdFlags),
		logger.Config{
			LogLevel: logger.Info, // 日志级别
		},
	)
	db, _ := gorm.Open(mysql.Open(dsn), &gorm.Config{
		Logger: newLogger,
	})
	// 迁移数据库
	db.AutoMigrate(&models.Teacher{}, &models.Class{}, &models.Student{}, &models.Course{})
}

增加数据

package method

import (
	"RelearnGin/models"
	"gorm.io/gorm"
)

func AddRecord(db *gorm.DB) {
	// 单条添加teacher对象
	t1 := models.Teacher{Tname: "ming", Tpwd: "1111", Tno: 1111, Tage: 20, Remark: "good"}
	db.Create(&t1)
	t2 := models.Teacher{Tname: "lin", Tpwd: "2222", Tno: 2222, Tage: 21, Remark: "alg"}
	db.Create(&t2)
	t3 := models.Teacher{Tname: "xing", Tpwd: "3333", Tno: 3333, Tage: 22, Remark: "nx"}
	db.Create(&t3)
	t4 := models.Teacher{Tname: "zhang", Tpwd: "4444", Tno: 4444, Tage: 23, Remark: "666"}
	db.Create(&t4)
	t5 := models.Teacher{Tname: "hong", Tpwd: "5555", Tno: 5555, Tage: 24, Remark: "666"}
	db.Create(&t5)

	// 批量 多对一 添加班级对象
	c1 := models.Class{Cname: "1班", Cnum: 30, TeacherID: 1}
	c2 := models.Class{Cname: "2班", Cnum: 31, TeacherID: 2}
	c3 := models.Class{Cname: "3班", Cnum: 32, TeacherID: 3}
	c4 := models.Class{Cname: "4班", Cnum: 33, TeacherID: 4}
	c5 := models.Class{Cname: "5班", Cnum: 34, TeacherID: 5}
	ClassList := []models.Class{c1, c2, c3, c4, c5}
	db.Create(&ClassList)

	// 批量添加课程对象
	CourseList := []models.Course{
		{Coname: "Go语言", Credit: 2, Period: 30, TeacherID: 1},
		{Coname: "Java", Credit: 1, Period: 31, TeacherID: 2},
		{Coname: "Python", Credit: 2, Period: 32, TeacherID: 3},
		{Coname: "C++", Credit: 3, Period: 33, TeacherID: 4},
		{Coname: "C", Credit: 4, Period: 34, TeacherID: 5},
	}
	db.Create(&CourseList)

	// 多对多添加学生表数据
	var course []models.Course
	courseName := []string{"Python", "Go语言"}
	db.Where("coname in ?", courseName).Find(&course)

	 方式一 添加Student对象时 同时创建多对多关系 先查询出课程对象 后创建Student实例
	s1 := models.Student{Sname: "yyy", Sno: 234, Sage: 18, Spwd: "234", Gender: "male", ClassID: 5, Courses: course}
	db.Create(&s1)

	// 方式二
	s2 := models.Student{Sname: "xyh", Sno: 123, Sage: 19, Spwd: "124", Gender: "female", ClassID: 4}
	db.Create(&s2)
	db.Model(&s2).Association("Courses").Append(&course)

	// 方式三 查询对象后绑定多对多
	s3 := models.Student{Sname: "zhangsan", Sno: 222, Sage: 17, Spwd: "146", Gender: "male", ClassID: 4}
	db.Create(&s3)
	var student models.Student
	db.Where("sname = ?", "zhangsan").Find(&student)
	db.Model(&student).Association("Courses").Append(&course)

	// 补充
	//解除某个多对多绑定关系
	db.Model(&student).Association("Courses").Delete(&course)
	// 清除多对多关系
	db.Model(&student).Association("Courses").Clear()
}

关于Association

Association 方法:专门用于管理多对多或一对多关系。
适用场景:
添加新的关联对象(如 .Append)。
删除某些关联对象(如 .Delete)。
清除所有关联对象(如 .Clear)。
替换关联对象(如 .Replace)。
内部机制:
当调用 Association("Courses") 时,GORM 会根据模型定义的关联关系,找到对应的中间表(如 student_courses)。
然后,根据后续的操作(如 .Append),执行相应的 SQL 语句来更新中间表中的记录。

查找数据

package method

import (
	"RelearnGin/models"
	"fmt"
	"gorm.io/gorm"
)

func SelectRecord(db *gorm.DB) {
	var teachers []models.Teacher

	// 查询全部记录
	db.Find(&teachers)
	fmt.Println(teachers)

	// where查询 如果查询的值有0值或nil 结构体会默认跳过(不参与查询)
	db.Where("id=?", 5).Take(&teachers)
	fmt.Println(teachers)
	db.Where("id between ? and ?", 2, 5).Find(&teachers) // 包括2和5
	fmt.Println(teachers)
	db.Where("id in ?", []int{1, 2}).Find(&teachers)
	fmt.Println(teachers)

	// or用法
	db.Where("id = ? or tname = ?", 5, "ming").Find(&teachers)
	fmt.Println(teachers)
	db.Where("id =?", 5).Or("tname = ?", "lin").Find(&teachers)
	fmt.Println(teachers)

	// like模糊查询
	db.Where("tname like ?", "%i%").Find(&teachers) // 名字中包含i的
	fmt.Println(teachers)

	// Struct & Map 条件
	// Struct条件查询
	//注意: 使用结构体作为条件查询时,GORM 只会查询非零值字段。
	db.Where(&models.Teacher{Tname: "ming", Tno: 1111}).Find(&teachers)
	fmt.Println(teachers)

	// Map条件查询
	// 实际执行的SQL语句:SELECT * FROM `teacher` WHERE `name` = ming AND `tno` = '1111'
	db.Where(map[string]interface{}{"tname": "ming", "tno": 1111}).Find(&teachers)
	fmt.Println(teachers)

	// count用法
	var count int64
	db.Where("id<=?", 4).Find(&teachers).Count(&count)
	fmt.Println(count)

	// 查询单条记录 First Last Take
	var class models.Class
	// Take查询一条记录 SELECT * FROM teachers LIMIT 1
	 First 查询第一条记录 Last 查询最后一条记录
	db.Take(&class)
	fmt.Println(class)

	// Select用法 查询某个字段
	// SELECT `name`,`ming` tno `1111` FROM `teachers`
	db.Select("tname", "tno").Find(&teachers)
	fmt.Println(teachers)

	// Omit用法 忽略值 查询出不要的
	//SELECT `name`,`tno` FROM `teachers`
	db.Omit("tname", "tno").Find(&teachers)
	fmt.Println(teachers)

	// Order,Limit,Having同上
	//Offset的用法 忽略前n条记录  .Offset(n)

	// Distinct去重
	db.Where("id in ?", []string{"ming", "lin"}).Distinct("tname").Find(&teachers)
	fmt.Println(teachers)

	// Group用法 需要定义结构体接收结果
	type Result struct {
		TeacherID string
		Count     int
	}
	var result []Result
	db.Model(&models.Class{}).Select("teacher_id,Count(*) as count").Group("teacher_id").Scan(&result)
	fmt.Println("result", result)
}

修改数据

package method

import (
	"RelearnGin/models"
	"fmt"
	"gorm.io/gorm"
)

func UpdateRecord(db *gorm.DB) {
	// save更新方式  不建议用
	var course models.Course
	db.Where("name = ?", "Java").Find(&course)
	fmt.Println(course)
	course.Credit = 4
	db.Save(&course)
	fmt.Println(course)

	// Update 更新单个字段
	db.Model(&models.Course{}).Where("name = ?", "java").Update("credit", 2)

	// Updates 更新多个字段 需要结构体或Map
	// 通过 `struct` 更新多个字段,不会更新零值字段
	//var course Course
	db.Model(&course).Where("id = ?", 2).Updates(&models.Course{Coname: "sre", Period: 25})

	// 通过 `map` 更新多个字段,零值字段也会更新
	//var course Course
	db.Model(&course).Where("id = ?", 2).Updates(map[string]interface{}{"coname": "rust", "credit": "5"})

	// update 表达式更新 实现表所有credit+1
	db.Model(&course).Where("1 = 1").Update("credit", gorm.Expr("credit+1"))

}

删除数据

Delete使用的是软删除 不会删除数据库数据 会在删除时间字段写入值

package method

import (
	"RelearnGin/models"
	"gorm.io/gorm"
)

func DeleteRecord(db *gorm.DB) {
	// 删除单条记录

	// 按条件删除
	var course models.Course
	db.Where("name = ?", "C++").Take(&course)
	db.Delete(&course)

	// 写法二
	db.Where("name = ?", "C++").Delete(models.Course{})

	// 删除所有记录 必须加where条件
	db.Where("1 = 1").Delete(&models.Student{})
}

预加载

package method

import (
	"RelearnGin/models"
	"fmt"
	"gorm.io/gorm"
	"gorm.io/gorm/clause"
)

func PreloadRecord(db *gorm.DB) {
	// 查询学号是234学生的班级
	var student models.Student
	var class models.Class
	db.Where("sno = ?", 234).Find(&student)
	db.Where("id = ?", student.ClassID).Find(&class)
	fmt.Println("class.Name::", class.Cname)

	// Preload预加载实现 多表之间查询
	db.Preload("Class").Where("sno = ?", 234).Find(&student)		// 这里的Class对应的是Student结构体的Class字段
	fmt.Println("student.Class.Cname::", student.Class.Cname)

	// 可预加载多个表(直接关联时)
	db.Preload("Courses").Preload("Class").Where("sno = ?", 123).Find(&student)
	//多对多关系表加载 可循环取出
	fmt.Println("student.Courses::", student.Courses)
	// 多对一加载
	fmt.Println("student.Class::", student.Class)

	// 不直接关联时 例如student-teacher表  预加载嵌套
	db.Preload("Class.Teacher").Preload("Class").Where("sno = ?", 123).Find(&student)
	fmt.Println(student.Class.Teacher)

	// 预加载所有直接关联
	db.Preload(clause.Associations).Where("sno = ?", 123).Find(&student)
	fmt.Println("student-all::", student.Courses)
}

JWT认证

JWT 全名 JSON Web Tokens 它是一种开放的,安全的,紧凑的,以 JSON 对象为载体在服务双方之间传输信息的方式,它的特点就是安全性高,内容防篡改,消耗低
在 RFC 标准中,JWT 由以下三个部分组成:

  • Header 头部
  • Payload 载荷
  • Signature 签名

然后每一个部分用一个点.来分隔,最后组成一个字符串,格式就是header.payload.signature,这就是一个 JWT 令牌的标准结构

package main

import (
	"fmt"
	"github.com/golang-jwt/jwt/v4"
	"net/http"
	"strings"
	"time"

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

// 定义密钥
var jwtKey = []byte("my_secret_key")

// 定义 JWT 的声明
type Claims struct {
	Username string `json:"username"`
	jwt.RegisteredClaims
}

type LoginData struct {
	Username string `json:"username" binding:"required"`
	Password string `json:"password" binding:"required"`
}

func Login(c *gin.Context) {
	login := LoginData{}
	c.ShouldBind(&login)
	if login.Username != "xyh" || login.Password != "123" {
		c.String(200, "用户名或密码错误")
		return
	}

	// 创建 JWT Token
	// 创建负载 过期时间为15分钟
	expirationTime := time.Now().Add(15 * time.Minute)
	claims := &Claims{
		Username: login.Username,
		RegisteredClaims: jwt.RegisteredClaims{
			ExpiresAt: jwt.NewNumericDate(expirationTime),
		},
	}

	// 创建header 加入创建的负载
	token := jwt.NewWithClaims(jwt.SigningMethodHS256, claims)

	// 签名过程
	tokenString, err := token.SignedString(jwtKey)
	if err != nil {
		c.JSON(http.StatusInternalServerError, gin.H{"error": "Could not create token"})
		return
	}
	// 返回 JWT Token
	c.JSON(http.StatusOK, gin.H{
		"token": tokenString,
	})
}

// 验证中间件
func AuthMiddleware() gin.HandlerFunc {
	return func(c *gin.Context) {
		// 从请求头获取 JWT
		tokenString := c.GetHeader("Authorization")
		if tokenString == "" {
			c.JSON(http.StatusUnauthorized, gin.H{"error": "Missing token"})
			c.Abort()
			return
		}

		// 去除前导和尾随空格
		tokenString = strings.TrimSpace(tokenString)

		// 检查是否以 'Bearer ' 开头
		if len(tokenString) <= 7 || tokenString[:7] != "Bearer " {
			c.JSON(http.StatusUnauthorized, gin.H{"error": "Invalid token format"})
			c.Abort()
			fmt.Println("Invalid token format")
			return
		}

		// 去掉 'Bearer ' 前缀
		tokenString = tokenString[7:]

		// 解析和验证 JWT
		claims := &Claims{}
		token, err := jwt.ParseWithClaims(tokenString, claims, func(token *jwt.Token) (interface{}, error) {
			return jwtKey, nil
		})
		if err != nil || !token.Valid {
			c.JSON(http.StatusUnauthorized, gin.H{"error": "Invalid token"})
			c.Abort()
			return
		}

		// 将用户信息存储到上下文
		c.Set("username", claims.Username)
		c.Next()
	}
}

// 受保护的接口
func ProtectedHandler(c *gin.Context) {
	username := c.GetString("username")
	c.JSON(http.StatusOK, gin.H{
		"message": fmt.Sprintf("Welcome %s!", username),
	})
}

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

	// 登录接口
	r.POST("/login", Login)

	// 受保护的接口
	protected := r.Group("/protected")
	protected.Use(AuthMiddleware())
	protected.GET("/home", ProtectedHandler)

	fmt.Println("Server is running on port 8080...")
	r.Run(":8080")
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

云Apprentice

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值