在当今数字化消费时代,食品零售行业正经历着深刻的线上化转型。对于甜品这类注重视觉美感、口味多样性和即时体验的商品而言,一个功能完备的线上销售平台不仅能突破传统门店的时空限制,更能通过精准的商品展示和便捷的购物流程,直接激发消费者的购买欲望。本系统正是基于这一市场需求,采用SSM这一经典且高效的Java企业级开发框架组合构建而成,为甜品品牌提供了一个稳定可靠的线上经营阵地。
该系统在技术架构上严格遵循了经典的三层架构模式,确保了代码的清晰分层和职责分离。表现层由Spring MVC框架担纲,它通过@Controller注解简化了请求映射,利用@RequestMapping将HTTP请求精准路由到对应的处理方法上,并结合JSP视图技术动态生成用户界面。业务逻辑层是系统的核心,由Spring Framework的IoC容器统一管理所有Service组件。Spring的声明式事务管理在此层至关重要,它通过@Transactional注解确保了如“创建订单并同步扣减库存”这类复杂业务操作的原子性,有效保障了数据一致性。数据持久层则选用了MyBatis框架,其核心优势在于将SQL语句的灵活性与对象映射的便捷性相结合。开发人员通过XML配置文件或注解方式编写SQL,MyBatis负责将结果集自动映射到Java实体对象,大大简化了数据库操作。项目依赖管理通过Maven进行,前端页面则使用HTML、CSS和JavaScript构建交互体验。
数据库架构设计与核心表解析
一个稳健的电商系统,其基石在于合理高效的数据库设计。本系统共设计了5张核心数据表,以下是其中关键表结构的深度分析:
1. 用户表:系统安全与个性化的基石 用户表不仅存储基本的身份认证信息,更是实现个性化推荐和权限控制的基础。其设计考虑了扩展性和安全性。
CREATE TABLE users (
user_id INT AUTO_INCREMENT PRIMARY KEY,
username VARCHAR(50) NOT NULL UNIQUE,
password_hash VARCHAR(255) NOT NULL,
email VARCHAR(100) UNIQUE,
phone VARCHAR(20),
avatar_url VARCHAR(255),
default_shipping_address TEXT,
role ENUM('CUSTOMER', 'ADMIN') DEFAULT 'CUSTOMER',
account_status ENUM('ACTIVE', 'INACTIVE', 'SUSPENDED') DEFAULT 'ACTIVE',
points_balance INT DEFAULT 0,
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
last_login_at TIMESTAMP NULL,
INDEX idx_username (username),
INDEX idx_email (email),
INDEX idx_status (account_status)
);
设计亮点分析:
- 安全存储:
password_hash字段使用哈希算法(如BCrypt)存储密码,而非明文,这是现代Web应用的安全标配。 - 角色与权限分离:
role字段采用ENUM类型明确区分客户和管理员,为后续功能权限控制(如基于Spring Security的拦截)打下基础。 - 状态管理:
account_status字段允许灵活管理用户账户生命周期,如激活、禁用或挂起,增强了系统的可管理性。 - 可扩展性:预留了
avatar_url(头像)、points_balance(积分)等字段,便于未来集成用户画像和忠诚度计划。
2. 商品表:精细化SKU管理与营销支持 商品表的设计直接关系到前台展示的丰富度和后台管理的灵活性,尤其需要应对甜品品类复杂多变的属性。
CREATE TABLE products (
product_id INT AUTO_INCREMENT PRIMARY KEY,
product_name VARCHAR(200) NOT NULL,
category_id INT NOT NULL,
description TEXT,
main_image_url VARCHAR(255) NOT NULL,
detail_images JSON,
unit_price DECIMAL(10, 2) NOT NULL,
stock_quantity INT NOT NULL DEFAULT 0,
sugar_level ENUM('LOW', 'MEDIUM', 'HIGH') DEFAULT 'MEDIUM',
tags JSON,
is_reservable BOOLEAN DEFAULT FALSE,
reservation_lead_time INT COMMENT '提前预订小时数',
is_available BOOLEAN DEFAULT TRUE,
sales_count INT DEFAULT 0,
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
FOREIGN KEY (category_id) REFERENCES categories(category_id),
INDEX idx_category (category_id),
INDEX idx_availability (is_available),
INDEX idx_price (unit_price)
);
设计亮点分析:
- JSON字段的灵活应用:
detail_images和tags字段使用JSON数据类型,可以存储一个商品的多张详情图和一个由多个标签组成的数组(如["新品", "爆款", "无麸质"])。这种设计避免了创建多张关联表,在查询时又能利用数据库的JSON函数进行高效检索,非常适合存储结构可变但查询需求不极端复杂的非结构化数据。 - 商品属性精细化:
sugar_level(甜度)和is_reservable(是否可预订)等字段精准刻画了甜品的特性,为前端筛选和个性化推荐提供了数据支持。 - 运营数据追踪:
sales_count(销量)和updated_at(最后更新时间)字段为热门商品排序、库存预警等运营功能提供了数据依据。
3. 订单表:电商业务复杂逻辑的核心载体 订单表是电商系统中最为复杂的表之一,它需要准确记录一次交易的生命周期和所有关键快照信息。
CREATE TABLE orders (
order_id VARCHAR(32) PRIMARY KEY COMMENT '业务主键,如202411051000010001',
user_id INT NOT NULL,
total_amount DECIMAL(12, 2) NOT NULL,
order_status ENUM('PENDING_PAYMENT', 'PAID', 'CONFIRMED', 'IN_DELIVERY', 'COMPLETED', 'CANCELLED') DEFAULT 'PENDING_PAYMENT',
shipping_address TEXT NOT NULL,
recipient_name VARCHAR(100) NOT NULL,
recipient_phone VARCHAR(20) NOT NULL,
payment_method ENUM('ALIPAY', 'WECHAT_PAY') DEFAULT 'ALIPAY',
payment_status ENUM('UNPAID', 'PAID', 'REFUNDED') DEFAULT 'UNPAID',
order_notes TEXT COMMENT '用户订单备注',
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
paid_at TIMESTAMP NULL,
completed_at TIMESTAMP NULL,
FOREIGN KEY (user_id) REFERENCES users(user_id),
INDEX idx_user_id (user_id),
INDEX idx_create_time (created_at),
INDEX idx_status (order_status)
);
设计亮点分析:
- 业务主键设计:
order_id没有使用简单的自增ID,而是采用了包含时间戳和序列号的业务主键(如202411051000010001),这在日常运维和客户查询时更为直观和高效。 - 状态机设计:
order_status和payment_status共同定义了订单的完整状态流。这种明确的状态枚举是后端业务逻辑正确流转的保证,例如,只有状态为PAID的订单才能被确认发货。 - 信息快照:
shipping_address、recipient_name等字段在创建订单时存储了当时的信息,即使用户后来修改了默认地址,订单内的收货信息也不会改变,保证了交易记录的不可篡改性。
核心功能模块的技术实现
1. 用户认证与状态管理
用户登录是系统的入口,其实现涉及安全的密码比对和会话管理。
@Controller
@RequestMapping("/user")
public class UserController {
@Autowired
private UserService userService;
@PostMapping("/login")
public String login(@RequestParam String username,
@RequestParam String password,
HttpSession session,
Model model) {
User user = userService.authenticateUser(username, password);
if (user != null && user.getAccountStatus() == AccountStatus.ACTIVE) {
// 登录成功,将用户信息存入Session
session.setAttribute("currentUser", user);
// 根据角色跳转不同页面
if (user.getRole() == UserRole.ADMIN) {
return "redirect:/admin/dashboard";
} else {
return "redirect:/product/list";
}
} else {
model.addAttribute("errorMsg", "用户名或密码错误,或账户已被禁用");
return "user/login";
}
}
@GetMapping("/logout")
public String logout(HttpSession session) {
// 清除Session,实现登出
session.invalidate();
return "redirect:/user/login";
}
}
@Service
public class UserServiceImpl implements UserService {
@Autowired
private UserMapper userMapper;
@Override
public User authenticateUser(String username, String password) {
User user = userMapper.selectByUsername(username);
if (user != null) {
// 使用BCrypt等密码编码器进行匹配
if (passwordEncoder.matches(password, user.getPasswordHash())) {
return user;
}
}
return null;
}
}

