前端初学Gin | 青训营笔记

197 阅读4分钟

前言

作为一个前端,我们对Web框架的选择有Express,Koa,Nuxt等。相应地,后端也有Web框架,比如我问chatGPT后端Go的Web框架有哪些 Gin

image.png

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把依赖描述清楚

1685002834100.png

继续我按照官网要求把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调用注意的两个点:

  1. RunServer第一个字母大写,大写了就是相当于export 所以是RunServer,这样才能被外部访问,小写会报错
  2. 上面的包名是cmd,那在引用的时候就cmd.

但按照工程的结构目录来说,这样写cmd还是太混乱了

因为cmd预留用来承接各种命令行,这个地方的gin只是其中一个

所以在internal下面新建两个文件夹,controller和router,router下创建router.go文件,由router决定到底执行controller下的哪个func

image.png

再次改写后

//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")
}