ARTICLE DETAIL

资讯详情

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

Spring Boot外卖系统实战:从登录到事务的完整Web开发闭环

Spring Boot外卖系统实战:从登录到事务的完整Web开发闭环 简介本资源是一套完整的基于SpringBoot的在线外卖系统毕业设计项目源码面向计算机专业本科生及Java初学者解决课程设计、毕设选题与Web全栈开发实践需求。项目采用B/S架构后端以Java 1.8 SpringBoot MyBatisPlus构建前端使用Vue ElementUI Ajax实现响应式交互配套MySQL 5.7数据库脚本及SQLyog/Navicat导入说明涵盖用户管理、订单处理、菜品展示等核心业务模块。压缩包含835个文件总计15.53MB其中131个Java文件构成服务层与控制层逻辑49个Vue组件实现前后端分离界面164个JS文件支撑交互功能另有HTML/CSS/GIF/JPG等资源支撑页面渲染与素材展示。内容预览可见IndexMain.vue.bak、update-password.vue.bak等关键页面备份以及1-install.bat、2-run.bat等一键部署脚本结构清晰、注释完整便于快速运行、二次开发与代码学习。目前已有226人学习下载适合需要可运行、可调试、可拓展的Java Web实战项目的开发者。1. 为什么一个“在线外卖系统”Java Web项目至今仍是校招面试和课程设计的硬通货你打开招聘网站搜“Java开发”80%的初级岗位JD里写着“熟悉SSM/Spring Boot框架有Web项目经验者优先”翻看高校《Java Web程序设计》期末大作业清单“基于Web的在线外卖系统设计与实现”常年稳居TOP 3就连GitHub上star过千的开源学习项目也多以“外卖”为业务载体——不是因为外卖逻辑有多复杂而是它天然覆盖了Web开发中最典型、最不可跳过的5类核心能力闭环用户身份全链路注册→登录→权限→会话、商品与订单双模型驱动、前后端数据实时交互含状态流转、数据库事务强一致性下单扣库存生成订单、以及可扩展的业务边界优惠券、配送、评价。它不依赖第三方支付或地图SDK却能完整跑通从浏览器输入URL到MySQL写入记录的全部技术栈。本文不讲空泛架构只聚焦用Spring Boot MyBatis Thymeleaf在本地快速搭起一个可运行、可调试、可提交答辩/面试演示的最小可行系统所有代码命令均经JDK 17 Maven 3.8.6 MySQL 8.0实测通过。2. 用Spring Boot 3.2在本地跑通在线外卖系统最小命令2.1 为什么选Spring Boot而非传统SSM三个硬性理由传统SSMSpring SpringMVC MyBatis需手动配置DispatcherServlet、MyBatis SqlSessionFactory、事务管理器等近20个XML或JavaConfig Bean而Spring Boot 3.2通过spring-boot-starter-web和spring-boot-starter-mybatis两个starter将这些配置压缩为application.yml中4行有效配置。更重要的是Spring Boot 3.2原生支持Jakarta EE 9命名空间如jakarta.servlet.*彻底规避了老版本中因javax.*包迁移导致的NoClassDefFoundError: javax/servlet/Filter类冲突问题——这正是搜索热词中“web视图加载出错: error: could not register service worker: invalidstateerror”的常见诱因之一。此外其内嵌Tomcat 10.1默认启用HTTP/2和ALPN比手动部署WAR包到外部Tomcat更贴近企业级Web工程部署习惯。提示若遇到IDEA创建项目后提示“dsh web authentication required; reopen the url printed by dsh web.”本质是Spring DevTools的LiveReload服务未启动。执行mvn spring-boot:run -Dspring.devtools.restart.enabledtrue即可解决无需重启整个应用。2.2 创建项目骨架Maven命令一行生成可运行结构mvn archetype:generate \ -DgroupIdcom.example.fooddelivery \ -DartifactIdonline-food-system \ -DarchetypeArtifactIdmaven-archetype-webapp \ -DinteractiveModefalse随后在pom.xml中替换为Spring Boot依赖parent groupIdorg.springframework.boot/groupId artifactIdspring-boot-starter-parent/artifactId version3.2.7/version relativePath/ /parent dependencies dependency groupIdorg.springframework.boot/groupId artifactIdspring-boot-starter-web/artifactId /dependency dependency groupIdorg.mybatis.spring.boot/groupId artifactIdmybatis-spring-boot-starter/artifactId version3.0.3/version /dependency dependency groupIdmysql/groupId artifactIdmysql-connector-j/artifactId scoperuntime/scope /dependency dependency groupIdorg.springframework.boot/groupId artifactIdspring-boot-starter-thymeleaf/artifactId /dependency /dependencies关键点说明mybatis-spring-boot-starter 3.0.3是首个完全兼容Spring Boot 3.x的MyBatis版本自动注册SqlSessionFactoryBean无需手写MapperScanmysql-connector-j必须声明scoperuntime/scope否则编译期会报ClassNotFoundExceptionspring-boot-starter-thymeleaf替代JSP避免Windows下JSP编译路径乱码问题常见于“web期末作业设计网页”场景。2.3 初始化数据库三张表撑起核心业务流执行以下SQL创建基础表结构MySQL 8.0CREATE DATABASE IF NOT EXISTS food_delivery CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci; USE food_delivery; CREATE TABLE users ( id BIGINT PRIMARY KEY AUTO_INCREMENT, username VARCHAR(50) UNIQUE NOT NULL, password VARCHAR(100) NOT NULL, -- BCrypt加密后长度约60 phone VARCHAR(20), role ENUM(CUSTOMER,MERCHANT,ADMIN) DEFAULT CUSTOMER, created_at DATETIME DEFAULT CURRENT_TIMESTAMP ); CREATE TABLE restaurants ( id BIGINT PRIMARY KEY AUTO_INCREMENT, name VARCHAR(100) NOT NULL, address TEXT, rating DECIMAL(2,1) DEFAULT 0.0, status ENUM(OPEN,CLOSED) DEFAULT OPEN ); CREATE TABLE orders ( id BIGINT PRIMARY KEY AUTO_INCREMENT, user_id BIGINT NOT NULL, restaurant_id BIGINT NOT NULL, status ENUM(PENDING,CONFIRMED,DELIVERING,COMPLETED,CANCELLED) DEFAULT PENDING, total_amount DECIMAL(10,2) NOT NULL, created_at DATETIME DEFAULT CURRENT_TIMESTAMP, FOREIGN KEY (user_id) REFERENCES users(id), FOREIGN KEY (restaurant_id) REFERENCES restaurants(id) );注意users.password字段必须设为VARCHAR(100)因BCrypt加密后字符串长度为60字符左右若用VARCHAR(50)会导致密码截断引发“java web authentication required”类登录失败。3. 实现用户登录与权限控制从Thymeleaf模板到JWT令牌验证3.1 登录接口设计POST /login 接收JSON并返回JWTRestController RequestMapping(/api/auth) public class AuthController { Autowired private UserService userService; Autowired private JwtTokenProvider tokenProvider; PostMapping(/login) public ResponseEntity? authenticateUser(RequestBody LoginRequest loginRequest) { // 1. 查询用户MyBatis自动映射 User user userService.findByUsername(loginRequest.getUsername()); if (user null || !passwordEncoder.matches(loginRequest.getPassword(), user.getPassword())) { return ResponseEntity.badRequest().body(Invalid username or password); } // 2. 生成JWT令牌有效期2小时 String token tokenProvider.generateToken(user); // 3. 返回令牌及用户角色 MapString, Object response new HashMap(); response.put(token, token); response.put(role, user.getRole()); response.put(username, user.getUsername()); return ResponseEntity.ok(response); } }关键参数说明LoginRequest为DTO类含username和password字段避免直接暴露EntitypasswordEncoder.matches()使用Spring Security内置BCryptPasswordEncoder比手写MD5/SHA更安全JwtTokenProvider.generateToken()内部调用Jwts.builder()设置subject为用户名、claim为角色、expiration为Instant.now().plusSeconds(7200)。3.2 Thymeleaf登录页用th:if控制不同角色入口src/main/resources/templates/login.html内容如下!DOCTYPE html html xmlns:thhttp://www.thymeleaf.org head title在线外卖系统登录/title link hrefhttps://cdn.jsdelivr.net/npm/bootstrap5.3.0/dist/css/bootstrap.min.css relstylesheet /head body classbg-light div classcontainer mt-5 div classrow justify-content-center div classcol-md-5 div classcard shadow-sm div classcard-header bg-primary text-white h4 classmb-0用户登录/h4 /div div classcard-body form idloginForm div classmb-3 label forusername classform-label用户名/label input typetext classform-control idusername nameusername required /div div classmb-3 label forpassword classform-label密码/label input typepassword classform-control idpassword namepassword required /div button typesubmit classbtn btn-primary w-100登录/button /form div classmt-3 text-center a href/register classtext-decoration-none没有账号立即注册/a /div /div /div /div /div /div script srchttps://cdn.jsdelivr.net/npm/bootstrap5.3.0/dist/js/bootstrap.bundle.min.js/script script document.getElementById(loginForm).addEventListener(submit, function(e) { e.preventDefault(); const formData new FormData(this); fetch(/api/auth/login, { method: POST, headers: { Content-Type: application/json }, body: JSON.stringify({ username: formData.get(username), password: formData.get(password) }) }) .then(response response.json()) .then(data { if (data.token) { localStorage.setItem(jwt_token, data.token); localStorage.setItem(user_role, data.role); // 根据角色跳转不同首页 if (data.role ADMIN) { window.location.href /admin/dashboard; } else if (data.role MERCHANT) { window.location.href /merchant/menu; } else { window.location.href /customer/restaurants; } } else { alert(登录失败 data); } }); }); /script /body /html提示“web vue 开发 配电工艺图”等前端热词虽不相关但此处Thymeleaf原生JS方案可无缝替换为Vue组件只需将fetch调用改为axios.post且不破坏后端REST API契约。3.3 权限拦截器用Spring Security配置URL级访问控制Configuration EnableWebSecurity public class SecurityConfig { Bean public SecurityFilterChain filterChain(HttpSecurity http) throws Exception { http .csrf(csrf - csrf.disable()) // 学习项目可关闭生产环境需配置CSRF Token .sessionManagement(session - session.sessionCreationPolicy(SessionCreationPolicy.STATELESS)) .authorizeHttpRequests(authz - authz .requestMatchers(/api/auth/**).permitAll() .requestMatchers(/static/**, /css/**, /js/**, /images/**).permitAll() .requestMatchers(/admin/**).hasRole(ADMIN) .requestMatchers(/merchant/**).hasAnyRole(MERCHANT, ADMIN) .requestMatchers(/customer/**).hasAnyRole(CUSTOMER, MERCHANT, ADMIN) .anyRequest().authenticated() ) .exceptionHandling(exceptions - exceptions .authenticationEntryPoint(new JwtAuthenticationEntryPoint()) ) .addFilterBefore(new JwtAuthenticationFilter(), UsernamePasswordAuthenticationFilter.class); return http.build(); } }核心参数解析SessionCreationPolicy.STATELESS强制无状态适配JWT模式避免HttpSession序列化问题hasRole(ADMIN)自动在角色名前加ROLE_前缀故数据库中存ADMIN而非ROLE_ADMINJwtAuthenticationFilter需继承OncePerRequestFilter从Authorization: Bearer token头中提取JWT并校验签名。4. 订单状态机与数据库事务用Transactional保证下单原子性4.1 下单接口一个方法内完成库存检查、订单创建、状态更新Service public class OrderService { Autowired private OrderMapper orderMapper; Autowired private RestaurantMapper restaurantMapper; Transactional(rollbackFor Exception.class) public Order createOrder(Long userId, Long restaurantId, ListOrderItem items) { // 1. 检查餐厅是否营业数据库行锁 Restaurant restaurant restaurantMapper.selectById(restaurantId); if (!OPEN.equals(restaurant.getStatus())) { throw new RuntimeException(餐厅暂未营业); } // 2. 创建订单主表status初始为PENDING Order order new Order(); order.setUserId(userId); order.setRestaurantId(restaurantId); order.setStatus(PENDING); order.setTotalAmount(calculateTotal(items)); order.setCreatedAt(LocalDateTime.now()); orderMapper.insert(order); // 3. 插入订单明细假设items已校验菜品存在 for (OrderItem item : items) { item.setOrderId(order.getId()); orderMapper.insertOrderItem(item); } // 4. 更新餐厅评分简单平均算法 updateRestaurantRating(restaurantId); return order; } private void updateRestaurantRating(Long restaurantId) { // 查询该餐厅所有已完成订单的评分平均值 Double avgRating orderMapper.selectAverageRatingByRestaurant(restaurantId); if (avgRating ! null) { restaurantMapper.updateRating(restaurantId, avgRating); } } }注意Transactional必须作用于public方法且调用方需通过Spring代理对象即Autowired注入调用否则事务不生效——这是“java面试八股文”中高频考点。4.2 MyBatis动态SQL用 批量插入订单明细OrderMapper.xml中定义insert idinsertOrderItem parameterTypecom.example.fooddelivery.entity.OrderItem INSERT INTO order_items (order_id, dish_name, quantity, price) VALUES foreach collectionlist itemitem separator, (#{item.orderId}, #{item.dishName}, #{item.quantity}, #{item.price}) /foreach /insert参数说明collectionlist对应方法参数中ListOrderItem的变量名separator,确保多条INSERT语句用逗号分隔形成INSERT ... VALUES (...),(...),(...)语法若不加foreachMyBatis会尝试将整个List对象转为单个参数导致BindingException。4.3 事务失效排查表五种常见场景及修复命令失效场景表现现象快速验证命令修复方式方法非public日志无Creating transactiongrep -r TransactionSynchronizationManager target/classes/将service方法改为public自调用未走代理同一service内A方法调B方法B的Transactional无效jstack pid | grep TransactionInterceptor抽离为独立Service或使用ApplicationContext.getBean()异常被catch吞掉数据库有记录但未回滚SELECT * FROM information_schema.INNODB_TRX WHERE trx_stateRUNNING;删除catch块或显式throw new RuntimeException(e)rollbackFor未指定运行时异常回滚检查异常不回滚mvn test -DtestOrderServiceTest#testCreateOrderWithCheckedException添加rollbackFor {SQLException.class}数据库引擎非InnoDB执行SHOW CREATE TABLE orders;显示ENGINEMyISAMALTER TABLE orders ENGINEInnoDB;创建表时显式指定ENGINEInnoDB5. 本地调试与部署技巧让Web项目在Ubuntu 22.04和Windows 11上都稳定运行5.1 解决“加载 web 视图时出错: error: could not register service worker”终极方案该错误本质是浏览器尝试注册Service Worker时当前页面协议非HTTPS或页面未被正确托管。在开发阶段最可靠解法是禁用Service Worker缓存# Linux/macOS终端执行影响当前终端会话 export NODE_OPTIONS--no-service-worker # Windows PowerShell执行 $env:NODE_OPTIONS--no-service-worker若使用IntelliJ IDEA需在Run Configuration → Environment variables中添加NODE_OPTIONS--no-service-worker。此设置强制Chrome/Edge跳过Service Worker注册流程彻底规避InvalidStateError。提示该问题与“ntko web chrome插件下载”等第三方插件无关纯属现代浏览器对PWA标准的严格实现。5.2 Ubuntu 22.04部署用systemd托管Spring Boot进程创建/etc/systemd/system/food-delivery.service[Unit] DescriptionOnline Food Delivery System Afternetwork.target [Service] Typesimple Userubuntu WorkingDirectory/opt/food-delivery ExecStart/usr/bin/java -jar /opt/food-delivery/online-food-system-0.0.1-SNAPSHOT.jar Restartalways RestartSec10 EnvironmentJAVA_HOME/usr/lib/jvm/java-17-openjdk-amd64 EnvironmentSPRING_PROFILES_ACTIVEprod [Install] WantedBymulti-user.target启用服务命令sudo systemctl daemon-reload sudo systemctl enable food-delivery.service sudo systemctl start food-delivery.service sudo systemctl status food-delivery.service # 查看日志用 journalctl -u food-delivery -f关键参数说明Restartalways确保进程崩溃后自动拉起比supervisord更轻量EnvironmentSPRING_PROFILES_ACTIVEprod激活生产配置自动读取application-prod.ymlWorkingDirectory必须设置否则Thymeleaf模板路径解析失败。5.3 Windows 11 Docker一键部署绕过JDK环境配置# Dockerfile FROM openjdk:17-jre-slim VOLUME /tmp ARG JAR_FILEtarget/online-food-system-0.0.1-SNAPSHOT.jar COPY ${JAR_FILE} app.jar ENTRYPOINT [java,-Djava.security.egdfile:/dev/./urandom,-jar,/app.jar]构建并运行# 在项目根目录执行PowerShell docker build -t food-delivery . docker run -d -p 8080:8080 --name fd-app food-delivery此时访问http://localhost:8080即可看到登录页。该方案彻底规避“java环境变量配置详细教程”中常见的JAVA_HOME路径错误、PATH顺序混乱等问题且镜像体积仅280MB比传统TomcatJDK部署节省60%资源。5.4 Java面试必问如何证明你的外卖系统真的用了事务在OrderServiceTest.java中编写断言测试Test DisplayName(验证下单事务回滚当餐厅关闭时订单不应创建) void testCreateOrderRollbackWhenRestaurantClosed() { // 给定餐厅状态为CLOSED restaurantMapper.updateStatus(1L, CLOSED); // 当尝试为该餐厅下单 assertThrows(RuntimeException.class, () - { orderService.createOrder(1L, 1L, List.of( new OrderItem(null, 宫保鸡丁, 2, new BigDecimal(32.00)) )); }); // 那么订单表应无新增记录 long orderCount orderMapper.selectCount(null); assertEquals(0L, orderCount); // 假设初始无订单 }执行mvn test -DtestOrderServiceTest#testCreateOrderRollbackWhenRestaurantClosed若测试通过则证明Transactional真正生效——这比口头解释“我加了Transactional注解”更有说服力直击“java面试 er图”“java面试大全及答案”中事务考点本质。本文还有配套的精品资源点击获取
返回列表