依照学习路径图,本文进入Web框架部分。
本文将对Gin框架的基础进行讲解,希望你已经具备一定的知识,这样才能从本文中获得最大化的收益。
简介
Gin框架是用 Go (Golang) 编写的 Web 框架,主要是基于httprouter,它的性能非常优秀,但是只能在简单的场景使用。因为一旦需求非常庞大,Gin的路由配置会让你崩溃。
Hello World
第一个Gin程序我们还是选择Hello World,因为这是亘古不变的规矩了。
hold on,你不会还没有下载gin的依赖吧?
依赖安装
这一步应该在go.mod文件存在时才能操作,所以建议先在goland创建好项目,再在控制台操作。
安装gin:
go get -u github.com/gin-gonic/gin
如果你感到网速很慢建议你配置镜像:
这里推荐七牛云:goproxy.cn/
Go 1.13 及以上版本可以直接打开你的终端并执行:
$ go env -w GO111MODULE=on
$ go env -w GOPROXY=https://goproxy.cn,direct
Hello World的实现
func main() {
r := gin.Default()
r.GET("/get", func(context *gin.Context) {
context.String(http.StatusOK, "Hello World")
})
r.Run(":8099")
}
从代码中依次分析:
第一步 我们需要创建一个gin的默认实例
第二步 通过实例创建Get方法,Get方法第一个参数为路径,第二个参数为一个方法,是handle,执行回调或者处理逻辑,context是gin的上下文逻辑,通过context实现返回不同类型的结果。
第三步 将服务挂载到8099端口
一个基本的Hello World就已经完成,当你访问 http://localhost:8099/get 时 你就能看到页面显示了 Hello World。
基本的路由操作
Gin 的核心就是路由,所以所有操作都伴随路由的创建。
如何实现基本路由并能使其能够交互呢,我们下面继续讲解。
在Hello World中,我们的程序已经是单向数据交互了,如果想要接收客户端传来的数据就需要进行对参数的接收和处理。
Get方法
路径参数
一般样式:http://localhost:port/get/{:param}
其中在{:param}处
: 只能匹配1个,* 可以匹配任意个数
: 方式的参数获取
此规则能够匹配/user/xxx这种格式,但不能匹配/user/ 或 /user这种格式)
访问方式:http://localhost:8099/get/TT
r.GET("/get/:name", func(context *gin.Context) {
context.String(http.StatusOK,"Hello "+context.Param("name")) //Hello TT
})
*方式的参数获取
此规则既能匹配 /user/xxx/ 格式也能匹配 /user/xxx/other1/other2 这种格式
访问方式:http://localhost:8099/getMulti/TT/To/Learning
r.GET("/getMulti/:name/*action", func(context *gin.Context) {
context.String(http.StatusOK, context.Param("name")+context.Param("action")) // Result: TT/To/Learning
})
非路径参数(URL参数)
URL参数可以通过DefaultQuery()或Query()方法获取
DefaultQuery()若参数不存在,返回默认值,Query()若不存在,返回空串
r.GET("/getParam", func(context *gin.Context) {
// 获取name字段,如果不存在则用默认值代替
name := context.DefaultQuery("name", "normal")
//获取具体值
age := context.Query("age")
context.String(http.StatusOK, fmt.Sprintf("hello %s, your age is %s", name, age))
})
Post方法
通过form-data传递参数
r.POST("/post", func(context *gin.Context) {
context.JSON(http.StatusOK, gin.H{ //返回JSON
"username": context.PostForm("username"),
})
})
使用json传递数据获取
r.POST("/post", func(context *gin.Context) {
json := make(map[string]interface{}) //使用map接收json
context.BindJSON(&json)
username := json["username"]
context.JSON(http.StatusOK, gin.H{
"username": username,
})
})
返回值类型
最常见的就是直接返回字符串
context.String(http.StatusOK,"Hello "+context.Param("name"))
如果想要返回一个json则是使用:
context.JSON(http.StatusOK, gin.H{
"username": username,
})
gin.H会创建一个符合json的数据。
分组路由
相信看到这里的你已经发现,如果这样的单个路由很多会让代码越来越混乱,自然也就一些常用的解决方法出现。
分组路由是指将路由依据功能不同进行划分,一般它遵循这样的文件目录。
├── MultiRouter
│ │
│ ├── V1 (分组路由V1)
│ │ ├── Handle.go (事件触发函数)
│ │ └── router.go (路由分组及声明)
│ │
│ ├── V2
│ │ ├── Handle.go
│ │ └── router.go
│ │
│ └── setup_router.go (批量注册路由)
│
└── main.go (通过setup_router注册路由并开启服务)
分组路由后将会在自定的路径前加上组的信息,例如:
原来:http://localhost:8099/get
现在(v1分组下):http://localhost:8099/v1/get
V1
V1 分组代码:
router.go
func LoadRouter(e *gin.Engine) {
v1 := e.Group("v1")
{
v1.GET("/get", getHandle)
v1.POST("/post", postHandle)
}
}
Handle.go
func getHandle(c *gin.Context) {
c.JSON(http.StatusOK, gin.H{
"result": "V1 Get",
})
}
func postHandle(c *gin.Context) {
c.JSON(http.StatusOK, gin.H{
"result": "V1 Post",
})
}
V2
router.go
func LoadRouter(e *gin.Engine) {
v2 := e.Group("v2")
{
v2.GET("/get", getHandle)
v2.POST("/post", postHandle)
}
}
Handle.go
func getHandle(c *gin.Context) {
c.JSON(http.StatusOK, gin.H{
"result": "V2 get",
})
}
func postHandle(c *gin.Context) {
c.JSON(http.StatusOK, gin.H{
"result": "V2 post",
setup_router.go (批量注册路由)
type Register func(engine *gin.Engine)
func Init(route ...Register) *gin.Engine {
//注册路由
rs := append([]Register{}, route...)
r := gin.New()
for _, register := range rs {
register(r)
}
return r
}
main.go
func main() {
r := MultiRouter.Init(
V1.LoadRouter,
V2.LoadRouter,
)
r.Run(":8000")
}
SessionControl 会话控制
Cookie
func main() {
r := gin.Default()
r.Run(":8099")
}
func cookies(r *gin.Engine) {
r.GET("/getCookie", func(c *gin.Context) {
// 获取客户端是否携带cookie
cookie, err := c.Cookie("key_cookie")
if err != nil {
cookie = "cookie"
c.SetCookie("key_cookie", "value_cookie", // 参数1、2: key & value
60, // 参数3: 生存时间(秒)
"/", // 参数4: 所在目录
"localhost", // 参数5: 域名
false, // 参数6: 安全相关 - 是否智能通过https访问
true, // 参数7: 安全相关 - 是否允许别人通过js获取自己的cookie
)
}
fmt.Printf("cookie的值是: %s\n", cookie)
})
}
Session
func main() {
r := gin.Default()
// 注意该密钥不要泄露了
store := cookie.NewStore([]byte("secret"))
//路由上加入session中间件
r.Use(sessions.Sessions("mySession", store))
r.GET("/setSession", func(c *gin.Context) {
// 设置session
session := sessions.Default(c)
session.Set("key", "value")
session.Save()
})
r.GET("/getSession", func(c *gin.Context) {
// 获取session
session := sessions.Default(c)
v := session.Get("key")
fmt.Println(v)
})
r.Run(":8080")
}
middleware 中间件
中间件是介于应用系统和系统软件之间的一类软件,它使用系统软件所提供的基础服务(功能),衔接网络上应用系统的各个部分或不同的应用,能够达到资源共享、功能共享的目的。
默认中间件
在Gin中给出的中间件很多,基本涵盖了简单使用,如果你有特殊需求也可以进行自定义。
func BasicAuth(accounts Accounts) HandlerFunc // 身份认证
func BasicAuthForRealm(accounts Accounts, realm string) HandlerFunc
func Bind(val interface{}) HandlerFunc //拦截请求参数并进行绑定
func ErrorLogger() HandlerFunc //错误日志处理
func ErrorLoggerT(typ ErrorType) HandlerFunc //自定义类型的错误日志处理
func Logger() HandlerFunc //日志记录
func LoggerWithConfig(conf LoggerConfig) HandlerFunc
func LoggerWithFormatter(f LogFormatter) HandlerFunc
func LoggerWithWriter(out io.Writer, notlogged ...string) HandlerFunc
func Recovery() HandlerFunc
func RecoveryWithWriter(out io.Writer) HandlerFunc
func WrapF(f http.HandlerFunc) HandlerFunc //将http.HandlerFunc包装成中间件
func WrapH(h http.Handler) HandlerFunc //将http.Handler包装成中间件
在使用gin.default时默认使用了Logger(), Recovery()全局作用了两个中间件.
使用中间件
全局使用
func main() {
r := gin.Default()
//配置中间件
r.Use(...)
r.GET("/get", func(context *gin.Context) {
context.String(http.StatusOK, "I get it")
})
r.Run(":8000")
}
在路由分组中使用
func main() {
r := gin.Default()
v1 := r.Group("/v1", gin.Logger(), gin.Recovery())
{
v1.GET("/", func(c *gin.Context) {
c.JSON(200, gin.H{"name": "m1"})
})
v1.GET("/test", func(c *gin.Context) {
c.JSON(200, gin.H{"name": "m1 test"})
})
}
r.Run()
}
或者使用这样的方式也是对的
v1.Use(MyLogMiddleWare())
在单个路由中使用
func main() {
r := gin.Default()
r.GET("/", gin.Recovery(), gin.Logger(), func(c *gin.Context) {
c.JSON(200, gin.H{"name": "m1"})
})
r.Run()
}
自定义中间件
自定义中间件是在无法满足需求的情况下,自己定制化的中间件。一般来说只要它返回的是gin.HandlerFunc 就是自定义中间件。
func MyMiddleware() gin.HandlerFunc {
return func(context *gin.Context) {
fmt.Println("[MyLog] IP:", context.ClientIP())
}
}
Abort()和Next()
next()函数会跳过当前中间件中next()后的逻辑,当下一个中间件执行完成后再执行剩余的逻辑
abort()函数执行终止当前中间件以后的中间件执行,但是会执行当前中间件的后续逻辑
简单实现一个记录函数执行的时间中间件
func nextControl(router *gin.Engine) {
router.GET("/mid", TimeMiddleware, handler)
}
// 记录函数执行的时间中间件
func TimeMiddleware(ctx *gin.Context) {
fmt.Println("------------TimeMiddleware 计时开始------------")
start := time.Now()
ctx.Next()
Since := time.Since(start)
fmt.Println(Since)
fmt.Println("++++++++++++TimeMiddleware 计时结束++++++++++++")
}
func handler(ctx *gin.Context) {
fmt.Println("#############正常的handler执行开始#############")
ctx.JSON(http.StatusOK, gin.H{
"ziop": "ziop",
})
}
FileUpdate 文件上传
单文件
func single(r *gin.Engine) {
// 给表单限制上传大小 (默认 32 MiB)
r.MaxMultipartMemory = 8 << 20 // 8 MiB
r.POST("/upload", func(c *gin.Context) {
file, err := c.FormFile("file")
if err != nil {
c.String(500, "上传文件出错")
}
// 上传到指定路径
c.SaveUploadedFile(file, "~/Desktop/"+file.Filename)
c.String(http.StatusOK, "fileName:", file.Filename)
})
}
多文件
func multi(r *gin.Engine) {
r.MaxMultipartMemory = 8 << 20 // 8 MiB
r.POST("/uploadMulti", func(c *gin.Context) {
// 获取MultipartForm
form, err := c.MultipartForm()
if err != nil {
c.String(http.StatusBadRequest, fmt.Sprintf("get err %s", err.Error()))
}
// 获取所有文件
files := form.File["files"]
for _, file := range files {
// 逐个存
fmt.Println(file.Filename)
}
c.String(200, fmt.Sprintf("upload ok %d files", len(files)))
})
}
与Mysql交互
依赖包
_ "github.com/go-sql-driver/mysql"
"github.com/jmoiron/sqlx"
定义数据库基本信息
var (
UserName string = "root" //用户名
PassWord string = "123456" //密码
IP string = "localhost"
Port int = 3306
DbName string = "go_db"
CharSet string = "utf8mb4"
Db *sql.DB
err error
)
创建一个链接
func connectDataBase() *sqlx.DB {
connect := fmt.Sprintf("%s:%s@tcp(%s:%d)/%s?charset=%s", UserName, PassWord, IP, Port, DbName, CharSet)
Db, _ := sqlx.Open("mysql", connect)
return Db
}
获取全部用户
func GetUserAll() (userSlice []pojo.User) {
Db := connectDataBase()
sql := "select * from user"
Db.Select(&userSlice, sql)
defer Db.Close() //异步关闭链接
return userSlice
}
通过ID获取
func GetUserById(id int) pojo.User {
Db := connectDataBase()
sql := "select * from user where id= ?"
var u pojo.User
Db.Get(&u, sql, id)
defer Db.Close()
return u
}
通过id更新数据
func UpdateById(name string, id int) (int64, error) {
Db := connectDataBase()
sql := "update user set name = ? where id = ?"
ret, _ := Db.Exec(sql, name, id)
defer Db.Close()
return ret.RowsAffected()
}
新增用户
func AddUser(name string, age int) (int64, error) {
Db := connectDataBase()
sql := "insert into user(name,age) values (?,?)"
ret, _ := Db.Exec(sql, name, age)
defer Db.Close()
return ret.LastInsertId()
}
删除用户
func DeleteById(id int) (int64, error) {
Db := connectDataBase()
sql := "delete from user where id=?"
ret, _ := Db.Exec(sql, id)
defer Db.Close()
return ret.RowsAffected()
}
以上就是gin框架的简单使用。
我正在参与掘金技术社区创作者签约计划招募活动,点击链接报名投稿。