
简介这是一套基于Java语言开发的完整版微信外卖小程序系统包含前后端源码及数据库脚本面向需要快速搭建外卖平台的开发者、商家以及学习Java全栈与小程序开发的学员。系统覆盖商家管理、骑手派单、用户下单、订单跟踪等核心流程支持微信支付适用于餐饮、商超、堂食及自提等多类场景。资源包共2825个文件压缩后约27.61MB内部以log运行日志、fr3报表文件、PDF说明文档及DLL动态库为主另有若干配置文件和图片素材可辅助理解系统运行机制与数据表结构。已有908人学习下载。通过源码与数据库配合读者能获得一套可直接部署或二次开发的在线订餐解决方案尤其适合用于毕业设计、课程项目或商业原型验证。数据库中包含用户、商家、商品、订单、骑手等多张关联数据表便于学习表设计思路与业务逻辑实现整体包体紧凑、目录结构清晰可按模块快速检索和拆分学习。1. 微信外卖小程序系统源码到底能解决什么先看清这套Java前后端方案值不值得接手标题里的“JAVE”大概率是笔误实际就是 Java。很多标着“完整版”的源码号称功能强、系统稳定接手后第一周就卡在订单状态对不上、骑手派单乱、结算金额飘最后整套代码变成废文件。判断一套微信外卖小程序系统源码值不值得投入重点不是页面数量而是商家、骑手、用户三端的数据闭环是否完整以及前后端是否齐、数据库脚本能不能直接跑起来。这个方向非常适合需要快速跑通微信小程序项目实例的同学或者想把现成系统改成自己业务场景的开发者你可以跳过大面积造轮子把时间花在自己的核心流程上。我经手过几次外卖项目源码第一件事永远是先看订单表、商家表和骑手表能不能各自独立维护再看后端接口是不是统一返回结构。只要这几点成立这套代码就值得部署进去后续加商家、加骑手、改配送规则都有地方下手。下面按这个思路把它背后的架构、部署、调优和坑一条条讲清楚。2. 先拆业务架构商家、骑手、用户之间的订单状态机与数据库关系源码本身是个黑匣子但外卖系统的业务核心是订单。用户、商家、骑手三个角色都在围着订单转所以先看业务闭环再落回代码和表结构。2.1 角色权限与订单状态机商家、骑手、用户端的核心闭环一个外卖订单从用户端提交到商家接单再到骑手送达最后完成结算看起来是一条直线但实际状态远比直线复杂。用户下单后可能取消商家可以拒单骑手可能超时这些分支如果不用状态机管理后面做对账和统计就是一场灾难。我见过最糟的二次开发是把 status 字段直接用字符串保存代码里到处写“如果是‘配送中’并且用户点了‘确认收货’就改成‘已完成’”业务一扩展就崩。所以这套系统里最值得先读的就是订单状态枚举。常见做法是单独定义枚举而不是散落一堆魔数public enum OrderStatus { UNPAID(0, 待支付), PAID(1, 已支付待商家接单), ACCEPTED(2, 商家已接单待骑手), DELIVERING(3, 骑手配送中), COMPLETED(4, 已完成), CANCELED(5, 已取消); private final int code; private final String desc; OrderStatus(int code, String desc) { this.code code; this.desc desc; } public boolean canTransferTo(OrderStatus target) { // 正常链路相邻推进待支付状态下允许用户取消 return this.code 1 target.code || (this.code 0 target CANCELED) || (this.code 1 target CANCELED); } }这段逻辑很直白待支付只能变成已支付或已取消已支付可以变成商家接单或取消后面的状态只能在相邻状态推进。实际项目里我还见过“商家已接单后也可以取消订单”的流程那就要单独加分支但千万不要放开成任意跳转否则状态表里会出现“已完成”跳到“待支付”这种荒唐数据。订单状态机的每个动作还要和角色权限绑定。外卖系统至少有管理员、商家、骑手、用户四个角色后端一般用拦截器或 Spring Security 的注解控制访问。常见写法PreAuthorize(hasRole(SHOP)) PostMapping(/order/{orderId}/accept) public Result acceptOrder(PathVariable Long orderId) { orderService.accept(orderId); return Result.ok(); }这个注解表达的是只有商家角色能调用接单接口用户端就算知道接口地址也调不动。权限的意义不只是限制访问更是保证状态机的每一步动作都有对应的角色来源后面出问题能回溯到人。如果拿到的源码没有这种角色控制所有接口都能裸跑我建议先补上再谈稳定性。2.2 前后端分离的代码结构小程序端、管理端、服务端的模块划分前后端分离项目实战最容易踩的坑是不知道代码放哪。拿到的源码解压后一般是三层server 是 Java 后端admin 是网页管理后台miniapp 是微信小程序端。典型目录结构如下wx-waimai/ ├── server/ # Java 后端服务Spring Boot / SSM │ ├── src/main/java │ │ ├── controller # 对外接口 │ │ ├── service # 业务逻辑 │ │ ├── mapper # MyBatis 数据访问 │ │ └── config # 拦截器、跨域、Redis 配置 ├── admin/ # 管理后台前端Vue/ElementUI ├── miniapp/ # 微信小程序端 └── sql/ └── wx_waimai.sql # 初始化数据库脚本后端包的划分看起来简单但它决定了你加业务的方法。controller 只负责接参数、调 service、包装返回service 里写事务和状态流转mapper 里只放 SQL。如果你在 controller 里直接写了 SQL 更新那这条代码就是后期维护的灾难。这种按职责分层的结构在你新加一个“满减活动”功能时能明显感觉到改 service 和 mapper 就行controller 和小程序端基本不动。前后端能不能顺利联调取决于返回结构是否统一。常见做法是定义 Result 包装类public class ResultT { private int code; private String message; private T data; public static T ResultT ok(T data) { ResultT r new Result(); r.code 0; r.message success; r.data data; return r; } }小程序端拿到返回先看 code 是否为 0再取 data异常时的 message 才能展示给用户。如果每个接口返回结构都不一样前端写请求封装会非常痛苦。我一般拿到源码先搜索Result类没有就直接看 controller 的返回值尽早确认接口契约。2.3 数据库表设计商品、订单、配送、结算这几张表的关系源码附带的数据库不是几张表堆在一起而是围绕业务的主外键关系。外卖系统至少要有五类表用户表、商家表、骑手表、订单表、订单商品表。订单是核心它同时关联三个角色。CREATE TABLE order ( id BIGINT PRIMARY KEY AUTO_INCREMENT, order_no VARCHAR(32) NOT NULL UNIQUE, user_id BIGINT NOT NULL, shop_id BIGINT NOT NULL, rider_id BIGINT NULL COMMENT 接单骑手未派单前为空, status TINYINT NOT NULL DEFAULT 0 COMMENT 对应 OrderStatus, total_amount DECIMAL(10,2) NOT NULL, create_time DATETIME DEFAULT CURRENT_TIMESTAMP, KEY idx_shop_status (shop_id, status) ) ENGINEInnoDB DEFAULT CHARSETutf8mb4;这里 rider_id 允许为空是因为下单时还没有骑手介入派单后才填上。这种可空字段就是“可添加骑手”的落点骑手表独立维护订单里的 rider_id 在派单时更新。很多不完善的源码会在订单表里写死 rider_name 字符串骑手改名后就对不上账了。订单商品表必须做商品快照不能只存商品 id。因为商家改价或者下架商品后历史订单里的金额明细还要保留。常见做法CREATE TABLE order_item ( id BIGINT PRIMARY KEY AUTO_INCREMENT, order_id BIGINT NOT NULL, goods_id BIGINT NOT NULL, goods_name VARCHAR(100) NOT NULL, price DECIMAL(10,2) NOT NULL, quantity INT NOT NULL, KEY idx_order_id (order_id) ) ENGINEInnoDB DEFAULT CHARSETutf8mb4;goods_name 和 price 在下单那一刻写死后面商品怎么改都不影响历史订单。这个设计原则比加一百个索引都重要。至于商家和骑手是否“能添加”只要这两张表有后台新增接口就往表里插记录不需要改代码。真正要做到的是接口层和权限层都有对应入口后面部署时我会专门验证这一条。3. 把源码在本地跑通JDK、MySQL、小程序开发者工具的最小部署路径拿到源码别急着改功能先跑通。跑通的关键是版本匹配。系统写着 Java 开发但工具链版本不对第一步就会翻车。3.1 环境准备JDK、Maven、MySQL、Redis的版本搭配很多外卖源码是几年前的项目依赖还是 Spring Boot 2.x配 JDK 8 最省事。JDK 17 反而会因为 Lombok 或 javassist 版本旧而启动失败。我一般先检查本机环境java -version mvn -version mysql --version redis-cli ping输出里 java 版本应是 1.8Maven 3.6。MySQL 5.7 和 8.0 都能跑但要留意连接驱动8.0 必须用com.mysql.cj.jdbc.Driver5.7 用老驱动也能兼容。Redis 如果没装很多系统启动时连不上缓存会直接退出这套外卖系统如果依赖 Redis 做 token 或购物车那就必须先启动 Redis。实在不想装可以把配置里的 Redis 依赖临时注掉但我不建议因为后面你要做订单超时和派单冲突场景Redis 会派上用场。3.2 导入源码与初始化数据库sql脚本执行后要马上验证的几张表先到 sql 目录找到初始化脚本。按顺序执行mysql -u root -p -e CREATE DATABASE IF NOT EXISTS wx_waimai DEFAULT CHARACTER SET utf8mb4; mysql -u root -p wx_waimai sql/wx_waimai.sql mysql -u root -p wx_waimai -e SHOW TABLES;执行完不要直接启动先看表数量对不对。常见的外卖系统至少要有 user、shop、rider、orders、order_item、goods、goods_sku 这些表。如果少了 shop 或 rider说明脚本可能是分步执行的比如 admin.sql 和 business.sql 两个文件你得一起导入。这时候顺便验证一下脚本里的默认管理员账号。执行SELECT id, username, role, status FROM admin_user;如果只有一条记录并且状态是 1说明后台能登录。没有这一步后面你会卡在登录界面好几分钟最后发现是脚本没导入完整。3.3 启动后端服务与小程序前端前后端联调的最小配置最费时间的是配置数据库连接。打开 server 模块的 application.ymlserver: port: 8080 spring: datasource: url: jdbc:mysql://localhost:3306/wx_waimai?useUnicodetruecharacterEncodingutf8serverTimezoneAsia/Shanghai username: root password: 你的密码 driver-class-name: com.mysql.cj.jdbc.Driver redis: host: localhost port: 6379注意 url 里的serverTimezoneAsia/Shanghai不加的话 JDBC 驱动可能默认 GMT数据库里所有时间都差 8 小时。配置好后启动cd server mvn spring-boot:run看到Tomcat started on port(s): 8080才算成功。接着用 curl 验证接口能通curl -X POST http://localhost:8080/api/shop/login \ -H Content-Type: application/json \ -d {username:admin,password:123456}返回 JSON 里 code0 且有 token 字段说明后端数据库连接、Redis、登录接口都正常。如果返回 500先看控制台堆栈绝大多数是表名或字段大小写问题MySQL 在 Linux 下对表名大小写敏感。小程序端用微信开发者工具导入 miniapp 目录。在“详情-本地设置”里勾选“不校验合法域名、web-view业务域名、TLS 版本以及 HTTPS 证书”然后把 utils/request.js 里的 baseUrl 改成http://localhost:8080。开发模拟器里 localhost 能通真机预览要把 localhost 换成电脑的局域网 IP手机和电脑要在同一个网段。3.4 添加商家和骑手的后台操作路径验证“可添加、功能强”这套源码宣传“可添加商家、骑手”我们需要验证不是写死的。启动 admin 前端用管理员登录后菜单里应该有商家管理、骑手管理。新增商家流程一般包含商家名称、联系人手机、营业许可证图片、配送范围。提交后看接口INSERT INTO shop (shop_name, contact_phone, business_license, status) VALUES (测试餐厅, 13800000000, https://cdn.example.com/license.jpg, 1); INSERT INTO rider (rider_name, phone, status) VALUES (张三, 13900000000, 1);如果你不想点界面也可以直接执行 SQL再回后台列表看是否出现。能通过后台加记录说明商家和骑手不是写死在代码里的。这里要特别注意新增骑手接口保存后通常会把骑手状态置为“空闲”方便派单。如果源码里没有骑手状态字段派单功能基本是残缺的后面接入配送流程会很难这种源码就要再掂量一下。4. 让系统扛住高峰期JVM、连接池与订单超时的调优参数外卖系统是典型的“平时闲饭点炸”稳定性考验在午高峰。别等线上挂了再调先在本地压一压。4.1 JVM与Tomcat的并发参数下单高峰期不卡死的几个关键我一般用 Tomcat 线程池挡请求用 JVM 堆撑业务对象。应用启动参数java -Xms2g -Xmx2g -XX:UseG1GC -XX:MaxGCPauseMillis100 -jar server.jar-Xms 和 -Xmx 设为一致避免 JVM 运行时动态扩容。G1 在 JDK 8 后期版本里已经是默认但显式写出来更清楚。外卖服务器如果只有 4G 内存堆取 2G 合理留 1G 给操作系统和 MySQL硬撑到 3G 会导致系统内存交换反而更慢。Tomcat 线程池配置在 application.yml 里server: tomcat: threads: max: 300 min-spare: 20 accept-count: 200 max-connections: 1000max 是能同时处理请求的工作线程max-connections 是 TCP 连接数accept-count 是等待队列长度。这三个配合起来看连接最多 1000线程最多 300队列 200超过就拒绝。不要盲目把 max 调成 2000因为每个线程会占 1M 左右栈空间线程切换本身就耗 CPU。判断依据很简单下单接口平均耗时 100ms300 线程理论每秒最多处理 3000 个请求对一个小外卖项目完全够。4.2 MySQL连接池与事务隔离级别的取舍订单双写的正确姿势外卖系统最危险的操作是“扣库存和生成订单”必须同时成功。如果用户付款时一个事务里先减了库存后面写订单表失败库存就凭空消失了。所以要用连接池限制数据库连接数再用事务保证一致性。HikariCP 常见配置spring: datasource: hikari: maximum-pool-size: 50 minimum-idle: 10 connection-timeout: 30000 idle-timeout: 600000maximum-pool-size 不是越大越好建议按照CPU核心数 * 2 1的经验值调整。如果一台机器同时跑 Tomcat 和 MySQL50 个连接已经偏高我习惯压到 20。线上出现 “Connection is not available, request timed out” 时不是简单加大连接数而是看有没有慢 SQL 长期占着连接。事务方法加注解Transactional(isolation Isolation.READ_COMMITTED) public void createOrder(CheckoutVO vo) { // 1. 扣减商品库存 // 2. 创建订单主记录 // 3. 写入订单商品快照 }隔离级别用 READ_COMMITTED 而不是默认的 REPEATABLE_READ是为了减少间隙锁引发的死锁。具体表现是两个人同时抢最后一个商品REPEATABLE_READ 下很容易互相锁等待READ_COMMITTED 只锁当前行冲突概率小很多。这个细节是前后端分离项目实战里不太会讲但很影响线上稳定性。4.3 订单超时未支付与骑手派单冲突的兜底方案外卖下单后用户可能不付款必须自动取消。最简单的兜底是定时任务扫描UPDATE order SET status 5 WHERE status 0 AND create_time DATE_SUB(NOW(), INTERVAL 15 MINUTE);但这条 SQL 如果每分钟全表扫订单量大了会拖垮库。更常见的做法是 Redis 延迟队列下单时 ZADD 到有序集合score 是超时时间戳后台线程用 ZRANGEBYSCORE 取出已经到期的订单再取消。如果你的源码里没有延迟队列至少要把上面的 SQL 放到业务低峰期执行并且 order 表要有 create_time 索引。骑手派单冲突是另一个常见问题多个骑手同时抢一单。用乐观锁更新UPDATE order SET rider_id ?, status 3 WHERE id ? AND rider_id IS NULL;这句 SQL 的关键是rider_id IS NULL只有满足条件的更新才会影响一行。程序里判断返回值如果更新行数是 0说明单已经被别人抢走直接提示“手慢了”。这比先 select 再 update 稳得多。5. 二次开发的避坑记录从域名验收到订单状态更新的5个血泪经验我前后部署过三套类似的外卖系统没有一次是一遍跑通的。下面这五条是每天都在被问到的坑干脆写全。5.1 现象小程序 request 域名配置后仍报错 url not in domain list现象开发者工具里已经配置了域名请求还是报url not in domain list。原因开发环境没有勾选本地设置里的“不校验合法域名”或者你真机调试时用的是局域网 IP而 IP 本来就不在合法域名列表。解决开发调试阶段在微信开发者工具的“详情-本地设置”里勾选不校验合法域名生产环境必须在小程序后台配置 request 合法域名并且域名要有备案、支持 HTTPS。如果你只在后台填了域名但没上传 HTTPS 证书一样会报证书错误。5.2 现象数据库表字段乱码中文全部变成问号现象后台添加商家后商家名称乱码。原因建库时用了默认 latin1或者连接串没指定 characterEncoding。解决先看库字符集SHOW CREATE DATABASE wx_waimai;如果字符集不是 utf8mb4改掉ALTER DATABASE wx_waimai CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci;再在连接串上加useUnicodetruecharacterEncodingutf8重启后端。注意老数据如果已经被错误字节写入只改库表不重建乱码数据仍需手工清理。5.3 现象订单状态更新丢失接单后前端仍是待接单现象商家点击接单后台显示成功用户端却还停在“待商家接单”。原因接单逻辑里更新了 order 表但没有传原始状态条件两个并发请求互相覆盖。解决在 mapper 里用条件更新并且给 service 加事务Update(UPDATE order SET status #{target} WHERE id #{orderId} AND status #{expect}) int updateStatusIfMatch(Param(orderId) Long orderId, Param(expect) int expect, Param(target) int target);调用时先查出当前状态再传 expect返回值是 1 才继续推消息。如果 0 就告诉前端“订单状态已变化请刷新页面”。这种“比较并交换”的写法是防止状态错乱的最后一道防线。5.4 现象骑手定位偏离距离计算不准现象骑手端上报的位置和实际地图位置差几百米。原因微信小程序的wx.getLocation返回的是国测局坐标gcj02也就是俗称的火星坐标系而后端用的是 GPS 原始坐标或者高德、百度自己的坐标系混着算距离自然偏。解决在小程序里请求定位时明确wx.getLocation({ type: gcj02, success(res) { // 上传 res.latitude, res.longitude } });后端调用地图 API 做距离计算时也把坐标系参数标成 gcj02。如果系统里有历史 GPS 坐标数据需要写转换工具统一别在代码里加个随机偏移“修正”那只是自欺欺人。5.5 现象前后端时间差导致结算金额对不上现象每天对账时发现订单时间落后 8 小时或者某些订单金额显示 0.10.20.30000000000000004。原因数据库连接串没加时区MySQL 默认用了服务器本地时区金额用 Float 或 Double 存储二进制浮点无法精确表达十进制小数。解决连接串加serverTimezoneAsia/Shanghai金额字段统一用 DECIMALJava 里用 BigDecimalBigDecimal amount1 new BigDecimal(10.10); BigDecimal amount2 new BigDecimal(5.30); BigDecimal total amount1.add(amount2); // 15.40而不是15.399999记住任何给用户看的金额都不能用 Double 做计算。这是老生常谈但外卖源码里翻车最多的就是它。6. 上线前先做这轮验证核心链路检查与两处值得改的扩展点如果有精力不要着急加花哨功能先把核心交易链路手动跑一遍。我的做法是准备一张表按顺序勾验证步骤操作预期结果用户下单小程序端选商品提交订单商家后台出现新订单状态待接单商家接单后台点击接单用户端状态变为已接单后台派单商家或管理员指派骑手骑手端收到任务订单状态配送中骑手确认送达骑手端点击送达用户端状态变为已完成对账查询 orders order_item结算金额与下单时一致全部通过后再谈上线。上线前我一般还会改两个点一个是订单状态变化时的微信订阅消息另一个是图片上传改对象存储。订阅消息的 payload 看起来像这样{ touser: 用户openid, template_id: 模板ID, page: pages/order/detail?id123, data: { thing1: { value: 商家已接单 } } }这个推送要放在状态机流转成功之后不能放在用户点击之前。如果把发送函数写在 service 方法开头事务回滚时会发出假通知用户等半天看不到订单售后就有你忙的了。图片上传是源码里最容易偷懒的地方很多项目直接存本地磁盘小程序后台图片没法长期访问。把它改成对接 MinIO 或云对象存储前端拿到临时上传凭证直传对象存储再回写 URL 到 goods 表。这一步做完后台即便迁移换机器图片也不会丢。我自己的习惯是拿到任何外卖源码第一件事永远是把“下单-接单-派单-送达-结算”这条链路手动跑完跑不通的功能不启动。纸上写的功能再强状态流转一断系统就谈不上稳定。希望帮到你。本文还有配套的精品资源点击获取