ARTICLE DETAIL

资讯详情

深耕郑州网站建设与运营推广的一线实战洞察。

Flask+微信小程序搭建社区团购系统:从架构到部署实战

Flask+微信小程序搭建社区团购系统:从架构到部署实战 小区团购这个赛道这两年被互联网大厂打得火热但真正落地到单个小区、单条街道反而还是本地化的小程序最实用。我接到的这个需求很典型物业或者社区团长想用一个微信小程序承接业主的日常生鲜、日用品采购后端要能管商品、管订单、管配送还要能撑住每天几百单的峰值。技术栈定得干脆利落Python Flask 做服务端微信小程序做 C 端这套组合做社区团购开发速度快、维护成本低非常适合中小型项目的起步。这篇文章我把整个系统的设计思路、核心代码、部署要点和踩坑记录都摊开讲。适合正在做类似小程序商城、社区电商、校园订餐等项目的开发者参考也适合打算用 Flask 快速落地一个带微信小程序前端的产品经理或独立开发者拿去评估工作量。1. 项目整体设计为什么是 Flask 而不是 FastAPI先说结论Flask 不是性能最强的但它是这个场景下最稳的选择。社区团购系统的并发量通常不大一个小区几千户人家高峰期也就是早上下单那一个小时QPS 能到几十已经不错了。Flask 同步模型配合数据库连接池完全扛得住。对比 FastAPI它确实有异步性能优势但异步带来的代码复杂度、数据库驱动选型限制比如 SQLAlchemy 异步版本和同步版本 API 不一样对团队协作和后期维护都不太友好。尤其是项目里如果参杂了经验不等的开发者Flask 的同步写法几乎零学习成本出问题也好排查。这里我做一个技术选型对比方便你根据自己团队的实际情况判断维度FlaskFastAPI上手难度低文档概念少中需要理解异步性能上限同步为主够用异步高并发优势明显生态成熟度高第三方库齐全较新部分库需适配项目维护代码直观易排查异步调用链相对复杂适合场景中小规模业务系统API 服务、高并发微服务架构上我采用的是前后端完全分离微信小程序只管界面交互和调用接口Flask 只提供 JSON API。小程序端不需要直接访问数据库所有数据操作都走后端接口。这么做的好处是以后想扩展管理后台、运营后台直接复用同一套 API 就行不用再为 Web 端单独写一套服务。整体角色分成三种普通用户业主、团长小区组织者、管理员平台运营三者的权限在后端通过装饰器统一校验。系统核心模块包括用户认证、商品管理、购物车与下单、拼团逻辑、订单状态流转、配送提货管理、团长佣金结算、优惠券与积分。这些模块在后端对应着清晰的蓝图Blueprint划分前端对应小程序页面目录。我习惯在项目一开始就把蓝图分好后面加功能不会乱。2. 微信小程序端页面结构与登录逻辑拆解小程序端我采用的是原生开发没用 uniapp。原因很直接这个项目不要求同时发布到支付宝小程序、抖音小程序原生开发性能最好、调试最方便而且微信生态的 API登录、支付、手机号授权原生支持最完整。uniapp 虽然跨端但每次微信更新 API适配都有滞后踩过的坑都懂的。页面结构划分如下pages/index首页商品列表、今日推荐、轮播图pages/goods/detail商品详情加入购物车、立即购买pages/cart购物车改数量、结算pages/order/confirm确认订单页选地址、选配送方式pages/order/list订单列表按状态筛选pages/user个人中心我的优惠券、积分、团长申请入口pages/group/detail拼团详情邀请好友拼团登录逻辑是微信小程序的核心门槛。社区团购这种业务用户必须绑定手机号否则团长没法联系取货。微信小程序获取手机号需要用户点击授权按钮然后后端通过 code 换取手机号信息。注意这里有个关键点传统的wx.getUserInfo已经拿不到真实手机号了必须在页面里放一个button open-typegetPhoneNumber用户点击后触发的回调里会带上code这个 code 传给后端后端再调微信接口换取手机号。这个 code 是一次性的五分钟内有效后端拿到后要立即使用。后端接口设计为/api/auth/login接收小程序端传来的code这是用户登录凭证和phoneCode这是手机号授权凭证。前者用于code2Session换 openid后者用于getPhoneNumber换真实手机号。两条链路都走通了才算是完成注册登录。我建议把 openid 和手机号分开存储用户表主键用自增 IDopenid 加唯一索引手机号加唯一索引。未来如果接入 App 端用户就可以用手机号直接登录而不依赖微信。小程序端的请求封装我统一走了一个request.js工具模块每次请求自动带上token后端返回401时全局跳转到登录页。另外做了响应拦截后端业务错误码比如库存不足、拼团人数已满统一以{ code: 40001, msg: xxx }格式返回前端showToast提示。这样做的好处是业务错误和 HTTP 错误分开处理排查问题的时候很清爽。3. Flask 后端项目结构与数据库建模后端项目结构是我一直沿用的工厂模式结构清晰适合持续迭代app/ __init__.py # 应用工厂初始化扩展 config.py # 配置项数据库、Redis、密钥 models/ # SQLAlchemy 模型 user.py goods.py order.py group.py apis/ # 蓝图路由 auth.py goods.py order.py group.py admin.py utils/ # 工具函数JWT、微信请求、分页 jwt_utils.py wx_utils.py services/ # 业务逻辑层避免视图函数庞杂 order_service.py group_service.py commission_service.py run.py # 启动入口 requirements.txt数据库设计我用的是 MySQL引擎 InnoDB表结构以订单和商品为核心。以下这几张表我重点说一下设计思路。用户表字段id, openid, unionid, nickname, avatar, phone, role, status, created_at。role字段用整数表示 0 普通用户、1 团长、2 管理员。这个字段很重要决定了用户在小程序端能看到什么入口比如团长的核销页面、佣金提现页面。商品表字段id, title, cover, images, price, original_price, stock, sales, status, sort, created_at。社区团购的商品有季节性比如时令水果上架、下架频繁所以status字段必须支持上下架。价格统一用Decimal类型千万不能用 float尤其是涉及金额计算float 的精度问题会带来对账差额。stock字段每次下单扣减时要在 SQL 层面做原子操作UPDATE goods SET stock stock - 1 WHERE id ? AND stock 0这样可以避免并发下超卖。订单表是核心字段包括id, order_no, user_id, group_id, total_amount, pay_amount, discount_amount, status, address_snapshot, remark, pay_time, created_at。order_no用日期加随机序列生成比如20250110123456789001保证唯一性。address_snapshot是收货地址快照下单时把地址信息直接冗余进订单表防止用户后续修改地址影响历史订单。status用整数表示状态机0 待支付、1 已支付待成团、2 已成团待配送、3 配送中、4 待提货、5 已完成、6 已取消。拼团表group_order专门管理团次id, group_no, goods_id, leader_id, status, total_num, current_num, start_time, end_time。一个团对应一个商品发起人成为团长其他人参团加入。current_num字段记录当前参团人数每次有人参团都要在一个事务里同时更新current_num和创建订单用行锁防止并发问题。这里我实际画了一张表关系图开发时对照着写模型效率很高用户 1 ──── N 订单 商品 1 ──── N 订单 团次 1 ──── N 订单 商品 1 ──── N 团次4. 核心业务逻辑下单、成团、配送与佣金社区团购和普通电商最大的区别就是所有交易围绕团来组织。我拆成三个核心流程来讲分别是用户下单流程、团长核销流程、佣金结算流程。先看用户下单。用户在小程序选择商品后创建订单后端order_service.create_order()方法会做这几件事校验商品状态和库存、计算金额含优惠券抵扣、生成订单号、扣减库存、如果选择的是拼团模式还要创建或加入一个团次。整个操作我用一个数据库事务包住防止出现订单创建成功但库存没扣成功的情况。代码结构大致是这样的transactional def create_order(user_id, goods_id, quantity, group_idNone, coupon_idNone): goods Goods.query.filter_by(idgoods_id, status1).first() if not goods: raise BizException(40001, 商品不存在或已下架) if goods.stock quantity: raise BizException(40002, 库存不足) amount goods.price * quantity if coupon_id: amount apply_coupon(user_id, coupon_id, amount) order Order.create(...) goods.stock - quantity db.session.add(order) if group_id: join_group(group_id, user_id, order.id) return order注意这里必须用SELECT ... FOR UPDATE对商品行加锁避免多个用户同时买最后一个库存时发生超卖。这个场景在社区团购里太常见了尤其是秒杀类商品不加锁绝对出事。再看成团逻辑。我的设定是拼团有效期为 24 小时过期未成团自动退款。这个功能用定时任务扫描订单状态每 10 分钟跑一次查出所有已支付但未成团且已超时的团次把它们全部置为失败关联订单退单个用户。这里用 Flask 的扩展APScheduler实现的定时任务部署在应用进程内部。考虑到后续可能要扩展我把退款逻辑放到了services/refund_service.py方便以后接微信支付退款接口时直接换实现。团长核销是线下交付的关键环节。用户到小区提货点取货团长的微信小程序里有一个核销页面输入用户订单号或者直接扫描小程序端的取货码调/api/group/verify接口接口里校验该订单是否属于该团长管理的团次以及订单状态是否为待提货满足条件则改为已完成。这个流程一定要在服务端把关不能只在小程序端做判断否则恶意请求可以直接伪造状态。佣金结算我采用的是按订单比例抽成。团长发起一个团团内所有成交订单包括自己的都按预设比例比如 5%计入佣金账户。结算做到实时累计、每周提现。数据库里有张commission_log表记录每笔订单给团长带来的佣金变动。提现时生成提现记录打款走线下转账后由管理员在后台确认到账更新状态。这套流程虽然原始但最安全不会因为微信支付商户号的各类限制而卡住。5. 实操部署从本地联调到正式环境开发完本地跑通是第一步真正上线部署是另一个坎。我完整走了一遍把步骤和关键配置都记录下来。5.1 本地环境准备Python 版本我用的 3.8Flask 2.2.5SQLAlchemy 1.4.47。虚拟环境用venv创建依赖安装直接python -m venv venv source venv/bin/activate # Windows 是 venv\Scripts\activate pip install flask flask-sqlalchemy flask-cors pymysql pymysql[rsa] requests PyJWT APScheduler数据库我用 MySQL 8.0本地装好之后创建一个空库启动应用时写了个简单的初始化脚本来自动建表。开发阶段我用的是flask run默认的开发服务器回显错误方便调试。但前端小程序请求有个天然的限制必须使用 HTTPS 域名不能直接请求http://localhost:5000。所以我建议本地联调时用微信开发者工具的不校验合法域名选项省去临时配置等部署到测试环境再用真实域名联调。5.2 服务器部署服务器我用的是 Linux Nginx Gunicorn 的组合。Flask 自带的服务器连生产环境想都不要想并发稍微上来就一堆 timeout。Gunicorn 配置 4 个 worker每个 worker 开 2 个线程公式基本是CPU核心数 * 2 1这里我稳妥选了 4 worker*2 threads。Nginx 做反向代理静态资源小程序上传的图片直接用 Nginx 服务后端只处理 API 请求。Nginx 核心配置server { listen 443 ssl; server_name your.domain.com; ssl_certificate /path/your.pem; ssl_certificate_key /path/your.key; location /api/ { proxy_pass http://127.0.0.1:8000; proxy_set_header Host $host; proxy_set_header X-Real-IP $remote_addr; proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; } }HTTPS 证书我用的是腾讯云免费 SSL一年一续。微信小程序强制要求所有请求域名必须是 HTTPS并且要在小程序后台配置 request 合法域名。这里有个常见坑调试时容易忘记把新域名加进白名单导致体验版白屏。加完之后不是立刻生效有个生效时间所以建议提前配置不要到评审前才想起。数据库上线后的优化也很关键。订单表在几千单之后普通查询就开始变慢这时候要给关键字段加索引order.user_id、order.group_id、goods.status、group_order.status。另外order_no也要有唯一索引防止业务异常下重复单号。5.3 微信支付对接如果要做在线支付需要申请微信支付商户号这需要营业执照个人主体小程序无法申请。社区团购场景比较特殊很多是预充值或线下取货时付款的模式所以我把支付部分做成了双轨支持线上微信支付也支持货到付款团长收款。线上支付的流程是小程序端调用wx.requestPayment后端通过微信支付 API 生成预支付订单用户支付后微信回调通知后端后端验签后更新订单状态。这个回调接口必须是公网可访问的 HTTPS POST 接口Nginx 需要放行不要加鉴权。校验签名用微信支付 SDK秒级完成代码不算复杂但流程一定要对。我实际对接的时候踩过一次回调重复通知的坑微信支付回调在极端情况下会重试多次如果后端处理逻辑没有做幂等订单状态会被重复更新严重时会把已完成订单置回待支付。解决办法是在回调处理里先查询订单状态只有当前状态待支付才允许流转到已支付。6. 常见问题与排查技巧实录我挑几个实际开发调试中遇到的高频问题做成速查表你照着排查能省很多时间。问题现象可能原因排查方法小程序请求接口报 502后端服务挂了或未启动检查 Gunicorn 进程、Nginx 错误日志登录一直弹窗失败code2Session返回异常appid/secret 不匹配在微信后台核对 AppID打后端日志看微信返回手机号拿不到open-typegetPhoneNumber用的不对或后端未及时使用 code确认按钮属性确认 code 未过期5 分钟订单库存超卖并发扣减未做行锁或原子 SQL检查 SQL 是否WHERE stock 0必要时加事务隔离支付回调更新状态异常回调重复通知未做幂等处理确认回调处理里先查状态再更新状态机严格流转拼团人数已满还允许下单成团人数校验不严参团时加行锁SELECT FOR UPDATE校验 cur_num total_num跨域请求失败Flask 未配置 CORS 或 Nginx 未放行开发阶段用 flask-cors线上同域部署一般不存在跨域定时任务重复执行多 worker 都跑了 APScheduler用 Redis 分布式锁控制或单独开一个 scheduler 进程这里单独说一下微信小程序登录获取手机号的最新变化。微信官方开放了新的获取手机号方式是通过phoneNumberCode换取而不是像旧版本那样直接返回明文手机号。小程序端点击授权后调用wx.login拿到 code 和手机号授权 code后端拿着这两个 code 分别去微信接口换取 openid 和手机号。一定要记得phoneNumberCode 是一次性的使用一次后立即失效如果后端接口报错导致没有成功换取用户需要重新点击授权。另一个容易忽略的点是微信小程序顶部导航栏高度。在社区团购这种偏工具型的小程序里团长需要频繁扫码核销如果导航栏布局错位体验很受影响。iPhone X 及以后的机型有安全区小程序自定义导航栏时要用wx.getSystemInfoSync()里的statusBarHeight和菜单按钮位置来动态设置。很多开发者图方便用默认导航栏但内容页一旦沉浸式处理顶部高度不对就会按钮点不到。再有就是 Charles 抓包调试小程序。小程序在真机上默认不走系统代理抓包需要在微信开发者工具中打开不校验合法域名以及设置代理。Charles 抓 HTTPS 包时要安装证书并在手机上信任该证书。抓包对排查接口参数问题很有帮助尤其是请求微信支付的时候看真实回调字段是最直接的。7. 微信小程序端核心代码实现片段最后我把小程序端的几个核心页面代码片段贴出来尤其是商品列表和购物车这两个高频操作页面可以直接抄作业。商品列表页使用wx.request请求后端商品接口需要处理加载状态和下拉刷新用onReachBottom做触底加载分页。我封装了一个分页请求函数function loadGoods(page 1) { wx.showLoading({ title: 加载中 }); wx.request({ url: https://your.domain.com/api/goods/list, data: { page, size: 10 }, success(res) { const { list, totalPages } res.data.data; that.setData({ goodsList: page 1 ? list : that.data.goodsList.concat(list), page, totalPages, loading: false }); }, complete() { wx.hideLoading(); } }); }购物车的难点在于本地缓存和后端同步。我的方案是购物车数据存本地wx.setStorageSync(cart, cartList)每次修改数量后同步更新本地这样用户未登录也可以把商品加到购物车登录后再提交订单时把这些 item 传到后端由后端创建订单并清空本地购物车。这种方式交互成本低、体验流畅。老话说得好小程序项目做得好不好一半看页面交互一半看后端接口设计。接口字段命名规范前后端一致联调效率会高出很多。我的习惯是用snake_case统一命名JSON 返回的结构固定为{ code: 0, msg: success, data: {} }业务错误时code为非 0前端根据code提示对应文案。避免 HTTP 404/500 满天飞用户看到白屏和乱码体验极差。8. 部署上线之后的运维心得系统上线稳定运行一个月后我复盘了几个关键问题。最要命的是数据库连接池配置。Flask-SQLAlchemy 默认的连接池参数在小流量没问题一旦订单高峰期并发连接超过了pool_size max_overflow就会出现MySQL server has gone away。我给的配置参考SQLALCHEMY_ENGINE_OPTIONS { pool_size: 20, pool_recycle: 3600, max_overflow: 10, pool_pre_ping: True }pool_pre_ping非常重要每次连接前检查连接是否有效避免 MySQL 因为wait_timeout主动断开空闲连接后应用还拿着失效连接去查询。日志这块同样不能省。我在 Nginx 和 Gunicorn 层面都开启了访问日志并且用logging模块把业务日志写到按天滚动的文件里。业务日志里必须包含order_no、user_id、action三个关键字段这样用户反馈订单问题时搜索order_no能直接定位完整的操作链路。没有日志的排查等于大海捞针。最后再分享一个小技巧也是我踩了几次坑之后总结的社区团购的订单状态机一定要用整数状态码前后端都维护一份常量定义避免用中文或者简写字符串。比如ORDER_STATUS_PAID 1这样不管是数据库查询、接口返回、还是报表统计都是统一口径。如果一开始用字符串paid后面又要加状态很容易出现大小写不统一、新旧值混用的问题。我开发这个系统的整体感受是Flask 加微信小程序这套组合虽然看起来很常规但它确实用最小的复杂度解决了社区团购的核心问题。项目上线后除了数据库连接池调整过一次配置其余部分都运行得很稳。如果后续你要扩展多小区、多团长模式只需要在团次表和商品表再加一个community_id字段把数据隔离做好就行底层的订单和支付流程基本不用动。
返回列表