news 2026/8/22 9:52:26

AI 软件开发实战教程(十):把微信群消息变成结构化发布

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
AI 软件开发实战教程(十):把微信群消息变成结构化发布

“AI 软件开发实战教程”系列第 10 篇:把“八点左右”“现在出发”等群聊表达转换成可计算的时间契约,并用 TDD 完成发布、查找、详情和安全群文案。

邻行最初面对的是这样的消息:

【车找人】 【时间】明早 8:05 【路线】万科~环普/中软/阿里/华为

它对熟悉小区的人很好理解,对系统却不够明确:

  • “明早”是哪一个日期;
  • 8:05 是必须准时,还是前后都可以;
  • 发布者准备等到什么时候;
  • 斜杠分隔的是多个终点,还是大范围方向;
  • 消息被重复发送时,是新需求还是同一次点击。

K2 的任务不是给微信群加一个更漂亮的输入框,而是把这些含义变成可以保存、筛选和验收的产品事实。

时间不是一个字段

每条出行信息至少需要四个时间事实:

预计出发时刻 可出发窗口开始 可出发窗口结束 停止产生新候选的截止时刻

只填写预计时间时,邻行默认:

预计 08:20 可出发 08:05–08:35 匹配截止 08:05

这里“匹配截止”不是最晚出发时间。它表示系统从该时刻起不再形成新候选,让用户还有机会改乘其他交通方式。

上一篇产品审阅中曾出现一个矛盾表达:“可出发到 08:35,最晚等到 08:20”。正式规则把含糊的“最晚等到”拆成两个概念后,页面也不再使用那句话。

先用纯函数固定所有边界

时间规则不应该先藏进表单或数据库模型。K2 先写一个没有数据库副作用的build_schedule,输入当前时刻和用户选择,输出完整时间契约。

测试固定当前时间为北京时间 08:00,覆盖:

  • 预计 09:00,得到 08:45–09:15,截止 08:45;
  • 预计 08:10,距离不足 15 分钟,截止等于 08:10并标记时间紧迫;
  • 选择现在出发,窗口为 08:00–08:15,截止 08:15;
  • 把现在出发窗口延长到 08:30,未手改截止时一起延长;
  • 用户单独提前截止时不再自动覆盖;
  • 预计时刻在过去、窗口不包含预计时刻、截止已到或晚于窗口结束时拒绝。

第一批测试因为apps.trips尚不存在而失败,随后才创建规则模块。

这种做法让时间错误可以精确定位。否则一个浏览器测试只会说“发布失败”,很难判断是时区、表单、数据库还是页面脚本造成。

“现在”不能保存成一个会变化的词

用户点击“现在出发”时,数据库不会保存字符串now

服务在提交瞬间读取一次服务器时间,保存确定的带时区时间戳:

window_start = 提交时刻 expected_at = 提交时刻 window_end = 提交时刻 + 15 分钟 matching_deadline = 提交时刻 + 15 分钟

以后打开详情,看到的是当时保存的具体时刻,而不是每次刷新都向后移动的“现在”。

数据库统一保存 UTC,日期和页面显示按Asia/Shanghai解释。测试也使用带时区的固定时刻,避免依赖运行机器的本地时区。

车找人和人找车不是同一张随意表单

两种信息共享日期、时间和路线,却有一个关键差异:

  • 车找人必须填写正整数座位;
  • 人找车不能提交座位。

这个规则同时存在于三层:

  1. 表单给出可修复错误;
  2. 发布服务拒绝绕过表单的非法调用;
  3. 数据库 CheckConstraint 保证车主座位大于零、乘客座位为空。

只在页面隐藏座位框是不够的。旧页面、手工请求或后续代码都可能继续提交值,服务和数据库仍要守住同一个事实。

地点必须来自社区词表

首版不采集自由文本说明、联系方式或精确住宅地址。

管理员维护大范围地点,例如:

  • 万科悦城;
  • 软件新城;
  • 国际医学中心。

地点显式属于社区,停用后不能用于新发布。表单使用封闭选择控件,服务再次检查起终点都属于当前用户社区、仍然启用并且不能相同。

地点别名模型也已经建立,管理员可以把“中软”“软件新城”等输入习惯整理到标准地点。当前社区地点很少,页面先使用原生封闭选择;大量地点时的搜索体验留到完整 UI 验收增强,但不能为了搜索方便改回任意文本。

重复点击不能创建两条信息

