上个月一位做社区团购的团长朋友跟我抱怨说还在用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-typegetPhoneNumber的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) ) ENGINEInnoDB DEFAULT CHARSETutf8mb4 COMMENT用户表;订单表需要重点说明几个字段group_buy_id关联拼团记录order_status控制状态机流转pay_amount和refund_amount分别记录支付金额和已退款金额。pickup_code是核销码为了防碰撞加了唯一索引。还有pickup_point_id记录自提点方便团长端按自提点聚合统计今日需要核销的订单。拼团表有两个容易踩坑的字段group_type区分是“团长发起团”还是“用户自发团”joined_num记录当前参团人数。我在更新拼团人数时没有用常规的“先查后改”而是用UPDATE语句自增1UPDATE 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。为了方便我识别可以备注“社区团购源码”。源码本身是学习交流用途开箱即用但请尊重原创不要直接商用或二次分发。我在实际跑这套系统时最大的体会是技术层面的难点其实都不算难点真正复杂的是业务规则。库存、支付、佣金、核销每一个环节抠细节都可能多花好几天。但只要把订单状态机、库存扣减、事务边界这三件事想清楚了整个项目的骨架就很难晃。后续我计划把多小区分站、定时群接龙提醒、团长分销二级佣金这几个功能补上如果你有更好的思路欢迎在获取源码之后一起交流。