超详细带你学习go高性能web框架----fiber

go-fiber-fast

go-fiber 主要定位为一个轻量级、高性能的 Web 框架,但其灵活性使得它可以通过与其他库的集成,构建出强大而多功能的应用程序,满足不同的业务需求,和gin一样轻量级别的路由,但是性能特别是极端性能比gin好一些,都可以通过整合其他sdk服务来达到效果,由于使用 fasthttp 作为 HTTP 引擎,使得 Fiber 的性能非常出色。(Fasthttp是Go最快的HTTP 引擎。旨在简化快速开发**,零内存分配,并考虑**性能。)

由于该框架再国外文档 纯英文 所以写下该笔记 日后复习 该框架的时候和gin还是很相似的,主要是通过封装的上下文来进行操作

官方文档

https://gofiber.io/ https://docs.fiber.org.cn/

官网提示

从Node.js转到Go的新 Go 开发者在开始构建 Web 应用程序或微服务之前需要经历一段学习曲线。Fiber 作为一个Web 框架,以****极简主义的理念创建,并遵循UNIX 方式,以便新 Go 开发者能够以热情和信任的态度快速进入 Go 世界。

Fiber 的灵感来自于互联网上最流行的 Web 框架 Express。我们将Express 的易用性与 Go 的****原始性能相结合。如果您曾经在 Node.js 中实现过 Web 应用程序(使用 Express 或类似框架),那么许多方法和原则对您来说会显得非常常见

Fiber v3 目前处于测试阶段,正在积极开发中。虽然它提供了令人兴奋的新功能,但请注意,它可能不适合生产使用。我们建议在关键任务应用程序上使用最新的稳定版本 (v2.x)。如果您选择使用 v3,请为潜在的错误和重大更改做好准备。请务必查看官方文档和发行说明以获取更新并谨慎行事。祝您编码愉快!

所以该笔记主要讲解v2

限制

  • 由于 Fiber 使用了 unsafe,该库可能并不总是与最新的 Go 版本兼容。Fiber v3 已使用 Go 版本 1.22 和 1.23 进行了测试。
  • Fiber 与 net/http 接口不兼容。这意味着您将无法使用 gqlgen、go-swagger 或属于 net/http 生态系统的任何其他项目。

下面是笔者之前的笔记 javaer快速学习go-gin

https://blog.csdn.net/qq_55272229/article/details/141233160?spm=1001.2014.3001.5501

fiber官方的web性能测验结果图

在这里插入图片描述

下面代码gitee地址 https://gitee.com/hou-chengyi/go-fiber-fast.git

快速体验

新建目录 然后进入初始化一个go.mod文件

安装相关依赖

go get -u github.com/gofiber/fiber/v2

截至目前2024.8.29 官方推荐的推荐的是v2版本 v3目前还不稳定 官方git: https://github.com/gofiber/fiber 国外的框架需科学上网

快速启动新建 一个main文件

package main

import "github.com/gofiber/fiber/v2"

func main() {
	app := fiber.New()
	//绑定路由 和处理路由的处理器
	app.Get("/", func(c *fiber.Ctx) error {
		return c.SendString("Hello, World!")
	})
	app.Get("/hello", func(c *fiber.Ctx) error {

		return c.SendString("你好啊 路由绑定成功!")

	})

	err := app.Listen(":3000")
	if err != nil {
		return
	}
}

访问localhost:3000/hello

得到响应 ! 所以熟悉gin这个是不是入门超级快呢

路由绑定

基本路由

V2 和v3的不同是处理路由的中间传参使用的是结构体而不是指针

基本路由绑定 这些方法和和gin中的绑定案列都是一样的

在 Fiber 中,你可以使用 app.Get(), app.Post(), app.Put(), app.Delete() 等方法来绑定不同 HTTP 方法的路由。

import (
	"fmt"
	"github.com/gofiber/fiber/v2"
	"log"
)

func hello(c *fiber.Ctx) error {
	msg := fmt.Sprintf("✋ %s", c.Params("*"))
	return c.SendString(msg) // => ✋ register
}

/*
*
 */
func main() {
	app := fiber.New()
	//普通绑定
	app.Get("/", hello)
	app.Get("/hello", func(c *fiber.Ctx) error {
	return c.SendString("Hello, World!")
})

	app.Post("/submit", func(c *fiber.Ctx) error {
	return c.SendString("Form Submitted!")
})
	
	log.Fatal(app.Listen(":3000"))
}


分组绑定

和gin还是很类似的

写一个路由controller 或者handler 一样的

// 模拟设备控制器
type Device struct {
	Id     string `json:"id"`
	Name   string `json:"name"`
	Number string `json:"number"`
}

type DeviceController struct {
}

func NewDeviceController() *DeviceController {
	return &DeviceController{}
}

func (d *DeviceController) GetDevices(c *fiber.Ctx) error {
	return c.JSON(fiber.Map{
		"message": "查询成功",
		"data":    Device{Id: "1", Name: "设备1", Number: "123456"},
	})
}

func (d *DeviceController) CreateDevice(c *fiber.Ctx) error {
	return c.JSON(fiber.Map{
		"message": "创建成功",
		"data":    "创建成功",
	})
}

路由注册中间件

	//分组绑定
	device := app.Group("/device")
	device.Get("/info", deviceController.GetDevices)
	device.Post("/", deviceController.CreateDevice)

提供静态文件路由(v3版本)

//提供静态文件
app.Get("/*", static.New("./public"))

v2 采用

此时静态文件目录和/路由绑定在一起 访问ip:端口/文件地址 就可以直接访问文件数据

	app.Static("/", "router/public")

重构注册路由

和Springboot这样的框架注册路由相比 gin和fib已经相当简洁了 但是还是可以再次进行重构来进行操作

1. 使用 Router 和 Controller

你可以将路由绑定逻辑和处理函数分离到不同的文件中,从而保持代码的整洁和可维护性。具体做法如下:

1.1. 创建 Controller

首先,创建控制器文件 device_controller.go,并定义控制器的方法:

// controller/device_controller.go
package controller

import "github.com/gofiber/fiber/v2"

type DeviceController struct{}

func NewDeviceController() *DeviceController {
	return &DeviceController{}
}

func (dc *DeviceController) GetDevices(c *fiber.Ctx) error {
	// 处理获取设备的逻辑
	return c.SendString("Get Devices")
}

func (dc *DeviceController) CreateDevice(c *fiber.Ctx) error {
	// 处理创建设备的逻辑
	return c.SendString("Create Device")
}
1.2. 创建路由配置文件

然后,在 routes.go 文件中,配置路由绑定逻辑:

// routes/routes.go
package routes

import (
	"github.com/gofiber/fiber/v2"
	"yourapp/controller"
)

func SetupRoutes(app *fiber.App) {
	deviceController := controller.NewDeviceController()

	device := app.Group("/device")
	device.Get("/info", deviceController.GetDevices)
	device.Post("/", deviceController.CreateDevice)

	// 可以添加更多的路由组和控制器
}
1.3. 主程序文件

最后,在主程序文件中调用路由配置函数:

// main.go
package main

import (
	"github.com/gofiber/fiber/v2"
	"yourapp/routes" // 替换为实际路径
)

func main() {
	app := fiber.New()

	// 设置路由
	routes.SetupRoutes(app)

	// 启动服务器
	app.Listen(":3000")
}

2. 使用 Route Setup Function

你可以将路由绑定逻辑封装到一个函数中,以减少重复代码,并使主程序文件更加简洁:

// routes/device_routes.go
package routes

import (
	"github.com/gofiber/fiber/v2"
	"yourapp/controller"
)

func SetupDeviceRoutes(app *fiber.App) {
	deviceController := controller.NewDeviceController()

	device := app.Group("/device")
	device.Get("/info", deviceController.GetDevices)
	device.Post("/", deviceController.CreateDevice)
}

main.go 中调用这个函数:

// main.go
package main

import (
	"github.com/gofiber/fiber/v2"
	"yourapp/routes" // 替换为实际路径
)

func main() {
	app := fiber.New()

	// 设置路由
	routes.SetupDeviceRoutes(app)

	// 启动服务器
	app.Listen(":3000")
}

3. 动态路由注册

如果你有很多控制器和路由,考虑动态注册路由。可以将路由和控制器信息存储在配置文件或映射中,动态创建路由:

// routes/dynamic_routes.go
package routes

import (
	"github.com/gofiber/fiber/v2"
	"yourapp/controller"
)

func SetupDynamicRoutes(app *fiber.App) {
	routes := map[string]func(c *fiber.Ctx) error{
		"/device/info":    controller.NewDeviceController().GetDevices,
		"/device":         controller.NewDeviceController().CreateDevice,
		// 可以添加更多的路由和控制器
	}

	device := app.Group("/device")
	for route, handler := range routes {
		device.Add(route, handler)
	}
}

中间件

(老实说这个框架的api大多数和gin都一模一样 甚至实现逻辑都差不多 说是参考了express 不同在于底层引擎 至于谁借鉴谁 难说)

和路由处理器一样 ,都是以fiber封装的上下文为参数 对请求进行处理 , Next api的调用决定了中间件的执行为前置还是后置 调用下一个中间件 控制权给下一个中间件 否则就会停止执行

func main() {
	app := fiber.New()

	// 全局中间件,注册顺序决定了执行顺序
	app.Use(func(c *fiber.Ctx) error {
		fmt.Println("🥇 再fiber app实列上注册的为全局中间件 并且为第一个注册 第一个执行")
		return c.Next() // 继续到下一个中间件
	})
	app.Use(func(c *fiber.Ctx) error {
		c.Next() // 调用下一个中间件 控制权给下一个中间件 否则就会停止执行
		fmt.Println(" 再fiber app实列上注册的为全局中间件 虽然是第二个注册的 但是放行上下文处理 其他中间件执行完毕才该我")
		return nil
	})

	// 匹配所有路由/api开头的接口 等效于分组
	app.Use("/api", func(c *fiber.Ctx) error {
		fmt.Println("🥈 第二个注册到api开头前缀的 第二个执行 等效注册到分组中间件")
		return c.Next() // 继续到下一个中间件或处理函数
	})

	// GET /api/list
	app.Get("/api/list", func(c *fiber.Ctx) error {
		fmt.Println("🥉 到达接口")
		return c.SendString("Hello, World 👋!") // 返回响应
	})

	log.Fatal(app.Listen(":3000"))
}

由于路由处理的中间件本身就是中间件执行的终点 所以无需进行放行fiber上下文

上述中间件匹配代码 等效于分组注册

	group.Use("/list", func(c *fiber.Ctx) error {
		fmt.Println("🥈 第二个注册到api开头前缀的 第二个执行 等效注册到分组中间件")
		return c.Next()
	})

中间注册顺序


func main() {
	app := fiber.New()

	app.Use(func(c *fiber.Ctx) error {
		fmt.Println("🥇 再fiber app实列上注册的为全局中间件 并且为第一个注册 第一个执行")
		return c.Next()
	})
	app.Use(func(c *fiber.Ctx) error {
		fmt.Println("🥇 再fiber app实列上注册的为全局中间件 第二个个注册 第二个执行")
		return c.Next()
	})
	app.Use(func(c *fiber.Ctx) error {
		c.Next()
		fmt.Println(" 后置中间件:1")
		return nil
	})
	app.Use(func(c *fiber.Ctx) error {
		c.Next()
		fmt.Println(" 后置中间件:2")
		return nil
	})

	group := app.Group("/api")
	// Match all routes starting with /api
	app.Use("/api", func(c *fiber.Ctx) error {
		fmt.Println("🥈 我是第第一个执行的分组中间件")
		return c.Next()
	})
	group.Use("/list", func(c *fiber.Ctx) error {
		fmt.Println("🥈我是第第二个执行的分组中间件 第二个注册到api开头前缀的 第二个执行 ")
		return c.Next()
	})
	// GET /api/list
	app.Get("/api/list", func(c *fiber.Ctx) error {
		fmt.Println("🥉 到达接口")
		return c.SendString("Hello, World 👋!")
	})

	log.Fatal(app.Listen(":3000"))
}

