短链接生成应采用自增ID转62进制或随机生成+冲突重试,避免哈希截断;存储推荐Redis并设TTL与空值锁;重定向用302防缓存问题;接口需限流、校验与结构化响应。

短链接生成的核心逻辑必须避开重复和冲突
短链接不是简单把长 URL 做哈希再截断——md5(url)[:6] 看似快,但碰撞概率在万级请求下就明显上升,且无法保证可读性或可控长度。真实场景中,你得用「自增 ID + 字符映射」或「随机生成 + 冲突重试」两种可靠路径。
推荐用自增主键(如数据库 id)转 62 进制(0-9a-zA-Z),这样天然无冲突、可预测、易追溯。例如 ID=123456 → "1eO4",只要 ID 不重复,短码就唯一。注意:别直接暴露自增 ID 本身(防爬序号),但通过进制转换后已足够脱敏。
- 用 PostgreSQL 或 SQLite 的
RETURNING id拿到新插入记录的 ID,避免额外查询 - 62 进制转换函数要预热缓存字符表,别每次
string(rune)动态拼接 - 如果用 Redis 自增(
INCR),务必设置过期时间并配合 Lua 脚本保证原子性,否则分布式下可能重复发号
Go 里怎么安全地存储和查询短码映射
短链接系统本质是「短码 → 长 URL」的高速查表。别一上来就上 MySQL:单机场景下 sync.Map + 定期持久化够用;中等流量用 Redis 更稳;高并发写多读少才考虑分片 MySQL。
关键不是存哪,而是怎么读写不翻车。比如用 Redis 存,SET short:abc123 https://example.com?ref=a 是基础操作,但漏掉两点就出事:没设 TTL、没处理空值穿透。
- 所有
SET必须带EX 3600(或按业务定策略),否则缓存爆炸 - 查不到时,不要直接回源 DB——先
SETNX short:abc123 "NULL"加空值锁,防缓存击穿 - Go 客户端推荐
github.com/redis/go-redis/v9,用ctx控制超时,别让一个慢查询拖垮整个 HTTP handler
HTTP 路由和重定向必须绕开常见陷阱
GET /abc123 重定向到原始 URL,看似一行 http.Redirect(w, r, target, http.StatusFound) 就完事,但线上会踩三个坑:Header 泄露、跳转链过长、Referer 丢失。
尤其注意 http.StatusFound(302)和 http.StatusMovedPermanently(301)的区别:301 会被浏览器强缓存,改链接后用户刷不出来;新系统一律用 302,后期稳定再按需切。
- 手动清除
w.Header().Set("Server", "..."),别暴露 Go 默认 Server 名 - 重定向前检查
target是否以http://或https://开头,防 Open Redirect 漏洞 - 如果需要透传 UTM 参数(如
?utm_source=short),别拼在原始 URL 后——应在生成时就 encode 进短码,否则重定向链里参数会丢
生成接口要限制频率并返回结构化响应
POST /api/v1/shorten 接口不能裸奔。没限流?爬虫几秒打爆你的发号器。没校验?有人传 10MB JSON 直接 OOM。没返回体?前端不知道短链在哪。
最简可用的响应至少包含 code、short_url、long_url 三字段,别学某些教程只返个字符串。
- 用
golang.org/x/time/rate做 per-IP 限流,rate.NewLimiter(10, 20)表示每秒最多 10 次,允许突发 20 次 - 用
json.NewDecoder(r.Body).Decode(&req)读请求体,开头加r.Body = http.MaxBytesReader(w, r.Body, 1024*1024)防大包 - 生成失败时返回标准 HTTP 状态码:
400(URL 格式错)、429(限流)、500(存储异常),别全扔 200
真正难的不是写完这四个模块,而是当短码库里已有 500 万个映射时,你改一个字符集(比如从 62 进制扩到 64 进制),旧链接还能不能跳——这种兼容性设计,往往在第一版代码里就被忽略。


















