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

JavaJavaScriptMavenHTMLCSSSSM框架MySQLJSP+Servlet
2026-02-194 浏览

文章摘要

本项目是一款基于SSM(Spring+SpringMVC+MyBatis)框架构建的在线学习用品商城系统,旨在为教师、学生及家长提供一站式、便捷高效的学习用品采购平台。系统核心解决了传统线下购买学习用品时存在的品类不全、价格不透明、时间地点受限等痛点,通过线上集中展示与销售,帮助用户快速找到所需文具...

随着教育信息化的不断深入,学习用品的采购方式也迎来了数字化转型。传统的线下文具店采购模式存在品类有限、价格不透明、受时间和地点制约等诸多不便。为此,我们设计并实现了一个名为“学海优品”的在线学习用品商城系统。该系统基于成熟的SSM(Spring + SpringMVC + MyBatis)技术栈构建,旨在为教师、学生及家长提供一个品类齐全、操作便捷、高效安全的一站式学习用品采购平台。

系统架构与技术栈

“学海优品”系统采用经典的三层架构模式,实现了表现层、业务逻辑层和数据持久层的清晰分离,确保了系统的高内聚、低耦合特性。

  • 表现层:基于SpringMVC框架构建,负责接收用户HTTP请求、调用业务逻辑处理并渲染视图返回响应。通过@Controller注解声明控制器,利用@RequestMapping映射请求路径,实现了灵活的请求路由。视图层采用JSP(JavaServer Pages)技术,结合JSTL标签库和EL表达式,实现了动态内容的展示,同时保证了页面逻辑的简洁。
  • 业务逻辑层:由Spring Framework的核心IoC(控制反转)容器管理。所有业务逻辑组件,如商品服务、用户服务、订单服务等,均以@Service注解声明,并通过@Autowired注解实现依赖注入。Spring的声明式事务管理(@Transactional)被广泛应用于订单创建、库存扣减等需要保证数据一致性的核心业务场景,确保了操作的原子性。
  • 数据持久层:选用MyBatis作为ORM框架。它通过XML配置文件或注解的方式,将Java对象与数据库表记录进行映射。MyBatis提供了强大的动态SQL能力,能够灵活地构建复杂的查询条件。与Hibernate等全自动ORM框架相比,MyBatis允许开发者对执行的SQL语句进行精确控制,这对于性能要求较高的电商查询操作尤为重要。
  • 其他技术:项目依赖管理由Maven完成,数据库采用稳定可靠的MySQL,前端页面使用HTML、CSS和JavaScript进行构建和交互。

数据库设计与核心表分析

数据库是系统的基石,良好的设计是保证系统性能、数据一致性和扩展性的关键。本系统共设计11张核心表,以下对其中的几个关键表进行深入分析。

1. 商品信息表 (stationery)

商品表是电商系统的核心,其设计直接影响到商品展示、搜索和库存管理的效率。