类似java的过滤器链设计风格 先注册的先执行,后注册的后执行(前置中间件 ) 后置中间件则是后注册的先执行

框架中自带了很多中间件 用于处理web中的常见问题

在这里插入图片描述

上下文

和gin一样每次收到一个请求 都会被封装为携程隔离的上下文 那么同样可以在上下文中做处理 比如登录后存放当前用户信息


func main() {
	app := fiber.New()
	key := "userinfo"

	// 注册全局中间件
	app.Use(func(c *fiber.Ctx) error {
		fmt.Println("🥇 在 fiber app 实例上注册的为全局中间件,并且为第一个注册,第一个执行")
		var userinfo string
		if bytes, err := json.Marshal(user{Name: "admin", Password: "123456"}); err != nil {
			fmt.Println(err.Error())
		} else {
			userinfo = string(bytes)
		}
		fmt.Println("存入个人数据: " + userinfo)
		c.Locals(key, userinfo) // 使用 Locals 存储数据
		return c.Next()
	})

	app.Use(func(c *fiber.Ctx) error {
		c.Next()
		get := c.Locals(key).(string) // 使用 Locals 读取数据
		fmt.Println("读取到当前用户操作信息: " + get)
		return nil
	})

	// Match all routes starting with /api
	app.Use("/api", func(c *fiber.Ctx) error {
		get := c.Locals(key).(string) // 使用 Locals 读取数据
		fmt.Println("分组中间件读取到当前用户操作信息: " + get)
		return c.Next()
	})

	// GET /api/list
	app.Get("/api/list", func(c *fiber.Ctx) error {
		fmt.Println("🥉 到达接口")
		get := c.Locals(key).(string) // 使用 Locals 读取数据
		fmt.Println("接口读取到用户信息: " + get)
		u := new(user)
		json.Unmarshal([]byte(get), u)

		return c.SendString(fmt.Sprintf("Hello, World 👋! %s", u.Name))
	})

	log.Fatal(app.Listen(":3000"))
}

接收请求

fiber中对各个请求参数的接收

在 GoFiber 中,处理不同类型的请求参数(如路径参数、查询参数、请求体参数)非常常见。以下是如何在 GoFiber 中接收和处理这些参数的示例。

1. 路径参数(Route Parameters)

路径参数是 URL 中的一部分,通常用于标识资源。例如 /api/user/:id 中的 :id

app.Get("/api/user/:id", func(c *fiber.Ctx) error {
    id := c.Params("id") // 获取路径参数
    return c.SendString("User ID: " + id)
})

2. 查询参数(Query Parameters)

查询参数是 URL 中以 ? 开头的部分,通常用于传递非路径的附加数据。例如 /api/search?name=John&age=30

app.Get("/api/search", func(c *fiber.Ctx) error {
    name := c.Query("name") // 获取查询参数
    age := c.Query("age")   // 获取查询参数
    return c.SendString("Name: " + name + ", Age: " + age)
})

3. 请求体参数(Body Parameters)

请求体参数用于发送更复杂的数据结构,通常通过 POST、PUT 等方法发送,支持不同的格式如 JSON、XML、表单数据等。

3.1 JSON

处理 JSON 格式的请求体:

type User struct {
    Name string `json:"name"`
    Age  int    `json:"age"`
}

app.Post("/api/user", func(c *fiber.Ctx) error {
    user := new(User)
    if err := c.BodyParser(user); err != nil {
        return c.Status(fiber.StatusBadRequest).SendString(err.Error())
    }
    return c.JSON(user)
})
3.2 表单数据(Form Data)

处理 application/x-www-form-urlencodedmultipart/form-data 格式的表单数据:

app.Post("/api/form", func(c *fiber.Ctx) error {
    name := c.FormValue("name") // 获取表单数据参数
    age := c.FormValue("age")
    return c.SendString("Name: " + name + ", Age: " + age)
})
3.3 原始文本(Plain Text)

处理纯文本格式的请求体:

app.Post("/api/text", func(c *fiber.Ctx) error {
    body := c.Body() // 获取原始文本内容
    return c.SendString("Received text: " + string(body))
})

4. Headers(请求头)

获取请求头信息:

app.Get("/api/header", func(c *fiber.Ctx) error {
    contentType := c.Get("Content-Type") // 获取请求头参数
    return c.SendString("Content-Type: " + contentType)
})

5. 上传文件(File Uploads)

处理文件上传:

app.Post("/api/upload", func(c *fiber.Ctx) error {
    file, err := c.FormFile("file") // 获取上传的文件
    if err != nil {
        return c.Status(fiber.StatusBadRequest).SendString(err.Error())
    }
    return c.SaveFile(file, "./uploads/"+file.Filename) // 保存文件
})
	//多文件上传-- 获取所有上传的文件
	//	form, err := c.MultipartForm()
		//if err != nil {
		//	return c.Status(fiber.StatusBadRequest).SendString("Failed to get multipart form: " + err.Error())
		//}

	
上传多个文件
  • API 路径/upload
  • 请求方法:POST
  • 请求:使用工具(如 Postman)上传多个文件到 http://localhost:3000/upload
  • 处理函数
