前言
作为一个前端,我们对Web框架的选择有Express,Koa,Nuxt等。相应地,后端也有Web框架,比如我问chatGPT后端Go的Web框架有哪些 Gin
what:Gin是什么
看了官网的介绍我只知道这玩意儿应该有点牛逼,那么怎么用呢?
Gin 是一个用 Go (Golang) 编写的 HTTP Web 框架。 它具有类似 Martini 的 API,但性能比 Martini 快 40 倍。如果你需要极好的性能,使用 Gin 吧。
how:如何使用Gin?
官网让我运行这样一段代码,go get -u github.com/gin-gonic/gin
,好的我乖乖运行了。
此时自动多了一个go.sum文件,这个文件类似于前端的package.lock把依赖描述清楚
继续我按照官网要求把main.go里面的内容抄了一部分到自己的main.go里面
//main.go
package main
import(
"net/http"
"github.com/gin-gonic/gin"
)
func setupRouter() *gin.Engine {
r := gin.Default()
r.GET("/ping", func(c *gin.Context) {
c.String(http.StatusOK, "pong")
})
return r
}
func main() {
r := setupRouter()
// Listen and Server in 0.0.0.0:8080
r.Run(":8080")
}
疑问一
为什么是用路由去运行?
因为在前端,一开始是创建一个server,然后用server去run,下面就是用express写的服务端
const express = require('express')
const app = express()
const port = 8080
app.get('/ping', (req, res) => {
res.send('pong')
})
app.listen(port, () => {
console.log(`Example app listening on port ${port}`)
})
在Gin中,核心是路由,任何请求先进路由,然后路由再分发到其他地方,就没有像前端一样先创建一个server一样的东西了
疑问二
在Go中,发现经常使用单字母做变量名
比如这个地方的c *gin.Context
通过c把里面的内容点出来,发现c.Request几乎包含了用户请求的所有内容
比较懵逼的是c.Writer(其实就是Response,比如c.Writer.WriteString("pong")
)
这个时候如果会前端可以给这个8080/ping发送一个请求,不会前端可以用postman,再不然就用curl,就可以得到返回的“pong”
疑问三
为什么这个r.Run()不会退出?
比如我在r.Run()下面添加一行打印信息,但不会被打印
因为r.Run()无限执行,一直跟着代码进到Run里面,srv.Server(ln)会无限循环,所以后面的代码不会被打印
总结
- 如何设置路由
r.GET("/XXX",fn)
- 如何写入响应
c.String()
c.JSON()
c.Write.WriteString()
- 如何读取请求
c.Request.URL
c.Request.Body
大概知道了用法后,我直接让chatGPT给我布置任务来学习,现在接收到chatGPT的第一个任务
学习任务:创建基本的路由和处理程序
任务细节:
- 创建一个简单的 "Hello World" 应用程序,使用 Gin 框架创建一个基本的路由和处理程序,返回一个简单的文本响应。
- 添加更多的路由和处理程序,例如处理 GET、POST 请求,处理 URL 参数等。
任务一:Hello World
由官方的pingpong例子,不难修改,就直接变成了Hello World
func setupRouter() *gin.Engine {
r := gin.Default() //r就是路由
r.GET("/", func(c *gin.Context) {
c.String(http.StatusOK, "Hello World")
})
return r
}
任务二:添加更多路由
因为我们要添加更多的路由,如果全写在main里面比较繁琐,所以在cmd新建一个cmd.go 用cmd来统一管理路由
//cmd.go
package cmd
import(
"net/http"
"github.com/gin-gonic/gin"
)
func setupRouter() *gin.Engine {
r := gin.Default()
r.GET("/ping", func(c *gin.Context) {
c.String(http.StatusOK, "pong")
})
return r
}
func RunServer(){
r := setupRouter()
r.Run(":8080")
}
//main.go
package main
import(
"mangosteen/cmd"
)
func main() {
cmd.RunServer()
}
main调用注意的两个点:
- RunServer第一个字母大写,大写了就是相当于export 所以是RunServer,这样才能被外部访问,小写会报错
- 上面的包名是cmd,那在引用的时候就cmd.
但按照工程的结构目录来说,这样写cmd还是太混乱了
因为cmd预留用来承接各种命令行,这个地方的gin只是其中一个
所以在internal下面新建两个文件夹,controller和router,router下创建router.go文件,由router决定到底执行controller下的哪个func
再次改写后
//router.go
package router
import(
"mangosteen/internal/controller"
"github.com/gin-gonic/gin"
)
func New() *gin.Engine {
r := gin.Default()
r.GET("/ping", controller.Ping)
return r
}
//cmd.go
package cmd
import(
"mangosteen/internal/router"
)
func RunServer(){
r := router.New()
r.Run(":8080")
}
//controller.go
package controller
import (
"net/http"
"github.com/gin-gonic/gin"
)
func Ping(c *gin.Context) {
c.String(http.StatusOK, "hello world")
}
连接数据库
package cmd
import (
"log"
"mangosteen/internal/database"
"mangosteen/internal/router"
)
func RunServer() {
database.Connect() //连接数据库
database.CreateTables() //创建表
defer database.Close() //数据库什么时候关呢?服务器挂掉之后就关
r := router.New()
err := r.Run(":8080")
if err != nil {
log.Fatalln(err)
}
log.Println("r.Run 的下一行")
}
pg.go
package database
import (
"database/sql"
"fmt"
"log"
_ "github.com/lib/pq"
)
const (
host = "pg-for-go-mangosteen"
port = 5432
user = "mangosteen"
password = "123456"
dbname = "mangosteen_dev"
)
func Connect() {
// 连接数据库 S表示string
connStr := fmt.Sprintf("host=%s port=%d user=%s password=%s dbname=%s sslmode=disable",
host, port, user, password, dbname)
// open第一个参数是驱动名,第二个参数是连接数据库的字符串
db, err := sql.Open("postgres", connStr)
if err != nil {
log.Fatalln(err)
}
//DB是一个全局变量,在同一个包里就可以使用
DB = db
err = db.Ping()
if err != nil {
log.Fatal(err)
}
log.Println("Successfully connect to db")
}
// 创建 users 表 用DB.Exec去执行一个语句
func CreateTables() {
_, err := DB.Exec(`CREATE TABLE IF NOT EXISTS users (
id SERIAL PRIMARY KEY,
email VARCHAR(100) NOT NULL,
created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
updated_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP
)`)
if err != nil {
log.Fatalln(err)
}
log.Println("Successfully create users table")
}
func Close() {
DB.Close()
log.Println("Successfully close db")
}