基于SSM框架的在线甜品商城系统 - 源码深度解析

JavaJavaScriptMavenHTMLCSSSSM框架MySQL
2026-03-253 浏览

文章摘要

本项目是一款基于SSM(Spring + Spring MVC + MyBatis)框架技术栈开发的在线甜品商城系统,旨在为甜品商家和消费者提供一个高效、稳定、功能完整的线上交易平台。其核心业务价值在于解决了传统线下甜品店经营中地域限制强、营业时间固定、客户触达渠道单一等核心痛点,通过数字化的方式将...

在当今数字化消费时代,食品零售行业正经历着深刻的线上化转型。对于甜品这类注重视觉美感、口味多样性和即时体验的商品而言,一个功能完备的线上销售平台不仅能突破传统门店的时空限制,更能通过精准的商品展示和便捷的购物流程,直接激发消费者的购买欲望。本系统正是基于这一市场需求,采用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_imagestags字段使用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_statuspayment_status共同定义了订单的完整状态流。这种明确的状态枚举是后端业务逻辑正确流转的保证,例如,只有状态为PAID的订单才能被确认发货。
  • 信息快照shipping_addressrecipient_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...
}

系统优化与未来展望

“甜意优选”系统已具备一个成熟电商平台的核心功能,但在以下方面仍有显著的优化和扩展空间:

  1. 引入Redis提升性能:将热门商品信息、购物车数据(在用户登录后)、首页静态数据等缓存至Redis。这能极大减轻MySQL的读取压力,特别是在促销活动期间。例如,购物车服务可以改造为优先读写Redis,异步持久化到数据库。
  2. 集成Elasticsearch实现高级搜索:对于商品搜索,尤其是模糊匹配和多条件筛选(如按口味、标签、价格区间),MySQL的LIKE查询效率低下。引入Elasticsearch可以构建强大的全文搜索引擎,支持分词、同义词、权重设置和高亮显示,极大提升用户体验。
  3. 构建微服务架构:随着业务复杂度的增长,可以将单体应用拆分为用户中心、商品服务、订单服务、库存服务、支付服务等独立的微服务。这有助于团队并行开发、独立部署
本文关键词
SSM框架在线甜品商城源码解析数据库设计Java企业级开发

上下篇

上一篇
没有更多文章
下一篇
没有更多文章