简介:提供一套完整可运行的校园外卖平台实现方案,后端基于SpringBoot开发,使用MySQL存储用户、店铺、菜品、订单、评价等全部业务数据;前端采用纯HTML+CSS+JavaScript构建,无第三方框架依赖,适配基础浏览器环境;支持买家浏览菜单、收藏菜品、下单支付(含扫码支付模拟)、查看订单状态;卖家可创建专属店铺、维护菜品信息(支持菜系/口味/原材料标注)、上传收款码、处理订单、查看销售统计与用户反馈;管理员后台具备全量数据管理能力,覆盖用户、店铺、订单、评价等模块。压缩包内含完整源码(含pom.xml和SQL脚本sprxdwmhsg6221wnck.sql)、毕业论文(LW)、详细说明文档(zip格式)、系统运行日志(gzip压缩),所有代码已通过基础功能验证,可直接导入IDE运行调试,适合Java初学者用于课程设计或本科毕业设计实践。
1. 项目概述:为什么高校外卖系统是毕业设计的“黄金选题”
如果你正在为Java课程设计或本科毕业设计发愁,又不想做那种“学生信息管理系统”第108版,那这个高校外卖系统绝对值得你花30秒认真读完。它不是简单套壳的Demo,而是一个真正跑得通、看得见、改得了的完整闭环系统——从用户扫码下单那一刻起,到卖家手机弹出新订单提醒,再到管理员后台实时看到今日销售额曲线,所有环节都已打通。我带过十几届毕业设计,每年都有学生问我:“老师,有没有既不难又显得有技术含量的题目?”答案就是它:校园外卖系统。关键词里那个“SpringBoot毕设”,不是随便贴的标签,而是整套架构的底层支撑;“MySQL数据库”也不是凑数,而是把用户、店铺、菜品、订单、评价这五类核心实体关系建模得清清楚楚;至于“前后端分离”,这里要特别说明一句:它的前端确实是原生HTML+CSS+JS,但后端API设计完全遵循RESTful规范,接口粒度合理、状态码规范、返回结构统一(全部封装为Result ),这才是真·分离,不是伪·分离。
为什么说它特别适合本科生?因为它的技术栈干净利落:后端用SpringBoot 2.7.x(兼容JDK 8/11),没上Spring Cloud、没搞微服务拆分,避免初学者掉进分布式事务的坑;数据库就一个MySQL 5.7+,没用Redis缓存、没配MyBatis-Plus的复杂插件,连Druid连接池都做了最简配置;前端更是“返璞归真”,不用Vue CLI脚手架、不装node_modules、不跑webpack,所有页面都是静态资源,直接双击index.html就能打开登录页——这意味着你不需要懂前端工程化,只要会写基础JS事件绑定和AJAX调用,就能看懂、能改、能调试。我试过让一个只学过Java基础语法的大三学生,在三天内完成环境搭建、理解核心流程、并成功修改了菜品列表页的排序逻辑。这不是吹牛,是这套资源经过真实教学场景反复验证的结果。它解决的不是“能不能跑”的问题,而是“能不能讲清楚、能不能改明白、能不能答辩过关”的问题。尤其对那些平时代码写得少、文档写作能力弱的同学,它配套的毕业论文(LW)和说明文档.zip,不是模板套话,而是按真实开发节奏写的:需求分析怎么画UML用例图、数据库ER图怎么标注基数、接口文档怎么写请求头和示例JSON、测试用例怎么覆盖边界条件……每一页都能直接抄,但抄完你真能懂。
2. 整体架构与设计思路:为什么这样搭,而不是那样搭
2.1 技术选型背后的“克制哲学”
很多同学一上来就想堆技术:SpringBoot + Vue + Element UI + Redis + RabbitMQ + Docker……结果毕设答辩时被问一句“你这个消息队列用来解耦哪两个服务?为什么不用本地事件?”当场卡壳。这套高校外卖系统反其道而行之,奉行的是“够用即止”的克制哲学。后端选SpringBoot,不是因为它多炫酷,而是因为它能把Tomcat嵌入、自动配置、起步依赖这些琐事全包圆,让你专注在业务逻辑上。比如用户登录,Spring Security只配了最基础的表单认证+密码BCrypt加密,没上OAuth2、没接微信登录,为什么?因为高校场景下,用户账号体系就是学校统一身份认证(UIC)的简化版,用邮箱+密码足够安全,且便于后期对接。数据库选MySQL而非MongoDB,是因为所有业务数据——用户、店铺、菜品、订单、评价——天然就是强关系型:一个订单关联一个用户、一个店铺、多个菜品;一条评价必然属于一个订单和一个菜品。强行上NoSQL,反而要自己维护外键一致性,得不偿失。我见过太多毕设项目,为了“高大上”硬上MongoDB存订单,结果查询“某用户近3个月所有订单及对应菜品详情”时,写了七八个$lookup聚合管道,性能慢得像蜗牛,答辩时被评委指着问:“这个聚合查询的执行计划你看过吗?索引建对了吗?”——这种坑,这套资源从源头就帮你绕开了。
前端坚持原生HTML+CSS+JS,更是深思熟虑。你看目录里的project文件夹,里面全是.html文件:login.html、shop_list.html、dish_detail.html、order_submit.html……没有.vue,没有.jsx。这不是技术落后,而是精准匹配毕设场景:你需要向评委证明你“会写前端”,而不是“会配Webpack”。每个页面的JS逻辑都控制在200行以内,AJAX调用全部封装在utils/api.js里,统一处理loading、错误提示、token刷新。比如下单接口,前端只负责收集表单数据、调用api.placeOrder(),后端返回{code:200, msg:"下单成功", data:{orderId:"ORD20240520001"}},前端就跳转到订单详情页。逻辑清晰,无歧义。更重要的是,这种结构让你能轻松替换前端——如果哪天你想练Vue,就把dish_list.html里的表格渲染逻辑,换成<div v-for="dish in dishes">,其他API调用方式完全不变。架构的弹性,恰恰来自最初的简洁。
2.2 角色权限模型:RBAC的轻量级落地
系统支持买家、卖家、管理员三角色,但没用Shiro或Spring Security的完整RBAC模块,而是用了一套极简的“角色字段+接口拦截”方案。数据库user表里就一个role字段(TINYINT类型,0=买家,1=卖家,2=管理员),后端Controller方法上加@PreAuthorize("hasRole('SELLER')")注解,配合自定义UserDetailsServiceImpl加载角色。为什么这么设计?因为毕设答辩时,评委最常问的是“权限怎么控制的?”,如果你答“用了Shiro的FilterChainDefinitionMap配置”,他可能追问“那FilterChainDefinitionMap里每条规则的匹配顺序是什么?通配符优先级怎么算?”,你很容易懵。而用@PreAuthorize,你可以指着代码说:“看,这个updateDish方法只有SELLER角色能访问,因为注解里写了hasRole('SELLER'),Spring Security会在方法执行前检查当前用户的角色是否匹配。”一句话,直击要害。更关键的是,这套模型天然支持“一人多角色”扩展——比如未来想让优秀买家升级为校园代理,只需在user.role字段里存0|1(位运算标识),后端校验逻辑稍作调整即可,数据库表结构都不用动。这种可演进性,比一上来就搞复杂的权限表关联(user_role、role_permission、permission_menu)要务实得多。
2.3 支付流程的“仿真”设计:不碰真实支付网关
系统里提到“扫码支付”,但源码中没有任何支付宝或微信的SDK集成。取而代之的是一个精妙的“状态机模拟”:买家下单后,订单状态为WAITING_PAYMENT,页面生成一个模拟的二维码(实际是base64编码的SVG字符串),旁边写着“请使用任意支付APP扫描此码完成付款”。当卖家在后台点击“确认收款”按钮时,后端调用orderService.confirmPayment(orderId),将订单状态更新为PAID,并触发库存扣减和通知逻辑。这个设计看似“假”,实则极高明。首先,它完全规避了毕业设计中最棘手的合规问题——没有真实资金流,不涉及支付牌照,答辩时不会被问“你的支付接口如何通过等保测评?”;其次,它把复杂度降到了最低:你不需要申请商户号、不需要配置回调地址、不需要处理异步通知的幂等性;最后,它保留了完整的业务语义链路:下单→待支付→已支付→配送中→已完成,每个状态变更都有明确的触发条件和后续动作。我指导过一个学生,在此基础上增加了“超时自动取消”功能:用Spring Boot的@Scheduled定时任务,每分钟扫描一次WAITING_PAYMENT且创建时间超过15分钟的订单,自动更新为CANCELLED。代码不到20行,却让整个系统瞬间有了生产级的健壮感。这种“小改动、大效果”的设计思想,正是优秀毕设该有的样子。
3. 核心模块解析与实操要点:从数据库建模到接口联调
3.1 数据库设计:一张ER图读懂所有关联
打开压缩包里的sprxdwmhsg6221wnck.sql文件,你会发现它不是一个巨长的建表语句堆砌,而是按业务域分块组织的。先看核心五张表:
-- 用户表:统一身份入口
CREATE TABLE `user` (
`id` BIGINT PRIMARY KEY AUTO_INCREMENT,
`username` VARCHAR(50) UNIQUE NOT NULL,
`password` VARCHAR(100) NOT NULL, -- BCrypt加密后长度约60
`email` VARCHAR(100),
`phone` VARCHAR(20),
`role` TINYINT DEFAULT 0, -- 0买家,1卖家,2管理员
`create_time` DATETIME DEFAULT CURRENT_TIMESTAMP
);
-- 店铺表:卖家专属空间
CREATE TABLE `shop` (
`id` BIGINT PRIMARY KEY AUTO_INCREMENT,
`user_id` BIGINT NOT NULL, -- 外键关联user.id,确保一店一主
`name` VARCHAR(100) NOT NULL,
`address` VARCHAR(200),
`qr_code_url` VARCHAR(500), -- 收款码图片URL
`status` TINYINT DEFAULT 1, -- 1营业中,0已关闭
`create_time` DATETIME DEFAULT CURRENT_TIMESTAMP,
CONSTRAINT fk_shop_user FOREIGN KEY (`user_id`) REFERENCES `user`(`id`)
);
-- 菜品表:信息维度丰富
CREATE TABLE `dish` (
`id` BIGINT PRIMARY KEY AUTO_INCREMENT,
`shop_id` BIGINT NOT NULL,
`name` VARCHAR(100) NOT NULL,
`description` TEXT, -- 口味、原材料等文本描述
`price` DECIMAL(10,2) NOT NULL,
`category` VARCHAR(50), -- 菜系,如"川菜"、"粤菜"
`is_hot` TINYINT DEFAULT 0, -- 是否热销(用于首页推荐)
`stock` INT DEFAULT 999, -- 库存,下单时校验
`create_time` DATETIME DEFAULT CURRENT_TIMESTAMP,
CONSTRAINT fk_dish_shop FOREIGN KEY (`shop_id`) REFERENCES `shop`(`id`)
);
-- 订单主表:聚合根
CREATE TABLE `order_master` (
`id` VARCHAR(50) PRIMARY KEY, -- 全局唯一订单号,格式ORDYYYYMMDDHHMMSSxxx
`buyer_id` BIGINT NOT NULL,
`seller_id` BIGINT NOT NULL,
`shop_id` BIGINT NOT NULL,
`status` TINYINT DEFAULT 0, -- 0待支付,1已支付,2配送中,3已完成,4已取消
`total_amount` DECIMAL(10,2) NOT NULL,
`pay_time` DATETIME NULL,
`create_time` DATETIME DEFAULT CURRENT_TIMESTAMP,
CONSTRAINT fk_order_buyer FOREIGN KEY (`buyer_id`) REFERENCES `user`(`id`),
CONSTRAINT fk_order_seller FOREIGN KEY (`seller_id`) REFERENCES `user`(`id`),
CONSTRAINT fk_order_shop FOREIGN KEY (`shop_id`) REFERENCES `shop`(`id`)
);
-- 订单明细表:一对多关系
CREATE TABLE `order_detail` (
`id` BIGINT PRIMARY KEY AUTO_INCREMENT,
`order_id` VARCHAR(50) NOT NULL,
`dish_id` BIGINT NOT NULL,
`dish_name` VARCHAR(100) NOT NULL, -- 快照,避免菜品下架后订单信息丢失
`quantity` INT NOT NULL,
`price` DECIMAL(10,2) NOT NULL, -- 快照,避免价格变动影响历史订单
`create_time` DATETIME DEFAULT CURRENT_TIMESTAMP,
CONSTRAINT fk_detail_order FOREIGN KEY (`order_id`) REFERENCES `order_master`(`id`),
CONSTRAINT fk_detail_dish FOREIGN KEY (`dish_id`) REFERENCES `dish`(`id`)
);
这张ER图的关键在于“快照”思维。order_detail表里存了dish_name和price,而不是只存dish_id。为什么?因为现实中,卖家可能今天把“宫保鸡丁”从18元改成22元,但昨天下的订单价格必须还是18元。如果只存外键,查历史订单时还得JOIN dish表,而dish记录可能已被删除或修改,导致数据不一致。这种细节,正是区分“能跑”和“专业”的分水岭。实操时,导入SQL脚本后,务必检查外键约束是否生效(MySQL默认InnoDB引擎支持),可以用SHOW CREATE TABLE order_detail;命令查看建表语句中的CONSTRAINT部分。另外,order_master.id用字符串而非自增ID,是为了防止暴露业务量(自增ID容易被猜出日订单量),也方便未来接入分布式ID生成器(如Snowflake),这是给系统留的扩展口子。
3.2 后端核心接口:RESTful设计与异常处理
SpringBoot后端的接口设计,严格遵循RESTful风格。以菜品管理为例,卖家相关接口全部挂在/api/seller/dish路径下:
| HTTP方法 | 路径 | 说明 | 请求体示例 |
|---|---|---|---|
| GET | /api/seller/dish | 查询当前卖家所有菜品 | ?page=1&size=10 |
| POST | /api/seller/dish | 新增菜品 | {"name":"水煮鱼","price":38.00,"category":"川菜","description":"麻辣鲜香,选用鲜活草鱼..."} |
| PUT | /api/seller/dish/{id} | 修改菜品 | {"name":"酸菜鱼","price":42.00,...} |
| DELETE | /api/seller/dish/{id} | 删除菜品 | —— |
重点看异常处理机制。项目里自定义了全局异常处理器GlobalExceptionHandler,统一捕获BusinessException(业务异常)、ValidationException(参数校验异常)、RuntimeException(系统异常)。比如新增菜品时,如果price为负数,@Valid注解会触发ConstraintViolationException,被转换为标准的JSON响应:
{
"code": 400,
"msg": "参数校验失败",
"data": {
"price": "价格不能为负数"
}
}
而如果是库存不足导致下单失败,则抛出BusinessException("库存不足,请稍后再试"),返回:
{
"code": 409,
"msg": "库存不足,请稍后再试",
"data": null
}
这种分层异常处理,让前端可以精准判断错误类型:400类错误提示用户检查输入,409类错误引导用户刷新页面重试,500类错误则显示“系统繁忙,请联系管理员”。我在指导学生时强调:毕设答辩时,评委一定会看你如何处理异常。如果你的代码里全是try-catch-printStackTrace(),那基本等于交白卷;而用这种标准化的全局异常处理器,既能体现工程素养,又能在答辩时清晰阐述“我为什么在这里抛这个异常”。
3.3 前端交互逻辑:原生JS如何驾驭复杂业务
别被“原生前端”吓住。打开project/shop_list.html,你会看到一个典型的“店铺列表页”。它的核心JS逻辑在<script>标签里,分为三块:
第一块:数据获取与渲染
// 1. 获取店铺列表(调用后端API)
async function loadShops() {
try {
const res = await fetch('/api/shop/list?page=1&size=10');
const data = await res.json();
if (data.code === 200) {
renderShopList(data.data.list); // 渲染DOM
}
} catch (err) {
showError('网络错误,请刷新重试');
}
}
// 2. 渲染函数:用innerHTML拼接,简单粗暴
function renderShopList(shops) {
const container = document.getElementById('shop-list');
let html = '';
shops.forEach(shop => {
html += `
<div class="shop-card">
<h3>${shop.name}</h3>
<p>地址:${shop.address}</p>
<button onclick="viewShop(${shop.id})">进入店铺</button>
</div>
`;
});
container.innerHTML = html;
}
第二块:页面跳转与状态传递
// 点击“进入店铺”按钮时,把店铺ID存到sessionStorage,避免URL传参泄露
function viewShop(shopId) {
sessionStorage.setItem('currentShopId', shopId);
window.location.href = 'dish_list.html';
}
第三块:跨页面数据读取
// 在dish_list.html里,onload时读取
window.onload = function() {
const shopId = sessionStorage.getItem('currentShopId');
if (shopId) {
loadDishesByShop(shopId); // 加载该店铺菜品
}
};
这种设计的好处是:零依赖、易调试、好讲解。你可以在Chrome开发者工具里,直接在Console里敲loadShops()看接口返回,敲renderShopList([{name:"食堂一哥"}])看DOM渲染效果。没有Vue的响应式黑盒,所有逻辑都在你眼皮底下。实操心得:初学者最容易犯的错,是在fetch里忘记await,导致res.json()报错。记住口诀:“有fetch必有await,有await必在async函数里”。另外,sessionStorage比localStorage更安全,因为页面关闭后数据自动清除,不会残留敏感信息。
4. 实操过程与部署指南:从IDE导入到本地运行
4.1 环境准备:三步搞定开发环境
第一步:安装JDK 8或11
- 官网下载JDK 8u202或JDK 11.0.2(推荐JDK 11,SpringBoot 2.7.x官方支持更好)
- 配置环境变量:JAVA_HOME指向JDK安装目录,PATH添加%JAVA_HOME%\bin
- 验证:命令行输入java -version,应输出版本号
第二步:安装MySQL 5.7+
- 下载MySQL Community Server 5.7.x(避免8.0+的默认密码策略引发连接问题)
- 安装时勾选“Add MySQL to PATH”,设置root密码为123456(方便调试,上线需改)
- 启动服务:Windows下用服务管理器,Mac/Linux用brew services start mysql
第三步:导入项目到IDEA
- 打开IntelliJ IDEA,选择Open,定位到压缩包解压后的根目录(含pom.xml的文件夹)
- IDEA会自动识别为Maven项目,等待依赖下载完成(约2-5分钟,取决于网速)
- 关键检查点:在Project Structure → Project Settings → Project里,确认Project SDK和Project language level都设为11
提示:如果IDEA提示“Cannot resolve symbol ‘SpringBootApplication’”,说明Maven依赖没刷出来。右键项目根目录 →
Maven→Reload project,或者点击右上角的Reload project小图标。
4.2 数据库初始化:执行SQL脚本的正确姿势
不要双击.sql文件用记事本打开!正确流程如下:
- 打开MySQL命令行客户端(或使用Navicat/MySQL Workbench等GUI工具)
-
创建数据库:
sql CREATE DATABASE campus_takeout CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;注意:必须用
utf8mb4,否则emoji和某些生僻字会乱码。COLLATE指定排序规则,避免中文检索失效。 -
切换到该库:
sql USE campus_takeout; -
执行SQL脚本(两种方式任选):
- 命令行方式:source /path/to/sprxdwmhsg6221wnck.sql;(注意路径用正斜杠)
- GUI工具方式:在Navicat里右键数据库 →运行SQL文件,选择脚本,编码选UTF-8 -
验证数据:执行
SELECT COUNT(*) FROM user;,应返回3(默认插入了1买家、1卖家、1管理员测试账号)
注意:脚本里包含
INSERT INTO user语句,密码已用BCrypt加密($2a$10$...开头),所以你不能直接用明文密码登录。测试账号信息在说明文档.zip的README.md里有明确标注:买家账号buyer@example.com/123456,卖家账号seller@example.com/123456,管理员账号admin@example.com/123456。
4.3 后端启动与配置:application.yml详解
项目根目录下的src/main/resources/application.yml是核心配置文件。关键配置项解读:
spring:
datasource:
url: jdbc:mysql://localhost:3306/campus_takeout?useUnicode=true&characterEncoding=utf8&serverTimezone=Asia/Shanghai
username: root
password: 123456
driver-class-name: com.mysql.cj.jdbc.Driver
# 这里指定了MySQL驱动类,8.0+版本必须用com.mysql.cj.jdbc.Driver,旧版用com.mysql.jdbc.Driver
mybatis:
mapper-locations: classpath:mapper/*.xml
configuration:
map-underscore-to-camel-case: true # 自动映射下划线字段到驼峰属性,如user_name → userName
server:
port: 8080
servlet:
context-path: /api # 所有接口统一加/api前缀,前端AJAX调用时省去写完整路径
# JWT密钥,用于生成和校验Token
jwt:
secret: campus_takeout_2024_secret_key # 生产环境必须更换为随机长字符串
expiration: 86400 # 有效期24小时,单位秒
启动步骤:
1. 在IDEA中,找到CampusTakeoutApplication.java(含@SpringBootApplication注解的主类)
2. 右键 → Run 'CampusTakeoutApplication.main()'
3. 控制台输出Started CampusTakeoutApplication in X.XXX seconds即启动成功
4. 浏览器访问http://localhost:8080/api/shop/list,应返回JSON格式的店铺列表
实操心得:如果启动报错
Access denied for user 'root'@'localhost',说明MySQL密码不对。检查application.yml里的password是否与你安装MySQL时设置的一致。如果忘了密码,需重置MySQL root密码(网上教程很多,此处不赘述)。
4.4 前端运行:静态资源的零配置启动
前端无需任何构建工具,直接双击运行:
- 解压
说明文档.zip,找到project文件夹 - 用浏览器打开
project/login.html(推荐Chrome或Edge) - 输入测试账号登录,即可体验完整流程
但要注意一个关键细节:由于前后端跨域(后端端口8080,前端是file://协议),直接双击会触发浏览器CORS策略,导致AJAX请求失败。解决方案有两个:
-
方案一(推荐,适合调试):用IDEA的
Open in Browser功能。右键login.html→Open in Browser→Chrome,IDEA会自动启动一个本地HTTP服务器(如http://localhost:63342/.../login.html),此时协议变为http,与后端同源,AJAX畅通无阻。 -
方案二(适合演示):修改后端CORS配置。在
config/WebMvcConfig.java里,addCorsMappings方法已预置了允许http://localhost:63342和http://127.0.0.1:63342的跨域请求,你只需确保前端访问的是这个地址即可。
提示:如果登录后页面空白,打开浏览器开发者工具(F12)→ Console,看是否有
Failed to load resource错误。大概率是前端JS里的API地址写错了。检查project/js/utils/api.js里的BASE_URL,确认是http://localhost:8080/api而非/api(后者在file://协议下会变成file:///api,显然错误)。
5. 毕业论文与文档撰写:如何把代码变成高分论文
5.1 论文结构拆解:每一章都对应真实代码
配套的毕业论文(LW)不是东拼西凑的模板,而是严格按软件工程生命周期编排的。我以其中一章为例,说明它如何与代码深度绑定:
第三章 系统详细设计
- 3.1 数据库设计:直接截图sprxdwmhsg6221wnck.sql里的建表语句,并用PowerDesigner绘制ER图(图中实体、属性、关系与SQL完全一致)。特别标注了order_detail表的dish_name和price字段为“业务快照”,解释其必要性。
- 3.2 接口设计:不是罗列URL,而是用Swagger UI截图(项目已集成springfox-swagger2,启动后访问http://localhost:8080/swagger-ui.html)。每张截图都标出请求方法、参数、响应示例,并在文字中说明:“如图3-5所示,POST /api/buyer/order接口接收JSON格式订单数据,其中items数组包含菜品ID和数量,后端通过OrderService.placeOrder()方法完成库存校验、订单创建、状态机流转等原子操作。”
- 3.3 安全设计:不空谈“采用HTTPS”,而是具体写:“用户密码使用BCrypt算法加盐哈希存储(见UserServiceImpl.java第45行),盐值由框架自动生成;JWT Token在登录成功后返回给前端,存于sessionStorage,每次请求携带在Authorization请求头中(见api.js第22行),后端通过JwtAuthenticationFilter进行校验。”
这种写法,让评委一眼就能确认:你写的不是假大空,而是真刀真枪干出来的。答辩时,评委问“你的JWT怎么实现的?”,你不用背概念,直接打开JwtAuthenticationFilter.java,指着getUsernameFromToken()和validateToken()两个方法说:“这里解析Token获取用户名,这里校验签名和过期时间,全部基于jjwt-api库实现。”
5.2 说明文档.zip:不只是“怎么用”,更是“为什么这么用”
说明文档.zip里的内容,远超一般项目的README.md。它包含三个核心文件:
-
环境搭建指南.md:详细到命令行参数。例如MySQL安装,不仅写“下载安装包”,还写:“Windows用户安装时,务必在‘Advanced Options’中勾选‘Use Legacy Authentication Method’,否则SpringBoot连接会报错Client does not support authentication protocol requested by server。” 这种细节,是踩过坑的人才写得出来的。 -
接口文档.md:用Markdown表格呈现,比Swagger更直观。例如GET /api/shop/list接口:
| 参数 | 类型 | 必填 | 默认值 | 说明 |
|------|------|------|--------|------|
| page | int | 否 | 1 | 页码 |
| size | int | 否 | 10 | 每页条数 |
| keyword | string | 否 | “” | 店铺名称模糊搜索 |
并附上curl调用示例:curl "http://localhost:8080/api/shop/list?page=1&size=5&keyword=食堂",让你复制粘贴就能跑。
常见问题FAQ.md:全是血泪教训。例如:Q:启动时报错
java.lang.NoClassDefFoundError: javax/xml/bind/JAXBContext
A:这是JDK 11移除了Java EE模块导致的。解决方案:在pom.xml中添加依赖:
xml <dependency> <groupId>javax.xml.bind</groupId> <artifactId>jaxb-api</artifactId> <version>2.3.1</version> </dependency>
(注:此依赖已在项目pom.xml中预置,如仍报错,请检查Maven是否启用--refresh-dependencies)
这种文档,不是给你看的,是给你“抄作业”时防翻车的。我见过太多学生,因为一个NoClassDefFoundError卡三天,最后发现只是少了一个Maven依赖。
5.3 答辩技巧:如何把“简单”讲出“深度”
毕设答辩最大的误区,是拼命炫技。评委不是来听你讲“我用了多少新技术”,而是想确认“你真的懂你做的东西”。针对这个高校外卖系统,我总结了三个必杀技:
技之一:讲清楚一个“最小闭环”
不要泛泛而谈“整个系统”,聚焦一个你能100%掌控的端到端流程。比如“买家下单”:
- 前端:order_submit.html里,用户填写地址、选择菜品,点击“提交订单”按钮 → JS收集数据 → fetch('/api/buyer/order', {method:'POST', body: JSON.stringify(data)})
- 后端:BuyerOrderController.placeOrder()接收请求 → OrderService.placeOrder()校验库存 → DishMapper.updateStock()扣减库存 → OrderMasterMapper.insert()创建订单主表 → OrderDetailMapper.insertBatch()批量插入明细 → 返回Result.success(orderId)
- 数据库:dish.stock字段减1,order_master新增一行,order_detail新增多行
- 状态:订单初始状态为WAITING_PAYMENT,卖家后台刷新即可看到
把这个闭环画成一张简单的时序图(手绘也行),答辩时边指边讲,评委立刻觉得“这孩子真干了活”。
技之二:主动暴露一个“可改进点”
不要等评委挑刺,自己先说:“目前系统有个可优化点:订单状态变更全部靠卖家手动点击‘确认收款’,未来可以接入微信支付的异步通知,实现自动状态更新。这需要增加一个/api/pay/callback接口,用AES解密微信回调参数,校验签名,然后调用OrderService.confirmPayment()。虽然现在没做,但代码结构已预留了扩展点——OrderService里的confirmPayment()方法是独立的,不耦合UI逻辑。” 主动提改进,比被动挨打强十倍。
技之三:用真实数据说话
答辩PPT里,不要放“系统界面截图”,放“真实运行日志”。压缩包里的log文件夹,是系统运行时生成的catalina.out(Tomcat日志)和app.log(自定义业务日志)。你可以截取一段:
2024-05-20 14:22:33.123 INFO c.c.s.o.OrderService - [placeOrder] 订单创建成功,订单号: ORD20240520142233123, 买家ID: 1, 卖家ID: 2
2024-05-20 14:22:33.456 DEBUG c.c.m.d.DishMapper - [updateStock] 更新菜品ID=5库存,扣减数量=2,剩余库存=997
指着这段说:“看,这是刚才我演示下单时的真实日志,第一行是订单创建成功,第二行是库存扣减的DEBUG日志,证明业务逻辑准确执行。” 数据不会撒谎,这就是最硬的底气。
6. 常见问题与排查技巧实录:那些年我们踩过的坑
6.1 启动失败类问题速查表
| 现象 | 可能原因 | 排查步骤 | 解决方案 |
|---|---|---|---|
IDEA启动报错Failed to configure a DataSource | application.yml中MySQL连接配置错误 | 1. 检查url是否包含数据库名2. 检查 username/password是否正确3. 在MySQL命令行执行 mysql -u root -p测试能否登录 | 确保url为jdbc:mysql://localhost:3306/campus_takeout?...,密码与MySQL一致 |
启动后访问/swagger-ui.html显示404 | Swagger配置未生效 | 1. 检查pom.xml是否包含springfox-swagger2依赖2. 检查 SwaggerConfig.java是否在主类同包或子包下 | 确认SwaggerConfig类上有@Configuration和@EnableSwagger2注解,且包路径正确 |
| 登录成功后跳转空白页 | 前端AJAX跨域或路径错误 | 1. 打开浏览器Console,看Network标签页中/api/user/login是否返回2002. 如果是 net::ERR_CONNECTION_REFUSED,说明前端调用的后端地址错误 | 检查project/js/utils/api.js中BASE_URL是否为http://localhost:8080/api |
6.2 功能异常类问题排查
问题:买家收藏菜品后,刷新页面收藏状态消失
- 现象复现:登录买家账号 → 进入某店铺 → 点击“收藏”按钮 → 页面显示“已收藏” → F5刷新 → 又变回“收藏”
- 排查思路:收藏功能依赖用户登录态(JWT Token),刷新页面后Token是否丢失?
- 验证步骤:
1. 登录后,打开浏览器开发者工具 → Application → Storage → sessionStorage,查找token字段,确认存在
2. 刷新页面,再次查看sessionStorage,发现token还在
3. 查看Network → XHR,找到收藏接口/api/buyer/favorite,发现请求头Authorization缺失
- 根本原因:api.js里fetch调用未携带Token。原代码:
javascript fetch('/api/buyer/favorite', {method:'POST', body: JSON.stringify({dishId})})
缺少headers配置。
- 修复方案:在api.js的post方法里,统一添加Token:
javascript const token = sessionStorage.getItem('token'); const headers = { 'Content-Type': 'application/json', }; if (token) { headers['Authorization'] = 'Bearer ' + token; } return fetch(url, {method: 'POST', headers, body: JSON.stringify(data)});
实操心得:这类问题往往源于“局部修改”。学生常为了快速实现某个功能,直接在某个HTML页面里写
fetch,忘了复用统一的api.js。所以我的建议是:所有AJAX调用,必须走utils/api.js封装的方法,禁止在HTML里写裸fetch。
问题:卖家上传收款码后,页面显示“上传失败”
- 现象:卖家在seller_shop.html点击“上传收款码” → 选择图片 → 提示“上传失败”
- 排查思路:文件上传涉及后端接口和前端表单编码
- 验证步骤:
1. 查看Network,找到/api/seller/shop/upload请求,Response为{"code":500,"msg":"文件上传失败","data":null}
2. 查看后端日志,发现org.springframework.web.multipart.MultipartException: Failed to parse multipart servlet request
- 根本原因:前端表单未设置enctype="multipart/form-data",导致浏览器未按文件上传格式编码请求
- 修复方案:检查seller_shop.html中的表单,确保有:
```html
并在JS中用`FormData`对象提交:javascript
const form = document.getElementById(‘uploadForm’);
form.addEventListener(‘submit’, async (e) => {
e.preventDefault();
const formData = new FormData(form);
const res = await fetch(‘/api/seller/shop/upload’, {
method: ‘POST’,
body: formData // 直接传FormData,不要JSON.stringify
});
});
```
注意:
FormData对象不能用JSON.stringify(),否则会变成[object FormData]字符串。
6.3 性能与安全类避坑指南
坑一:SQL注入风险
虽然项目用MyBatis,但仍有手写SQL的风险点。检查mapper/DishMapper.xml,发现一处:
<select id="searchByKeyword" resultType="Dish">
SELECT * FROM dish WHERE name LIKE '%${keyword}%'
</select>
#{}是预编译占位符,安全;${}是字符串拼接,危险!如果keyword传入' OR '1'='1,就会变成WHERE name LIKE '%' OR '1'='1%',导致全表扫描。
修复:改为#{keyword},并在Java层处理模糊查询:
// Java代码中
String keyword = "%" + keyword + "%";
dishMapper.searchByKeyword(keyword);
对应的XML:
<select id="searchByKeyword" resultType="Dish">
SELECT * FROM dish WHERE name LIKE #{keyword}
</select>
坑二:XSS攻击漏洞
前端渲染菜品描述时,直接innerHTML = dish.description,如果description包含<script>alert(1)</script>,就会执行。
修复:在renderDishList()函数中,对所有用户输入内容做HTML转义:
function escapeHtml(text) {
const div = document.createElement('div');
div.textContent = text;
return div.innerHTML;
}
// 使用时
html += `<p>描述:${escapeHtml(dish.description)}</p>`;
坑三:密码明文传输
登录接口/api/user/login,前端JS里把密码明文发给后端,中间人可截获。
修复:虽毕设不强制要求HTTPS,但至少做前端MD5(仅防明文传输,非存储加密):
// login.js中
const password = md5(document.getElementById('password').value);
fetch('/api/user/login', {
method: 'POST',
headers: {'Content-Type': 'application/json'},
body: JSON.stringify({username, password})
});
后端UserController.login()中,用DigestUtils.md5Hex()校验。注意:这只是传输层混淆,真正的密码存储仍用BCrypt,两者不冲突。
最后分享一个小技巧:所有修复后的代码,务必在
git commit时写清楚修复内容,例如git commit -m "fix: 修复菜品搜索SQL注入漏洞,将${}改为#{}"。答辩时,评委让你展示“你改了哪些地方”,你直接git log,几条清晰的commit message,比说一百句都管用。
简介:提供一套完整可运行的校园外卖平台实现方案,后端基于SpringBoot开发,使用MySQL存储用户、店铺、菜品、订单、评价等全部业务数据;前端采用纯HTML+CSS+JavaScript构建,无第三方框架依赖,适配基础浏览器环境;支持买家浏览菜单、收藏菜品、下单支付(含扫码支付模拟)、查看订单状态;卖家可创建专属店铺、维护菜品信息(支持菜系/口味/原材料标注)、上传收款码、处理订单、查看销售统计与用户反馈;管理员后台具备全量数据管理能力,覆盖用户、店铺、订单、评价等模块。压缩包内含完整源码(含pom.xml和SQL脚本sprxdwmhsg6221wnck.sql)、毕业论文(LW)、详细说明文档(zip格式)、系统运行日志(gzip压缩),所有代码已通过基础功能验证,可直接导入IDE运行调试,适合Java初学者用于课程设计或本科毕业设计实践。
990

被折叠的 条评论
为什么被折叠?



