1. 项目概述:JSP+Servlet网上商城系统技术解析
十年前我刚入行JavaWeb开发时,网上商城系统是检验开发者技术能力的"试金石"。这个基于JSP+Servlet+MySQL的技术组合,至今仍是许多高校教学和企业内部系统开发的主流选择。不同于现在流行的Spring Boot框架,这种经典架构能让我们更透彻地理解JavaWeb的核心机制。
这个系统本质上是一个B/S架构的电子商务平台,包含商品展示、购物车、订单管理、用户中心等标准模块。采用JSP负责视图渲染、Servlet处理业务逻辑、MySQL存储数据、AJAX实现异步交互的技术路线。虽然架构简单,但涵盖了JavaWeb开发的全部核心技术点,包括:
- 前后端数据交互(表单提交/参数传递)
- 会话管理(Cookie/Session)
- 数据库CRUD操作
- 事务处理
- 基础安全防护
提示:建议使用IntelliJ IDEA + Tomcat 9.0 + MySQL 8.0作为开发环境,这是目前最稳定的组合。社区版IDEA完全够用,没必要追求旗舰版。
需要模型API调用? 免费领10W Token,多模型网关一键接入 Claude、DeepSeek 等主流模型。
2. 技术栈深度解析
2.1 JSP与Servlet的黄金组合
JSP(JavaServer Pages)本质上是Servlet的"语法糖",在项目结构中你会看到每个JSP最终都会被编译成Servlet。但二者在项目中承担不同角色:
-
JSP:视图层担当
- 使用EL表达式简化Java代码嵌入(如
${productList}) - 通过JSTL标签库实现循环、条件判断(避免在JSP中写Java代码)
- 典型文件结构:
/WEB-INF/views/目录下存放.jsp文件
- 使用EL表达式简化Java代码嵌入(如
-
Servlet:控制器核心
- 继承
HttpServlet并重写doGet()/doPost()方法 - 通过
request.getParameter()获取表单数据 - 调用Service层处理业务逻辑
- 使用
request.setAttribute()传递数据到JSP - 通过
response.sendRedirect()或request.getRequestDispatcher().forward()控制页面跳转
- 继承
java复制// 典型Servlet结构示例
@WebServlet("/product/detail")
public class ProductDetailServlet extends HttpServlet {
protected void doGet(HttpServletRequest request, HttpServletResponse response) {
String pid = request.getParameter("id");
ProductService service = new ProductService();
Product product = service.findById(pid);
request.setAttribute("product", product);
request.getRequestDispatcher("/WEB-INF/views/product_detail.jsp").forward(request, response);
}
}
2.2 MySQL数据库设计要点
商城系统的数据库设计直接影响后续开发效率,推荐采用InnoDB引擎并注意以下设计原则:
-
核心表结构:
- 用户表(users):
user_id, username, password(加密), email, phone, create_time - 商品表(products):
product_id, name, price, stock, category_id, description - 订单表(orders):
order_id, user_id, total_amount, status, create_time - 订单项(order_items):
item_id, order_id, product_id, quantity, price
- 用户表(users):
-
索引优化:
sql复制ALTER TABLE products ADD INDEX idx_category (category_id); ALTER TABLE orders ADD INDEX idx_user_status (user_id, status); -
事务处理:
java复制// 下单事务示例 Connection conn = null; try { conn = dataSource.getConnection(); conn.setAutoCommit(false); orderDao.create(conn, order); for(Item item : items) { item.setOrderId(order.getId()); orderItemDao.create(conn, item); productDao.reduceStock(conn, item.getProductId(), item.getQuantity()); } conn.commit(); } catch (SQLException e) { if(conn != null) conn.rollback(); throw e; } finally { if(conn != null) conn.close(); }
2.3 AJAX的巧妙应用
虽然项目主体采用同步请求,但在以下场景推荐使用AJAX提升体验:
-
购物车操作:
javascript复制// 添加购物车 function addToCart(productId) { $.post('/cart/add', {pid: productId}, function(data) { if(data.success) { $('#cart-count').text(data.totalItems); } }); } -
用户名实时验证:
javascript复制$('#username').on('blur', function() { $.get('/user/check?username=' + $(this).val(), function(data) { $('#username-msg').text(data.available ? '可用' : '已存在'); }); }); -
后台响应设计:
java复制@WebServlet("/user/check") public class UserCheckServlet extends HttpServlet { protected void doGet(HttpServletRequest request, HttpServletResponse response) throws IOException { String username = request.getParameter("username"); boolean available = !userService.exists(username); response.setContentType("application/json"); response.getWriter().write("{\"available\":" + available + "}"); } }
3. 项目实战关键环节
3.1 用户认证与安全防护
-
密码加密存储:
java复制// 使用BCrypt加密 public String encryptPassword(String raw) { return BCrypt.hashpw(raw, BCrypt.gensalt()); } public boolean checkPassword(String raw, String encrypted) { return BCrypt.checkpw(raw, encrypted); } -
Session管理:
java复制// 登录成功设置Session HttpSession session = request.getSession(); session.setAttribute("currentUser", user); session.setMaxInactiveInterval(30 * 60); // 30分钟过期 // 拦截器检查登录 if(request.getSession().getAttribute("currentUser") == null) { response.sendRedirect("/login"); return; } -
XSS防护:
jsp复制<!-- JSP中输出内容时使用JSTL转义 --> <c:out value="${userInputContent}"/>
3.2 商品展示与分页实现
-
分页查询SQL:
sql复制SELECT * FROM products WHERE category_id = ? ORDER BY create_time DESC LIMIT ?, ? -- 参数:offset, pageSize -
分页工具类设计:
java复制public class Page<T> { private int pageNumber; private int pageSize; private int totalCount; private List<T> data; // 计算总页数 public int getTotalPages() { return (int) Math.ceil((double)totalCount / pageSize); } } -
前端分页组件:
jsp复制<div class="pagination"> <c:if test="${page.pageNumber > 1}"> <a href="?page=${page.pageNumber-1}">上一页</a> </c:if> <c:forEach begin="1" end="${page.totalPages}" var="i"> <c:choose> <c:when test="${i == page.pageNumber}"> <span class="current">${i}</span> </c:when> <c:otherwise> <a href="?page=${i}">${i}</a> </c:otherwise> </c:choose> </c:forEach> <c:if test="${page.pageNumber < page.totalPages}"> <a href="?page=${page.pageNumber+1}">下一页</a> </c:if> </div>
3.3 订单业务流程实现
-
订单状态机设计:
java复制public enum OrderStatus { UNPAID(1, "待支付"), PAID(2, "已支付"), SHIPPED(3, "已发货"), COMPLETED(4, "已完成"), CANCELLED(0, "已取消"); // 状态转换校验 public static boolean canChangeTo(int from, int to) { // 实现状态转换规则... } } -
库存扣减策略:
sql复制UPDATE products SET stock = stock - ? WHERE product_id = ? AND stock >= ? -- 防止超卖 -
订单超时处理:
java复制// 定时任务检查未支付订单 @WebListener public class OrderTimeoutChecker implements ServletContextListener { private ScheduledExecutorService scheduler; public void contextInitialized(ServletContextEvent event) { scheduler = Executors.newSingleThreadScheduledExecutor(); scheduler.scheduleAtFixedRate(() -> { orderService.cancelUnpaidOrders(30); // 30分钟未支付 }, 1, 1, TimeUnit.MINUTES); } }
4. 开发常见问题与解决方案
4.1 中文乱码问题
-
全局解决方案:
xml复制<!-- web.xml配置过滤器 --> <filter> <filter-name>encodingFilter</filter-name> <filter-class>org.apache.catalina.filters.SetCharacterEncodingFilter</filter-class> <init-param> <param-name>encoding</param-name> <param-value>UTF-8</param-value> </init-param> </filter> <filter-mapping> <filter-name>encodingFilter</filter-name> <url-pattern>/*</url-pattern> </filter-mapping> -
JSP页面设置:
jsp复制<%@ page contentType="text/html;charset=UTF-8" language="java" %> -
MySQL连接配置:
properties复制jdbc.url=jdbc:mysql://localhost:3306/mall?useUnicode=true&characterEncoding=UTF-8
4.2 静态资源加载问题
-
正确目录结构:
code复制/WEB-INF/ /static/ /css/ /js/ /images/ /META-INF/ -
Servlet配置注意:
java复制@WebServlet(urlPatterns = "/admin/*", loadOnStartup = 1) // 不要用/*拦截所有请求,否则会拦截静态资源 -
前端资源引用:
html复制<!-- 正确方式 --> <link href="${pageContext.request.contextPath}/static/css/style.css"> <!-- 错误方式 --> <link href="/static/css/style.css"> <!-- 缺少contextPath -->
4.3 性能优化技巧
-
数据库连接池配置:
xml复制<!-- context.xml配置 --> <Resource name="jdbc/mall" auth="Container" type="javax.sql.DataSource" maxTotal="100" maxIdle="30" maxWaitMillis="10000" username="root" password="123456" driverClassName="com.mysql.cj.jdbc.Driver" url="jdbc:mysql://localhost:3306/mall"/> -
JSP预编译:
xml复制<!-- web.xml配置 --> <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> </init-param> </servlet> -
缓存策略:
java复制// 商品详情缓存示例 public Product getProductById(String id) { String cacheKey = "product_" + id; Product product = cache.get(cacheKey); if(product == null) { product = productDao.findById(id); cache.put(cacheKey, product, 30, TimeUnit.MINUTES); } return product; }
5. 项目部署与上线准备
5.1 生产环境配置
-
Tomcat优化:
xml复制<!-- conf/server.xml 连接器配置 --> <Connector port="8080" protocol="HTTP/1.1" maxThreads="200" minSpareThreads="25" connectionTimeout="20000" redirectPort="8443" compression="on" compressableMimeType="text/html,text/xml,text/css,application/javascript"/> -
MySQL生产配置:
ini复制[mysqld] innodb_buffer_pool_size = 1G innodb_log_file_size = 256M max_connections = 200 query_cache_size = 64M -
日志管理:
properties复制# log4j.properties 配置示例 log4j.rootLogger=INFO, file log4j.appender.file=org.apache.log4j.RollingFileAppender log4j.appender.file.File=/var/log/mall/app.log log4j.appender.file.MaxFileSize=10MB log4j.appender.file.MaxBackupIndex=10
5.2 持续集成方案
-
Maven打包:
xml复制<plugin> <groupId>org.apache.maven.plugins</groupId> <artifactId>maven-war-plugin</artifactId> <version>3.3.2</version> <configuration> <warName>mall</warName> <failOnMissingWebXml>false</failOnMissingWebXml> </configuration> </plugin> -
自动化部署脚本:
bash复制#!/bin/bash WAR_PATH="/path/to/mall.war" TOMCAT_HOME="/opt/tomcat" BACKUP_DIR="/backup/$(date +%Y%m%d)" mkdir -p $BACKUP_DIR cp $TOMCAT_HOME/webapps/mall.war $BACKUP_DIR systemctl stop tomcat rm -rf $TOMCAT_HOME/webapps/mall* cp $WAR_PATH $TOMCAT_HOME/webapps/ systemctl start tomcat -
健康检查接口:
java复制@WebServlet("/health") public class HealthCheckServlet extends HttpServlet { protected void doGet(HttpServletRequest request, HttpServletResponse response) throws IOException { boolean dbOk = checkDatabase(); response.setContentType("application/json"); response.getWriter().write("{\"status\":\"" + (dbOk ? "UP" : "DOWN") + "\"}"); } }
6. 项目扩展方向建议
-
引入Redis缓存:
- 会话共享(替代HttpSession)
- 商品热门排行榜
- 秒杀系统库存缓存
-
增加支付接口:
- 支付宝沙箱环境集成
- 微信支付H5对接
- 支付结果异步通知处理
-
升级前端技术:
- 渐进式采用Vue.js替换部分JSP
- 使用Axios替代原生AJAX
- 引入Element UI等组件库
-
微服务化改造:
- 将用户服务、商品服务、订单服务拆分
- 采用Dubbo或Spring Cloud进行服务通信
- 引入API网关统一入口
-
安全加固:
- 添加CSRF防护
- 实施SQL注入过滤
- 敏感数据加密存储
- 增加操作日志审计
这个经典架构的网上商城系统虽然技术不算新颖,但正是这种"原始"的实现方式,能让我们深入理解JavaWeb开发的本质。我在实际教学中发现,能完整实现这个项目的开发者,过渡到Spring Boot等现代框架会非常顺畅,因为他们已经掌握了Web开发的核心原理。
