因纯随机易碰撞,10万数据冲突率超0.3%,且无法保证首次插入成功;应改用crypto/rand+base62编码,配合数据库唯一索引与最多3次重试。

短网址生成时为什么不能直接用 rand.Intn() 生成6位随机字符串?
因为纯随机容易碰撞,尤其在高并发或数据量过万后,rand.Intn() 配合简单字符集(如 a-z0-9)生成的6位码冲突概率远超预期。实测 10 万条数据下冲突率可达 0.3%+,且无法保证“首次插入即成功”。
更稳妥的做法是:用 crypto/rand 生成高强度字节,再 Base62 编码(避免 0/O/l/I 等易混淆字符),并配合数据库唯一索引 + 重试逻辑:
- 建表时对短码字段加
UNIQUE约束 - 生成后先
INSERT,若报ERROR: duplicate key value violates unique constraint,则重新生成再试,最多 3 次 - Base62 编码建议手写(避免引入大而重的第三方包),核心逻辑是循环取模 62 查表
Gin 中如何统一拦截并校验短码格式?
别在每个 GET /:code 路由里重复写正则判断,用 Gin 的 gin.HandlerFunc 中间件最干净:
func validateShortCode() gin.HandlerFunc {
return func(c *gin.Context) {
code := c.Param("code")
if len(code) != 6 || !regexp.MustCompile(`^[a-zA-Z0-9]{6}$`).MatchString(code) {
c.JSON(400, gin.H{"error": "invalid short code format"})
c.Abort()
return
}
c.Next()
}
}
// 使用:r.GET("/:code", validateShortCode(), redirectToOrigin)
注意:正则必须严格限定长度和字符集,否则可能被绕过(比如传入 ../../../etc/passwd 之类路径遍历尝试);中间件里调用 c.Abort() 后务必 return,否则后续 handler 仍会执行。
跳转时为什么必须用 http.Redirect 而不是 c.JSON 返回原始 URL?
短网址服务的核心语义是「跳转」,不是「返回地址」。如果用 c.JSON 返回 URL 字符串,前端需额外 JS 跳转,既暴露原始链接,又破坏 SEO 和微信等平台的预加载机制。
正确做法是用 http.Redirect 发送 302(或 301,视是否允许缓存而定)响应:
- 302 更安全:浏览器不缓存跳转,便于后期修改目标地址
- 必须显式设置
c.Header("Location", originURL)再调用c.Status(302),不能只靠http.Redirect函数——Gin 的c.Redirect内部虽也调它,但会强制写入 body,对某些 CDN 或反向代理不友好 - 跳转前务必校验
originURL是否为合法 HTTP/HTTPS 协议开头,防止 Open Redirect 漏洞(如javascript:alert(1)或//evil.com)
为什么 Redis 不适合单独作为短码存储主库?
Redis 速度快,但无法替代 PostgreSQL/MySQL 做主存储:短码需持久化、需关联创建时间/访问统计/所属用户等结构化字段,且要支持复杂查询(如“某用户所有短链”“最近一小时点击 Top10”)。
合理分工是:
- 主库用 PostgreSQL 存
short_codes表(含id,code,origin_url,created_at,click_count等) - Redis 仅作缓存层:key 为短码,value 为 origin_url + click_count(用
INCR原子计数),TTL 设为 1 小时 - 读流程:先查 Redis → 命中则返回并
INCR;未命中则查 DB → 写入 Redis 并设 TTL
漏掉 TTL 或缓存击穿处理,会导致 DB 在热点短码失效瞬间被打垮。
golang免费学习笔记(深入):立即使用
在学习笔记中,你将探索golang的核心概念和高级技巧!











