在传统零售业数字化转型的浪潮中,毛绒玩具行业面临着销售渠道单一、库存管理效率低下以及客户触达范围有限等挑战。针对这些痛点,一个基于JSP+Servlet技术栈构建的在线商城系统应运而生。该系统被命名为“PlushPalace”,旨在为消费者打造一个便捷、友好的线上选购平台,同时为商家提供一套高效、可靠的后台管理工具,实现从商品上架、用户下单到库存更新的全流程数字化管理。
PlushPalace系统严格遵循模型-视图-控制器(MVC)设计模式,确保了代码的高内聚和低耦合。Servlet作为系统的控制器,负责处理所有来自客户端的HTTP请求,执行核心业务逻辑,如用户身份验证、购物车管理和订单处理。视图层由JSP页面构成,通过嵌入JSTL标签和EL表达式动态渲染数据,有效避免了在页面中直接编写Java代码,提升了代码的可读性和可维护性。模型层则由一系列封装了业务数据的JavaBean组成,并通过JDBC技术与MySQL数据库进行交互,完成数据的持久化操作。这种清晰的分层架构不仅便于团队协作开发,也为系统的后续功能扩展和维护奠定了坚实的基础。
系统架构与技术栈
PlushPalace的技术选型体现了经典Java Web开发的成熟与稳定。前端展示层采用HTML、CSS和JavaScript构建用户界面,确保跨浏览器的兼容性和响应式布局。JSP页面作为动态内容渲染的核心,利用JSTL(JSP Standard Tag Library)简化了逻辑控制,例如使用<c:forEach>标签遍历商品列表,使用EL表达式${product.name}直接输出对象属性,使得页面代码更加简洁。
<%-- 商品列表展示JSP片段 --%>
<div class="product-grid">
<c:forEach items="${productList}" var="product">
<div class="product-card">
<img src="${product.imageUrl}" alt="${product.name}">
<h3>${product.name}</h3>
<p class="price">¥${product.price}</p>
<a href="addToCart?productId=${product.id}" class="btn">加入购物车</a>
</div>
</c:forEach>
</div>
后端控制层由Servlet实现,每个Servlet对应一个具体的业务功能模块。通过@WebServlet注解或web.xml配置映射关系,Servlet能够精准拦截用户请求。例如,处理用户登录的LoginServlet会验证用户凭证,并创建会话(HttpSession)来管理用户状态。
@WebServlet("/login")
public class LoginServlet extends HttpServlet {
protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
String username = request.getParameter("username");
String password = request.getParameter("password");
UserService userService = new UserService();
User user = userService.authenticate(username, password);
if (user != null) {
HttpSession session = request.getSession();
session.setAttribute("currentUser", user);
response.sendRedirect("index.jsp");
} else {
request.setAttribute("errorMessage", "用户名或密码错误");
request.getRequestDispatcher("login.jsp").forward(request, response);
}
}
}
数据访问层采用DAO(Data Access Object)模式,将数据库操作封装在独立的类中。通过使用PreparedStatement防止SQL注入,并利用连接池(如DBCP或HikariCP)管理数据库连接,提升系统性能和安全性。
public class ProductDAO {
public List<Product> findAll() throws SQLException {
List<Product> products = new ArrayList<>();
String sql = "SELECT id, name, price, stock, image_url, description FROM products WHERE status = 1";
try (Connection conn = DataSourceUtil.getConnection();
PreparedStatement stmt = conn.prepareStatement(sql);
ResultSet rs = stmt.executeQuery()) {
while (rs.next()) {
Product product = new Product();
product.setId(rs.getInt("id"));
product.setName(rs.getString("name"));
product.setPrice(rs.getBigDecimal("price"));
product.setStock(rs.getInt("stock"));
product.setImageUrl(rs.getString("image_url"));
product.setDescription(rs.getString("description"));
products.add(product);
}
}
return products;
}
}
数据库设计深度剖析
PlushPalace的数据库由6张核心表构成,设计上充分考虑了业务的完整性和查询效率。以下重点分析用户表、商品表和订单表的设计亮点。
用户表(users) 的设计不仅包含了基本的身份信息,还通过user_type字段区分普通消费者和管理员角色,实现了系统权限的初步控制。registration_date字段记录了用户注册时间,为后续的用户行为分析和生命周期管理提供了数据支持。email字段设置为UNIQUE约束,确保了账户的唯一性,同时作为密码找回的重要凭证。
CREATE TABLE users (
id INT PRIMARY KEY AUTO_INCREMENT,
username VARCHAR(50) NOT NULL UNIQUE,
password VARCHAR(255) NOT NULL COMMENT '存储BCrypt加密后的密码',
email VARCHAR(100) NOT NULL UNIQUE,
phone VARCHAR(20),
address TEXT,
user_type ENUM('customer', 'admin') DEFAULT 'customer',
registration_date TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
last_login TIMESTAMP NULL,
status TINYINT DEFAULT 1 COMMENT '1-正常,0-禁用'
);
商品表(products) 的设计体现了电商系统的典型特征。价格字段使用DECIMAL类型,确保金融计算的精确性。stock字段实时反映库存数量,在用户下单时通过数据库事务保证并发安全。category_id外键关联商品分类表,支持灵活的商品分类管理。status字段实现了商品的软删除功能,下架的商品仍保留在数据库中但不对外展示。
CREATE TABLE products (
id INT PRIMARY KEY AUTO_INCREMENT,
name VARCHAR(200) NOT NULL,
description TEXT,
price DECIMAL(10,2) NOT NULL,
stock INT NOT NULL DEFAULT 0,
image_url VARCHAR(500),
category_id INT,
status TINYINT DEFAULT 1 COMMENT '1-上架,0-下架',
create_time TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
update_time TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
FOREIGN KEY (category_id) REFERENCES categories(id)
);
订单表(orders) 的设计是系统中最复杂的部分,采用了主订单与订单明细分离的经典模式。主订单表记录订单的整体信息,如订单号、总金额、收货地址等。订单号(order_number)使用唯一约束,通常由时间戳+随机数生成,避免重复。order_status字段使用ENUM类型明确限定订单的生命周期状态,如待支付、已支付、已发货、已完成等,便于状态跟踪和流程管理。
CREATE TABLE orders (
id INT PRIMARY KEY AUTO_INCREMENT,
order_number VARCHAR(64) NOT NULL UNIQUE,
user_id INT NOT NULL,
total_amount DECIMAL(10,2) NOT NULL,
order_status ENUM('pending', 'paid', 'shipped', 'completed', 'cancelled') DEFAULT 'pending',
shipping_address TEXT NOT NULL,
payment_method VARCHAR(50),
create_time TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
pay_time TIMESTAMP NULL,
ship_time TIMESTAMP NULL,
complete_time TIMESTAMP NULL,
FOREIGN KEY (user_id) REFERENCES users(id)
);
订单明细表(order_items)与主订单表通过order_id关联,记录了每个订单中包含的具体商品信息。这种设计支持一个订单购买多个商品的需求,同时保留了购买时的商品快照(如价格、名称),即使后续商品信息变更,订单历史记录仍保持准确。
CREATE TABLE order_items (
id INT PRIMARY KEY AUTO_INCREMENT,
order_id INT NOT NULL,
product_id INT NOT NULL,
product_name VARCHAR(200) NOT NULL,
product_price DECIMAL(10,2) NOT NULL,
quantity INT NOT NULL DEFAULT 1,
subtotal DECIMAL(10,2) NOT NULL,
FOREIGN KEY (order_id) REFERENCES orders(id) ON DELETE CASCADE,
FOREIGN KEY (product_id) REFERENCES products(id)
);
核心功能实现解析
1. 用户认证与会话管理
用户登录功能由LoginServlet处理,它验证用户提交的用户名和密码。系统使用BCrypt算法对密码进行哈希加密存储,验证时比较哈希值而非明文密码,显著提升了安全性。登录成功后,用户对象被存入HttpSession中,并在后续请求中通过过滤器(Filter)验证会话有效性,实现访问控制。

