跳到正文
Joeplover
工程·2026-07-24·约 12 分钟阅读

小城小摊:微信小程序的主要功能与技术架构

小城小摊是一套面向小城摊主和消费者的轻量化数字工具。本文从项目实际代码出发,介绍附近摊位、市场聚合、营业会话、优惠券核销和运营治理,以及前后端整体架构。

集市摊位与手机上的数字化服务

小城小摊:微信小程序的主要功能与技术架构

项目想解决什么问题?

小城里的摊位有一个很明显的特点:营业时间和位置都不固定。早市、夜市、临时摊点和周末集市各有自己的节奏,消费者往往知道自己想吃什么,却不知道附近哪里正在营业;摊主有好商品,却缺少一个轻量、低成本的展示和经营工具。

“小城小摊”就是围绕这个场景设计的 MVP。它不做支付、订单、配送和购物车,而是把范围集中在一条更适合地摊场景的链路上:

消费者发现附近摊位 → 查看营业信息 → 领取限时优惠 → 到摊核销 → 反馈与治理

对于摊主,则是:

申请入驻 → 管理摊位 → 发布营业会话 → 配置优惠 → 核销优惠 → 处理举报和申诉

一、消费者端:让附近的摊位被看见

1. 附近摊位发现

小程序打开后,用户可以通过微信定位查找附近正在营业的摊位。后端使用 MySQL 8 的空间能力保存位置,并基于 POINT SRID 4326 做附近和视野内查询。

返回结果不只是一个名称列表,还可以携带距离、营业时间、摊位评分、优惠信息和位置等数据。这样用户打开小程序后,看到的是当前可以去的摊位,而不是一份静态商家名录。

发现模块还支持不同排序方式和视野范围查询,适合在地图或附近列表中持续浏览。

2. 市场聚合

小城里很多摊位会集中在早市、夜市和周末集市。市场模块把这些固定区域单独聚合起来,用户可以浏览市场列表、查看市场详情,以及了解当前市场内正在营业的摊位。

用户还可以提交市场建议。市场不是一次配置后就永远不变的静态数据,运营人员可以在后台审核这些建议,逐步完善城市中的市场信息。

3. 摊位详情和收藏

摊位详情页用于展示摊位描述、图片、商品关键词、营业状态和位置等信息。用户可以收藏摊位,也可以收藏常去的地点,方便下一次快速找到熟悉的摊位。

图片文件不会直接作为业务数据散落在业务表里,后端通过独立的文件服务和私有对象存储管理上传内容。

4. 限时优惠券

优惠券是项目中连接消费者和摊主的一条重要业务链路。摊主可以配置优惠模板,消费者在营业会话中领取一次性优惠券,到摊后通过六位数字码或二维码完成核销。

优惠券有明确的生命周期:领取、有效、已核销、已过期或其他不可用状态。项目还提供过期任务,避免已经失效的优惠一直停留在可用状态。

优惠券码和二维码 Token 不直接保存明文,而是保存 HMAC-SHA256 摘要。核销时对用户提交的内容进行确定性校验,并结合幂等处理防止同一张券被重复核销。核销接口还支持请求幂等键,网络重试不会把一次消费变成两次消费。

5. 举报功能

如果消费者遇到虚假宣传、价格问题或其他不符合平台规则的情况,可以提交举报,并上传证据图片。举报窗口与营业会话或优惠券的结束时间关联,避免无限期提交历史投诉。

举报不是简单的“写一条文本”,而是进入后续的运营治理流程:运营人员查看举报和证据,做出处理决定,摊主可以针对决定提交申诉。

二、摊主端:把一次出摊变成一个可管理的营业会话

1. 摊主入驻申请

普通用户不能直接创建摊位并立即对外展示,而是先提交摊主入驻申请。申请中包含摊位名称、描述和分类等信息,运营人员审核通过后,用户才获得相应的摊主管理能力。

这一步把“用户身份”和“摊主资格”分开,避免任何登录用户都可以创建虚假摊位。

2. 摊位模板和营业会话

摊主可以维护自己的摊位信息,包括名称、介绍、分类和图片。真正面向消费者展示的营业状态,则通过营业会话表达。

一次营业会话通常包含:

  • 营业地点和地图坐标
  • 开始时间与结束时间
  • 当前营业状态
  • 关联的优惠模板
  • 是否允许续期以及续期次数

地点选择支持地图选点和逆地理编码。后端通过腾讯位置服务把坐标转换成用户更容易理解的地址信息,同时保留坐标用于附近发现。

这样设计的好处是,摊位资料可以长期存在,而每次出摊的地点和时间可以独立变化。摊主不需要每天重新创建一个全新的商家档案,只要发布一次新的营业会话即可。

