在线点餐小程序源码:Java前后端分离与多门店扫码点餐开发实战
发布时间:2026/9/28 9:08:23来源:尧图网络
简介这套在线点餐小程序源码采用Java后端与uniapp(vue3)前端的前后分离架构支持扫码点餐、外卖与自取两种模式并内置多门店管理能力完整覆盖真实餐饮业务中的订单流转、门店隔离与用户端交互场景适合计算机、软件、电子信息等专业学生用于课程设计、期末大作业或毕业设计参考。压缩包共2005个文件约18.17MB含1323个Java核心业务类、257个Vue页面组件、161个JS脚本、84个Markdown说明文档另附SQL初始化脚本、YAML配置及HTML/CSS样式文件目录按后端接口、前端页面、项目文档等维度组织便于快速定位与二次开发。资源附带项目说明可辅助理解环境搭建和业务实现思路外卖与自取双流程、多门店隔离机制也是面试与答辩中值得展开的技术亮点。目前已有97人学习下载适合需要完整前后端参考案例并希望动手扩展功能的开发者。1. 在线点餐小程序源码适合Java课程设计与独立二次开发的前后端分离方案餐饮行业的点餐需求在小程序里一直是高频高价值场景。这套带完整商品管理、购物车、下单支付链路的在线点餐小程序源码后端是Java前端是uniapp(vue3)走的是扫码点餐为主、外卖与自取双通道、支持多门店的路线。对于想交课程设计、期末大作业或毕设的计算机专业学生来说它代码量足够撑起一次答辩对于想在真实门店场景里做二次开发的开发者它把门店、菜品、订单、桌台的关系拆得相对清楚拿来改比从零写省事得多。我拆完一圈后发现这套资源最大的价值不是“能跑”而是把前后端分离的工程边界和多门店的数据归属讲清楚了这两点恰恰是很多课程设计里最容易翻车的地方。2. 在线点餐的整体拆解技术栈、数据模型与多门店边界2.1 技术栈选型Java后端 uniapp(vue3)前端的分工逻辑前后端分离不是把代码分开就叫分离重点是接口边界和职责边界。这套资源的前端用uniapp(vue3)编译成微信小程序负责展示、交互、登录态、购物车这些用户侧逻辑后端Java负责门店、菜品、订单、支付、配送费这些业务规则与数据存储。两侧通过HTTP接口通信没有模板渲染纠缠这和我平时做的项目工程结构基本一致。我一般在上手源码之前会先打开package.json和pom.xml确认依赖锁定在哪一代。后端如果出现spring-boot-starter-web和mybatis-plus说明是Spring Boot MyBatis-Plus这套常见组合前端依赖里出现vue3和pinia说明状态管理走的是组合式API。别急着配环境先看锁的版本再决定JDK和Node版本这一步能省掉大半天的玄学报错。顺便说一句前端小程序端编译目标是微信小程序H5、App端只是uniapp的附带能力真正要对标的运行环境是微信那一套。2.2 多门店模式数据归属与场景参数怎么设计多门店的关键在数据隔离。常见做法是每张业务表都放store_id门店表单独一张store小程序端在请求头或接口路径里带storeId后端在Service层根据storeId过滤数据。不要把所有门店数据混在一张表里靠代码if区分那样门店少时没感觉门店一多查出来的数据互相污染改都改不干净。订单表至少要区分order_type1代表堂食扫码点餐2代表外卖3代表自取。外卖场景多两个字段配送地址、配送费自取场景要生成取餐号并计算预计出炉时间。我在这类项目里会把订单号设计成“门店前缀日期四位流水”比如B001-20250612-0001既方便按店区分也方便后端做分库分表时的路由键。桌台状态也是扫码点餐的边界。桌台要维护一个状态字段空闲、已占、待清理。扫码进来的tableId是前提没有桌台绑定订单就没法落到堂食链路。很多新手拿到这类源码第一件事是追着支付流程看我反而建议先看桌台和门店这两个基础表因为它们决定了扫码进来的请求往哪个订单类型上挂。2.3 目录结构与UEditor静态资源说明资源包解压后主要分三块后端工程、小程序前端、项目说明文档。后端按标准Maven结构分controller、service、mapper、entity小程序前端按pages、components、store、api拆。包里还带了一套UEditor编辑器静态文件ueditor.css、video-js.css这些都在一般是给商家后台或管理端用来编辑菜品图文详情的。提示UEditor在包里的版本可能比较老静态资源加载不出来时优先检查路径大小写和静态资源映射别急着升级版本。UEditor升级往往会连带替换图片上传接口一换就是一条链路。3. 本地跑通后端数据库初始化、配置修改与启动验证3.1 环境清单与版本搭配先把环境对齐再谈跑通。JDK 8或11对应Spring Boot 2.xJDK 17对应Spring Boot 3.x具体看项目pom里锁的是哪个版本Maven用3.6以上MySQL用5.7或8.0都行但字符集必须统一成utf8mb4Redis必须有小程序端登录态和购物车缓存一般都要依赖它。前端uniapp部分需要HBuilderX 4.x以上或者直接用vue3工程跑npm run dev:h5做浏览器调试。我拿到源码的第一步永远是先看pom和README。README里如果写了数据库脚本的位置就按它的顺序来如果没写就自己在sql目录里找。最怕的是不看环境直接双击启动报错以后才回头翻文档来回折腾的时间够重新配两遍环境了。3.2 数据库初始化从建库到建表脚本先建库再导表字符集用utf8mb4排序规则用utf8mb4_general_ci。多门店相关表的主键建议用雪花算法生成的bigint别用自增int——课程设计阶段看不出差别真要上线自增id在门店数据量大之后是灾难分表合并时还得重新生成主键。代码块里给出核心表的建表结构与字段含义说明-- 建库实际项目里SQL文件会包含全量脚本此处只抽取门店表做示例 CREATE DATABASE IF NOT EXISTS ordering DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci; USE ordering; -- 门店表多门店模式的核心归属表 DROP TABLE IF EXISTS store; CREATE TABLE store ( id BIGINT NOT NULL COMMENT 门店id由雪花算法生成, store_name VARCHAR(64) NOT NULL COMMENT 门店名称, store_code VARCHAR(16) NOT NULL COMMENT 门店编码用于订单号前缀, status TINYINT NOT NULL DEFAULT 1 COMMENT 1营业 0歇业, create_time DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT 创建时间, PRIMARY KEY (id), UNIQUE KEY uk_store_code (store_code) ) ENGINEInnoDB DEFAULT CHARSETutf8mb4 COMMENT门店表; -- 菜品表通过store_id归属具体门店 DROP TABLE IF EXISTS dish; CREATE TABLE dish ( id BIGINT NOT NULL COMMENT 菜品id, store_id BIGINT NOT NULL COMMENT 所属门店id, category_id BIGINT NOT NULL COMMENT 分类id, dish_name VARCHAR(64) NOT NULL COMMENT 菜品名称, price DECIMAL(10,2) NOT NULL COMMENT 售价, status TINYINT NOT NULL DEFAULT 1 COMMENT 1上架 0下架, sort INT NOT NULL DEFAULT 0 COMMENT 排序权重, PRIMARY KEY (id), KEY idx_store_category (store_id, category_id) ) ENGINEInnoDB DEFAULT CHARSETutf8mb4 COMMENT菜品表;这段SQL的逻辑很直白store表是全项目的根dish表通过store_id和category_id两级归属查询时用联合索引idx_store_category一次定位到某个门店的某个分类。实际SQL文件会比这个长很多包含桌台、订单、订单明细、购物车、用户表但核心思想一致——每张业务表都带store_id查询全部以store_id为第一过滤条件。导入全量脚本时直接用source命令或Navicat运行整个.sql文件即可不用手动分行执行。3.3 修改配置文件数据源、Redis与多门店开关数据库导完接下来全文搜索application.yml或application-dev.yml主要改三处数据源、Redis、文件上传路径。数据源URL里我会强制带上useUnicodetruecharacterEncodingutf8serverTimezoneAsia/Shanghai缺一不可——不带前两个中文菜品名写入后读出来是一串问号不带时区日期字段会比北京时间差8小时。spring: datasource: url: jdbc:mysql://localhost:3306/ordering?useUnicodetruecharacterEncodingutf8serverTimezoneAsia/Shanghai username: root password: yourpassword driver-class-name: com.mysql.cj.jdbc.Driver redis: host: 127.0.0.1 port: 6379 database: 0 # 多门店开关true时所有接口强制校验请求头里的storeId app: multi-store: enabled: true upload: # 菜品图片上传根路径Windows和Linux写法不同 path: /data/ordering/upload配置里最容易被忽略的是app.multi-store.enabled这个开关。很多课程设计版本是单门店代码里如果没做这个开关所有接口默认不带storeId查询而这份源码强制开启后前端每次请求必须带storeId否则接口直接返回参数缺失。多门店模式的边界不在SQL就在这个开关和请求头校验之间。3.4 启动后端与接口验证配置改完Maven打包启动。Spring Boot 2.x用java -jar直接跑3.x也一样但注意如果项目同时存在admin和api两个启动模块说明商家端和用户端是分开部署的启动哪个看你要调试哪条链路。# 跳过测试打包避免单元测试环境问题阻塞启动 mvn clean package -DskipTests # 启动后端项目约5-10秒视机器性能而定 java -jar target/ordering-api.jar \ --spring.profiles.activedev \ --server.port8080 # 验证门店接口是否正常返回 # storeId放在请求头向后端声明当前用户属于哪个门店 curl http://localhost:8080/api/store/list -H storeId: 1001 | python3 -m json.tool打包命令加了-DskipTests意思是跳过测试用例编译和执行只打包业务代码这是本地联调最常见的做法。启动命令里的--spring.profiles.activedev指定使用dev环境配置--server.port8080覆盖端口。curl那一步验证的是最基础的连通性如果返回门店列表JSON说明数据库连接和MyBatis映射都正常如果返回401或500优先检查数据源账号密码和Redis是否启动。后端接口通常还会带一个Swagger地址浏览器打开http://localhost:8080/swagger-ui.html可以直接调试所有接口比curl直观得多。4. 小程序端编译与扫码点餐链路打通4.1 uniapp(vue3)工程导入与依赖安装后端跑通后开始处理前端。用HBuilderX导入uniapp工程目录或者传统的npm install方式安装依赖。这个环节最容易错的是Node版本uniapp(vue3)对Node 16以上支持更好Node 14在某些依赖上会报错建议直接装Node 18 LTS版本省得在版本兼容上浪费时间。请求地址的切换是一个必然要改的点。开发阶段微信开发者工具里可以勾选“不校验合法域名”请求直接打到本机http://localhost:8080上线阶段必须换成HTTPS的正式域名否则微信会拦截请求。我习惯把请求基地址独立放在一个配置文件里而不是散落在各个页面后面换环境只改一处就行。// api/request.js 统一请求封装 const BASE_URL http://localhost:8080 export function request(path, { method GET, data {} }) { return new Promise((resolve, reject) { uni.request({ url: BASE_URL path, method, data, header: { // 多门店模式下每个请求都必须携带storeId storeId: uni.getStorageSync(storeId) || , token: uni.getStorageSync(token) || }, success: ({ statusCode, data }) { // 后端统一返回结构code为0表示业务成功 if (statusCode 200 data.code 0) { resolve(data) } else { reject(data) } }, fail: reject }) }) }这段封装的要点有三个第一BASE_URL是唯一需要切换的环境地址考试环境、本地联调、正式环境只改这个常量第二storeId从本地缓存读取后放进请求头保证多门店模式下后端知道当前请求属于哪个店第三成功回调里同时判断statusCode和业务codeHTTP 200只代表网络通不代表业务成功这是前后端分离项目里新手最容易踩的认知坑。后续所有页面接口调用都走这个request方法购物车、下单、支付都统一走这条通道。4.2 扫码进入解析scene参数与门店桌台绑定扫码点餐的起点是用户扫桌面二维码。微信小程序的扫码参数统一放在scene字段里二维码内容不是直接可读的URL而是后端生成二维码时编码进去的一段参数串。我在这类项目里约定的格式是storeId1001tableId8小程序在onLoad里拿到scene后解析出门店和桌台分别写入缓存。// pages/index/index.vue 扫码落地页逻辑 onLoad(query) { // scene是微信扫码传来的参数需要先解码 const scene decodeURIComponent(query.scene || ) // 解析约定格式storeId1001tableId8 const params new URLSearchParams(scene) const storeId params.get(storeId) const tableId params.get(tableId) // 写入缓存后续所有请求头自动携带storeId uni.setStorageSync(storeId, storeId) uni.setStorageSync(tableId, tableId) // 门店和桌台都拿到后再请求菜品菜单 if (storeId tableId) { this.loadMenu(storeId) } else { uni.showToast({ title: 无效的桌台码, icon: none }) } }这段逻辑的先后顺序很重要先解析参数再写缓存最后请求菜单。因为request封装里每次都会从缓存取storeId如果先请求菜单再写缓存第一个请求就丢失门店信息了。另一个细节是桌台码失效的场景后端在有效期内才允许下单扫一次码管一段时间超时要重新扫码这是餐饮行业的通用做法防止顾客离桌后别人继续用这个桌台买单。4.3 外卖与自取流程的差异外卖、自取、堂食三条链路在同一套代码里靠orderType区分。外卖走完整配送链路下单时要填收货地址结算时叠加配送费后端接单后等待骑手取件自取更接近“预点单”用户下单后到店报取餐号拿走就行不需要配送地址但要判断门店营业时间和预计出餐时间。// 下单时根据orderType决定提交哪些字段 function submitOrder(orderType, formData) { const payload { storeId: uni.getStorageSync(storeId), tableId: orderType 1 ? uni.getStorageSync(tableId) : null, orderType, items: cartItems, remark: formData.remark } // 外卖场景才需要配送地址自取与堂食不需要 if (orderType 2) { payload.address formData.address payload.deliveryFee calcDeliveryFee(formData.address.distance) } request(/api/order/create, { method: POST, data: payload }) }代码里orderType2表示外卖额外补address和deliveryFeeorderType1堂食必须带tableIdorderType3自取不需要桌台也不需要地址。配送费不是一个固定值常见做法按距离阶梯计算我一般建议后端算而不是前端算——前端算的是展示值后端算的才是结算值前后端不一致会在对账时出问题。5. 避坑指南从导入到上线的常见问题排查5.1 前端能启动但接口全返回空数据或404现象小程序页面能打开菜单页显示空白后端日志里完全没有请求记录或者请求到了但查出来是空数组。原因多门店开关开启后前端请求头没带storeId后端Interceptor直接拦截返回参数错误另一种情况是接口路径跟前端封装的BASE_URL拼起来不对多了一个斜杠或少了一段前缀。解决第一步在开发者工具的Network面板里看请求头确认storeId是否传了第二步看后端控制台有没有打印请求日志。我惯用的做法是在request方法里加一个console.log打印完整URL和header前后端联调时这一条信息能定位掉80%的路径问题。storeId没传就去检查onLoad里缓存是否写入成功路径不对就去后端Controller的RequestMapping里核对前缀。5.2 菜品中文乱码或启动时数据库连接报错现象后端启动失败报Communications link failure或者启动成功但菜品名称读出乱码。原因数据库连接URL没带characterEncoding和useUnicode参数MySQL驱动用默认字符集读数据或者数据库本身建库时用了latin1不管连接串怎么改都是乱码。解决两步一起做。连接URL补上useUnicodetruecharacterEncodingutf8数据库字符集用ALTER DATABASE ordering CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci重建。我在这里吃过一次亏只改连接串不改库字符集结果治标不治本换一台机器重新导数据又乱了。数据库脚本导入前要确认.sql文件本身是UTF-8编码Windows记事本另存为时选UTF-8不然SQL里带中文注释都会导入报错。5.3 小程序请求提示“不在以下合法域名列表中”现象开发者工具里所有请求都报url not in domain list点开详情看是request:fail。原因微信小程序对网络请求有域名白名单限制开发时只要在详情页勾选“不校验合法域名、web-view域名、TLS版本以及HTTPS证书”就能绕过但前提是你用的是开发者工具而不是真机预览真机预览时这个选项经常失效尤其是安卓真机会直接拦截。解决开发阶段用开发者工具调试真机预览需要在小程序后台配置request合法域名域名必须是HTTPS且ICP备案。如果临时要在手机上演示把后端部署到服务器并配好HTTPS证书再把这个域名加到白名单里。不要试图在代码里关掉这个校验微信没有提供这个后门。5.4 Redis反序列化报错登录状态反复失效现象后端启动正常但一调用登录或购物车接口就报SerializationException或者Redis里存进去的对象读出来类型对不上。原因RedisTemplate默认使用JDK序列化但项目里存的可能是JSON字符串或某个实体对象两种序列化方式混用时数据写进去和读出来不一致。另一种典型场景是多个环境共用同一个Redis库键冲突导致类型错乱。解决把RedisTemplate统一配置为StringRedisSerializer键和值都存字符串实体对象转JSON后写入读取时再转回对象。我一般会在RedisConfig里显式设置序列化器不依赖默认配置同时给键加业务前缀比如ordering:cart:1001这样不同环境之间不会互相污染。看报错信息时重点看Caused by那行别被前面的长堆栈带偏方向。5.5 菜单图片加载不出来UEditor静态资源404现象菜品图片位置一片空白后台编辑器也打不开检查文件发现ueditor.css等静态文件都在但访问就是404。原因Spring Boot对静态资源的默认映射路径是classpath:/static/如果前端上传的图片放在服务器磁盘路径而没做静态资源映射或页面引用的UEditor路径大小写不一致都会导致404。Linux服务器对大小写敏感UEditor.js和ueditor.js是两个不同文件。解决确认图片上传后保存的磁盘路径和访问URL之间的映射关系在配置里加静态资源映射规则前端页面引用UEditor时严格核对文件名大小写。我从这类项目里总结的规律是图片上传和访问一共涉及三个路径存盘路径、访问映射、数据库记录三者必须保持一致任何一个对不上就404。6. 二次开发的三个切入点从会跑到会改动手改代码之前先摸清三条链路的先后顺序扫码进入→选择菜品→购物车结算→提交订单→支付回调→门店接单。任何一个环节断掉整体体验都不完整。想在课程设计答辩里做出亮点我建议优先碰菜品维度的扩展因为它影响面最小、展示效果最直接。菜品维度最值得加的是“今日沽清”状态。原始代码里菜品只有上架和下架两个状态但真实餐饮场景里经常出现“菜单上有但后厨已经卖完”的情况。扩展方式是给dish表加一个sold_out字段后端根据库存或人工操作更新前端菜单展示时把sold_out的菜品置灰并禁止加入购物车。-- 扩展字段0正常 1今日沽清 ALTER TABLE dish ADD COLUMN sold_out TINYINT NOT NULL DEFAULT 0 COMMENT 今日沽清状态; -- 查询菜单时过滤掉沽清菜品或返回状态让前端展示 SELECT id, dish_name, price, sold_out FROM dish WHERE store_id #{storeId} AND status 1 ORDER BY sort DESC;前端拿到sold_out后在菜品卡片上做一个置灰状态加“今日售罄”标签点击时提示且不加入购物车。这里要注意的是后端仍然要校验不能只靠前端拦截——用户绕过前端直接调接口照样能把沽清菜品加进订单。验证方式是在下单接口里再加一道校验查菜品表时带上sold_out0的条件从服务端彻底堵死。外卖配送费是第二个值得下功夫的点。原始代码如果是固定配送费改成阶梯计费会显得更完整。常见做法是按距离分三档3公里内起步价3到5公里加一档5公里以上每公里叠加。我会把这部分逻辑单独抽成一个类不要在Controller里写if-else方便后面出活动的时候替换算法。桌台状态管理是第三个切入点。原始代码里桌台的占用状态如果没做自动释放会出现顾客离桌后桌台一直显示“已占”。常见做法是结账完成后自动释放、超时未下单自动释放再加一个服务员手动释放的入口。这三段逻辑各有各的判断时机加在一起才能保证桌台状态的准确性。从那以后我每次拿到这类前后端分离的源码第一件事永远是先把数据库脚本完整跑一遍再决定要不要改代码——因为数据模型里藏着整个项目的业务答案表关系理顺了代码逻辑自然就通了。这套在线点餐源码我整体过下来多门店归属、外卖自取分流、扫码绑桌这三个点做得都比较干净适合当成课程设计的起点也适合作为研究前后端分离工程结构的素材。希望帮到你。本文还有配套的精品资源点击获取
网站建设高端定制企业官网