零、前语:
话不多说,先附上Github链接,欢迎star:github.com/Conqueror71…
再附上我的博客链接,欢迎访问:conqueror712.gitee.io/conqueror71…
2023.01.24下午PS:莫名其妙推上首页了(?)
2023.01.25上午PS:为便利咱们阅览,在文末附上完好代码。
简介:
Q:Gin和Gorm都是干什么的?有什么区别?
A:Gin 和 Gorm 是 Go 编程语言中盛行的开源库。可是,它们服务于不同的意图,一般在 web 开发项目中一同运用。
Gin 是一个用于构建 HTTP 服务器的 web 结构。它供给了一个简略易用的 API,用于处理 HTTP 恳求和响应、路由、中间件和其他常见的 web 应用程序所需的功用。它以其高功能和简约为特色,供给了轻量级和灵敏的解决方案来构建 web 服务器。
Gorm 是 Go 的一个 ORM(对象联系映射)库。它供给了一个简略易用的 API,用于与数据库交互、处理数据库搬迁和履行常见的数据库操作,如查询、插入、更新和删去记录。它支撑多种数据库后端,包含 MySQL、PostgreSQL、SQLite 等。
总而言之, Gin 是用于处理 HTTP 恳求和响应、路由、中间件和其他与网络相关的东西的 web 结构,而 Gorm 则是用于与数据库交互并履行常见数据库操作的 ORM 库。它们一般一同运用,来处理 HTTP 恳求/响应并在 web 开发项目中存储或获取数据。
开发环境:
- Windows 10
- VSCode
一、Gin
0. 快速入门:
package main
import (
"encoding/json"
"net/http"
"github.com/gin-gonic/gin"
"github.com/thinkerou/favicon"
)
// 中间件(拦截器),功用:预处理,登录授权、验证、分页、耗时计算...
// func myHandler() gin.HandlerFunc {
// return func(ctx *gin.Context) {
// // 通过自界说中间件,设置的值,在后续处理只需调用了这个中间件的都能够拿到这儿的参数
// ctx.Set("usersesion", "userid-1")
// ctx.Next() // 放行
// ctx.Abort() // 阻止
// }
// }
func main() {
// 创立一个服务
ginServer := gin.Default()
ginServer.Use(favicon.New("./Arctime.ico")) // 这儿假如增加了东西然后再运转没有改变,请重启浏览器,浏览器有缓存
// 加载静态页面
ginServer.LoadHTMLGlob("templates/*") // 一种是全局加载,一种是加载指定的文件
// 加载资源文件
ginServer.Static("/static", "./static")
// 相应一个页面给前端
ginServer.GET("/index", func(ctx *gin.Context) {
ctx.HTML(http.StatusOK, "index.html", gin.H{
"msg": "This data is come from Go background.",
})
})
// 能加载静态页面也能够加载测验文件
// 获取恳求中的参数
// 传统办法:usl?userid=xxx&username=conqueror712
// Rustful办法:/user/info/1/conqueror712
// 下面是传统办法的例子
ginServer.GET("/user/info", func(context *gin.Context) { // 这个格式是固定的
userid := context.Query("userid")
username := context.Query("username")
// 拿到之后回来给前端
context.JSON(http.StatusOK, gin.H{
"userid": userid,
"username": username,
})
})
// 此时履行代码之后,在浏览器中能够输入http://localhost:8081/user/info?userid=111&username=666
// 就能够看到回来了JSON格式的数据
// 下面是Rustful办法的例子
ginServer.GET("/user/info/:userid/:username", func(context *gin.Context) {
userid := context.Param("userid")
username := context.Param("username")
// 仍是相同,回来给前端
context.JSON(http.StatusOK, gin.H{
"userid": userid,
"username": username,
})
})
// 指定代码后,只需求在浏览器中http://localhost:8081/user/info/111/555
// 就能够看到回来了JSON数据了,非常便利简洁
// 序列化
// 前端给后端传递JSON
ginServer.POST("/json", func(ctx *gin.Context) {
// request.body
data, _ := ctx.GetRawData()
var m map[string]interface{} // Go语言中object一般用空接口来表明,能够接纳anything
// 顺带一提,1.18以上,interface能够直接改成any
_ = json.Unmarshal(data, &m)
ctx.JSON(http.StatusOK, m)
})
// 用apipost或者postman写一段json传到localhost:8081/json里就能够了
/*
json示例:
{
"name": "Conqueror712",
"age": 666,
"address": "Mars"
}
*/
// 看到后端的实时响应里边接纳到数据就能够了
// 处理表单恳求 这些都是支撑函数式编程,Go语言特性,能够把函数作为参数传进来
ginServer.POST("/user/add", func(ctx *gin.Context) {
username := ctx.PostForm("username")
password := ctx.PostForm("password")
ctx.JSON(http.StatusOK, gin.H{
"msg": "ok",
"username": username,
"password": password,
})
})
// 路由
ginServer.GET("/test", func(ctx *gin.Context) {
// 重定向 -> 301
ctx.Redirect(301, "https://conqueror712.gitee.io/conqueror712.gitee.io/")
})
// http://localhost:8081/test
// 404
ginServer.NoRoute(func(ctx *gin.Context) {
ctx.HTML(404, "404.html", nil)
})
// 路由组暂略
// 服务器端口,用服务器端口来访问地址
ginServer.Run(":8081") // 不写的话默许是8080,也能够更改
}
API用法示例:https://gin-gonic.com/zh-cn/docs/examples/
1. 基准测验
Q:基准测验是什么?
A:基准测验,也称为功能测验或压力测验,是一种用于丈量体系或组件功能的测验。基准测验的意图是了解体系或组件在特定条件下的功能,并将结果与其他类似体系或组件进行比较。基准测验可用于评价各种类型的体系和组件,包含硬件、软件、网络和整个体系。
Q:什么时分需求基准测验呀?
A:基准测验一般涉及在被测体系或组件上运转特定作业负载或任务,并丈量吞吐量、延迟时刻、CPU运用率、内存运用率等各种功能指标。基准测验的结果可用于辨认瓶颈和功能问题,并做出有关怎么优化体系或组件以提高功能的正确决议计划。
有许多不同类型的基准测验,每种类型都有自己的指标和作业负载。常见的基准测验类型包含:
- 人工基准测验:运用人作业业负载来丈量体系或组件的功能。
- 实在世界基准测验:运用实在世界的作业负载或场景来丈量体系或组件的功能。
- 压力测验:旨在将体系或组件推到极限,以确定在正常运用条件下可能不明显的功能问题
重要的是要知道基准测验不是一次性的活动,而是应该定期进行的活动,以评价体系的功能并检测随时刻的耗费。
Q:什么样的基准测验结果是咱们想要的呀?
A:
- 在必定的时刻内实现的总调用数,越高越好
- 单次操作耗时(ns/op),越低越好
- 堆内存分配 (B/op), 越低越好
- 每次操作的均匀内存分配次数(allocs/op),越低越好
2. Gin的特性与Jsoniter:
Gin v1 稳定的特性:
- 零分配路由。
- 仍然是最快的 http 路由器和结构。
- 完好的单元测验支撑。
- 实战检测。
- API 冻住,新版别的发布不会破坏你的代码。
- Gin 项目能够轻松部署在任何云供给商上。
Gin 运用 encoding/json 作为默许的 json 包,可是你能够在编译中运用标签将其修正为 jsoniter。
$ go build -tags=jsoniter .
Jsoniter是什么?
-
json-iterator
是一款快且灵敏的JSON
解析器,同时供给Java
和Go
两个版别。 -
json-iterator
是最快的JSON
解析器。它最多能比一般的解析器快10倍之多 - 独特的
iterator api
能够直接遍历JSON
,极致功能、零内存分配 - 从dsljson和jsonparser借鉴了很多代码。
下载依赖:go get github.com/json-iterator/go
二、GORM
0. 特性与装置:
- 全功用 ORM
- 相关 (Has One,Has Many,Belongs To,Many To Many,多态,单表承继)
- Create,Save,Update,Delete,Find 中钩子办法
- 支撑
Preload
、Joins
的预加载 - 事务,嵌套事务,Save Point,Rollback To Saved Point
- Context、预编译形式、DryRun 形式
- 批量插入,FindInBatches,Find/Create with Map,运用 SQL 表达式、Context Valuer 进行 CRUD
- SQL 构建器,Upsert,数据库锁,Optimizer/Index/Comment Hint,命名参数,子查询
- 复合主键,索引,约束
- Auto Migration
- 自界说 Logger
- 灵敏的可扩展插件 API:Database Resolver(多数据库,读写别离)、Prometheus…
- 每个特性都经过了测验的重重检测
- 开发者友爱
go get -u gorm.io/gorm
go get -u gorm.io/driver/sqlite
其他的弥补内容:
- Gorm是软删去,为了保证数据库的完好性
三、Navicat
新建衔接 -> MySQL -> 衔接名随意 -> 暗码随意 -> 双击左边翻开 -> 右键information_schema -> 新建数据库 -> 称号crud-list -> 字符集utf8mb4
这儿假如翻开的时分报错navicat 1045 - access denied for user 'root'@'localhost' (using password: 'YES')
,则需求检查自己的数据库本身的问题
四、Gin+Gorm的CRUD
衔接数据库
编写测验代码,成功运转即可,可是这个时分还不能检查数据库是否被创立,
假如要看咱们需求界说结构体,然后界说表搬迁,具体代码如下:
package main
import (
"fmt"
"time"
// "gorm.io/driver/sqlite"
"github.com/gin-gonic/gin"
"gorm.io/driver/mysql"
"gorm.io/gorm"
)
func main() {
// 怎么衔接数据库 ? MySQL + Navicat
// 需求更改的内容:用户名,暗码,数据库称号
dsn := "root:BqV?eGcc_1o+@tcp(127.0.0.1:3306)/crud-list?charset=utf8mb4&parseTime=True&loc=Local"
db, err := gorm.Open(mysql.Open(dsn), &gorm.Config{})
fmt.Println("db = ", db)
fmt.Println("err = ", err)
// 衔接池
sqlDB, err := db.DB()
// SetMaxIdleConns 设置闲暇衔接池中衔接的最大数量
sqlDB.SetMaxIdleConns(10)
// SetMaxOpenConns 设置翻开数据库衔接的最大数量。
sqlDB.SetMaxOpenConns(100)
// SetConnMaxLifetime 设置了衔接可复用的最大时刻。
sqlDB.SetConnMaxLifetime(10 * time.Second) // 10秒钟
// 结构体
type List struct {
Name string
State string
Phone string
Email string
Address string
}
// 搬迁
db.AutoMigrate(&List{})
// 接口
r := gin.Default()
// 端口号
PORT := "3001"
r.Run(":" + PORT)
}
界说好之后咱们运转,没有报错并且在终端显示出来3001就是正确的,这个时分咱们能够去Navicat里边检查crud-list下面的”表”,刷新后发现有一个lists发生,那就是对的了。
可是这个时分咱们存在两个问题:
- 没有主键:在struct里增加
gorm.Model
来解决,Ctrl+左键能够检查model
- 表里边的称号变成了复数:详见文档的高档主题-GORM配置里,在
*db*, *err* *:=* gorm.Open(mysql.Open(dsn), *&*gorm.Config{})
里边增加一段话即可
更改完结之后咱们要先在Navicat里边把原来的表lists
删掉才能重新创立,这个时分咱们重新运转,就会发现表单里边多了很多东西
结构体界说与优化
例如:
`gorm:"type:varchar(20); not null" json:"name" binding:"required"`
需求留意的是:
- 结构体里边的变量(Name)有必要首字母大写,否则创立不出列,会被自动疏忽
- gorm指定类型
- json表明json接纳的时分的称号
- binding required表明有必要传入
CRUD接口
// 测验
r.GET("/", func(c *gin.Context) {
c.JSON(200, gin.H{
"message": "恳求成功",
})
})
编写完这一段之后运转代码,然后去postman里边新建一个GET接口127.0.0.1:3001
然后send一下,出现恳求成功就恳求成功了。
增也是相同,写好之后直接用如下JSON来测验就能够:
{
"name" : "张三",
"state" : "在职",
"phone" : "13900000000",
"email" : "6666@qq.com",
"address" : "二仙桥成华大路"
}
回来:
{
"code": "200",
"data": {
"ID": 1,
"CreatedAt": "2023-01-24T09:27:36.73+08:00",
"UpdatedAt": "2023-01-24T09:27:36.73+08:00",
"DeletedAt": null,
"name": "张三",
"state": "在职",
"phone": "13900000000",
"email": "6666@qq.com",
"address": "二仙桥成华大路"
},
"msg": "增加成功"
}
这时分也能够在数据库里看到这条数据
删去也是相同,编写完运转之后增加一个DELETE接口,然后输入127.0.0.1:3001/user/delete/2
之后send就能够看到回来了(前提是有数据)
{
"code": 200,
"msg": "删去成功"
}
假如是删去了不存在的id,就会回来
{
"code": 400,
"msg": "id没有找到,删去失利"
}
顺带一提,事实上这个代码还能够优化,这儿的if else太多了,后边优化的时分有过错直接return
修正也是相同,示例:
{
"name" : "张三",
"state" : "离任",
"phone" : "13900000000",
"email" : "6666@qq.com",
"address" : "二仙桥成华大路"
}
回来:
{
"code": 200,
"msg": "修正成功"
}
查询分为两种:
- 条件查询
- 分页查询
条件查询的话,直接写好了恳求127.0.0.1:3001/user/list/王五
回来:
{
"code": "200",
"data": [
{
"ID": 3,
"CreatedAt": "2023-01-24T10:06:25.305+08:00",
"UpdatedAt": "2023-01-24T10:06:25.305+08:00",
"DeletedAt": null,
"name": "王五",
"state": "在职",
"phone": "13100000000",
"email": "8888@qq.com",
"address": "八仙桥成华大路"
}
],
"msg": "查询成功"
}
悉数 / 分页查询的话
譬如说恳求是:127.0.0.1:3001/user/list?pageNum=1&pageSize=2
意思就是查询第一页的两个
回来:
{
"code": 200,
"data": {
"list": [
{
"ID": 3,
"CreatedAt": "2023-01-24T10:06:25.305+08:00",
"UpdatedAt": "2023-01-24T10:06:25.305+08:00",
"DeletedAt": null,
"name": "王五",
"state": "在职",
"phone": "13100000000",
"email": "8888@qq.com",
"address": "八仙桥成华大路"
}
],
"pageNum": 1,
"pageSize": 2,
"total": 2
},
"msg": "查询成功"
}
假如恳求是:127.0.0.1:3001/user/list
回来:
{
"code": 200,
"data": {
"list": [
{
"ID": 1,
"CreatedAt": "2023-01-24T09:27:36.73+08:00",
"UpdatedAt": "2023-01-24T09:55:20.351+08:00",
"DeletedAt": null,
"name": "张三",
"state": "离任",
"phone": "13900000000",
"email": "6666@qq.com",
"address": "二仙桥成华大路"
},
{
"ID": 3,
"CreatedAt": "2023-01-24T10:06:25.305+08:00",
"UpdatedAt": "2023-01-24T10:06:25.305+08:00",
"DeletedAt": null,
"name": "王五",
"state": "在职",
"phone": "13100000000",
"email": "8888@qq.com",
"address": "八仙桥成华大路"
}
],
"pageNum": 0,
"pageSize": 0,
"total": 2
},
"msg": "查询成功"
}
完好代码如下:
package main
import (
"fmt"
"strconv"
"time"
// "gorm.io/driver/sqlite"
"github.com/gin-gonic/gin"
"gorm.io/driver/mysql"
"gorm.io/gorm"
"gorm.io/gorm/schema"
)
func main() {
// 怎么衔接数据库 ? MySQL + Navicat
// 需求更改的内容:用户名,暗码,数据库称号
dsn := "root:password@tcp(127.0.0.1:3306)/database?charset=utf8mb4&parseTime=True&loc=Local"
db, err := gorm.Open(mysql.Open(dsn), &gorm.Config{
NamingStrategy: schema.NamingStrategy{
SingularTable: true,
},
})
fmt.Println("db = ", db)
fmt.Println("err = ", err)
// 衔接池
sqlDB, err := db.DB()
// SetMaxIdleConns 设置闲暇衔接池中衔接的最大数量
sqlDB.SetMaxIdleConns(10)
// SetMaxOpenConns 设置翻开数据库衔接的最大数量。
sqlDB.SetMaxOpenConns(100)
// SetConnMaxLifetime 设置了衔接可复用的最大时刻。
sqlDB.SetConnMaxLifetime(10 * time.Second) // 10秒钟
// 结构体
type List struct {
gorm.Model // 主键
Name string `gorm:"type:varchar(20); not null" json:"name" binding:"required"`
State string `gorm:"type:varchar(20); not null" json:"state" binding:"required"`
Phone string `gorm:"type:varchar(20); not null" json:"phone" binding:"required"`
Email string `gorm:"type:varchar(40); not null" json:"email" binding:"required"`
Address string `gorm:"type:varchar(200); not null" json:"address" binding:"required"`
}
// 搬迁
db.AutoMigrate(&List{})
// 接口
r := gin.Default()
// 测验
// r.GET("/", func(c *gin.Context) {
// c.JSON(200, gin.H{
// "message": "恳求成功",
// })
// })
// 业务码约定:正确200,过错400
// 增
r.POST("/user/add", func(ctx *gin.Context) {
// 界说一个变量指向结构体
var data List
// 绑定办法
err := ctx.ShouldBindJSON(&data)
// 判别绑定是否有过错
if err != nil {
ctx.JSON(200, gin.H{
"msg": "增加失利",
"data": gin.H{},
"code": "400",
})
} else {
// 数据库的操作
db.Create(&data) // 创立一条数据
ctx.JSON(200, gin.H{
"msg": "增加成功",
"data": data,
"code": "200",
})
}
})
// 删
// 1. 找到对应的id对应的条目
// 2. 判别id是否存在
// 3. 从数据库中删去 or 回来id没有找到
// Restful编码标准
r.DELETE("/user/delete/:id", func(ctx *gin.Context) {
var data []List
// 接纳id
id := ctx.Param("id") // 假如有键值对形式的话用Query()
// 判别id是否存在
db.Where("id = ? ", id).Find(&data)
if len(data) == 0 {
ctx.JSON(200, gin.H{
"msg": "id没有找到,删去失利",
"code": 400,
})
} else {
// 操作数据库删去(删去id所对应的那一条)
// db.Where("id = ? ", id).Delete(&data) <- 其实不需求这样写,因为查到的data里边就是要删去的数据
db.Delete(&data)
ctx.JSON(200, gin.H{
"msg": "删去成功",
"code": 200,
})
}
})
// 改
r.PUT("/user/update/:id", func(ctx *gin.Context) {
// 1. 找到对应的id所对应的条目
// 2. 判别id是否存在
// 3. 修正对应条目 or 回来id没有找到
var data List
id := ctx.Param("id")
// db.Where("id = ?", id).Find(&data) 能够这样写,也能够写成下面那样
// 还能够再Where后边加上Count函数,能够查出来这个条件对应的条数
db.Select("id").Where("id = ? ", id).Find(&data)
if data.ID == 0 {
ctx.JSON(200, gin.H{
"msg": "用户id没有找到",
"code": 400,
})
} else {
// 绑定一下
err := ctx.ShouldBindJSON(&data)
if err != nil {
ctx.JSON(200, gin.H{
"msg": "修正失利",
"code": 400,
})
} else {
// db修正数据库内容
db.Where("id = ?", id).Updates(&data)
ctx.JSON(200, gin.H{
"msg": "修正成功",
"code": 200,
})
}
}
})
// 查
// 第一种:条件查询,
r.GET("/user/list/:name", func(ctx *gin.Context) {
// 获取途径参数
name := ctx.Param("name")
var dataList []List
// 查询数据库
db.Where("name = ? ", name).Find(&dataList)
// 判别是否查询到数据
if len(dataList) == 0 {
ctx.JSON(200, gin.H{
"msg": "没有查询到数据",
"code": "400",
"data": gin.H{},
})
} else {
ctx.JSON(200, gin.H{
"msg": "查询成功",
"code": "200",
"data": dataList,
})
}
})
// 第二种:悉数查询 / 分页查询
r.GET("/user/list", func(ctx *gin.Context) {
var dataList []List
// 查询悉数数据 or 查询分页数据
pageSize, _ := strconv.Atoi(ctx.Query("pageSize"))
pageNum, _ := strconv.Atoi(ctx.Query("pageNum"))
// 判别是否需求分页
if pageSize == 0 {
pageSize = -1
}
if pageNum == 0 {
pageNum = -1
}
offsetVal := (pageNum - 1) * pageSize // 固定写法 记住就行
if pageNum == -1 && pageSize == -1 {
offsetVal = -1
}
// 回来一个总数
var total int64
// 查询数据库
db.Model(dataList).Count(&total).Limit(pageSize).Offset(offsetVal).Find(&dataList)
if len(dataList) == 0 {
ctx.JSON(200, gin.H{
"msg": "没有查询到数据",
"code": 400,
"data": gin.H{},
})
} else {
ctx.JSON(200, gin.H{
"msg": "查询成功",
"code": 200,
"data": gin.H{
"list": dataList,
"total": total,
"pageNum": pageNum,
"pageSize": pageSize,
},
})
}
})
// 端口号
PORT := "3001"
r.Run(":" + PORT)
}
THE END