2. 商品详情页与购物车交互
商品详情页需要聚合展示商品信息,并处理加入购物车的异步请求。
@RestController // 返回JSON数据,用于处理AJAX请求
@RequestMapping("/api/cart")
public class CartApiController {
@Autowired
private CartService cartService;
@PostMapping("/add")
public ResponseEntity<Map<String, Object>> addToCart(@RequestBody AddToCartRequest request,
HttpSession session) {
User currentUser = (User) session.getAttribute("currentUser");
if (currentUser == null) {
return ResponseEntity.status(HttpStatus.UNAUTHORIZED).body(Map.of("success", false, "message", "请先登录"));
}
try {
cartService.addItemToCart(currentUser.getUserId(), request.getProductId(), request.getQuantity());
Map<String, Object> response = new HashMap<>();
response.put("success", true);
response.put("message", "商品已成功加入购物车");
response.put("cartItemCount", cartService.getCartItemCount(currentUser.getUserId()));
return ResponseEntity.ok(response);
} catch (Exception e) {
// 例如库存不足
return ResponseEntity.badRequest().body(Map.of("success", false, "message", e.getMessage()));
}
}
}
@Service
@Transactional // 声明式事务,确保操作原子性
public class CartServiceImpl implements CartService {
@Autowired
private CartMapper cartMapper;
@Autowired
private ProductMapper productMapper;
@Override
public void addItemToCart(Long userId, Long productId, Integer quantity) {
// 1. 检查商品是否存在且可用
Product product = productMapper.selectById(productId);
if (product == null || !product.getIsAvailable()) {
throw new RuntimeException("商品不存在或已下架");
}
// 2. 检查库存
if (product.getStockQuantity() < quantity) {
throw new RuntimeException("商品库存不足");
}
// 3. 判断购物车中是否已有该商品
CartItem existingItem = cartMapper.selectByUserIdAndProductId(userId, productId);
if (existingItem != null) {
// 更新数量
existingItem.setQuantity(existingItem.getQuantity() + quantity);
cartMapper.updateQuantity(existingItem);
} else {
// 新增项
CartItem newItem = new CartItem();
newItem.setUserId(userId);
newItem.setProductId(productId);
newItem.setQuantity(quantity);
newItem.setUnitPrice(product.getUnitPrice()); // 快照加入时的价格
cartMapper.insert(newItem);
}
}
}