// 密码加密验证工具类
public class PasswordUtil {
private static final int BCRYPT_STRENGTH = 12;
public static String hashPassword(String plainPassword) {
return BCrypt.hashpw(plainPassword, BCrypt.gensalt(BCRYPT_STRENGTH));
}
public static boolean checkPassword(String plainPassword, String hashedPassword) {
return BCrypt.checkpw(plainPassword, hashedPassword);
}
}
2. 商品浏览与搜索
商品展示页面采用分页技术处理大量商品数据,避免一次性加载所有数据导致的性能问题。前端通过GET请求传递页码参数,后端Servlet计算分页偏移量,查询数据库并返回指定页面的商品数据。

@WebServlet("/products")
public class ProductListServlet extends HttpServlet {
protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
int page = 1;
int pageSize = 12;
String pageParam = request.getParameter("page");
if (pageParam != null && !pageParam.isEmpty()) {
page = Integer.parseInt(pageParam);
}
ProductService productService = new ProductService();
PageResult<Product> pageResult = productService.getProductsByPage(page, pageSize);
request.setAttribute("pageResult", pageResult);
request.getRequestDispatcher("/product-list.jsp").forward(request, response);
}
}
搜索功能通过SQL的LIKE语句实现模糊匹配,支持按商品名称或描述进行关键字搜索。为了提高搜索效率,可以在products表的name和description字段上建立全文索引。
-- 为商品表添加全文索引
CREATE FULLTEXT INDEX idx_product_search ON products(name, description);
-- 使用全文索引进行搜索
SELECT * FROM products
WHERE MATCH(name, description) AGAINST('毛绒熊' IN NATURAL LANGUAGE MODE)
AND status = 1;
3. 购物车管理与订单生成
购物车功能采用HttpSession临时存储用户选择的商品,无需频繁操作数据库。每个购物车项包含商品ID、数量和小计金额,当用户添加商品时,系统会检查库存是否充足。

