当前位置:网站首页 >  攻略

商城功能从零到一:手把手搭建可落地的电商系统

时间:2026年06月11日 04:57:43 来源:易频IT社区

一、环境准备与项目初始化

我们使用Spring Boot 2.7.15 + MySQL 8.0 + Redis 6.2作为技术栈,这是目前最稳定且文档最全的组合。

1.1 开发环境配置

打开终端,执行以下命令创建项目骨架:

```bash curl https://start.spring.io/starter.zip \ -d type=maven-project \ -d language=java \ -d bootVersion=2.7.15 \ -d baseDir=shop-system \ -d groupId=com.example \ -d artifactId=shop \ -d name=shop \ -d description=商城系统 \ -d packageName=com.example.shop \ -d packaging=jar \ -d javaVersion=11 \ -d dependencies=web,data-jpa,mysql,redis,validation \ -o shop.zip unzip shop.zip cd shop-system ```

解压后,用IDE打开项目。在pom.xml中添加MyBatis Plus依赖:

```xml com.baomidou mybatis-plus-boot-starter 3.5.3.1 org.apache.commons commons-lang3 3.12.0 ```

1.2 数据库配置

创建MySQL数据库并配置连接,在application.yml中写入:

```yaml spring: datasource: driver-class-name: com.mysql.cj.jdbc.Driver url: jdbc:mysql://localhost:3306/shop_db?useUnicode=true&characterEncoding=utf8&serverTimezone=Asia/Shanghai username: root password: your_password_here jpa: hibernate: ddl-auto: update show-sql: true redis: host: localhost port: 6379 password: database: 0 timeout: 3000ms lettuce: pool: max-active: 8 max-wait: -1ms max-idle: 8 min-idle: 0 mybatis-plus: configuration: log-impl: org.apache.ibatis.logging.stdout.StdOutImpl global-config: db-config: logic-delete-field: deleted logic-delete-value: 1 logic-not-delete-value: 0 ```

二、核心数据表设计与实现

2.1 商品表结构

在MySQL中执行以下SQL创建商品表:

```sql CREATE TABLE `product` ( `id` bigint NOT NULL AUTO_INCREMENT COMMENT '主键', `name` varchar(200) NOT NULL COMMENT '商品名称', `category_id` bigint NOT NULL COMMENT '分类ID', `price` decimal(10,2) NOT NULL COMMENT '销售价格', `market_price` decimal(10,2) DEFAULT NULL COMMENT '市场价', `stock` int NOT NULL DEFAULT '0' COMMENT '库存数量', `main_image` varchar(500) DEFAULT NULL COMMENT '主图URL', `sub_images` text COMMENT '子图URL,JSON数组格式', `detail` text COMMENT '商品详情', `status` tinyint NOT NULL DEFAULT '1' COMMENT '状态:0-下架 1-上架', `sort_order` int DEFAULT '0' COMMENT '排序权重', `created_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP, `updated_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, `deleted` tinyint NOT NULL DEFAULT '0', PRIMARY KEY (`id`), KEY `idx_category` (`category_id`), KEY `idx_status` (`status`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='商品表'; ```

2.2 订单表结构

创建订单表,注意索引优化:

```sql CREATE TABLE `order` ( `id` bigint NOT NULL AUTO_INCREMENT COMMENT '订单ID', `order_no` varchar(32) NOT NULL COMMENT '订单编号', `user_id` bigint NOT NULL COMMENT '用户ID', `total_amount` decimal(10,2) NOT NULL COMMENT '订单总金额', `pay_amount` decimal(10,2) NOT NULL COMMENT '实际支付金额', `freight_amount` decimal(10,2) DEFAULT '0.00' COMMENT '运费', `pay_type` tinyint DEFAULT NULL COMMENT '支付方式:1-支付宝 2-微信', `status` tinyint NOT NULL DEFAULT '0' COMMENT '状态:0-待支付 1-已支付 2-已发货 3-已完成 4-已取消', `delivery_company` varchar(50) DEFAULT NULL COMMENT '物流公司', `delivery_no` varchar(50) DEFAULT NULL COMMENT '物流单号', `receiver_name` varchar(50) NOT NULL COMMENT '收货人姓名', `receiver_phone` varchar(20) NOT NULL COMMENT '收货人电话', `receiver_address` varchar(500) NOT NULL COMMENT '收货地址', `remark` varchar(500) DEFAULT NULL COMMENT '订单备注', `payment_time` datetime DEFAULT NULL COMMENT '支付时间', `delivery_time` datetime DEFAULT NULL COMMENT '发货时间', `receive_time` datetime DEFAULT NULL COMMENT '确认收货时间', `created_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP, `updated_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, `deleted` tinyint NOT NULL DEFAULT '0', PRIMARY KEY (`id`), UNIQUE KEY `uk_order_no` (`order_no`), KEY `idx_user` (`user_id`), KEY `idx_status` (`status`), KEY `idx_create_time` (`created_time`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='订单表'; ```