CREATE TABLE `stationery` (
  `id` int(11) NOT NULL AUTO_INCREMENT COMMENT '商品ID',
  `name` varchar(255) NOT NULL COMMENT '商品名称',
  `type_id` int(11) NOT NULL COMMENT '商品分类ID',
  `price` decimal(10,2) NOT NULL COMMENT '商品价格',
  `original_price` decimal(10,2) DEFAULT NULL COMMENT '商品原价',
  `stock` int(11) NOT NULL DEFAULT '0' COMMENT '商品库存',
  `image_url` varchar(500) DEFAULT NULL COMMENT '商品图片URL',
  `description` text COMMENT '商品描述',
  `sales_volume` int(11) DEFAULT '0' COMMENT '商品销量',
  `status` tinyint(4) DEFAULT '1' COMMENT '商品状态(1:上架,0:下架)',
  `create_time` datetime DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
  `update_time` datetime DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',
  PRIMARY KEY (`id`),
  KEY `idx_type_id` (`type_id`),
  KEY `idx_status` (`status`),
  KEY `idx_sales_volume` (`sales_volume`),
  CONSTRAINT `fk_stationery_type` FOREIGN KEY (`type_id`) REFERENCES `stationery_type` (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='商品信息表';

设计亮点分析

  • 字段完整性:表结构涵盖了商品的基本信息(名称、价格)、业务信息(库存、销量、状态)、扩展信息(描述、图片)以及审计信息(创建/更新时间),设计全面。
  • 索引优化:针对高频查询场景,建立了多个索引。idx_type_id用于加速按分类筛选商品;idx_status用于快速查询已上架商品;idx_sales_volume则便于按销量排序,实现热门商品推荐。合理的索引策略是应对电商系统海量商品数据查询性能挑战的关键。
  • 外键约束:通过FOREIGN KEY与商品分类表(stationery_type)关联,保证了数据的一致性和完整性,避免了“脏数据”的产生。
  • 价格精度:使用DECIMAL(10,2)类型存储价格,精确到分,完全符合金融计算的要求。

2. 订单主表 (orders)

订单表记录了交易的核心信息,其设计需要兼顾查询效率与业务逻辑的复杂性。

CREATE TABLE `orders` (
  `id` varchar(32) NOT NULL COMMENT '订单ID(非自增,例如使用UUID或雪花算法ID)',
  `user_id` int(11) NOT NULL COMMENT '用户ID',
  `total_amount` decimal(10,2) NOT NULL COMMENT '订单总金额',
  `actual_amount` decimal(10,2) NOT NULL COMMENT '实付金额',
  `payment_status` tinyint(4) NOT NULL DEFAULT '0' COMMENT '支付状态(0:待支付,1:已支付,2:已退款)',
  `order_status` tinyint(4) NOT NULL DEFAULT '0' COMMENT '订单状态(0:待发货,1:已发货,2:已完成,3:已取消)',
  `shipping_address` varchar(500) NOT NULL COMMENT '收货地址',
  `create_time` datetime DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
  `pay_time` datetime DEFAULT NULL COMMENT '支付时间',
  `shipping_time` datetime DEFAULT NULL COMMENT '发货时间',
  `end_time` datetime DEFAULT NULL COMMENT '交易完成时间',
  PRIMARY KEY (`id`),
  KEY `idx_user_id` (`user_id`),
  KEY `idx_create_time` (`create_time`),
  KEY `idx_payment_status` (`payment_status`),
  KEY `idx_order_status` (`order_status`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='订单主表';

设计亮点分析

  • 订单ID设计:主键id未采用传统的自增整数,而是使用字符串(如UUID或分布式ID生成算法如雪花算法)。这样做的好处是:1)在分布式系统环境下保证ID全局唯一;2)避免通过ID序号推测平台订单量等敏感信息。
  • 状态字段分离:将payment_status(支付状态)和order_status(订单状态)分离。这种设计使得状态流转更加清晰灵活,例如可以处理“已支付但未发货”或“已发货但用户申请退款”等复杂业务场景。
  • 时间戳记录:不仅记录了订单创建时间,还详细记录了支付、发货、完成等关键节点的时间。这些数据对于后续的业务分析、用户行为追踪和纠纷处理至关重要。
  • 复合索引考虑:虽然当前索引是单字段的,但在实际业务中,查询“某个用户待支付的订单”(user_id + payment_status)是非常高频的操作。在数据量增大后,可以考虑建立(user_id, payment_status)这样的复合索引来进一步提升查询性能。

3. 购物车表 (cart)

购物车作为用户操作的临时载体,其设计需要关注并发和数据清理。

CREATE TABLE `cart` (
  `id` int(11) NOT NULL AUTO_INCREMENT,
  `user_id` int(11) NOT NULL COMMENT '用户ID',
  `stationery_id` int(11) NOT NULL COMMENT '商品ID',
  `quantity` int(11) NOT NULL COMMENT '商品数量',
  `selected` tinyint(1) DEFAULT '1' COMMENT '是否选中(1:是,0:否)',
  `create_time` datetime DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
  `update_time` datetime DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',
  PRIMARY KEY (`id`),
  UNIQUE KEY `uk_user_stationery` (`user_id`, `stationery_id`),
  KEY `idx_user_id` (`user_id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='购物车表';

设计亮点分析

  • 唯一性约束:通过UNIQUE KEY uk_user_stationery (user_id, stationery_id),确保同一个用户不能重复添加同一商品到购物车,而是应该更新已有记录的数量。这有效防止了数据冗余。
  • 选中状态selected字段支持用户在前端灵活选择购物车中的部分商品进行结算,是实现复杂购物车逻辑的基础。
  • 性能考虑:基于user_id的索引可以快速获取指定用户的整个购物车列表,响应迅速。

核心功能模块深度解析

1. 商品浏览与分类检索

商品展示是商城系统的门面。系统实现了多维度的商品浏览方式,包括分类导航、关键词搜索、排序等。

后端控制器代码示例 (StationeryController.java):

@Controller
@RequestMapping("/stationery")
public class StationeryController {

    @Autowired
    private StationeryService stationeryService;

    /**
     * 根据分类ID分页查询商品
     * @param typeId 分类ID
     * @param pageNum 页码
     * @param pageSize 每页大小
     * @param model SpringMVC模型
     * @return 商品列表页面
     */
    @GetMapping("/category/{typeId}")
    public String getStationeryByType(@PathVariable("typeId") Integer typeId,
                                      @RequestParam(value = "page", defaultValue = "1") Integer pageNum,
                                      @RequestParam(value = "size", defaultValue = "12") Integer pageSize,
                                      Model model) {
        // 构建分页请求
        PageRequest pageRequest = PageRequest.of(pageNum - 1, pageSize);
        // 调用服务层查询
        Page<Stationery> stationeryPage = stationeryService.findByTypeId(typeId, pageRequest);
        // 将结果添加到模型,供视图层渲染
        model.addAttribute("stationeryPage", stationeryPage);
        model.addAttribute("typeId", typeId);
        return "stationery/list";
    }

    /**
     * 商品关键词搜索
     * @param keyword 搜索关键词
     * @param pageNum 页码
     * @param pageSize 每页大小
     * @param model SpringMVC模型
     * @return 搜索结果页面
     */
    @GetMapping("/search")
    public String searchStationery(@RequestParam("keyword") String keyword,
                                   @RequestParam(value = "page", defaultValue = "1") Integer pageNum,
                                   @RequestParam(value = "size", defaultValue = "12") Integer pageSize,
                                   Model model) {
        PageRequest pageRequest = PageRequest.of(pageNum - 1, pageSize);
        Page<Stationery> stationeryPage = stationeryService.searchByName(keyword, pageRequest);
        model.addAttribute("stationeryPage", stationeryPage);
        model.addAttribute("keyword", keyword);
        return "stationery/search-result";
    }
}

服务层代码片段 (StationeryServiceImpl.java):

@Service
@Transactional(readOnly = true) // 查询操作设置为只读事务,提升性能
public class StationeryServiceImpl implements StationeryService {

    @Autowired
    private StationeryMapper stationeryMapper;

    @Override
    public Page<Stationery> findByTypeId(Integer typeId, PageRequest pageRequest) {
        // 1. 根据条件查询总记录数
        long total = stationeryMapper.countByTypeId(typeId);
        // 2. 查询当前页数据列表
        List<Stationery> content = stationeryMapper.selectByTypeId(typeId, pageRequest);
        // 3. 构造Page对象返回
        return new PageImpl<>(content, pageRequest, total);
    }

    @Override
    public Page<Stationery> searchByName(String keyword, PageRequest pageRequest) {
        // 处理关键词,例如添加模糊匹配符
        String processedKeyword = "%" + keyword.trim() + "%";
        long total = stationeryMapper.countByNameLike(processedKeyword);
        List<Stationery> content = stationeryMapper.selectByNameLike(processedKeyword, pageRequest);
        return new PageImpl<>(content, pageRequest, total);
    }
}

MyBatis Mapper XML 片段 (StationeryMapper.xml):

<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.xuehaiyoupin.mapper.StationeryMapper">

    <sql id="Base_Column_List">
        id, name, type_id, price, stock, image_url, sales_volume, status
    </sql>

    <!-- 根据分类ID查询商品(带分页) -->
    <select id="selectByTypeId" resultType="com.xuehaiyoupin.entity.Stationery">
        SELECT
        <include refid="Base_Column_List"/>
        FROM stationery
        WHERE type_id = #{typeId}
        AND status = 1 <!-- 只查询上架商品 -->
        ORDER BY sales_volume DESC, create_time DESC
        LIMIT #{offset}, #{pageSize}
    </select>

    <!-- 根据商品名称模糊查询 -->
    <select id="selectByNameLike" resultType="com.xuehaiyoupin.entity.Stationery">
        SELECT
        <include refid="Base_Column_List"/>
        FROM stationery
        WHERE name LIKE #{keyword}
        AND status = 1
        ORDER BY 
          CASE WHEN name LIKE CONCAT(#{keyword}, '%') THEN 1 ELSE 2 END, -- 前缀匹配的优先级更高
          sales_volume DESC
        LIMIT #{offset}, #{pageSize}
    </select>

    <select id="countByTypeId" parameterType="int" resultType="long">
        SELECT COUNT(*) FROM stationery WHERE type_id = #{typeId} AND status = 1
    </select>

</mapper>

商品分类浏览 图:清晰的商品分类浏览界面,用户可快速定位所需品类。

2. 购物车管理与订单生成

购物车是连接商品浏览和订单结算的桥梁,其核心逻辑包括添加商品、更新数量、选中状态切换以及生成订单。

购物车项实体类 (CartItem.java):

public class CartItem {
    private Integer id;
    private Integer userId;
    private Stationery stationery; // 关联的商品对象,而非仅ID
    private Integer quantity;
    private Boolean selected;
    // ... getters and setters

    /**
     * 计算当前购物车项的总价
     * @return 商品单价 * 数量
     */
    public BigDecimal getTotalPrice() {
        if (stationery != null && stationery.getPrice() != null) {
            return stationery.getPrice().multiply(new BigDecimal(quantity));
        }
        return BigDecimal.ZERO;
    }
}

订单生成服务核心代码 (OrderServiceImpl.java):

@Service
@Transactional // 订单生成涉及多表操作,需要事务管理
public class OrderServiceImpl implements OrderService {

    @Autowired
    private OrderMapper orderMapper;
    @Autowired
    private OrderDetailMapper orderDetailMapper;
    @Autowired
    private StationeryMapper stationeryMapper;
    @Autowired
    private CartService cartService;

    @Override
    public String createOrder(Integer userId, List<Integer> cartItemIds, String shippingAddress) {
        // 1. 参数校验
        if (userId == null || cartItemIds == null || cartItemIds.isEmpty()) {
            throw new IllegalArgumentException("参数错误");
        }

        // 2. 验证并获取选中的购物车项
        List<CartItem> selectedItems = cartService.getSelectedItems(userId, cartItemIds);
        if (selectedItems.isEmpty()) {
            throw new RuntimeException("购物车中无选中的商品");
        }

        // 3. 计算订单总金额并校验库存
        BigDecimal totalAmount = BigDecimal.ZERO;
        for (CartItem item : selectedItems) {
            Stationery stationery = item.getStationery();
            if (stationery.getStock() < item.getQuantity()) {
                throw new RuntimeException("商品【" + stationery.getName() + "】库存不足");
            }
            totalAmount = totalAmount.add(item.getTotalPrice());
        }

        // 4. 生成订单ID(这里使用简单UUID,生产环境建议用雪花算法)
        String orderId = UUID.randomUUID().toString().replace("-", "").toUpperCase();

        // 5. 创建订单主记录
        Order order = new Order();
        order.setId(orderId);
        order.setUserId(userId);
        order.setTotalAmount(totalAmount);
        order.setActualAmount(totalAmount); // 假设无优惠,实付金额等于总金额
        order.setShippingAddress(shippingAddress);
        order.setPaymentStatus(0); // 待支付
        order.setOrderStatus(0);   // 待发货
        order.setCreateTime(new Date());
        orderMapper.insert(order);

        // 6. 创建订单明细记录并扣减库存
        for (CartItem item : selectedItems) {
            OrderDetail detail = new OrderDetail();
            detail.setOrderId(orderId);
            detail.setStationeryId(item.getStationery().getId());
            detail.setQuantity(item.getQuantity());
            detail.setPrice(item.getStationery().getPrice());
            orderDetailMapper.insert(detail);

            // 扣减库存
            int updateCount = stationeryMapper.decreaseStock(item.getStationery().getId(), item.getQuantity());
            if (updateCount == 0) {
                // 如果扣减失败,抛出异常,事务回滚
                throw new RuntimeException("商品库存更新失败,可能已被其他用户购买");
            }
        }

        // 7. 清空已生成订单的购物车项
        cartService.deleteItemsByIds(cartItemIds);

        return orderId;
    }
}

添加商品到购物车 图:用户查看商品详情并可将商品加入购物车。

![提交订单](https://images.maancode.com/projects/ssm-online

本文关键词
SSM框架在线学习用品商城源码解析SpringMyBatis

上下篇

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