@WebServlet("/addToCart")
public class AddToCartServlet extends HttpServlet {
protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
int productId = Integer.parseInt(request.getParameter("productId"));
int quantity = Integer.parseInt(request.getParameter("quantity", "1"));
HttpSession session = request.getSession();
Map<Integer, CartItem> cart = (Map<Integer, CartItem>) session.getAttribute("cart");
if (cart == null) {
cart = new HashMap<>();
session.setAttribute("cart", cart);
}
ProductService productService = new ProductService();
Product product = productService.getProductById(productId);
if (product != null && product.getStock() >= quantity) {
CartItem item = cart.get(productId);
if (item != null) {
item.setQuantity(item.getQuantity() + quantity);
} else {
item = new CartItem(product, quantity);
cart.put(productId, item);
}
}
response.sendRedirect("cart.jsp");
}
}
订单生成是系统的核心交易环节,涉及多个数据库表的原子性操作。使用数据库事务确保订单创建、库存扣减和购物车清空等操作要么全部成功,要么全部回滚,保证数据一致性。
@WebServlet("/createOrder")
public class CreateOrderServlet extends HttpServlet {
protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
HttpSession session = request.getSession();
User user = (User) session.getAttribute("currentUser");
Map<Integer, CartItem> cart = (Map<Integer, CartItem>) session.getAttribute("cart");
if (user == null) {
response.sendRedirect("login.jsp");
return;
}
OrderService orderService = new OrderService();
try {
String orderNumber = orderService.createOrder(user, cart, request.getParameter("shippingAddress"));
session.removeAttribute("cart"); // 清空购物车
response.sendRedirect("orderSuccess.jsp?orderNumber=" + orderNumber);
} catch (InsufficientStockException e) {
request.setAttribute("errorMessage", "库存不足,无法完成订单");
request.getRequestDispatcher("cart.jsp").forward(request, response);
} catch (Exception e) {
request.setAttribute("errorMessage", "订单创建失败,请重试");
request.getRequestDispatcher("cart.jsp").forward(request, response);
}
}
}
4. 后台管理功能
后台管理模块为商家提供了完整的商品和订单管理能力。管理员可以添加新商品、调整价格和库存、处理订单发货等。管理界面通过权限控制确保只有管理员角色可以访问。

