文章目录
安装
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/json
,application/x-www-form-urlencoded
,application/xml
,multipart/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")
}