我们使用Spring Boot 2.7.15 + MySQL 8.0 + Redis 6.2作为技术栈,这是目前最稳定且文档最全的组合。
打开终端,执行以下命令创建项目骨架:
```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创建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 ```在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='商品表'; ```创建订单表,注意索引优化:
```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='订单表'; ```创建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创建库存服务类,使用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
创建订单服务,使用数据库唯一索引保证订单号不重复:
```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; } } ```使用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易频IT社区是综合性互联网IT技术门户网站,专注分享网络技术、服务器运维、网络安全、编程开发、系统架构、云计算、大数据等行业干货,实时更新IT行业资讯、零基础教程、实战案例,为IT从业者、技术爱好者提供专业的学习交流平台。
Copyright © 2021-2026 易频IT社区. All Rights Reserved. 备案号:闽ICP备2023013482号 网站地图