资讯动态

Vue+Servlet+AJAX前后端分离外卖项目:从请求链路到部署实战

发布时间:2026/10/19 3:18:20 来源:尧图企业网站定制
简介以饿了么为原型的Java Web前后端分离项目基于Vue.jsServletAJAX技术栈实现适合Java Web学习者、前端转后端开发者以及需要课程设计参考的高校学生。项目覆盖用户登录、商品查询、订单处理等典型业务场景前端通过Vue组件组织页面与交互后端由Servlet接收AJAX请求并返回JSON数据清晰展现从浏览器请求到服务端响应的完整链路同时涉及RESTful接口组织、数据库读写与安全设计思路。压缩包共131个文件、约4.15MB其中45张PNG图片直观呈现页面效果与运行结果LESS/SCSS/CSS样式文件与HTML页面共同构成前端展示层Java源码及class文件对应Servlet业务逻辑JAR包提供依赖支持properties与XML等配置文件用于环境设置目录按前端资源和后端代码分层便于逐模块对照学习。已有1435人学习下载作为外卖类Web应用开发范例既能帮助初学者快速建立前后端分离项目整体认知也可用于毕业设计或进阶实战时参考其中的接口设计与代码组织。1. 这个饿了么风格的外卖后端项目是理解前后端分离最好的起点每次拿到一套带完整前端页面的 Java Web 项目我第一件事不是看代码而是先确认它到底在教什么。这个饿了么后端项目是典型的「Vue Servlet AJAX」前后端分离架构前端用 Vue 管理页面交互后端用原生 Servlet 暴露接口两者通过 AJAX 交换 JSON 数据。没有 Spring Boot 那种自动装配的黑匣子所有请求怎么进、参数怎么解析、数据怎么返回每一层都看得清清楚楚。适合两类人一是正在做 Java Web 课程设计、需要一套能跑通全流程的参考实现二是写惯了 Spring Boot、想回头补 Servlet 底层功底的开发者。项目不大但登录、商家列表、购物车、下单、订单管理这些外卖核心链路一个不少非常适合二次开发。2. 技术路线与整体架构一个请求从 Vue 到 Servlet 的完整链路2.1 技术选型为什么还在用 Servlet而不是直接上 Spring Boot先说结论这项目不是技术落后而是刻意保留 Servlet 层来暴露 Web 应用的本质。Spring Boot 的RestController写起来爽但请求怎么被容器接收、web.xml怎么映射 URL、HttpServletRequest里到底装了什么这些底层细节全被框架吞掉了。Servlet 方案下一个 HTTP 请求从进入到返回每一步都是你自己管理的代码调试起来反而更直接。常见做法是这么分工的层技术职责前端展示Vue 2 Element UI Axios页面渲染、表单校验、调用后端接口接口层Java Servlet 3.0接收 AJAX 请求、解析参数、返回 JSON业务层Service 类订单计算、登录校验、状态流转数据层DAO JDBC 封装操作 MySQL 表返回实体对象数据库MySQL 5.7 / 8.0用户、商家、菜品、订单、购物车数据存储前端工程通过 Vue CLI 创建和后端是两个独立项目。前端开发服务器跑在8080或8081后端 Tomcat 跑在8080会调整两者之间通过 Axios 发起 HTTP 请求通信。生产环境下前端npm run build打包成静态资源丢到 Tomcat 的webapps下由同一个容器托管本地开发则用代理解决跨域。2.2 请求如何从 Vue 页面走到 MySQL一张链路图讲透我拆这种项目时习惯先把「一个请求的完整生命周期」捋清楚。拿「用户登录」举例用户在登录页输入账号密码点击提交。Vue 组件里的submitForm方法被触发调用api/login(loginForm)。api模块通过 Axios 发起POST /api/user/login请求体是{ username: admin, password: 123456 }。Tomcat 根据映射规则找到UserLoginServlet容器把 HTTP 请求封装成HttpServletRequest对象。Servlet 的doPost方法被调用从请求体里取出 JSON 字符串转成 Java 对象。Servlet 调用UserService.login(username, password)Service 层做参数校验比如检查是否为空。Service 调用UserDAO.findByUsername(username)DAO 层用 JDBC 执行SELECT语句。结果集封装成User实体对象返回Service 判断密码是否匹配。Servlet 把结果拼成{ code: 200, message: 登录成功, data: { userId: 1 } }写入响应。Vue 的 Axiosthen回调拿到 JSON根据code决定跳转或提示错误。// 前端的 api/login 方法Vue 组件里调用的唯一入口 login(loginForm) { return request({ url: /api/user/login, // 后端 Servlet 映射的地址 method: post, data: loginForm // { username: admin, password: 123456 } }) }这段代码的逻辑核心在于前端不关心后端是 Servlet 还是 Spring Boot只要接口约定好 URL、请求方法和参数结构就能联调。request是提前封装好的 Axios 实例统一处理了请求头里的Content-Type和响应拦截器所有接口都走它这样后期要加 token 校验或统一错误提示只需改一个文件。Servlet 端的写法是另一个关键点WebServlet(/api/user/login) // 注解方式声明 URL 映射省去 web.xml 配置 public class UserLoginServlet extends HttpServlet { Override protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { // 1. 设置请求和响应的编码防止中文乱码 request.setCharacterEncoding(UTF-8); response.setContentType(application/json;charsetUTF-8); // 2. 读取前端传来的 JSON 请求体 BufferedReader reader request.getReader(); StringBuilder sb new StringBuilder(); String line; while ((line reader.readLine()) ! null) { sb.append(line); } JSONObject params JSONObject.parseObject(sb.toString()); String username params.getString(username); String password params.getString(password); // 3. 调业务层做登录校验 UserService userService new UserService(); User user userService.login(username, password); // 4. 组装统一返回格式 JSONObject result new JSONObject(); if (user ! null) { result.put(code, 200); result.put(message, 登录成功); result.put(data, user.toJson()); } else { result.put(code, 500); result.put(message, 用户名或密码错误); } response.getWriter().write(result.toJSONString()); } }这里有个实际开发中容易忽略的细节request.getReader()只对 POST 请求体有效如果前端用 GET 传参得改用request.getParameter(username)。这个项目的所有写操作都走 POST读操作走 GET所以两类入参方式都能在代码里看到。response.setContentType(application/json;charsetUTF-8)这行决定了浏览器解析返回内容时用什么编码和格式漏掉它前端拿到的就是乱码或下载文件而非 JSON。2.3 前端开发服务器的跨域处理前后端分离后第一个拦路虎是跨域。前端跑在http://localhost:8081后端跑在http://localhost:8080浏览器默认拒绝跨端口 AJAX 请求。常见做法有两种这个项目里用的是 Vue CLI 的 devServer 代理。// vue.config.js 里的代理配置 module.exports { devServer: { port: 8081, // 前端开发服务器端口 proxy: { /api: { // 以 /api 开头的请求全部转发到后端 target: http://localhost:8080, // 后端 Tomcat 地址 changeOrigin: true } } } }这种配置妙在浏览器看到的请求地址始终是同源的http://localhost:8081/api/xxx代理在 Node 层替你把请求转发到后端避开了浏览器跨域限制。changeOrigin: true会把请求头里的Host字段改成目标地址后端拿到的就像是浏览器直接请求它一样调试时不至于被防盗链或域名校验卡住。生产环境直接把打包产物扔到 Tomcat 的webapps下同源部署代理配置就不再生效代码里的baseURL需要配合调整。3. 源码结构与核心模块拆解从登录到下单的代码长什么样3.1 前后端目录结构拿到源码后先看目录能快速建立项目的整体认知。这类 Servlet Vue 项目通常分两个根目录前端叫frontend或直接叫vue-web后端叫backend或server。后端是标准的 Maven 工程结构backend/ ├── pom.xml # Maven 依赖管理 ├── src/main/java │ └── com/edu/order │ ├── common/ # 公共工具类 │ │ ├── DBUtil.java # JDBC 连接池封装 │ │ ├── Result.java # 统一响应结果类 │ │ └── JsonUtil.java # JSON 序列化工具 │ ├── entity/ # 实体类 │ │ ├── User.java │ │ ├── Merchant.java │ │ ├── Dish.java │ │ ├── CartItem.java │ │ └── Order.java │ ├── dao/ # 数据库操作层 │ │ ├── UserDAO.java │ │ ├── MerchantDAO.java │ │ ├── DishDAO.java │ │ ├── CartDAO.java │ │ └── OrderDAO.java │ ├── service/ # 业务逻辑层 │ │ ├── UserService.java │ │ ├── CartService.java │ │ └── OrderService.java │ └── servlet/ # 控制器层一个 Servlet 管一组接口 │ ├── UserLoginServlet.java │ ├── MerchantListServlet.java │ ├── DishListServlet.java │ ├── CartServlet.java │ └── OrderServlet.java ├── src/main/resources │ └── db.properties # 数据库连接配置 └── src/main/webapp └── WEB-INF/web.xml # 项目部署描述符实体类对应数据库表DAO 层只有 SQL 操作没有业务判断Service 层不碰 JDBCServlet 层不写 SQL。这条依赖链是单向的改任何一层都不影响另外两层。前端vue-web目录典型结构是vue-web/ ├── src/ │ ├── api/ # 所有后端接口的封装按模块拆文件 │ │ ├── request.js # Axios 统一实例 │ │ ├── user.js # 用户相关接口 │ │ ├── merchant.js # 商家和菜品接口 │ │ └── order.js # 购物车和订单接口 │ ├── router/ # 前端路由配置 │ ├── store/ # Vuex 状态管理 │ ├── views/ # 页面组件 │ │ ├── Login.vue │ │ ├── Home.vue │ │ ├── Cart.vue │ │ └── OrderList.vue │ └── App.vue前后端严格通过api目录下的接口函数交互页面组件不直接写 Axios。这是前后端分离项目最该养成的习惯接口变更时只需改api层业务代码不用动。3.2 登录模块从表单到数据库校验的完整逻辑登录是最能体现分层思想的功能。先看 DAO 层的实现这层只做一件事查用户是否存在。public class UserDAO { // 根据用户名查询用户 public User findByUsername(String username) { String sql SELECT id, username, password, nickname, avatar FROM t_user WHERE username ?; // JDBC 查询代码省略使用 DBUtil 工具类获取连接 try (Connection conn DBUtil.getConnection(); PreparedStatement ps conn.prepareStatement(sql)) { ps.setString(1, username); ResultSet rs ps.executeQuery(); if (rs.next()) { User user new User(); user.setId(rs.getInt(id)); user.setUsername(rs.getString(username)); user.setPassword(rs.getString(password)); user.setNickname(rs.getString(nickname)); // 注意返回的 User 对象带了 password 字段 return user; } } catch (SQLException e) { e.printStackTrace(); } return null; } }使用PreparedStatement而不是Statement核心目的是防 SQL 注入。?占位符由 JDBC 驱动做转义即使用户输入 or 11这类攻击性字符串也只是被当作普通字符参与比较。这段代码的另一个细节是结果集处理rs.next()判断是否有下一行没有就返回 null调用方根据 null 判断登录失败这是 JDBC 最朴素的写法不需要任何 ORM。Service 层的职责是补上 DAO 层不做的业务判断public class UserService { private UserDAO userDAO new UserDAO(); public User login(String username, String password) { if (username null || password null || username.trim().isEmpty() || password.isEmpty()) { return null; // 参数为空直接返回 null省一次数据库查询 } User user userDAO.findByUsername(username); if (user ! null user.getPassword().equals(password)) { return user; // 密码匹配才算登录成功 } return null; } }这段代码先做非空校验再走数据库是英明的——把无效请求提前拦截掉否则空参查询会成为 SQL 层面的隐患。密码比对用的是 String 的equals方法这个项目是教学用途所以明文存储真实生产环境必须用 BCrypt 之类的算法做哈希存储。拆这种项目时我一般会在 Service 层留这个注释提醒二次开发的同学注意安全改造。3.3 数据访问层DBUtil 连接池的设计思路整个项目的数据层都跑在 DBUtil 之上。它解决的问题是「每次数据操作都获取一个数据库连接」。如果每次手动DriverManager.getConnection高并发下数据库连接会被很快耗尽所以通常用连接池技术这个项目的常见写法是封装一个简易连接池或直接使用阿里巴巴的 Druid。public class DBUtil { private static DruidDataSource dataSource; static { try { Properties props new Properties(); // 从类路径下的 db.properties 读取配置 props.load(DBUtil.class.getClassLoader().getResourceAsStream(db.properties)); dataSource new DruidDataSource(); dataSource.setUrl(props.getProperty(jdbc.url)); dataSource.setUsername(props.getProperty(jdbc.username)); dataSource.setPassword(props.getProperty(jdbc.password)); dataSource.setInitialSize(Integer.parseInt(props.getProperty(jdbc.initialSize))); } catch (Exception e) { throw new ExceptionInInitializerError(数据库初始化失败); } } public static Connection getConnection() throws SQLException { return dataSource.getConnection(); } }连接池的核心配置有三个initialSize是启动时预先创建的连接数maxActive是最大连接数maxWait是拿不到连接时的等待时间。开发机配置initialSize5足够生产环境根据并发量调大。DBUtil 用了静态块初始化类加载时只执行一次避免重复创建数据源。3.4 下单模块事务和状态更新的核心购物车和下单是外卖系统的命门。这个项目里有一个很值得学习的点下单不是简单 insert 一条订单记录而是同时操作多个表。public class OrderService { private OrderDAO orderDAO new OrderDAO(); private CartDAO cartDAO new CartDAO(); public boolean createOrder(int userId, int merchantId, ListCartItem items) { Connection conn null; try { conn DBUtil.getConnection(); // 开启手动事务保证多表操作要么全部成功要么全部回滚 conn.setAutoCommit(false); // 1. 插入订单主表拿到生成的订单 ID double totalPrice 0; for (CartItem item : items) { totalPrice item.getPrice() * item.getQuantity(); } int orderId orderDAO.insertOrder(conn, userId, merchantId, totalPrice); // 2. 插入订单明细表 for (CartItem item : items) { orderDAO.insertOrderDetail(conn, orderId, item); } // 3. 清空该用户的购物车 cartDAO.clearCart(conn, userId); conn.commit(); // 全部成功提交事务 return true; } catch (Exception e) { if (conn ! null) { try { conn.rollback(); // 任何一步失败全部回滚 } catch (SQLException ex) { ex.printStackTrace(); } } e.printStackTrace(); return false; } } }setAutoCommit(false)是事务开始的分界线。insertOrder返回的orderId是数据库自增主键insertOrderDetail需要拿它做外键。整个过程把订单主表、订单明细表、购物车三块数据变成要么全部生效要么完全不生效的原子操作这就是事务的意义。拆项目时多留意这类多表联写的地方通常就是业务的核心复杂度所在。购物车部分常见做法是建一张t_cart表字段包含user_id、dish_id、quantity用户每次加菜就是 update 这条记录的 quantity。登录后首次进入购物车页面前端调用 CartServlet 的 GET 接口拿当前用户购物车列表数据通过联表查询拼装菜品名称、价格和图片地址。4. 环境准备与运行部署从 JDK 到 Vue 的完整启动步骤4.1 环境清单与版本匹配跑这个项目,最先要搞定的是一套兼容的开发环境。Servlet 项目对版本敏感,版本配不对,启动就直接翻车。我通常建议按下面这套来装:组件推荐版本注意事项JDK1.8 或 11Servlet 3.0 和 Tomcat 9 都需要 JDK 8 起步Tomcat8.5 或 9.08.5 以下不支持 Servlet 3.1 注解MySQL5.7 或 8.08.0 需要对应版本的 JDBC 驱动Maven3.6项目依赖统一由 Maven 管理Node.js14 及以上Vue CLI 5 要求 Node 12 起步IDEAUltimate 或社区版社区版需要手动配置 Tomcat需要注意:MySQL 8.0 的认证插件是caching_sha2_password,老版本驱动连不上;Tomcat 10 开始包名从javax.servlet变成jakarta.servlet,现有代码如果按javax写的,直接扔到 Tomcat 10 会报ClassNotFoundException。这个项目用的是javax命名空间,所以选 Tomcat 9 最稳。4.2 数据库初始化源码包里一般附带sql/order_db.sql初始化脚本。建表脚本按这个思路:-- 用户表 CREATE TABLE t_user ( id INT PRIMARY KEY AUTO_INCREMENT, username VARCHAR(50) NOT NULL UNIQUE, password VARCHAR(100) NOT NULL, nickname VARCHAR(50), avatar VARCHAR(255), role TINYINT DEFAULT 0 -- 0 普通用户, 1 商家 ); -- 商家表 CREATE TABLE t_merchant ( id INT PRIMARY KEY AUTO_INCREMENT, name VARCHAR(100) NOT NULL, image VARCHAR(255), delivery_fee DECIMAL(5,2) DEFAULT 0, rating FLOAT DEFAULT 5.0 ); -- 菜品表 CREATE TABLE t_dish ( id INT PRIMARY KEY AUTO_INCREMENT, merchant_id INT NOT NULL, name VARCHAR(100) NOT NULL, price DECIMAL(8,2) NOT NULL, image VARCHAR(255), description VARCHAR(500) ); -- 购物车表 CREATE TABLE t_cart ( id INT PRIMARY KEY AUTO_INCREMENT, user_id INT NOT NULL, dish_id INT NOT NULL, quantity INT DEFAULT 1, UNIQUE KEY uk_user_dish (user_id, dish_id) -- 同一个用户同一道菜只有一条记录 ); -- 订单主表 CREATE TABLE t_order ( id INT PRIMARY KEY AUTO_INCREMENT, user_id INT NOT NULL, merchant_id INT NOT NULL, total_price DECIMAL(10,2) NOT NULL, status TINYINT DEFAULT 0, -- 0 待支付, 1 已支付, 2 配送中, 3 已完成 create_time DATETIME DEFAULT CURRENT_TIMESTAMP ); -- 订单明细表 CREATE TABLE t_order_detail ( id INT PRIMARY KEY AUTO_INCREMENT, order_id INT NOT NULL, dish_id INT NOT NULL, dish_name VARCHAR(100) NOT NULL, -- 冗余菜品名防止菜品改名影响历史订单 price DECIMAL(8,2) NOT NULL, -- 冗余下单时的价格 quantity INT NOT NULL );执行时用source命令或 Navicat 直接运行脚本。注意t_cart表的唯一键uk_user_dish,它的作用是防止同一用户重复添加同一道菜时产生两条记录,而是直接更新数量——这是购物车表设计的一个细节。4.3 后端启动步骤后端是 Maven 工程,导入方式很简单:IDEA 里选择File - New - Project from Existing Sources,选中backend目录下的pom.xml。等待 Maven 下载依赖(首次会比较慢,网络不好时可能卡在druid或fastjson的下载)。修改src/main/resources/db.properties里的连接信息:jdbc.drivercom.mysql.cj.jdbc.Driver jdbc.urljdbc:mysql://localhost:3306/order_db?useUnicodetruecharacterEncodingUTF-8serverTimezoneAsia/Shanghai jdbc.usernameroot jdbc.password你的密码 jdbc.initialSize5 jdbc.maxActive20配置 Tomcat:IDEA 里Run - Edit Configurations,点加号选Tomcat Server - Local,把 Tomcat 9 的目录指进去,然后在Deployment页签点击选Artifact,把项目部署上去。启动 Tomcat,控制台出现Server startup in xxx ms就成功了。serverTimezoneAsia/Shanghai这个参数很关键,MySQL 8.0 默认时区是 UTC,不加它 JDBC 驱动会报时区错误。连接失败时先看这个参数。4.4 前端启动步骤前端部分,Vue CLI 项目:# 进入前端项目目录 cd vue-web # 安装依赖 npm install # 启动开发服务器 npm run servenpm install是前端项目最容易出问题的环节,网络不好时推荐用国内镜像源:npm config set registry https://registry.npmmirror.com启动成功后浏览器访问http://localhost:8081,如果第 2 章里的代理配置没问题,前端页面能正常拿到后端数据。如果网络没问题但npm install卡住,看看是不是没配镜像源——我遇到过不少同学在这一步卡一下午,换源后一分钟装完。4.5 首次运行自测清单启动完成不等于项目能跑,建议按这个顺序自测链路:浏览器打开http://localhost:8081,进入登录页,输入源码自带的admin / 123456,能跳转到首页说明登录接口通。打开浏览器 DevTools 的 Network 面板,点击菜单栏的「商家列表」,看/api/merchant/list请求返回 200 且 JSON 里有商家数据,说明 DAO 查询正常。点进一家店,随便加一个菜进购物车,查看/api/cart/add是否有响应。若 500,多半是数据库表字段对不上。提交订单,确认订单列表页出现新订单。如果这一步通了,整个前后端链路就完全打通了。5. 常见问题与避坑:JDBC、Tomcat、编码与跨域踩坑记录5.1 接口返回中文变问号现象:前端页面能拿到数据,但所有中文显示成???,英文和数字正常。原因:Tomcat 8 以上 GET 请求的 URI 默认按 UTF-8 解码,但 POST 请求体的编码取决于request.setCharacterEncoding有没有在读取参数前调用。如果 Servlet 里漏了request.setCharacterEncoding(UTF-8),容器会用默认的 ISO-8859-1 解码请求体,中文就乱码了。同理,响应侧漏掉response.setContentType(application/json;charsetUTF-8),浏览器就会用页面编码来解析,输出中文变问号。解决:在 Servlet 的doPost方法第一行加request.setCharacterEncoding(UTF-8),并确保响应头显式声明charsetUTF-8。检查 web.xml 里有没有配置CharacterEncodingFilter,这个全局过滤器可以一次性解决所有 Servlet 的编码问题。5.2 浏览器报 CORS 跨域错误现象:前端请求发出后,Network 面板显示请求状态(failed),Console 里报No Access-Control-Allow-Origin header is present on the requested resource。原因:前端localhost:8081直接请求后端localhost:8080,浏览器判定为跨域,拦截了响应。这类项目通常用 devServer 代理,如果你的请求地址写死了完整后端 URL,代理就不生效了。常见于 Axios 的baseURL配成了http://localhost:8080,跳过了 Vue CLI 的/api前缀。解决:确认vue.config.js里配置了 proxy,且请求路径以/api开头,路径才能被代理捕获。如果必须用完整地址,给每个 Servlet 的doOptions方法加上 CORS 响应头:response.setHeader(Access-Control-Allow-Origin, http://localhost:8081); response.setHeader(Access-Control-Allow-Methods, GET, POST, OPTIONS); response.setHeader(Access-Control-Allow-Headers, Content-Type);5.3 访问 Servlet 返回 404现象:前端点击某个功能,Network 面板显示 404,后端控制台没有对应日志。原因:404 有三种情况。一是 URL 写错,前端请求的路径和WebServlet注解的值不一致;二是 Servlet 类没有被 Tomcat 扫描到;三是 Tomcat 版本太老,不支持注解方式映射,必须用 web.xml。解决:先核对 URL。再看WebServlet注解的类是否放在com/edu/order/servlet包下,Tomcat 默认扫描规则是启动时扫描部署目录下的WEB-INF/classes。最后检查 Tomcat 版本,8.5 及以上支持注解,8.0 以下需要手动写 web.xml。5.4 前端拿到的 data 是字符串而不是 JSON现象:接口返回 200,但 Vue 组件里res.data不是对象,而是[object Object]或一串字符串,渲染不出数据。原因:Servlet 输出 JSON 时可能调了两次response.getWriter().write(),第一次写了个普通文本,第二次才写 JSON;或者返回的字符串本身就是被转义过的 JSON,比如把 JSON 字符串当成普通字符串置于双引号内。解决:检查 Servlet 里是否只调用了一次write。如果用了JSONObject.toJSONString后直接write,一般没问题。另一个常见做法是看 Fastjson 的JSONObject.parseObject和toJSONString是否配对使用,避免把 JSON 对象直接toString输出。前端层面,请求函数里加一层JSON.parse(res.data)只能临时解决,根治还得修后端。5.5 MySQL 8.0 连接报错 SSL 和驱动问题现象:后端启动时报Unable to load authentication plugin caching_sha2_password或Public Key Retrieval is not allowed。原因:MySQL 8.0 默认认证插件是caching_sha2_password,MySQL 5.x 时代的驱动类com.mysql.jdbc.Driver不支持。另外 8.0 默认开启 SSL 但 JDBC 连接串没配 SSL 参数,驱动就抛异常。解决:驱动用mysql-connector-java8.0.x 以上版本,pom.xml 里确认依赖版本;JDBC URL 加allowPublicKeyRetrievaltrueuseSSLfalse两个参数。完整的连接串可以参考第 4.3 节,注意顺序不能乱。6. 二次开发:把「订单状态流转」从数据库到 Vue 完整实现一遍拿到源码后,光跑通不够,要上手改才算吃透。我建议你从「订单状态流转」这个小功能入手——这个改动牵涉数据库、后端接口、前端页面三个层面,完整走一遍就能彻底理解整个项目的数据流。第一步先看需求:当前订单表只有status字段,注释是0 待支付, 1 已支付, 2 配送中, 3 已完成。我给商家加一个「接单」操作,把订单从「已支付」推到「配送中」。先改数据库,加一个字段记录操作时间:ALTER TABLE t_order ADD COLUMN accept_time DATETIME DEFAULT NULL COMMENT 商家接单时间;后端在 OrderServlet 里加一个方法,接收orderId参数,更新状态和时间:WebServlet(/api/order/accept) public class OrderAcceptServlet extends HttpServlet { private OrderDAO orderDAO new OrderDAO(); Override protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { request.setCharacterEncoding(UTF-8); response.setContentType(application/json;charsetUTF-8); JSONObject result new JSONObject(); // 从 JSON 请求体里取订单 ID JSONObject params JsonUtil.parse(request); int orderId params.getIntValue(orderId); boolean success orderDAO.updateStatus(orderId, 2, new Date()); result.put(code, success ? 200 : 500); result.put(message, success ? 接单成功 : 取消失败); response.getWriter().write(result.toJSONString()); } }DAO 层写对应的更新 SQL,注意要加WHERE status 1条件做状态校验,防止重复接单:public boolean updateStatus(int orderId, int targetStatus, Date time) { String sql UPDATE t_order SET status ?, accept_time ? WHERE id ? AND status 1; // 执行更新,返回受影响行数 0 说明更新成功 }前端在第 3.4 节的订单列表页加一个按钮,用第 2.3 节的request调用新接口:// api/order.js 里新增方法 acceptOrder(orderId) { return request({ url: /api/order/accept, method: post, data: { orderId: orderId } }) } // OrderList.vue 模板里,对状态为 1 的订单显示接单按钮 el-button v-iforder.status 1 typesuccess clickhandleAccept(order.id)接单/el-button handleAccept(orderId) { this.acceptOrder(orderId).then(res { if (res.code 200) { this.$message.success(接单成功) this.fetchOrderList() // 重新拉取订单列表,刷新状态 } }) }这一步完整覆盖了「前端组件 → api 层 → Servlet → DAO → 数据库 → 响应回前端」的全链路。从数据库加字段到现在,一个功能改下来基本不用动其他模块,这就是分层设计的价值。拆过这么多项目,我养成一个习惯:拿到源码先梳理请求链路,再改代码,改完一定打开 Network 面板看每个请求的响应。每次改完接口我都强制走一遍「看 Network → 看响应体 → 看前端是否按预期渲染」三步,用这个流程查出的问题比我翻代码猜原因快得多。希望帮到你。本文还有配套的精品资源点击获取

读完文章,也想定制专属网站?

尧图设计师 24 小时内与您沟通定制方案

免费获取报价 →
↑