☰
基于微信小程序与Spring Boot的社区团购系统实战开发
2026/10/14 7:53:37 网站建设 项目流程

上个月,一位做社区团购的团长朋友跟我抱怨,说还在用Excel表格记账,每天早上在微信群里接龙,晚上对账对到头疼。这几乎是社区团购刚起步阶段的真实写照:订单靠接龙、收款靠私聊、货品靠人工清点,等到单量上了100单,光靠人力根本扛不住。当时我就在琢磨,与其劝他花钱买SaaS,不如自己动手写一套社区团购系统,前端用微信小程序,后端用Spring Boot,把接龙、支付、核销、提现全部串起来。项目从设计到跑通花了大半个月,现在源码整理好了,放到文末,大家按需要自取。

1. 项目概述与需求拆解

1.1 社区团购的痛点与小程序优势

社区团购本质上做的不是“卖菜”生意,而是“半熟人信任+集中采购”的生意。团长在小区群里发起接龙,群友下单,第二天货送到自提点,团长负责分发。单量少时,这套模式很顺畅;单量一上来,问题就全部暴露出来了。

最大的痛点是订单数据散落在微信群聊里。接龙信息被聊天淹没,经常有人跟错楼层、漏单、改单,团长对着手机翻聊天记录,效率低得让人崩溃。其次是收款和对账,有人用微信转账,有人下单选了货到付款,还有人临时取消,收入支出全凭脑子记,月底利润根本算不清。库存更是完全靠经验:昨天土豆卖爆了,今天进多少?没有数据,只能拍脑袋。售后也缺乏统一入口,客户在群里吼一句“菜坏了”,消息很快被新消息顶掉。

这些痛点决定了社区团购天然适合做成小程序,而不是H5或独立App。原因很直接:微信小程序在聊天和朋友圈场景下的打开路径最短,不需要安装,点一下就能进;手机号授权和微信支付直接打通,用户从浏览到付款全流程不用跳出微信;最关键的是拼团、邀请、分享自带社交裂变属性,小程序内置的转发按钮可以让老用户带来新用户,获客成本远低于独立App。

1.2 系统功能全景与适用人群

这个项目不是一个只做了“下单买菜”四个字的Demo,而是按真实运营场景拆成了三个端:用户端小程序、团长端小程序、管理后台。

用户端包含:首页轮播图、附近自提点、商品分类、商品详情、发起拼团/参团、确认订单、微信支付、订单状态查询、售后申请、优惠券领取与核销。

团长端包含:今日待核销订单、核销码扫描/输入核销、订单收益统计、提现申请、团长佣金明细。

管理后台包含:商品管理(上下架、库存、团购价格)、订单管理(全状态筛选、发货、退款)、团长管理(入驻审核、佣金比例配置)、用户管理、营销配置(优惠券、满减)、配送设置(自提点经纬度、配送范围)。

适合参考这套系统的人群主要有三类:一是正在做社区团购、不想被SaaS平台抽成、想自己掌控数据的创业者;二是学过Java或小程序开发、想找一个完整实战项目练手的同学,这套系统麻雀虽小五脏俱全,登录、支付、订单、拼团、提现都有;三是接外包的技术团队,框架和基础模块可以直接复用,省掉从零搭脚手架的功夫。

2. 整体架构与方案选型

2.1 技术栈选型与理由

前端我选了原生微信小程序,没有用uni-app。社区团购的逻辑多、页面状态复杂,原生小程序在调试器里看WXML和页面的层级关系最直观,踩坑时排查路径最短。如果后续真的有打包成Android/iOS/鸿蒙的需求,再迁移到uni-app也不迟,因为页面拆分和接口设计都是按组件化、模块化做的,迁移成本可控。

后端用的是Spring Boot 2.7 + MyBatis-Plus + MySQL 8.0 + Redis。Spring Boot是Java后端里最主流的选择,网上资料丰富,遇到问题搜索一下基本都有答案。MyBatis-Plus比原生MyBatis省事不少,通用Mapper和LambdaQueryWrapper让CRUD代码量减少一半,适合快速做业务系统。Redis主要用来扛库存预占、热点缓存、登录态和拼团倒计时。

为什么不选Node或Go?不是不能用,而是这套系统的交付目标是“让人快速看懂、能二次开发”。Java Spring Boot的分层结构清晰,项目中大批学Java的人都能接手。如果你熟悉PHP也可以改成PHP,但业务核心在服务端,前端小程序的代码是通用的。

2.2 前端小程序端设计

小程序端的基础架构我按四个层次拆:页面层、组件层、请求层、配置层。