app.Post("/upload", func(c *fiber.Ctx) error {
    form, err := c.MultipartForm()
    if err != nil {
        return err
    }
    files := form.File["files"]
    for _, file := range files {
        err := c.SaveFile(file, "./uploads/"+file.Filename)
        if err != nil {
            return err
        }
    }
    return c.JSON(fiber.Map{
        "status": "success",
        "files":  len(files),
    })
})

6. Cookies

获取和设置 Cookies:

app.Get("/api/set-cookie", func(c *fiber.Ctx) error {
    c.Cookie(&fiber.Cookie{
        Name:  "session_id",
        Value: "123456",
    })
    return c.SendString("Cookie set")
})

app.Get("/api/get-cookie", func(c *fiber.Ctx) error {
    sessionID := c.Cookies("session_id") // 获取 cookie
    return c.SendString("Session ID: " + sessionID)
})

session

对于session fiber需要安装对应的中间件进行操作

  // 初始化 session 存储
    store := session.New()

    // 使用 session 中间件
    app.Use(func(c *fiber.Ctx) error {
        // 获取会话
        sess, err := store.Get(c)
        if err != nil {
            return err
        }

        // 在中间件中设置或操作会话数据
        if sess.Get("name") == nil {
            // 如果 "name" 不存在,则设置一个默认值
            sess.Set("name", "default_user")
        } else {
            // 如果 "name" 存在,更新或读取其值
            name := sess.Get("name").(string)
            log.Println("Current user:", name)
        }

        // 保存会话
        if err := sess.Save(); err != nil {
            return err
        }

        // 继续处理下一个中间件或路由
        return c.Next()
    })

    // 访问会话数据的路由示例
    app.Get("/profile", func(c *fiber.Ctx) error {
        // 从会话中获取数据
        sess, err := store.Get(c)
        if err != nil {
            return err
        }

        name := sess.Get("name").(string)
        return c.SendString("Hello, " + name)
    })

    // 修改会话数据的路由示例
    app.Post("/login", func(c *fiber.Ctx) error {
        sess, err := store.Get(c)
        if err != nil {
            return err
        }

        // 从请求中获取用户名并设置到会话中
        username := c.FormValue("username")
        sess.Set("name", username)

        // 保存会话
        if err := sess.Save(); err != nil {
            return err
        }

        return c.SendString("Logged in as: " + username)
    })

    log.Fatal(app.Listen(":3000"))
}

测试代码


func main() {
	app := fiber.New(fiber.Config{
		BodyLimit: 100 * 1024 * 1024, // 100 MB
	})
	// 初始化 session 存储
	store := session.New()

	// 使用 session 中间件
	app.Use(func(c *fiber.Ctx) error {
		// 获取会话
		sess, err := store.Get(c)
		if err != nil {
			return err
		}

		// 在中间件中设置或操作会话数据
		if sess.Get("name") == nil {
			// 如果 "name" 不存在,则设置一个默认值
			sess.Set("name", "default_user")
		} else {
			// 如果 "name" 存在,更新或读取其值
			name := sess.Get("name").(string)
			log.Info("Current user:", name)
		}

		// 保存会话
		if err := sess.Save(); err != nil {
			return err
		}

		// 继续处理下一个中间件或路由
		return c.Next()
	})

	// 访问会话数据的路由示例
	app.Get("/profile", func(c *fiber.Ctx) error {
		// 从会话中获取数据
		sess, err := store.Get(c)
		if err != nil {
			return err
		}

		name := sess.Get("name").(string)
		return c.SendString("Hello, " + name)
	})

	type User struct {
		Name string `json:"name"`
		Age  int    `json:"age"`
	}
	// GET /api/list
	app.Get("/", func(c *fiber.Ctx) error {
		return c.SendString(fmt.Sprintf("Hello, World 👋! %s", "本案列按时如何接收请求的各个方式传值"))
	})
	app.Get("/api/user/:id", func(c *fiber.Ctx) error {
		id := c.Params("id") // 获取路径参数
		return c.SendString("收到用户id: " + id)
	})
	app.Get("/user/list", func(c *fiber.Ctx) error {
		page := c.Query("page") // 获取查询参数
		size := c.Query("size") // 获取查询参数
		return c.SendString("分页页码: " + page + ", 分页大小: " + size)
	})
	//新增用户
	app.Post("/api/user", func(c *fiber.Ctx) error {
		name := c.FormValue("name") // 获取表单数据参数
		age := c.FormValue("age")
		return c.SendString("新增用户: " + name + ", 年龄: " + age)
	})
	app.Post("/api/jsonuser", func(c *fiber.Ctx) error {
		user := new(User)
		if err := c.BodyParser(user); err != nil {
			return c.Status(fiber.StatusBadRequest).SendString(err.Error())
		}
		return c.JSON(fiber.Map{
			"message": "添加用户成功",
			"data":    user,
		})
	})
	app.Post("/api/text", func(c *fiber.Ctx) error {
		body := c.Body() // 获取原始文本内容
		return c.SendString("接收文本内容: " + string(body))
	})
	/**
	上传文件

	*/
	app.Post("/api/upload", func(c *fiber.Ctx) error {

		file, err := c.FormFile("file") // 获取上传的文件
		if err != nil {
			return c.Status(fiber.StatusBadRequest).SendString(err.Error())
		}
		// 在应用启动时检查并创建上传目录
		if _, err := os.Stat("./uploads"); os.IsNotExist(err) {
			err := os.Mkdir("./uploads", os.ModePerm)
			if err != nil {
				log.Fatalf("Failed to create uploads directory: %v", err)
			}
		}

		return c.SaveFile(file, "./uploads/"+file.Filename) // 保存文件
	})
	app.Get("/api/set-cookie", func(c *fiber.Ctx) error {
		c.Cookie(&fiber.Cookie{
			Name:  "session_id",
			Value: "我是手动保存到游览器的cookie数值",
		})
		return c.SendString("Cookie set")
	})
	app.Get("/api/get-cookie", func(c *fiber.Ctx) error {
		sessionID := c.Cookies("session_id") // 获取 cookie
		return c.SendString("Session ID: " + sessionID)
	})

	log.Fatal(app.Listen(":3000"))
}

