Go开发指南-Gin与Web开发

目录:

(1)Go开发指南-Hello World

(2)Go开发指南-Gin与Web开发

(3)Go开发指南-Gorouting

Gin 是一个用 Go 语言编写的轻量级、高性能的 Web 框架,主要用于构建 API 服务和微服务。由于其简洁的 API 设计和强大的路由功能,Gin 在 Go 社区中广受欢迎。

运行Web程序

创建一个目录web-service-gin,初始化模块

go mod init example/web-service-gin

创建main.go

go 复制代码
package main

import (
	"net/http"

	"github.com/gin-gonic/gin"
)

type album struct {
	ID     string  `json:"id"`
	Title  string  `json:"title"`
	Artist string  `json:"artist"`
	Price  float64 `json:"price"`
}

func main() {
	router := gin.Default()
	router.GET("/albums", getAlbums)
	router.Run("localhost:8080")
}

var albums = []album{
	{ID: "1", Title: "Blue Train", Artist: "John Coltrane", Price: 56.99},
	{ID: "2", Title: "Jeru", Artist: "Gerry Mulligan", Price: 17.99},
	{ID: "3", Title: "Sarah Vaughan and Clifford Brown", Artist: "Sarah Vaughan", Price: 39.99},
}

func getAlbums(c *gin.Context) {
	c.IndentedJSON(http.StatusOK, albums)
}

将其运行起来: go run .,就能访问http://lcoalhost:8080/albums了。

再新增一个POST接口:

go 复制代码
// add post handler
func postAlbums(c *gin.Context) {
	var newAlbum album

	if err := c.BindJSON(&newAlbum); err != nil {
		return
	}

	albums = append(albums, newAlbum)
	c.IndentedJSON(http.StatusCreated, newAlbum)
}

// update router
router.POST("/albums", postAlbums)

此时用POST方法来访问就可以来新增album了。

根据指定id返回

新增一个接口:

go 复制代码
// add getById handler
func getAlbumByID(c *gin.Context) {
    id := c.Param("id")
    for _, a := range albums {
        if a.ID == id {
            c.IndentedJSON(http.StatusOK, a)
            return
        }
    }
    c.IndentedJSON(http.StatusNotFound, gin.H{"message": "album not found"})
}

// update router
router.GET("/albums/:id", getAlbumByID)

发起请求:curl http://localhost:8080/albums/1 即可获取第一个album了。

连接数据库

Web服务中访问数据库是必不可少的,下面以postgresql数据库为例来演示如何连接数据库。

先创建数据库和表:

CREATE DATABASE music;

CREATE TABLE album(
    id SERIAL PRIMARY KEY,
    title VARCHAR(36) NOT NULL,
    artist VARCHAR(36) NOT NULL,
    price NUMBRIC(10, 2) NOT NULL
) ;

INSERT INTO album(title, artist, price) VALUES
('Album A', 'Artist 1', 9.99),
('Album B', 'Artist 1', 14.99),
('Album C', 'Artist 2', 19.99);

创建main.go:

go 复制代码
package main

import (
	"database/sql"
	"fmt"
	"log"
	"net/http"

	"github.com/gin-gonic/gin"
	_ "github.com/lib/pq"
)

type Album struct {
	ID     string  `json:"id"`
	Title  string  `json:"title"`
	Artist string  `json:"artist"`
	Price  float64 `json:"price"`
}

var db *sql.DB

func main() {
	dsn := "user=postgres password=admin dbname=music sslmode=disable"
	var err error
	db, err = sql.Open("postgres", dsn)
	if err != nil {
		log.Fatal("Failed to connect to database", err)
	}
	pingErr := db.Ping()
	if pingErr != nil {
		log.Fatal("Failed to ping database", pingErr)
	}
	fmt.Println("Connected!")

	router := gin.Default()
	router.GET("/albums/:artist", getAlbumsByArtist)
	router.Run("localhost:8080")
}

func getAlbumsByArtist(c *gin.Context) {
	artist := c.Param("artist")
	albums, err := queryAlbumsByArtist(artist)
	if err != nil {
		c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
		return
	}

	if len(albums) == 0 {
		c.JSON(http.StatusNotFound, gin.H{"message": "No albums found for artist"})
		return
	}
	c.JSON(http.StatusOK, albums)
}

func queryAlbumsByArtist(artist string) ([]Album, error) {
	rows, err := db.Query("SELECT * FROM album WHERE artist = $1", artist)
	
	if err != nil {
		return nil, err
	}
	defer rows.Close()

	var albums []Album
	for rows.Next() {
		var album Album
		if err := rows.Scan(&album.ID, &album.Title, &album.Artist, &album.Price); err != nil {
			return nil, err
		}
		albums = append(albums, album)
	}

	if err = rows.Err(); err != nil {
		return nil, err
	}
	return albums, nil
}

注意,在导入包时使用了 _ "github.com/lib/pq", 此处_表示该包被导入,但不直接在代码中使用,其作用是执行该包的初始化函数。这种用法称为空白标识符导入,通常用于以下几种情况:

  • 注册驱动程序或插件
  • 执行包的初始化代码

