【项目实战】第8篇:分享链接与 URL 教程——发出去的链接,朋友打不开

博客文章#Workbuddy阅读 0
【项目实战】第8篇:分享链接与 URL 教程——发出去的链接,朋友打不开

系列:《用 AI 写出高质量代码:AI 狼人杀开发实录》 本篇对应版本:v1.11.0 → v1.11.5|时间:2026-10-23

前言

第 7 篇做完账号,游戏终于「有主」了。接下来是最重要的一件事:让别人看到。

我做了战绩卡、分享面板、观战链接,自己测了三遍都没问题。然后我把观战链接发给朋友,他回了一张截图——平台提示「链接不完整 / 当前地址缺少访问参数」。


引子:能打开的和打不开的,只差一个 #

我发出去的是两个东西:

  1. 战绩卡——结算页「保存图片 / 分享给朋友」出的那张卡,朋友能打开
  2. 观战链接——局中点「叫朋友来看」拿到的那条地址,朋友打不开

图:结算页的战绩卡。它是能正常打开的那一条——一张图片,不依赖地址里的任何参数。

同一份代码、同一个域名、同一次发布。差别只有一个:打不开的那个是带 # 的地址(#watch=…),能打开的那个根本不是地址,是一张图片。

这条线索看着不起眼,但它把排查范围从「整个分享功能」直接缩到了「带 # 的链接」。线上排查最值钱的动作,就是把「能用的」和「不能用的」摆在一起看差在哪——比读十遍代码都快。


问题:先证明「服务器没收到」

平台的报错页只说了「缺少访问参数」,没说缺哪个、也没说是哪一段地址有问题。所以我不猜,先做对照实验:同一台服务器,三条请求。

三条 curl 只打印状态码:① 裸域名、② 带 ?sharecode=…、③ 带 ?sharecode=…#watch=abc。

结果是 403 / 200 / 200。

结论一句话:没有 sharecode 就 403。 而 ② 和 ③ 结果完全一样,这一点更关键——它证明 # 后面的内容根本没参与这次请求,服务器看到的只有 ?sharecode=…。


问题原因:一条地址只有两半,一半永远到不了服务器

图:一条 URL 被切成两半。左边 query 随请求发给服务器,右边 fragment 只留在浏览器里。事故版本把 sharecode 拼到了右半边——服务器看到的请求里压根没这个参数,于是回 403。

要补的背景只有一句:# 最早是用来做「页内跳转锚点」的,所以它天然属于浏览器内部信息。哪怕你把 # 后面写得跟参数一模一样,服务器也永远收不到。

我们的代码是怎么把参数放错的

生成观战链接是两步:先拼深链 ${origin}/#watch=${gameId},再调 withShareCode(url, shareCode) 补分享码;而出事的 withShareCode 判断分隔符只看有没有问号(url.includes('?') ? '&' : '?'),完全不知道井号的存在。整串里确实没有 ?(只有 #),于是它用了 ? 直接追加——分享码就落进了 fragment,服务器一个字都收不到。

这不是「写错了一个字符」,是函数不知道地址有结构。 它把 URL 当成一根字符串,所以只能判断「有没有问号」,问不出「问号在井号前还是井号后」。


解决方案:三层处理,三个动作

这次的修法不是「踩一步修一步」,而是按「错在哪一层」各修一处:

  • 生成时:参数落进了 # 后面 → ① 让函数懂地址边界
  • 代码里:同名两份实现 → ② 只留一份
  • 回归:没人拦着它回来 → ③ 把顺序写成断言

① 生成时:参数必须插在 # 之前

修好的实现只有五步:先切开 #,取前半段 base 和后半段 hash;在 base 上按「有没有 ?」决定用 & 还是 ? 拼上 sharecode;最后把 hash 原样拼回去(没有分享码时原样返回)。

解析:思路就一句——先把 # 切开,在「前半段」上拼 query,再把 hash 拼回去。 这样无论原串有没有 #、# 里有什么,参数都落在服务器能看到的位置。

注意那个 sep 仍然只看 base(不含 # 的部分)。把「判断」和「拼接」都收进同一个函数,它才可能同时正确。

② 代码里:同名函数只留一份

share.ts 里重复的处理逻辑删掉,改成从 url.ts 导入再导出。

这件事的价值不在「少了几行」,在于把未来的改动收敛到一个点。 只要有两份,就一定有一次是「改了一份忘了另一份」——而这次事故正好证明了这一点:url.ts 那份其实也没处理 #,只是没人走到那条路径。

③ 回归:把顺序写成断言

改了代码不算完,得有东西拦着它回来,随后让AI加了 7 条用例进行检查。


效果:新会话里打开是 200

修完之后生成的链接是这样:

https://<域名>/?sharecode=2IBHTRFFFL#watch=gmvknynr5d64f93

?sharecode=… 在前、#watch=… 在后。我把它粘进一个全新的无痕窗口:HTTP 200,观战页 6 个座位正常加载,进度跟着实时推进。

图:修复后的分享面板(v1.13.1)。同样是三级降级,但「复制链接」给出的地址已经是 ?sharecode=… 在前、#watch=… 在后。


内容小结

  1. 线上排查先找对照组:能用的和不能用的差在哪,范围立刻缩小一半。三条 curl 比读一小时代码有用。
  2. 同名函数留两份,等于给未来的自己埋一次「改漏」。这次两份都没处理 #,只是恰好只有一条路径会撞上。
  3. 可迁移吗? 任何带深链(# 路由)、带邀请参数、带追踪参数的产品都适用——这是前端最常见的一类「环境知识」。

参考资料

  • MDN:URL、Location.hash、片段标识符
  • 项目仓库:client/src/lib/url.ts(唯一的 withShareCode / screenUrl)、tests/url.test.ts、tests/share-url.test.ts
  • 代码截图取自提交 c7cb6c9(事故版本),修复见 5820841、504a46b
  • 上一篇:第7篇:登录鉴权与数据库教程——「我是谁」卡了我一整天
  • 下一篇:第9篇:自动化测试教程——怎么证明它是对的

[评论 0]

评论加载中…