响应结果

fiber 也对常见的web响应结果 进行了封装

1. 文本

  • API 路径/text
  • 请求方法:GET
  • 请求curl http://localhost:3000/text
  • 处理函数
app.Get("/text", func(c *fiber.Ctx) error {
    return c.SendString("Hello, World 👋!")
})

2. **JSON **

  • API 路径/json
  • 请求方法:GET
  • 请求curl http://localhost:3000/json
  • 处理函数
app.Get("/json", func(c *fiber.Ctx) error {
    response := fiber.Map{
        "message": "Hello, World 👋!",
        "status":  "success",
    }
    return c.JSON(response)
})

3. 文件下载

  • API 路径/download
  • 请求方法:GET
  • 请求curl http://localhost:3000/download
  • 处理函数
app.Get("/download", func(c *fiber.Ctx) error {
    filePath := "./uploads/sample.pdf"
    return c.Download(filePath)
})

4. 二进制文件响应

  • API 路径/file
  • 请求方法:GET
  • 请求curl http://localhost:3000/file
  • 处理函数
app.Get("/file", func(c *fiber.Ctx) error {
    filePath := "./uploads/sample.pdf"
    return c.SendFile(filePath)
})

5. 数据流响应

  • API 路径/stream
  • 请求方法:GET
  • 请求curl http://localhost:3000/stream
  • 处理函数
	app.Get("/stream", func(c *fiber.Ctx) error {
		data := []byte("你好 世界 👋!...")
		c.Set("Content-Type", "text/event-stream")
		return c.SendStream(bytes.NewReader(data), len(data))
	})

6. WebSocket 通信

  • API 路径/ws
  • 请求方法:WebSocket
  • 请求:使用 WebSocket 客户端连接到 ws://localhost:3000/ws
  • 处理函数
app.Get("/ws", websocket.New(func(c *websocket.Conn) {
    for {
        mt, message, err := c.ReadMessage()
        if err != nil {
            log.Println("read:", err)
            break
        }
        log.Printf("recv: %s", message)
        err = c.WriteMessage(mt, message)
        if err != nil {
            log.Println("write:", err)
            break
        }
    }
}))
6.1socket的高级用法

比如实现广播 会话管理

package main

import (
    "log"
    "sync"
    "github.com/gofiber/fiber/v2"
    "github.com/gofiber/websocket/v2"
)

//会话工具类代码
var (
    // 用于存储 WebSocket 会话的线程安全映射 类似java的concurrentmap
    sessions sync.Map 
)

// 存储会话
func storeSession(key string, conn *websocket.Conn) {
    sessions.Store(key, conn)
}

// 获取会话
func getSession(key string) (*websocket.Conn, bool) {
    conn, ok := sessions.Load(key)
    if ok {
        return conn.(*websocket.Conn), true
    }
    return nil, false
}

// 删除会话
func deleteSession(key string) {
    sessions.Delete(key)
}

//服务端代码
func main() {
    app := fiber.New()

    app.Get("/ws/:userID", websocket.New(func(c *websocket.Conn) {
        userID := c.Params("userID")
        log.Printf("User %s connected", userID)

        // 存储连接到会话映射中
        storeSession(userID, c)

        // 处理消息
        for {
            mt, message, err := c.ReadMessage()
            if err != nil {
                log.Println("Read error:", err)
                break
            }
            log.Printf("Received from %s: %s", userID, message)

            // 回显消息
            if err := c.WriteMessage(mt, message); err != nil {
                log.Println("Write error:", err)
                break
            }
        }

        // 连接关闭
        log.Printf("User %s disconnected", userID)
        deleteSession(userID)
    }))

    log.Fatal(app.Listen(":3000"))
}

//根据socket收到的消息发给具体用户
func sendMessageToUser(userID string, message string) {
    conn, ok := getSession(userID)
    if ok {
        if err := conn.WriteMessage(websocket.TextMessage, []byte(message)); err != nil {
            log.Println("Send message error:", err)
        }
    } else {
        log.Println("No session found for user:", userID)
    }
}

测试代码
func main() {
	app := fiber.New()

	// 文本响应
	app.Get("/text", func(c *fiber.Ctx) error {
		return c.SendString("Hello, World 👋!")
	})

	// JSON 响应
	app.Get("/json", func(c *fiber.Ctx) error {
		response := fiber.Map{
			"message": "你好 世界 👋!",
			"status":  "success",
		}
		return c.JSON(response)
	})

	// 文件下载
	app.Get("/download", func(c *fiber.Ctx) error {
		filePath := "./uploads/4.jpg"
		return c.Download(filePath)
	})

	// 二进制文件响应
	app.Get("/file", func(c *fiber.Ctx) error {
		filePath := "./uploads/4.jpg"
		return c.SendFile(filePath)
	})

	// Stream 数据
	// Stream 数据
	app.Get("/stream", func(c *fiber.Ctx) error {
		data := []byte("你好 世界 👋!...")
		c.Set("Content-Type", "text/event-stream")
		return c.SendStream(bytes.NewReader(data), len(data))
	})

	// WebSocket 通信
	app.Get("/ws", websocket.New(func(c *websocket.Conn) {
		for {
			// 读取消息
			mt, message, err := c.ReadMessage()
			if err != nil {
				log.Println("读取消息错误:", err)
				break
			}
			log.Printf("收到消息: %s", message)

            /**
            第一个参数的数字代表
            websocket.TextMessage (1): 表示消息是文本类型。
			websocket.BinaryMessage (2): 表示消息是二进制类型。
			websocket.CloseMessage (8): 表示关闭消息。 使用这个状态码后关闭通道不会发送消息
			websocket.PingMessage (9): 表示Ping消息。
			websocket.PongMessage (10): 表示Pong消息。
            */
			// 响应消息
			err = c.WriteMessage(mt, message)
			if err != nil {
				log.Println("写入与消息错误:", err)
				break
			}
		}
	}))



	log.Fatal(app.Listen(":3000"))
}

