暂无图片
暂无图片
暂无图片
暂无图片
暂无图片

Go Fiber 极速学-自搭框架脚手架笔记-(1)框架基础极速入门长文篇

小儿来一壶枸杞酒泡茶 2021-11-03
4080

为什么使用go fiber?

image.png

出于好奇心,之前就听闻过这个框架,虽然我的不会Express,听说Fiber是一个参考了Express的Web框架,建立在Go语言写的最快的FasthttpHTTP引擎的基础上。

按官网说的:皆在简化 零内存分配和提高性能,以便快速开发。

至于为啥不用Gin,beego,iris,echo,gf·····感觉上面哪些的框架目前市面上应该是已经很多人,有自己的教脚手架了!我自己出于刚重新接触GO回来没多久的!尝鲜的过程中去学东西也是很有感觉的!所以试一试吧!

目前的它已经出到了V2的版本了,和V1的差别还是比较大的!百度一番之后,也没教程!惯例!哈哈搬砖系列~之官网!!

Fiber 的特点(优势)

官网的大佬的给的几个点:

  • 强大的路由
  • 静态文件服务
  • 极限表现
  • 内存占用低
  • API 接口
  • 中间件和Next支持
  • 快速服务器端编程
  • 模版引擎
  • WebSocket 支持
  • 频率限制器
  • 15 种语言

Fiber的限制

由于 Fiber 使用了 unsafe 特性,导致其可能与最新的 Go 版本不兼容。Fiber 2.18.0 已经在 Go 1.14 到 1.17 上验证过。Fiber 与 net/http 接口不兼容。这意味着你无法使用 gqlen,go-swagger 或者任何其他属于 net/http 生态的项目。

Fiber v2.21.0 版本初步使用

本节内容:

  • 热更新插件使用
  • Fiber app对象的配置项
  • Fiber app路由和路由组
  • Fiber app启动监听自定义(http和https的配置)

1、来自官网的示例代码

1.1 fresh热重启

插件:

D:\code\go\awesomeProject1>go get github.com/pilu/fresh

使用:

D:\code\go\awesomeProject1>fresh
20:23:57 runner      | InitFolders
20:23:57 runner      | mkdir ./tmp
20:23:57 runner      | mkdir ./tmp: Cannot create a file when that file already exists.
20:23:57 watcher     | Watching .
20:23:57 main        | Waiting (loop 1)...
20:23:57 main        | receiving first event /
20:23:57 main        | sleeping for 600 milliseconds
20:23:57 main        | flushing events
20:23:57 main        | Started! (5 Goroutines)
20:23:57 main        | remove tmp\runner-build-errors.log: The system cannot find the file specified.
20:23:57 build       | Building...
20:23:58 runner      | Running...



自己玩的示例代码:

package main

import (
 "fmt"

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

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

 // 定义全局的中间件
 app.Use(func(c *fiber.Ctx) error {
  fmt.Println("🥇 First 2222handler")
  return c.Next()
 })
 // 定义路由
 app.Get("/", func(c *fiber.Ctx) error {
  return c.SendString("你好小钟同学!")
 })

 app.Listen(":3000")
}

启动图示:

然后访问接口即可:http://127.0.0.1:3000/

其实对比Fastapi的框架的来说的,其实框架这东西多数蕾西,而且也和我们的GIN其实总体是保持差不多的。


1.2 其他官网示例的安利

📖 基础路由

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

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

    // GET /flights/LAX-SFO
    app.Get("/flights/:from-:to", func(c *fiber.Ctx) error {
        msg := fmt.Sprintf("💸 From: %s, To: %s", c.Params("from"), c.Params("to"))
        return c.SendString(msg) // => 💸 From: LAX, To: SFO
    })

    // GET /dictionary.txt
    app.Get("/:file.:ext", func(c *fiber.Ctx) error {
        msg := fmt.Sprintf("📃 %s.%s", c.Params("file"), c.Params("ext"))
        return c.SendString(msg) // => 📃 dictionary.txt
    })

    // GET /john/75
    app.Get("/:name/:age/:gender?", func(c *fiber.Ctx) error {
        msg := fmt.Sprintf("👴 %s is %s years old", c.Params("name"), c.Params("age"))
        return c.SendString(msg) // => 👴 john is 75 years old
    })

    // GET /john
    app.Get("/:name", func(c *fiber.Ctx) error {
        msg := fmt.Sprintf("Hello, %s 👋!", c.Params("name"))
        return c.SendString(msg) // => Hello john 👋!
    })

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

📖 静态文件服务

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

    app.Static("/""./public")
    // => http://localhost:3000/js/script.js
    // => http://localhost:3000/css/style.css

    app.Static("/prefix""./public")
    // => http://localhost:3000/prefix/js/script.js
    // => http://localhost:3000/prefix/css/style.css

    app.Static("*""./public/index.html")
    // => http://localhost:3000/any/path/shows/index/html

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

📖 中间件和Next

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

 // 全局中间件件,对所有的路由生效
 app.Use(func(c *fiber.Ctx) error {
  fmt.Println("🥇 First handler")
  return c.Next()
 })

 // 匹配所有使用 /api开头的路由
 app.Use("/api", func(c *fiber.Ctx) error {
  fmt.Println("🥈 Second handler")
  return c.Next()
 })
  
  // 配置多个中间件一起的使用
  app.Use("/api",func(c *fiber.Ctx) error {
  c.Set("X-Custom-Header", random.String(32))
    return c.Next()
}, func(c *fiber.Ctx) error {
    return c.Next()
})

 //注册的具体的路由示例,地址为/api/list
 app.Get("/api/list", func(c *fiber.Ctx) error {
  fmt.Println("🥉 Last handler")
  return c.SendString("Hello, World 👋!")
 })

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

📖 根据官网扩展示例-获取路径参数(1)

package main

import (
 "fmt"

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

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

 // 定义全局的中间件
 app.Use(func(c *fiber.Ctx) error {
  fmt.Println("🥇 First 2222handler")
  return c.Next()
 })
 

 app.Get("/:value", func(c *fiber.Ctx) error {
  return c.SendString("value: " + c.Params("value"))
  // => Get request with value: hello world
 })

 app.Listen(":3000")
}

