🔗 让链接显示成可读文字:平台抓标题 + 企业微信通知的两步优化
在测试管理平台里,我们每天都在和各种链接打交道:需求文档链接、设计稿链接、缺陷详情链接……但很长一段时间里,这些链接在平台里、在企业微信通知里,都是一串又长又难看的裸网址 😵。
更难受的是:把链接复制到协作文档里,粘贴出来的还是那串长网址,而不是一个人类能一眼看懂的文字标题。
这篇就来讲讲,我是怎么分两步把「链接显示成文字」这件事做完整的:第一步解决平台内外部链接的展示,第二步解决企业微信通知里链接的展示。👏
一、问题:链接为什么总是一串网址 🤔
先还原一下改造前的两个痛点场景:
| 场景 | 现象 | 期望 |
|---|---|---|
| 平台内 | 粘贴的 PRD 链接、正文里的 URL 显示成裸网址(截断加省略号) | 显示成网页标题 |
| 企业微信通知 | 缺陷/需求/版本详情链接在通知里显示成裸网址 | 显示成「【缺陷】#123 标题」这样的文字 |
两个场景看起来不一样,但本质是同一个诉求:链接应该显示成人类可读的文字,而不是 URL 本身。
所以我把这个需求拆成了两步来做,下面分别展开。👇
二、场景一:平台内的外部链接 → 抓网页标题 🌐
平台里用户会粘贴各种外部链接:需求 PRD 链接、设计稿链接、正文里混着的 URL。这些链接的「标题」平台自己并不知道,得去目标网页抓。
1. 标题的取值优先级
抓标题不能只靠自动抓取,得给用户留一个手动纠正的口子。最终定的优先级是:
手动填写的标题 > 自动抓取的网页标题 > 网址本身
手动 > 自动 > 网址。手动标题让用户可以纠正自动抓取的结果——抓到的标题不对、太长,或者想换个更短的别名。
2. 后端:抓取服务(关键在 SSRF 防护)
后端新建一张 link_titles 表,按「项目 + URL」唯一约束,同一 URL 在不同项目里可以起不同标题。
抓取服务是整个功能里最需要谨慎的地方——让服务器去请求一个用户可控的地址,是典型的 SSRF(服务端请求伪造) 攻击面:如果用户提交一个内网地址(http://192.168.1.1、http://localhost:6379、云元数据地址 http://169.254.169.254),服务器就会变成探测内网的跳板。🚨
所以抓取必须做完整防护:
async def _is_private_host(host: str) -> bool: |
要点:
- ✅ 只允许 http/https,拒绝 file、gopher 等协议
- ✅ DNS 解析后校验 IP,拒绝内网 / 环回 / 链路本地 / 保留地址
- ✅ 超时 5s,只读响应体前 512KB,防止被超大响应拖垮
- ✅ 成功缓存 7 天、失败缓存 1 小时,避免对同一链接反复发请求
3. 前端:异步解析,先显示网址后替换
前端用一个 composable 管理标题状态,核心是不阻塞页面渲染:
- 输入 URL 列表,批量发给后端解析
- loading 态先显示网址,标题到了再替换成标题
- 会话内内存缓存,同一 URL 不重复请求
- hover 出「✎ 改标题」入口,可手动改名或恢复自动
三、场景二:企业微信通知里的链接 → markdown 化 💬
场景一解决了「平台内」的链接,但还有一个更隐蔽的地方没解决:企业微信通知里的链接。
1. 根因:text 消息 vs markdown 消息
排查时发现一个有意思的根因:企业微信的消息分两种类型——
| 消息类型 | 链接表现 |
|---|---|
| text(纯文本) | 链接只能显示裸网址,不支持 [文字](链接) |
| markdown | 支持 [文字](链接) 语法,链接能显示成文字标题 |
而当时平台里除了「压力测试报告」之外,缺陷、需求、版本、催办这些通知全都在用 text 消息发,所以它们的链接永远是一串裸网址。😅
2. 为什么压力测试报告可以?
答案很简单:压力测试报告的通知从一开始就用的是 markdown 消息(send_wecom_markdown)。这就是用户反馈「除了压力测试报告,别处的链接都没法显示成文字」的根本原因——不是别处不想,而是别处压根没走 markdown。
3. 改造:把通知统一升级成 markdown
方案很清晰:把通知发送函数从 send_wecom_text 换成 send_wecom_markdown,模板里的链接改成 markdown 语法。
改造前(text):
"【缺陷 {num}】{title}\n" |
改造后(markdown):
"[【缺陷 {num}】{title}]({link})\n" |
这样企业微信里收到的就是一条带文字标题的链接,点击「【缺陷】#123 登录页崩溃」直接跳转,而不是一长串 https://xxx.com/bugs/...。✨
4. 连带问题:markdown 的 @ 需要 userid
改 markdown 后,@ 人的方式也变了:
| text 消息 | markdown 消息 | |
|---|---|---|
| @ 方式 | 手机号(mentioned_mobiles) | <@userid> 语法 |
| 前提 | 有手机号即可 | 需要成员先填 userid |
而 markdown 消息无法通过手机号 @,只能退回「@姓名」的纯文本提示。所以为了把 @ 也做好,我又补了一个小功能:让项目管理员也能帮成员填写 userid(之前只能系统管理员填)。
四、总结与可复用经验 📝
回头看,这两个场景虽然一个在「平台内」、一个在「企业微信」,但本质是同一件事:链接要显示成可读文字。真正值得沉淀的经验有三条:
- 先找参照物再动手:压力测试报告「能显示成文字」这个对比,直接暴露了根因是消息类型差异,省了大量排查时间。
- 服务端抓取用户可控的 URL,SSRF 防护是必修课:协议白名单 + DNS 解析后校验 IP + 超时体积限制,一个都不能少。
- 改消息类型会牵出连带依赖:text 的 @ 靠手机号,markdown 的 @ 靠 userid,改之前先确认成员数据是否齐备。
⚠️ 一点局限:平台内抓标题依赖「服务端能访问目标 URL」,纯内网部署时外网链接抓不到,会退回显示网址本身——这是合理的优雅降级,而不是错误。
可复用检查清单(再做「链接显示成文字」可以对照):
- 平台内外部链接:标题优先级定为「手动 > 自动抓取 > 网址」
- 抓取服务:只允许 http/https + DNS 解析后拒绝内网 + 超时体积限制 + 分级缓存
- 前端:异步解析,先显示网址后替换,不阻塞渲染
- 企业微信通知:统一走 markdown 消息,链接用
[标题](url)语法 - 改消息类型前,确认 @ 依赖的数据(userid / 手机号)是否齐备