自定义中间件

到目前位置一个web框架的功能已经能基本实现了 对于 gin和fib强大的是轻量的同时还是可以使用中间链来实现很多复杂的功能

统一错误

// 自定义错误处理中间件
	app.Use(func(c *fiber.Ctx) error {
		// 先处理请求
		if err := c.Next(); err != nil {
			// 检查错误类型并返回相应的状态码和消息
			var appErr *AppError
			if ok := errorAs(err, &appErr); ok {
				// 自定义错误类型
				return c.Status(appErr.StatusCode).JSON(fiber.Map{
					"error": appErr.Message,
				})
			}

			// 默认处理
			return c.Status(fiber.StatusInternalServerError).JSON(fiber.Map{
				"error": "服务器错误",
			})
		}
		return nil
	})

	// 示例路由
	app.Get("/error", func(c *fiber.Ctx) error {
		return &AppError{
			StatusCode: fiber.StatusBadRequest,
			Message:    "这是一个自定义的错误",
		}
	})

限流中间件

package main

import (
	"log"
	"sync"
	"time"

	"github.com/gofiber/fiber/v2"
)

var (
	requestCounts = sync.Map{}       // 存储每个 IP 的请求次数和时间
	blacklist     = sync.Map{}       // 存储被拉黑的 IP 及其拉黑过期时间
	maxRequests   = 100              // 最大请求次数
	window        = time.Minute      // 时间窗口这里设置的1分种
	blacklistTime = 10 * time.Minute // 拉黑时间
)

func limitRate(c *fiber.Ctx) error {
	clientIP := c.IP()

	// 检查是否在黑名单中
	if blacklistTime, exists := blacklist.Load(clientIP); exists {
		if time.Now().Before(blacklistTime.(time.Time)) {
			//如果黑名单种
			return c.Status(fiber.StatusTooManyRequests).SendString("请求过多,请稍后再试")
		}
		// 从黑名单中移除
		blacklist.Delete(clientIP)
	}

	// 限流逻辑
	if value, exists := requestCounts.Load(clientIP); exists {
		data := value.(struct {
			Count    int
			LastTime time.Time
		})

		// 判断时间窗口是否过期
		if time.Since(data.LastTime) > window {
			// 重置请求次数
			requestCounts.Store(clientIP, struct {
				Count    int
				LastTime time.Time
			}{
				Count:    1,
				LastTime: time.Now(),
			})
			return c.Next()
		}

		// 检查请求次数
		if data.Count >= maxRequests {
			// 添加到黑名单
			blacklist.Store(clientIP, time.Now().Add(blacklistTime))
			return c.Status(fiber.StatusTooManyRequests).SendString("请求过多,请稍后再试")
		}

		// 更新请求次数
		requestCounts.Store(clientIP, struct {
			Count    int
			LastTime time.Time
		}{
			Count:    data.Count + 1,
			LastTime: data.LastTime,
		})
	} else {
		// 添加新的 IP 记录
		requestCounts.Store(clientIP, struct {
			Count    int
			LastTime time.Time
		}{
			Count:    1,
			LastTime: time.Now(),
		})
	}

	return c.Next()
}

func main() {
	app := fiber.New()

	// 使用限流中间件
	app.Use(limitRate)

	// 示例路由
	app.Get("/", func(c *fiber.Ctx) error {
		return c.SendString("Hello, World!")
	})

	log.Fatal(app.Listen(":3000"))
}

.限流策略的目的是限制每个 IP 地址在指定时间窗口内的请求次数,从而防止滥用和保护服务器资源。具体来说,代码实现了基于时间窗口的限流策略,其核心思路如下:

限流策略说明

  1. 时间窗口
    • 限流策略使用了一个时间窗口 (window),在此时间窗口内,客户端 IP 的请求次数会被记录和限制。例如,window 设置为 1 分钟 (time.Minute),表示每个 IP 地址在 1 分钟内最多可以发起 maxRequests 次请求。
  2. 最大请求次数
    • maxRequests 设置了每个 IP 地址在时间窗口内允许的最大请求次数。例如,maxRequests 设置为 100,意味着每个 IP 地址在 1 分钟内最多可以发起 100 次请求。如果超过这个限制,该 IP 地址将被添加到黑名单中。
  3. 黑名单机制
    • 如果某个 IP 地址在时间窗口内的请求次数超过了 maxRequests,它会被添加到黑名单中,并在一段时间内(blacklistTime,例如 10 分钟)被禁止继续发送请求。在此期间内的请求会被拒绝,并返回“请求过多”的错误信息。

100个请求依旧可以
在这里插入图片描述

101个即开始收到429状态码

在这里插入图片描述

钩子函数

fiber种对于路由的各个周期都有对应的钩子函数 主要针对2.30版本

go get github.com/gofiber/fiber/v2@v2.30.0

测试i代码

可以对项目的路由等生命周期进行输出查看

