4.1 分享链接:短链与提取码

场景:一串长链接引发的尴尬

最早分享长这样:把签名 URL 直接贴微信。链接长到微信里显示三行,预览没有标题也没有文件名,对方还以为是诈骗。更要命的是签名 URL 有时效,过期了链接还在聊天记录里,点开 403,双方都尴尬。

分享功能是网盘从工具变成产品的关键一步。它要解决三个问题:链接短到能看、过期能自管理、身份能验住。我们把分享从下载链路里彻底拆出来,成为独立的小系统。

方案:短链怎么生

短链的核心是长链到短码的映射。生码方式我们比较过两种:

方案 实现 冲突处理 我们的选择
哈希截取 md5 取前 6 位 冲突重试 备用
发号器 自增 id 转 62 进制 无冲突 采用

发号器方案天然无冲突,6 位可表达 568 亿个链接,团队工具用一百年也够了。短码生得短还有一个额外好处:好看,用户愿意贴。

提取码的设计也有取舍:

方案 体验 安全 我们的选择
无提取码 极简 链接即权限 私密分享用
四位数字 输入快 弱,可爆破 公开分享
六位含字母 尚可 强 高价值文件

默认四位数字,可按需升级。还有个讨巧的细节:提取码输入错误三次锁定该链接一段时间,防爆破又不影响正常人。

配置:短链与分享记录

短码生成与跳转逻辑很薄:

// 发号器取号,转 62 进制生成短码
seq := counter.Next(ctx)
code := toBase62(seq) // 6 位短码

// 跳转时查映射,校验有效期与提取码
share := store.Get(code)
if share.Expired() || share.Code != inputCode {
    return ErrInvalidShare
}

映射表按月分表,过期链接自动归档。分享的有效期用户可选:一天、七天、永久。永久不等于不管,我们永久分享默认仍然走审核与屏蔽状态校验,只是不过期。

踩坑:分享功能的四个坑

第一个坑,短链被缓存到死。短链跳转做了 301 重定向后被浏览器永久缓存,取消分享时对方还能打开。必须用 302 而非 301,并在响应头声明短链不缓存。

第二个坑,提取码爆破。没做限流前有人拿脚本猜四位数,一秒几千次。加了每链接错误三次锁定与每 IP 限频后世界清静了。安全功能上线后要持续观察攻击模式,规则是被攻击者教出来的。

第三个坑,分享页面没有内容预览。光给个文件名点击率低得可怜,加了文件类型图标、大小、缩略图后,打开率翻了一倍。分享页是给别人看的,要按落地页标准做。

第四个坑,取消分享不等于文件安全。某用户取消分享后,已下载过的人自然仍有副本,聊天记录里的旧签名 URL 也可能没过期。取消分享只是切断新访问,敏感内容要配合缩短签名有效期一起用。

发号器我们最初用数据库自增列,每次取号一行写操作,并发高时锁得明显。后来换成 Redis 的 incr 指令取号,每隔一万号回写一次数据库做持久化,重启不丢号。这个改动让生码接口的延迟从十几毫秒降到一毫秒以内。

62 进制转换本身几行代码,但字符表有讲究。0 和 O、1 和 l 这种形近字符必须从表里剔除,不然用户肉眼看错码、客服跟你扯皮。我们的字符表是 2 到 9 加上大小写字母去掉歧义字符,6 位空间依然够用到天荒地老。

短链跳转服务单独部署,跟主业务隔离。跳转逻辑极轻,QPS 扛到几千无压力,但它的可用性要求反而最高——主站挂了用户能等,短链打不开对方只会觉得你跑路了。我们给跳转服务配了多实例与独立告警,等级比主站还高半级。

短码到长链的映射放了双层缓存:本地 LRU 兜热点,Redis 兜全量。热门链接的跳转根本打不到数据库,数据库只负责冷启动。缓存里存的是分享元数据而非最终签名 URL,避免签名过期把缓存带废。

分享记录表字段精简到极致:短码、文件 id、创建人、提取码哈希、过期时间、状态。提取码绝不存明文,用 bcrypt 哈希后入库,即使库被拖也爆破不出四位数字。校验时用户输入直接比对哈希,代价只是几毫秒。

过期链接不立即删除,先标记后归档。归档保留了追查能力:有次争议需要举证某文件曾被分享过,归档表救了我们。数据可以老,但不能丢,这是做后台的基本自觉。

短链域名本身是资产。最早图省事用了主域名子域,后来发现短链被聊天软件拦截时,主域名跟着背锅。给短链单独申请一个短域名,既好看又隔离风险,被拦了也不影响主站。

短链被 IM 拦截是很现实的问题。新域名没备案或被举报几次,链接在微信里就只剩一行文字。我们的应对是做好域名健康度观察,同时给短链配上分享页摘要与标题,让平台爬虫能识别这是正经页面。

