生产环境推荐gin或echo,避开beego(路由耦合重、中间件不透明)和fiber(v2升级后中间件顺序变更致鉴权失效);gin的Abort()/Next()语义清晰,echo的Stream()/FileUpload()更直白。

Go 后端用什么框架不踩坑
选框架不是比谁功能多,而是看它是否默认帮你避开常见陷阱。生产环境推荐 gin 或 echo,别碰 beego(路由耦合重、中间件行为不透明)和刚起步的轻量框架(比如 fiber 在 v2 升级后中间件顺序逻辑变了,不少老项目升级后鉴权失效)。
关键判断点:gin 的 router.Use() 是全局中间件,router.Group().Use() 才是局部;而 echo 的 echo.Group().Use() 和 echo.Use() 行为一致,但它的 c.Bind() 默认不校验结构体 tag,容易漏掉 json:"xxx,required" 这类声明。
- 如果团队熟悉 HTTP 生命周期,选
gin—— 它的Abort()和Next()语义清晰,调试时打印中间件执行顺序一目了然 - 如果接口大量涉及文件上传或流式响应,
echo的c.Stream()和c.FileUpload()接口更直白,不用手动调multipart.Reader - 别在
main.go里写业务逻辑,把路由注册抽成func(r *gin.Engine)函数,方便单元测试时 mock router
前后端通信怎么设 CORS 才安全
CORS 不是加个中间件就完事。浏览器会先发 OPTIONS 预检请求,而 Go 框架默认不处理,导致前端 fetch 报 404 或静默失败。
gin 常用的 cors.Default() 允许所有域名 + 所有 header + 支持 cookie,这在开发环境没问题,上线立刻被扫出漏洞。真实项目必须显式控制:
立即学习“go语言免费学习笔记(深入)”;
- Origin 只允许前端部署域名,比如
https://app.example.com,禁用*(否则带credentials: true会直接被浏览器拒绝) - ExposedHeaders 至少加上
X-Total-Count(分页用)、X-Request-ID(链路追踪用),不然前端拿不到 - 如果前端用
fetch({ credentials: 'include' }),后端必须设AllowCredentials: true,且此时AllowOrigins不能为*
示例配置(gin):
config := cors.Config{<br> AllowOrigins: []string{"https://app.example.com"},<br> AllowMethods: []string{"GET", "POST", "PUT", "DELETE", "OPTIONS"},<br> AllowHeaders: []string{"Content-Type", "Authorization", "X-Requested-With"},<br> ExposeHeaders: []string{"X-Total-Count", "X-Request-ID"},<br> AllowCredentials: true,<br>}<br>r.Use(cors.New(config))
Go 配置库,使用 spf13/viper — 分层优先级(flag > env >file > KV > default),提供 BindPFlag/BindPFlags、SetEnvPrefix + SetEnvKeyReplace 等功能。
前端静态资源怎么托管不暴露源码
别用 http.FileServer 直接挂整个 dist/ 目录——它默认开启目录遍历,访问 /static/..%2f..%2f..%2f/etc/passwd 就能读服务器文件。
正确做法是用 http.Dir + http.StripPrefix + 显式禁止路径穿越:
- 确保构建产物放在独立目录(如
./web/dist),不要和 Go 源码混放 - 用
fs := http.Dir("./web/dist"),再 wrap 一层检查路径:if strings.Contains(r.URL.Path, "..") { http.Error(w, "Forbidden", http.StatusForbidden); return } - 如果前端用了 Vue Router / React Router 的 history 模式,404 时要 fallback 到
index.html,但注意:只对非 API 路径 fallback,否则/api/users404 也会返回 HTML
简单 fallback 示例(gin):
r.NoRoute(func(c *gin.Context) {<br> if strings.HasPrefix(c.Request.URL.Path, "/api/") {<br> c.AbortWithStatusJSON(404, gin.H{"error": "not found"})<br> return<br> }<br> c.File("./web/dist/index.html")<br>})
JWT token 怎么存才不会被 XSS 窃取
把 token 存 localStorage 是最常见错误。XSS 一旦触发,localStorage.getItem('token') 立刻被拿走。HttpOnly Cookie 是唯一靠谱方案,但 Go 后端设 Cookie 时容易忽略几个细节:
- SetCookie 的
Secure字段必须为true(仅 HTTPS 传输),本地开发用localhost时浏览器也认Secure,所以开发环境要加判断 -
SameSite设成Lax(默认值)不够,跨站表单提交时可能丢失 Cookie;设Strict又会导致用户从邮件链接进站时没登录态;推荐SameSite: http.SameSiteNoneMode+ 强制Secure: true - 不要用
time.Now().Add(24*time.Hour)算过期时间,要用time.Now().UTC().Add(24*time.Hour),否则时区错乱导致 token 提前失效
示例(gin):
http.SetCookie(c.Writer, &http.Cookie{<br> Name: "token",<br> Value: jwtString,<br> Path: "/",<br> Domain: ".example.com", // 注意带点前缀才能跨子域<br> Expires: time.Now().UTC().Add(24 * time.Hour),<br> HttpOnly: true,<br> Secure: c.Request.TLS != nil || c.Request.Header.Get("X-Forwarded-Proto") == "https",<br> SameSite: http.SameSiteNoneMode,<br>})
真正麻烦的是前端配合:fetch 必须带 credentials: 'include',且 API 域名必须和 Cookie Domain 匹配(比如 Cookie 设了 .example.com,前端请求就得发到 api.example.com,不能是 localhost:8080)

















