第一个用户
发布两周了,零个用户
我花了两个周末写完短链接服务,又花了一个周末部署上线。然后在 V2EX、掘金、少数派上各发了一篇介绍帖。
三天过去,访问量 47。注册用户:0。
一周过去,访问量 112。注册用户:还是 0。
说实话,我有点沮丧。每天打开服务器监控面板,看到的只有我自己测试时留下的请求。那些 /a3f8c2、/b7d9e1 的跳转记录,全是我自己的 IP。
第二周结束时,我认真考虑要不要把这台每月 ¥68 的云服务器退掉。反正也没人用,省点钱不好吗?
那天晚上,我正准备登录云服务商控制台,Gmail 弹出了一封新邮件。
发件人:王志远(小王)
我的心跳快了半拍。
第一封邮件
嗨,我是小王,一个独立开发者。
我刚刚发布了自己的第一个 SaaS 产品——一个在线简历生成工具。需要在社交媒体上推广,但产品链接实在太长了:
https://www.myresumebuilder.com/landing?utm_source=twitter&utm_medium=social&utm_campaign=launch我在 V2EX 上看到你的短链接服务,想试试!请问怎么使用?
另外说一句,你的服务真的很快,页面秒开。
我激动得差点从椅子上跳起来。不是因为它是一封”用户反馈”——而是因为真的有人,一个活生生的人,在互联网的某个角落,用了我写的东西。
我花了五分钟冷静下来,然后认真写了一封回信,附上了完整的 API 调用示例。
第一次真实访问
十分钟后,我的服务器日志开始滚动了。
小王按照我给的文档,发出了他的第一个请求:
验证要点
- 命令只用于验证系统状态,读者不需要记具体参数。
- 请求验证关注返回状态、跳转目标和响应时间。
服务器记录了全过程:
落地思路
- 这里省略具体语法,只保留设计层面的职责边界。
- 读这段时重点看:输入是什么、系统做哪些判断、状态如何变化、失败时如何兜底。
返回结果:
配置要点
- 配置表达的是环境差异和运行参数,不是业务规则本身。
然后我盯着终端等了整整十分钟。终于在 10:33,日志里出现了第一行来自外部用户的访问记录:
落地思路
- 这里省略具体语法,只保留设计层面的职责边界。
- 读这段时重点看:输入是什么、系统做哪些判断、状态如何变化、失败时如何兜底。
302 跳转,成功了。
那一刻我才真正理解了一件事:**服务在自己电脑上跑通是一回事,让别人在真实世界里用上是另一回事。**那种感觉完全不同。有人在 Twitter 上点了 short.url/a3f8c2,然后在浏览器里看到了他自己的产品页面。我的服务,完成了这次连接。
用户反馈:原来不只是缩短链接
一周后,小王发来了一封很长的反馈邮件:
太棒了!短链接服务很好用!
我在 Twitter、Facebook、LinkedIn 都分享了短链接,效果很好。
不过,我有几个建议:
- 点击统计:我想知道我的短链接被点击了多少次——哪个渠道来的多,哪个渠道没人点。这对营销来说太重要了。
- 自定义短链接:我想要一个更好记的,比如
short.url/myresume,可以印在名片上。- 有效期设置:有些推广活动只搞一周,链接能不能自动过期?
- API 文档:最好有更详细的 API 文档和接入说明,我打算集成到我的产品里。
我读完邮件,靠在椅背上想了很久。
原来用户需要的不只是”缩短链接”这么简单。他们要的是可追踪、可管理、可集成的链接服务。缩短只是最基础的功能——就像搜索引擎的搜索框只有一个,但背后有排名、有推荐、有广告。
我打开一个空白文档,列出了新的开发计划。但在此之前,我需要先处理另一个更紧迫的事情。
第一个 Bug
就在我觉得一切顺利的时候,小王发来了一条消息:
你好,我发现了一个问题:
我创建的短链接
https://short.url/d7f3b2在某些浏览器中无法跳转。具体表现:
- Chrome:正常跳转 ✅
- Firefox:正常跳转 ✅
- Safari:无法跳转 ❌
- 微信内置浏览器:无法跳转 ❌
能否帮忙看看?
收到这条消息的时候,我的心凉了半截。我只有一个用户,一个!而且他已经遇到了 Bug。
我马上打开服务器开始排查。
问题定位
首先检查重定向的代码逻辑——看起来没什么问题:
落地思路
- 这里省略具体语法,只保留设计层面的职责边界。
- 读这段时重点看:输入是什么、系统做哪些判断、状态如何变化、失败时如何兜底。
然后我去查不同浏览器的访问日志:
落地思路
- 这里省略具体语法,只保留设计层面的职责边界。
- 读这段时重点看:输入是什么、系统做哪些判断、状态如何变化、失败时如何兜底。
Safari 和微信都返回了 404?但 Chrome 和 Firefox 没问题?
我盯着这几行日志看了半天,突然一个念头闪过。
根因:大小写敏感
落地思路
- 这里省略具体语法,只保留设计层面的职责边界。
- 读这段时重点看:输入是什么、系统做哪些判断、状态如何变化、失败时如何兜底。
原来 Safari 和微信内置浏览器在某些场景下会把 URL 路径转为大写(或保持用户输入的原始大小写)。而我的数据库查询用的是 SQLite,默认是大小写敏感的。WHERE short_code = 'D7F3B2' 当然找不到 d7f3b2。
找到了!就是它。
修复方案
我列了三个方案:
落地思路
- 这里省略具体语法,只保留设计层面的职责边界。
- 读这段时重点看:输入是什么、系统做哪些判断、状态如何变化、失败时如何兜底。
方案 1 和方案 2 都是在应用层做转换,简单但容易遗漏——万一哪个新接口忘了加 .lower() 就又出 Bug。
我选择了方案 3:数据库层面解决,一次性堵死:
数据设计要点
- 核心是在
urls里保存业务事实,而不是把规则散落在应用逻辑里。- 关键字段包括
id、short_code、long_url、created_at,它们决定了后续查询和管理能力。
COLLATE NOCASE 让 SQLite 在比较 short_code 时自动忽略大小写。存储时保留原始大小写,查询时大小写不敏感,索引也能正常使用。一劳永逸。
我改完表结构,重新部署,给小王回了一封邮件:“已修复,请再试试。”
五分钟后他回复:“Safari 和微信都正常了!”
我长舒一口气。
系统改进
Bug 修完了,但这次事故让我意识到:我的系统太简陋了。一个大小写问题就让我手忙脚乱。如果以后用户多了,还不知道会出什么幺蛾子。
我决定在加新功能之前,先把系统架构升级一下。
当前系统的现状
先看看现在的处理链路有多朴素:
落地思路
- 这里省略具体语法,只保留设计层面的职责边界。
- 读这段时重点看:输入是什么、系统做哪些判断、状态如何变化、失败时如何兜底。
没有缓存,没有异步处理,没有统计。每次重定向都直接查数据库。
升级方案:缓存 + 异步日志
我引入了两项关键改进:Redis 缓存和异步日志队列。
落地思路
- 这里省略具体语法,只保留设计层面的职责边界。
- 读这段时重点看:输入是什么、系统做哪些判断、状态如何变化、失败时如何兜底。
为什么是异步日志?因为每次重定向都要写数据库的话,一旦数据库变慢,用户等的就是跳转——那是体感最明显的延迟。把日志扔进队列,后台线程慢慢写,用户完全无感知。
落地思路
- 这里省略具体语法,只保留设计层面的职责边界。
- 读这段时重点看:输入是什么、系统做哪些判断、状态如何变化、失败时如何兜底。
改进后的数据库表结构也做了调整:
数据设计要点
- 核心是在
urls里保存业务事实,而不是把规则散落在应用逻辑里。- 索引服务于高频查询,重点关注
AUTOINCREMENT、idx_short_code、idx_clicked_at。- 关键字段包括
id、short_code、long_url、created_at、clicked_at、ip、user_agent、referer,它们决定了后续查询和管理能力。
用户使用场景观察
上线后我观察了几天,发现用户的使用方式比我预想的丰富得多:
- 社交媒体营销:像小王这样的独立开发者,在 Twitter、LinkedIn 分享产品链接,链接被大量点击,需要稳定性。
- 短信营销:每条短信按字符计费,链接越短越省钱,5-6 个字符的短码是刚需。
- 二维码生成:线下推广场景,短链接生成的二维码更简单,更容易扫描。
- API 集成:有些开发者想把短链接功能嵌入自己的产品,需要 API 和文档。
第一周的数据
一周后,我统计了一下数据:
落地思路
- 这里省略具体语法,只保留设计层面的职责边界。
- 读这段时重点看:输入是什么、系统做哪些判断、状态如何变化、失败时如何兜底。
23 个独立用户,1243 次重定向,0 错误,99.9% 可用性。
成本:¥68/月(云服务器)+ ¥0(Redis 还跑在同一台机器上)。
收入:¥0。
但至少有人在用了。而且他们似乎还愿意继续用下去。