随着电子商务的蓬勃发展,传统零售业面临着数字化转型的迫切需求。本项目正是为应对这一趋势而设计构建的,它采用成熟的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_time和update_time字段分别使用CURRENT_TIMESTAMP和ON 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_address、shipping_name、shipping_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 {
//