移动网络中,用户可能双击按钮、返回重试,浏览器也可能在超时后重新提交。

发布页每次展示时生成一个随机幂等键,提交时和用户一起形成数据库唯一约束:

unique(publisher, idempotency_key)

第一次请求创建稳定 UUID 信息;后续相同请求返回原信息。服务测试连续执行两次相同命令,证明数据库最终只有一条记录。

幂等键不是短编号。短编号是每条信息独立生成的四位随机辅助标识,用于群里讨论“哪一条信息”,不能稳定追踪同一个发布者。

大厅查询本身就是状态规则

默认大厅只显示:

同一社区 状态有效 当前时间早于匹配截止 当前时间早于预计出发

后台过期任务即使晚运行,查询也不会把已经截止或已经出发的信息继续当成可联系内容。

成员可以按日期、类型、起点、终点和本地时间范围筛选。首版没有“方向”控件,也没有关键字搜索。路线兼容关系属于后续候选规则,不应该变成另一个含糊筛选入口。

详情链接使用随机 UUID,并在查询时同时限制社区。知道另一个社区的 URL 也只会得到 404;链接本身不绕过登录和社区资格。

群文案是主动分享的结构化快照

发布成功后,页面生成可以预览和手动复制的群文案:

【车找人】 【日期】2026-08-03 【时间】08:05–08:35 【路线】万科悦城 → 软件新城 【座位】2 个 【状态】正在寻找 【编号】A7K2 【详情】https://example.com/trips/...

文案只包含用户已经主动选择公开的大范围信息。它不包含登录账号、微信号、喵码、精确地址或自由说明。

详情页 Open Graph 描述也遵守同样边界。自动测试同时扫描 HTML、群文案和浏览器页面,而不是只检查数据库字段。

浏览器连续发现三个真实问题

HTTP 测试通过后,Playwright 在 375×812 视口执行:

登录 → 打开发布页 → 选择车找人 → 填写日期、时间、地点和座位 → 发布进入详情 → 检查群文案无敏感资料 → 返回大厅看到刚发布的信息

第一次失败发生在发布类型。代码定义的是普通 ChoiceField,模板却把它当单选卡片迭代,浏览器实际得到没有正确标签的下拉选项,提交提示类型必填。将字段明确改为 RadioSelect 后修复。

第二次失败发生在路线标题。视觉箭头被标成读屏忽略,辅助技术只读出两个地点,没有表达方向。页面增加视觉隐藏的“到”,可访问名称变成“万科悦城 到 软件新城”。

第三次失败更接近业务:默认大厅一直为空。无查询参数时筛选表单处于未绑定状态,旧代码只在form.is_valid()为真时执行查询。修正后先执行默认开放信息查询,只有用户实际提交且筛选有效时才覆盖。

这些问题都不是增加更多单元测试能自然发现的。真实浏览器把表单语义、无障碍树和完整页面数据流连在一起。

如何更新验收状态

K2 完成后,自动通过的场景包括:

  • 车主和乘客结构化发布;
  • 默认窗口、截止、紧迫边界和过去时间拒绝;
  • 重复发布幂等;
  • 五类大厅筛选且没有方向控件;
  • 现在出发及窗口联动;
  • 发布表单和群文案没有自由文本与联系方式。

仍然不能提前关闭:

  • AC-12 的查询部分已通过,但 K6 还要把状态正式落为过期;
  • AC-41 已证明交换前只显示角色和随机短编号,交换后的微信号权限等待 K4;
  • 真实提醒、WebKit 和微信真机仍未验证。

任务完成不等于所有相邻能力都已经完成。

本节点结果

K2 收口时:

  • 65 个非浏览器测试通过;
  • 分支覆盖率 90.24%;
  • Ruff、格式、mypy strict、迁移和 Django 检查通过;
  • V0、K1、K2 共三条 Chromium 用户旅程通过;
  • 发布详情和群文案不包含测试登录账号、微信号或喵码;
  • WebKit、PostgreSQL 实跑和微信真机限制继续如实保留;
  • 结果只形成本地提交,不推送。

写在最后

把群消息结构化,不是把一段文字拆成几个输入框。

真正的工作是消除会影响匹配和提醒的歧义,同时不采集产品根本不需要的个人信息。日期、时间窗口、截止、地点、座位和状态都必须有单一含义,并能被数据库、查询和页面共同验证。