访问地址:http://127.0.0.1:3000/sdf43534 输出的结果是:

value: sdf43534

📖 根据官网扩展示例-获取路径t数(2)

package main

import (
 "fmt"

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

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

 // 定义全局的中间件
 app.Use(func(c *fiber.Ctx) error {
  fmt.Println("🥇 First 2222handler")
  return c.Next()
 })

 //对参数的校验
 app.Get("/:name?", func(c *fiber.Ctx) error {
  if c.Params("name") != "" {
   return c.SendString("Hello " + c.Params("name"))
   // => Hello john
  }
  return c.SendString("Where is john?")
 })

 app.Listen(":3000")
}


访问的结果:

地址:http://127.0.0.1:3000/name
结果:Hello name
============
地址:http://127.0.0.1:3000
结果:Where is john?

📖 根据官网扩展示例-获取路径t数(3)-通配符的形式

package main

import (
 "fmt"

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

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

 // 定义全局的中间件
 app.Use(func(c *fiber.Ctx) error {
  fmt.Println("🥇 First 2222handler")
  return c.Next()
 })

 //对参数的校验
 app.Get("/api/*", func(c *fiber.Ctx) error {
  return c.SendString("API path: " + c.Params("*"))
  // => API path: user/john
 })

 app.Listen(":3000")
}


访问:

地址:http://127.0.0.1:3000/api/34534/3453
结果:API path: 34534/3453
============

📖 【新增】多应用和flask和fastapi的多应用有些类似!

func main() {
    micro := fiber.New()
    micro.Get("/doe", func(c *fiber.Ctx) error {
        return c.SendStatus(fiber.StatusOK)
    })
    app := fiber.New()
    app.Mount("/john", micro) // GET /john/doe -> 200 OK
    log.Fatal(app.Listen(":3000"))
}

上面的访问的的地址则为:GET /john/doe -> 200 OK

2、 Fiber实例的对象的配置

这官网的就有点不地道,锁好翻译很多种语言了!可惜进入官网还是英文!哈哈 那也只好应啃吧!

其实这个的Fiber实例的对象的配置和我们的之前的fastapi的配置是一个概念,就是对我们的Fiber实例的对象进行特殊的参数配置初始化。

2.1 new使用的是默认的配置

app := fiber.New()

2.2 自定义定制个人的配置初始化信息

app := fiber.New(fiber.Config{
    Prefork:       true,
    CaseSensitive: true,
    StrictRouting: true,
    ServerHeader:  "Fiber",
})

如上面的开启了多进程之后  Prefork:       true,:

2.3 配置初始化信息-配置项有哪些?

挑一些比较值得关注的:


  • Prefork: 是否开启多进程

    • 默认值是 false
    • 作用: 是否开启多进程模式,官网说注意点是:注意:如果启用了,应用程序将需要在shell中运行,因为预叉模式设置了环境变量。如果您使用的是Docker,请确保该应用程序是与CMD ./app或CMD ["sh", "-c", "/app"]
    • 示例,如果开启多进程的情况后:如上面的开启了多进程之后  Prefork:       true,:

  • ServerHeader: 定义响应头中的Server的标记头
    • 默认值是 :“”
    • 作用:启用Server具有给定值的http标头。
    • 示例,写入 ServerHeader:  "Fiber",:那么我们的请求响应头那就有会:

  • CaseSensitive: 路由定义大小写问题的匹配
    • 默认值是 :false
    • 作用:启用后,/Foo和/foo是不同的路由。

  • Immutable :按描述应该是上下文的值是否可复用的问题,以前gin使用的时候也会有这个问题
    • 默认值是 :false
    • 作用:启用后上下文方法返回的所有值都是不可变的。默认情况下,它们在从处理程序返回之前是有效的

  • UnescapePath:解决的应该是路由中的所有编码字符编码问题

  • ETag:应该解决缓存的问题

  • BodyLimit:
  • 默认值是:int类型的4 * 1024 * 1024
  • 作用:为请求体设置允许的最大大小,如果大小超过配置的限制,它将发送413 - Request Entity Too Large回应

  • Concurrency:

    • 默认值是:int类型的256 * 1024
    • 并发连接的最大数量。
  • ReadTimeout:

    • 默认值是:time.Duration
    • 读取请求完成的超时时间显示,默认是不约束。
  • WriteTimeout:

    • 默认值是:time.Duration
    • 写入最长响应时间,默认是不约束。
  • ReadBufferSize:请求读取的每个连接缓冲区大小

  • DisableKeepalive:禁用“保持活动连接”,服务器将在向客户端发送第一个响应后关闭传入连接。

  • ErrorHandler

  • 默认的全局的错误的异常处理。

2.4 应用静态文件服务提供

2.4.1 不设置虚拟路径

配置静态文件:

如设置静态文件的目录:

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

完整代码:

package main