访问curl http://localhost:8080/albums/Artist%201,即可获取返回结果。

Gin的中间件

Gin的中间件函数可以在请求到达handler之前做一些前置处理或者在响应返回给客户端之前做后置处理。Gin提供了很多内置的中间件函数,比如常见的日志记录,CORS处理等。开发者也可以根据需要自己定制中间件函数。

日志中间件

下面以内置的日志中间件函数为例来说明如何做日志前处理:

go 复制代码
package main

import (
	"log"
	"time"
	"github.com/gin-gonic/gin"
)

func LoggerMiddleware() gin.HandlerFunc {
	return func(c *gin.Context) {
		start := time.Now()
		c.Next()
		duration := time.Since(start)
		log.Printf("Request - Method: %s | Status : %d | Duration: %v", c.Request.Method, c.Writer.Status(), duration)
	}
}

func main() {
	router := gin.Default()

	router.Use(LoggerMiddleware())
	router.GET("/", func(c *gin.Context) {
		c.String(200, "Hello, World!")
	})
	router.Run(":8080")
}

访问 http://localhost:8080/, 会看到输出日志中打印日志:

Request - Method: GET | Status : 200 | Duration: 24.291µs

自定义中间件

假设我们需要在请求被处理之前对其进行鉴权,可以自定义中间件:

go 复制代码
package main

import (
	"github.com/gin-gonic/gin"
)

func AuthMiddleware() gin.HandlerFunc {
	return func(c *gin.Context) {
		apiKey := c.GetHeader("X-API-Key")
		if apiKey == "" {
			c.AbortWithStatusJSON(401, gin.H{"error": "Unauthorized"})
			return
		}
		c.Next()
	}
}

func main() {
	router := gin.Default()

	authGroup := router.Group("/api")
	authGroup.Use(AuthMiddleware())
	{
		authGroup.GET("/data", func(c *gin.Context) {
			c.JSON(200, gin.H{"message": "Authenticated and authorized!"})
		})
	}

	router.Run(":8080")
}

当访问http://localhost:8080/api/data时,返回{"error":"Unauthorized"}错误。

路由分组

上文中设置鉴权中间件时对路由进行了分组。Gin允许对路由进行分组,以便更好地组织和维护代码。

下面继续展示路由分组功能:

go 复制代码
package main

import (
	"github.com/gin-gonic/gin"
)

func AuthMiddleware() gin.HandlerFunc {
	return func(c *gin.Context) {
		apiKey := c.GetHeader("X-API-Key")
		if apiKey == "" {
			c.AbortWithStatusJSON(401, gin.H{"error": "Unauthorized"})
			return
		}
		c.Next()
	}
}

func main() {
	router := gin.Default()

	public := router.Group("/public")
	{
		public.GET("/info", func(c *gin.Context) {
			c.String(200, "Public information")
		})
		public.GET("/products", func(c *gin.Context) {
			c.String(200, "Public product list")
		})
	}

	private := router.Group("/private")
	private.Use(AuthMiddleware())
	{
		private.GET("/data", func(c *gin.Context) {
			c.String(200, "Private data accessible after authentication")
		})
		private.POST("/create", func(c *gin.Context) {
			c.String(200, "Create a new resource")
		})
	}

	router.Run(":8080")
}

控制器与Handlers

当后端接口不断增加时,如果将所有的业务逻辑全部放在路由的handlers里面是不明智的。

最好是增加控制器来处理业务逻辑,如下所示:

go 复制代码
package main

import (
	"github.com/gin-gonic/gin"
)

type UserController struct{}

func (uc *UserController) GetUserInfo(c *gin.Context) {
	userID := c.Param("id")
	c.JSON(200, gin.H{"id": userID, "name": "John Doe", "email": "john@example.com"})
}

func main() {
	router := gin.Default()
	userController := &UserController{}
	router.GET("/users/:id", userController.GetUserInfo)

	router.Run(":8080")
}

参考资料

[1]. https://go.dev/doc/tutorial/web-service-gin

相关推荐
__AtYou__4 小时前
Golang | Leetcode Golang题解之第563题二叉树的坡度
leetcode·golang·题解
凡人的AI工具箱11 小时前
15分钟学 Go 第 49 天 :复杂项目开发
开发语言·人工智能·后端·算法·golang
杜杜的man13 小时前
【go从零单排】Random Numbers、Number Parsing
开发语言·python·golang
aiee14 小时前
Golang时间函数
开发语言·后端·golang
还是转转15 小时前
Go开发指南- Goroutine
开发语言·golang
蜗牛沐雨15 小时前
Go语言中的`io.Pipe`:实现进程间通信的利器
开发语言·后端·golang·进程通信·pipe
杜杜的man15 小时前
【go从零单排】泛型(Generics)、链表
开发语言·链表·golang
杜杜的man15 小时前
【go从零单排】JSON序列化和反序列化
golang·json
wuh233315 小时前
golang-基础知识(函数)
开发语言·后端·golang
杜杜的man16 小时前
【go从零单排】XML序列化和反序列化
xml·开发语言·golang