3. 后台商品管理
后台管理功能允许管理员对商品进行全方位的CRUD操作,是系统运营的基础。
@Controller
@RequestMapping("/admin/product")
public class AdminProductController {
@Autowired
private ProductService productService;
@GetMapping("/list")
public String productList(@RequestParam(defaultValue = "1") Integer pageNum,
@RequestParam(defaultValue = "10") Integer pageSize,
Model model) {
// 分页查询商品列表
PageHelper.startPage(pageNum, pageSize);
List<Product> productList = productService.getAllProducts();
PageInfo<Product> pageInfo = new PageInfo<>(productList);
model.addAttribute("pageInfo", pageInfo);
return "admin/product_list";
}
@PostMapping("/save")
@ResponseBody
public ResponseEntity<String> saveProduct(@RequestBody Product product) {
try {
if (product.getProductId() == null) {
productService.addProduct(product);
} else {
productService.updateProduct(product);
}
return ResponseEntity.ok("操作成功");
} catch (Exception e) {
return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).body("操作失败: " + e.getMessage());
}
}
@PostMapping("/toggle/{productId}")
@ResponseBody
public ResponseEntity<String> toggleAvailability(@PathVariable Long productId) {
productService.toggleProductAvailability(productId);
return ResponseEntity.ok("状态已更新");
}
}
<!-- MyBatis Mapper XML 片段 - 动态SQL查询 -->
<mapper namespace="com.maancode.dessertmall.mapper.ProductMapper">
<select id="selectByCondition" parameterType="map" resultType="Product">
SELECT * FROM products
<where>
<if test="categoryId != null">
AND category_id = #{categoryId}
</if>
<if test="keyword != null and keyword != ''">
AND (product_name LIKE CONCAT('%', #{keyword}, '%') OR description LIKE CONCAT('%', #{keyword}, '%'))
</if>
<if test="minPrice != null">
AND unit_price >= #{minPrice}
</if>
<if test="maxPrice != null">
AND unit_price <= #{maxPrice}
</if>
<if test="isAvailable != null">
AND is_available = #{isAvailable}
</if>
</where>
ORDER BY
<choose>
<when test="sortBy == 'price_asc'">unit_price ASC</when>
<when test="sortBy == 'price_desc'">unit_price DESC</when>
<when test="sortBy == 'sales'">sales_count DESC</when>
<when test="sortBy == 'new'">created_at DESC</when>
<otherwise>product_id DESC</otherwise>
</choose>
</select>
</mapper>

4. 预订订单处理
针对生日蛋糕等需要提前制作的甜品,预订功能是核心特色。
@Entity
@Table(name = "reservation_orders")
public class ReservationOrder {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
private Long orderId;
private Long productId;
private LocalDateTime desiredPickupTime; // 期望取货时间
private String customMessage; // 蛋糕祝福语等定制信息
// ... getters and setters
}
@Service
public class ReservationServiceImpl implements ReservationService {
@Autowired
private ReservationOrderMapper reservationOrderMapper;
@Autowired
private OrderService orderService;
@Override
@Transactional
public void createReservationOrder(Order mainOrder, ReservationOrder reservationOrder) {
// 1. 创建主订单
orderService.createOrder(mainOrder);
// 2. 关联预订信息
reservationOrder.setOrderId(mainOrder.getOrderId());
reservationOrderMapper.insert(reservationOrder);
// 3. 可以在此处触发通知后厨的逻辑
// kitchenService.notifyNewReservation(reservationOrder);
}
@Override
public List<ReservationOrder> getReservationsForDate(LocalDate date) {
LocalDateTime startOfDay = date.atStartOfDay();
LocalDateTime endOfDay = date.atTime(23, 59, 59);
return reservationOrderMapper.selectByDesiredPickupTimeBetween(startOfDay, endOfDay);
}
}

实体模型与数据映射
MyBatis的核心在于对象关系映射。以下是核心实体类Product的定义,展示了与数据库表的映射关系。
public class Product {
private Long productId;
private String productName;
private Integer categoryId;
private String description;
private String mainImageUrl;
private String detailImages; // 存储JSON字符串
private BigDecimal unitPrice;
private Integer stockQuantity;
private String sugarLevel;
private String tags; // 存储JSON字符串
private Boolean isReservable;
private Integer reservationLeadTime;
private Boolean isAvailable;
private Integer salesCount;
private Date createdAt;
private Date updatedAt;
// 便捷方法:将JSON字符串转换为List
public List<String> getDetailImageList() {
if (StringUtils.hasText(detailImages)) {
return new Gson().fromJson(detailImages, new TypeToken<List<String>>(){}.getType());
}
return new ArrayList<>();
}
// 省略getter和setter...
}
系统优化与未来展望
“甜意优选”系统已具备一个成熟电商平台的核心功能,但在以下方面仍有显著的优化和扩展空间:
- 引入Redis提升性能:将热门商品信息、购物车数据(在用户登录后)、首页静态数据等缓存至Redis。这能极大减轻MySQL的读取压力,特别是在促销活动期间。例如,购物车服务可以改造为优先读写Redis,异步持久化到数据库。
- 集成Elasticsearch实现高级搜索:对于商品搜索,尤其是模糊匹配和多条件筛选(如按口味、标签、价格区间),MySQL的
LIKE查询效率低下。引入Elasticsearch可以构建强大的全文搜索引擎,支持分词、同义词、权重设置和高亮显示,极大提升用户体验。 - 构建微服务架构:随着业务复杂度的增长,可以将单体应用拆分为用户中心、商品服务、订单服务、库存服务、支付服务等独立的微服务。这有助于团队并行开发、独立部署