短链接应优先用自增ID转62进制而非哈希,因哈希易冲突、长度不可控、无序;需统一处理URL尾部斜杠;须在HTTP中间件限流防刷;跳转一律用302而非301以保可控性。

短链接生成核心:用哈希还是自增ID
短链接服务最关键的决策不是选什么框架,而是ID生成策略。哈希(比如 base64 编码 MD5)看着酷,但实际踩坑多:重复冲突要重试、无法预估长度、不支持按时间排序;而自增ID(配合 strconv.FormatInt(n, 62))更稳,只要数据库主键是递增的,就能保证唯一、有序、可预测。
常见错误现象:hash collision after 10k links 或生成的短码忽长忽短(比如 aB3 和 xyz789Q 并存),说明用了不可控哈希+截断逻辑。
- 生产环境优先用数据库自增 ID + 62 进制转换(字符集:
"0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ") - 避免用
time.Now().UnixNano()直接转短码——并发下易重复,且无序 - 如果必须用哈希,至少加 salt + 原始 ID 拼接再哈希,再 fallback 重试,别裸跑
md5([]byte(url))
Go HTTP 路由怎么处理 /aBc 和 /aBc/ 不区分问题
用户访问 /abc 和 /abc/ 都该跳转到同一目标,但 Go 的 http.ServeMux 默认不自动去尾斜杠,gin 和 echo 默认行为也不一致——这是上线后 404 最常见的来源之一。
使用场景:用户从微信、短信点开链接,客户端可能自动补斜杠;或前端拼 URL 时多写一个 /。
立即学习“go语言免费学习笔记(深入)”;
Go 配置库,使用 spf13/viper — 分层优先级(flag > env >file > KV > default),提供 BindPFlag/BindPFlags、SetEnvPrefix + SetEnvKeyReplace 等功能。
- 用
http.StripPrefix+ 手动 trim 后缀斜杠比依赖框架路由更可靠 - 在 handler 开头统一处理:
path := strings.TrimSuffix(r.URL.Path, "/"),再取最后一段做短码解析 - 别在数据库里存带斜杠的短码,入库前就
strings.TrimLeft(path, "/")
如何防止恶意刷短链导致 DB 写爆
没加限制的短链服务上线两小时就可能被爬虫扫出几十万条无效记录,尤其是开放 API 的场景。重点不是“防谁”,而是“限流在哪做”。
性能影响:在数据库层做唯一约束能拦住重复,但拦不住高频插入请求;在 Redis 做 IP 级限流又可能误伤 NAT 后用户。
- 最简方案:HTTP 中间件里用
net.ParseIP(r.RemoteAddr)提取真实 IP(注意 X-Forwarded-For 可伪造),每分钟最多允许 5 次 POST /shorten - 更准的做法:结合
r.Header.Get("User-Agent")+r.URL.Query().Get("source")做轻量指纹,避免只靠 IP - 数据库写入前,先查
SELECT id FROM links WHERE long_url = ? LIMIT 1,命中就直接返回已有短码,省写也防重复
跳转时为什么 Location 头返回 302 而不是 301
301 是永久重定向,浏览器和 CDN 会缓存它。一旦原始链接改了、短码要复用、或者你要灰度切流量,301 就会卡死你——用户本地缓存不走新逻辑,连日志都看不到真实跳转路径。
兼容性影响:某些老旧客户端对 301 缓存策略激进,甚至缓存数天;而 302(或 307)每次都会触发服务端逻辑,便于监控和热修复。
- 一律用
http.Redirect(w, r, target, http.StatusFound)(即 302) - 别在 Nginx 层硬配
return 301到短链跳转地址,那等于放弃控制权 - 如果真需要长期稳定跳转(比如品牌活动页),用独立域名 + 301,但主短链服务本身保持 302
真正麻烦的是统计和调试:302 下你得靠中间件记录 r.URL.Path 和 r.UserAgent,而不是依赖 CDN 日志;另外,短码解析失败时别返回 302,要明确返回 404 或 410,否则搜索引擎会反复抓。

