func main() {
	app := fiber.New(fiber.Config{
		DisableStartupMessage: true,
	})

	// 在路由注册时触发
	app.Hooks().OnRoute(func(route fiber.Route) error {
		log.Printf("路由组成: %s", route.Path)
		return nil
	})

	// 当路由命名时触发
	app.Hooks().OnName(func(r fiber.Route) error {
		fmt.Print("路由命名: " + r.Name + ", ")
		return nil
	})

	group := app.Group("/api")
	group.Get("/", func(c *fiber.Ctx) error {
		return c.SendString("访问成功")
	})

	// 在路由组创建时触发
	app.Hooks().OnGroup(func(group fiber.Group) error {
		log.Printf("组创建: %s", group.Prefix)
		return nil
	})

	// 当路由组命名时触发
	app.Hooks().OnGroupName(func(group fiber.Group) error {
		log.Printf("Group name: %s", group.Prefix)
		return nil
	})

	// 在应用启动并开始监听时触发
	addr := ":3000"

	// 示例路由
	app.Get("/", func(c *fiber.Ctx) error {
		return c.SendString("Hello, World!")
	})

	// 启动服务器
	go func() {
		if err := app.Listen(addr); err != nil {
			log.Fatalf("Failed to start server: %v", err)
		}
	}()

	// 模拟应用运行
	time.Sleep(10 * time.Second)

	// 关闭服务器
	if err := app.Shutdown(); err != nil {
		log.Fatalf("Failed to shut down server: %v", err)
	}
}

数据校验

官方的实现起来比较麻烦

官方演示代码 :数据校验必须query中的数据名字是5-20 年龄是12-18

package main

import (
	"fmt"
	"log"
	"strings"

	"github.com/go-playground/validator/v10"
	"github.com/gofiber/fiber/v2"
)

// 定义用户结构体
type (
	User struct {
		Name string `validate:"required,min=5,max=20"` // Name字段是必填项,长度最少5个字符,最多20个字符
		Age  int    `validate:"required,teenager"`     // Age字段是必填项,并且需要符合自定义的'teenager'标签
	}

	// 错误响应结构体
	ErrorResponse struct {
		Error       bool
		FailedField string
		Tag         string
		Value       interface{}
	}

	// 自定义的校验器结构体
	XValidator struct {
		validator *validator.Validate
	}

	// 全局错误处理响应结构体
	GlobalErrorHandlerResp struct {
		Success bool   `json:"success"`
		Message string `json:"message"`
	}
)

// 实例化一个全局的校验器
var validate = validator.New()

// 校验方法,接受一个结构体并返回错误信息
func (v XValidator) Validate(data interface{}) []ErrorResponse {
	validationErrors := []ErrorResponse{}

	// 对传入的结构体进行校验
	errs := validate.Struct(data)
	if errs != nil {
		// 如果有校验错误,遍历错误信息
		for _, err := range errs.(validator.ValidationErrors) {
			// 创建一个错误响应
			var elem ErrorResponse

			elem.FailedField = err.Field() // 获取错误的字段名
			elem.Tag = err.Tag()           // 获取违反的标签规则
			elem.Value = err.Value()       // 获取该字段的实际值
			elem.Error = true

			// 将错误响应添加到错误列表中
			validationErrors = append(validationErrors, elem)
		}
	}

	// 返回所有校验错误信息
	return validationErrors
}

func main() {
	// 创建一个自定义校验器
	myValidator := &XValidator{
		validator: validate,
	}

	// 创建 Fiber 实例,并配置全局错误处理
	app := fiber.New(fiber.Config{
		ErrorHandler: func(c *fiber.Ctx, err error) error {
			// 返回 JSON 格式的错误信息
			return c.Status(fiber.StatusBadRequest).JSON(GlobalErrorHandlerResp{
				Success: false,
				Message: err.Error(),
			})
		},
	})

	// 注册自定义的校验规则'teenager'
	myValidator.validator.RegisterValidation("teenager", func(fl validator.FieldLevel) bool {
		// 自定义规则:年龄必须在12到18岁之间
		return fl.Field().Int() >= 12 && fl.Field().Int() <= 18
	})

	// 定义路由
	app.Get("/", func(c *fiber.Ctx) error {
		// 创建用户对象,并从请求中获取name和age参数
		user := &User{
			Name: c.Query("name"),
			Age:  c.QueryInt("age"),
		}

		// 校验用户输入
		if errs := myValidator.Validate(user); len(errs) > 0 && errs[0].Error {
			// 如果校验有错误,收集所有错误信息
			errMsgs := make([]string, 0)

			for _, err := range errs {
				errMsgs = append(errMsgs, fmt.Sprintf(
					"[%s]: '%v' | 需要符合规则 '%s'",
					err.FailedField,
					err.Value,
					err.Tag,
				))
			}

			// 返回错误信息
			return &fiber.Error{
				Code:    fiber.ErrBadRequest.Code,
				Message: strings.Join(errMsgs, " 和 "),
			}
		}

		// 如果校验通过,返回成功信息
		return c.SendString("验证成功!")
	})

	// 启动服务器,监听3000端口
	log.Fatal(app.Listen(":3000"))
}

个人更喜欢的是依赖中间件,校验规则注册到validate后 使用对应路由的前置中间件 可以定义返回消息

package main

import (
	"fmt"
	"log"
	"strings"

	"github.com/go-playground/validator/v10"
	"github.com/gofiber/fiber/v2"
)

// 定义用户结构体
type User struct {
	Name   string `validate:"required" json:"name"`          // Name字段是必填项,长度最少5个字符,最多20个字符
	Age    int    `validate:"required,intRange" json:"age"` // Age字段使用自定义的intRange标签校验
	Score  int    `validate:"required,intRange" json:"score"` // Score字段使用同一个自定义的intRange标签校验
	Address string `validate:"required,home" json:"address"`     // Score字段使用同一个自定义的intRange标签校验
}

// 实例化全局的校验器
var validate = validator.New()

func init() {
	// 注册自定义的校验规则'intRange'
	validate.RegisterValidation("intRange", func(fl validator.FieldLevel) bool {
		value := fl.Field().Int()

		switch fl.FieldName() {
		case "Age":
			return value >= 12 && value <= 18
		case "Score":
			return value >= 0 && value <= 100
		default:
			return false
		}
	})
	validate.RegisterValidation("home", func(fl validator.FieldLevel) bool {
		value := fl.Field().String()

		switch fl.FieldName() {
		case "Adress":
			return strings.Contains(value, "重庆市")
		default:
			return false
		}
	})

}

