JavaWeb购物商城实战:Servlet+JSP+MySQL全流程开发指南
2026/9/23 5:47:49 网站建设 项目流程

简介:本资源是一套完整的JavaWeb购物商城实战项目源码,面向Java初学者及Web开发入门者,旨在通过真实业务场景巩固Servlet、JSP、MySQL及MVC架构等核心知识。项目覆盖前端展示、购物车管理、订单流程与后台管理四大模块,支持商品搜索、库存校验、地址管理、订单防重提交及会员/商品/订单的CRUD操作,是掌握动态代理与分层开发思想的典型练手案例。压缩包共613个文件,含66个Java源码、50个JSP页面、46个JS脚本、41个CSS样式、137个PNG/JPG图片及37个运行依赖JAR包,另有SQL建库脚本与批处理启动文件,整体16.85MB,结构清晰便于逐模块学习调试。已有21946人下载学习,配套代码规范、功能完整、注释充分,开箱即用,适合课程设计、毕业项目参考或求职作品集构建。

1. 这不是一个“写完就交”的课程设计——JavaWeb购物商城项目,本质是用 Servlet+JSP+MySQL 搭建可调试、可扩展、能跑通登录→浏览→下单→支付全流程的最小生产级骨架

很多刚学完 JDBC 和 Servlet 的同学拿到「JavaWeb购物商城项目(完整源码+Mysql数据库)」这个标题时,第一反应是:又一个带 CRUD 表单的练手 Demo?但实际部署时才发现,登录状态跨页面丢失、商品图片路径 404、订单提交后库存没扣减、MySQL 中文乱码导致收货地址存成问号……这些不是 Bug,而是 JavaWeb 工程化落地的必经关卡。这个项目真正的价值,不在于“有源码”,而在于它强制你面对真实 Web 应用的三层耦合:前端请求如何被 Servlet 正确解析并转发;DAO 层如何用 PreparedStatement 防 SQL 注入同时支持事务回滚;MySQL 表结构设计如何兼顾查询效率(如订单表加复合索引)与业务可读性(如 status 字段用 tinyint 而非 varchar)。适合已完成《Java Web 编程技术》前六章、能手写 login.jsp + LoginServlet + UserDAO 的开发者,目标不是“跑起来”,而是“查得清日志、改得动逻辑、扩得了模块”。

2. 从零初始化 MySQL 数据库:建库、建表、设字符集、插测试数据,四步闭环验证基础环境

2.1 创建商城专用数据库并强制 UTF8MB4 字符集

JavaWeb 项目中 90% 的中文乱码问题,根源在 MySQL 初始化阶段未显式指定字符集。不能依赖my.cnf全局配置,必须在建库时锁定。执行以下命令(注意:需用 root 或具备 CREATE 权限的用户):

CREATE DATABASE `shopping_mall` CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;

提示:utf8mb4是 MySQL 5.5.3+ 官方推荐的 UTF-8 实现,支持 emoji 和四字节 Unicode 字符;utf8mb4_unicode_ciutf8mb4_general_ci更准确处理多语言排序,尤其对中文姓名、商品名排序更可靠。

2.2 设计核心五张表:用户、商品、分类、订单、订单项,字段类型与约束精准匹配业务语义

仅建库不够,表结构设计决定后续开发效率。本项目采用符合第三范式的最小必要字段,避免冗余同时保证查询性能。关键设计逻辑如下:

表名核心字段示例设计依据索引策略
userid BIGINT PRIMARY KEY AUTO_INCREMENT,username VARCHAR(20) UNIQUE NOT NULL,password CHAR(64) NOT NULL(BCrypt 加密后长度)用户名唯一性强制校验,密码字段固定 64 字节适配 BCrypt 输出username单列索引(登录查询高频)
productid BIGINT PK,name VARCHAR(100) NOT NULL,price DECIMAL(10,2) NOT NULL CHECK(price >= 0),stock INT NOT NULL DEFAULT 0DECIMAL精确存储价格,CHECK约束防负数,stock默认 0 避免空值歧义category_id外键索引(按类目查商品)
order_masterorder_no VARCHAR(32) PRIMARY KEY,user_id BIGINT NOT NULL,total_amount DECIMAL(10,2) NOT NULL,status TINYINT NOT NULL DEFAULT 0 COMMENT '0待支付,1已支付,2已发货,3已完成'订单号用 UUID 或时间戳+随机数生成,避免自增 ID 泄露业务量;TINYINT存状态比VARCHAR查询快 3 倍以上user_id+status复合索引(用户查订单状态)

