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

JavaJavaScriptMavenHTMLCSSSSM框架MySQL
2026-02-234 浏览

文章摘要

本项目是一款基于SSM(Spring + Spring MVC + MyBatis)框架技术栈构建的在线百货商城系统,旨在为中小型零售企业提供一个功能完整、稳定可靠的电子商务解决方案。其核心业务价值在于打通线上销售渠道,将传统百货零售业务数字化,有效解决了实体门店受限于营业时间与地理位置的痛点,同时...

随着电子商务的蓬勃发展,传统零售业面临着数字化转型的迫切需求。本项目正是为应对这一趋势而设计构建的,它采用成熟的SSM(Spring + Spring MVC + MyBatis)技术栈,为中小型零售企业提供了一个功能完备、性能稳定的线上销售平台。该系统不仅实现了商品展示、在线交易、订单处理等核心电商功能,还通过精细化的后台管理模块,显著提升了商户的运营效率。

系统采用经典的三层架构设计。表现层由Spring MVC框架负责,其核心组件DispatcherServlet作为前端控制器,统一接收所有HTTP请求,并根据配置的映射关系分发给相应的控制器(@Controller)。控制器通过注解(如@RequestMapping)清晰地定义了URL与处理方法的对应关系,并利用@ResponseBody等注解支持RESTful风格的接口,便于前后端分离开发。业务逻辑层基于Spring框架的IoC(控制反转)容器进行管理。通过依赖注入(DI)机制,各服务组件(@Service)之间的耦合度被降至最低。Spring的声明式事务管理(@Transactional)确保了核心业务操作(如创建订单、扣减库存)的原子性和一致性。数据持久层选用MyBatis框架,它通过XML映射文件或注解方式,将Java对象(POJO)与数据库表记录灵活地映射起来。开发者可以编写高度优化的SQL语句,同时享受ORM带来的便利。数据库选用开源且性能优异的MySQL,其表结构设计紧密围绕电商业务的核心实体展开。

数据库设计亮点解析

一个稳健的电商系统,其基石在于合理、高效的数据库设计。本系统共设计22张数据表,以下对其中几个核心表的结构进行深入分析。