商品添加功能包含图片上传处理,使用Apache Commons FileUpload组件接收multipart/form-data类型的请求,将上传的图片保存到服务器指定目录,并在数据库中记录图片URL。
@WebServlet("/admin/addProduct")
@MultipartConfig
public class AddProductServlet extends HttpServlet {
protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
// 验证管理员权限
HttpSession session = request.getSession();
User user = (User) session.getAttribute("currentUser");
if (user == null || !user.getUserType().equals("admin")) {
response.sendError(403, "权限不足");
return;
}
String name = request.getParameter("name");
String description = request.getParameter("description");
BigDecimal price = new BigDecimal(request.getParameter("price"));
int stock = Integer.parseInt(request.getParameter("stock"));
int categoryId = Integer.parseInt(request.getParameter("categoryId"));
Part filePart = request.getPart("image");
String fileName = System.currentTimeMillis() + "_" + getSubmittedFileName(filePart);
String uploadPath = getServletContext().getRealPath("/uploads");
File uploadDir = new File(uploadPath);
if (!uploadDir.exists()) uploadDir.mkdir();
String filePath = uploadPath + File.separator + fileName;
filePart.write(filePath);
Product product = new Product();
product.setName(name);
product.setDescription(description);
product.setPrice(price);
product.setStock(stock);
product.setCategoryId(categoryId);
product.setImageUrl("uploads/" + fileName);
ProductService productService = new ProductService();
productService.addProduct(product);
response.sendRedirect("productManagement.jsp?message=添加成功");
}
private String getSubmittedFileName(Part part) {
for (String cd : part.getHeader("content-disposition").split(";")) {
if (cd.trim().startsWith("filename")) {
String fileName = cd.substring(cd.indexOf('=') + 1).trim().replace("\"", "");
return fileName.substring(fileName.lastIndexOf('/') + 1).substring(fileName.lastIndexOf('\\') + 1);
}
}
return null;
}
}
订单管理功能允许管理员查看所有订单,并按状态进行筛选。订单状态变更(如从"已支付"改为"已发货")时会记录操作时间,并更新相关时间戳字段。

@WebServlet("/admin/updateOrderStatus")
public class UpdateOrderStatusServlet extends HttpServlet {
protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
int orderId = Integer.parseInt(request.getParameter("orderId"));
String newStatus = request.getParameter("status");
OrderService orderService = new OrderService();
boolean success = orderService.updateOrderStatus(orderId, newStatus);
if (success) {
response.getWriter().write("{\"success\": true}");
} else {
response.getWriter().write("{\"success\": false, \"message\": \"状态更新失败\"}");
}
}
}
实体模型设计
系统的模型层由多个JavaBean组成,每个Bean对应数据库中的一张表,封装了对象的属性和行为。以Product类为例,它遵循JavaBean规范,提供私有字段和公共的getter/setter方法。
public class Product {
private int id;
private String name;
private String description;
private BigDecimal price;
private int stock;
private String imageUrl;
private int categoryId;
private int status;
private Date createTime;
private Date updateTime;
// 无参构造函数
public Product() {}
// Getter和Setter方法
public int getId() { return id; }
public void setId(int id) { this.id = id; }
public String getName() { return name; }
public void setName(String name) { this.name = name; }
public String getDescription() { return description; }
public void setDescription(String description) { this.description = description; }
public BigDecimal getPrice() { return price; }
public void setPrice(BigDecimal price) { this.price = price; }
public int getStock() { return stock; }
public void setStock(int stock) { this.stock = stock; }
public String getImageUrl() { return imageUrl; }
public void setImageUrl(String imageUrl) { this.imageUrl = imageUrl; }
public