ARTICLE DETAIL

资讯详情

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

SSM+微信小程序火锅店点餐系统毕设全流程实战指南

SSM+微信小程序火锅店点餐系统毕设全流程实战指南 简介面向高校计算机专业毕业设计场景的微信小程序火锅店点餐系统完整源码包。后台基于SSM框架构建管理页面由Vue渲染小程序端承担用户点餐与桌位预定流程数据库采用MySQLJDK 1.8运行环境支持Eclipse、MyEclipse、STS、IDEA等多种开发工具导入。系统功能设计分为管理员和用户两大角色管理员负责菜品管理、菜品分类、用户管理、订单管理等用户可查询菜品、在线点餐、预定餐桌、管理个人信息。包内文件数量达1369个主要由png图片、java源码、vue组件、js脚本、小程序wxml/wxss页面、json配置及数据库脚本构成并包含论文文档、环境工具包、安装运行脚本与说明文档压缩包体积约68.29MB。附带同框架项目的教学安装教程可帮助使用者从环境搭建到系统部署快速走通。目前已有151人学习下载适合毕业设计参考、课题答辩展示或作为二次开发的基线项目。1. 火锅店点餐系统毕设SSM 微信小程序这条技术栈值不值得做拿到“毕业设计 java 微信小程序火锅店点餐系统的设计与实现”这个题目时我和很多同学想法一样一个点餐系统菜品列表、购物车、下订单能有什么技术含量。真把需求拆开才发现微信小程序和 SSM 的组合里有不少让演示现场翻车的点——登录态怎么维护、购物车状态放前端还是后端、多桌并发下单怎么保证不错账、图片路径怎么在小程序里正常显示。这篇笔记从需求分析开始把数据库设计、小程序端代码、SSM 后端接口、避坑经验一直串到答辩准备全程按可复现的操作写。适合两类人一是用它做毕业设计、需要一整套能跑通的源码和文档的学生二是想用经典 Java 技术栈练一个完整闭环项目的开发者。2. 从需求到建模把火锅店的点餐流程拆成表、接口和职责边界2.1 业务边界扫码点餐、加菜、结账后厨不碰先泼一盆冷水火锅店点餐系统最忌讳把后厨大屏、库存预警和经营报表全塞进去。毕设阶段你的时间是有限的SSM 这套技术栈写起来又不像 Spring Boot 那样靠自动配置省事每多一个模块就意味着多几张表、多几个 Mapper XML、多一堆可以出 bug 的边界。我一般会把边界收在三条主链路上顾客扫码进店看菜单、加菜下单、模拟支付结账管理员登录后台改菜品、上下架、看订单状态。后厨大屏、库存预警这类功能在论文里写成“可扩展设计”实现上不碰。这样四张核心表加两张辅助表就能盖住全部业务源码工程量大概在 30 个类左右对毕设来说非常健康。如果题目硬性要求“后台管理端”就做一个独立的 Web 管理页用 SSM 自带的 JSP 或者加一个简单的 Bootstrap 页面。注意别做成两套系统管理端只是复用同一个 Service 层权限用最朴素的方式区分顾客端接口走 token 校验管理端接口走会话校验两套 Controller 分开。2.2 数据库设计菜品、订单、订单明细、桌台四类表的字段定义表设计是后面所有代码的地基。我刚开始图省事把菜品价格设计成 float后面订单金额一路算一路错。先给核心 SQL这段脚本直接作为初始化脚本放进项目文档里-- 菜品类别 CREATE TABLE category ( id INT(11) NOT NULL AUTO_INCREMENT, name VARCHAR(50) NOT NULL COMMENT 类别名锅底/涮品/饮料/蘸料, sort INT(11) DEFAULT 0 COMMENT 排序值越小越靠前, PRIMARY KEY (id) ) ENGINEInnoDB DEFAULT CHARSETutf8mb4; -- 菜品表 CREATE TABLE dish ( id INT(11) NOT NULL AUTO_INCREMENT, category_id INT(11) NOT NULL COMMENT 所属类别ID, name VARCHAR(100) NOT NULL COMMENT 菜品名, price DECIMAL(10,2) NOT NULL COMMENT 单价必须用DECIMAL, image VARCHAR(255) DEFAULT NULL COMMENT 图片相对路径如 /upload/dish/1.jpg, stock INT(11) DEFAULT 999 COMMENT 库存默认999代表不限量, status TINYINT(1) DEFAULT 1 COMMENT 1上架 0下架, sales INT(11) DEFAULT 0 COMMENT 销量用于展示和排序, PRIMARY KEY (id), KEY idx_category (category_id) ) ENGINEInnoDB DEFAULT CHARSETutf8mb4;两个细节要注意price 用 decimal(10,2) 而不是 float/double这是金额精度的底线后面避坑章节会再展开一次image 字段只存相对路径/upload/dish/1.jpg不存完整http://地址因为毕设项目在开发机、演示机之间搬来搬去IP 一换完整 URL 就全部失效。订单相关两张表是一对多的主从关系CREATE TABLE orders ( id INT(11) NOT NULL AUTO_INCREMENT, order_no VARCHAR(32) NOT NULL COMMENT 业务订单号用户可见, table_no VARCHAR(20) NOT NULL COMMENT 桌台号来自小程序码参数, customer_id INT(11) DEFAULT NULL COMMENT 顾客ID未登录时为空, total_amount DECIMAL(10,2) NOT NULL COMMENT 订单总金额, status TINYINT(1) DEFAULT 0 COMMENT 0待支付 1已支付 2制作中 3已完成 4已取消, create_time DATETIME DEFAULT CURRENT_TIMESTAMP, pay_time DATETIME DEFAULT NULL, PRIMARY KEY (id), UNIQUE KEY uk_order_no (order_no) ) ENGINEInnoDB DEFAULT CHARSETutf8mb4; CREATE TABLE order_item ( id INT(11) NOT NULL AUTO_INCREMENT, order_id INT(11) NOT NULL COMMENT 所属订单ID, dish_id INT(11) NOT NULL, dish_name VARCHAR(100) NOT NULL COMMENT 冗余菜品名快照, price DECIMAL(10,2) NOT NULL COMMENT 下单时单价快照, quantity INT(11) NOT NULL DEFAULT 1, PRIMARY KEY (id), KEY idx_order (order_id) ) ENGINEInnoDB DEFAULT CHARSETutf8mb4;order_item 里冗余 dish_name 和 price 不是浪费是刚需。火锅店的菜单改价、改名很常见如果不做快照历史订单里看到的金额和菜名会跟着实时数据一起变验收时被问到会很尴尬。order_no 用业务单号而不是数据库自增 id 给用户看也是为了让订单号带时间信息查单的时候不用解释自增 id 是多少。桌台的信息我没单独建表把桌台号作为小程序码路径里的固定参数扫桌台码进入小程序时从页面参数取 tableNo下单时带给后端。如果一定要管理桌台状态加一张只有 id、table_no、status 的小表即可优先级最低。2.3 登录与身份顾客走微信登录管理员走账号密码顾客端和管理端是两个入口身份体系必须分开。顾客用微信小程序自带登录能力wx.login 拿到临时 code后端拿这个 code 去微信接口换 openid再用 openid 查用户表查不到就自动注册。这里不要执着于“获取手机号”个人主体的小程序没有这个权限而且现在的 getPhoneNumber 需要企业认证加按钮授权毕设里用 openid 加昵称已经能说明登录闭环。管理员端就简单了一张 admin 表存账号密码SSM 里用传统的 session 登录即可。密码存 MD5 加盐或 BCrypt 哈希别存明文这在论文里是加分项。两块身份接口隔离开避免出现“管理员走微信登录”这种答辩时的逻辑漏洞。2.4 接口清单先定好这 7 个 HTTP 接口再动手写代码做毕设最容易出现的状况是前端和后端各写各的联调时接口对不上。我习惯先把接口清单定成表格发给前后端各自照着实现接口路径方法参数返回结果说明/api/auth/loginPOSTcodetoken、userId小程序登录换 token/api/dish/categoriesGET无ListCategory左侧导航分类/api/dish/listGETcategoryIdListDish按分类查菜品/api/orders/createPOSTtableNo、itemsOrder创建订单需带 token/api/orders/payPOSTorderNonull模拟支付/api/orders/myGET无ListOrder我的订单列表/api/admin/dish/savePOSTdish 表单null管理端新增/修改菜品这个清单定下来前后端就能并行开发。注意所有接口的返回结构统一用后面的 Result 包装前端只需要判断 code 是不是 200其余情况一律弹 message。2.5 前后端分工哪层管状态、哪层管数据我在这类项目里一个重要决定是购物车状态放前端。原因很简单火锅店点餐的购物车是临时性的顾客可能换桌、退单、重新选如果把每个操作都发给后端接口数量翻倍而且还得处理“顾客加了菜但没下单”的垃圾数据。购物车用小程序内存对象保存只在提交订单时一次性发给后端。后端的职责是管数据和事务菜品数据以数据库为准下单时重新查价格重新算总价不信任前端传的金额。这个职责分割在代码里要贯彻到底前端只传 dishId 和 quantity后端返回总价前端只做展示。后面第 4 章的代码会具体体现这一点。3. 小程序端实现从登录到下单的完整调用链3.1 wx.login code2session怎么把微信用户映射成系统用户小程序端第一个要跑通的不是页面是登录。微信小程序登录的链路是wx.login 产生一个临时 code把 code 发给后端后端拿着 code 调微信的 code2session 接口换 openid 和 session_key。一个 code 只能用一次5 分钟有效所以换成 openid 后我会在服务端生成一个自定义 token 返回小程序后续请求都带 token不再依赖 wx.login。// utils/auth.js function wxLogin() { return new Promise((resolve, reject) { wx.login({ success(res) { if (!res.code) { reject(new Error(wx.login 拿不到 code)) return } wx.request({ url: getApp().globalData.baseUrl /api/auth/login, method: POST, data: { code: res.code }, success(resp) { if (resp.data.code 200) { const { token, userId } resp.data.data wx.setStorageSync(token, token) wx.setStorageSync(userId, userId) resolve(resp.data.data) } else { reject(new Error(resp.data.message)) } }, fail: reject }) }, fail: reject }) }) }登录接口传 code、不传昵称头像是我刻意简化的一步。现在微信不再建议把 wx.getUserProfile 当默认弹窗用毕设阶段后端只要拿到 openid 就能建立用户身份昵称头像属于锦上添花。如果你想展示用户信息用 button 的 open-type 能力让用户主动填头像昵称在代码注释里说明这套新规范即可。3.2 菜品分类与购物车页面上的点餐交互和本地状态设计点餐页是这个小程序的门面。我用左侧类别导航、右侧菜品卡片的方式布局顶部放桌台号底部放一个固定的结算栏。类别导航用 scroll-view 实现右侧菜品列表根据当前选中的类别请求后端接口。先看 WXML 结构!-- pages/order/order.wxml -- view classorder-page view classtable-header桌台{{tableNo}}/view view classbody scroll-view classside-bar scroll-ytrue view wx:for{{categoryList}} wx:keyid classside-item {{activeCategoryId item.id ? side-item-active : }} >// pages/order/order.js Page({ data: { categoryList: [], dishList: [], activeCategoryId: 0, tableNo: , cartMap: {} }, onLoad(options) { this.setData({ tableNo: options.tableNo || A01 }) this.loadCategories() }, loadCategories() { wx.request({ url: getApp().globalData.baseUrl /api/dish/categories, success: (res) { const list res.data.data || [] this.setData({ categoryList: list }) if (list.length 0) { this.setData({ activeCategoryId: list[0].id }) this.loadDishes(list[0].id) } } }) }, switchCategory(e) { const id e.currentTarget.dataset.id this.setData({ activeCategoryId: id }) this.loadDishes(id) }, loadDishes(categoryId) { wx.request({ url: getApp().globalData.baseUrl /api/dish/list?categoryId categoryId, success: (res) { const list res.data.data || [] list.forEach(item { if (item.image !item.image.startsWith(http)) { item.image getApp().globalData.baseUrl item.image } }) this.setData({ dishList: list }) } }) }, addCart(e) { const id e.currentTarget.dataset.id const cartMap { ...this.data.cartMap, [id]: (this.data.cartMap[id] || 0) 1 } this.setData({ cartMap }) }, reduceCart(e) { const id e.currentTarget.dataset.id if (!this.data.cartMap[id]) return const cartMap { ...this.data.cartMap, [id]: this.data.cartMap[id] - 1 } this.setData({ cartMap }) } })购物车用对象cartMap存 dishId 到数量的映射核心好处是天然去重同一个菜只保留一条记录。这里有一个 setData 的玄学问题直接给this.data.cartMap赋值改数据再 setData 同一个引用视图经常不刷新所以每次都用展开运算符生成新对象这是小程序开发里性价比最高的习惯。3.3 提交订单前端传什么、后端查什么、金额在哪算提交逻辑是整个调用链里最重要的一环。我在点餐页底部结算栏做“去结算”提交前先检查购物车是否为空然后组装 items 数组发给后端submitOrder() { const cartMap this.data.cartMap const items Object.keys(cartMap) .filter(id cartMap[id] 0) .map(id ({ dishId: Number(id), quantity: cartMap[id] })) if (items.length 0) { wx.showToast({ title: 请先选择菜品, icon: none }) return } wx.request({ url: getApp().globalData.baseUrl /api/orders/create, method: POST, header: { Authorization: wx.getStorageSync(token) }, data: { tableNo: this.data.tableNo, items }, success: (res) { if (res.data.code 200) { const order res.data.data wx.navigateTo({ url: /pages/pay/pay?orderNo order.orderNo amount order.totalAmount }) this.setData({ cartMap: {} }) } else { wx.showToast({ title: res.data.message, icon: none }) } } }) }关键一点items 里只有 dishId 和 quantity没有 price。谁把价格传给后端谁就是在给测试者留后门——小程序请求可以被抓包修改别人把 price 改成 0.01 后下单后端如果直接用前端价格这顿饭就白吃了。后端必须拿 dishId 回表查真实价格再算总价这是整套系统信任边界的核心。3.4 我的订单页状态展示与取消逻辑订单页是给验收时演示“全过程闭环”用的。列表页展示 orderNo、金额、状态用一个 statusText 方法把后端数字状态翻译成中文。这里注意后端返回的 createTime 是 yyyy-MM-dd HH:mm:ss 字符串如果后端没配置日期序列化小程序端会收到一串时间戳数字多加一个格式化方法最省心。取消订单接口一般限定只有 0 待支付 状态能取消后端要校验状态不能只靠前端按钮隐藏。4. SSM 后端三层架构下接口怎么组织才经得起演示4.1 统一返回包装 Result 前端少写一半错误判断SSM 后端最容易被忽略、但最影响开发速度的是接口返回格式。如果不做统一包装每个 Controller 方法返回的数据结构都不一样小程序端每调一个接口就要写一遍判空逻辑出了错也不知道该看哪个字段。我先定义一个泛型结果类public class ResultT { private Integer code; private String message; private T data; public static T ResultT success(T data) { ResultT r new Result(); r.code 200; r.message ok; r.data data; return r; } public static T ResultT error(String message) { ResultT r new Result(); r.code 500; r.message message; return r; } // getter / setter 省略 }配合 Controller 使用就是这样RestController RequestMapping(/api/dish) public class DishController { Autowired private DishService dishService; GetMapping(/categories) public ResultListCategory categories() { return Result.success(dishService.listCategories()); } GetMapping(/list) public ResultListDish list(RequestParam Integer categoryId) { return Result.success(dishService.listByCategory(categoryId)); } }这里我用了 RestController 而不是 Controller省掉每个方法上的 ResponseBody。注意 SSM 项目里 RestController 是 Spring 4 之后才有的如果你的 spring-web 版本偏老需要在 pom 文件里确认版本大于 4.0否则会报请求映射异常。统一返回带来的另一个好处是可以做全局异常处理。定义一个 ControllerAdvice 类把业务异常、空指针异常、参数异常分别映射成不同 code小程序端只需要判断 code 是否等于 200其余全部弹 toast。这样前端逻辑非常单一后端也敢在 Service 里直接 throw 业务异常而不是把错误信息往返回对象里塞。4.2 下单接口事务、金额可信、防超卖三条底线下单接口是整个项目技术含量最集中的地方写的时候按三条底线来要求自己。第一条是事务orders 和 order_item 必须同生共死主表插入了、明细条数不够订单就是脏数据演示时一旦出现“订单存在但点开空白”基本就是这里的问题。Service public class OrderServiceImpl implements OrderService { Autowired private OrderMapper orderMapper; Autowired private OrderItemMapper orderItemMapper; Autowired private DishMapper dishMapper; Transactional(rollbackFor Exception.class) public Order createOrder(OrderDTO dto) { // 1. 回表查真实菜品价格 ListDish dishes dishMapper.selectByIds( dto.getItems().stream().map(OrderItemDTO::getDishId).collect(Collectors.toList()) ); MapInteger, Dish dishMap dishes.stream() .collect(Collectors.toMap(Dish::getId, d - d)); // 2. 计算总金额使用 BigDecimal BigDecimal total BigDecimal.ZERO; for (OrderItemDTO item : dto.getItems()) { Dish dish dishMap.get(item.getDishId()); if (dish null || dish.getStatus() 0) { throw new BusinessException(菜品不存在或已下架 item.getDishId()); } // 3. 扣库存用条件更新防止超卖 int rows dishMapper.reduceStock(item.getDishId(), item.getQuantity()); if (rows 0) { throw new BusinessException(库存不足 dish.getName()); } total total.add(dish.getPrice().multiply(new BigDecimal(item.getQuantity()))); } // 4. 插入订单主表 Order order new Order(); order.setOrderNo(generateOrderNo()); order.setTableNo(dto.getTableNo()); order.setCustomerId(dto.getCustomerId()); order.setTotalAmount(total); order.setStatus(0); orderMapper.insert(order); // 5. 批量插入明细 for (OrderItemDTO item : dto.getItems()) { Dish dish dishMap.get(item.getDishId()); orderItemMapper.insert(new OrderItem(order.getId(), dish.getId(), dish.getName(), dish.getPrice(), item.getQuantity())); } return order; } }第二条底线是金额可信。total 全程用 BigDecimal乘法累加不碰 double。第三条是防超卖库存扣减的 SQL 写成这样UPDATE dish SET stock stock - #{quantity} WHERE id #{dishId} AND stock #{quantity}返回影响行数等于 0 就说明库存不够。这种乐观更新的写法在毕设场景比悲观锁、分布式锁都实用代码量少还能在论文里讲一句“通过条件更新保证库存扣减的原子性”。Transactional 有一个常见的失效场景类内部调用。假设 OrderServiceImpl 里 A 方法调 B 方法B 上标了 Transactional事务不生效因为 Spring 的声明式事务基于代理内部调用走的是 this 引用而不是代理对象。解决方式是把事务方法放到独立的 Service Bean 里或者从 Controller 直接调用事务入口方法。4.3 图片上传与访问映射本地存储如何暴露成 URLSSM 项目做文件上传最常翻车的不是上传逻辑本身而是上传成功后文件在服务器磁盘里但前端访问不到。原因是 Spring MVC 默认把静态资源交给 Servlet 容器处理外部磁盘路径不在它的映射范围内。我在 WebConfig 里加一段资源映射Configuration public class WebConfig implements WebMvcConfigurer { Value(${upload.path}) private String uploadPath; Override public void addResourceHandlers(ResourceHandlerRegistry registry) { // 把 /upload/** 的请求映射到本地磁盘目录 registry.addResourceHandler(/upload/**) .addResourceLocations(file: uploadPath); } }upload.path在 properties 里配置比如upload.pathD:/workspace/upload/。上传接口用 MultipartFile 接收文件存到 uploadPath 下按日期分目录文件名用 UUID 加后缀避免中文名乱码和重名覆盖。这里有个容易被忽略的点文件上传接口的请求体是 multipart/form-data不是 application/jsonController 方法签名要写成RequestParam(file) MultipartFile file。如果全项目统一配置了 Jackson 消息转换器上传接口千万别用 RequestBody 接会直接报 415。4.4 我常用的后端配置端口、MyBatis 驼峰映射、日期格式每次搭 SSM 我都会先确认三个配置少了哪个都会在联调时花掉半天。第一个是 Tomcat 端口和编码尤其 Windows 环境下不乱码server.port8080 spring.characterEncodingUTF-8第二个是 MyBatis 的驼峰映射。数据库字段用下划线命名customer_idJava 实体用驼峰customerId如果不开启 mapUnderscoreToCamelCase查询结果里 customerId 永远为 null。这是 SSM 项目里最典型的黑匣子——代码不报错就是取不到值。configuration settings setting namemapUnderscoreToCamelCase valuetrue/ setting namelogImpl valueSTDOUT_LOGGING/ /settings /configuration第三个是日期序列化。MyBatis 查出来的是 java.util.DateJackson 默认序列化成时间戳小程序端拿到的是 1690000000000 这种数字没法直接展示。在 spring-mvc.xml 里配一个 ObjectMapper 日期格式或者在后端返回 VO 时手动 put 一个createTimeStr字段我后一种做法多一些改动面小。提示这套源码跑起来后先验证 2.4 清单里的 /api/dish/categories 能不能返回 JSON再去看页面。接口通了再调页面能把前后端问题快速分开别一上来就抓小程序的渲染。5. 避坑指南小程序 SSM 最容易翻车的5个位置5.1 真机连不上 localhost请求地址要换成局域网 IP现象微信开发者工具里点菜下单一切正常手机扫码预览后页面白屏或请求一直 pending。 原因开发者工具里请求地址写 http://localhost:8080在电脑上 localhost 指向本机在手机上 localhost 指向手机自己自然连不上后端。 解决把全局配置里的 baseUrl 改成http://电脑局域网IP:8080同时后端 Tomcat 要允许监听所有网卡。解压版 Tomcat 确认 server.xml 里 Connector 的 address 没被绑死Spring Boot 内嵌 Tomcat 默认就是 0.0.0.0。改完记得关掉电脑防火墙或者给 8080 端口加一条入站规则。这一步只靠开发者工具判断不出来必须真机验证。5.2 “不在合法域名列表中”微信的域名白名单拦了 wx.request现象真机上 wx.request 直接 fail控制台报url not in domain list。 原因微信要求 wx.request 的 URL 必须在小程序管理后台的“服务器域名”里登记而且必须 https开发阶段没配置就会拦。 解决如果有备案域名和 https 证书在小程序后台“开发管理-开发设置-服务器域名”里加 request 合法域名如果还没有在开发者工具“详情-本地设置”里勾选“不校验合法域名”但这个选项只对开发者工具生效。真机预览想绕过需要在开发者工具点“预览”时勾选“启用调试模式”配合开发版小程序才能生效。这条是血泪经验一定要在答辩前把域名配置处理好别等到现场演示时被白名单拦住。5.3 登录态静默失效token 过期后没有自动重登现象用户早上用过后台下午再来第一次请求全部 401页面卡在加载中必须杀掉小程序重新进才恢复。 原因后端生成的 token 有时效前端拿到 token 后没有任何过期处理逻辑401 来了也不重新登录。 解决封装统一 request 方法遇到 401 先重新走 wx.login 换新 token再重放原请求。function request(url, method, data, retry) { const token wx.getStorageSync(token) retry retry || 0 return new Promise((resolve, reject) { wx.request({ url: getApp().globalData.baseUrl url, method: method || GET, data: data || {}, header: { Authorization: token }, success(res) { if (res.statusCode 401 retry 1) { wxLogin().then(() request(url, method, data, retry 1).then(resolve)) return } if (res.data.code 200) resolve(res.data.data) else reject(new Error(res.data.message)) }, fail: reject }) }) }这个封装做完登录态问题基本绝迹。小程序端的请求逻辑都走这一个入口也能顺带统一 loading 动画和错误 toast别在页面里直接裸调 wx.request。5.4 金额精度double 拼出来的 0.30000000000000004现象单价 0.1 的蘸料买 3 份结算栏显示 0.30000000000000004。 原因Java 或 JavaScript 的浮点数在二进制下无法精确表示 0.1累加多次后就溢出。 解决数据库金额字段用 decimalJava 侧金额一律 BigDecimal禁止 double 运算前端展示用toFixed(2)做一个统一过滤器。这里不要抱侥幸心理哪怕单价看起来都是整数也要按这个标准写评委反手问一句“0.1 加 0.2 等于多少”你就得现场解释浮点数误差。另外购物车本地算总金额只是展示用后端折算是唯一依据两边金额对不上以接口返回为准。5.5 图片加载不出来相对路径在小程序里指错了地方现象菜品图片在浏览器管理端正常显示小程序端 image 组件一片灰。 原因后端存的图片地址是/upload/dish/1.jpg浏览器会拿当前页面域名去拼所以正常但小程序 image 组件会把相对路径当作小程序代码包内的路径请求到没有文件的地址。 解决后端在返回菜品列表时统一把相对路径拼上服务器地址我是在 Service 层处理加一个if (!image.startsWith(http)) image baseUrl image。注意 baseUrl 要从配置读不要写死在代码里否则部署环境一换又全挂。另一个连带问题是清华/百度图片防盗链那套逻辑在小程序里同样存在图片域名也得配到 downloadFile 合法域名里否则真机只显示一个空白占位。6. 部署验证与答辩把这套源码变成能现场演示的完整闭环6.1 部署顺序JAVA_HOME、SQL 初始化、Tomcat 端口我自己习惯按这个顺序部署每一步验证过再走下一步省得最后出一堆纠缠不清的问题。第一步检查 JAVA_HOME 和 PATHJAVA_HOME 没配好 Tomcat 会闪退这是毕设机房最常见的开场事故第二步用 MySQL 命令行或 Navicat 执行初始化 SQL确认四张表都建出来第三步把项目打成 war 包放进 Tomcat 的 webapps启动后访问 http://localhost:8080/项目名/ 能看到接口返回第四步用微信开发者工具导入小程序工程改 utils/config.js 里的 baseUrl先开发者工具跑通再真机预览。最后按“扫码进店 → 选菜加购 → 提交订单 → 模拟支付 → 管理端看见订单状态变化”这条脚本走一遍全程不超过 10 分钟这就是答辩演示的完整剧本。6.2 评委常问的四个问题怎么接第一个问题是“为什么用 SSM 而不用 Spring Boot”。我的处理思路是把选择题说成继承题毕设要覆盖 SSM 三层架构的学习目标MyBatis 的 XML 配置让你把 SQL 看得更清楚Spring MVC 的拦截器、视图解析都摆在明面上比自动配置更适合课程设计。不用批判 Spring Boot坦诚说它是新项目首选但 SSM 适合展示底层原理。第二个问题是“下单并发怎么办”。直接亮出stock quantity的条件更新 SQL 和 Transactional说明在库存扣减上做了原子操作比空谈“用锁”更有说服力。如果没做库存就坦白说“这里我简化了库存维度”同时指出真实场景中的行锁方案这种诚实的边界描述比硬凹更得分。第三个问题是“微信支付做没做”。诚实说毕设采用模拟支付真实接入需要商户号、证书和回调接口同时补一句“如果要上生产订单创建后调微信 jsapi 下单拿 prepay_id前端 wx.requestPayment 完成支付后端用回调更新订单状态”。能写出这个链路评委就知道你懂原理只是条件受限。第四个问题是“你项目里最难的 bug 是什么”。选 5.2 或 5.4 这种有清晰因果链的坑按现象、原因、解决三步讲比挑一个复杂的技术点讲不清强得多。我自己每次都会被问这个提前把避坑记录整理到论文附录里现场直接翻那一页讲。从需求建模到部署演示这套火锅店点餐系统本质上是一个“把业务流程翻译成技术实现”的训练。答辩前我的习惯是把自己写的每张表、每个接口、每段关键代码都过一遍问自己为什么它是这样写的能不能在几分钟内讲清楚。先想明白再动手这个顺序比多堆两个功能重要得多希望帮到你。本文还有配套的精品资源点击获取
返回列表