import (
 "fmt"

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

func main() {
 app := fiber.New(fiber.Config{
  Prefork:       true,
  CaseSensitive: true,
  StrictRouting: true,
  ServerHeader:  "Fiber",
 })

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

 // 定义全局的中间件
 app.Use(func(c *fiber.Ctx) error {
  fmt.Println("🥇 First 2222handler")
  return c.Next()
 })

 //对参数的校验
 app.Get("/api/*", func(c *fiber.Ctx) error {
  return c.SendString("API path: " + c.Params("*"))
  // => API path: user/john
 })

 app.Listen(":3000")
}

直接的访问:

http://127.0.0.1:3000/

2.4.2 设置虚拟路径

其中路径实际上不存在于文件系统中,静态方法,为静态目录指定前缀路径

配置静态文件:

如设置静态文件的目录:

 app.Static("/static""./public")

完整代码:

package main

import (
 "fmt"

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

func main() {
 app := fiber.New(fiber.Config{
  Prefork:       true,
  CaseSensitive: true,
  StrictRouting: true,
  ServerHeader:  "Fiber",
 })

 //app.Static("/""./public")
 app.Static("/static""./public")
 // 定义全局的中间件
 app.Use(func(c *fiber.Ctx) error {
  fmt.Println("🥇 First 2222handler")
  return c.Next()
 })

 //对参数的校验
 app.Get("/api/*", func(c *fiber.Ctx) error {
  return c.SendString("API path: " + c.Params("*"))
  // => API path: user/john
 })

 app.Listen(":3000")
}

访问地址:

http://127.0.0.1:3000/static/hello.html

结果:

2.4.3 更多的静态服务配置项参数

app.Static("/""./public", fiber.Static{
  Compress:      true, //是否开启压缩
  ByteRange:     true, //是否启用字节范围请求。
  Browse:        true, //是否启用目录浏览
  Index:         "index.html" //默认的访问
  CacheDuration: 10 * time.Second,//缓存时间
  MaxAge:        3600,
})

3、 Fiber路由和路由组

3.1 Fiber路由和路由组

其实这个概念和GIN的类似,如果你接触过GIN的话其实都一样的性质。完整示例:

package main

import (
 "log"

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

func main() {
 app := fiber.New(fiber.Config{
  Prefork:       true,
  CaseSensitive: true,
  StrictRouting: true,
  ServerHeader:  "Fiber",
 })

 handler := func(c *fiber.Ctx) error {
  c.Set("X-Custom-Header""saaaaaaaaaa")
  return c.SendString("我是你的谁!111111!")
 }
 api := app.Group("/api", handler) // /api
 v1 := api.Group("/v1", handler)   // /api/v1
 v1.Get("/list", handler)          // /api/v1/list
 v1.Get("/user", handler)          // /api/v1/user
 v2 := api.Group("/v2", handler)   // /api/v2
 v2.Get("/list", handler)          // /api/v2/list
 v2.Get("/user", handler)          // /api/v2/user
 log.Fatal(app.Listen(":3000"))

 app.Listen(":3000")
}


具体结果:

3.2 查看所有的路由信息列表

package main

import (
 "encoding/json"
 "fmt"

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

func main() {
 app := fiber.New()
 //app.Server().MaxConnsPerIP = 1

 handler := func(c *fiber.Ctx) error {
  c.Set("X-Custom-Header""saaaaaaaaaa")
  return c.SendString("我是你的谁!111111!")
 }
 api := app.Group("/api", handler) // /api
 v1 := api.Group("/v1", handler)   // /api/v1
 v1.Get("/list", handler)          // /api/v1/list
 v1.Get("/user", handler)          // /api/v1/user
 v2 := api.Group("/v2", handler)   // /api/v2
 v2.Get("/list", handler)          // /api/v2/list
 v2.Get("/user", handler)          // /api/v2/user

 //打印
 data, _ := json.MarshalIndent(app.Stack(), """  ")
 fmt.Println(string(data))

 app.Listen(":3000")
}


输出的结果为:

4、 Fiber启动的监听

http的启动监听:

通常我们的启动的,可以定制器的HOST和PORT,Fiber也一样的可以提供:

// 设置启动的监听端口,默认的HOTS的是本地
app.Listen(":8080")
//  自定义启动额HOTS+端口
app.Listen("127.0.0.1:8080")

https的启动监听:

app.ListenTLS(":443""./cert.pem""./cert.key");

还可以对https进行配置:

&tls.Config{
    MinVersion:               tls.VersionTLS12,
    PreferServerCipherSuites: true,
    Certificates: []tls.Certificate{
        cert,
    },
}

使用net.Listen来启动

package main

import (
 "crypto/tls"
 "net"

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

func main() {
 app := fiber.New()
 //app.Server().MaxConnsPerIP = 1

 handler := func(c *fiber.Ctx) error {
  c.Set("X-Custom-Header""saaaaaaaaaa")
  return c.SendString("我是你的谁!111111!")
 }
 api := app.Group("/api", handler) // /api
 v1 := api.Group("/v1", handler)   // /api/v1
 v1.Get("/list", handler)          // /api/v1/list
 v1.Get("/user", handler)          // /api/v1/user
 v2 := api.Group("/v2", handler)   // /api/v2
 v2.Get("/list", handler)          // /api/v2/list
 v2.Get("/user", handler)          // /api/v2/user

 //打印
 //data, _ := json.MarshalIndent(app.Stack(), """  ")
 //fmt.Println(string(data))

 ln, _ := net.Listen("tcp"":3000")
 cer, _ := tls.LoadX509KeyPair("server.crt""server.key")
 ln = tls.NewListener(ln, &tls.Config{Certificates: []tls.Certificate{cer}})
 app.Listener(ln)
}


5、 Fiber请求上下文篇

5.1 添加响应头信息

package main

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

func main() {
   app := fiber.New()
   //app.Server().MaxConnsPerIP = 1

   app.Get("/", func(c *fiber.Ctx) error {
      c.Accepts("png")
      //c.Accepts("json""text")     // "json"
      //c.Accepts("application/json") // "application/json"

      //新增的响应头信息---下面的字段会出现再响应头
      c.Append("Link""Test")
      c.Append("Link""http://google.com""http://localhost")

      //返回的APP路由堆栈
      return c.JSON(c.App().Stack())
   })

   app.Listen(":3000")
}

结果:

image.png

其他设置响应的方法 c.Set():

app.Get("/", func(c *fiber.Ctx) error {
   c.Set("Content-Type""text/plain")
   // => "Content-type: text/plain"
   // ...
   c.Vary("Accept-Encoding""Accept")
})

5.2 返回JSON格式数据

  return c.JSON(c.App().Stack())

完整示例:

package main

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

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

   type SomeStruct struct {
      Name string
      Age  uint8
   }
   app.Get("/json1", func(c *fiber.Ctx) error {
      // Create data struct:
      data := SomeStruct{
         Name: "Grame",
         Age:  20,
      }
      return c.JSON(data)
   })
   app.Get("/json2", func(c *fiber.Ctx) error {
      return c.JSON(fiber.Map{
         "name""Gram222e",
         "age":  2000,
      })
      
   })

   app.Listen(":3000")
}

或者链式的返回:

return c.Status(fiber.StatusInternalServerError).JSON(fiber.Map{
"message": err.Error(),
})

5.3 获取请求的baseurl和hotsname

fmt.Println("c.BaseURL()", c.BaseURL())
fmt.Println("c.Hostname()", c.Hostname())


image.png

5.4 获取POST提交的body参数

示例:

package main


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

func main() {
   app := fiber.New()
  
   app.Post("/", func(c *fiber.Ctx) error {
      // Get raw body from POST request:
      return c.Send(c.Body()) // []byte("user=john")
   })

   app.Listen(":3000")
}

结果:

5.5 POST中的body参数绑定解析

示例代码:

package main

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

func main() {
   app := fiber.New()
   //app.Server().MaxConnsPerIP = 1

   type Person struct {
      Name string `json:"name" xml:"name" form:"name"`
      Pass string `json:"pass" xml:"pass" form:"pass"`
   }

   app.Post("/", func(c *fiber.Ctx) error {
      p := new(Person)
      if err := c.BodyParser(p); err != nil {
         return err
      }
      return c.JSON(p)
   })

   app.Listen(":3000")
}

结果:

image.png

5.6 Cookie设置和删除

package main

import (
   "time"

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

func main() {
   app := fiber.New()
   //app.Server().MaxConnsPerIP = 1

   type Cookie struct {
      Name     string    `json:"name"`
      Value    string    `json:"value"`
      Path     string    `json:"path"`
      Domain   string    `json:"domain"`
      MaxAge   int       `json:"max_age"`
      Expires  time.Time `json:"expires"`
      Secure   bool      `json:"secure"`
      HTTPOnly bool      `json:"http_only"`
      SameSite string    `json:"same_site"`
   }

   app.Get("/", func(c *fiber.Ctx) error {
      cookie := new(fiber.Cookie)
      cookie.Name = "john"
      cookie.Value = "doe"
      cookie.Expires = time.Now().Add(24 * time.Hour)
      // Set cookie
      c.Cookie(cookie)
      return c.SendString("设置cookie成功!")
   })

   app.Post("/", func(c *fiber.Ctx) error {
      //删除所有的ClearCookie
      c.ClearCookie()
      // 根据键值对删除
      c.ClearCookie("user")
      return c.SendString("删除cookie成功!")
   })

   app.Listen(":3000")
}

5.7 文件下载和发送文件

  • 下载文件示例代码:
package main

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

func main() {
   app := fiber.New()
   //app.Server().MaxConnsPerIP = 1

   app.Get("/", func(c *fiber.Ctx) error {
      //return c.Download("./files/report-12345.pdf");
      // => Download report-12345.pdf
      return c.Download("./小同学.txt""小钟同学的密码文件.txt")
      // => Download report.pdf
   })

   app.Listen(":3000")
}

结果:

image.png

  • 发送文件示例代码:

从给定路径传输文件。设置内容-类型响应HTTP报头字段

PS:发送文件时候,默认的是开启了gzipping的压缩机制,如果需要关闭,设置为false即可,如下

c.SendFile("./static/index.html"false);

演示文件结构:

示例代码:

package main

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

func main() {
   app := fiber.New()
   app.Get("/sendfile", func(c *fiber.Ctx) error {
      return c.SendFile("./public/hello.html")
      // Disable compression
      //return c.SendFile("./static/index.html"false);
   })

   app.Listen(":3000")
}

执行:

image.png

5.8 上传文件-获取表单文件内容

番外篇说:

  • 1:form-data主要是以键值对的形式来上传参数,同时参数之间以&分隔符分开,同时也可以上传文件,文件上传要指定文件类型。
image.png
  • 2:x-www-form-urlencode 这种参数的传递与form-data最大的区别是,x-www-form-urlencode只能是以键值对的形式传参,但是不可以上传文件。

按名称检索MultipartForm文件,第一返回来自给定密钥的文件

示例代码:

package main

import (
   "fmt"

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

func main() {
   app := fiber.New()
   //app.Server().MaxConnsPerIP = 1

   app.Post("/", func(c *fiber.Ctx) error {
      // Get first file from form field "document":
      //MultipartForm()。这将返回一个map[string][]string
      file, _ := c.FormFile("5gmsg.conf")
      // Save file to root directory:
      fmt.Println("文件名称", file.Filename)
      return c.SaveFile(file, fmt.Sprintf("./%s", file.Filename))
   })

   app.Listen(":3000")
}

代码执行结果:

image.png

执行上传后:

image.png

5.9 多文件接收-获取表单文件内容

示例代码:

```
package main

import (
   "fmt"

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

func main() {
   app := fiber.New()
   app.Post("/", func(c *fiber.Ctx) error {
      //MultipartForm()。这将返回一个map[string][]string
      if form, err := c.MultipartForm(); err == nil {
         fmt.Println("输出表单信息,", form)
         files := form.File["5gmsg.conf"]
         fmt.Println("输出files信息,", files)
         for _, file := range files {
            fmt.Println(file.Filename, file.Size, file.Header["Content-Type"][0])
            if err := c.SaveFile(file, fmt.Sprintf("./%s", file.Filename)); err != nil {
               return c.SendString("上传失败!")
            }
         }
      }
      return c.SendString("上传成功!")

   })

   app.Listen(":3000")
}
```

代码执行结果:

image.png

执行上传后:

image.png

5.10 获取表单值内容信息

示例代码:

package main

import (
   "fmt"

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

func main() {
   app := fiber.New()
   app.Post("/", func(c *fiber.Ctx) error {
      //MultipartForm()。这将返回一个map[string][]string
      fmt.Println("name表单值信息", c.FormValue("name"))
      fmt.Println("age表单值信息", c.FormValue("age"))
      return c.SendString(fmt.Sprintf("%s-%s-%s""表单值信息为:", c.FormValue("name"), c.FormValue("age")))

   })

   app.Listen(":3000")
}

输出结果:

image.png

5.11 获取自定义的请求头信息

示例代码:

package main

import (
   "fmt"

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

func main() {
   app := fiber.New()
   app.Get("/", func(c *fiber.Ctx) error {
      //MultipartForm()。这将返回一个map[string][]string

      c.Get("Content-Type") // "text/plain"

      return c.SendString(fmt.Sprintf("%s-%s-%s""请求头信息为:", c.Get("Content-Type"), c.Get("xiaozhong")))

   })

   app.Listen(":3000")
}

示例:

image.png

5.12 获取客户端请求IP信息

c.IP(), c.IPs()

示例代码:

package main

import (
   "fmt"

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

func main() {
   app := fiber.New()
   app.Get("/", func(c *fiber.Ctx) error {
      //MultipartForm()。这将返回一个map[string][]string

      return c.SendString(fmt.Sprintf("%s-%s-%s""请求IP信息为:", c.IP(), c.IPs()))

   })

   app.Listen(":3000")
}

执行截图:

image.png

5.13 判断Content-Type类型和是否XHR请求

package main

import (
   "fmt"

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

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

   app.Get("/", func(c *fiber.Ctx) error {
      c.Is("html")  // true
      c.Is(".html") // true
      c.Is("json")  // false
      c.XHR() // 判断是否XHRjQuery提交
      return c.SendString(fmt.Sprintf("%s-%s-%s""请求IP信息为:", c.Is("html"), c.Is(".html"), c.Is("json")))
   })

   app.Listen(":3000")
}

5.14 存贮变量传递(中间件之间的信息传递)

这个其实对后续的鉴权非常有用滴,比如我的鉴权完成后,穿得TOKEN到下一个接口!

示例代码:

package main

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

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

   //全局的中间件中我们的设置一个参数值信息---
   app.Use(func(c *fiber.Ctx) error {
      c.Locals("user""小钟同学-变量存贮传递")
      return c.Next()
   })
   app.Get("/", func(c *fiber.Ctx) error {
      return c.JSON(fiber.Map{
         "name": c.Locals("user"),
         "age":  2000,
      })
   })

   app.Listen(":3000")
}

执行结果:

image.png

5.15 301或302的重定向


示例代码:

package main

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

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

   //全局的中间件中我们的设置一个参数值信息---
   app.Get("/coffee", func(c *fiber.Ctx) error {
      return c.Redirect("/teapot")
   })
   app.Get("/teapot", func(c *fiber.Ctx) error {
      return c.Status(fiber.StatusTeapot).SendString("我是重定向过来的!")
   })

   app.Listen(":3000")
}

访问地址:http://127.0.0.1:3000/coffee 执行结果:

image.png

其他示例:

app.Get("/", func(c *fiber.Ctx) error {
   return c.Redirect("/foo/bar")
   return c.Redirect("../login")
   return c.Redirect("http://example.com")
   return c.Redirect("http://example.com", 301)
})

5.16 Params参数-获取path参数


代码示例:

package main

import (
   "fmt"

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

func main() {
   app := fiber.New()
   app.Get("/user/:name", func(c *fiber.Ctx) error {
      c.Params("name")
      return c.SendString(fmt.Sprintf("%s-%s""获取路径参数上的name值:", c.Params("name")))
   })

   app.Listen(":3000")
}

访问地址:

http://127.0.0.1:3000/user/nihao

执行结果:

image.png

复杂匹配示例:

package main

import (
   "fmt"

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

func main() {
   app := fiber.New()
   app.Get("/user/:name", func(c *fiber.Ctx) error {
      c.Params("name")
      return c.SendString(fmt.Sprintf("%s-%s""获取路径参数上的name值:", c.Params("name")))
   })
   app.Get("/user2/*", func(c *fiber.Ctx) error {
      return c.SendString(fmt.Sprintf("%s-%s-%s""获取路径参数上的name值:", c.Params("*"), c.Params("*1")))
   })

   app.Listen(":3000")
}

可以访问的地址:

http://127.0.0.1:3000/user2/nihao/3453
http://127.0.0.1:3000/user2/nihao3453
http://127.0.0.1:3000/user2/nihao=3453

image.png

5.17 获取Query参数和QueryParser参数解析


获取参数示例:

package main

import (
   "fmt"

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

func main() {
   app := fiber.New()
   app.Get("/user", func(c *fiber.Ctx) error {
      return c.SendString(fmt.Sprintf("%s-%s-%s""获取路径参数上的name值:", c.Query("name"), c.Query("age")))
   })

   app.Listen(":3000")
}

请求返回:

image.png

示例绑定解析:

package main

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

type Person struct {
   Name string `query:"name"`
   Age  string `query:"age"`
}

func main() {
   app := fiber.New()
   app.Get("/user", func(c *fiber.Ctx) error {
      p := new(Person)
      if err := c.QueryParser(p); err != nil {
         return err
      }
      return c.JSON(p)
   })

   app.Listen(":3000")
}

请求执行:

image.png

获取是有请求路径和参数信息:

package main

import (
   "fmt"

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

func main() {
   app := fiber.New()
   app.Get("/user", func(c *fiber.Ctx) error {

      return c.SendString(fmt.Sprintf("%s-%s""获取访问地址信息:", c.OriginalURL()))
   })

   app.Listen(":3000")
}

执行结果:

image.png

5.18 响应字节流数据

示例代码:

package main

import (
   "bytes"

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

func main() {
   app := fiber.New()
   app.Get("/", func(c *fiber.Ctx) error {
      return c.Send([]byte("Hello, World!")) // => "Hello, World!"
   })
   app.Get("/user", func(c *fiber.Ctx) error {
      return c.SendStream(bytes.NewReader([]byte("Hello, World!"))) // => "Hello, World!"
   })

   app.Listen(":3000")
}

字节流信息的追加写入:


//仅仅是设置响应码信息
app.Get("/ok1", func(c *fiber.Ctx) error {
   c.Write([]byte("Hello, World!111")) // => "Hello, World!"
   c.Write([]byte("Hello, World!222")) // => "Hello, World!"
   c.Status(200)
   return nil
})

执行结果:

image.png

5.19 响应码和响应体同时设置

示例代码:

package main

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

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

   //仅仅是设置响应码信息
   app.Get("/ok1", func(c *fiber.Ctx) error {
      c.Status(200)
      return nil
   })

   app.Get("/ok2", func(c *fiber.Ctx) error {
      return c.Status(400).SendString("Bad Request")
   })

   app.Get("/ok3", func(c *fiber.Ctx) error {
      return c.Status(404).SendFile("./public/gopher.png")
   })
   app.Listen(":3000")
}

5.20 自定义404的错误响应处理

PS:关键坑点:这个的404自定义的话,你需要放到当所有的路由都注册完成后才可以天机,不然它所有的地址都会找不到!PS:关键坑点:这个的404自定义的话,你需要放到当所有的路由都注册完成后才可以天机,不然它所有的地址都会找不到!PS:关键坑点:这个的404自定义的话,你需要放到当所有的路由都注册完成后才可以天机,不然它所有的地址都会找不到!

主要还是使用中间件的方式来处理。

错误的示例代码:

package main

import (
   "github.com/gofiber/fiber/v2"
   "github.com/gofiber/fiber/v2/middleware/recover"
)

func main() {
   app := fiber.New()
   //坑点注意了!!!!!
   //坑点注意了!!!!!
   //坑点注意了!!!!!
   app.Use(func(c *fiber.Ctx) error {
      return c.Status(fiber.StatusNotFound).JSON(fiber.Map{
         "code""404",
         "msg":  "找不到这个地址啊!你估计是搞错地址了!",
      })
   })
   //仅仅是设置响应码信息
   app.Get("/ok1", func(c *fiber.Ctx) error {
      return c.Status(200).SendString("日志记录!!!")
   })

   // 设置全局错误
   app.Use(recover.New(recover.Config{
      //Next: func(c *fiber.Ctx) bool {
      // return c.Query("refresh") == "true"
      //},
      EnableStackTrace: true,
      StackTraceHandler: func(e interface{}) {
         //堆栈信息

      },
   }))
   // This panic will be catch by the middleware
   app.Get("/", func(c *fiber.Ctx) error {
      panic("I'm an error")
   })
   app.Listen(":3000")
}

正确的应该是放最后:

package main

import (
   "fmt"

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

func main() {

   app := fiber.New(fiber.Config{
      ErrorHandler: func(c *fiber.Ctx, err error) error {

         return c.Status(fiber.StatusInternalServerError).JSON(fiber.Map{
            "code": fiber.StatusInternalServerError,
            "msg":  err.Error()},
         )
      },
   })

   //仅仅是设置响应码信息
   app.Get("/ok1", func(c *fiber.Ctx) error {
      return c.Status(200).SendString("日志记录!!!")
   })

   // 设置全局错误
   app.Use(recover.New(recover.Config{
      //Next: func(c *fiber.Ctx) bool {
      // return c.Query("refresh") == "true"
      //},
      EnableStackTrace: true,
      StackTraceHandler: func(e interface{}) {
         //堆栈信息

      },
   }))
   // This panic will be catch by the middleware
   app.Get("/", func(c *fiber.Ctx) error {
      panic("大爷!")
   })
   
   //放到最后来啊!
   app.Use(func(c *fiber.Ctx) error {
      fmt.Println(c)
      return c.Status(fiber.StatusNotFound).JSON(fiber.Map{
         "code""404",
         "msg":  "找不到这个地址啊!你估计是搞错地址了!",
      })
   })

   app.Listen(":3000")
}

执行访问:

image.png

6、 Fiber 中间件篇

来自官网的文档的搬砖系列!

6.1 前言:

Fiberv1和Fiberv2的中间件有区别,

Fiberv2中的中间件必须有return c.next()才可以!

其实这个中间件和我们的所知的fastapi和gin的大致的功能是一样的,都是类似的钩子函数一样的。

在FiberV2中它自己内置了很多的中间件:

  • 用于基础使用用户名和密码认证的中间件 - BasicAuth
  • 缓存作用的中间件 -  Cache
  • 压缩使用的中间件件 - Compress
  • 跨域中间件- CORS
  • 过期设置的中间件- ETag
  • 设置访问的Favicon图标的中间件
  • 配置静态文件服务的一些配置信息中间件-#  FileSystem
  • 限流中间件 -# Limiter
  • 性能分析中间件 -# Pprof
  • 日志记录中间件 -# Logger
  • 全局异常捕获Recover中间件
  • 代理请求访问中间件
  • 全局链路追踪RequestID中间件
  • Session处理中间件
  • 接口请求超时限制的Timeout中间件

下面我的开始搬砖了!!!我就挑几个聊聊!一一的实践一下看看具体的中间件玩法!

6.2 BasicAuth用户名密码基础认证中间件

首先BasicAuth其实就是请求的时候,你需要提供用户名和账号,来验证!

示例代码:

package main

import (
   "github.com/gofiber/fiber/v2"
   "github.com/gofiber/fiber/v2/middleware/basicauth"
)

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

   // 配置我们基础的认证的需要的用户和密码信息-----简单的配置
   app.Use(basicauth.New(basicauth.Config{
      Users: map[string]string{
         "name":     "xiaozhong",
         "password""123456",
      },
   }))
   //仅仅是设置响应码信息
   app.Get("/ok1", func(c *fiber.Ctx) error {
      return c.Status(200).SendString("basicauth 认证的处理成功!!")

   })

   app.Listen(":3000")
}

浏览器访问吧!这样明细:然后访问地址:

image.png

此时需要你提供用户名和密码:

package main

import (
   "github.com/gofiber/fiber/v2"
   "github.com/gofiber/fiber/v2/middleware/basicauth"
)

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

   // 配置我们基础的认证的需要的用户和密码信息-----简单的配置
   app.Use(basicauth.New(basicauth.Config{
      Users: map[string]string{
         "xiaozhong""123456",
         "tongxue""123456",
      },
      Realm: "Forbidden",
      Authorizer: func(user, pass string) bool {
         if user == "xiaozhong" && pass == "123456" {
            return true
         }
         if user == "tongxue" && pass == "123456" {
            return true
         }
         return false
      },
      //认证失败,机型让它弹出窗口!!!!!
      //Unauthorized: func(c *fiber.Ctx) error {
      //
      // return c.Status(200).SendString("basicauth 认证的失败!!!!非法访问!")
      //},
      ContextUsername: "_user",
      ContextPassword: "_pass",
   }))
   //仅仅是设置响应码信息
   app.Get("/ok1", func(c *fiber.Ctx) error {
      return c.Status(200).SendString("basicauth 认证的处理成功!!")
   })

   app.Listen(":3000")
}

输入用户名和密码:

image.png
image.png
image.png

6.3 跨域中间件

  • 跨域默认配置
var ConfigDefault = Config{
   Next:             nil,
   AllowOrigins:     "*",
   AllowMethods:     "GET,POST,HEAD,PUT,DELETE,PATCH",
   AllowHeaders:     "",
   AllowCredentials: false,
   ExposeHeaders:    "",
   MaxAge:           0,
}

  • 跨域中间件使用
package main

import (
   "github.com/gofiber/fiber/v2"
   "github.com/gofiber/fiber/v2/middleware/cors"
)

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

   app.Use(cors.New())
   // Or extend your config for customization
   app.Use(cors.New(cors.Config{
      AllowOrigins: "https://gofiber.io, https://gofiber.net",
      AllowHeaders: "Origin, Content-Type, Accept",
   }))
   //仅仅是设置响应码信息
   app.Get("/ok1", func(c *fiber.Ctx) error {
      return c.Status(200).SendString("basicauth 认证的处理成功!!")
   })

   app.Listen(":3000")
}

6.4 限流中间件

  • 限流中间件件默认配置
var ConfigDefault = Config{
   Max:        5,
   Expiration: 1 * time.Minute,
   KeyGenerator: func(c *fiber.Ctx) string {
      return c.IP()
   },
   LimitReached: func(c *fiber.Ctx) error {
      return c.SendStatus(fiber.StatusTooManyRequests)
   },
}

  • 限流中间件使用:

示例:5秒内最多给2个访问!

package main

import (
   "time"

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

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

   app.Use(limiter.New(limiter.Config{
      Next:       nil,
      Max:        2,
      // 5秒内最多给访问2个
      Expiration: 5 * time.Second,
      KeyGenerator: func(c *fiber.Ctx) string {
         return c.Get("x-forwarded-for")
      },
      LimitReached: func(c *fiber.Ctx) error {
         return c.Status(429).SendString("你丫的访问这么快!!!")
      },
      //Store: myCustomStore{}
   }))
   //仅仅是设置响应码信息
   app.Get("/ok1", func(c *fiber.Ctx) error {
      return c.Status(200).SendString("basicauth 认证的处理成功!!")
   })

   app.Listen(":3000")
}

超过的时候:正常的时候:

image.png

6.5 日志中间件

官网文档中提示提供的很多的参数:

甚至它还已经包括了响应体了内容了!!!不需要我fastapi那样还需要自己去写一次了!!!好像可以哟!哈哈

如,日志中可以记录的常量信息如下:

// Logger variables
const (
   TagPid           = "pid"
   TagTime          = "time"
   TagReferer       = "referer"
   TagProtocol      = "protocol"
   TagIP            = "ip"
   TagIPs           = "ips"
   TagHost          = "host"
   TagMethod        = "method"
   TagPath          = "path"
   TagURL           = "url"
   TagUA            = "ua"
   TagLatency       = "latency"
   TagStatus        = "status"        // response status
   TagResBody                = "resBody"    // response body
   TagQueryStringParams            = "queryParams"    // request query parameters
   TagBody          = "body"          // request body
   TagBytesSent     = "bytesSent"
   TagBytesReceived = "bytesReceived"
   TagRoute         = "route"
   TagError         = "error"
   TagHeader        = "header:"       // request header
   TagQuery         = "query:"        // request query
   TagForm          = "form:"         // request form
   TagCookie        = "cookie:"       // request cookie
   TagLocals        = "locals:"
   // colors
   TagBlack         = "black"
   TagRed           = "red"
   TagGreen         = "green"
   TagYellow        = "yellow"
   TagBlue          = "blue"
   TagMagenta       = "magenta"
   TagCyan          = "cyan"
   TagWhite         = "white"
   TagReset         = "reset"
)

默认的日志中间件的配置:

var ConfigDefault = Config{
   Next:         nil,
   Format:       "[${time}${status} - ${latency} ${method} ${path}\n",
   TimeFormat:   "15:04:05",
   TimeZone:     "Local",
   TimeInterval: 500 * time.Millisecond,
   Output:       os.Stderr,
}

  • Next:是处理是否传递到下一个的,可以自定义
  • Format:定义日志记录的格式
  • TimeFormat:日志的记录的时间格式
  • TimeZone:日志记录的时间时区
  • TimeInterval:时间即那个
  • Output:输出到哪里,默认的是控制台

默认的日志配置

app.Use(logger.New())

然后请求接口控制台会输出日志信息:

image.png

添加全局追踪ID日志配置

package main

import (
   "github.com/gofiber/fiber/v2"
   "github.com/gofiber/fiber/v2/middleware/logger"
   "github.com/gofiber/fiber/v2/middleware/requestid"
)

func main() {
   app := fiber.New()
   app.Use(requestid.New())
   app.Use(logger.New(logger.Config{
      Format:     "${pid} ${locals:requestid} ${status} - ${method} ${path}\n",
      TimeFormat: "02-Jan-2006",
      TimeZone:   "Asia/Shanghai",
   }))
   //仅仅是设置响应码信息
   app.Get("/ok1", func(c *fiber.Ctx) error {
      return c.Status(200).SendString("日志记录!!!")
   })

   app.Listen(":3000")
}

上面的示例,执行后,我完全没看到有requestid

所以继续往下试一试下入文件看看:

package main

import (
   "log"
   "os"

   "github.com/gofiber/fiber/v2"
   "github.com/gofiber/fiber/v2/middleware/logger"
   "github.com/gofiber/fiber/v2/middleware/requestid"
)

func main() {
   app := fiber.New()
   app.Use(requestid.New())
   file, err := os.OpenFile("./req.log", os.O_RDWR|os.O_CREATE|os.O_APPEND, 0666)
   if err != nil {
      log.Fatalf("error opening file: %v", err)
   }
   defer file.Close()
   app.Use(logger.New(logger.Config{
      Format:     "${pid} ${locals:requestid} ${status} - ${method} ${path}\n",
      TimeFormat: "02-Jan-2006",
      TimeZone:   "Asia/Shanghai",
      Output:     file,
   }))
   //仅仅是设置响应码信息
   app.Get("/ok1", func(c *fiber.Ctx) error {
      return c.Status(200).SendString("日志记录!!!")
   })

   app.Listen(":3000")
}

查看我们的日志文件/req.log:

15784 9e7bd80c-4198-4d3e-800a-7fef42810a55 200 - GET /ok1
15784 9f7bd80c-4198-4d3e-800a-7fef42810a55 200 - GET /ok1
15784 a07bd80c-4198-4d3e-800a-7fef42810a55 200 - GET /ok1

嗯嗯,算是看了!!!!

6.6 全局异常中间件

  • 默认的配置信息:
var ConfigDefault = Config{
   Next:              nil,
   EnableStackTrace:  false,
   StackTraceHandler: defaultStackTraceHandler,
}

示例:

package main

import (
   "github.com/gofiber/fiber/v2/middleware/recover"

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

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

   //仅仅是设置响应码信息
   app.Get("/ok1", func(c *fiber.Ctx) error {
      return c.Status(200).SendString("日志记录!!!")
   })

   app.Use(recover.New())
   // This panic will be catch by the middleware
   app.Get("/", func(c *fiber.Ctx) error {
      panic("I'm an error")
   })
   app.Listen(":3000")
}

执行结果:

image.png

这里有疑问,我想捕获我自己定义现实怎么显示呢?一个自定义的示例:

package main

import (
   "github.com/gofiber/fiber/v2"
   "github.com/gofiber/fiber/v2/middleware/recover"
)

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

   //仅仅是设置响应码信息
   app.Get("/ok1", func(c *fiber.Ctx) error {
      return c.Status(200).SendString("日志记录!!!")
   })

   // 设置全局错误
   app.Use(recover.New(recover.Config{
      //Next: func(c *fiber.Ctx) bool {
      // return c.Query("refresh") == "true"
      //},
      EnableStackTrace: true,
      StackTraceHandler: func(e interface{}) {
         //堆栈信息

      },
   }))
   // This panic will be catch by the middleware
   app.Get("/", func(c *fiber.Ctx) error {
      panic("I'm an error")
   })
   app.Listen(":3000")
}

好像还是有点迷糊?如果全局的处理错误呐?会看我们的fiber.New配置项中其实有一个ErrorHandler,我们只需要在这个地方定义我们的自己的处理器就可以了!!!

自定义全局异常处理函数:

package main

import (
   "fmt"

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

func main() {
   app := fiber.New(fiber.Config{
      // Override default error handler
      ErrorHandler: func(ctx *fiber.Ctx, err error) error {
         //获取当前的异常响应码
         code := fiber.StatusInternalServerError
         // 如果有自定义的响应码的话,那么就返回自己的自定义的响应码信息
         if e, ok := err.(*fiber.Error); ok {
            code = e.Code
         }
         fmt.Println(code)
         fmt.Println(err)
         // 发送自定义的异常错误的响应页面
         //err = ctx.Status(code).SendFile(fmt.Sprintf("./%d.html", code))
         err = ctx.JSON(fiber.Map{
            "code": code,
            "msg":  err.Error(),
         })
         // 如果找不到这个页面,那么就直接其他其他的异常
         if err != nil {
            // In case the SendFile fails
            return ctx.Status(fiber.StatusInternalServerError).SendString("程序员哥哥睡眠不足,系统崩溃了!")
         }
         // 返回这个函数
         return nil
      },
   })

   //仅仅是设置响应码信息
   app.Get("/ok1", func(c *fiber.Ctx) error {
      return c.Status(200).SendString("日志记录!!!")
   })

   // 设置全局错误
   app.Use(recover.New(recover.Config{
      //Next: func(c *fiber.Ctx) bool {
      // return c.Query("refresh") == "true"
      //},
      EnableStackTrace: true,
      StackTraceHandler: func(e interface{}) {
         //堆栈信息

      },
   }))
   // This panic will be catch by the middleware
   app.Get("/", func(c *fiber.Ctx) error {
      panic("我是故意的!")
   })
   app.Listen(":3000")
}

然后访问验证:

image.png

6.7 全局异常中间件-提取出全局错误处理问题

app := fiber.New(fiber.Config{
   ErrorHandler: func(c *fiber.Ctx, err error) error {
      return c.Status(fiber.StatusInternalServerError).JSON(fiber.Map{
         "code": 500,
         "msg":  err.Error()},
      )
   },
})

3.总结

以上仅仅是个人结合官网做的一系列的实践总结梳理,如有笔误!欢迎批评指正!感谢各位大佬!

码字不易,如果觉得有点点帮助的话!要不你点个赞呗 哈哈!

码字不易,如果觉得有点点帮助的话!要不你点个赞呗 哈哈!

码字不易,如果觉得有点点帮助的话!要不你点个赞呗 哈哈!

结尾

END

简书:https://www.jianshu.com/u/d6960089b087

掘金:https://juejin.cn/user/2963939079225608

公众号:微信搜【小儿来一壶枸杞酒泡茶】

小钟同学 | 文 【欢迎一起学习交流】| QQ:308711822


文章转载自小儿来一壶枸杞酒泡茶,如果涉嫌侵权,请发送邮件至:contact@modb.pro进行举报,并提供相关证据,一经查实,墨天轮将立刻删除相关内容。

评论