做点餐系统的人这几年特别多,尤其是“微信小程序+SSM”这个组合,几乎成了课设和毕设里的常客。不少同学上来就担心:SSM是不是太老了?小程序端是不是太难搞?我帮人调过不少这类项目,可以明确说一句:这套组合不但不过时,反而是最适合用来说明“前后端怎么协作”的题材。小程序端有真实的登录态、请求、购物车交互,SSM后端有清晰的三层结构,两边一对接,整个软件工程的链路就完整了。这篇文章会从选题逻辑、流程设计、后端骨架、小程序对接、联调踩坑一直讲到答辩准备,尽量把我在实际项目中反复校验过的方案和细节都写清楚。
1. 从选题到方案:为什么“微信小程序+SSM”总被拿来当作点餐系统的默认组合
1.1 点餐系统为什么是练手的好题目
想找一个既不会太小、又不会复杂到做不完的题目,点餐系统其实是很好的选择。它虽然叫“点餐”,但背后几乎覆盖了一个业务系统该有的所有要素:用户登录、内容展示、购物车、订单状态流转、后台管理、数据统计。一个菜品列表,既要做数据库查询,又要做前端渲染;一个下单操作,既要有事务,又要处理库存,还要生成订单号。做完这一套,你对软件开发的整体认知会比单纯写几个增删改查扎实得多。
更关键的是,点餐系统贴近日常生活,评委和读者不需要业务背景就能理解。你做“供应链协同平台”,可能还要解释什么是供应链;你做“高校实验室管理系统”,还得说清排课和耗材的关系。点餐不一样,人人都点过餐。需求容易讲明白,演示效果也直观,这是它成为经典选题的根本原因。
1.2 SSM不是过时,而是适合“讲原理”的技术栈
很多同学纠结:现在企业都用SpringBoot了,为什么还要用SSM?这个想法我能理解,但放在学习和答辩的场景下,SSM其实有它独特的优势。
SSM全称是Spring + SpringMVC + MyBatis。Spring管对象和事务,SpringMVC管接口路由,MyBatis管数据库操作。三者边界分明,每一条请求从进来到返回,经过了哪些组件、各自做了什么,几乎可以一行一行对着源码讲清楚。SpringBoot则不同,它把大量配置自动化了,很多同学写完一个接口都不知道内嵌Tomcat是怎么启动的,更解释不了自动配置的原理。答辩时老师最常问的一句就是“你这个项目里,Spring到底帮你做了什么”,用SSM回答这道题,你可以从IOC容器、AOP事务说到DispatcherServlet,素材非常多。
不过我也要说句公道话:如果你追求的是快速实现功能、以后打算直接走SpringBoot路线,那用SpringBoot确实效率更高。但如果你希望项目能讲出深度、能应对追问,SSM反而是更好的训练场。
1.3 小程序端给项目加了一层“真实感”
和纯后台管理系统的课设相比,微信小程序端带来的最大价值是“真实感”。真机扫码、手机点击、点餐下单,用户天然觉得这就是一个可用的产品。这种体验上的优势,在答辩演示环节尤其明显。
从技术角度看,小程序端的开发也很有含金量。wx.login获取登录凭证、request发起网络请求、setData驱动视图更新、本地缓存做持久化,这些知识点放到真实企业项目中依然成立。小程序端的代码量虽然不大,但它逼着你思考“前端需要什么数据、后端怎么提供这些数据”,这一个接口设计的过程,恰恰是很多同学平时最欠缺的。所以别再觉得小程序端只是附赠品,认真做完它,你的收获会和别人拉开差距。
需要模型API调用? 免费领10W Token,多模型网关一键接入 Claude、DeepSeek 等主流模型。
2. 业务流程先行:先想清楚“谁在用、怎么流转”,再动手建表
2.1 把角色和用例盘清楚
我见过太多人拿到题目第一件事就是建表,结果建到一半发现字段不够用,又回来改。正确的做法是先盘清楚“谁在用系统、每个角色能干什么”,把用例写出来再设计表结构。
点餐系统至少有三个角色:
- 普通用户:在小程序端浏览菜品、加入购物车、提交订单、查看订单状态、确认收货。
- 商家/店长:在后台管理端维护菜品分类、上下架菜品、处理用户订单(接单、出餐、完成)。
- 系统管理员:可选项,负责账号管理、数据统计等。如果只是课设,这一步可以弱化,但保留一个管理员角色会让系统层次更完整。
把这些角色对应的功能列成表格,前端页面、后端接口、数据表就都有了大致轮廓。比如“用户提交订单”这个用例,前端要有购物车确认页,后端要有下单接口,数据库要有订单表和订单明细表。一个用例能映射出一整条开发链路,这就叫需求驱动设计。
2.2 核心流程与订单状态机设计
点餐系统的核心链路是:浏览菜品加入购物车提交订单支付商家处理完成。这里最容易出问题的是订单状态的管理。我建议用数字常量来表示状态,而不是直接存中文字符串。原因有两个:一是数字占空间小、查询判断快;二是避免出现“待接单”“待受理”“待确认”这种同义不同名的情况。
以我的项目为例,订单状态定义如下:
| 状态值 | 含义 | 触发动作 |
|---|---|---|
| 0 | 待支付 | 用户提交订单后 |
| 1 | 已支付待接单 | 用户完成模拟支付后 |
| 2 | 已接单制作中 | 商家后台点击接单 |
| 3 | 已出餐/待配送 | 商家点击出餐 |
| 4 | 已完成 | 用户确认收货或商家确认完成 |
| 5 | 已取消 | 用户支付前取消或超时未支付 |
为什么要把状态拆得这么细?因为每一次状态变化,都对应一个操作入口和一条业务规则。比如状态为0时,用户可以取消订单;状态为1之后,取消就要经过商家同意。如果只用一个“未完成/已完成”来概括,后面做后台列表筛选和历史订单展示时,你就会发现状态信息根本不够用。
2.3 数据表设计的多套现场经验
核心表也就是那么几张:用户表(user)、菜品表(dish)、分类表(category)、购物车表(cart)、订单表(orders)、订单明细表(order_detail)。但每一张表的字段怎么定,里面有不少讲究。
用户表要单独说两点。第一,openid字段必须加唯一索引。openid是微信用户在小程序下的唯一标识,同一个用户重复登录时,要根据openid去判断是插入还是更新。第二,用户头像和昵称字段长度要留够。微信昵称现在最多能到32个字符,有的还有emoji,所以表结构要使用utf8mb4,否则存emoji会报错。
菜品表要特别注意价格字段。价格一律用DECIMAL(10,2),不能用FLOAT或DOUBLE。浮点数在计算机里是近似存储,0.1加0.2会得到0.30000000000000004,金额一旦差一分钱,前端展示和订单统计都会出问题。
订单表和明细表有一个非常值得答辩时说的设计:明细表里冗余了菜品名称和菜品价格。为什么?因为订单属于历史数据,如果菜品后来改了价格或删除了,历史订单里存的商品快照不能跟着变。把商品名称、价格在下单那一刻复制一份到明细表,就能保证订单永远可追溯。这叫“快照冗余”,是真实电商系统里常用的手段。
至于要不要建物理外键,我的建议是逻辑外键就好,不要加物理外键。外键约束会让删除和更新变得很麻烦,后期调试时还要考虑关联限制,课设完全没必要。只要在业务代码里保证user_id、dish_id这些字段引用正确即可,用逻辑外键就足够安全了。
3. SSM后端搭骨架:Spring、SpringMVC、MyBatis如何各司其职
3.1 工程结构与依赖配置
后端我建议直接用Maven工程,IDEA创建一个普通的Web项目,不要用Spring Initializr生成SpringBoot,因为我们要手动体验SSM整合的过程。整体目录结构大概是这样:
text复制src/main/java/com/example/order/
├── controller/ # 接口层
│ ├── UserController.java
│ ├── DishController.java
│ └── OrderController.java
├── service/ # 业务层
│ ├── OrderService.java
│ └── impl/
│ └── OrderServiceImpl.java
├── mapper/ # MyBatis的Mapper接口
│ ├── DishMapper.java
│ └── OrderMapper.java
├── entity/ # 实体类
│ ├── Dish.java
│ ├── Orders.java
│ └── OrderDetail.java
├── common/ # 通用类
│ ├── Result.java # 统一返回结果
│ ├── ResultCode.java # 状态码常量
│ └── GlobalExceptionHandler.java
src/main/resources/
├── jdbc.properties # 数据库连接配置
├── spring.xml # Spring核心配置
├── spring-mvc.xml # SpringMVC配置
├── spring-mybatis.xml # MyBatis整合配置
└── mapper/ # Mapper XML文件
pom.xml里最核心的依赖就是那几个:spring-webmvc、mybatis、mybatis-spring、mysql-connector-java、druid、jackson-databind、javax.servlet-api。我特别建议连接池用Druid,别用默认的。Druid自带监控页面,可以看SQL执行时间和并发情况,答辩时打开监控页给老师看一眼,比嘴上说“系统性能很好”有说服力得多。
3.2 父子容器的概念为什么必须搞懂
SSM启动时容易报NoSuchBeanDefinitionException,十有八九是容器配置出了问题。这里有个必须理解的概念:SSM有两个容器。
Spring容器是父容器,由ContextLoaderListener创建,管理service、mapper这些业务组件;SpringMVC容器是子容器,由DispatcherServlet创建,管理controller。子容器可以引用父容器里的Bean,但父容器不能引用子容器里的Bean。如果你把service的扫描配置写进了spring-mvc.xml,而controller又说要注入service,多数情况下也能工作,但这个结构是拧巴的,排查问题时会很别扭。
我的习惯是:spring.xml里只扫描service和mapper相关的包,spring-mvc.xml里只扫描controller包。两个配置文件职责分开,后面加新功能时基本不会因为Bean找不到而卡壳。
3.3 Controller-Service-Mapper的分层边界
很多同学的代码问题不是不工作,而是分层不明确。Controller里写SQL、Service手里全是JSON字符串,这种代码能跑,但没法学到架构思想。
我的建议是三层各司其职:
- Controller层:只做参数接收、参数校验、调用Service、把结果包成统一格式返回。不要在Controller里写任何业务判断。
- Service层:写业务逻辑。比如下单要校验库存、计算金额、插入主表、插入明细、扣减库存、清空购物车,这些都要在Service里完成,并且加上事务。
- Mapper层:只负责单一的SQL操作,方法名要见名知义。selectByCategoryId就是查分类,insertBatch就是批量插入。
下面是一段典型的Controller写法:
java复制@RestController
@RequestMapping("/api/order")
public class OrderController {
@Autowired
private OrderService orderService;
@PostMapping("/submit")
public Result<String> submit(@RequestBody OrderSubmitDTO dto,
@RequestHeader("token") String token) {
// token解析出userId的逻辑可以放在拦截器或Service里
String orderNo = orderService.submitOrder(token, dto);
return Result.success("下单成功", orderNo);
}
}
注意这里我用了@RestController,其实SSM原生是@Controller加@ResponseBody,但@RestController就是这俩的合体,写起来更简洁,答辩时老师也不会觉得有问题。统一返回结构Result里至少包含code、message、data三个字段,前端就能根据code判断是否成功,而不是每次用HTTP状态码来猜业务成功与否。
3.4 MyBatis动态SQL与事务的常见坑
MyBatis的核心是SQL,但SQL多起来之后,动态拼接就成了刚需。菜品列表按分类筛选就是典型的动态查询场景,用
xml复制<select id="selectByCondition" resultType="com.example.order.entity.Dish">
SELECT id, category_id, name, price, image, status, stock
FROM dish
<where>
<if test="categoryId != null">
AND category_id = #{categoryId}
</if>
<if test="keyword != null and keyword != ''">
AND name LIKE CONCAT('%', #{keyword}, '%')
</if>
<if test="status != null">
AND status = #{status}
</if>
</where>
ORDER BY sort_order ASC, id DESC
</select>
这里要特别强调一点:所有参数拼接必须用#{},不能用${}。#{ }会走到预编译,能防止SQL注入;${ }是字符串直接替换,传入特殊字符就可能被注入。有些同学喜欢把order by后面的字段也拼成${},这很危险。如果一定要动态排序,建议先在后端校验字段名是不是在白名单里,再做拼接。
下单接口的事务也很关键。用户一次下单,可能要同时操作订单主表、明细表、菜品库存表、购物车表,任何一个步骤失败,都不应该留下脏数据。在Service方法上加@Transactional注解就能搞定。但我必须提醒你三个事务失效的场景:第一,同类内部调用,方法A调方法B,B的事务不生效;第二,方法不是public的,事务不生效;第三,异常被try-catch吞掉了,事务回滚不了。这三个坑我全踩过,排查时一定要先看日志里SQL执行有没有被包进同一个事务。
4. 小程序端从页面到接口:登录态、购物车、订单提交流程怎么做
4.1 小程序目录结构与页面划分
小程序端我建议按功能拆页面,但不要拆得过散。一个合理的目录结构是这样:
text复制pages/
├── index/ # 首页:菜品分类 + 菜品列表
├── cart/ # 购物车
├── order/
│ ├── confirm/ # 确认订单页
│ └── list/ # 订单列表/订单详情
├── user/ # 个人中心
utils/
├── request.js # 统一请求封装
├── auth.js # 登录态相关
app.js
app.json
app.wxss
首页是核心,可以做成左侧分类、右侧菜品的经典布局,这也是餐馆点餐小程序最常见的交互方式。用户点击“加入购物车”后,购物车数据要先在本地保存(globalData或storage),再在确认订单页根据这些数据调后端接口。除非你想把购物车也做成跨设备同步,否则不建议一开始就把购物车表设计得过于复杂。
4.2 登录态的前后端联动:wx.login到token
微信小程序的登录流程,是这个小程序端最值得讲清楚的部分。标准流程是这样的:
- 小程序端调用wx.login(),获取一个临时code。
- 小程序端把code发给后端自己的登录接口。
- 后端拿到code后,调用微信官方接口 jscode2session,换取openid和session_key。
- 后端拿openid去用户表查询,新用户则自动注册,老用户则更新登录时间。
- 后端生成一个token(可以是UUID,也可以是JWT),返回给小程序端。
- 小程序端把token存入wx.setStorageSync,之后每次请求都放到header的Authorization里。
后端调用微信接口的代码大致长这样:
java复制public String code2Session(String code) {
String url = "https://api.weixin.qq.com/sns/jscode2session"
+ "?appid=" + APPID
+ "&secret=" + SECRET
+ "&js_code=" + code
+ "&grant_type=authorization_code";
// 使用HttpClient或OKHttp发起GET请求,解析返回JSON
// errcode为0时,取openid字段
}
这里有个必须注意的点:code只能用一次,而且有效期很短。如果前端在短时间内重复调用wx.login,后端的同一个code可能已经失效,就会导致登录偶尔失败。解决方案是:在小程序启动时只调用一次登录接口,把登录结果存为Promise,后续所有页面都在这个Promise之后再去拿token,避免重复触发。
另外提醒一句:现在微信已经不支持直接通过wx.getUserProfile一键拿到用户头像和昵称了,想要用户昵称,得用input让用户自己输入;想要头像,得用button的open-type="chooseAvatar"让用户选择。这个是微信最新的用户隐私规范,很多老教程还停留在过去,照抄会踩坑。
4.3 请求封装与购物车本地化
在小程序里,我强烈建议统一封装一个request方法,不要在业务页面里裸写wx.request。这样做的最大好处是:token注入、错误提示、加载动画都可以集中处理。一个完整的request.js核心逻辑如下:
javascript复制const BASE_URL = 'http://192.168.1.100:8080';
function request(path, method, data) {
return new Promise((resolve, reject) => {
wx.showLoading({ title: '加载中' });
const token = wx.getStorageSync('token');
wx.request({
url: BASE_URL + path,
method: method,
data: data,
header: {
'Content-Type': 'application/json',
'token': token
},
success(res) {
wx.hideLoading();
if (res.data.code === 200) {
resolve(res.data.data);
} else if (res.data.code === 401) {
// token失效,跳转登录
wx.navigateTo({ url: '/pages/login/login' });
reject(new Error('未登录'));
} else {
wx.showToast({ title: res.data.message, icon: 'none' });
reject(new Error(res.data.message));
}
},
fail(err) {
wx.hideLoading();
wx.showToast({ title: '网络错误', icon: 'none' });
reject(err);
}
});
});
}
module.exports = { request, BASE_URL };
购物车这块,我的建议是先用本地缓存实现。加购操作就是把一份菜品数据push到数组里,同时更新全局的购物车数量角标。真正下单时,把整个购物车数组传给后端,后端在事务里统一处理。这样写的好处是前期工作量大减,演示效果却不打折;如果后面有精力,再升级成后端购物车也不难。
4.4 后端下单逻辑为什么要“二次计算价格”
提交订单是点餐系统里最核心的接口,这部分逻辑一定要写严谨。我强烈建议前端只传菜品ID和数量,金额由后端重新计算,绝不要信任前端传来的价格。理由很简单:小程序端是可以被改的,前端传一个totalAmount=1元,后端如果直接收下,那系统就没有任何安全可言。
下单接口的标准业务顺序是这样的:
- 根据token解析出用户ID。
- 遍历前端传来的菜品列表。
- 根据菜品ID去数据库查当前价格,计算总金额。
- 校验菜品状态是否为上架、库存是否足够。
- 插入orders订单主表,获取自增主键。
- 批量插入order_detail订单明细表。
- 执行库存扣减:UPDATE dish SET stock = stock - #{num} WHERE id = #{id} AND stock >= #{num};
- 清空购物车(如果购物车存在后端)。
- 返回订单号给前端。
第7步的操作方式非常关键。用UPDATE语句来完成库存扣减,天然带行锁,配合stock >= num条件,可以防止并发下买超库存。这个点如果在答辩时能讲出来,老师会高看你一眼:你不仅想到了并发,还给出了具体解法。
5. 联调阶段的高频坑与排查链路
5.1 小程序真机请求失败的完整排查链路
我接手过的项目里,十个有八个卡在“开发者工具能请求,真机不行”。遇到这种问题,各位不要慌,按照下面的链路一步步查:
- 确认开发者工具里是不是勾了“不校验合法域名”。如果勾了,开发者工具能跑,但真机不认。
- 确认真机预览时,后端监听的IP地址。后端如果监听的是127.0.0.1,真机自然连不上。改为本机局域网IP,并且小程序里的BASE_URL要改成http://192.168.x.x:8080。
- 确认手机和电脑在同一个WiFi下。听起来像废话,但真的有人VLAN隔离导致连不上。
- 确认后端防火墙是否放行了8080端口。Windows本机跑Tomcat时,经常弹防火墙许可,点掉之后就一切正常。
- 确认小程序后台有没有配置合法域名。注意,正式上线时小程序要求request的域名必须是HTTPS的,而且要在mp后台配置到白名单里。测试阶段可以在小程序右上角“...详情-本地设置”里开启“不校验合法域名”,真机调试也能过。
- 看后端日志。如果后端日志完全没有任何请求记录,说明请求根本没到后端;如果到了但返回500,那就是后端代码问题。这能快速区分前后端哪一侧出错。
按照这个顺序查,大部分网络类问题十分钟内能定位。
5.2 数据能查出来但页面不显示:最常见的字段命名问题
前后端联调时,还有一个非常隐蔽的问题:后端返回的字段名是下划线风格,而前端取值用的是驼峰风格。比如数据库字段是create_time,MyBatis默认映射成的JSON字段就是createTime还是create_time,取决于有没有开启驼峰转换。
如果你发现返回的JSON是create_time,而前端写的是order.createTime,那数据就会显示成undefined。解决办法很简单,在MyBatis配置里打开驼峰映射:
xml复制<settings>
<setting name="mapUnderscoreToCamelCase" value="true"/>
</settings>
打开之后,数据库的create_time会自动映射到实体类的createTime属性。如果不用这个配置,就得在SQL里写别名,SELECT create_time AS createTime,这样也能解决,但每个查询都要写,麻烦且容易漏。我强烈建议直接开驼峰映射,这是最省事的方案。
更隐蔽的一个问题是多层嵌套的JSON结构。很多人喜欢直接返回实体类,实体类里又套了其他实体,结果前端一取值发现多了一层数组或对象,调试半天。建议后端设计接口时,先想清楚前端需要的结构,再用DTO/VO来出参,而不是所有接口都直接返回数据库实体。这虽然是后端的设计习惯,但能直接减少小程序端联调时的大量沟通成本。
5.3 中文乱码:一个老生常谈但必须提前处理的坑
中文乱码问题在SSM项目里几乎人人会遇到。出现乱码的原因通常是三层不统一:
- 数据库连接URL没加characterEncoding=utf8。
- 数据库表不是utf8mb4。
- 前端页面或接口响应没指定UTF-8。
我的建议是:建库时直接指定utf8mb4,连接URL里同时带上useUnicode=true&characterEncoding=utf8&serverTimezone=Asia/Shanghai。微信小程序端的JSON本身走的是UTF-8,只要后端接口响应头设置成UTF-8,一般不会再乱。
连接串参考:
properties复制jdbc:mysql://localhost:3306/order_db?useUnicode=true&characterEncoding=utf8&useSSL=false&serverTimezone=Asia/Shanghai
另外,新版本的MySQL驱动包(如8.x)要求必须带serverTimezone,否则启动时还会报时区错误。这个属于新手必踩,提前写上能省不少事。
5.4 微信登录接口的偶发失效
有相当一部分项目,登录接口在开发者工具里次次成功,但真机上时不时报“登录失败”或者“用户信息获取失败”。除了前面说的code只能使用一次的问题,还有几个容易被忽略的原因。
一是后端请求微信接口时超时。jscode2session是一个外网请求,如果后端没有设置超时时间,网络抖动时会让整个登录请求卡很久,前端以为失败了。建议在后端调用微信接口时设置连接超时和读取超时,比如3秒和5秒,并做好失败降级处理。
二是后端没有处理微信返回的errcode非0的情况。比如code换openid时,微信偶尔会返回40029(code无效)或者45011(频率限制)。如果你只取openid字段,而微信返回的是errcode,你的接口可能就空指针了。正确的做法是先判断errcode是否为0,非0时直接把错误信息返回给前端,前端给出“请稍后重试”的提示。
三是演示环境的网络不稳定。如果后端服务器在本地,手机连的是办公WiFi,微信接口请求可能被网络策略拦截。这种问题往往是临时的,多试几次就能过,但演示前一定要提前测好。
6. 从“能跑”到“能答辩”:文档、测试用例和演示怎么准备
6.1 项目文档怎么写才不显得空洞
项目能跑起来只是第一步,课设和毕设里文档质量往往比代码更影响成绩。很多同学的文档喜欢用大段文字堆背景,比如“随着移动互联网的发展,人们的生活发生了翻天覆地的变化”,这种话老师已经看腻了,毫无信息量。真正有价值的是你的设计过程。
我建议文档这样安排:
- 需求分析部分:不要只列功能模块,要把用例图画出来,并用表格描述每个用例的参与者、前置条件、主流程和异常流程。比如“提交订单”这个用例,你要写清楚“用户未登录时点击下单会跳转登录页”这种异常分支。
- 数据库设计部分:给出ER图,并对每一张核心表写设计说明。重点是说明“为什么这个字段存在”“这个状态代码的含义是什么”。比如订单明细表为什么要冗余菜品名称和价格,这个设计点比贴一张建表SQL有价值得多。
- 系统实现部分:不要通篇贴代码,挑几个有亮点的点展开讲。比如“登录态如何设计”、“下单接口的事务控制”、“库存扣减如何防止超卖”。这三个点每个都能讲1000字,而且是有深度的内容。
- 测试部分:给出具体测试用例表格,包含用例编号、测试步骤、输入数据、预期结果、实际结果。下面我给出一个例子。
| 用例编号 | 测试步骤 | 输入数据 | 预期结果 | 实际结果 |
|---|---|---|---|---|
| TC001 | 未登录点提交订单 | 购物车有2件商品 | 跳转登录页 | 通过 |
| TC002 | 正常下单 | 菜品A数量2,菜品B数量1 | 生成订单号,库存扣减 | 通过 |
| TC003 | 库存不足下单 | 菜品C数量999 | 提示库存不足,不回滚脏数据 | 通过 |
| TC004 | 订单状态流转 | 支付后点击接单 | 状态变为制作中 | 通过 |
6.2 演示时最容易翻车的几个环节
答辩演示的现场风险,基本都集中在环境依赖上。提前把下面几件事准备好,至少能挡住一半的意外。
- 数据库服务必须开机自启,最好是MySQL单独启动好,不要现场打开Navicat去连。
- 后端项目用Maven打包成war或jar之前,先在本地完整跑一遍。注意后端不要依赖IDE环境变量,否则换一台机器会起不来。
- 小程序预览前,一定要确保电脑和手机连的是同一个WiFi,并且后端监听地址是0.0.0.0,而不是127.0.0.1。
- 提前准备好演示数据:分类至少3个,菜品至少8个,图片要能正常显示,订单数据要预留一两条已完成的,这样演示“查看历史订单”时不用现等。
- 如果演示时出现“域名不在合法域名列表”,不要慌,扫码预览会有一个“开发调试”入口,点开后可以临时跳过域名校验;或者在开发者工具详情里开启“不校验合法域名”,再重新上传预览。
演示流程建议按用户视角走:打开小程序 -> 微信授权登录 -> 浏览分类 -> 加购物车 -> 提交订单 -> 模拟支付 -> 打开后台看订单状态的变化。一气呵成,控制在5分钟到7分钟,然后留时间回答提问。
6.3 答辩追问的常见问题与回答方向
提前备好这几个高频问题的回答思路,现场就不会卡壳:
为什么用SSM而不是SpringBoot? 可以回答:SSM结构清晰,能把IOC、AOP、事务、动态SQL这些底层原理讲清楚,有利于学习框架核心思想;如果未来要迁移到SpringBoot,主要工作是自动配置替换,业务代码基本不用改。
下单并发时会不会超卖? 这是一个加分题。回答要点是:通过乐观锁或条件更新扣减库存,如UPDATE dish SET stock = stock - 1 WHERE id = ? AND stock >= 1;如果更新行数小于1,说明库存不足,事务回滚。还可以说,如果并发量更大,可以引入Redis分布式锁或消息队列,但课设阶段这个方案已经足够。
token过期了怎么办? 回答要点:前端每次请求拦截401状态码,跳转重新登录;为了保证体验,可增加刷新token机制,简单做法是token有效期设置较长(如7天),并做失败重试。
如果让你升级系统,你会怎么做? 可以往这些方向讲:SpringBoot替换SSM、Redis缓存菜品分类和token、用Elasticsearch做菜品搜索、引入WebSocket做订单实时通知、把文件存储迁移到对象存储。不需要每个都实现,能说清思路就足够显示视野。
我把这套方案反复用了很多次,最深的感受是:做这种全栈项目,难的不是某个技术点,而是把登录、下单、库存、订单状态、前后端联调这些环节串在一起的能力。如果你正在做这个题,建议先跑通“浏览菜品加购下单查订单”这个最小闭环,再慢慢补后台管理和各种优化。最小闭环一旦通了,后面每一步都是加分项;如果一上来就想把系统做得面面俱到,反而容易被细枝末节拖住。
