HalfCart:基于LBS的1.5km本地拼单系统全栈实践与踩坑复盘
项目背景与定位
本地即时拼单的需求一直存在,但多数拼单场景要么依附于社群(信息过载、履约无保障),要么局限于大型电商平台(履约周期长、无法覆盖即时性需求)。HalfCart 瞄准的是 1.5km 半径内的邻里 / 商圈轻量拼单场景:用户通过自然语言快速发布拼单,平台基于地理位置做半径筛选与就近排序,支持从发单、参团、支付到核销的完整闭环,主打低门槛、快履约、强本地属性。
本文从技术选型、核心链路实现、踩坑复盘三个维度,完整复盘这个项目从 0 到 1 的落地过程。
技术选型与整体架构
项目处于 MVP 验证阶段,选型核心原则是:业务交付优先,合理控制架构复杂度,核心能力依赖成熟第三方服务,不重复造轮子。整体采用前后端分离的轻量架构,无复杂的分布式组件。
后端技术栈
Web 框架:FastAPI(Python)。选择核心原因有三点:一是 Python 生态对接第三方服务(LLM、地图、支付)的 SDK 最完善,开发效率高;二是原生支持异步、类型提示与自动 OpenAPI 文档,接口联调成本极低;三是 MVP 阶段 QPS 压力小,性能完全满足需求。
数据层:MySQL 8.0 作为主存储,承载用户、订单、支付、地址等核心业务数据;Redis 用于接口限流、地理编码结果缓存与热点数据缓存,降低第三方接口调用频次。
消息队列:RabbitMQ 承载异步通知、状态变更回调等非核心链路,避免同步阻塞主业务流程。
核心第三方能力:
Dify:负责自然语言结构化解析,实现 “说一句话就能发单”;
高德地图 LBS:提供地理编码、步行距离计算、定位反欺诈能力;
微信公众号 OAuth:实现微信生态内一键登录;
支付宝沙箱:支付链路闭环验证。
前端技术栈
前端没有采用主流的 Vite + Vue SPA 工程化方案,而是主动选择了独立 HTML 多页面架构 + CDN 引入 Vue3 + Vant4。
决策依据:
MVP 阶段页面数量有限(19 个业务页面),多页面架构改造成本低、迭代快,修改即生效,无需构建流程;
H5 场景下多页面首屏性能优于单页应用,无打包体积问题,弱网环境加载体验更好;
部署简单,纯静态资源可直接托管,无需前端构建流水线,降低运维成本。
公共逻辑(请求封装、跳转工具、Token 管理)统一抽离到common.js,所有页面统一引入,兼顾复用性与灵活性。
核心技术实现
1. 基于 LLM 的自然语言发单链路
这是项目最核心的差异化能力:用户无需逐个填写表单,只需输入一段自然语言(如 “今晚 8 点山姆拼瑞士卷,差 3 人,小区北门取,人均 35”),系统自动解析为标准化的拼单字段。
链路设计原则:解耦提取与校验
Dify 工作流只负责一件事:实体提取与结构化输出。通过优化后的提示词,强制大模型输出固定 8 字段的 JSON 结构,包含商品名称、拼单人数、人均价格、取货时间、取货地址、备注等,同时输出置信度评分。
后端负责所有规则校验:人数下限校验、价格合法性校验、时间过期校验、地址二次标准化。业务规则不依赖大模型的概率性输出,保证逻辑确定性。
关键设计
置信度分级:置信度≥0.6 时自动填充表单,低于阈值时仅做参考,由用户手动确认;
降级兜底:Dify 服务不可用时,自动降级为纯手动表单发单,不阻塞主流程;
字段强校验:后端对返回的 JSON 做严格 Schema 校验,字段缺失、类型错误时直接走降级逻辑,避免脏数据入库。
2. LBS 地理围栏与距离排序
1.5km 半径筛选是产品的核心边界,整套地理能力基于高德 Web 服务 API 实现。
核心流程
地理编码:用户输入的文字地址转换为 GCJ-02 坐标系经纬度,同时返回标准化地址,解决用户输入地址模糊、不规范的问题;
批量距离计算:拼单列表查询时,基于用户当前坐标批量计算所有有效拼单的步行距离,而非直线距离,避免 “直线很近、实际隔河 / 隔高速” 的体验问题;
半径筛选与排序:过滤步行距离≤1500 米的拼单,按距离由近到远排序返回,前端展示具体距离数值。
稳定性设计
Mock 模式:本地开发默认开启 Mock,无需真实 Key 即可联调业务逻辑,避免调试消耗免费配额;
熔断机制:连续调用失败达到阈值后触发熔断,冷却期内自动走本地估算降级,防止第三方服务故障拖垮主业务;
反欺诈校验:基于定位速度、定位精度做基础反作弊,拦截模拟定位、虚拟定位的异常请求。
3. 多模式统一鉴权体系
支持三种登录方式,后端统一基于 JWT 鉴权,前端无感知:
微信 OAuth 登录:微信生态内主登录入口,授权后自动创建 / 匹配账号,登录即注册;
手机号验证码登录:通用兜底登录方式,首次登录自动静默注册,生成默认昵称与账户,用户无感知;
账号密码登录:补充登录方式,满足偏好密码登录的用户。
配套机制
前端统一请求拦截器:自动注入 Bearer Token,401 状态自动清空本地凭证并跳转登录页,同时携带当前页面路径作为回跳参数;
页面级登录拦截:除首页、详情页等游客可浏览的页面外,其余页面初始化时强制校验 Token,无权限自动跳转登录;
接口粒度权限控制:团长专属操作(核销、结束拼单)在后端做身份校验,前端仅做视图控制,避免前端越权。
踩坑复盘与解决方案
落地过程中踩了不少意料之外的坑,多数问题不在核心业务逻辑,而在第三方对接与架构细节上。
坑 1:Dify 输出格式不稳定与变量映射错误
问题:初期试图把业务校验逻辑全部写入 Dify 提示词,导致输出格式波动大,字段时有时无;工作流中代码节点与 LLM 节点的输出变量绑定错误,频繁出现入参为空的情况,排查成本极高。
解决方案:
职责拆分:Dify 只做结构化提取,所有业务规则校验全部后置到后端,用确定性代码替代大模型的概率性判断;
输出强约束:提示词中强制指定 JSON Schema,配合系统级输出格式约束,大幅降低字段缺失、格式错误的概率;
后端二次校验:对返回结果做全量 Schema 校验,异常直接走降级,不把不确定性传递给业务层。
坑 2:高德地图前后端密钥混用
问题:初期混淆了高德两类 Key 的用途,将前端 JS API 的 Key 填入后端服务,导致地理编码接口持续报权限错误,排查了近两个小时才定位根因。
解决方案:
双密钥隔离:后端 Web 服务 Key 仅部署在服务端,配置 IP 白名单;前端 JS Key 仅用于页面地图渲染,配置域名白名单,二者物理隔离,从架构上避免混用;
配置分层:所有密钥统一通过环境变量管理,代码中无硬编码,前后端配置文件完全分离;
增加 Mock 层:本地开发默认走 Mock 模式,无需真实 Key 即可跑通全流程,上线前再切换正式密钥。
坑 3:多页面架构下的跳转与状态治理缺失
问题:原型阶段为了快速出页面,所有跨页跳转全部用console.log占位,最终统计有 42 处占位逻辑,导致核心流程完全无法推进;同时各页面独立管理 Token,登录状态不同步,回跳逻辑混乱。
解决方案:
公共层收敛:封装全局
goPage跳转工具与getQueryParam参数读取工具,统一替换所有占位代码,统一管理路径拼接与参数传递;状态统一:Token、用户信息统一存储在 localStorage,公共 JS 统一初始化,所有页面共享同一份状态;
回跳标准化:跳转登录页时自动携带当前页面路径作为
redirect参数,登录成功后自动回跳,无需每个页面单独处理。
坑 4:前后端字段对齐成本高
问题:后端字段以下划线命名(goods_name),前端初期按驼峰取值(goodsName),加上字段名拼写错误,导致联调时大量接口 “通了但拿不到值”,低级错误占比很高。
解决方案:
约定命名规范:前后端统一采用下划线命名,从根源消除命名转换问题;
静态对齐审计:联调前用脚本全量扫描前后端字段,输出对齐清单,提前发现拼写、类型不匹配问题;
统一请求封装:前端 request 层统一做错误提示,字段缺失时输出明确日志,降低排障成本。
一点架构思考
做这个项目最大的感受是:技术选型没有优劣,只有是否匹配阶段。
MVP 阶段最珍贵的是迭代速度,而不是架构的 “优雅性”。多页面架构不如 SPA “标准”,但它让前端开发效率提升了一倍;用第三方服务做核心能力,不如自研 “可控”,但它让一个人就能在三周内跑通完整闭环。很多人做项目容易陷入架构焦虑,总觉得不上微服务、不上工程化就是 “不专业”,但对创业项目、个人项目而言,能快速验证业务、跑通闭环,才是第一优先级。
当然,这种轻量架构也有明确的边界:如果后续用户量上涨、页面数量翻倍,依然会向工程化 SPA、服务化拆分演进。但那是业务验证成功之后的事,而不是现在。
后续规划
目前核心交易链路已全部跑通,接下来的重点是三个方向:一是完善拼单内聊天、履约通知等体验功能;二是强化反作弊与风控能力,治理虚假拼单与恶意定位;三是优化前端交互细节,提升移动端体验。
等正式在小区落地跑一段时间,再补一篇线上真实运行数据的复盘。