下一篇进入 K3:当一条车找人和一条人找车先后出现时,系统怎样用明确规则形成“可能同路”的候选,保存可解释原因,并为双方创建同一个业务事件下的两条站内提醒。

关键代码与操作

下面的简化测试固定同一个幂等键连续发布两次,核心断言是数据库最终只有一条信息:

deftest_repeated_publish_returns_one_trip(user,command):first=publish_trip(user=user,command=command,now=NOW)second=publish_trip(user=user,command=command,now=NOW)assertsecond.pk==first.pkassertTripPost.objects.count()==1

验证命令:make bugfix TEST=tests/trips/test_publishing.py::test_repeated_publish_command_returns_one_trip

这类测试比只禁用提交按钮更可靠,因为重试请求和旧客户端仍会经过服务与数据库。

本篇验证摘要

  • 日期、预计时刻、可出发窗口和匹配截止被保存为含义明确的时间事实;
  • 车主座位、社区地点和起终点规则同时由表单、服务与数据库约束保护;
  • 发布使用幂等键,重复提交只返回同一条信息;
  • 大厅和详情从查询层限制社区与有效状态,不能靠隐藏模板代替权限;
  • 移动浏览器已经走通发布、详情、群文案和大厅回看,文案不包含账号或联系方式。

附录:相关工具与仓库

gstack

仓库:garrytan/gstack
地址:https://github.com/garrytan/gstack

dev-harness

仓库:Dev-Wiki/dev-harness
地址:https://github.com/Dev-Wiki/dev-harness

UI UX Pro Max Skill

仓库:nextlevelbuilder/ui-ux-pro-max-skill
地址:https://github.com/nextlevelbuilder/ui-ux-pro-max-skill

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/8/22 9:51:37

5 分钟给页面加上开源图标库:Remix Icon 引入与定制教程

5 分钟给页面加上开源图标库:Remix Icon 引入与定制教程 【免费下载链接】RemixIcon Open source neutral style icon system 项目地址: https://gitcode.com/gh_mirrors/re/RemixIcon Remix Icon 是一套线条风格中性的开源图标库,包含 3200 多个…

作者头像 李华
网站建设 2026/8/22 9:51:09

CSP-J初赛通关指南:从进制转换到栈队列的算法思维构建

1. 项目概述:从零开始的CSP-J初赛通关之路如果你正在为孩子的CSP-J初赛,或者自己作为编程初学者第一次接触信息学奥赛而感到迷茫,那么这套“CSP-J初赛集训(0-26课)”可能就是为你量身定制的路线图。CSP-J/S认证作为国内…

作者头像 李华
网站建设 2026/8/22 9:48:47

Agently框架:从零构建可工程化的大模型智能体应用

1. 项目概述:从“智能体”到“智能体时代”的工程化跃迁最近在跟几个做AI应用开发的朋友聊天,大家普遍有个感觉:大模型的能力是越来越强了,但真要把这些能力稳定、高效地集成到自己的业务系统里,总感觉像是在“手搓火箭…

作者头像 李华
网站建设 2026/8/22 9:48:41

新手小白学习计算机的第十二天(老王专场)

#define _CRT_SECURE_NO_WARNINGS #include <stdio.h> #include <stdlib.h>// 宏定义棋盘尺寸&#xff0c;统一管理&#xff0c;修改方便 #define ROW 15 #define COL 15// 函数声明 void initBoard(char board[ROW][COL]); // 初始化棋盘 void drawBoard(char …

作者头像 李华
网站建设 2026/8/22 9:46:39

AI又“幻觉“了?我在提示词发布流程加了一道“安检门“

一、问题现状&#xff1a;AI的"一本正经胡说八道" 在AI对话产品的实际运营中&#xff0c;长期存在一类难以被传统测试手段覆盖的质量问题&#xff1a; > 现象&#xff1a; AI回答流畅、逻辑自洽、代码工整&#xff0c;但核心事实完全错误。 1.1 典型案例&#xff…

作者头像 李华
网站建设 2026/8/22 9:45:40

GetQzonehistory:免费完整导出QQ空间全部历史说说,三步完成备份

GetQzonehistory&#xff1a;免费完整导出QQ空间全部历史说说&#xff0c;三步完成备份 【免费下载链接】GetQzonehistory 获取QQ空间发布的历史说说 项目地址: https://gitcode.com/GitHub_Trending/ge/GetQzonehistory 想翻回大学时期的说说&#xff0c;却发现QQ空间有…

作者头像 李华