2023-05-15 18:37:40 +08:00
|
|
|
|
## 中间件/拦截器
|
|
|
|
|
|
|
|
|
|
目录
|
|
|
|
|
|
|
|
|
|
- 介绍
|
|
|
|
|
- 全局中间件
|
|
|
|
|
- 鉴权中间件
|
|
|
|
|
- 响应中间件
|
|
|
|
|
- 更多
|
|
|
|
|
|
|
|
|
|
### 介绍
|
|
|
|
|
- 在hotgo中,中间件/拦截器主要作用于web请求的上下文预设、跨域请求处理、鉴权处理、请求拦截和请求结束后统一响应处理等。
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
### 全局中间件
|
|
|
|
|
```go
|
|
|
|
|
package main
|
|
|
|
|
|
|
|
|
|
import (
|
|
|
|
|
"hotgo/internal/service"
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
func main() {
|
|
|
|
|
|
|
|
|
|
// 初始化请求上下文,一般需要第一个进行加载,后续中间件存在依赖关系
|
|
|
|
|
service.Middleware().Ctx()
|
|
|
|
|
|
|
|
|
|
// 跨域中间件,自动处理跨域问题
|
|
|
|
|
service.Middleware().CORS()
|
|
|
|
|
|
|
|
|
|
// IP黑名单中间件,如果请求IP被后台拉黑,所有请求将被拒绝
|
|
|
|
|
service.Middleware().Blacklist()
|
|
|
|
|
|
|
|
|
|
// 演示系統操作限制,当开启演示模式时,所有POST请求将被拒绝
|
|
|
|
|
service.Middleware().DemoLimit()
|
2023-07-20 18:01:10 +08:00
|
|
|
|
|
|
|
|
|
// 请求输入预处理,api使用gf规范路由并且XxxReq结构体实现了validate.Filter接口即可隐式预处理
|
|
|
|
|
service.Middleware().PreFilter()
|
2023-05-15 18:37:40 +08:00
|
|
|
|
|
|
|
|
|
// HTTP响应预处理,在业务处理完成后,对响应结果进行格式化和错误过滤,将处理后的数据发送给请求方
|
|
|
|
|
service.Middleware().ResponseHandler()
|
|
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
```
|
|
|
|
|
### 鉴权中间件
|
|
|
|
|
```go
|
|
|
|
|
package main
|
|
|
|
|
|
|
|
|
|
import (
|
|
|
|
|
"github.com/gogf/gf/v2/frame/g"
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
func main() {
|
|
|
|
|
|
|
|
|
|
// 在鉴权中间件下的路由如果没有通过权限验证,后续请求将被拒绝
|
|
|
|
|
// 在hotgo中,鉴权中间件一般是配合一个业务模块下的路由组进行使用
|
|
|
|
|
// 目前admin、api、home、websocket模块都已接入
|
|
|
|
|
// 如果你需要创建一个新的模块也需要用到鉴权中间件,可以参考:server/internal/logic/middleware/admin_auth.go
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// 一个简单例子
|
|
|
|
|
s := g.Server()
|
|
|
|
|
s.Group("/api", func(group *ghttp.RouterGroup) {
|
|
|
|
|
group.Middleware(service.Middleware().ApiAuth)
|
|
|
|
|
group.Bind(
|
|
|
|
|
member.Member, // 管理员
|
|
|
|
|
)
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
### 响应中间件
|
|
|
|
|
- 文件路径:server/internal/logic/middleware/response.go
|
|
|
|
|
|
|
|
|
|
|
2023-07-03 20:31:29 +08:00
|
|
|
|
#### 常用响应类型
|
|
|
|
|
|
|
|
|
|
- hotgo为一些常用的响应类型做了统一格式封装,例如:`application/json`、`text/xml`、`text/html`、`text/event-stream`等,默认使用`application/json`。
|
|
|
|
|
- 下面我们以`text/xml`为例简单演示几种使用方法:
|
|
|
|
|
|
|
|
|
|
1. 当你使用规范化路由时,可直接在XxxRes结构体的`g.Meta`中声明响应类型:
|
|
|
|
|
```go
|
|
|
|
|
type HelloReq struct {
|
|
|
|
|
g.Meta `path:"/hello" tags:"Hello" method:"get" summary:"You first hello api"`
|
|
|
|
|
Name string `json:"name" d:"hotgo" dc:"名字"`
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
type HelloRes struct {
|
|
|
|
|
g.Meta `mime:"text/xml" type:"string"`
|
|
|
|
|
Tips string `json:"tips"`
|
|
|
|
|
}
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
2. 在响应前设置响应头:
|
|
|
|
|
```go
|
|
|
|
|
var (
|
|
|
|
|
Hello = cHello{}
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
type cHello struct{}
|
|
|
|
|
|
|
|
|
|
func (c *cHello) Hello(ctx context.Context, req *user.HelloReq) (res *user.HelloRes, err error) {
|
|
|
|
|
r := ghttp.RequestFromCtx(ctx)
|
|
|
|
|
r.Response.Header().Set("Content-Type", "text/xml")
|
|
|
|
|
|
|
|
|
|
res = &user.HelloRes{
|
|
|
|
|
Tips: fmt.Sprintf("hello %v, this is the api for %v applications.", req.Name, simple.AppName(ctx)),
|
|
|
|
|
}
|
|
|
|
|
return
|
|
|
|
|
}
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
- 浏览器中访问响应内容如下:
|
2023-07-24 09:35:30 +08:00
|
|
|
|
|
|
|
|
|
![./images/sys-middleware-com-response.png](./images/sys-middleware-com-response.png)
|
2023-07-03 20:31:29 +08:00
|
|
|
|
|
|
|
|
|
|
2023-05-15 18:37:40 +08:00
|
|
|
|
#### 自定义响应
|
2023-07-03 20:31:29 +08:00
|
|
|
|
- 在实际开发中,可能需要使用自定义的响应类型,由于响应中间件是全局的,因此您需要对其进行单独处理。
|
|
|
|
|
- 推荐以下几种处理方案,可做参考:
|
2023-05-15 18:37:40 +08:00
|
|
|
|
1. 使用`ghttp.ExitAll()`,需要注意的是此方法会终止后续所有的http处理
|
|
|
|
|
|
|
|
|
|
```go
|
|
|
|
|
package main
|
|
|
|
|
|
|
|
|
|
import (
|
|
|
|
|
"github.com/gogf/gf/v2/net/ghttp"
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
func main() {
|
|
|
|
|
r := new(ghttp.Request) // 当前请求对象
|
|
|
|
|
|
|
|
|
|
// 清空响应
|
|
|
|
|
r.Response.ClearBuffer()
|
|
|
|
|
|
|
|
|
|
// 写入响应
|
|
|
|
|
r.Response.Write("自定义响应内容")
|
|
|
|
|
|
|
|
|
|
// 终止后续http处理
|
|
|
|
|
r.ExitAll()
|
|
|
|
|
}
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
2. 在`server/internal/logic/middleware/response.go`中根据请求的独有特征进行单独的处理,兼容后续http处理。
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
#### 重写响应错误提示
|
|
|
|
|
|
|
|
|
|
- 在实际开发中,我们可能想要隐藏一些敏感错误,返回给客户端友好的错误提示,但开发者同时又想需要看到真实的敏感错误。对此hotgo已经进行了过滤处理,下面是一个简单的例子:
|
|
|
|
|
|
|
|
|
|
```go
|
|
|
|
|
package main
|
|
|
|
|
|
|
|
|
|
import (
|
|
|
|
|
"github.com/gogf/gf/v2/errors/gerror"
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
func test() error {
|
|
|
|
|
err = gerror.New("这是一个sql执行错误")
|
|
|
|
|
err = gerror.Wrap(err, "用户创建失败,请稍后重试!~")
|
|
|
|
|
return err
|
|
|
|
|
}
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
- 开启debug时的客户端响应:
|
|
|
|
|
```json
|
|
|
|
|
{
|
|
|
|
|
"code": -1,
|
|
|
|
|
"message": "用户创建失败,请稍后重试!~",
|
|
|
|
|
"error": [
|
|
|
|
|
"1. 用户创建失败,请稍后重试!~",
|
|
|
|
|
" 1). hotgo/internal/logic/admin.(*sAdminMember).List",
|
|
|
|
|
" E:/Users/Administrator/Desktop/gosrc/hotgo_dev/server/internal/logic/admin/member.go:526",
|
|
|
|
|
"2. 这是一个sql执行错误", " 1). hotgo/internal/logic/admin.(*sAdminMember).List",
|
|
|
|
|
" E:/Users/Administrator/Desktop/gosrc/hotgo_dev/server/internal/logic/admin/member.go:525",
|
|
|
|
|
" 2). hotgo/internal/controller/admin/admin.(*cMember).List",
|
2023-05-29 11:54:51 +08:00
|
|
|
|
" E:/Users/Administrator/Desktop/gosrc/hotgo_dev/server/internal/controller/admin/admin/member.go:157", ""
|
2023-05-15 18:37:40 +08:00
|
|
|
|
],
|
|
|
|
|
"timestamp": 1684145107,
|
|
|
|
|
"traceID": "084022730d495f17f19e550140f3e1a8"
|
|
|
|
|
}
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
- 关闭debug时的客户端响应:
|
|
|
|
|
```json
|
|
|
|
|
{
|
|
|
|
|
"code": -1,
|
|
|
|
|
"message": "用户创建失败,请稍后重试!~",
|
|
|
|
|
"timestamp": 1684145107,
|
|
|
|
|
"traceID": "084022730d495f17f19e550140f3e1a8"
|
|
|
|
|
}
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
- 控制台的输出日志:
|
|
|
|
|
```shell
|
|
|
|
|
2023-05-15 18:05:07.776 {084022730d495f17f19e550140f3e1a8} 200 "GET http localhost:8000 /admin/member/list?page=1&pageSize=10&roleId=-1 HTTP/1.1" 0.002, 127.0.0.1, "http://192.168.0.207:8001/login", "Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/94.0.4606.71 Safari/537.36 Co
|
|
|
|
|
re/1.94.197.400 QQBrowser/11.7.5287.400", -1, "", ""
|
|
|
|
|
Stack:
|
|
|
|
|
1. 用户创建失败,请稍后重试!~
|
|
|
|
|
1). hotgo/internal/logic/admin.(*sAdminMember).List
|
|
|
|
|
E:/Users/Administrator/Desktop/gosrc/hotgo_dev/server/internal/logic/admin/member.go:526
|
|
|
|
|
2. 这是一个sql执行错误
|
|
|
|
|
1). hotgo/internal/logic/admin.(*sAdminMember).List
|
|
|
|
|
E:/Users/Administrator/Desktop/gosrc/hotgo_dev/server/internal/logic/middleware/response.go:24
|
|
|
|
|
13). hotgo/internal/logic/middleware.(*sMiddleware).DemoLimit
|
|
|
|
|
E:/Users/Administrator/Desktop/gosrc/hotgo_dev/server/internal/logic/middleware/init.go:90
|
|
|
|
|
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
- 如果你开启了访问日志,那么日志记录中会详细记录本次请求的相关信息,内容如下:
|
|
|
|
|
![./images/sys-middleware-error-log.png](./images/sys-middleware-error-log.png)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
#### 重写错误码
|
|
|
|
|
- hotgo默认使用了gf内置的错误码进行业务处理,通常情况下成功状态码为`0`,失败状态码为`-1`
|
|
|
|
|
- 查看gf内置错误码:https://goframe.org/pages/viewpage.action?pageId=30739587
|
|
|
|
|
- 以下是自定义错误码的简单例子:
|
|
|
|
|
|
|
|
|
|
```go
|
|
|
|
|
package main
|
|
|
|
|
|
|
|
|
|
import (
|
|
|
|
|
"github.com/gogf/gf/v2/errors/gerror"
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
func test() error {
|
|
|
|
|
// 使用自定义状态码30001响应客户端
|
|
|
|
|
err = gerror.NewCode(gcode.New(30001, "用户创建失败,请稍后重试!~", nil))
|
|
|
|
|
return err
|
|
|
|
|
}
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
- 客户端响应如下:
|
|
|
|
|
```json
|
|
|
|
|
{
|
|
|
|
|
"code": 30001,
|
|
|
|
|
"message": "用户创建失败,请稍后重试!~",
|
|
|
|
|
"timestamp": 1684146313,
|
|
|
|
|
"traceID": "b4f90e16264a5f17cd3fc27141aba448"
|
|
|
|
|
}
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
### 更多
|
|
|
|
|
- 更多关于中间件/拦截器的介绍请参考:https://goframe.org/pages/viewpage.action?pageId=55289881
|
|
|
|
|
|