今天我在用app调试写好的后端接口时,遇到了下面的报错:

[GIN-debug] redirecting request 307: /douyin/user/login –> /douyin/user/login?username=wxy&password=123456

我的路由逻辑如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
func NewRouter() *gin.Engine {
r := gin.Default()
v1 := r.Group("/douyin")
{
v2 := v1.Group("/user")
{
v2.POST("/register", http.RegisterHandler)
v2.POST("/login", http.LoginHandler)
v2.GET("/", http.UserInfoHandler)
}
}
return r

}

这个问题在于你注册的路由和实际请求的路由不相匹配。比如你注册的路由为/douyin/user/login,但实际访问的路由为/douyin/user/login/,就会出现这个错误,但是亲测用postman或者apifox不会出现这种报错,而我用app调试确出现了这种错误。

当我增加部分”/“后,问题得到解决。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
func NewRouter() *gin.Engine {
r := gin.Default()
v1 := r.Group("/douyin")
{
v2 := v1.Group("/user")
{
v2.POST("/register/", http.RegisterHandler)
v2.POST("/login/", http.LoginHandler)
v2.GET("/", http.UserInfoHandler)
}
}
return r

}

我这里使用了Group,多多留意你的路由是否少了/或者多了/

然后不久我又遇到了这个报错:

[GIN-debug] redirecting request 301: /douyin/feed/ –> /douyin/feed/?latest_time=1692417925203

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
func NewRouter() *gin.Engine {
r := gin.Default()
v1 := r.Group("/douyin")
{
v2 := v1.Group("/user")
{
v2.POST("/register/", http.RegisterHandler)
v2.POST("/login/", http.LoginHandler)
v2.GET("/", http.UserInfoHandler)
}

v1.GET("/feed/", http.FeedHandler)

v2 = v1.Group("/publish")
{
//v2.POST("/action/", http.PublishHandler)
v2.GET("/list/", http.PublishListHandler)
}

}
return r

}

如果当前路由无法匹配,但存在带(不带)尾斜杠的路径的处理程序,则启用自动重定向。例如,如果/foo/被请求,但是路由只存在于/foo,那么客户端将被重定向到/foo,GET 请求的 HTTP状态码是301,其他所有请求方法的路由是307。

所以究其原因是我新增加的部分路由多了”/“,我将其删掉后,问题得到解决。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
func NewRouter() *gin.Engine {
r := gin.Default()
v1 := r.Group("/douyin")
{
v2 := v1.Group("/user")
{
v2.POST("/register/", http.RegisterHandler)
v2.POST("/login/", http.LoginHandler)
v2.GET("/", http.UserInfoHandler)
}

v1.GET("/feed", http.FeedHandler)

v2 = v1.Group("/publish")
{
//v2.POST("/action/", http.PublishHandler)
v2.GET("/list/", http.PublishListHandler)
}

}
return r

}

这边建议允许跨域请求,能省去一些麻烦。

先介绍使用社区的https://github.com/gin-contrib/cors 库,一行代码解决前后端分离架构下的跨域问题。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
package router

import (
"ByteRhythm/app/gateway/http"
"ByteRhythm/app/gateway/middleware"

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

func NewRouter() *gin.Engine {
r := gin.Default()
r.Use(cors.Default())
r.Use(middleware.JWT())

v1 := r.Group("/douyin")
{
v1.GET("/feed", http.FeedHandler)

v2 := v1.Group("/user")
{
v2.POST("/register/", http.RegisterHandler)
v2.POST("/login/", http.LoginHandler)
v2.GET("/", http.UserInfoHandler)
}

v2 = v1.Group("/publish")
{
v2.POST("/action/", http.PublishHandler)
v2.GET("/list/", http.PublishListHandler)
}

}
return r

}

当然也可以自定义跨域中间件:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
//cors.go
package middleware

import (
"fmt"
"net/http"
"strings"

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

// Cors 跨域配置
func Cors() gin.HandlerFunc {
return func(c *gin.Context) {
method := c.Request.Method // 请求方法
origin := c.Request.Header.Get("Origin") // 请求头部
var headerKeys []string // 声明请求头keys
for k := range c.Request.Header {
headerKeys = append(headerKeys, k)
}
headerStr := strings.Join(headerKeys, ", ")
if headerStr != "" {
headerStr = fmt.Sprintf("access-control-allow-origin, access-control-allow-headers, %s", headerStr)
} else {
headerStr = "access-control-allow-origin, access-control-allow-headers"
}
if origin != "" {
c.Writer.Header().Set("Access-Control-Allow-Origin", "*")
c.Header("Access-Control-Allow-Origin", "*") // 这是允许访问所有域
c.Header("Access-Control-Allow-Methods", "POST, GET, OPTIONS, PUT, DELETE,UPDATE") // 服务器支持的所有跨域请求的方法,为了避免浏览次请求的多次'预检'请求
// header的类型
c.Header("Access-Control-Allow-Headers", "Authorization, Content-Length, X-CSRF-Token, Token,session,X_Requested_With,Accept, Origin, Host, Connection, Accept-Encoding, Accept-Language,DNT, X-CustomHeader, Keep-Alive, User-Agent, X-Requested-With, If-Modified-Since, Cache-Control, Content-Type, Pragma")
// 允许跨域设置 可以返回其他子段
c.Header("Access-Control-Expose-Headers", "Content-Length, Access-Control-Allow-Origin, Access-Control-Allow-Headers,Cache-Control,Content-Language,Content-Type,Expires,Last-Modified,Pragma,FooBar") // 跨域关键设置 让浏览器可以解析
c.Header("Access-Control-Max-Age", "172800") // 缓存请求信息 单位为秒
c.Header("Access-Control-Allow-Credentials", "false") // 跨域请求是否需要带cookie信息 默认设置为true
c.Set("content-type", "application/json") // 设置返回格式是json
}
// 放行所有OPTIONS方法
if method == "OPTIONS" {
c.JSON(http.StatusOK, "Options Request!")
}
// 处理请求
c.Next() // 处理请求
}
}

相应的路由逻辑如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
package router

import (
"ByteRhythm/app/gateway/http"
"ByteRhythm/app/gateway/middleware"

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

func NewRouter() *gin.Engine {
r := gin.Default()
//允许跨域请求
r.Use(middleware.Cors())
v1 := r.Group("/douyin")
{
v2 := v1.Group("/user")
{
v2.POST("/register/", http.RegisterHandler)
v2.POST("/login/", http.LoginHandler)
v2.GET("/", http.UserInfoHandler)
}

v1.GET("/feed", http.FeedHandler)

v2 = v1.Group("/publish")
{
//v2.POST("/action/", http.PublishHandler)
v2.GET("/list/", http.PublishListHandler)
}

}
return r
}