第09章:重定向与路由跳转
本章目标
学完本章后,你将能够:
- 使用
c.Redirect()发送 HTTP 重定向 - 使用
r.HandleContext()实现内部路由跳转 - 区分 301 和 302 的使用场景
- 正确实现登录后跳转等常见功能
前置知识
- 需要先学习:第03章(路由基础)、第02章(HTTP 状态码)
9.1 HTTP 重定向:c.Redirect()
重定向告诉浏览器:"去访问另一个 URL":
go
r.GET("/old-page", func(c *gin.Context) {
c.Redirect(http.StatusMovedPermanently, "/new-page")
})
r.GET("/new-page", func(c *gin.Context) {
c.String(200, "这是新页面")
})bash
curl -v http://localhost:8080/old-page
# < HTTP/1.1 301 Moved Permanently
# < Location: /new-page
# 浏览器看到 301,自动跳转到 /new-page9.2 301 vs 302
| 状态码 | 含义 | 浏览器行为 | 使用场景 |
|---|---|---|---|
| 301 | 永久重定向 | 浏览器缓存跳转,下次直接访问新地址 | 网站搬家、域名迁移 |
| 302 | 临时重定向 | 每次都问原地址 | 登录后跳回原页面 |
| 307 | 临时重定向(保持方法) | POST 请求跳转后还发 POST | 需要保持 HTTP 方法 |
go
// 301:永久迁移
r.GET("/blog", func(c *gin.Context) {
c.Redirect(http.StatusMovedPermanently, "/articles")
})
// 302:临时跳转(登录后跳回)
r.GET("/login", func(c *gin.Context) {
// 登录成功后跳回目标页面
c.Redirect(http.StatusFound, "/dashboard")
})
// 307:保持方法的临时重定向
r.POST("/old-api", func(c *gin.Context) {
c.Redirect(http.StatusTemporaryRedirect, "/new-api")
})9.3 内部路由跳转:r.HandleContext()
c.Redirect() 会让浏览器发第二次请求。如果你希望在服务端内部跳转,浏览器不知道:
go
r.GET("/short", func(c *gin.Context) {
// 修改请求路径
c.Request.URL.Path = "/long/detailed/path"
// 让 Gin 重新路由
r.HandleContext(c)
})
r.GET("/long/detailed/path", func(c *gin.Context) {
c.String(200, "这是长路径的处理结果")
})bash
curl http://localhost:8080/short
# "这是长路径的处理结果"
# 浏览器看到的 URL 仍然是 /short,但实际执行的是 /long/detailed/path 的处理逻辑Redirect vs HandleContext
| 特性 | c.Redirect() | r.HandleContext() |
|---|---|---|
| 浏览器 URL 变化 | ✅ 变为新地址 | ❌ 保持原地址 |
| 请求次数 | 2 次(原有 + 重定向后) | 1 次 |
| SEO 影响 | 301 会传递权重 | 无影响 |
| 性能 | 多一次请求 | 无额外请求 |
9.4 实战:登录后回跳
go
package main
import (
"net/http"
"github.com/gin-gonic/gin"
)
func main() {
r := gin.Default()
// 首页(需要登录)
r.GET("/", func(c *gin.Context) {
// 检查是否登录(简化示例)
token := c.Query("token")
if token == "" {
// 未登录 → 302 跳转到登录页,带上回跳地址
redirectURL := "/login?redirect=" + c.Request.URL.Path
c.Redirect(http.StatusFound, redirectURL)
return
}
c.String(200, "欢迎回来!")
})
// 登录页
r.GET("/login", func(c *gin.Context) {
redirectTo := c.DefaultQuery("redirect", "/")
c.String(200, "登录页面(成功后跳转到: %s)", redirectTo)
})
// 仪表盘(需要登录)
r.GET("/dashboard", func(c *gin.Context) {
token := c.Query("token")
if token == "" {
c.Redirect(http.StatusFound, "/login?redirect=/dashboard")
return
}
c.String(200, "仪表盘数据...")
})
r.Run(":8080")
}常见错误
错误1:Redirect 后还写响应
go
// ❌ Redirect 后又 c.JSON,触发警告
r.GET("/old", func(c *gin.Context) {
c.Redirect(301, "/new")
c.JSON(200, gin.H{"msg": "ok"}) // 多余的响应!
})
// Gin 会输出警告:[GIN] Headers were already written.错误2:该用 301 用了 302
go
// 网站域名永久迁移 → 用 301
c.Redirect(http.StatusMovedPermanently, "https://new-domain.com")
// 登录后临时跳转 → 用 302
c.Redirect(http.StatusFound, "/dashboard")错误3:重定向到外部网站忘记加协议
go
// ❌ 缺少协议头
c.Redirect(301, "www.example.com")
// 浏览器会以为是相对路径,跳转到 http://你的域名/www.example.com
// ✅ 完整的 URL
c.Redirect(301, "https://www.example.com")本章小结
c.Redirect(code, url)发送 HTTP 重定向- 301 永久重定向、302 临时重定向、307 保持方法
r.HandleContext(c)服务端内部跳转(浏览器无感)- 登录后回跳是 302 的经典应用
- Redirect 后不要再写响应
练习题
- 写一个 301 重定向,将
/old永久重定向到/new。 - 用
HandleContext实现一个短链接服务:/s/abc内部跳转到/articles/123。 - 实现登录后回跳:访问
/dashboard时没登录,302 跳转到/login?redirect=/dashboard。 - (思考题)为什么 HTML 表单的 POST-Redirect-GET 模式要用 302 重定向?直接用 200 返回不行吗?