高校外卖系统毕业设计全套资源:SpringBoot后端+原生前端+MySQL数据库+论文文档

高校外卖系统毕业设计全套资源:SpringBoot后端+原生前端+MySQL数据库+论文文档
本文还有配套的精品资源点击获取简介提供一套完整可运行的校园外卖平台实现方案后端基于SpringBoot开发使用MySQL存储用户、店铺、菜品、订单、评价等全部业务数据前端采用纯HTMLCSSJavaScript构建无第三方框架依赖适配基础浏览器环境支持买家浏览菜单、收藏菜品、下单支付含扫码支付模拟、查看订单状态卖家可创建专属店铺、维护菜品信息支持菜系/口味/原材料标注、上传收款码、处理订单、查看销售统计与用户反馈管理员后台具备全量数据管理能力覆盖用户、店铺、订单、评价等模块。压缩包内含完整源码含pom.xml和SQL脚本sprxdwmhsg6221wnck.sql、毕业论文LW、详细说明文档zip格式、系统运行日志gzip压缩所有代码已通过基础功能验证可直接导入IDE运行调试适合Java初学者用于课程设计或本科毕业设计实践。1. 项目概述为什么高校外卖系统是毕业设计的“黄金选题”如果你正在为Java课程设计或本科毕业设计发愁又不想做那种“学生信息管理系统”第108版那这个高校外卖系统绝对值得你花30秒认真读完。它不是简单套壳的Demo而是一个真正跑得通、看得见、改得了的完整闭环系统——从用户扫码下单那一刻起到卖家手机弹出新订单提醒再到管理员后台实时看到今日销售额曲线所有环节都已打通。我带过十几届毕业设计每年都有学生问我“老师有没有既不难又显得有技术含量的题目”答案就是它校园外卖系统。关键词里那个“SpringBoot毕设”不是随便贴的标签而是整套架构的底层支撑“MySQL数据库”也不是凑数而是把用户、店铺、菜品、订单、评价这五类核心实体关系建模得清清楚楚至于“前后端分离”这里要特别说明一句它的前端确实是原生HTMLCSSJS但后端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聚合管道性能慢得像蜗牛答辩时被评委指着问“这个聚合查询的执行计划你看过吗索引建对了吗”——这种坑这套资源从源头就帮你绕开了。前端坚持原生HTMLCSSJS更是深思熟虑。你看目录里的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-fordish 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元。如果只存外键查历史订单时还得JOINdish表而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查询当前卖家所有菜品?page1size10POST/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?page1size10); 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 classshop-card h3${shop.name}/h3 p地址${shop.address}/p button onclickviewShop(${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 11SpringBoot 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里有明确标注买家账号buyerexample.com/123456卖家账号sellerexample.com/123456管理员账号adminexample.com/123456。4.3 后端启动与配置application.yml详解项目根目录下的src/main/resources/application.yml是核心配置文件。关键配置项解读spring: datasource: url: jdbc:mysql://localhost:3306/campus_takeout?useUnicodetruecharacterEncodingutf8serverTimezoneAsia/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 rootlocalhost说明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→ChromeIDEA会自动启动一个本地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?page1size5keyword食堂让你复制粘贴就能跑。常见问题FAQ.md全是血泪教训。例如Q启动时报错java.lang.NoClassDefFoundError: javax/xml/bind/JAXBContextA这是JDK 11移除了Java EE模块导致的。解决方案在pom.xml中添加依赖xml dependency groupIdjavax.xml.bind/groupId artifactIdjaxb-api/artifactId version2.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字段减1order_master新增一行order_detail新增多行- 状态订单初始状态为WAITING_PAYMENT卖家后台刷新即可看到把这个闭环画成一张简单的时序图手绘也行答辩时边指边讲评委立刻觉得“这孩子真干了活”。技之二主动暴露一个“可改进点”不要等评委挑刺自己先说“目前系统有个可优化点订单状态变更全部靠卖家手动点击‘确认收款’未来可以接入微信支付的异步通知实现自动状态更新。这需要增加一个/api/pay/callback接口用AES解密微信回调参数校验签名然后调用OrderService.confirmPayment()。虽然现在没做但代码结构已预留了扩展点——OrderService里的confirmPayment()方法是独立的不耦合UI逻辑。” 主动提改进比被动挨打强十倍。技之三用真实数据说话答辩PPT里不要放“系统界面截图”放“真实运行日志”。压缩包里的log文件夹是系统运行时生成的catalina.outTomcat日志和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] 更新菜品ID5库存扣减数量2剩余库存997指着这段说“看这是刚才我演示下单时的真实日志第一行是订单创建成功第二行是库存扣减的DEBUG日志证明业务逻辑准确执行。” 数据不会撒谎这就是最硬的底气。6. 常见问题与排查技巧实录那些年我们踩过的坑6.1 启动失败类问题速查表现象可能原因排查步骤解决方案IDEA启动报错Failed to configure a DataSourceapplication.yml中MySQL连接配置错误1. 检查url是否包含数据库名2. 检查username/password是否正确3. 在MySQL命令行执行mysql -u root -p测试能否登录确保url为jdbc:mysql://localhost:3306/campus_takeout?...密码与MySQL一致启动后访问/swagger-ui.html显示404Swagger配置未生效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/api6.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方法里统一添加Tokenjavascript 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-根本原因前端表单未设置enctypemultipart/form-data导致浏览器未按文件上传格式编码请求-修复方案检查seller_shop.html中的表单确保有html上传并在JS中用FormData对象提交javascriptconst 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 idsearchByKeyword resultTypeDish SELECT * FROM dish WHERE name LIKE %${keyword}% /select#{}是预编译占位符安全${}是字符串拼接危险如果keyword传入 OR 11就会变成WHERE name LIKE % OR 11%导致全表扫描。修复改为#{keyword}并在Java层处理模糊查询// Java代码中 String keyword % keyword %; dishMapper.searchByKeyword(keyword);对应的XMLselect idsearchByKeyword resultTypeDish SELECT * FROM dish WHERE name LIKE #{keyword} /select坑二XSS攻击漏洞前端渲染菜品描述时直接innerHTML dish.description如果description包含scriptalert(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存储用户、店铺、菜品、订单、评价等全部业务数据前端采用纯HTMLCSSJavaScript构建无第三方框架依赖适配基础浏览器环境支持买家浏览菜单、收藏菜品、下单支付含扫码支付模拟、查看订单状态卖家可创建专属店铺、维护菜品信息支持菜系/口味/原材料标注、上传收款码、处理订单、查看销售统计与用户反馈管理员后台具备全量数据管理能力覆盖用户、店铺、订单、评价等模块。压缩包内含完整源码含pom.xml和SQL脚本sprxdwmhsg6221wnck.sql、毕业论文LW、详细说明文档zip格式、系统运行日志gzip压缩所有代码已通过基础功能验证可直接导入IDE运行调试适合Java初学者用于课程设计或本科毕业设计实践。本文还有配套的精品资源点击获取