批量分享是企业场景的刚需。运营要一次发五十个文件给合作方,一个一个建分享能把人逼疯。后来加了批量接口,一次请求建一组链接,统一提取码统一有效期。小功能,但上线那天运营给开发买了奶茶。

分享页的数据要回给创建者。谁点开过、什么时候点开、是否下载,这些信息让分享者心里有底。有同事靠这个发现甲方根本没看他发的资料,然后在例会上掌握了主动权,这件事在团队里传为佳话。

短链服务上线后经历过一次域名更换。新域名与旧域名并行了三个月,旧链接逐步引导到新域名。直接切域名等于让所有历史链接当场死亡,这种断崖式变更在分享系统里是大忌。

提取码是双刃剑。四位数字用户喜欢但强度有限,我们做了分级:私密文件默认六位,公开资料默认四位,可自定义。规则在设置入口明确告知,用户自己选安全等级比替用户做决定更少抱怨。

分享记录里我们加了审计字段:创建、打开、下载、撤销各记时间。某次内部追溯谁泄露了未公开资料,就是靠审计字段锁定的传播路径。功能上线时多写两行日志,出事时少加两天班。

短链与权限系统的关系要厘清。短链只是入口,真正放行与否由权限层决定。我们把短链服务做得纯转发,所有鉴权逻辑下沉到统一网关,职责单一后,排错链路清晰很多。

分享链接的打开速度直接影响信任感。分享页首屏目标是 800 毫秒内渲染完,为此把页面做成了服务端直出加骨架屏,缩略图走 CDN 边缘。速度是隐性功能,慢半秒用户就觉得这链接不靠谱。

短链支持自定义后缀。品牌感强的场合用户想用自家名字做短码,比如公司活动都用活动名做后缀。自定义码与发号器码共用一张表,只是分配段不同,实现成本极低,用户感知却是高级功能。

分享链接还支持访问密码字段给团队用。团队空间分享默认要求登录,外部协作再用提取码。这两种模式共用一套分享表,区分只在身份字段。产品做久了会发现,权限模型越早统一,后面越省事。

短链跳转的埋点与 4.4 的统计系统打通。每次跳转除了鉴权还顺手记一份事件,这样分享打开量天然有了。埋点与业务代码解耦放在统一中间件里,想加新指标只改中间件,业务层零侵入。

分享功能的产品细节值得反复打磨。有效期剩余一天时系统自动提醒创建者,避免链接过期对方还没下完的尴尬。创建者可以一键续期,对方无感知。这功能代码量微小,用户口碑却是实打实的。

短链系统的容量规划顺带分享一个数字:我们的日新增分享约两千条,五年累计不到四百万条记录,MySQL 单表轻量运行。中小型工具的数据量远没到需要炫技的程度,别被中间件焦虑带偏而过度设计。

回看这一节,分享系统真正难的不是短码算法,而是把权限、过期、审计、体验揉成一股绳。我们的分享模块改了三版才稳定,第一版连提取码都没有,第二版止步于能跳转,第三版才有完整生命周期管理。

给准备做分享功能的同行一句忠告:上线第一天就配好监控与审计,别等事故教你怎么做。分享是网盘里唯一主动把内容送出门的功能,它值得你用最高的规格对待。

分享模块上线后,产品同学提了个口号:让每个链接都有交代。现在想来,所谓交代就是过期有提醒、撤销有反馈、访问有记录。技术实现的每一行日志,最后都化作用户对产品的信任。

写到这里我翻了下线上数据:日均分享打开量稳定在两万出头,短链服务的可用性达到四个九。这是团队两年打磨的结果,而这本笔记里写的每一条坑,都来自真实的事故单。

短链系统的演进还没停。最近在做的需求是分享链接带权限水印与访问地域限制,企业客户的合规要求总在推着我们往前走。技术人的成长路径,往往就是被需求推着走的路径。

提取码的输入体验我们打磨过两轮。第一版是方框输四位,用户反馈在手机上要点四下。第二版做成一个长输入框自动填位,一次点击就能输完。体验差异在转化率上立刻显现:完成率涨了一成。

短码生成还做了防预测设计。纯自增序列被遍历就能枚举所有分享,我们在发号器结果上叠加了一层随机扰动,让相邻短码看不出关联。安全与美观可以兼得,多想一步的事。

分享系统的最终形态是一个小型权限网关:每个短码是一把钥匙,每个链接是一份授权,每次跳转是一次核验。想通这一点后,分享模块的重构方向就清晰了。

小结

分享系统的本质是一个带权限语义的短链服务。发号器生码简单可靠,提取码加限流守住入口,分享页按落地页标准打磨。这套东西上线后,我们的分享链接终于能体面地出现在任何对话框里。

延伸阅读