AI 软件开发实战教程(十二):安全地交换双方微信号

发布时间:2026/8/24 14:54:02
AI 软件开发实战教程(十二):安全地交换双方微信号 “AI 软件开发实战教程”系列第 12 篇让候选中的一方主动确认后双方同时获得对方微信号同时保证确认前不泄露、重复点击不重复、资料修改不改写历史。系统找到“可能同路”的人以后真正的沟通仍要回到微信。邻行不做站内聊天因此联系方式交换是第一条核心流程中风险最高的一步。如果只把微信号显示在候选详情里会出现三个问题用户没有明确同意把自己的联系方式给对方页面权限的一处疏漏就可能批量泄露用户修改微信号后过去已经交换的结果会悄悄变化。K4 先把交换定义成一个独立、不可覆盖的业务事实。“双向交换”不是“双向审批”产品规则是候选双方中的任何一方都可以发起。确认页必须明确告诉发起者确认后你会看到对方的微信号 你的微信号也会同时提供给对方。 这只是方便双方回微信沟通 不代表已经约定同行。对方不需要再点一次同意。因此页面不能写成“双向确认”否则发起者会误以为还要等待。一次操作同时披露是这个产品在小范围社区场景中的明确选择。它减少了双方异步上线造成的等待但也要求确认文案、权限复核和审计事实都足够清楚。红灯先固定授权边界K4 的第一组测试在导入阶段失败因为交换模型和服务还不存在。随后测试逐个固定这些拒绝条件操作者不是候选双方之一任一方账户被限制或不可用候选已经失效任一出行信息已经关闭、取消或过期已到双方较早的预计出发时间双方不再属于同一个社区任一方尚未设置微信号。交换服务不相信用户刚刚打开的候选页面。点击确认到请求抵达服务器之间状态可能已经改变所以服务会在短事务中重新锁定和检查候选及双方信息。一个容易混淆的边界是匹配截止。它只表示停止产生新候选。截止前已经形成的候选即使现在已经停止匹配只要双方尚未到较早预计出发时间仍然可以交换微信号。专门的注入时钟测试固定了这条规则。保存“当时披露的值”交换不能只保存两个用户 ID然后每次打开页面去读取最新个人资料。假设乘客交换时的微信号是wx_old后来改成wx_new。过去的车主已经拿到wx_old系统却显示wx_new历史事实就不再可信。因此ContactExchange保存候选 发起者 交换时间 车主当时微信号的加密快照 乘客当时微信号的加密快照 密钥版本服务先用个人资料对应版本的密钥解密再用当前活动密钥重新加密为交换快照。测试在交换后修改乘客资料旧结果仍必须返回交换时的值。这里保存快照不是为了无限保留。账户删除任务仍要按产品规则擦除该用户在在线资料和交换快照中的敏感值那属于后续 K8 的数据生命周期工作。一对一约束是幂等的最后防线同一个候选只能有一次联系方式交换。数据模型使用候选的一对一关系事务中也先返回已经存在的结果。测试让车主先发起再由乘客重复发起最终只能得到1 个 ContactExchange 1 个 contact.exchanged 业务事件 双方各 1 条站内提醒 双方各 1 条渠道投递记录无论双击按钮、网络重试还是双方几乎同时操作业务语义都不会变成交换两次。SQLite 上的测试可以证明顺序重试和数据库唯一事实。真正的 PostgreSQL 双连接并发仍应在具备服务环境时补跑当前机器没有 PostgreSQL不能把 SQLite 冒充并发证据。不要把秘密交给模板再隐藏一个常见但危险的做法是把车主和乘客的微信号都放入模板上下文然后用页面条件决定显示哪一个。隐藏的 DOM、调试输出、错误页或以后新增的前端序列化都可能把不该出现的值送到浏览器。邻行的结果服务先判断当前查看者身份查看者是车主 → 只解密乘客快照 查看者是乘客 → 只解密车主快照 其他人 → 拒绝模板上下文只有一个other_wechat_id。HTTP 测试分别断言确认页源码不包含双方任一微信号交换后页面包含对方微信号页面不包含自己的微信号页面不包含登录账号非参与者打开确认和结果地址都得到 404。安全边界应当尽量发生在数据进入表现层之前而不是依赖 CSS 或前端脚本隐藏。复制按钮必须允许失败微信内置浏览器、系统权限和非安全上下文都可能让 Clipboard API 不可用。一键复制只能是增强能力不能成为唯一出口。结果页把对方微信号放在只读但可以聚焦、长按和选择的输入框中。复制成功显示“已复制”失败时自动选中文本并提示复制失败请长按或手动选择上面的微信号。页面不会尝试打开未知的微信协议也不会自动跳走。下一步文案只告诉用户返回微信添加对方并确认具体上下车位置。自动浏览器可以证明结构和降级路径已经存在但 iOS 和 Android 微信中的长按选择、复制权限与返回操作仍必须由 Gate B 真机验收不能由桌面 Chromium 最终代替。双用户浏览器闭环K3 已有两个隔离浏览器上下文。K4 在同一旅程后继续执行乘客打开候选 → 点击“想和对方联系” → 确认页看不到任何微信号 → 阅读互相披露说明并确认 → 乘客只看到车主微信号 → 车主刷新候选并打开结果 → 车主只看到乘客微信号 → 双方都看到返回微信的下一步两个上下文有独立 Cookie 和会话。测试不是把同一个客户端来回切换用户因此更接近双方异步操作的真实授权路径。本节点结果K4 收口时85 个非浏览器测试通过分支覆盖率 90.79%Ruff、格式、mypy strict、迁移和 Django 检查通过4 条 Chromium 旅程通过其中双用户旅程已走到互相披露结果重复交换、资料修改、非参与者、受限账户、失效状态和时间边界都有自动证据确认前不含微信号结果页每人只收到对方微信号外部提醒实际发送、PostgreSQL 并发、WebKit 和微信真机仍未宣称通过结果只形成本地提交不推送。验收矩阵中AC-21 至 AC-23 可以在当前页面范围内自动通过AC-41 也完成交换前后身份展示闭环。AC-24 仍是部分通过交换事实与双方独立投递记录已经形成第三方一方失败不影响另一方要等 K7 故障发送测试。写在最后敏感数据功能的关键不是“加密了”三个字而是明确谁在什么时刻授权、事务中重新检查什么、保存哪个时间点的值以及秘密最早在哪一层被裁剪。当结果模板从一开始就只拿到对方微信号后续页面改版也更难意外泄露另一份数据。好的隐私设计往往同时让代码职责更清晰。下一篇进入 K5双方回微信沟通后怎样分别反馈结果为什么单方确认不能占座以及如何用数据库锁保证最后一个座位不会同时分给两位乘客。关键代码与操作下面的简化测试从双方视角读取同一次交换证明每个人只能得到对方的微信号deftest_one_actor_discloses_both_contacts(candidate,driver,passenger):exchangeexchange_contact(candidate_idcandidate.pk,actor_iddriver.pk,nowtimezone.now(),)assertget_disclosed_contact(exchangeexchange,viewerdriver)passenger-wechatassertget_disclosed_contact(exchangeexchange,viewerpassenger)driver-wechatassertBusinessEvent.objects.filter(event_typecontact.exchanged).count()1验证命令make bugfix TESTtests/matching/test_contact_exchange.py::test_one_actor_discloses_both_contacts_and_creates_one_event示例只表达权限方向真实测试还会覆盖重复点击、资料修改、外部成员和过期候选。本篇验证摘要双方必须在同一个有效候选中明确同意互相披露系统才创建交换交换保存当时双方微信号的加密快照之后修改资料不会悄悄改写历史结果唯一约束和幂等服务保证重复点击不会创建第二次交换查询服务只向每位参与者返回对方的联系方式页面拿不到自己的解密值移动浏览器已验证双方确认、查看和复制流程提醒码始终不会进入页面。附录相关工具与仓库gstack仓库garrytan/gstack地址https://github.com/garrytan/gstackdev-harness仓库Dev-Wiki/dev-harness地址https://github.com/Dev-Wiki/dev-harnessUI UX Pro Max Skill仓库nextlevelbuilder/ui-ux-pro-max-skill地址https://github.com/nextlevelbuilder/ui-ux-pro-max-skill