1. 商品信息表(product 商品表是系统的核心数据载体,其设计需兼顾查询性能与扩展性。

CREATE TABLE `product` (
  `product_id` int(11) NOT NULL AUTO_INCREMENT COMMENT '商品ID',
  `product_name` varchar(255) NOT NULL COMMENT '商品名称',
  `category_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 '库存数量',
  `status` tinyint(4) NOT NULL DEFAULT '1' COMMENT '状态(1:上架,0:下架)',
  `main_image` varchar(500) DEFAULT NULL COMMENT '主图URL',
  `sub_images` text COMMENT '副图URLs(JSON格式存储)',
  `detail` text COMMENT '商品详情(HTML内容)',
  `sales_volume` int(11) DEFAULT '0' COMMENT '销量',
  `create_time` datetime DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
  `update_time` datetime DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',
  PRIMARY KEY (`product_id`),
  KEY `idx_category_id` (`category_id`),
  KEY `idx_status` (`status`),
  KEY `idx_create_time` (`create_time`),
  KEY `idx_sales_volume` (`sales_volume`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='商品信息表';

设计亮点分析:

  • 字段冗余与查询优化sales_volume(销量)字段是一个典型的冗余设计。虽然销量可以通过聚合订单明细表计算得出,但在高并发查询场景下(如按销量排序),实时计算会带来巨大的数据库压力。通过冗余存储,并配合idx_sales_volume索引,可以极大提升商品列表排序查询的性能。
  • 灵活的多图存储sub_images字段采用TEXT类型,用于存储商品副图的URL列表。通常建议使用JSON格式(如["url1", "url2", "url3"])进行存储。这种设计比建立单独的商品图片关系表更为灵活,减少了表关联查询,在读取商品信息时可直接获取所有图片数据,适合图片数量相对固定且不多的场景。
  • 自动化的时间追踪create_timeupdate_time字段分别使用CURRENT_TIMESTAMPON UPDATE CURRENT_TIMESTAMP作为默认值。这确保了记录创建和最后一次更新的时间能被自动、准确地记录,无需在业务代码中手动设置,简化了开发并保证了数据的一致性。

2. 订单主表(order_master 订单表是交易流程的核心,其设计复杂,需要准确反映订单的生命周期。

CREATE TABLE `order_master` (
  `order_id` varchar(32) NOT NULL COMMENT '订单ID(非自增,业务生成)',
  `user_id` int(11) NOT NULL COMMENT '用户ID',
  `order_amount` decimal(10,2) NOT NULL COMMENT '订单总金额',
  `order_status` tinyint(4) NOT NULL DEFAULT '0' COMMENT '订单状态(0:新订单,1:已支付,2:已发货,3:已完成,4:已关闭)',
  `pay_status` tinyint(4) NOT NULL DEFAULT '0' COMMENT '支付状态(0:未支付,1:已支付)',
  `shipping_address` varchar(500) NOT NULL COMMENT '收货地址',
  `shipping_name` varchar(20) NOT NULL COMMENT '收货人姓名',
  `shipping_phone` varchar(20) NOT NULL COMMENT '收货人电话',
  `pay_time` datetime DEFAULT NULL COMMENT '支付时间',
  `shipping_time` datetime DEFAULT NULL COMMENT '发货时间',
  `end_time` datetime DEFAULT NULL COMMENT '交易完成时间',
  `create_time` datetime DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
  `update_time` datetime DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',
  PRIMARY KEY (`order_id`),
  KEY `idx_user_id` (`user_id`),
  KEY `idx_create_time` (`create_time`),
  KEY `idx_order_status` (`order_status`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='订单主表';

设计亮点分析:

  • 非自增订单号order_id没有使用常见的自增整数,而是采用varchar(32)并由业务系统生成(例如,使用“时间戳+随机数+机器标识”的算法)。这样做的好处是:1. 避免通过订单ID推测平台业务量;2. 更易于在分布式系统中保证唯一性;3. 订单号本身可以携带一定的业务信息(如日期)。
  • 状态分离设计:将order_status(订单状态)和pay_status(支付状态)分离。这种设计更为精细,例如,一个订单可能处于“已发货”的order_status,但后续如果发生退款,其pay_status可能变更为其他状态。状态的分离使得业务逻辑更加清晰,便于扩展复杂的售后流程。
  • 地址信息快照shipping_addressshipping_nameshipping_phone等字段在创建订单时从用户地址簿中复制过来,而非直接关联地址ID。这是电商系统中一个至关重要的设计。它保证了订单历史数据不受用户后续修改收货地址的影响,确保了订单数据的不可变性,对于售后和物流追踪具有重要意义。

核心功能实现深度解析

1. 商品浏览与购物车管理 商品浏览是用户旅程的起点,而购物车是促成交易转化的关键环节。

前端页面交互:商品列表页通过Ajax异步加载数据,实现无刷新分页和筛选。当用户点击“加入购物车”按钮时,前端会调用对应的购物车API。 商品详情页

后端购物车服务实现:购物车数据可以存储在Session中(适用于未登录用户)或数据库中(适用于已登录用户,实现多端同步)。以下是处理添加商品到购物车的Controller层代码:

@Controller
@RequestMapping("/cart")
public class CartController {

    @Autowired
    private CartService cartService;

    @PostMapping("/add")
    @ResponseBody
    public ResponseEntity<Map<String, Object>> addToCart(
            @RequestParam Integer productId,
            @RequestParam Integer quantity,
            HttpSession session) {
        
        // 1. 参数校验
        if (productId == null || quantity == null || quantity <= 0) {
            return ResponseEntity.badRequest().body(Collections.singletonMap("msg", "参数错误"));
        }

        // 2. 获取当前用户(从Session或Token中)
        User currentUser = (User) session.getAttribute("currentUser");
        Integer userId = currentUser != null ? currentUser.getUserId() : null;

        try {
            // 3. 调用服务层业务逻辑
            cartService.addOrUpdateItem(userId, productId, quantity, session);
            Map<String, Object> result = new HashMap<>();
            result.put("code", 200);
            result.put("msg", "添加成功");
            return ResponseEntity.ok(result);
        } catch (ProductNotFoundException e) {
            return ResponseEntity.status(HttpStatus.NOT_FOUND).body(Collections.singletonMap("msg", "商品不存在"));
        } catch (Exception e) {
            return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).body(Collections.singletonMap("msg", "系统错误"));
        }
    }
}

对应的Service层方法负责核心的业务逻辑,包括库存检查、数量合并等:

@Service
@Transactional
public class CartService {

    @Autowired
    private ProductMapper productMapper;
    @Autowired
    private CartItemMapper cartItemMapper;

    public void addOrUpdateItem(Integer userId, Integer productId, Integer quantity, HttpSession session) {
        // 检查商品是否存在且已上架
        Product product = productMapper.selectByPrimaryKey(productId);
        if (product == null || product.getStatus() != 1) {
            throw new ProductNotFoundException("商品不存在或已下架");
        }
        // 检查库存是否充足
        if (product.getStock() < quantity) {
            throw new InsufficientStockException("商品库存不足");
        }

        String cartKey = userId != null ? "cart:" + userId : "session_cart:" + session.getId();

        // 如果用户已登录,操作数据库
        if (userId != null) {
            CartItem existingItem = cartItemMapper.selectByUserIdAndProductId(userId, productId);
            if (existingItem != null) {
                // 更新已有商品数量
                existingItem.setQuantity(existingItem.getQuantity() + quantity);
                cartItemMapper.updateByPrimaryKey(existingItem);
            } else {
                // 新增购物车项
                CartItem newItem = new CartItem();
                newItem.setUserId(userId);
                newItem.setProductId(productId);
                newItem.setQuantity(quantity);
                newItem.setCreateTime(new Date());
                cartItemMapper.insert(newItem);
            }
        } else {
            // 未登录用户,操作Session中的购物车
            // ... (Session操作逻辑)
        }
    }
}

查看购物车

2. 订单创建与库存扣减 订单创建是一个典型的分布式事务场景,需要保证数据的一致性。

订单生成流程:从购物车结算页提交订单时,系统会执行一系列原子操作。以下是创建订单的核心Service方法,该方法被@Transactional注解标记,确保在一个数据库事务中执行。

@Service
public class OrderServiceImpl implements OrderService {

    @Autowired
    private OrderMasterMapper orderMasterMapper;
    @Autowired
    private OrderDetailMapper orderDetailMapper;
    @Autowired
    private ProductMapper productMapper;

    @Override
    @Transactional(rollbackFor = Exception.class) // 发生任何异常都回滚事务
    public OrderCreateResult createOrder(OrderCreateRequest request, Integer userId) {
        // 1. 生成唯一的订单号
        String orderId = generateOrderId();

        // 2. 计算订单总金额并校验(遍历商品,计算总价,与前端传入的总价进行校验,防止篡改)
        BigDecimal orderAmount = BigDecimal.ZERO;
        List<OrderDetail> orderDetailList = new ArrayList<>();

        for (OrderItemDTO item : request.getItems()) {
            Product product = productMapper.selectByPrimaryKey(item.getProductId());
            // 校验商品状态和库存
            if (product == null || product.getStatus() != 1) {
                throw new BusinessException("商品[" + product.getProductName() + "]已下架或不存在");
            }
            if (product.getStock() < item.getQuantity()) {
                throw new BusinessException("商品[" + product.getProductName() + "]库存不足");
            }

            // 计算该商品项总价
            BigDecimal itemTotal = product.getPrice().multiply(new BigDecimal(item.getQuantity()));
            orderAmount = orderAmount.add(itemTotal);

            // 构建订单明细
            OrderDetail detail = new OrderDetail();
            detail.setOrderId(orderId);
            detail.setProductId(product.getProductId());
            detail.setProductName(product.getProductName());
            detail.setProductPrice(product.getPrice());
            detail.setProductQuantity(item.getQuantity());
            detail.setProductIcon(product.getMainImage());
            orderDetailList.add(detail);

            // 3. 扣减库存(乐观锁实现,防止超卖)
            int updateCount = productMapper.reduceStock(item.getProductId(), item.getQuantity());
            if (updateCount == 0) {
                // 扣减失败,说明库存已被其他线程修改,抛出异常回滚事务
                throw new BusinessException("商品[" + product.getProductName() + "]库存变更,请重新确认");
            }
        }

        // 4. 校验总金额
        if (orderAmount.compareTo(request.getOrderAmount()) != 0) {
            throw new BusinessException("订单金额校验失败");
        }

        // 5. 插入订单主表记录
        OrderMaster orderMaster = new OrderMaster();
        orderMaster.setOrderId(orderId);
        orderMaster.setUserId(userId);
        orderMaster.setOrderAmount(orderAmount);
        orderMaster.setOrderStatus(OrderStatusEnum.NEW.getCode());
        orderMaster.setPayStatus(PayStatusEnum.WAIT.getCode());
        // ... 设置其他字段(地址等)
        orderMasterMapper.insert(orderMaster);

        // 6. 批量插入订单明细记录
        for (OrderDetail detail : orderDetailList) {
            orderDetailMapper.insert(detail);
        }

        // 7. 清空用户购物车中已下单的商品
        cartService.clearCartItem(userId, request.getItems());

        // 8. 返回订单创建结果
        return new OrderCreateResult(orderId, orderAmount);
    }

    private String generateOrderId() {
        // 示例:时间戳(yyyyMMddHHmmss) + 6位随机数
        SimpleDateFormat sdf = new SimpleDateFormat("yyyyMMddHHmmss");
        String timeStr = sdf.format(new Date());
        String randomNum = String.valueOf((int) ((Math.random() * 9 + 1) * 100000));
        return timeStr + randomNum;
    }
}

在上述代码中,库存扣减使用了乐观锁机制,这是通过MyBatis的Mapper映射的SQL语句实现的:

<!-- ProductMapper.xml -->
<update id="reduceStock">
    UPDATE product
    SET stock = stock - #{quantity},
        update_time = NOW()
    WHERE product_id = #{productId}
    AND stock >= #{quantity} <!-- 乐观锁条件:确保扣减后库存不为负 -->
</update>

确认提交订单 提交订单成功

3. 后台商品管理与库存维护 后台管理系统是商户运营的核心,商品管理模块尤为重要。

商品增删改查:后台Controller提供RESTful API供管理前端调用,实现对商品的全面管理。

@RestController
@RequestMapping("/admin/product")
public class AdminProductController {

    @Autowired
    private ProductAdminService productAdminService;

    @GetMapping("/list")
    public PageInfo<ProductVO> getProductList(
            @RequestParam(defaultValue = "1") Integer pageNum,
            @RequestParam(defaultValue = "10") Integer pageSize,
            ProductQueryDTO queryDTO) {
        // 使用PageHelper进行分页
        PageHelper.startPage(pageNum, pageSize);
        List<ProductVO> productList = productAdminService.getProductList(queryDTO);
        return new PageInfo<>(productList);
    }

    @PostMapping("/save")
    public ResponseEntity<ApiResponse> saveProduct(@RequestBody @Valid ProductSaveRequest request) {
        productAdminService.saveProduct(request);
        return ResponseEntity.ok(ApiResponse.success("操作成功"));
    }

    @PostMapping("/{productId}/updateStatus")
    public ResponseEntity<ApiResponse> updateStatus(@PathVariable Integer productId, 
                                                   @RequestParam Integer status) {
        productAdminService.updateStatus(productId, status);
        return ResponseEntity.ok(ApiResponse.success("状态更新成功"));
    }
}

ProductAdminService中包含了复杂的业务逻辑,例如在保存商品时处理图片上传和分类关联:

@Service
public class ProductAdminServiceImpl implements ProductAdminService {

    @Autowired
    private ProductMapper productMapper;
    @Autowired
    private FileStorageService fileStorageService;

    @Override
    @Transactional
    public void saveProduct(ProductSaveRequest request) {
        Product product = new Product();
        BeanUtils.copyProperties(request, product); // 属性拷贝

        // 处理主图上传
        if (request.getMainImageFile() != null && !request.getMainImageFile().isEmpty()) {
            String mainImageUrl = fileStorageService.storeFile(request.getMainImageFile());
            product.setMainImage(mainImageUrl);
        }

        // 处理副图上传(多文件)
        if (request.getSubImageFiles() != null && !request.getSubImageFiles().isEmpty()) {
            List<String> subImageUrls = new ArrayList<>();
            for (MultipartFile file : request.getSubImageFiles()) {
                String url = fileStorageService.storeFile(file);
                subImageUrls.add(url);
            }
            // 将URL列表转换为JSON字符串存储
            product.setSubImages(JSON.toJSONString(subImageUrls));
        }

        if (request.getProductId() == null) {
            // 新增商品
            productMapper.insert(product);
        } else {
            // 
本文关键词
SSM框架在线百货商城源码解析电商系统数据库设计

上下篇

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