超详细带你学习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 mainimport "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 controllerimport "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 routesimport ("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 mainimport ("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 routesimport ("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 mainimport ("github.com/gofiber/fiber/v2""yourapp/routes" // 替换为实际路径
)func main() {app := fiber.New()// 设置路由routes.SetupDeviceRoutes(app)// 启动服务器app.Listen(":3000")
}

3. 动态路由注册

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

// routes/dynamic_routes.go
package routesimport ("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/listapp.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 /apiapp.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/listapp.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 stringif 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 /apiapp.Use("/api", func(c *fiber.Ctx) error {get := c.Locals(key).(string) // 使用 Locals 读取数据fmt.Println("分组中间件读取到当前用户操作信息: " + get)return c.Next()})// GET /api/listapp.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") // 获取 cookiereturn 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/listapp.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") // 获取 cookiereturn 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 mainimport ("log""sync""github.com/gofiber/fiber/v2""github.com/gofiber/websocket/v2"
)//会话工具类代码
var (// 用于存储 WebSocket 会话的线程安全映射 类似java的concurrentmapsessions 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 *AppErrorif 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 mainimport ("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    intLastTime time.Time})// 判断时间窗口是否过期if time.Since(data.LastTime) > window {// 重置请求次数requestCounts.Store(clientIP, struct {Count    intLastTime 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    intLastTime time.Time}{Count:    data.Count + 1,LastTime: data.LastTime,})} else {// 添加新的 IP 记录requestCounts.Store(clientIP, struct {Count    intLastTime 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 mainimport ("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       boolFailedField stringTag         stringValue       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 ErrorResponseelem.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 mainimport ("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 <= 18case "Score":return value >= 0 && value <= 100default: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 的性能不满意,我们建议你使用这些库

  • goccy/go-json
  • bytedance/sonic
  • segmentio/encoding
  • mailru/easyjson
  • minio/simdjson-go
  • wI2L/jettison

优化性能配置

package mainimport (/**自 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 或功能改进

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.rhkb.cn/news/412795.html

如若内容造成侵权/违法违规/事实不符,请联系长河编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

【Gradle】window下安装gradle及idea配置

gradle安装与配置 背景基本概念下载配置环境变量idea配置构建命令配置全局的镜像仓库 背景 最近在看spring源码时&#xff0c;Spring5 以后都是采用 Gradle 来编译&#xff0c;所以构建源码前先安装 Gradle 环境。 基本概念 Gradle是一个基于Apache Ant和Apache Maven概念的…

优化农业项目流程 10款实用管理系统推荐

国内外主流的 10 款农业建设管理系统对比&#xff1a;PingCode、Worktile、建米农业工程项目管理系统、泛普软件的农业项目管理系统、开创云数字农业管理平台、Trimble Ag Software、Agworld、FarmLogs、Granular、Conservis。 在管理复杂的农业建设项目时&#xff0c;选择合适…

LuaJit分析(三)luajit字节码文件格式

Luajit字节码文件格式的完整信息如上图所示&#xff0c;包括文件头Header和原型Proto&#xff0c;一个原型可以对应lua源码中的一个函数或源文件。一、文件头文件标志&#xff1a;占用三个字节&#xff0c;始终是0x1B4C4A&#xff0c;表示这是一个luajit文件版本&#xff1a;占…

时序预测 | 基于VMD-SSA-LSSVM+LSTM多变量时间序列预测模型(Matlab)

目录 效果一览基本介绍程序设计参考资料 效果一览 基本介绍 旧时回忆&#xff0c;独此一家。基于VMD-SSA-LSSVMLSTM多变量时间序列预测模型&#xff08;Matlab&#xff09; ——————组合模型预测结果—————————— 预测绝对平均误差MAE LSTM VMDSSALSSVM 组合模型 …

Java项目:基于SpringBoot+mysql在线拍卖系统(含源码+数据库+答辩PPT+毕业论文)

一、项目简介 本项目是一套基于SSM框架mysql在线拍卖系统 包含&#xff1a;项目源码、数据库脚本等&#xff0c;该项目附带全部源码可作为毕设使用。 项目都经过严格调试&#xff0c;eclipse或者idea 确保可以运行&#xff01; 该系统功能完善、界面美观、操作简单、功能齐全、…

基层医疗云HIS系统源码:云计算、大数据等现代信息技术研发

云HIS源码&#xff0c;基层云HIS系统源码&#xff0c;基层医疗云HIS系统 利用云计算、大数据等现代信息技术研发的基层医疗云HIS系统实现了医院信息化从局域网向互联网转型&#xff0c;重新定义医疗卫生信息化建设的理念、构架、功能和运维体系。实现了医院信息化由局域网向互…

分享两个方法分析python打包exe

在Python开发中&#xff0c;常常需要将Python脚本打包成独立的可执行文件&#xff0c;以便在没有Python环境的电脑上运行。你是否曾为此感到困惑&#xff0c;不知道该选择哪种工具来实现这一目标&#xff1f;其实&#xff0c;打包Python脚本并不难&#xff0c;关键在于选择合适…

C++从入门到起飞之——list模拟实现 全方位剖析!

​ ​ ​ &#x1f308;个人主页&#xff1a;秋风起&#xff0c;再归来~&#x1f525;系列专栏&#xff1a;C从入门到起飞 &#x1f516;克心守己&#xff0c;律己则安 目录 ​ ​1、list的整体框架 2、list迭代器 >整体分析 >整体框架 >成员函数 >运…

windows安全软件之火绒杀毒的密码忘记后处理

一、问题描述 某次&#xff0c;想升级系统补丁&#xff0c;但多次尝试后都失败&#xff0c;排查杀毒软件影响过程中&#xff0c;发现火绒杀毒配置了密码保护&#xff0c;但因时间太久&#xff0c;密码已无从考证&#xff0c;那我们应该怎样处理这种情况呢&#xff1f; 二、处…

开发知识付费小程序的秘诀:从设计到上线一步到位

在移动互联网时代&#xff0c;知识付费小程序成为内容创作者和教育者的热门选择。它不仅降低了用户的使用门槛&#xff0c;还具备高效传播的优势。本文将带你一步步了解如何开发一个功能齐全的知识付费小程序&#xff0c;从设计规划到技术实现&#xff0c;最后顺利上线。 一、…

QT接收并解析GPS模块串口数据

目录 一、QT读取串口数据 二、解析数据 目标&#xff1a; 使用QT&#xff0c;读取gps模块的串口数据&#xff0c;并解析其中的经纬高数据&#xff0c;然后进行处理 一、QT读取串口数据 变量定义 QSerialPort *serial; QSerialPortInfo SerialPortInfo; QByteArray lineData…

RKNPU2从入门到实践 --- 【10】RKNPU2零拷贝API实现RKNN模型在RK3588开发板上的部署

目录 一、为什么叫零拷贝API&#xff1f; 二、零拷贝API执行流程&#xff08;代码解读&#xff09; 2.1 前奏工作 2.2 main.cc文件的编写&#xff08;代码的编写&#xff09; 2.2.1 第一步&#xff1a;rknn_init接口创建rknn_context对象、加载RKNN模型 2.2.2 第二步…

C# 传值参数

传值参数 1.值类型 值参数创建变量的副本&#xff1a;当传递值参数时&#xff0c;实际上是创建了原始变量的一个副本&#xff0c;然后将副本传递给方法。对值参数的操作永远不影响变量的值&#xff1a;由于是复制了一份新的副本&#xff0c;所以对副本进行操作不会影响原始变量…

python,json数据格式,pyecharts模块,pycharm中安装pyecharts

json数据格式 JSON是一种轻量级的数据交互格式 可以按照JSON指定的格式去组织和封装数据 JSON本质上是一个带有特定格式的字符串 主要功能&#xff1a; json就是一种在各个编程语言中流通的数据格式&#xff0c;负责不同编程语言中的数据传递和交互. 类似于&#xff1a; 国…

普元Devops-在云主机上拉取harbor的docker镜像并部署

1 前言 本文讲解如何从普元Devops配置构建&#xff0c;从而实现在云主机上拉取Docker镜像&#xff0c;然后运行Docker容器&#xff0c;实现云主机的Docker部署。 2 主要步骤说明 首先&#xff0c;我们有一个Devops服务器&#xff0c;还有一个云主机服务器&#xff0c;还有一个…

【微信小程序】如何触发按钮事件,例如调起微信客服

需求 实现一个如下图的效果, 点击客服按钮, 调起微信客服功能, 需要和button组合使用 效果图 实现思路 客服只能通过button按钮调起, 所以我们需要写一个button按钮, open-type“contact”, 然后把它隐藏起来。给客服图标加一个label, 设置for“btnId”, 这样点击图片就会触…

在ElementUI项目中集成iconfont图标库

在前端项目开发中经常会遇到使用的组件库提供的ICON图标不够用的情况。最常见的解决方案无非就是把设计图的图标切图引入到项目中。还有就是使用svg图标&#xff0c;封装一个渲染组件在项目里面直接引入这个组件。 本文将介绍另一种方法&#xff0c;即集成iconfont图标库的图标…

养老小程序源码家政服务小程序开发方案

预约上门养老小程序&#xff0c;是php开发预约&#xff0c;前端是uniapp&#xff0c;有开发好的小程序案例&#xff0c;可源码&#xff0c;也可以二开&#xff0c;也可以定制开发。 一 用户端&#xff1a;服务分类、服务内容详情介绍、在线下单支付&#xff0c;管理我的订单。…

前缀和差分【算法 13】

在算法领域中&#xff0c;前缀和与差分数组是两种高效处理区间问题的技术。它们能在特定问题场景下将时间复杂度从 (O(n)) 降到 (O(1))&#xff0c;适用于频繁的区间查询与修改操作。本文将简要介绍这两种技术及其应用。 1. 前缀和 (Prefix Sum) 前缀和是指一个数组的第 (i)…

利用TOPSIS算法进行生长素和施肥量对农作物各指标影响力的分析

文章目录 1 摘要2 问题的重述1&#xff0e; 背景介绍2&#xff0e; 问题的产生及进行数学建模的意义 3 TOPSIS算法1. TOPSIS算法介绍2. TOPSIS算法使用步骤 4 问题的分析1. 对问题一的分析及解答2. 对问题二的分析及解答3. 对问题三的分析及解答 5 模型的改进1. 验证2.模型改进…