AI 软件开发实战教程(九):从邀请注册开始第一条用户流程 “AI 软件开发实战教程”系列第 9 篇用 TDD 完成社区邀请码、登录会话、账户限制与私密资料加密再让 375px 的真实浏览器走完第一条用户流程。上一篇完成了工程骨架。测试能运行、浏览器能打开页面、生产配置会尽早失败但产品还不能让任何真实成员进入。这一次开始第一个业务任务 K1收到社区邀请链接 → 注册登录账号和密码 → 直接进入信息大厅先浏览 → 设置微信号和喵码 → 用假渠道确认提醒状态 → 退出后不能继续查看社区内容它还没有发布和匹配却已经是一条完整的用户流程。每一步都同时涉及产品规则、权限、安全、页面和测试正适合展示纵向 TDD 与“先把所有数据表建完”的区别。先把产品句子变成失败测试K1 负责的产品验收并不是一句“实现登录”。它至少包含有效邀请码可以注册无效、停用、耗尽和过期邀请码不能注册两个顺风车群的邀请最终加入同一个社区注册只要求登录账号和密码不强迫立即填写联系方式未登录不能查看社区信息受限或正在删除的账户不能登录也不能靠旧会话继续访问登录错误不能透露账号是否存在登录账号不会展示给其他成员微信号和喵码加密保存页面与 Admin 不回显原文修改喵码以后以前的“已测试”状态立即失效。第一批测试先导入尚不存在的社区和私密资料模块。运行结果是ModuleNotFoundError: No module named apps.communities ModuleNotFoundError: No module named apps.accounts.services这一次红灯明确说明目标模块还不存在。随后才创建模型、服务、页面和迁移。邀请码不是数据库里的一串明文如果数据库直接保存邀请码管理员后台、备份或一次误打日志就可能把仍然有效的注册链接全部暴露。邻行采用和密码相似的思路随机生成邀请码 → 与服务端密钥做 HMAC-SHA256 → 数据库只保存 64 位摘要 → 明文注册链接只在签发时输出一次验证邀请时对用户提交的明文做同样摘要再查找对应记录。数据库不需要、也不能恢复原邀请码。邀请码还保存所属社区来源群启用或停用状态有效期最大使用次数和已使用次数。注册服务在数据库事务中锁定邀请、重新检查状态、创建用户并增加使用次数设计目标是让同一张只允许使用一次的邀请不能被并发正常消费两次。当前自动测试已经覆盖顺序消费和耗尽拒绝真正的 PostgreSQL 双连接并发证明仍要在具备 PostgreSQL 的环境补做不能用 SQLite 测试替代。为什么需要单独的签发命令最小实现完成后出现了一个实际可用性问题Admin 可以看到邀请记录却没有办法安全地产生“只显示一次”的明文。如果允许管理员自己填写摘要他必须先在别处计算既容易出错也会鼓励把明文复制进不受控工具。因此增加管理命令python manage.py issue_invite 社区编号 来源群命令生成随机邀请码只打印一次完整邀请路径Admin 禁止直接新建邀请记录只负责查看状态和停用。测试同时证明输出中有邀请链接数据库中只有摘要摘要本身不会被当成可用链接输出。工程安全不仅是“数据库用了哈希”还要保证日常操作不会逼人绕过安全设计。注册账号和微信号必须是两个概念用户可以把登录账号填写成与微信号相同的内容但系统仍然把它们当作不同资料登录账号用于认证微信号只在合法候选交换后披露喵码只用于提醒渠道。注册页面因此只包含登录账号、密码、确认密码和产品边界确认。账号帮助文字明确说明不会展示给其他成员。注册成功后直接进入信息大厅不弹出“还差两项资料”阻断浏览。这个决定来自前面的产品规划用户可以先看看社区里有没有有价值的信息第一次发布前再补齐联系方式和提醒。K1 只证明“先浏览”和“能够完成资料准备”。真正阻止资料不完整的首次发布要等 K2 有发布入口、K7 有真实提醒状态后才能完整验收。登录错误不能帮攻击者确认账号登录流程支持登录成功后轮换会话打开受保护深层链接时登录后安全返回原页面外部网址不能伪装成next参数把用户带走退出使用 POST使当前会话失效受限或删除中的账号使用与密码错误相同的提示账户状态在已有会话期间改变时下一次请求立即退出。错误统一显示账号或密码不正确不存在的账号、错误密码和受限状态不会得到三个不同答案。架构审阅还要求限制连续失败。实现没有保存原始账号和 IP而是把“规范化账号 来源地址”与服务端密钥做 HMAC 摘要只保存摘要、窗口开始时间和失败次数。同一组合五分钟内连续失败五次后下一次返回稍后再试成功登录会清除对应计数。这样可以限速又不在额外安全表中复制登录账号。敏感字段加密不是简单调用一次 encrypt微信号和喵码需要在合法流程中恢复原文所以不能像邀请码一样只保存不可逆摘要。邻行使用 Fernet 认证加密并保存当前密钥版本。测试不只断言“字段看起来不像原文”还直接查询数据库encrypted_wechat_id 不包含测试微信号 encrypted_miaocode 不包含测试喵码随后打开设置页断言 HTML 也不包含两项原文。页面只显示微信号已设置 / 未设置 微信提醒未设置 / 等待测试 / 已测试可用喵码永远不回填到输入框。当前微信号也不完整回显避免旁观者、页面插件或浏览器表单恢复无意读取旧值。密钥轮换暴露了一个重构陷阱私密资料最初只有一个key_version微信号和喵码共享这个版本。假设旧数据都用密钥 1 加密系统切换到密钥 2 后用户只修改微信号。如果代码只加密新微信号并把版本改成 2旧喵码仍然是密钥 1 的密文却被标成版本 2之后无法解密。这类错误在“保存后字段不是明文”的测试中不会出现。因此增加轮换测试用密钥 1 保存微信号和喵码 → 切换活动密钥到 2 → 只修改微信号 → 微信号和未修改的喵码都必须能以版本 2 解密实现遇到旧版本时先用旧密钥解密两个已有字段再用活动密钥一起重加密最后保存统一版本。这说明 TDD 的价值不只是让当前页面可用也能把未来运维动作转成现在可以重复证明的行为。假提醒为什么不传真实喵码K1 需要让页面出现“已设置”和“已测试可用”状态但真实喵提醒适配器属于 K7。测试渠道接收的是内部私密资料引用private-profile:内部编号而不是喵码原文。它记录一条测试消息并返回成功不访问外网也不会把喵码写进测试结果。这只能证明站内状态变化和页面流程正确不能证明真实第三方的失败、超时或正文分类。AC-05 因此继续保持未实现由 K7 用故障桩和真实渠道契约关闭。页面设计从 375px 开始K1 同时实现了第一组真实页面产品说明页邀请注册页登录页暂时为空的信息大厅联系与提醒设置页邀请不可用状态页。页面沿用已经批准的蓝绿色设计系统使用服务端模板、原生 CSS 和少量浏览器能力。表单控件至少 48px 高焦点清晰错误靠文字表达支持深色模式和减少动效偏好。没有为了让“大厅看起来真实”伪造任何车主、乘客或成功案例。K2 尚未实现信息发布所以大厅诚实显示空状态和下一步能力说明。浏览器测试为什么也会先失败单元和 HTTP 测试通过以后Playwright 使用 375×812 视口执行打开邀请链接 → 填写账号和密码 → 确认产品边界 → 注册进入大厅 → 打开联系与提醒 → 保存微信号和喵码 → 发送假测试提醒 → 退出 → 再访问大厅被要求登录第一次浏览器测试停在确认密码输入框。原因不是页面坏了而是自动化查找“确认密码”页面真实可访问名称是“密码确认”。失败截图清楚显示了页面状态。修正定位器后旧的主页冒烟测试又失败了V0 时它寻找一整句纯文本标题而现在页面已经有正式的品牌、眉题和主标题结构。测试没有把页面改回纯文本而是更新为检查文档标题和真实一级标题。最终两条 Chromium 测试都通过并额外断言注册页和设置页在 375px 下没有水平溢出。如何诚实更新验收矩阵K1 完成后验收状态不是全部变绿AC-01邀请注册规则有服务、HTTP 和浏览器证据自动通过AC-02当前大厅的未登录阻断有 HTTP 和浏览器证据自动通过AC-03两个来源群进入同一社区自动通过AC-04先浏览和资料设置已经通过但首次发布门禁等待 K2/K7部分通过AC-40当前页面不向其他成员展示登录账号但未来页面尚未出现等待 K9 全量扫描部分通过AC-05真实提醒失败语义尚未实现保持未实现。任务可以完成同时某些跨任务验收仍是部分通过。只要负责人、剩余证据和后续节点写清楚这比为了让看板漂亮而提前宣称完成更可靠。本节点的结果K1 收口时得到47 个非浏览器测试通过分支覆盖率 92.25%Ruff、格式、mypy strict、迁移检查和 Django system check 通过两条 Chromium 测试通过生产部署检查零警告邀请码、登录账号、微信号和喵码没有进入不该出现的页面或数据库字段WebKit 与微信真机仍明确未验证只创建本地提交不推送。写在最后第一条业务流程让项目第一次像一个真实产品而不仅是能启动的工程。但真正重要的并不是出现了注册页而是每一层都在表达同一组产品决定社区邀请控制边界用户可以先浏览登录身份不公开联系方式只为后续合法交换准备第三方提醒不是站内事实源。下一篇进入 K2结构化发布、信息大厅和详情。那时将第一次处理用户真正要发布的日期、时间窗口、截止、路线、座位和分享文案也会遇到“现在出发”、重复提交、过去时间和详情权限这些更接近业务核心的 TDD 场景。关键代码与操作下面是敏感资料测试的简化摘录。示例值都是虚构数据完整测试还会直接检查数据库密文deftest_private_values_are_not_rendered(client,user):update_private_profile(useruser,wechat_idexample-wechat,miaocodeexample-reminder-code,)client.force_login(user)contentclient.get(/settings/profile/).content.decode()assertexample-wechatnotincontentassertexample-reminder-codenotincontentassert已设置incontent验证命令make bugfix TESTtests/accounts/test_private_profile.py::test_private_contact_values_are_encrypted_and_not_rendered这里同时验证“数据能够使用”和“秘密不会被页面回显”不能只断言保存成功。本篇验证摘要邀请码只保存不可逆摘要并在事务中检查有效期、启用状态和剩余次数登录失败统一提示并限制连续尝试不向攻击者透露账号是否存在微信号和喵码使用版本化加密保存页面、Admin 和日志不回显原文移动浏览器已经走通邀请注册、先浏览、设置资料、测试提醒和退出流程SQLite 已覆盖顺序消费邀请码并发仍需 PostgreSQL 双连接补充证明。附录相关工具与仓库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