页面层放tabBar页面和分包页面。社区团购这种项目建议开启分包,因为主包大小限制是2M,而商品图片、详情页、售后页很容易把包体涨上去。我把用户首页、分类、购物车、我的放在主包,把商品详情、拼团页、订单页、售后页放到分包,分包加载速度明显更快。

组件层封装了商品卡片、数量步进器、订单状态标签这些复用组件。尤其是商品卡片,几乎每个页面都要用,如果不做成组件,改一次样式就得全局搜索替换,非常痛苦。

请求层单独封装了一个request工具,统一处理baseURL、token注入和错误码提示。项目里所有接口都走这个请求工具,不用在每个页面重复写wx.request。

配置层放在app.js里,把后端接口域名、环境标识、分享文案抽成配置项。以后切换正式环境和测试环境,只改这一处就可以。

2.3 后端服务端设计

后端按照经典的三层架构来写:controller、service、mapper。这里的核心思想是“接口不直接操作数据库,业务逻辑收口在service层”。刚开始写项目的人容易把SQL塞到controller里,后面一旦要复用业务逻辑,就会发现代码根本没法调。

统一返回结构是必须的。我定义了一个Result对象,所有接口返回固定格式:code、msg、data。前端的request工具里对code做了全局判断,如果code不是200,就弹Toast并中断,不用每个接口都写重复的错误处理。

登录态我用的方案是Redis存储Session信息,而不是JWT。小程序端的session_key本来就必须存在服务端,Redis过期时间天然支持token失效,而且加了个“续期”逻辑:每次请求时如果token剩余有效期不到7天,就自动刷新过期时间,保证用户在活跃状态下不会突然掉线。

2.4 自研系统与现成SaaS的取舍

在动工之前,我也认真研究过市面上现成的社区团购SaaS系统。坦白讲,SaaS的功能确实很全,有的还带供应链货源,我朋友也差点直接付费购买。

但SaaS有几个让人不太舒服的地方:首先是抽成模式,很多平台按交易流水扣点,单量越大,扣得越肉疼;其次是数据不掌握在自己手里,用户手机号、订单明细、商品数据都放在对方数据库里,想导出做二次分析很难;第三是定制化受限,团长想让首页多展示一个“今日特价”区块,SaaS走工单流程可能要等一两个月。

自己写系统虽然前期成本高,但后续的边际成本几乎为零。这套系统我可以随意改首页布局、加营销工具、调整佣金规则,数据导出一键完成,这是自研最大的底气。如果你只是想快速验证社区团购这个模式,不想投入太多时间,那直接买SaaS更划算;如果你想长期经营、把数据当成核心资产,自研绝对是正确的方向。

3. 核心模块与关键实现

3.1 微信登录与手机号授权

微信小程序登录不是直接拿openid做登录,而是分两步:前端调用wx.login拿到临时code,把code传给后端;后端拿着code调微信的code2Session接口,换取openid和session_key。openid是用户在微信生态里的唯一标识,数据库里用openid关联用户,不用再另外做账号注册流程。

手机号授权这里我要多说一句,很多新手还在用老的getPhoneNumber方式,其实微信在2023年之后就逐步收紧了这个接口的权限。现在一般改用open-type="getPhoneNumber"的button组件,用户点击后通过bindgetphonenumber事件拿到加密的手机号数据进行解密,而不是直接返回明文。后端的解密需要依赖session_key,所以登录接口必须保证先执行,再处理手机号授权,顺序反了会导致解密失败。

实操中还有一个细节:很多用户进入首页只是随便看看,不会主动点授权按钮。这时候如果强制要求手机号才能浏览,流失率非常高。我的做法是游客可以浏览商品、查看详情,只有在下单结算时,才弹手机号授权。

3.2 商品与拼团模块

拼团是社区团购的灵魂。商品表里除了普通价格,还要加一个group_price拼团价和一个group_need_num成团人数门槛。用户在商品详情页可以选择“单独购买”或“发起拼团”。

发起拼团的流程是:先创建一条拼团记录,状态为“拼团中”;其他用户参团时,直接往这个记录的团员列表里追加;当人数达到成团门槛时,把拼团状态改成“已成团”,同时触发支付后的订单状态流转。这里需要处理超时问题,我在拼团记录上加了end_time字段,定时任务每隔几分钟扫描一次,发现超时未成团的记录就自动退款,并把状态改成“拼团失败”。