3. 优惠模板与现场核销

摊主可以提前配置“满额减免”等优惠模板,在发布营业会话时选择是否启用。消费者领取优惠后,摊主可以扫码或手动输入六位券码完成核销。

核销结果需要立即反馈给摊主,并且必须具备防重复能力。后端通过状态检查、摘要校验、事务和幂等键共同保证这条链路的正确性。

4. 举报查看与申诉

摊主工作台可以查看针对自己摊位的举报。当运营人员认定违规并产生信用处置后,摊主可以提交申诉,运营人员再进行复核。

项目把举报、证据、申诉、信用事件和处罚期限拆成独立领域对象,避免把治理逻辑塞进一个越来越复杂的摊位表中。

三、运营控制台:让平台能够审核和治理

运营控制台使用 Vue 3、TypeScript、Vite、Vue Router、Pinia 和 Element Plus 构建,面向平台运营人员。

控制台主要包含以下功能:

1. 运营概览

概览页面聚合当前平台状态,包括正在营业的会话、活跃市场、待审核摊主申请、待处理举报和申诉,以及优惠券核销统计等数据。

2. 摊主申请审核

运营人员可以查看摊主申请,执行通过或驳回操作。审核动作会记录审计日志,便于后续追溯谁在什么时间处理了哪一条申请。

3. 市场和建议管理

运营人员可以维护固定市场、设置经纬度,并审核用户提交的市场建议。地图坐标选择由 MapCoordinatePicker 等组件承载,减少手工输入坐标造成的错误。

4. 举报、申诉与信用处置

运营人员可以查看举报详情和证据图片,对举报做出认定或驳回;对于摊主提交的申诉,可以重新核查原始证据和处理理由。

信用模块采用时间窗口计分。项目按九十天窗口累计违规事件,达到阈值后可以暂停摊主的优惠推广资格。信用事件和处罚动作独立记录,不直接覆盖原始举报数据。

5. 审计和报表

运营控制台提供审计日志查看和报表能力。运营动作至少需要关联操作者、动作、目标对象、原因、请求 ID 和时间等信息。这样出现争议时,可以还原一次完整的业务处理过程。

四、整体技术架构

项目采用“原生微信小程序 + 模块化 Spring Boot 单体 + Vue 运营控制台”的架构。

微信小程序(原生 TypeScript + WXML + WXS)
        │ HTTPS / Bearer Token
        ▼
Spring Boot 3.3.13 API(Java 21)
        │
        ├── auth          微信登录、设备绑定
        ├── security      JWT、刷新令牌、限流
        ├── discovery     附近和视野内摊位发现
        ├── market        市场和用户建议
        ├── stall         摊位模板、图片、收藏
        ├── business      营业会话和过期处理
        ├── promotion     优惠模板、券码和核销
        ├── trust         举报、申诉和信用分
        ├── vendor        摊主申请和审核
        ├── storage       MinIO 文件存储和签名 URL
        ├── map           腾讯位置服务适配
        └── admin         运营概览、审核、审计和报表
        │
        ├── MySQL 8:业务数据和空间查询
        ├── MinIO:私有对象存储
        └── 腾讯位置服务:逆地理编码和地址解析

Vue 3 运营控制台 ───────────────► Spring Boot Admin API

1. 后端:按领域组织的模块化单体

后端没有把所有代码放在一个大 Controller 或 Service 中,而是按领域划分为 auth、stall、market、discovery、business、promotion、trust、vendor、storage、security 和 admin 等模块。

模块化单体保留了单体应用部署简单、事务边界清晰的优点,同时让业务边界在代码层面可见。对于当前规模的 MVP,不需要一开始就把每个领域拆成独立微服务;先把领域职责和跨模块调用关系整理清楚,更有利于后续演进。

数据访问层使用 MyBatis-Plus 和 MyBatis XML。通用 CRUD 可以使用 MyBatis-Plus,复杂的附近查询、市场查询、举报详情和后台统计则保留在领域 XML 中,方便精确控制 SQL。

2. 数据库:MySQL 8 与空间数据

MySQL 8 保存用户、摊位、市场、营业会话、优惠券、举报、申诉、信用事件、审计日志和文件对象等业务数据。

摊位和市场位置使用支持 SRID 4326 的 POINT 数据。附近发现不是把所有摊位查出来后再由 Java 逐条计算距离,而是尽可能利用数据库空间查询完成范围筛选和距离排序,减少无效数据传输。

Flyway 负责数据库迁移,让初始表结构和后续变更都可以被版本化管理。MyBatis 的 UUID 类型处理器负责 Java UUID 与数据库字段之间的转换。

3. 小程序:原生 TypeScript 按业务拆分