2.3 实体类实现

创建Product实体类,使用MyBatis Plus注解:

```java @Data @TableName("product") public class Product { @TableId(type = IdType.AUTO) private Long id; private String name; private Long categoryId; private BigDecimal price; private BigDecimal marketPrice; private Integer stock; private String mainImage; @TableField(typeHandler = JacksonTypeHandler.class) private List subImages; private String detail; private Integer status; private Integer sortOrder; @TableField(fill = FieldFill.INSERT) private LocalDateTime createdTime; @TableField(fill = FieldFill.INSERT_UPDATE) private LocalDateTime updatedTime; @TableLogic private Integer deleted; } ```

三、关键业务逻辑实现

3.1 商品库存扣减方案

创建库存服务类,使用Redis分布式锁防止超卖:

```java @Service public class StockService { @Autowired private StringRedisTemplate redisTemplate; @Autowired private ProductMapper productMapper; private static final String STOCK_LOCK_PREFIX = "stock:lock:"; private static final long LOCK_EXPIRE = 3000; public boolean reduceStock(Long productId, Integer quantity) { String lockKey = STOCK_LOCK_PREFIX + productId; String lockValue = UUID.randomUUID().toString(); try { // 获取分布式锁 Boolean locked = redisTemplate.opsForValue() .setIfAbsent(lockKey, lockValue, LOCK_EXPIRE, TimeUnit.MILLISECONDS); if (Boolean.TRUE.equals(locked)) { // 检查库存 Product product = productMapper.selectById(productId); if (product == null || product.getStock() < quantity) { return false; } // 扣减库存 int rows = productMapper.updateStock(productId, quantity); return rows > 0; } return false; } finally { // 释放锁 String currentValue = redisTemplate.opsForValue().get(lockKey); if (lockValue.equals(currentValue)) { redisTemplate.delete(lockKey); } } } } ```

在ProductMapper.xml中实现updateStock方法:

```xml UPDATE product SET stock = stock - {quantity}, updated_time = NOW() WHERE id = {productId} AND stock >= {quantity} AND deleted = 0 ```

3.2 订单生成与幂等性处理

商城功能从零到一:手把手搭建可落地的电商系统

创建订单服务,使用数据库唯一索引保证订单号不重复:

```java @Service public class OrderService { @Autowired private OrderMapper orderMapper; @Transactional(rollbackFor = Exception.class) public String createOrder(CreateOrderDTO dto) { // 生成订单号:时间戳 + 随机数 String orderNo = generateOrderNo(); Order order = new Order(); order.setOrderNo(orderNo); order.setUserId(dto.getUserId()); order.setTotalAmount(dto.getTotalAmount()); order.setPayAmount(dto.getPayAmount()); order.setStatus(0); order.setReceiverName(dto.getReceiverName()); order.setReceiverPhone(dto.getReceiverPhone()); order.setReceiverAddress(dto.getReceiverAddress()); try { orderMapper.insert(order); return orderNo; } catch (DuplicateKeyException e) { // 订单号重复,重试 return createOrder(dto); } } private String generateOrderNo() { SimpleDateFormat sdf = new SimpleDateFormat("yyyyMMddHHmmss"); String timeStr = sdf.format(new Date()); String randomStr = String.valueOf((int)((Math.random() 9 + 1) 1000)); return timeStr + randomStr; } } ```

四、购物车功能实现

4.1 Redis购物车结构设计

使用Hash结构存储购物车,key为用户ID,field为商品ID,value为商品数量和选中状态:

```java @Service public class CartService { @Autowired private StringRedisTemplate redisTemplate; private static final String CART_KEY_PREFIX = "cart:user:"; public void addToCart(Long userId, Long productId, Integer quantity) { String key = CART_KEY_PREFIX + userId; String field = productId.toString(); // 获取当前数量 String current = (String) redisTemplate.opsForHash().get(key, field); Integer currentQuantity = current != null ? Integer.parseInt(current.split(":")[0]) : 0; // 更新数量 String value = (currentQuantity + quantity) + ":1"; // 1表示选中状态 redisTemplate.opsForHash().put(key, field, value); // 设置过期时间30天 redisTemplate.expire(key, 30, TimeUnit.DAYS); } public List getCart(Long userId) { String key = CART_KEY_PREFIX + userId; Map entries = redisTemplate.opsForHash().entries(key); List items = new ArrayList<>(); for (Map.Entry entry : entries.entrySet()) { Long productId = Long.parseLong((String) entry.getKey()); String[] values = ((String) entry.getValue()).split(":"); CartItemVO item = new CartItemVO(); item.setProductId(productId); item.setQuantity(Integer.parseInt(values[0])); item.setSelected("1".equals(values[1])); items.add(item); } return items; } } ```

4.2 购物车结算

实现购物车结算接口,验证库存并生成预订单:

```java @PostMapping("/cart/checkout") public ApiResult checkout(@RequestBody CheckoutDTO dto) { // 1. 获取购物车选中商品 List cartItems = cartService.getCart(dto.getUserId()); List selectedItems = cartItems.stream() .filter(CartItemVO::isSelected) .collect(Collectors.toList()); // 2. 验证库存 for (CartItemVO item : selectedItems) { Product product = productService.getById(item.getProductId()); if (product.getStock() < item.getQuantity()) { return ApiResult.error("商品[" + product.getName() + "]库存不足"); } } // 3. 计算总金额 BigDecimal totalAmount = selectedItems.stream() .map(item -> { Product product = productService.getById(item.getProductId()); return product.getPrice().multiply( BigDecimal.valueOf(item.getQuantity())); }) .reduce(BigDecimal.ZERO, BigDecimal::add); // 4. 生成预订单数据 PreOrderVO preOrder = new PreOrderVO(); preOrder.setItems(selectedItems); preOrder.setTotalAmount(totalAmount); preOrder.setFreightAmount(calculateFreight(dto.getAddressId())); preOrder.setPayAmount(preOrder.getTotalAmount() .add(preOrder.getFreightAmount())); return ApiResult.success(preOrder); } ```

五、支付回调处理

5.1 支付回调接口

实现支付宝/微信支付回调,注意验签和幂等处理:

```java @PostMapping("/pay/callback") public String payCallback(HttpServletRequest request) { // 1. 获取回调参数 Map params = getRequestParams(request); String orderNo = params.get("out_trade_no"); // 2. 验证签名 if (!verifySignature(params)) { return "FAIL"; } // 3. 查询订单状态 Order order = orderService.getByOrderNo(orderNo); if (order == null || order.getStatus() != 0) { return "SUCCESS"; // 已处理过,直接返回成功 } // 4. 更新订单状态 order.setStatus(1); order.setPaymentTime(LocalDateTime.now()); order.setPayType(Integer.parseInt(params.get("pay_type"))); orderService.updateById(order); // 5. 扣减库存 List items = orderItemService.getByOrderId(order.getId()); for (OrderItem item : items) { stockService.reduceStock(item.getProductId(), item.getQuantity()); } // 6. 清理购物车 cartService.clearCheckedItems(order.getUserId()); return "SUCCESS"; } ```

5.2 回调验签方法

支付宝回调验签实现:

```java private boolean verifySignature(Map params) { try { String sign = params.get("sign"); String signType = params.get("sign_type"); params.remove("sign"); params.remove("sign_type"); String content = params.entrySet().stream() .sorted(Map.Entry.comparingByKey()) .map(entry -> entry.getKey() + "=" + entry.getValue()) .collect(Collectors.joining("&")); if ("RSA2".equals(signType)) { Signature signature = Signature.getInstance("SHA256WithRSA"); signature.initVerify(getAlipayPublicKey()); signature.update(content.getBytes(StandardCharsets.UTF_8)); return signature.verify(Base64.getDecoder().decode(sign)); } } catch (Exception e) { log.error("验签失败", e); } return false; } ```

六、部署与监控

6.1 Docker部署配置

创建Dockerfile:

```dockerfile FROM openjdk:11-jre-slim WORKDIR /app COPY target/shop-0.0.1-SNAPSHOT.jar app.jar RUN ln -snf /usr/share/zoneinfo/Asia/Shanghai /etc/localtime && echo Asia/Shanghai > /etc/timezone EXPOSE 8080 ENTRYPOINT ["java", "-jar", "app.jar", "--spring.profiles.active=prod"] ```

创建docker-compose.yml:

```yaml version: '3.8' services: mysql: image: mysql:8.0 environment: MYSQL_ROOT_PASSWORD: ${DB_PASSWORD} MYSQL_DATABASE: shop_db ports: - "3306:3306" volumes: - mysql_data:/var/lib/mysql redis: image: redis:6.2-alpine ports: - "6379:6379" app: build: . depends_on: - mysql - redis environment: SPRING_DATASOURCE_URL: jdbc:mysql://mysql:3306/shop_db SPRING_REDIS_HOST: redis ports: - "8080:8080" volumes: mysql_data: ```

6.2 健康检查配置

在application.yml中添加:

```yaml management: endpoints: web: exposure: include: health,info,metrics,prometheus endpoint: health: show-details: always metrics: export: prometheus: enabled: true ```

以上实现了商城系统的核心功能,包括商品管理、购物车、订单处理、支付回调等完整链路。按照步骤操作即可搭建可用的电商系统,所有代码均可直接复制使用。

标签 商城功能

相关推荐

最新

热门

推荐

精选

标签

易频IT社区是综合性互联网IT技术门户网站,专注分享网络技术、服务器运维、网络安全、编程开发、系统架构、云计算、大数据等行业干货,实时更新IT行业资讯、零基础教程、实战案例,为IT从业者、技术爱好者提供专业的学习交流平台。

Copyright © 2021-2026 易频IT社区. All Rights Reserved. 备案号:闽ICP备2023013482号 网站地图