自定义设计
那天早上,我像往常一样打开邮箱,准备处理用户的反馈邮件。大多是些”链接打不开”、“访问速度慢”之类的问题,我已经习惯了。
但有一封邮件让我停下了手头的动作。
发件人:Nike Digital Team 主题:关于您的短链接服务
您好,
我们在社交媒体上看到很多用户在使用您的短链接服务,效果很好。
我们有一个商业需求:我们希望获得 short.url/nike 这样的品牌短链接。 我们的产品链接会定期更新,但短链接可以保持不变,这样在广告投放时更加稳定。
关于价格,我们可以接受按月或按年付费。请告知您的合作方式。
期待您的回复。
—— Nike Digital Team
我的心跳突然加速了。
付费? 这是我的短链接服务第一次有人主动提出要付费!
我立刻意识到:这不是一个简单的功能需求,这是一个商业机会。
自定义短链接——这三个字在我脑海中闪烁。用户想要自己定义的短链接后缀,而不是系统生成的随机字符。
这不仅仅是个技术问题,这是个产品设计问题。我需要设计一套完整的自定义别名系统。
我开始思考第一个问题:什么样的自定义别名应该被允许?
如果允许用户输入任意字符,可能会出现安全问题、URL冲突问题、甚至是系统路径混乱的问题。
我拿出一张纸,开始画规则边界:
┌─────────────────────────────────────────────┐
│ 自定义别名验证规则 │
├─────────────────────────────────────────────┤
│ 1. 长度限制:3-30个字符 │
│ 2. 字符集:字母、数字、连字符、下划线 │
│ 3. 保留词:系统保留的敏感词汇 │
│ 4. 重复检查:不能与现有别名冲突 │
│ 5. 敏感词过滤:不当词汇拦截 │
└─────────────────────────────────────────────┘
规则1:长度限制
为什么不能太短?
长度 < 3字符:
- 组合太少(26个字母 × 26个字母 = 676种)
- 很容易冲突
- 不够有意义
- 容易与系统路径混淆
为什么不能太长?
长度 > 30字符:
- 失去短链接的意义
- 用户体验差
- 违背了"短"的初衷
- 可能超过某些系统的URL长度限制
我先把验证规则写清楚:
落地思路
- 长度必须在 3 到 30 个字符之间,落在区间外直接拒绝。
- 少于 3 个字符的组合太少,撞车概率高,也容易和系统路径混淆。
- 超过 30 个字符就失去了短链接的意义,还会撞上部分系统的 URL 长度上限。
规则2:字符集限制
允许哪些字符?
我查阅了RFC 3986(URL规范),发现URL路径段中安全使用的字符包括:
- 字母:a-z, A-Z
- 数字:0-9
- 连字符:-
- 下划线:_
- 点号:.(但我不想使用,因为看起来像文件扩展名)
不允许哪些字符?
× 空格:会被编码成 %20,破坏短链接的简洁性
× 中文/日文等非ASCII字符:需要编码,变长
× 特殊字符:/、?、&、#、@等都有URL特殊含义
× 控制字符:可能破坏系统
× 表情符号:编码后极长
我继续把字符规则写清楚:
落地思路
- 只放行字母、数字、连字符和下划线,其余字符一律拒绝。
- 斜杠、问号、井号等在 URL 里有结构含义,放进来会改变路径的解析结果。
- 空格和中文会被百分号编码,长度膨胀且各平台显示不一致,所以提前拒绝而不是编码后凑合。
- 点号也一并排除,避免看起来像一个文件扩展名。
规则3:保留词检查
这是最棘手的部分。我需要确保用户不能注册与系统路径冲突的别名。
我列出了第一版保留词清单:
落地思路
- 保留词存
reserved_words表,注册时拿别名去比对,命中就拒绝。- 清单里至少覆盖系统自身的路径前缀和保留的短码段,防止用户注册出和内部接口撞车的别名。
- 保留词要做大小写不敏感的比对,否则 Admin 会绕过 admin 这条规则。
- 保留词命中和敏感词命中的错误提示要分开,否则等于告诉用户哪些词是被系统占用的。
规则4:重复检查
即使别名通过了所有验证规则,我还必须确保它没有被其他用户使用。
落地思路
- 重复检查查的是
custom_aliases表,别名唯一性由数据库唯一索引保证。- 先查后插只是给用户更快报错;真正防并发重复的是插入时唯一索引拦下的那一下。
- 查询要走
idx_custom_alias,否则别名数量涨上来后每次注册都要扫全表。- 查不到不代表可注册,最终结论以写入结果为准。
规则5:敏感词过滤
最后一个安全网:防止用户注册不当的别名。
落地思路
- 敏感词过滤放在重复检查之前,先把明显不当的别名挡在写入之前。
- 词库独立于保留词维护,命中即拒绝,不给「换个说法再试」的机会。
- 过滤在应用层做,不依赖数据库——敏感词库里没有索引可依赖。
- 命中的别名要记审计记录,便于事后追溯是谁在什么时候尝试注册了什么。
现在我把所有规则整合成一个完整的验证器:
落地思路
- 验证器按固定顺序逐条判定:长度 → 字符集 → 保留词 → 敏感词 → 重复 → 冲突。
- 任何一条不通过就立刻返回对应原因,不继续往下查,也不在错误信息里暴露后续规则。
- 全部通过后才进入写入,写入失败按数据库返回的冲突原因区分「已被占用」和「服务异常」。
- 验证器是纯函数:输入别名和上下文,输出通过与否加原因,方便单独测试。
我意识到自定义别名和自动生成的短码是两个不同的体系:
自动生成的短码:
- 长度固定(如6字符)
- 字符集固定(Base62)
- 无特殊含义
- 冲突概率极低
自定义别名:
- 长度不固定(3-30字符)
- 字符集宽松但有限制
- 有商业价值
- 冲突概率较高
这意味着我需要设计一个新的表结构,而不是简单地复用现有的urls表。
设计方案1:扩展现有表
数据设计要点
- 两个方案都要能按别名做唯一性查询,所以索引
idx_custom_alias在两种设计里都必须存在。- 差别在唯一约束挂在哪张表上:
urls上一条记录只能有一个别名,custom_aliases则是一行一个别名。
优点:
- 改动最小
- 查询简单(一张表)
缺点:
- 字段冗余(
short_code和custom_alias) - 不够清晰(自定义和自动生成的混在一起)
设计方案2:独立表(我选择这个)
数据设计要点
- 核心是在
custom_aliases里保存业务事实,而不是把规则散落在应用逻辑里。- 写入时要保证短码唯一、字段完整,并为后续查询留下必要状态。
- 更新操作通常意味着链接状态变化,要同步考虑缓存刷新和审计记录。
- 关键字段包括
id、user_id、custom_alias、long_url、created_at、updated_at、word、reason,它们决定了后续查询和管理能力。
优点:
- 职责清晰
- 易于扩展(比如未来要给自定义别名加更多属性)
- 便于审计和历史追踪
缺点:
- 查询时需要JOIN两张表(但这个场景很少)
我选择了方案2。
这是一个真实的问题:如果有两个商家都想注册 nike 这个别名,应该给谁?
我设计了一个”先到先得”的机制,配合事务保证原子性:
落地思路
- 规则是先到先得:别名归先注册成功的那个用户,后来的直接收到「已被占用」。
- 写入放在一个事务里:插入
custom_aliases并绑定user_id,要么都成功,要么都回滚。- 唯一索引是唯一的裁判:并发下两个事务抢同一个别名,数据库只让一个插入成功,另一个拿到冲突错误。
- 冲突属于可预期错误,转成用户能看懂的提示,不记为系统故障。
并发安全
在高并发场景下,两个用户可能同时注册同一个别名。我需要确保只有一个人能成功。
落地思路
- 应用层「先查后插」在并发下会同时看到「不存在」,两个请求都会认为自己能注册成功。
- 真正兜住并发的是唯一索引:插入时才发现的冲突,比插入前查到的结果更可信。
- 所以校验分两层:查一次是为了快速反馈,最终以插入是否成功为准。
- 插入失败时不能留下半条记录——别名和归属必须在同一个事务里提交。
我花了两天时间优化一个细节:当用户输入自定义别名时,实时显示它是否可用。
这需要前端的防抖(debounce)和后端的高效验证配合。
前端实现
落地思路
- 输入框做防抖:停止输入一小段时间后才发请求,而不是每敲一个字符就查一次。
- 请求带上当前别名和所属链接的上下文,后端返回「可用 / 已占用 / 不合法」三种明确状态。
- 已占用的别名在输入框旁直接标注,让用户换一个,而不是提交表单后才被告知。
- 前端的判断只用于提示,最终能不能注册仍以后端写入结果为准。
后端API
落地思路
- 接口是幂等的:同一个别名重复请求可用性检查,返回结果必须一致。
- 检查只读不写,命中重复时直接返回占用状态,不触发任何写操作。
- 这个接口会被公开表单调用,必须限流,否则等于开放了一个别名枚举接口。
- 返回里不要带上别名的归属者信息,只回答「能不能用」。
这个交互细节花了两天时间,但效果很好——用户可以立即知道他们想要的别名是否可用,避免了填写完表单后才发现别名被占用的挫败感。
自定义别名功能上线了。
我满怀期待地打开后台监控,看看有没有人会使用这个新功能。
第一天结束时,我看到了一些数据:
自定义别名注册数量:127个
最热门的别名:
- nike (1次)
- adidas (1次)
- apple (1次)
- samsung (1次)
- test (3次)
- mylink (5次)
- brand123 (2次)
看起来还好。
但当我深入查看时,发现了一个问题:有一个用户注册了50个知名品牌的别名。
用户 ID: 18472
注册的别名:
- nike
- adidas
- puma
- reebok
- newbalance
- converse
- asics
- saucony
- ...
(共50个运动品牌名)
更糟糕的是,这些别名指向的URL都是一些不知名的电商网站,很明显是抢注行为。
我意识到:这需要防抢注机制。
问题:
- 恶意用户可以批量注册大量品牌名
- 真正的品牌所有者反而无法使用自己的品牌名
- 这可能导致法律纠纷
- 破坏了平台的公信力
我需要解决:
- 限制每个用户的注册数量
- 实施品牌名验证机制
- 建立申诉流程
- 考虑引入付费门槛
这些,将是下一节的内容。
完整的别名验证流程
用户输入别名
↓
【规则1】长度检查(3-30字符)
↓
【规则2】字符检查(字母、数字、-、_)
↓
【规则3】保留词检查(系统路径)
↓
【规则4】敏感词检查(不当词汇)
↓
【规则5】重复检查(数据库查询)
↓
【规则6】冲突处理(事务保证)
↓
创建成功 ✅
核心能力拆解
| 组件 | 职责 |
|---|---|
CustomAliasValidator | 完整的别名验证器 |
AliasCharacterValidator | 字符规则验证 |
ReservedWordChecker | 保留词检查 |
ProfanityFilter | 敏感词过滤 |
custom_aliases 表 | 存储自定义别名 |
reserved_words 表 | 存储保留词 |
数据库表结构
数据设计要点
custom_aliases表保存别名本身:归属用户、别名文本、指向的长 URL、创建与更新时间。word和reason记录这类别名为什么被拦下(命中哪条规则),方便申诉时回溯。
问题1:品牌验证
Nike团队真的注册了nike这个别名,还是被抢注者抢占了?
提示
考虑:
- 如何验证用户真的是品牌所有者?
- 是否需要上传商标证书?
- 是否需要企业认证?
- 未认证用户是否允许注册品牌名?
我们将在下一节讨论这个问题。
问题2:批量注册防护
如何防止一个用户在短时间内注册大量别名?
提示
考虑:
- 限制每个用户的注册数量(如10个)
- 限制注册频率(如每小时最多5个)
- 短别名(3-4字符)需要额外审核
- 引入付费门槛(减少恶意注册)
问题3:历史遗留
如果某个别名被抢注了,真正的品牌所有者怎么办?
提示
考虑:
- 是否可以强制转移别名?
- 是否需要建立申诉流程?
- 如何证明所有权?
- 是否需要仲裁机制?
(下一节:抢注防护与品牌验证)
发现内容有问题?
告诉我们哪里需要改进。
