ARTICLE DETAIL

资讯详情

深耕编程入门与网站建设的一线实战洞察。

微信小程序+Python校园自动点餐与跑腿系统开发实战

微信小程序+Python校园自动点餐与跑腿系统开发实战 大学食堂一到饭点那个队伍排得真是让人绝望。我当年在学校的时候第四节课下课铃一响从教学楼冲到食堂窗口前已经弯了好几道弯。后来毕业工作了有一次回学校找老师发现食堂排队的情况一点没变。就是这个痛点让我萌生了做一套微信小程序Python校园自动点餐系统带跑腿的想法。所谓带跑腿就是在传统点餐之外把谁去取餐、怎么送到你手上这层也做了。餐做好了骑手接单送到宿舍楼下或者教学楼门口高峰期不用再人挤人。这篇文章不是我拿了什么现成开源项目来介绍而是从我实际做这个系统的过程出发把完整的设计思路、代码结构、核心接口和踩坑点都讲一遍。内容覆盖小程序端uni-app架构、Python后端Flask框架、数据库设计MySQL、跑腿派单逻辑、前后端联调、上线部署等完整环节。适合正在准备毕业设计、想做个真实全栈项目练手、或者真有校园点餐需求想落地的人可以直接照着搭。1. 先算清账再写代码——校园点餐与跑腿需求的三个核心问题做项目最忌讳一上来就写代码。我见过太多人拿了个开源点餐系统改改名字就当作自己的项目最后答辩老师问两个问题就懵了。在动工之前我花了两周时间在校园里蹲点观察学生和食堂阿姨的行为最后总结出三个核心问题这三个问题直接决定了系统的设计方案。1.1 排队的本质是信息不对称不是做餐慢食堂高峰期排长队大多数人以为是出餐速度跟不上。实际上我观察下来食堂窗口的出餐速度并不慢一份盖浇饭从下锅到打包平均40秒到1分钟。真正的问题在于信息不对称学生不知道哪个窗口人少、哪个菜快好了、哪个窗口还在准备配菜。结果就是所有人凭习惯往固定窗口挤冷热不均。所以小程序端的第一版我重点做了两个信息透传功能实时展示每个窗口的排队人数以及每道菜的预计出餐时间。这个数据哪来的不是食堂阿姨手动填的是系统根据每道菜的历史订单数据算的比如红烧肉从下单到出餐平均需要3分20秒加上当前排队的订单数就能估算出新用户下单后大概多久能出餐。这个设计虽然不是什么高深算法但在实际体验中非常有用用户一打开小程序就能看到哪个窗口现在下单最划算。1.2 点餐和跑腿是两套逻辑不能混在一个流程里很多做校园点餐系统的人把跑腿当成点餐的一个附属功能订单里加个是否配送字段就完了。这样做在技术实现上很简单但实际运营起来会发现一堆问题谁去取餐取错了怎么办配送费怎么结算送到哪里怎么联系用户我的做法是将系统拆成两个相对独立又互相协同的模块点餐模块处理从用户下单到食堂出餐的链路跑腿模块处理从出餐到送达的链路。两者通过一个待取餐订单池衔接。点餐模块的终点是食堂把小票打出来、餐做好了系统把这个订单放入取餐池跑腿模块的起点是骑手在取餐池里看到这个订单接单去取。这样设计的好处是某一个模块出问题不会拖累另一个比如食堂出餐慢只是订单晚点进入取餐池不会导致配送流程的数据错乱。1.3 三方角色都要照顾到系统才有人用一个校园点餐系统表面上用户是学生但实际上这是个三方系统。学生要的是便宜、快、准时。食堂要的是省人力、不增加阿姨负担、账目清晰。骑手要的是配送费合理、路线不绕、接单信息明确。我在设计后台时单独给食堂做了一个迷你管理端不需要安装App就一个小程序页面扩展或者PC端网页。食堂能看到实时的订单列表、菜品售罄标记、每道菜今日销量。骑手端则更简化核心就三个页面抢单大厅、我的待配送订单、配送记录。这三方需求列出来后技术方案就清晰了。学生端是核心小程序食堂端走网页版后台骑手端复用小程序的多角色切换。也就是说一个微信小程序通过登录时选择的角色渲染不同的页面。这样整个系统只需要一次开发用户体验上也说得过去。2. 系统架构与技术选型为什么是uni-app加Flask而不是其他组合我接触过不少学生项目技术栈选得很随意有的用了好几种框架每个都只会一点结果系统一跑起来问题层出不穷。技术选型首先要想清楚一个道理项目是给自己用的不是给面试官表演的稳定、能落地、出了问题你能快速定位比名字听起来高级重要得多。我先用一张表说明我的选型然后每一条都解释为什么。模块我的选型常见备选选择理由小程序前端uni-app原生微信小程序、Taro一套代码同时支持微信小程序和H5方便平时调试和演示后端框架Python FlaskDjango、FastAPI轻量上手快项目结构自己掌控数据库MySQLSQLite、PostgreSQL稳定、资料多、后期可无缝上云缓存Redis无存储购物车和热门菜品缓存减轻数据库压力定时任务APSchedulerCelery内置简单应付超时关单、状态推送足够2.1 为什么小程序端不用原生语言原生微信小程序其实不难它的WXML和WXSS跟HTML和CSS非常接近。那为什么我坚持用uni-app最重要的原因是可以跑H5端。这听起来好像没什么了不起但在实际开发中帮了我大忙。校园里的用户不只有微信有的学生可能不想授权微信登录或者辅导员、食堂管理员想在电脑上直接看页面。只要后端接口不变H5端拉起来就能用免去了每次都要打开微信开发者工具调试的麻烦。而且uni-app的开发模式类似Vue写过Vue的人几乎零成本迁移。对于以后想让这个项目升级做多端应用也省了一笔重写的钱。另外组件的丰富度也更高。像点餐系统必备的数量加减器、下拉刷新、滚动菜单联动这类UI组件uni-app的插件市场里能找到很多现成的比原生微信小程序自己手写好几套要省时间。2.2 后端为什么选Flask而不是更重的Django说实话我之前也考虑过Django因为Django自带Admin后台、ORM和完整的用户认证看起来什么都有。但实际评估下来对于这种校园级项目Flask反而更合适。Django太重了它自带的一套东西会限制我按自己的需求去设计表结构和API。比如跑腿派单这种有状态流转的业务Django的Admin后台没法直接满足我还得自己重写视图那自带的那些功能反而成了累赘。Flask不一样它只负责路由和请求响应其他一切自己说了算。我可以清晰地控制每个API按自己的业务逻辑组织代码结构。此外Flask的调试模式对新手非常友好改完代码自动重载报错信息在浏览器里直接显示定位问题速度很快。API文档配合Flask-RESTful或者自己写个简单的装饰器也能搞定。当然如果你本来就非常熟悉Django用它也完全没问题核心是不要跟自己的经验过不去选自己最有把握的。2.3 六张核心数据表先把肚子里的东西定好我给这个系统设计了六张核心表这六张表在项目启动前就定了后面开发中不再大改。这比边写代码边加字段要稳得多。用户表user主键用户ID、微信openid、昵称、头像、手机号、角色学生/食堂/骑手、余额、创建时间。食堂/商家表canteen主键食堂ID、食堂名称、位置描述、营业时间、公告、评分。菜品表dish主键菜品ID、所属食堂ID、菜品名称、价格、描述、图片URL、分类荤菜/素菜/主食/饮料、是否售罄、预计出餐时长。订单表order主键订单ID、用户ID、食堂ID、总金额、订单状态、下单时间、支付时间、出餐时间、取餐时间、送达时间、备注。订单明细表order_item主键明细ID、订单ID、菜品ID、菜品名称、单价、数量、小计。跑腿任务表delivery_task主键任务ID、订单ID、骑手用户ID、接单时间、取餐时间、送达时间、配送费、状态待接单/已接单/配送中/已完成/已取消。有同学可能会问为什么订单表和跑腿任务表要分开而不是直接挂一个配送状态答案在1.2节已经说过两个模块的生命周期不同分开才能独立调度。举个例子一个订单可能是用户自己取餐那就不产生跑腿任务如果某个时段骑手不足订单先做好放在取餐池跑腿任务稍后再建立这样也不会影响点餐主流程的数据完整性。3. 小程序端核心实现从首页到支付一段真实的开发记录前端这块我把微信小程序端的页面拆成了六个主要页面首页、食堂列表页、菜品详情页、购物车页、订单确认页、个人中心。另外骑手端的接单大厅和配送列表是复用个人中心的角色切换实现的本质上还是同一套组件。3.1 目录结构和页面划分用uni-app创建项目后pages.json里注册页面。我的主要配置逻辑是分模块管理页面用户端页面放pages/骑手端页面放pages_rider/食堂管理端页面放pages_admin/。这样代码结构一眼看过去就很清晰哪部分属于哪个角色一目了然。公共组件放在components/API请求封装放在utils/request.js里。一个很关键的细节是tabBar的设置。学生端和骑手端的底部导航项不同但微信小程序原生tabBar在运行中很难动态修改。我的方案是主包放三个tab页首页、订单、我的食堂和骑手管理的入口统统放在我的页面里用角色判断要不要显示。这样既避免了tabBar动态切换的复杂技术问题也简化了用户认知所有入口都从我的进入。3.2 微信登录到后端看似一条线其实有三个坎微信小程序的登录流程官方文档有标准写法wx.login拿code发给后端后端用code加AppID和AppSecret请求微信接口换openid和session_key然后后端自己生成一个token返回给小程序。看起来简单实际踩坑点有三个。第一code是一次性的而且有效时间很短。如果后端处理慢了微信会返回错误你得让用户重新触发wx.login。我一开始没做容错结果用户登录的时候偶尔报错后来把wx.login封装成了一个Promise失败就自动重试一次。第二不要在前端处理openid。很多人为了方便在微信开发者工具里看到已获取到openid就直接把openid存到本地存储下次免登录。这在开发环境没问题但一旦上线openid在后端或数据库里匹配用户时会出现不一致而且有安全风险。正确的做法是前端只把code发给后端后端换来的openid自己存好发给前端的只有自定义的token。第三token要设置有效期。我用的方案是Python的itsdangerous库生成签名token有效期为7天。7天内打开小程序自动登录超过7天让用户重新授权。这个体验比较平滑用户不会频繁被要求登录。我把封装好的登录请求代码放在这里基本可以复用// utils/request.js 节选 const BASE_URL https://yourdomain.com/api function login() { return new Promise((resolve, reject) { uni.login({ provider: weixin, success: (loginRes) { uni.request({ url: ${BASE_URL}/auth/login, method: POST, data: { code: loginRes.code }, success: (res) { if (res.data.code 0) { uni.setStorageSync(token, res.data.data.token) resolve(res.data.data) } else { reject(new Error(res.data.msg)) } }, fail: reject }) }, fail: reject }) }) }对应后端的处理逻辑我贴一段Flask的代码示例方便理解code换openid的流程# app/auth.py 节选 import requests from itsdangerous import TimedJSONWebSignatureSerializer as Serializer APPID your_appid SECRET your_secret WX_API https://api.weixin.qq.com/sns/jscode2session def wx_code_to_session(code): params { appid: APPID, secret: SECRET, js_code: code, grant_type: authorization_code } resp requests.get(WX_API, paramsparams, timeout5).json() if errcode in resp: return None, resp.get(errmsg) return resp[openid], None app.route(/api/auth/login, methods[POST]) def login(): code request.json.get(code) openid, err wx_code_to_session(code) if err: return jsonify({code: 1, msg: err}) user User.query.filter_by(openidopenid).first() if not user: user User(openidopenid, nickname微信用户, rolestudent) db.session.add(user) db.session.commit() s Serializer(your_secret_key, expires_in604800) token s.dumps({uid: user.id}).decode() return jsonify({code: 0, data: {token: token, role: user.role}})3.3 购物车不要放到数据库放Redis就够了购物车这个功能很多初学者习惯设计一张购物车表用户每次点击添加按钮就往表里写记录。但仔细想想购物车是一个临时性的东西用户可能加了一会儿又清空了完全没必要长期存储。更重要的是如果购物车表放在MySQL里每次增删改查都要走数据库事务压力一大接口响应就变慢。而且高峰期食堂订单比较多购物车表还会跟订单表抢数据库连接。我用的方案是把购物车放在Redis里key是cart:{user_id}:{canteen_id}value是一个JSON字符串存的是菜品ID和数量的映射。Redis的读写速度是微秒级而且天然支持过期时间用户超过30分钟不操作购物车自动清空这个体验逻辑也说得过去都三十分钟没下单了之前的购物车内容早就不新鲜了。后端操作Redis这一段也很简单import json import redis r redis.Redis(hostlocalhost, port6379, db0) def get_cart(user_id, canteen_id): data r.get(fcart:{user_id}:{canteen_id}) return json.loads(data) if data else [] def add_to_cart(user_id, canteen_id, dish_id, count): key fcart:{user_id}:{canteen_id} cart json.loads(r.get(key) or []) for item in cart: if item[dish_id] dish_id: item[count] count break else: cart.append({dish_id: dish_id, count: count}) r.set(key, json.dumps(cart), ex1800)下单的时候后端把Redis里的购物车数据拿出来校验价格和库存然后生成订单。这样做的好处是数据库只记录确定性的东西购物车这种中间态数据不落库整个系统的数据模型清爽很多。3.4 微信支付最容易被卡住的环节点餐系统必然涉及支付。微信支付的接入流程其实是标准的但每一步都有不少同学被卡住这里我把关键点串一遍。第一步申请微信支付商户号。需要营业执照、法人身份证、对公账户。对于校园项目如果是毕设可以用测试号或者虚拟支付环境来模拟流程没必要真去申请商户号。如果是真实运营那就需要学校食堂作为一个主体帮你申请商户号挂在食堂名下或者找当地有资质的平台做代收付。第二步小程序端调起支付。前端拿到后端的调起支付参数后调用uni.requestPayment这个API跟微信原生的wx.requestPayment等价参数直接透传就行。第三步后端接收支付回调。这是最容易写错的地方。支付成功之后微信支付服务器会向你配置的回调地址发一个POST请求这个请求的参数是加密的。你需要先解密校验签名然后看订单状态是否是SUCCESS最后再更新订单状态、给用户加余额或者发餐券。回调处理有两点必须注意。一是处理好幂等性同一笔支付回调可能微信会连续发几次所以要判断订单状态如果已经是已支付就不再重复处理。二是回调接口不能用登录态的token鉴权因为微信支付服务器没有你的token需要靠签名来验证来源。这个和普通API不一样如果按照传统思路加token回调就会一直失败。4. 跑腿模块的三个核心设计任务流转、派单策略、异常处理跑腿是整个系统里最有意思的部分也是技术含量最高的部分。网上很多点餐系统根本不做跑腿把订单状态设计成待付款、已付款、已完成就结束了那撑不起带跑腿这三个字。4.1 跑腿任务的生命周期设计跑腿任务不是一出现就绑定骑手的。我的设计是当一个订单出餐后系统判断这个订单是否需要配送。需要配送的订单自动生成一个跑腿任务状态是待接单然后进入骑手端的接单大厅。骑手看到这个任务点击抢单任务状态变为已接单同时骑手ID写入任务记录。骑手到食堂窗口取餐点确认取餐任务状态变为配送中。送到指定地点后点确认送达任务状态变为已完成。整个过程中用户可以实时看到跑腿任务的状态变化但只有骑手能操作状态流转的按钮。这个设计的关键是任务状态机的严谨性。每一个状态能往哪儿跳、谁有权限触发这个跳转都要在代码里明确限制。否则就会出现骑手还没取餐就点确认送达的情况。我在后端加了状态机的校验装饰器每个状态变更接口都会检查当前状态是否合法# app/order_status.py TRANSITIONS { pending: [accepted, cancelled], accepted: [delivering, cancelled], delivering: [completed, cancelled], completed: [], cancelled: [] } def can_transition(current, target): return target in TRANSITIONS.get(current, [])4.2 派单策略抢单还是派单我做了折中方案派单策略是个很有意思的问题。纯抢单模式谁手快谁接平台省事但会导致很多骑手打开App一直盯着刷单高优先级订单反而被抢得太快影响体验纯派单模式系统根据距离、骑手评分分配订单听起来公平但实现起来要GIS支持和复杂的匹配算法校园项目搞不定。我用的折中方案是限时抢单加智能推荐。订单进入接单大厅后首先根据骑手当前位置和食堂的距离前端通过微信定位接口拿到的经纬度算好做一个初步排序距离近的骑手在接单大厅里看到的该订单排序更靠前。也就是说不是系统强制派给你但距离更近的骑手更容易看到、更容易抢到。实际操作中骑手端接单大厅每次刷新会从后端拉取距我最近的待接单任务列表按距离升序排列。同时我加了一个小细节新任务在抢单大厅置顶5分钟5分钟后按距离排序。这样避免有人专门等大额订单而普通小订单无人问津。实测下来这种折中方案基本能保证单量均匀分布也不会有较强的命令感。4.3 超时取消与异常处理最容易忽视但最影响口碑跑腿业务的异常处理很多人都是在系统上线后才意识到。最典型的问题是接了单不取餐。骑手抢单后又不想跑了或者人跑到半路发现食堂已经关门。我的处理是接单后15分钟内没有点击确认取餐任务自动取消同时记录一次骑手的接单爽约标记。同一个骑手爽约三次当天禁止接单。第二个问题是用户找不到骑手。很多校园点餐系统里用户下单时填一个送到宿舍楼下但宿舍楼那么多到底哪个门我的方案是配送地址用地图选点用户在提交订单时选择地图上的坐标点再手动补充楼栋号和房间号备注。骑手端用微信的地图组件直接导航到这个坐标避免口头描述带来的偏差。第三个是异常订单的退款流程。如果是骑手原因导致餐凉了、洒了用户申请退款系统要有一个清晰的仲裁逻辑。我的方案是用户先申请退款订单状态变为退款审核中通知骑手和食堂端任意一方同意退款系统自动退余额给用户跑腿费退到平台并冻结等仲裁结果。这可能比大型平台简单但足以应付校园场景。5. 前后端联调与部署上线最容易翻车的三个地方系统写完之后联调和部署是另外一场硬仗。我见过太多项目在本地跑得好好的一部署到服务器就各种404、跨域、请求超时。下面这三个地方是我觉得最容易出问题、也是解决后最有成就感的环节。5.1 本地联调不要用localhost要用局域网IP很多新手在本地开发时小程序端请求后端的地址直接写http://localhost:5000。这在H5端可能没问题但放到微信开发者工具或者真机预览里就废了。因为localhost指向的是手机或者工具自己而不是你的电脑。正确做法是开发时后端启动Flask监听0.0.0.0然后在微信开发者工具的详情-本地设置里勾选不校验合法域名...选项再把API地址改成你电脑的局域网IP比如http://192.168.31.24:5000。同一Wi-Fi下手机真机预览也能访问到。需要特别提醒的是这种方式只能用于开发环境。微信开发者工具可以跳过域名校验但真机预览必须保证后端服务也开在同一局域网内而且防火墙要允许5000端口通过。我当时就是被Windows防火墙拦住死活连不上关掉防火墙就好了但要小心安全性最后还是要用真实部署地址。5.2 服务器部署与HTTPS域名配置部署我选择的是阿里云轻量应用服务器2核4G的配置跑Flask加MySQL加Redis完全够用学生优惠价非常便宜。系统的部署流程大致是服务器装好Python 3.8、MySQL 8.0、Redis、Nginx。把Flask项目拉上服务器用gunicorn或者uwsgi启动绑定内网端口5000。Nginx做反向代理监听443端口把请求转发到5000。申请一个域名做好ICP备案再申请免费的SSL证书配置HTTPS。微信小程序后台配置服务器域名。这里的核心工作量是配置Nginx的HTTPS和微信小程序的合法域名。微信小程序要求所有请求必须是HTTPS而且域名不能是IP地址必须经过ICP备案。如果你的服务器还没备案上线这一步是卡住的这一点要提前规划好。Nginx配置HTTPS的一个最小可用配置我贴在下面大家可以直接参考server { listen 443 ssl; server_name yourdomain.com; ssl_certificate /etc/nginx/cert/yourdomain.pem; ssl_certificate_key /etc/nginx/cert/yourdomain.key; location /api/ { proxy_pass http://127.0.0.1:5000/api/; proxy_set_header Host $host; proxy_set_header X-Real-IP $remote_addr; proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; } }5.3 微信小程序上传与审核时的隐藏要求最后的环节是上传代码到微信公众平台、提交审核。这一步的坑主要不在技术而在类目资质。如果你制作的小程序涉及食品交易和在线支付微信的审核会让你选择餐饮类目这个类目需要提供《食品经营许可证》。如果是校内虚拟食堂可以和学校后勤部门衔接用学校名义办理。如果只是毕业设计演示建议在体验版里走通流程即可不提交审核或者用测试类目提交。另外小程序里有用户产生的内容评论、用户头像昵称等要在隐私协议里写明信息收集用途。我在开发早期没注意审核时被驳回过两次后来把隐私弹窗和用户协议加上就顺利通过了。6. 复盘这个项目有没有白做以及我学到的真正有用的东西去年年底我复盘了一下这个项目从想法到最终运行稳定前后花了大约四个月。期间经历了技术选型调整、接口反复改、真机调试到半夜、审核被拒。但越到后面我越发现最有价值的并不是某个页面做得多精致而是整个系统的业务闭环被打通了。从技术上讲我算是真正理解了状态机的设计、购物车的缓存方案、支付回调的幂等机制。这些内容在学校课本里都有但只有落到自己的代码里跑出真实的效果才知道为什么非这么做不可。从学校场景来讲我也慢慢体会到一点做一个工具不是把功能做完就够了。真正难的是让食堂愿意配合、让骑手愿意接单、让学生觉得好用。平台初期没有骑手我自己拉了十几个同学注册体验每单给两块钱跑腿费跑了两个礼拜慢慢形成了一个小圈子。这个冷启动阶段的经验其实是任何商业项目都躲不掉的。最后再说一个我自己比较得意的细节。用户在取餐时无需报订单号和手机号只要把小程序里的取餐码亮给食堂阿姨看阿姨用食堂管理端扫一下订单就自动变成已出餐状态。这个小功能大大减少了食堂高峰期确认订单的沟通成本也是我认为整套系统里最能体现产品思维的设计。它也让我意识到好的系统不只是数据库和接口的堆砌而是能在真实场景里替人省掉一步是一步。这一点比会多少框架都重要。
返回列表