小程序的主 Tab 包含附近、市场、优惠和我的四个入口;详情页、举报、摊主入驻、收藏和摊主工作台等页面通过分包组织。

services 目录按 API 领域拆分认证、定位、文件、发现、市场、优惠、个人资料和请求封装。摊主端又单独提供摊位、营业会话、优惠模板、扫码核销、媒体和申诉等服务。

这种组织方式让页面主要负责交互和状态展示,网络请求与数据类型集中在服务和 types 目录中,后续更换接口实现时不会把修改扩散到所有页面。

4. 安全:登录、令牌和敏感数据分层保护

用户端使用微信登录获取身份,后端签发 JWT,并配合刷新令牌维持会话。BearerTokenFilter 从请求中解析访问令牌,SecurityConfig 负责接口访问控制。

项目没有把所有安全问题都归结为 JWT:

  • 刷新令牌支持存储和轮换,降低长期凭证泄露的影响。
  • 重要接口提供限流能力。
  • 手机号使用 AES-256-GCM 加密保存,并保留独立的 HMAC 索引用于查询。
  • 密码类凭证使用 BCrypt。
  • 优惠券码和 QR Token 只保存 HMAC-SHA256 摘要。
  • 生产配置守卫会阻止不安全配置直接启动。
  • 全局异常处理统一返回错误结构,并通过请求 ID 关联日志。

安全设计的重点是让不同类型的数据使用不同的保护方式:需要恢复的手机号使用可解密的加密,需要比较的券码使用不可逆摘要,需要短期访问的文件使用签名 URL。

5. 文件:MinIO 私有对象存储

摊位图片和举报证据通过文件服务上传到 MinIO 私有桶。业务数据库只保存文件对象的元数据和关联关系,实际访问通过短时签名 URL 完成。

私有桶、随机对象键、文件用途校验和过期清理任务共同降低了文件被越权访问或长期堆积的风险。

五、一次“发布营业会话”的完整过程

把一次摊主操作串起来,可以看到各个模块如何协同:

  1. 摊主在小程序填写地点、营业时间和优惠模板。
  2. 小程序通过地图服务选择坐标,并请求逆地理编码地址。
  3. 请求携带 Bearer Token 到达 Spring Security 过滤器。
  4. 业务服务校验当前用户确实拥有该摊位的管理权限。
  5. 服务检查营业时间、地点、优惠模板和当前会话状态。
  6. 事务内创建营业会话及其关联的优惠信息。
  7. 记录业务动作和请求 ID,必要时保存审计日志。
  8. 附近发现模块在空间查询中筛选出正在营业的会话。
  9. 消费者看到摊位并领取优惠券。

这条链路说明了为什么项目要同时存在 auth、business、discovery、promotion、map 和 audit 等模块。一个看似简单的“开摊”按钮,背后实际上包含身份、权限、位置、时间、优惠和治理多个边界。

六、项目明确不做什么?

当前版本是 MVP,明确不包含支付、订单、配送、购物车、Redis、消息队列和腾讯云 COS。

这不是功能不完整,而是范围控制。小城摊位的第一阶段问题是“能不能被附近的人发现,并建立可信的营业和优惠闭环”,而不是先复制大型电商平台的交易链路。

把不做的事情写清楚,同样是架构设计的一部分。它可以避免数据库、接口和页面过早承担不属于当前产品阶段的复杂度。

七、如何验证这套系统?

后端使用 JUnit 5、Spring Boot Test,并尽量使用真实 MySQL 风格的测试环境验证空间查询和业务约束。测试覆盖微信认证、摊主市场流程、附近发现、优惠券核销、举报申诉、信用处理和管理员操作等关键链路。

运营控制台使用 Vitest、Vue Test Utils 和 jsdom,测试路由守卫、认证状态、API 模块、状态标签、原因对话框和格式化工具等前端行为。

仓库还提供可重复运行的验证脚本,以及包含 MySQL 和 MinIO 的 Docker Compose 配置,方便在干净环境中进行启动和集成验证。

结语

小城小摊没有把“数字化”理解成增加更多菜单,而是从摊位经营的实际节奏出发,把几个关键动作连接起来:

  • 消费者能找到正在营业的附近摊位。
  • 摊主能用营业会话表达一次真实出摊。
  • 优惠券可以领取、核销、过期且不能重复使用。
  • 市场、摊位和图片有清晰的数据归属。
  • 举报、申诉、信用和审计形成治理闭环。
  • 微信小程序、后端领域模块和运营控制台各自承担清晰职责。

这套架构的价值不在于技术名词数量,而在于它把一个小城摊位场景中最重要的业务边界落实到了代码、数据库、权限和测试里。