比较容易被忽略的是拼团按钮的动态文案。如果商品已经有人开团,用户进入详情页后,按钮要显示“去参团”而不是“开新团”,并且在页面顶部显示“某某小区邻居正在拼团”。这个功能我用聚合查询实现:查拼团记录表里所有“拼团中”的记录,按时间倒序取最近三条展示。用户点击参团时绑定其中一条,而不是全部都给发起新团,这样成团率会高很多。

3.3 订单与库存扣减

库存扣减是最容易出事故的地方。我看到很多初学项目把扣库存更新成一条update语句,然后直接下单,这在低并发时没问题,一旦活动上线,瞬间就可能超卖。

我用的方案是“预占库存+真实库存”两阶段。用户提交订单时,先检查并预占库存,把可用库存直接减掉,同时记录预占数量;如果用户在15分钟内没有支付,订单自动取消,返回预占库存。支付成功后才正式扣减真实库存,因为支付成功意味着这个商品已经卖出去了。

预占库存阶段对高并发做了保护。用Redis的原子操作命令来扣减预占库存,而不是先查询后更新。扣减前用DECR操作检查剩余量,如果扣完后的值小于0,就说明库存不足,直接返回下单失败。这里的关键在于用Redis的Lua脚本保证“检查+扣减”是原子操作,避免两个请求同时读到剩余库存还有1,然后都扣成了-1的情况。

3.4 支付与订单状态流转

微信支付的接入链路不算长但容易踩坑:前端调wx.requestPayment之前,后端要先拿着订单号调“统一下单”接口,拿到支付参数(时间戳、nonceStr、package等)返回给前端;前端拉起支付组件;微信支付完成后,会往后端配置的支付回调地址发一条异步通知。

最容易栽跟头的地方在回调接口。支付回调是可以重发的,同一笔订单微信可能给你发多次通知,所以回调处理一定要做幂等:先查订单状态,如果订单已经是“已支付”,就直接返回success,不再重复处理。回调里还要验证签名,用微信支付平台证书的公钥对回调报文做验签,防止伪造通知。

订单状态机我在代码里用一个枚举类管理:待支付、已支付待成团、已支付已成团、待确认收货、已完成、已退款、拼团失败。状态转移统一在一个方法里做,只有允许的前置状态才能跳到后面的状态,比如“待支付”只能到“已支付待成团”或“已取消”,不能直接从“待支付”跳到“已完成”。这种收敛式的写法,比在处理逻辑里随手改状态要可靠得多。

退款接口用的是微信支付的退款API。需要注意退款不一定即时到账,微信文档说一般几个小时内到账,所以退款触发的动作是把订单状态改成“退款中”,等退款结果回调后再改成“已退款”。

3.5 团长端与配送核销

团长端做了一个独立的小程序Tab,用role字段区分普通用户和团长。用户申请成为团长时,需要填写小区名称、详细地址、自提点定位,后台审核通过后才拥有团长端权限。

核销是团长端最常用的功能。用户下单付款后会生成一个核销码,我用用户ID和订单ID加密生成一段8位字符。团长端提供“扫码核销”和“手动输入核销码”两个入口。核销成功后就改订单状态为“已提货”,同时给该团长累计一笔佣金。这里我留了一个“撤销核销”的补救接口,防止团长操作失误提前核销,但加了限制:订单在当天内才能撤销,超过一天就不能撤销。

提现逻辑要刻意做得保守。团长的佣金属于平台和团长之间的结算关系,系统里我设计了结算流水表,每个订单完成时自动给团长生成一笔佣金流水,提现申请只能针对“已入账”的流水,不能随意提超额度的钱。每次提现生成一个提现批次,后台审核后走人工转账,同时把流水状态标记为“已完成”,避免重复提现。

4. 数据库设计与核心表结构

4.1 表设计规范

这套系统一共设计了接近20张表,核心业务集中在6张主表上:用户表、商品表、拼团表、订单表、订单明细表、团长结算表。表设计遵循几个原则:每张表都带主键id、create_time、update_time这三个基础字段;金额字段统一用decimal(10,2),不直接用浮点数存储,否则算佣金时很容易出现小数点精度缺失;订单号、核销码这些业务编号设唯一索引,既方便快速查询又能防止重复数据。

订单表是数据量增长最快的表,我在设计时就考虑了分库分表的问题。虽然目前的单表完全够用,但在订单号字段上加了一个order_prefix(时间戳+随机数),将来做水平拆分时可以直接用这个前缀做路由,不用再大规模改代码。

4.2 核心表结构与字段说明

用户表的核心字段如下:

CREATE TABLE `t_user` ( `id` BIGINT NOT NULL AUTO_INCREMENT, `openid` VARCHAR(64) NOT NULL COMMENT '微信openid', `nickname` VARCHAR(64) DEFAULT NULL, `mobile` VARCHAR(20) DEFAULT NULL COMMENT '手机号', `role` TINYINT NOT NULL DEFAULT 0 COMMENT '0普通用户 1团长', `status` TINYINT NOT NULL DEFAULT 1 COMMENT '1正常 0禁用', `create_time` DATETIME DEFAULT NULL, `update_time` DATETIME DEFAULT NULL, PRIMARY KEY (`id`), UNIQUE KEY `uk_openid` (`openid`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='用户表';

订单表需要重点说明几个字段:group_buy_id关联拼团记录,order_status控制状态机流转,pay_amount和refund_amount分别记录支付金额和已退款金额。pickup_code是核销码,为了防碰撞加了唯一索引。还有pickup_point_id记录自提点,方便团长端按自提点聚合统计今日需要核销的订单。

拼团表有两个容易踩坑的字段:group_type区分是“团长发起团”还是“用户自发团”,joined_num记录当前参团人数。我在更新拼团人数时没有用常规的“先查后改”,而是用UPDATE语句自增1:

UPDATE t_group_buy SET joined_num = joined_num + 1 WHERE id = #{groupId} AND joined_num < need_num;

如果受影响行数为0,说明拼团已经满了,不允许再加人。这个策略在并发场景下比“先select再update”要安全得多。

4.3 数据一致性与事务边界

数据一致性是复杂业务系统绕不开的话题。我最注重的是下单和支付这两个事务边界,不能为了“简单”而把不相关的东西塞进同一个事务。

下单场景的事务范围控制在:预占库存、创建订单主表、创建订单明细、记录积分流水。这四个动作要么一起成功要么一起失败,我用@Transactional包起来。但事务里不能包含微信支付接口调用,因为支付是外部IO,网络波动不可控,如果支付接口超时让整个事务回滚,用户的下单数据也会丢。我的做法是:事务只负责生成本地订单,支付是事务提交之后异步发起的动作。

拼团成团和订单状态更新也要放在同一个事务里。因为“已成团”这个状态直接影响后续发货和团长佣金计算,如果成团状态更新了但订单状态没同步更新,团长端看到的就永远是“待成团”,用户也收不到取货通知。

5. 常见问题与排查技巧

5.1 小程序审核被拒

社区团购小程序第一天上架的审核通过率并不高,最常见的驳回理由有两类。一类是类目选择问题。社区团购直接选“电商平台”往往无法通过,需要结合自身的经营资质选择“商家自营-食品”或“购物-自营”等对应类目,同时勾选涉及前置审批的资质项。另一类是功能权限问题,比如申请了获取位置权限但页面没有实际使用场景、申请了相册权限但找不到上传入口,这些都会被判定为“权限与功能不符”。

我自己的规避办法是先过一遍微信平台的“代码审核”预检查工具,再人工模拟整个下单流程,把没有用到的权限全部删掉。选用wx.getLocation时,我会在首页明确展示“定位附近自提点”的引导文案,让审核人员一眼能看出这个权限的用途,审核通过率会明显提高。

5.2 支付回调丢失问题

支付回调偶尔会出现延迟或者没有及时收到的情况,这个不需要慌,微信支付本身提供了“查单接口”。我在定时任务里加了一个补偿机制:扫描所有“已支付待成团”状态下超过10分钟没有更新的订单,主动调用微信支付查单接口,确认支付成功后补更新订单状态。

这里有个细节必须提醒:查单不是查一次就完了,微信支付要求超过一定时间仍未成功时主动停掉重试,避免死循环。我的做法是每隔3分钟查一次,连续查5次未成功就发告警通知后端开发,同时把订单标记为“待人工核查”。用这种“回调+定时对账兜底”的双通道方案,支付状态基本不会出问题。

5.3 配送范围与定位权限

自提点必须有经纬度坐标,用户首页的“附近自提点”按距离排序。经纬度距离我直接用Haversine公式计算,把数据库里的所有自提点坐标取出来,和用户的定位坐标逐一算距离,量小的时候性能完全够用:

public double distance(double lat1, double lng1, double lat2, double lng2) { double radLat1 = Math.toRadians(lat1); double radLat2 = Math.toRadians(lat2); double a = radLat1 - radLat2; double b = Math.toRadians(lng1) - Math.toRadians(lng2); double s = 2 * Math.asin(Math.sqrt(Math.pow(Math.sin(a / 2), 2) + Math.cos(radLat1) * Math.cos(radLat2) * Math.pow(Math.sin(b / 2), 2))); return s * 6371.0; }

如果自提点数量涨到几千个,逐个算距离会慢,这时候建议引入Redis Geo或MySQL空间索引,但初版这样做会拖慢交付节奏,我建议按业务规模来选型。

5.4 高并发下的超卖问题

超卖问题在社区团购里的典型场景是“限量秒杀”。用户涌入瞬间,并发请求可能同时读到库存还有10,然后一起下单,结果卖出了15件。解决方案就是用Redis的Lua脚本做原子扣减:

local stock = tonumber(redis.call('get', KEYS[1])) if stock and stock > 0 then redis.call('decr', KEYS[1]) return 1 else return 0 end

这个脚本的返回值如果是1,再走后续的数据库事务;返回0就直接告诉前端“手慢无”。这里一定要注意的是Redis扣减和数据库库存要保证最终一致,我是在支付成功后同步更新数据库真实库存。如果用户下单后又取消,就回补Redis库存,保证整个链路不出现账实不符。

常见问题速查表

问题现象可能原因排查方向
用户登录后偶尔掉线token过期没有续期检查Redis中token剩余有效期是否小于7天
支付成功但订单还显示待支付支付回调没有收到先查微信支付商户平台回调记录,再验证回调接口幂等性
拼团人数满但仍显示拼团中joined_num更新失败检查update语句的where条件是否带上了joined_num < need_num
团长核销后佣金没到账结算流水未生成或状态异常查订单状态和结算流水表中团长ID对应的记录
下单提示库存不足但实际还有货Redis库存和数据库库存不一致手动重置Redis库存,并增加回补逻辑
首页自提点距离永远一样用户定位失败走了默认坐标检查wx.getLocation的授权状态和模拟器定位设置

6. 源码结构与获取方式

6.1 源码目录说明

整套源码提交前我重新整理过目录,打开就能看懂项目结构。

community-group-buy/ ├── miniapp/ # 微信小程序端 │ ├── pages/ # 页面文件 │ ├── components/ # 通用组件(商品卡片、订单状态标签等) │ ├── utils/ # request封装、工具函数 │ └── app.js # 全局配置 ├── server/ # Spring Boot后端 │ ├── src/main/java/ │ │ ├── controller/ # 接口控制层 │ │ ├── service/ # 业务逻辑层 │ │ ├── mapper/ # 数据访问层 │ │ └── config/ # 全局配置类(Redis、微信支付等) │ └── sql/ # 数据库初始化脚本 ├── admin/ # 管理后台(Vue + Element UI) └── README.md # 运行部署文档

管理后台我用Vue + Element UI做了一个简化版,包含商品管理、订单管理、团长审核和佣金提现审核四个核心页面。如果你不需要管理后台,直接忽略admin目录即可,用户端和团长端的功能依然可以完整跑起来。

6.2 如何快速跑起来

我按照“从零到运行”的顺序整理了一份部署文档,照着做基本半小时就能本地跑通。第一步是把server/sql目录下的初始化脚本导入MySQL,创建数据库和基础表。第二步改application.yml里的数据库连接信息、Redis地址、小程序AppID和Secret。第三步启动后端服务,确认日志输出没有报错。

然后打开微信开发者工具,导入miniapp目录,修改app.js里的接口baseURL为http://localhost:8080,在“详情-本地设置”里勾选“不校验合法域名”。这样就能在开发者工具里跑通完整的注册登录、商品浏览、下单支付流程。如果要用真实的微信支付,必须在微信公众平台配置合法域名和支付回调地址,本地调试只能用沙箱或测试号。

6.3 源码获取方式与后续计划

完整源码已整理打包,包含用户端小程序、团长端小程序、Spring Boot后端、Vue管理后台、SQL初始化脚本、部署文档。获取方式在文末:评论区或私信留一下邮箱,我每天会定时汇总发送,发送时文件名是community-group-buy-full-source.zip。为了方便我识别,可以备注“社区团购源码”。源码本身是学习交流用途,开箱即用,但请尊重原创,不要直接商用或二次分发。

我在实际跑这套系统时最大的体会是:技术层面的难点其实都不算难点,真正复杂的是业务规则。库存、支付、佣金、核销,每一个环节抠细节都可能多花好几天。但只要把订单状态机、库存扣减、事务边界这三件事想清楚了,整个项目的骨架就很难晃。后续我计划把多小区分站、定时群接龙提醒、团长分销二级佣金这几个功能补上,如果你有更好的思路,欢迎在获取源码之后一起交流。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询