// 创建数据校验中间件
func validateMiddleware(schema interface{}) fiber.Handler {
	return func(c *fiber.Ctx) error {
		// 将请求体解析到结构体
		if err := c.BodyParser(schema); err != nil {
			return c.Status(fiber.StatusBadRequest).SendString("请求数据格式错误")
		}

		// 进行校验
		err := validate.Struct(schema)
		if err != nil {
			//组装校验信息并返回
			var errorMessages []string
			//对已经注册校验规则进行判断
			for _, err := range err.(validator.ValidationErrors) {
				errorMessages = append(errorMessages, fmt.Sprintf(
					"字段 '%s' 无效: %v (不满足规则: %s)",
					err.Field(),
					err.Value(),
					err.Tag(),
				))
			}
			return c.Status(fiber.StatusBadRequest).SendString(strings.Join(errorMessages, "; "))
		}

		// 校验通过,继续处理请求
		return c.Next()
	}
}

func main() {
	app := fiber.New()

	// 定义路由,并应用校验中间件
	app.Post("/user", validateMiddleware(&User{}), func(c *fiber.Ctx) error {
		// 处理逻辑
		return c.SendString("用户数据有效")
	})

	// 启动服务器,监听3000端口
	log.Fatal(app.Listen(":3000"))
}

性能优化

即使fiber的性能已经很强了 官方文档依旧给出了性能优化 以及可以配置进行优化

官方:自 Fiber v2.32.0 起,我们使用 encoding/json 作为默认 json 库,因为它稳定且具有可生产性。但是,与第三方库相比,标准库有点慢。如果你对 encoding/json 的性能不满意,我们建议你使用这些库

优化性能配置

package main

import (
	/**
	自 Fiber v2.32.0 起,我们使用 encoding/json 作为默认 json 库,因为它稳定且具有可生产性。但是,与第三方库相比,标准库有点慢。如果你对 encoding/json 的性能不满意,我们建议你使用这些库
	*/
	"github.com/goccy/go-json"
	"github.com/gofiber/fiber/v2"
	"log"
	"time"

	"github.com/gofiber/fiber/v2/middleware/compress"
	"github.com/gofiber/fiber/v2/middleware/cors"
	"github.com/gofiber/fiber/v2/middleware/limiter"
	"github.com/gofiber/fiber/v2/middleware/logger"
	"github.com/gofiber/fiber/v2/middleware/recover"
)

func main() {
	// Fiber 配置
	app := fiber.New(fiber.Config{
		JSONEncoder:           json.Marshal,
		JSONDecoder:           json.Unmarshal,
		Prefork:               true,            // 开启 Prefork 模式,利用多核 CPU 提升性能
		IdleTimeout:           5 * time.Second, // 连接空闲超时时间,防止资源被长时间占用
		DisableStartupMessage: true,            // 禁用启动消息,减少启动开销
	})

	// 中间件配置
	app.Use(recover.New())                                                 // 捕获并恢复 panic,防止程序崩溃
	app.Use(logger.New())                                                  // 记录请求日志,方便调试和监控
	app.Use(compress.New(compress.Config{Level: compress.LevelBestSpeed})) // 启用压缩中间件,减少响应体积

	// CORS 配置
	app.Use(cors.New(cors.Config{
		AllowOrigins: "*", // 设置跨域
	}))

	// 请求速率限制,防止恶意攻击 这里只是简单哪处理 还可以黑名单拉黑
	app.Use(limiter.New(limiter.Config{
		Max:        100, // 每分钟最多 100 次请求
		Expiration: 1 * time.Minute,
	}))

	// 示例路由
	app.Get("/", func(c *fiber.Ctx) error {
		return c.SendString("Hello, Fiber!")
	})

	// 启动应用
	log.Fatal(app.Listen(":3000"))
}

版本 api差异

新版fiber主要适配当前go版本1.22,1.23

以下是 go-fiber v2 和 v3 中主要的 API 改动列表:

1. 中间件

  • v2: 使用指针传递上下文

    app.Use(func(c *fiber.Ctx) error {
        return c.Next()
    })
    
  • v3: 使用结构体传递上下文

    app.Use(func(c fiber.Ctx) error {
        return c.Next()
    })
    

2. 路由

  • v2: 路由定义

    
    app.Get("/path", handler)
    
  • v3: 路由定义基本相同,但可能有改进或新增参数支持

3. 请求上下文

  • v2: 上下文方法如 Params, Query, Body 使用方式

    
    id := c.Params("id")
    
  • v3: 上下文方法基本相同,但可能有语法或行为上的小调整具体看文档

4. 响应处理

  • v2: 使用 SendString, JSON, SendFile 等方法

    
    c.SendString("Hello")
    
  • v3: 方法使用基本相同,但可能有增强功能或新的方法

5. 错误处理

  • v2: 错误处理

    app.Use(func(c *fiber.Ctx) error {
        if err := someOperation(); err != nil {
            return c.Status(fiber.StatusInternalServerError).SendString("Internal Server Error")
        }
        return c.Next()
    })
    
  • v3: 错误处理机制基本相同,但可能有改进

6. 配置

  • v2: 配置项传递

    app := fiber.New(fiber.Config{
        // 配置项
    })
    
  • v3: 配置项传递方式类似,但可能有新增或移除的配置项

7. 静态文件

  • v2: 使用 app.Static()

    
    app.Static("/", "./public")
    
  • v3: 使用 static.New(),功能类似但方法调用方式有所变化

    app.Get("/*", static.New("./public"))
    

8. 插件和中间件

  • v2: 插件和中间件注册方式

    app.Use("/path", someMiddleware)
    
  • v3: 插件和中间件注册可能有变化,新增功能或改进

9. 其他

  • v2: 其他功能如 SendFile, Redirect, SendStatus
  • v3: 功能大体相同,可能有新的 API 或功能改进
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

蓝胖子不是胖子

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值