2026/10/4 4:17:47

微信小程序点餐系统毕业设计:Java后端与数据库实战

微信小程序点餐系统毕业设计:Java后端与数据库实战 简介这是一套面向高校计算机相关专业学生与Java初学者、用于毕业设计或课程设计实战的微信点餐系统完整项目包采用微信小程序前端搭配Java后端的技术路线可帮助读者解决从选题到落地实现的全流程参考需求。压缩包共1286个文件约13.72MB涵盖小程序端的wxml、wxss、js与vue页面文件后端123个java源码、sql数据库脚本以及json配置、png与svg图片素材、说明文档docx等前后端与数据库结构相对完整。目前已有570人学习下载适合需要完整赛题方案、可运行源码与数据库脚本的读者参考。项目包含点餐业务相关页面、后端接口与建表脚本并附有说明文档便于对照理解小程序与Java服务端的交互逻辑、目录组织方式及部署思路可作为二次开发或答辩演示的基础素材。1. 从一份点餐系统毕设包说起微信小程序Java后端到底能跑出什么每年毕业季计算机专业的学生都会面对同一个问题选一个能写进简历、又能真正跑起来的项目。微信点餐系统几乎是出现频率最高的选题之一原因很直接——它同时踩中了微信小程序、Java后端、数据库设计、前后端分离这几个企业招聘里反复出现的词。但真正动手时大多数人卡住的地方不是“不会写代码”而是不知道一个完整的点餐系统应该包含哪些模块、数据库表怎么设计、小程序端怎么和后端联调、以及答辩时老师会追问什么。这份“基于微信小程序Java后端的微信点餐系统毕业设计”本质上是一个典型的B/SC/S混合架构项目用户端用微信小程序承载浏览菜单、加购物车、下单、查看订单状态管理端用Web页面或小程序管理版完成菜品管理、订单处理、分类维护后端用Java提供RESTful接口数据库存储菜品、订单、用户、桌台等核心数据。它适合三类人正在找毕设选题的本科生、想通过一个完整项目补齐前后端联调经验的自学者、以及需要快速搭建点餐类Demo做二次开发的人。我见过太多人拿到这类压缩包后直接双击打开源码然后被一堆配置文件和环境依赖劝退。所以这篇笔记不按“先讲架构再讲代码”的套路走而是按一个真实开发者的落地顺序来先把技术选型和数据模型定下来再跑通最小闭环最后处理那些只有真正部署过才会遇到的坑。你跟着走完至少能得到一个能演示、能答辩、能继续扩展的点餐系统骨架。2. 技术选型与数据模型为什么用Spring Boot而不是Servlet2.1 后端框架选型Spring Boot 2.x MyBatis-Plus 是毕设的稳妥组合很多毕设指导书还在用ServletJSP但企业里早就不这么干了。微信小程序端通过HTTPS请求后端接口后端返回JSON这种前后端分离模式下Spring Boot是最省心的选择。它内置Tomcat不需要单独配置Web服务器一个main方法就能启动配合MyBatis-Plus单表增删改查几乎不用写SQL分页、逻辑删除、自动填充时间字段都有现成方案。我一般会这样搭后端骨架controller层只做参数校验和调用serviceservice层写业务逻辑mapper层继承BaseMapperentity层用注解映射数据库表。对于点餐系统核心业务逻辑集中在订单状态流转和购物车合并上这两块建议手写SQL或使用LambdaQueryWrapper不要依赖代码生成器一把梭。// OrderServiceImpl.java 订单状态流转核心逻辑 Service public class OrderServiceImpl extends ServiceImplOrderMapper, Order implements OrderService { Autowired private OrderDetailMapper orderDetailMapper; Override Transactional(rollbackFor Exception.class) public void createOrder(OrderDTO orderDTO) { // 1. 校验菜品是否下架 for (OrderDetailDTO detail : orderDTO.getDetails()) { Dish dish dishMapper.selectById(detail.getDishId()); if (dish null || dish.getStatus() 0) { throw new BusinessException(菜品已下架 detail.getDishName()); } } // 2. 写入订单主表 Order order new Order(); order.setOrderNo(generateOrderNo()); // 自定义订单号生成规则 order.setUserId(orderDTO.getUserId()); order.setTableId(orderDTO.getTableId()); order.setTotalAmount(orderDTO.getTotalAmount()); order.setStatus(OrderStatus.PENDING_PAYMENT.getCode()); this.save(order); // 3. 批量写入订单明细 for (OrderDetailDTO detail : orderDTO.getDetails()) { OrderDetail od new OrderDetail(); od.setOrderId(order.getId()); od.setDishId(detail.getDishId()); od.setQuantity(detail.getQuantity()); orderDetailMapper.insert(od); } } }这段代码的关键点有三个Transactional保证订单主表和明细表同时成功或同时失败订单号不要用自增ID直接暴露我一般用“时间戳用户ID后四位随机数”生成菜品下架校验必须放在写订单之前否则会出现用户点了已下架菜品但订单已生成的脏数据。参数方面OrderStatus建议用枚举定义待支付、已支付、制作中、已完成、已取消。数据库里存int值前端展示时通过字典转换。totalAmount用BigDecimal不要用double否则会出现0.10.2≠0.3的经典问题。2.2 数据库表设计六张核心表撑起整个点餐流程点餐系统的数据库不需要几十张表把下面六张设计清楚就够用了。我见过有人把菜品规格、口味、加料拆成五六张表结果查询时要关联七八次毕设答辩时自己都讲不清。合理的做法是常用字段冗余复杂规格用JSON字符串存。表名核心字段说明userid, openid, nickname, avatar, phone微信用户openid唯一categoryid, name, sort, status菜品分类sort控制排序dishid, category_id, name, price, image, description, status, stock菜品status控制上下架orderid, order_no, user_id, table_id, total_amount, status, create_time订单主表order_detailid, order_id, dish_id, dish_name, price, quantity订单明细冗余菜品名和价格table_infoid, table_no, capacity, status桌台信息status控制空闲/占用order_detail里冗余dish_name和price是故意的菜品可能改价或改名但历史订单必须保留下单时的快照。table_info在堂食场景下必须要有扫码点餐时小程序通过桌台二维码携带的tableId来绑定订单。建表时注意两个细节所有表加create_time和update_time用MyBatis-Plus的自动填充order表的order_no加唯一索引防止并发下重复。字符集用utf8mb4否则用户昵称里的emoji会插入失败。2.3 小程序端技术选型原生开发还是uni-app这是被问得最多的问题。如果你的毕设只要求微信小程序用原生开发最稳微信开发者工具直接调试API调用没有中间层。但如果你还想兼容支付宝小程序或H5uni-app更合适。我一般建议毕设时间紧就选原生因为uni-app打包微信小程序时偶尔会遇到样式错位和API兼容问题排查起来很耗时间。原生小程序的核心文件结构是app.json配置页面路由和窗口样式app.wxss写全局样式每个页面由.wxml、.wxss、.js、.json四件套组成。点餐系统的页面至少需要首页分类菜品列表、菜品详情、购物车、订单确认、订单列表、个人中心。首页的菜品列表建议用scroll-view做分类锚点联动不要用page滚动否则分类吸顶效果很难做。3. 从零跑通最小闭环登录、点餐、下单、查单3.1 微信登录与openid获取后端换取session_key的正确姿势微信小程序的登录流程是固定的小程序端调用wx.login()拿到临时code把code发给后端后端用codeappidsecret请求微信接口换openid和session_key。这里有个坑session_key不能返回给小程序端它只保存在后端用于解密用户信息。// 小程序端 login.js wx.login({ success: (res) { if (res.code) { wx.request({ url: https://your-domain.com/api/auth/login, method: POST, data: { code: res.code }, success: (resp) { // 后端返回自定义token存入storage wx.setStorageSync(token, resp.data.data.token); wx.setStorageSync(userId, resp.data.data.userId); } }); } } });后端处理时用RestTemplate或HttpClient请求https://api.weixin.qq.com/sns/jscode2session参数是appid、secret、js_code、grant_typeauthorization_code。拿到openid后查数据库如果用户不存在就自动注册一条记录。返回给小程序的token建议用JWT生成有效期设7天后续接口通过拦截器校验token。注意appid和secret不要硬编码在代码里放到application.yml中并且.gitignore要排除配置文件。我见过有人把secret提交到GitHub结果被扫到后小程序被恶意调用血泪教训。3.2 菜品列表与分类联动小程序页面列表加载更多的实现点餐系统首页需要展示分类和菜品用户点击左侧分类时右侧滚动到对应位置右侧滚动时左侧分类高亮。这个交互用scroll-view的scroll-into-view属性实现。// pages/menu/menu.js Page({ data: { categories: [], dishes: [], activeCategory: , scrollIntoView: }, onLoad() { this.loadCategories(); }, loadCategories() { wx.request({ url: https://your-domain.com/api/category/list, success: (res) { this.setData({ categories: res.data.data }); this.loadDishes(res.data.data[0].id); } }); }, loadDishes(categoryId) { wx.request({ url: https://your-domain.com/api/dish/list, data: { categoryId, page: 1, size: 20 }, success: (res) { this.setData({ dishes: res.data.data.records }); } }); }, onCategoryTap(e) { const categoryId e.currentTarget.dataset.id; this.setData({ activeCategory: categoryId, scrollIntoView: cat- categoryId }); this.loadDishes(categoryId); } });scroll-into-view的值必须是子元素的id所以每个分类区块要加idcat-{{item.id}}。菜品列表加载更多用onReachBottom触发下一页请求把新数据concat到原数组后面。注意page和size参数要和后端分页插件对应MyBatis-Plus的Page对象默认从1开始。3.3 购物车与下单本地存储还是后端存储购物车数据放哪里取决于你的业务复杂度。如果只是单桌点餐购物车放小程序的storage里最简单用户退出小程序再进来数据还在。但如果要支持多设备同步或后台改价就必须放后端。我一般用混合方案未登录时购物车存本地登录后同步到后端。本地存储结构是{dishId, name, price, quantity, image}的数组每次增减数量时更新storage并刷新页面。下单时把购物车数组发给后端后端重新计算总价不要信任前端传的总价生成订单后清空本地购物车。// 加入购物车 addToCart(e) { const dish e.currentTarget.dataset.dish; let cart wx.getStorageSync(cart) || []; const index cart.findIndex(item item.dishId dish.id); if (index -1) { cart[index].quantity 1; } else { cart.push({ dishId: dish.id, name: dish.name, price: dish.price, quantity: 1, image: dish.image }); } wx.setStorageSync(cart, cart); wx.showToast({ title: 已加入, icon: success }); }下单接口收到购物车数组后先查数据库校验每个菜品的当前价格和状态如果价格有变动要返回提示让用户确认。订单号生成用System.currentTimeMillis()加随机数保证唯一且不连续。3.4 订单状态流转与查询用户端和管理端看到的不一样订单状态是点餐系统最容易出bug的地方。用户端看到的是“待支付→已支付→制作中→已完成”管理端看到的是“新订单→已接单→已出餐”。两边的状态码要统一但展示文案可以不同。后端用枚举定义状态0待支付、1已支付、2制作中、3已完成、4已取消。用户支付成功后微信支付回调接口把状态从0改成1管理端点击“接单”改成2点击“出餐”改成3。用户取消订单只能在前两个状态操作制作中之后不允许取消。查询订单列表时用户端只查自己的订单管理端查全部。分页参数用page和size返回结果按create_time倒序。如果订单量不大可以直接用MyBatis-Plus的Page对象如果要做订单统计建议单独写一个count查询不要在前端遍历。4. 避坑与排查那些只有真正部署过才会遇到的问题4.1 小程序请求域名未配置真机调试全部失败现象微信开发者工具里接口请求正常但用手机预览时所有请求都失败控制台提示“不在以下 request 合法域名列表中”。原因微信小程序要求所有网络请求的域名必须在小程序管理后台的“开发设置→服务器域名”里配置且必须是HTTPS。开发者工具可以勾选“不校验合法域名”绕过但真机不行。解决把后端接口部署到有HTTPS证书的服务器上域名备案后在小程序后台配置。如果只是毕设演示可以用内网穿透工具临时生成一个HTTPS地址但注意这个地址不能用于正式上线。另外appid必须是小程序真实的appid测试号不支持配置域名。4.2 数据库中文乱码菜品名称变成问号现象管理端添加菜品时输入中文保存到数据库后变成???小程序端展示也是乱码。原因MySQL数据库、表、连接字符集不一致。常见的是数据库默认latin1或者JDBC连接URL没指定字符集。解决建库时指定CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ciJDBC URL加?useUnicodetruecharacterEncodingutf8serverTimezoneAsia/ShanghaiMyBatis-Plus的配置文件里不要手动改字符集。如果已经建表用ALTER TABLE dish CONVERT TO CHARACTER SET utf8mb4;修复。4.3 订单重复提交用户点一次生成两笔订单现象用户点击“提交订单”后网络稍慢用户又点了一次结果生成两笔相同订单。原因前端没有防重复提交机制后端也没有幂等校验。解决前端在点击后立即禁用按钮请求完成后再恢复后端用order_no唯一索引兜底或者在Redis里用userIdcartHash做5秒锁。毕设如果没有Redis可以在order表加一个request_id字段前端每次进入订单确认页生成一个UUID后端插入前先查这个request_id是否存在。4.4 微信支付回调没收到订单状态一直是待支付现象用户支付成功但订单状态没变管理端看不到新订单。原因微信支付回调地址必须是公网可访问的HTTPS地址且不能带参数。本地开发时回调地址是localhost微信服务器根本请求不到。解决开发阶段用内网穿透把本地接口暴露到公网把穿透地址配到微信支付回调里。回调接口要做签名验证防止伪造请求。回调处理成功后必须返回SUCCESS的XML否则微信会重复通知。如果毕设不要求真实支付可以做一个“模拟支付”按钮点击后直接改状态答辩时说明即可。4.5 小程序顶部导航栏高度不一致页面内容被遮挡现象自定义导航栏后不同机型顶部高度不一样有的页面内容被胶囊按钮挡住。原因微信小程序的导航栏高度由状态栏高度标题栏高度组成不同手机状态栏高度不同。解决用wx.getSystemInfoSync()获取statusBarHeight标题栏高度安卓固定48pxiOS固定44px。自定义导航栏时页面内容要加padding-top等于状态栏高度标题栏高度。更简单的做法是直接用微信默认导航栏只在app.json里改navigationBarTitleText和navigationBarBackgroundColor。5. 进阶技巧让毕设从“能跑”变成“能讲”5.1 用拦截器统一处理token和异常后端接口多了以后每个controller都写一遍token校验和异常捕获太啰嗦。我一般写两个拦截器AuthInterceptor校验请求头里的token解析出userId塞到ThreadLocal里GlobalExceptionHandler用RestControllerAdvice捕获BusinessException和系统异常统一返回{code, message, data}格式。Component public class AuthInterceptor implements HandlerInterceptor { Override public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) { String token request.getHeader(token); if (token null || !jwtUtil.validate(token)) { throw new BusinessException(401, 未登录或登录已过期); } UserContext.setUserId(jwtUtil.getUserId(token)); return true; } Override public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) { UserContext.clear(); // 防止线程池复用导致数据串号 } }UserContext用ThreadLocalInteger存userIdafterCompletion里必须remove否则Tomcat线程池复用时会拿到上一个请求的用户ID这个坑非常隐蔽。5.2 菜品图片上传与存储不要存数据库菜品图片如果以base64存数据库表会迅速膨胀查询也慢。正确做法是小程序端用wx.chooseImage选图wx.uploadFile上传到后端后端保存到服务器磁盘或对象存储数据库只存URL路径。PostMapping(/upload) public ResultString upload(RequestParam(file) MultipartFile file) { String originalName file.getOriginalFilename(); String suffix originalName.substring(originalName.lastIndexOf(.)); String fileName UUID.randomUUID().toString() suffix; File dest new File(uploadPath fileName); file.transferTo(dest); return Result.success(/images/ fileName); }uploadPath配置在application.yml里指向服务器的一个绝对路径。同时要配置静态资源映射让/images/**能访问到磁盘文件。如果部署到Linux注意目录权限否则上传会报FileNotFoundException。5.3 答辩时老师最爱问的三个问题第一个“你的订单号怎么保证唯一”回答时间戳用户ID后四位随机数数据库加唯一索引插入冲突时重试。第二个“购物车数据放哪里”回答未登录放本地storage登录后同步到后端下单时后端重新计算价格。第三个“微信支付怎么做的”如果没接真实支付就诚实说用模拟支付重点讲清楚支付回调的幂等处理思路。我自己的习惯是答辩前把整个系统在本地重新部署一遍从建库、改配置、启动后端、编译小程序每一步都截图存下来。老师问“这个功能怎么实现的”直接翻截图比现场敲代码稳得多。另外源码里不要留TODO和注释掉的废代码老师看到会追问“这块是不是没做完”。希望帮到你。本文还有配套的精品资源点击获取