Skip to content

第09章:重定向与路由跳转 ​

本章目标 ​

学完本章后,你将能够:

  1. 使用 c.Redirect() 发送 HTTP 重定向
  2. 使用 r.HandleContext() 实现内部路由跳转
  3. 区分 301 和 302 的使用场景
  4. 正确实现登录后跳转等常见功能

前置知识 ​

  • 需要先学习:第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-page

9.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 后不要再写响应

练习题 ​

  1. 写一个 301 重定向,将 /old 永久重定向到 /new。
  2. 用 HandleContext 实现一个短链接服务:/s/abc 内部跳转到 /articles/123。
  3. 实现登录后回跳:访问 /dashboard 时没登录,302 跳转到 /login?redirect=/dashboard。
  4. (思考题)为什么 HTML 表单的 POST-Redirect-GET 模式要用 302 重定向?直接用 200 返回不行吗?

把零散知识,整理成可以反复走的路。