执行建表语句(以product表为例):

USE shopping_mall; CREATE TABLE `product` ( `id` BIGINT PRIMARY KEY AUTO_INCREMENT, `name` VARCHAR(100) NOT NULL, `description` TEXT, `price` DECIMAL(10,2) NOT NULL CHECK (price >= 0), `stock` INT NOT NULL DEFAULT 0, `category_id` BIGINT NOT NULL, `create_time` DATETIME DEFAULT CURRENT_TIMESTAMP, `update_time` DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, INDEX `idx_category` (`category_id`), FOREIGN KEY (`category_id`) REFERENCES `category`(`id`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
2.2.1 为什么product.description用 TEXT 而非 VARCHAR(1000)?

TEXT类型在 InnoDB 中实际存储在独立页中,主记录只存 20 字节指针,大幅降低行大小,避免因描述过长导致 B+ 树节点分裂频繁。而VARCHAR(1000)强制所有数据存于主记录页,当商品描述平均超 500 字符时,单页能存的记录数下降 40%,直接影响SELECT * FROM product LIMIT 20的扫描效率。

2.3 插入基础测试数据:用 INSERT SELECT 批量生成 100 条商品,规避手动录入错误

为快速验证分页查询和搜索功能,需批量插入测试数据。避免逐条 INSERT,采用INSERT INTO ... SELECT构造:

-- 先插入 5 个分类 INSERT INTO `category` (`name`, `parent_id`) VALUES ('手机', 0), ('笔记本', 0), ('耳机', 0), ('配件', 0), ('智能设备', 0); -- 批量生成 100 条商品(利用 mysql.help_topic 表做行源) INSERT INTO `product` (`name`, `price`, `stock`, `category_id`) SELECT CONCAT('商品-', seq.seq_num), ROUND(RAND() * 9999 + 1, 2), FLOOR(RAND() * 100), FLOOR(RAND() * 5) + 1 FROM ( SELECT 1 AS seq_num UNION ALL SELECT 2 UNION ALL SELECT 3 UNION ALL SELECT 4 UNION ALL SELECT 5 UNION ALL SELECT 6 UNION ALL SELECT 7 UNION ALL SELECT 8 UNION ALL SELECT 9 UNION ALL SELECT 10 ) AS seq1 CROSS JOIN ( SELECT 1 AS seq_num UNION ALL SELECT 2 UNION ALL SELECT 3 UNION ALL SELECT 4 UNION ALL SELECT 5 UNION ALL SELECT 6 UNION ALL SELECT 7 UNION ALL SELECT 8 UNION ALL SELECT 9 UNION ALL SELECT 10 ) AS seq2 LIMIT 100;

注意:CROSS JOIN生成 10×10=100 行,seq_num仅作占位,实际用RAND()生成随机价格和库存。此方法比循环插入快 20 倍,且无需存储过程。

2.4 验证数据库连通性:用 JDBC URL 参数显式声明字符集,杜绝连接层乱码

即使数据库和表都是 utf8mb4,若 JDBC 连接未声明,仍会默认用 latin1。在context.xmldb.properties中必须包含:

jdbc.url=jdbc:mysql://localhost:3306/shopping_mall?useUnicode=true&characterEncoding=utf8mb4&serverTimezone=Asia/Shanghai&allowPublicKeyRetrieval=true&useSSL=false jdbc.username=root jdbc.password=your_password

关键参数说明:

  • useUnicode=true&characterEncoding=utf8mb4:强制驱动使用 utf8mb4 编码传输数据;
  • serverTimezone=Asia/Shanghai:解决java.sql.SQLException: The server time zone value 'XXX' is unrecognized错误;
  • allowPublicKeyRetrieval=true&useSSL=false:MySQL 8.0+ 默认要求 SSL,开发环境关闭以简化配置(生产环境必须启用 SSL)。

3. Servlet 层核心流程实现:从登录拦截到订单创建,用 Filter+Session+事务管理构建健壮请求链

3.1 登录状态统一拦截:用 Filter 拦截 /admin/ 和 /user/ 路径,拒绝未登录访问

单纯在每个 Servlet 中if (session.getAttribute("user") == null)判断,会导致重复代码且易遗漏。标准做法是编写LoginFilter

@WebFilter(urlPatterns = {"/admin/*", "/user/*"}) public class LoginFilter implements Filter { @Override public void doFilter(ServletRequest req, ServletResponse resp, FilterChain chain) throws IOException, ServletException { HttpServletRequest request = (HttpServletRequest) req; HttpServletResponse response = (HttpServletResponse) resp; HttpSession session = request.getSession(false); User user = session != null ? (User) session.getAttribute("user") : null; if (user == null) { // 记录原始请求路径,登录后跳转 String originalUrl = request.getRequestURI() + (request.getQueryString() != null ? "?" + request.getQueryString() : ""); session.setAttribute("originalUrl", originalUrl); response.sendRedirect(request.getContextPath() + "/login.jsp"); return; } chain.doFilter(req, resp); } }
3.1.1 为什么getSession(false)而非getSession(true)

getSession(true)会在 session 不存在时自动创建新 session,导致未登录用户也获得 session ID,浪费服务器内存且可能被用于 Session Fixation 攻击。getSession(false)仅获取现有 session,严格区分“已登录”和“未登录”两种状态。

3.2 商品浏览与搜索:用 DAO 分页查询,SQL 中 LIMIT + OFFSET 配合 COUNT(*) 获取总页数

ProductDAO中实现分页核心方法:

public class ProductDAO { // 查询满足条件的商品总数(用于计算总页数) public int countByCondition(String keyword, Long categoryId) { String sql = "SELECT COUNT(*) FROM product WHERE 1=1"; List<Object> params = new ArrayList<>(); if (keyword != null && !keyword.trim().isEmpty()) { sql += " AND name LIKE ?"; params.add("%" + keyword.trim() + "%"); } if (categoryId != null && categoryId > 0) { sql += " AND category_id = ?"; params.add(categoryId); } return JdbcTemplate.queryForObject(sql, params.toArray(), Integer.class); } // 分页查询商品列表 public List<Product> findByPage(int offset, int pageSize, String keyword, Long categoryId) { String sql = "SELECT id, name, price, stock, category_id FROM product WHERE 1=1"; List<Object> params = new ArrayList<>(); if (keyword != null && !keyword.trim().isEmpty()) { sql += " AND name LIKE ?"; params.add("%" + keyword.trim() + "%"); } if (categoryId != null && categoryId > 0) { sql += " AND category_id = ?"; params.add(categoryId); } sql += " ORDER BY create_time DESC LIMIT ? OFFSET ?"; params.add(pageSize); params.add(offset); return JdbcTemplate.query(sql, params.toArray(), new ProductRowMapper()); } }

逻辑说明:countByConditionfindByPage使用完全相同的 WHERE 条件,确保总数与分页结果一致;OFFSET从 0 开始,第一页传offset=0,第二页传offset=10(pageSize=10);ORDER BY create_time DESC保证新商品优先展示,避免分页跳跃。

3.3 订单创建事务控制:用 Connection.setAutoCommit(false) 包裹库存扣减与订单写入

订单创建必须保证“扣库存成功则订单生成,任一失败则全部回滚”。不能依赖框架事务(本项目无 Spring),需手动管理:

public class OrderService { public boolean createOrder(Long userId, List<OrderItem> items) { Connection conn = null; try { conn = JdbcUtils.getConnection(); conn.setAutoCommit(false); // 关闭自动提交 // 1. 扣减库存(for update 锁定行,防超卖) String lockSql = "SELECT stock FROM product WHERE id = ? FOR UPDATE"; String updateSql = "UPDATE product SET stock = stock - ? WHERE id = ? AND stock >= ?"; for (OrderItem item : items) { // 先查当前库存 Integer stock = JdbcTemplate.queryForObject(lockSql, new Object[]{item.getProductId()}, Integer.class); if (stock == null || stock < item.getQuantity()) { throw new RuntimeException("商品ID " + item.getProductId() + " 库存不足"); } // 再更新库存 int updated = JdbcTemplate.update(updateSql, item.getQuantity(), item.getProductId(), item.getQuantity()); if (updated == 0) { throw new RuntimeException("库存扣减失败,可能已被其他订单占用"); } } // 2. 生成订单主表 String orderNo = generateOrderNo(); // 实现见下文 String masterSql = "INSERT INTO order_master (order_no, user_id, total_amount, status) VALUES (?, ?, ?, ?)"; BigDecimal totalAmount = calculateTotal(items); JdbcTemplate.update(masterSql, orderNo, userId, totalAmount, 0); // 3. 生成订单项 String itemSql = "INSERT INTO order_item (order_no, product_id, quantity, price) VALUES (?, ?, ?, ?)"; for (OrderItem item : items) { JdbcTemplate.update(itemSql, orderNo, item.getProductId(), item.getQuantity(), item.getPrice()); } conn.commit(); // 所有操作成功,提交事务 return true; } catch (Exception e) { if (conn != null) { try { conn.rollback(); // 出错时回滚 } catch (SQLException ex) { ex.printStackTrace(); } } e.printStackTrace(); return false; } } }
3.3.1FOR UPDATE的作用与风险

SELECT ... FOR UPDATE在 InnoDB 中对选中的行加写锁,阻止其他事务修改同一行,是解决超卖的核心。但需注意:若WHERE条件未命中索引,会升级为表锁,导致并发性能骤降。因此product.id必须有主键索引(默认存在),且item.getProductId()必须是有效 ID。

4. JSP 页面与前端交互:用 EL 表达式渲染数据,用 AJAX 提交异步请求,规避页面整刷刷新

4.1 商品列表页:用 JSTL<c:forEach>渲染分页数据,EL 表达式安全输出

product_list.jsp中关键片段:

<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %> <%@ taglib prefix="fmt" uri="http://java.sun.com/jsp/jstl/fmt" %> <!-- 商品卡片循环 --> <c:forEach items="${pageData.list}" var="p"> <div class="product-card"> <h3>${p.name}</h3> <p class="price">¥<fmt:formatNumber value="${p.price}" pattern="#,##0.00"/></p> <p>库存:<c:out value="${p.stock}"/> 件</p> <a href="${pageContext.request.contextPath}/product_detail?id=${p.id}" class="btn btn-primary">查看详情</a> <button onclick="addToCart(${p.id}, 1)" class="btn btn-success">加入购物车</button> </div> </c:forEach> <!-- 分页导航 --> <div class="pagination"> <c:if test="${pageData.pageNum > 1}"> <a href="?page=${pageData.pageNum - 1}&size=${pageData.pageSize}">上一页</a> </c:if> <span>第 ${pageData.pageNum} 页,共 ${pageData.pages} 页</span> <c:if test="${pageData.pageNum < pageData.pages}"> <a href="?page=${pageData.pageNum + 1}&size=${pageData.pageSize}">下一页</a> </c:if> </div>

关键点:<c:out value="${p.stock}"/>自动转义 HTML 特殊字符,防止 XSS;<fmt:formatNumber>确保价格显示为¥1,299.00格式;分页链接用?page=2&size=10传递参数,后端ProductServlet解析request.getParameter("page")即可。

4.2 购物车异步添加:用原生 JavaScript 发起 POST 请求,JSON 响应返回成功状态

避免页面跳转,提升体验。cart.js中:

function addToCart(productId, quantity) { const formData = new FormData(); formData.append("productId", productId); formData.append("quantity", quantity); fetch("${pageContext.request.contextPath}/add_to_cart", { method: "POST", body: formData }) .then(response => response.json()) .then(data => { if (data.success) { alert("已加入购物车!当前共 " + data.cartSize + " 件商品"); updateCartBadge(data.cartSize); // 更新顶部购物车徽标 } else { alert("添加失败:" + data.message); } }) .catch(error => { console.error("网络错误:", error); alert("网络异常,请重试"); }); } function updateCartBadge(size) { const badge = document.getElementById("cart-badge"); if (badge) { badge.textContent = size; badge.style.display = size > 0 ? "inline" : "none"; } }

后端AddToCartServlet接收并返回 JSON:

protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { Long productId = Long.parseLong(request.getParameter("productId")); Integer quantity = Integer.parseInt(request.getParameter("quantity")); HttpSession session = request.getSession(); Map<Long, Integer> cart = (Map<Long, Integer>) session.getAttribute("cart"); if (cart == null) { cart = new HashMap<>(); session.setAttribute("cart", cart); } cart.put(productId, cart.getOrDefault(productId, 0) + quantity); // 返回 JSON 响应 response.setContentType("application/json;charset=UTF-8"); PrintWriter out = response.getWriter(); out.print("{\"success\":true,\"cartSize\":" + cart.values().stream().mapToInt(Integer::intValue).sum() + "}"); out.flush(); }

注意:response.setContentType("application/json;charset=UTF-8")显式声明编码,避免中文乱码;cart存于 Session,无需数据库持久化(简化版),但需在用户登出时清理session.removeAttribute("cart")

5. 关键参数调优与典型故障排查:MySQL 连接池配置、JSP 编译缓存、中文乱码三处高发问题定位法

5.1 Tomcat 连接池参数:maxActive 与 maxWaitMillis 的平衡取舍

context.xml中配置Resource时,常见错误是盲目调大maxActive

<Resource name="jdbc/shoppingMall" auth="Container" type="javax.sql.DataSource" factory="org.apache.tomcat.jdbc.pool.DataSourceFactory" driverClassName="com.mysql.cj.jdbc.Driver" url="jdbc:mysql://localhost:3306/shopping_mall?..." username="root" password="..." maxActive="20" <!-- 关键:并发用户数 × 1.5 --> maxIdle="10" minIdle="5" maxWaitMillis="10000" <!-- 关键:超时时间设为 10 秒,避免线程无限等待 --> validationQuery="SELECT 1" testOnBorrow="true" removeAbandonedOnBorrow="true" removeAbandonedTimeout="60"/>
  • maxActive=20:假设应用峰值并发 12 个请求,每个请求平均持有连接 1 秒,则 20 足够;设为 100 会导致 MySQL 线程数暴增,触发max_connections限制(默认 151);
  • maxWaitMillis=10000:当连接池耗尽时,请求最多等待 10 秒,超时抛出SQLException,前端可捕获并提示“系统繁忙”,而非无限白屏。

5.2 JSP 编译缓存:禁用 development 模式避免热部署失效

开发时频繁修改 JSP 却不生效?检查web.xmljsp-config

<jsp-config> <jsp-property-group> <url-pattern>*.jsp</url-pattern> <page-encoding>UTF-8</page-encoding> <scripting-invalid>false</scripting-invalid> <el-ignored>false</el-ignored> </jsp-property-group> </jsp-config>

更重要的是 Tomcat 的conf/web.xmlJspServlet配置:

<servlet> <servlet-name>jsp</servlet-name> <servlet-class>org.apache.jasper.servlet.JspServlet</servlet-class> <init-param> <param-name>development</param-name> <param-value>false</param-value> <!-- 生产环境必须为 false --> </init-param> <init-param> <param-name>fork</param-name> <param-value>false</param-value> </init-param> <init-param> <param-name>xpoweredBy</param-name> <param-value>false</param-value> </init-param> <load-on-startup>3</load-on-startup> </servlet>

development=false强制 JSP 编译后缓存.class文件,修改 JSP 后需重启 Tomcat 才生效(符合生产习惯);设为true会导致每次请求都检查 JSP 修改时间,性能下降 30%,且热部署不稳定。

5.3 中文乱码终极排查表:按层级定位问题源头

当页面显示????时,按以下顺序逐层验证:

层级检查点验证命令/方法修复方案
MySQL 层数据库、表、列字符集SHOW CREATE DATABASE shopping_mall;
SHOW CREATE TABLE product;
执行ALTER DATABASE shopping_mall CHARACTER SET = utf8mb4 COLLATE = utf8mb4_unicode_ci;
JDBC 层连接 URL 是否含characterEncoding=utf8mb4db.propertiescontext.xml补全?useUnicode=true&characterEncoding=utf8mb4参数
Tomcat 层请求编码是否为 UTF-8在 Servlet 中System.out.println(request.getCharacterEncoding());web.xml添加filter设置request.setCharacterEncoding("UTF-8")
JSP 层页面响应编码浏览器开发者工具 → Network → Response Headers →Content-Type在 JSP 顶部加<%@ page contentType="text/html;charset=UTF-8" %>

最小复现法:直接在浏览器地址栏输入http://localhost:8080/shopping_mall/product_list.jsp?keyword=手机,若 URL 中中文正常,但页面显示乱码,则问题在 JSP 或 Servlet 响应编码;若 URL 中已是%E6%89%8B%E6%9C%BA编码,但页面仍乱码,则问题在响应头或 JSP page 指令。

5.4 用EXPLAIN分析慢查询:识别未走索引的订单查询

当用户订单列表加载缓慢,执行EXPLAIN定位瓶颈:

EXPLAIN SELECT om.order_no, om.total_amount, om.status, om.create_time FROM order_master om WHERE om.user_id = 1001 AND om.status IN (0,1) ORDER BY om.create_time DESC LIMIT 10;

type列为ALL(全表扫描),key列为NULL,说明未使用索引。此时需添加复合索引:

ALTER TABLE order_master ADD INDEX idx_user_status_time (user_id, status, create_time);

复合索引(user_id, status, create_time)满足最左前缀原则:WHERE user_id = ? AND status IN (?,?)可用前两列,ORDER BY create_time DESC可用第三列,使该查询从 2s 降至 0.02s。

6. 从源码到可运行环境:三步完成本地部署,验证登录、浏览、下单全流程

6.1 下载源码后必须执行的三项初始化操作

拿到「JavaWeb购物商城项目(完整源码+Mysql数据库)」压缩包,解压后不要直接导入 IDE。按顺序执行:

  1. 导入数据库:用 MySQL Workbench 或命令行执行shopping_mall.sql(通常位于src/main/resources/sql/
    mysql -u root -p shopping_mall < shopping_mall.sql
  2. 配置数据库连接:修改src/main/resources/db.properties中的jdbc.urljdbc.usernamejdbc.password
  3. 设置 Tomcat 服务器:在 IDE 中添加 Tomcat 9+,部署项目时勾选exploded模式(非 WAR),确保 JSP 修改实时生效

注意:shopping_mall.sql必须包含CREATE DATABASE语句及CHARACTER SET utf8mb4,否则导入后仍需手动ALTER DATABASE

6.2 启动后立即验证的三个黄金路径

部署成功后,在浏览器依次访问以下 URL,每步验证通过再进行下一步:

路径预期结果故障信号快速定位
http://localhost:8080/shopping_mall/login.jsp显示登录表单,输入admin/123456可登录404 或 500 错误检查web.xmlwelcome-file-list是否为login.jsp,或index.jsp是否重定向错误
http://localhost:8080/shopping_mall/product_list.jsp显示 100 条商品,分页导航可点击商品名显示????或数量为 0执行SELECT * FROM product LIMIT 1确认数据库数据正常,再查 JSP 中${p.name}是否被<c:out>包裹
http://localhost:8080/shopping_mall/order_confirm.jsp选择商品后进入确认页,点击“提交订单”跳转至order_success.jsp提交后报NullPointerException检查OrderService.createOrder()items是否为空,前端是否正确传递了productIdquantity参数

6.3 用 curl 模拟登录并获取 Cookie,验证后端 Session 机制

绕过浏览器,用命令行验证登录逻辑是否真正生效:

# 1. 发起登录请求,保存 Cookie curl -X POST "http://localhost:8080/shopping_mall/login" \ -H "Content-Type: application/x-www-form-urlencoded" \ -d "username=admin" -d "password=123456" \ -c cookies.txt # 2. 用 Cookie 访问受保护页面 curl -b cookies.txt "http://localhost:8080/shopping_mall/user_center.jsp" # 3. 检查响应中是否包含 "欢迎,管理员" grep "欢迎,管理员" user_center.html

若第二步返回登录页,说明LoginFilter未正确识别 Session 中的user对象,需检查session.setAttribute("user", user)是否在登录成功后执行,且LoginFiltersession.getAttribute("user")获取时机是否正确(应在doFilter开头)。

部署完成后,打开浏览器访问http://localhost:8080/shopping_mall/,输入默认账号admin密码123456,即可进入后台管理首页,此时user对象已存入 Session,所有/admin/*路径均可访问,证明整个 JavaWeb 购物商城项目的请求拦截、数据持久、事务控制、前端渲染四层链路已贯通。

本文还有配套的精品资源,点击获取

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询