环境SpringBoot 2.5.15 Java8 MySQL8 Redis Redisson RabbitMQ 原生MyBatis XML包含数据库全表、完整配置、全部实体、常量、工具类、MapperXML、Service、Controller、防重令牌、分布式锁、Redis 缓存、MQ 延时关单、乐观锁防超卖、短事务、支付 / 取消 / 超时关闭一、SQL 全量建表语句sqlCREATE DATABASE IF NOT EXISTS shop_full DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci; USE shop_full; -- 商品表(乐观锁version) CREATE TABLE product ( id BIGINT PRIMARY KEY AUTO_INCREMENT, product_name VARCHAR(100) NOT NULL COMMENT 商品名称, price DECIMAL(10,2) NOT NULL COMMENT 单价, stock INT NOT NULL DEFAULT 0 COMMENT 库存, version INT NOT NULL DEFAULT 0 COMMENT 乐观锁版本号, create_time DATETIME DEFAULT CURRENT_TIMESTAMP, update_time DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP ) ENGINEInnoDB DEFAULT CHARSETutf8mb4; -- 订单主表 CREATE TABLE order_info ( id BIGINT PRIMARY KEY AUTO_INCREMENT, order_no VARCHAR(32) NOT NULL COMMENT 订单号, user_id BIGINT NOT NULL, total_amount DECIMAL(10,2) NOT NULL, pay_amount DECIMAL(10,2) DEFAULT 0, order_status TINYINT NOT NULL DEFAULT 0 COMMENT 0待支付 1已支付 2已取消 3已退款, pay_time DATETIME NULL, cancel_time DATETIME NULL, expire_time DATETIME NULL, create_time DATETIME DEFAULT CURRENT_TIMESTAMP, update_time DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, UNIQUE KEY uk_order_no (order_no), KEY idx_user_id (user_id), KEY idx_order_status (order_status) ) ENGINEInnoDB DEFAULT CHARSETutf8mb4; -- 订单明细表 CREATE TABLE order_item ( id BIGINT PRIMARY KEY AUTO_INCREMENT, order_no VARCHAR(32) NOT NULL, product_id BIGINT NOT NULL, product_name VARCHAR(100) NOT NULL, product_price DECIMAL(10,2) NOT NULL, quantity INT NOT NULL, create_time DATETIME DEFAULT CURRENT_TIMESTAMP, KEY idx_order_no (order_no) ) ENGINEInnoDB DEFAULT CHARSETutf8mb4; -- 支付记录表 CREATE TABLE pay_log ( id BIGINT PRIMARY KEY AUTO_INCREMENT, order_no VARCHAR(32) NOT NULL, pay_no VARCHAR(64) COMMENT 第三方支付流水号, pay_amount DECIMAL(10,2) NOT NULL, pay_status TINYINT NOT NULL DEFAULT 0 COMMENT 0待支付 1成功 2失败, pay_type TINYINT DEFAULT 1 COMMENT 1微信 2支付宝, create_time DATETIME DEFAULT CURRENT_TIMESTAMP, update_time DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, KEY idx_order_no (order_no) ) ENGINEInnoDB DEFAULT CHARSETutf8mb4; -- 操作日志表 CREATE TABLE operation_log ( id BIGINT PRIMARY KEY AUTO_INCREMENT, content VARCHAR(255) NOT NULL, create_time DATETIME DEFAULT CURRENT_TIMESTAMP ) ENGINEInnoDB DEFAULT CHARSETutf8mb4; -- 测试数据 INSERT INTO product(product_name,price,stock,version) VALUES (华为手机,3999.00,100,0);二、pom.xml 完整依赖xml?xml version1.0 encodingUTF-8? project xmlnshttp://maven.apache.org/POM/4.0.0 xmlns:xsihttp://www.w3.org/2001/XMLSchema-instance xsi:schemaLocationhttp://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd modelVersion4.0.0/modelVersion parent groupIdorg.springframework.boot/groupId artifactIdspring-boot-starter-parent/artifactId version2.5.15/version relativePath/ /parent groupIdcom.ecommerce/groupId artifactIdorder-full-system/artifactId version0.0.1-SNAPSHOT/version properties java.version1.8/java.version /properties dependencies !-- Web -- dependency groupIdorg.springframework.boot/groupId artifactIdspring-boot-starter-web/artifactId /dependency !-- JDBC -- dependency groupIdorg.springframework.boot/groupId artifactIdspring-boot-starter-jdbc/artifactId /dependency !-- MyBatis -- dependency groupIdorg.mybatis.spring.boot/groupId artifactIdmybatis-spring-boot-starter/artifactId version2.2.2/version /dependency !-- Redis -- dependency groupIdorg.springframework.boot/groupId artifactIdspring-boot-starter-data-redis/artifactId /dependency !-- Redisson分布式锁 -- dependency groupIdorg.redisson/groupId artifactIdredisson-spring-boot-starter/artifactId version3.15.0/version /dependency !-- RabbitMQ -- dependency groupIdorg.springframework.boot/groupId artifactIdspring-boot-starter-amqp/artifactId /dependency !-- 定时任务 -- dependency groupIdorg.springframework.boot/groupId artifactIdspring-boot-starter-task/artifactId /dependency !-- AOP -- dependency groupIdorg.springframework.boot/groupId artifactIdspring-boot-starter-aop/artifactId /dependency !-- MySQL -- dependency groupIdmysql/groupId artifactIdmysql-connector-java/artifactId scoperuntime/scope /dependency !-- 工具 -- dependency groupIdorg.projectlombok/groupId artifactIdlombok/artifactId optionaltrue/optional /dependency dependency groupIdcom.alibaba/groupId artifactIdfastjson/artifactId version1.2.83/version /dependency /dependencies build plugins plugin groupIdorg.springframework.boot/groupId artifactIdspring-boot-maven-plugin/artifactId configuration excludes exclude groupIdorg.projectlombok/groupId artifactIdlombok/artifactId /exclude /excludes /configuration /plugin /plugins /build /project三、application.yml 完整配置yamlserver: port: 8080 spring: datasource: url: jdbc:mysql://localhost:3306/shop_full?useUnicodetruecharacterEncodingutf8serverTimezoneAsia/ShanghaiallowMultiQueriestrue username: root password: 123456 driver-class-name: com.mysql.cj.jdbc.Driver redis: host: localhost port: 6379 password: database: 0 rabbitmq: host: localhost port: 5672 username: guest password: guest virtual-host: / task: scheduling: core-size: 5 mybatis: mapper-locations: classpath:mapper/*.xml configuration: map-underscore-to-camel-case: true # 业务配置 order: expire-minute: 30 token-expire-minute: 5 # Redis Key前缀 redis: key: order-token: order:token: product-stock: product:stock: product-info: product:info:四、全局常量类OrderConstant.javajava运行public class OrderConstant { // 订单状态 public static final int ORDER_WAIT_PAY 0; public static final int ORDER_PAY_SUCCESS 1; public static final int ORDER_CANCEL 2; public static final int ORDER_REFUND 3; // 支付状态 public static final int PAY_WAIT 0; public static final int PAY_SUCCESS 1; public static final int PAY_FAIL 2; }MqConstant.javajava运行public class MqConstant { public static final String ORDER_NORMAL_QUEUE order.normal.queue; public static final String ORDER_NORMAL_EXCHANGE order.normal.exchange; public static final String ORDER_NORMAL_ROUTING_KEY order.normal.key; public static final String ORDER_DELAY_QUEUE order.delay.queue; public static final String ORDER_DELAY_EXCHANGE order.delay.exchange; public static final String ORDER_DELAY_ROUTING_KEY order.delay.key; }五、统一返回结果 Result.javajava运行import lombok.Data; Data public class Result { private Integer code; private String msg; private Object data; public static Result success(Object data, String msg) { Result result new Result(); result.setCode(200); result.setMsg(msg); result.setData(data); return result; } public static Result fail(String msg) { Result result new Result(); result.setCode(500); result.setMsg(msg); return result; } }六、全部实体类Product.javajava运行import lombok.Data; import java.math.BigDecimal; import java.time.LocalDateTime; Data public class Product { private Long id; private String productName; private BigDecimal price; private Integer stock; private Integer version; private LocalDateTime createTime; private LocalDateTime updateTime; }OrderInfo.javajava运行import lombok.Data; import java.math.BigDecimal; import java.time.LocalDateTime; Data public class OrderInfo { private Long id; private String orderNo; private Long userId; private BigDecimal totalAmount; private BigDecimal payAmount; private Integer orderStatus; private LocalDateTime payTime; private LocalDateTime cancelTime; private LocalDateTime expireTime; private LocalDateTime createTime; }OrderItem.javajava运行import lombok.Data; import java.math.BigDecimal; Data public class OrderItem { private Long id; private String orderNo; private Long productId; private String productName; private BigDecimal productPrice; private Integer quantity; }PayLog.javajava运行import lombok.Data; import java.math.BigDecimal; Data public class PayLog { private Long id; private String orderNo; private String payNo; private BigDecimal payAmount; private Integer payStatus; private Integer payType; }OperationLog.javajava运行import lombok.Data; import java.time.LocalDateTime; Data public class OperationLog { private Long id; private String content; private LocalDateTime createTime; }七、工具类 配置类RedisUtil.javajava运行import org.springframework.data.redis.core.RedisTemplate; import org.springframework.data.redis.core.script.DefaultRedisScript; import org.springframework.stereotype.Service; import javax.annotation.Resource; import java.util.Collections; import java.util.concurrent.TimeUnit; Service public class RedisUtil { Resource private RedisTemplateString, String stringRedisTemplate; public void set(String key, String value, long time, TimeUnit unit) { stringRedisTemplate.opsForValue().set(key, value, time, unit); } public String get(String key) { return stringRedisTemplate.opsForValue().get(key); } public Boolean delete(String key) { return stringRedisTemplate.delete(key); } public boolean checkAndDelete(String key) { String script if redis.call(get,KEYS[1]) then return redis.call(del,KEYS[1]) else return 0 end; DefaultRedisScriptLong redisScript new DefaultRedisScript(script, Long.class); Long result stringRedisTemplate.execute(redisScript, Collections.singletonList(key)); return result ! null result 0; } }RedissonConfig.javajava运行import org.redisson.Redisson; import org.redisson.api.RedissonClient; import org.redisson.config.Config; import org.springframework.boot.autoconfigure.data.redis.RedisProperties; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import javax.annotation.Resource; Configuration public class RedissonConfig { Resource private RedisProperties redisProperties; Bean public RedissonClient redissonClient() { Config config new Config(); String address redis:// redisProperties.getHost() : redisProperties.getPort(); config.useSingleServer() .setAddress(address) .setPassword(redisProperties.getPassword()) .setDatabase(redisProperties.getDatabase()); return Redisson.create(config); } }RabbitConfig.javajava运行import org.springframework.amqp.core.*; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import java.util.HashMap; import java.util.Map; Configuration public class RabbitConfig { Bean public Queue orderNormalQueue() { return new Queue(MqConstant.ORDER_NORMAL_QUEUE, true); } Bean public DirectExchange orderNormalExchange() { return new DirectExchange(MqConstant.ORDER_NORMAL_EXCHANGE); } Bean public Binding orderNormalBinding() { return BindingBuilder.bind(orderNormalQueue()) .to(orderNormalExchange()) .with(MqConstant.ORDER_NORMAL_ROUTING_KEY); } Bean public Queue orderDelayQueue() { MapString, Object args new HashMap(); args.put(x-dead-letter-exchange, MqConstant.ORDER_NORMAL_EXCHANGE); args.put(x-dead-letter-routing-key, MqConstant.ORDER_NORMAL_ROUTING_KEY); return new Queue(MqConstant.ORDER_DELAY_QUEUE, true, false, false, args); } Bean public DirectExchange orderDelayExchange() { return new DirectExchange(MqConstant.ORDER_DELAY_EXCHANGE); } Bean public Binding orderDelayBinding() { return BindingBuilder.bind(orderDelayQueue()) .to(orderDelayExchange()) .with(MqConstant.ORDER_DELAY_ROUTING_KEY); } }八、Mapper 接口ProductMapper.javajava运行import org.apache.ibatis.annotations.Mapper; import org.apache.ibatis.annotations.Param; Mapper public interface ProductMapper { Product selectById(Long id); int deductStock(Param(id) Long id, Param(num) Integer num, Param(version) Integer version); int addStock(Param(id) Long id, Param(num) Integer num); }OrderMapper.javajava运行import org.apache.ibatis.annotations.Mapper; import org.apache.ibatis.annotations.Param; import java.time.LocalDateTime; import java.util.List; Mapper public interface OrderMapper { void insert(OrderInfo orderInfo); OrderInfo selectByOrderNo(String orderNo); int updateOrderStatus(Param(orderNo) String orderNo, Param(status) Integer status, Param(oldStatus) Integer oldStatus, Param(payTime) LocalDateTime payTime); int updateOrderCancel(Param(orderNo) String orderNo, Param(cancelTime) LocalDateTime cancelTime); ListString listExpireWaitPayOrder(); }OrderItemMapper.javajava运行import org.apache.ibatis.annotations.Mapper; Mapper public interface OrderItemMapper { void insert(OrderItem orderItem); OrderItem selectByOrderNo(String orderNo); }PayLogMapper.javajava运行import org.apache.ibatis.annotations.Mapper; import org.apache.ibatis.annotations.Param; import java.math.BigDecimal; Mapper public interface PayLogMapper { void insert(PayLog payLog); void updatePaySuccess(Param(orderNo) String orderNo, Param(payNo) String payNo, Param(payType) Integer payType, Param(payAmount) BigDecimal payAmount); }OperationLogMapper.javajava运行import org.apache.ibatis.annotations.Mapper; Mapper public interface OperationLogMapper { void insert(OperationLog log); }九、Mapper XML 文件ProductMapper.xmlxml?xml version1.0 encodingUTF-8? !DOCTYPE mapper PUBLIC -//mybatis.org//DTD Mapper 3.0//EN http://mybatis.org/dtd/mybatis-3-mapper.dtd mapper namespacecom.ecommerce.mapper.ProductMapper select idselectById resultTypecom.ecommerce.entity.Product select * from product where id #{id} /select update iddeductStock UPDATE product SET stock stock - #{num}, version version 1 WHERE id #{id} AND version #{version} AND stock #{num} /update update idaddStock UPDATE product SET stock stock #{num} WHERE id #{id} /update /mapperOrderMapper.xmlxml?xml version1.0 encodingUTF-8? !DOCTYPE mapper PUBLIC -//mybatis.org//DTD Mapper 3.0//EN http://mybatis.org/dtd/mybatis-3-mapper.dtd mapper namespacecom.ecommerce.mapper.OrderMapper insert idinsert INSERT INTO order_info(order_no,user_id,total_amount,order_status,expire_time) VALUES(#{orderNo},#{userId},#{totalAmount},#{orderStatus},#{expireTime}) /insert select idselectByOrderNo resultTypecom.ecommerce.entity.OrderInfo select * from order_info where order_no #{orderNo} /select update idupdateOrderStatus UPDATE order_info SET order_status#{status},pay_time#{payTime} WHERE order_no#{orderNo} AND order_status#{oldStatus} /update update idupdateOrderCancel UPDATE order_info SET order_status2,cancel_time#{cancelTime} WHERE order_no#{orderNo} /update select idlistExpireWaitPayOrder resultTypejava.lang.String SELECT order_no FROM order_info WHERE order_status 0 AND expire_time NOW() LIMIT 100 /select /mapperOrderItemMapper.xmlxml?xml version1.0 encodingUTF-8? !DOCTYPE mapper PUBLIC -//mybatis.org//DTD Mapper 3.0//EN http://mybatis.org/dtd/mybatis-3-mapper.dtd mapper namespacecom.ecommerce.mapper.OrderItemMapper insert idinsert INSERT INTO order_item(order_no,product_id,product_name,product_price,quantity) VALUES(#{orderNo},#{productId},#{productName},#{productPrice},#{quantity}) /insert select idselectByOrderNo resultTypecom.ecommerce.entity.OrderItem select * from order_item where order_no #{orderNo} /select /mapperPayLogMapper.xmlxml?xml version1.0 encodingUTF-8? !DOCTYPE mapper PUBLIC -//mybatis.org//DTD Mapper 3.0//EN http://mybatis.org/dtd/mybatis-3-mapper.dtd mapper namespacecom.ecommerce.mapper.PayLogMapper insert idinsert INSERT INTO pay_log(order_no,pay_amount,pay_status) VALUES(#{orderNo},#{payAmount},#{payStatus}) /insert update idupdatePaySuccess UPDATE pay_log SET pay_no#{payNo},pay_type#{payType},pay_status1 WHERE order_no#{orderNo} /update /mapperOperationLogMapper.xmlxml?xml version1.0 encodingUTF-8? !DOCTYPE mapper PUBLIC -//mybatis.org//DTD Mapper 3.0//EN http://mybatis.org/dtd/mybatis-3-mapper.dtd mapper namespacecom.ecommerce.mapper.OperationLogMapper insert idinsert INSERT INTO operation_log(content) VALUES(#{content}) /insert /mapper十、Service 层全量代码OperationLogService.javajava运行import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Propagation; import org.springframework.transaction.annotation.Transactional; import javax.annotation.Resource; Service public class OperationLogService { Resource private OperationLogMapper operationLogMapper; Transactional(propagation Propagation.REQUIRES_NEW,rollbackFor Exception.class) public void recordLog(String content) { OperationLog log new OperationLog(); log.setContent(content); operationLogMapper.insert(log); } }OrderMqProducer.javajava运行import org.springframework.amqp.core.ReturnedMessage; import org.springframework.amqp.rabbit.core.RabbitTemplate; import org.springframework.stereotype.Service; import javax.annotation.Resource; Service public class OrderMqProducer { Resource private RabbitTemplate rabbitTemplate; public void sendNormalMsg(String orderNo) { rabbitTemplate.convertAndSend(MqConstant.ORDER_NORMAL_EXCHANGE, MqConstant.ORDER_NORMAL_ROUTING_KEY,orderNo); } public void sendDelayOrderMsg(String orderNo, long delayMinute) { long delay delayMinute * 60 * 1000; rabbitTemplate.convertAndSend(MqConstant.ORDER_DELAY_EXCHANGE, MqConstant.ORDER_DELAY_ROUTING_KEY, orderNo, msg - { msg.getMessageProperties().setExpiration(String.valueOf(delay)); return msg; }); } }OrderMqConsumer.javajava运行import lombok.extern.slf4j.Slf4j; import org.springframework.amqp.rabbit.annotation.RabbitListener; import org.springframework.stereotype.Service; import javax.annotation.Resource; Slf4j Service public class OrderMqConsumer { Resource private OrderService orderService; RabbitListener(queues MqConstant.ORDER_NORMAL_QUEUE) public void consume(String orderNo){ try { orderService.autoCloseOrder(orderNo); }catch (Exception e){ log.error(MQ关闭订单异常{},e.getMessage()); } } }OrderService.java【核心】java运行import com.alibaba.fastjson.JSON; import lombok.extern.slf4j.Slf4j; import org.redisson.api.RLock; import org.redisson.api.RedissonClient; import org.springframework.beans.factory.annotation.Value; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Isolation; import org.springframework.transaction.annotation.Transactional; import org.springframework.util.StringUtils; import javax.annotation.Resource; import java.math.BigDecimal; import java.time.LocalDateTime; import java.util.UUID; import java.util.concurrent.TimeUnit; Slf4j Service public class OrderService { Resource private ProductMapper productMapper; Resource private OrderMapper orderMapper; Resource private OrderItemMapper orderItemMapper; Resource private PayLogMapper payLogMapper; Resource private OperationLogService operationLogService; Resource private RedisUtil redisUtil; Resource private RedissonClient redissonClient; Resource private OrderMqProducer orderMqProducer; Value(${order.expire-minute}) private Integer expireMinute; Value(${redis.key.order-token}) private String orderTokenPrefix; Value(${redis.key.product-info}) private String productInfoPrefix; Value(${redis.key.product-stock}) private String productStockPrefix; public String createOrder(Long userId, Long productId, Integer quantity, String orderToken) { // 1. 防重令牌校验 String tokenKey orderTokenPrefix userId : orderToken; if(!redisUtil.checkAndDelete(tokenKey)){ throw new RuntimeException(请勿重复提交); } // 2. 商品分布式锁 String lockKey lock:product: productId; RLock lock redissonClient.getLock(lockKey); boolean lockOk false; try { lockOk lock.tryLock(0,30, TimeUnit.SECONDS); if(!lockOk){ throw new RuntimeException(下单拥挤请稍后重试); } // 3. Redis缓存查询 String infoKey productInfoPrefix productId; String stockKey productStockPrefix productId; Product product; String stockStr redisUtil.get(stockKey); if(StringUtils.hasText(stockStr)){ product JSON.parseObject(redisUtil.get(infoKey),Product.class); if(Integer.parseInt(stockStr) quantity){ throw new RuntimeException(库存不足); } }else{ product productMapper.selectById(productId); if(product null){ throw new RuntimeException(商品不存在); } redisUtil.set(infoKey,JSON.toJSONString(product),1,TimeUnit.HOURS); redisUtil.set(stockKey,product.getStock().toString(),1,TimeUnit.HOURS); } if(product.getStock() quantity){ throw new RuntimeException(库存不足); } // 4. 短事务落库 return doCreateOrder(productId,quantity,userId,product); }catch (Exception e){ throw new RuntimeException(e.getMessage()); }finally { if(lockOk lock.isHeldByCurrentThread()){ lock.unlock(); } } } Transactional(rollbackFor Exception.class,isolation Isolation.READ_COMMITTED) public String doCreateOrder(Long productId, Integer quantity, Long userId, Product product){ // 乐观锁扣库存 int rows productMapper.deductStock(productId,quantity,product.getVersion()); if(rows 0){ throw new RuntimeException(并发冲突下单失败); } // 缓存库存扣减 redisUtil.getOperations().increment(productStockPrefixproductId,-quantity); // 生成订单 String orderNo ORD System.currentTimeMillis() UUID.randomUUID().toString().substring(0,6); LocalDateTime expireTime LocalDateTime.now().plusMinutes(expireMinute); OrderInfo order new OrderInfo(); order.setOrderNo(orderNo); order.setUserId(userId); order.setTotalAmount(product.getPrice().multiply(new BigDecimal(quantity))); order.setOrderStatus(OrderConstant.ORDER_WAIT_PAY); order.setExpireTime(expireTime); orderMapper.insert(order); OrderItem item new OrderItem(); item.setOrderNo(orderNo); item.setProductId(productId); item.setProductName(product.getProductName()); item.setProductPrice(product.getPrice()); item.setQuantity(quantity); orderItemMapper.insert(item); PayLog payLog new PayLog(); payLog.setOrderNo(orderNo); payLog.setPayAmount(order.getTotalAmount()); payLog.setPayStatus(OrderConstant.PAY_WAIT); payLogMapper.insert(payLog); // 发送延时关单MQ orderMqProducer.sendDelayOrderMsg(orderNo,expireMinute); orderMqProducer.sendNormalMsg(orderNo); operationLogService.recordLog(用户userId创建订单:orderNo); return orderNo; } Transactional(rollbackFor Exception.class) public void payOrder(String orderNo,String payNo,Integer payType){ OrderInfo order orderMapper.selectByOrderNo(orderNo); if(order null || !order.getOrderStatus().equals(OrderConstant.ORDER_WAIT_PAY)){ throw new RuntimeException(订单状态异常); } orderMapper.updateOrderStatus(orderNo,OrderConstant.ORDER_PAY_SUCCESS, OrderConstant.ORDER_WAIT_PAY,LocalDateTime.now()); payLogMapper.updatePaySuccess(orderNo,payNo,payType,order.getTotalAmount()); operationLogService.recordLog(订单orderNo支付成功); } Transactional(rollbackFor Exception.class) public void cancelOrder(String orderNo){ OrderInfo order orderMapper.selectByOrderNo(orderNo); if(order null || !order.getOrderStatus().equals(OrderConstant.ORDER_WAIT_PAY)){ throw new RuntimeException(无法取消); } OrderItem item orderItemMapper.selectByOrderNo(orderNo); productMapper.addStock(item.getProductId(),item.getQuantity()); orderMapper.updateOrderCancel(orderNo,LocalDateTime.now()); operationLogService.recordLog(订单orderNo手动取消); } Transactional(rollbackFor Exception.class) public void autoCloseOrder(String orderNo){ OrderInfo order orderMapper.selectByOrderNo(orderNo); if(order null || !order.getOrderStatus().equals(OrderConstant.ORDER_WAIT_PAY)){ return; } OrderItem item orderItemMapper.selectByOrderNo(orderNo); productMapper.addStock(item.getProductId(),item.getQuantity()); orderMapper.updateOrderCancel(orderNo,LocalDateTime.now()); operationLogService.recordLog(订单orderNo超时自动关闭); } }十一、Controller 全量接口OrderTokenController.javajava运行import org.springframework.beans.factory.annotation.Value; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RequestParam; import org.springframework.web.bind.annotation.RestController; import javax.annotation.Resource; import java.util.UUID; import java.util.concurrent.TimeUnit; RestController RequestMapping(/api/token) public class OrderTokenController { Resource private RedisUtil redisUtil; Value(${redis.key.order-token}) private String orderTokenPrefix; Value(${order.token-expire-minute}) private Integer tokenExpireMinute; GetMapping(/get) public Result getToken(RequestParam Long userId){ String token UUID.randomUUID().toString().replace(-,); String key orderTokenPrefix userId : token; redisUtil.set(key,1,tokenExpireMinute, TimeUnit.MINUTES); return Result.success(token,令牌获取成功); } }OrderController.javajava运行import org.springframework.web.bind.annotation.PostMapping; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RequestParam; import org.springframework.web.bind.annotation.RestController; import javax.annotation.Resource; RestController RequestMapping(/api/order) public class OrderController { Resource private OrderService orderService; PostMapping(/create) public Result create(RequestParam Long userId, RequestParam Long productId, RequestParam Integer quantity, RequestParam String orderToken){ try { String orderNo orderService.createOrder(userId,productId,quantity,orderToken); return Result.success(orderNo,下单成功); }catch (Exception e){ return Result.fail(e.getMessage()); } } PostMapping(/pay) public Result pay(RequestParam String orderNo, RequestParam String payNo, RequestParam Integer payType){ try { orderService.payOrder(orderNo,payNo,payType); return Result.success(null,支付成功); }catch (Exception e){ return Result.fail(e.getMessage()); } } PostMapping(/cancel) public Result cancel(RequestParam String orderNo){ try { orderService.cancelOrder(orderNo); return Result.success(null,取消成功); }catch (Exception e){ return Result.fail(e.getMessage()); } } }十二、启动类java运行import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; import org.springframework.scheduling.annotation.EnableScheduling; SpringBootApplication EnableScheduling public class OrderApplication { public static void main(String[] args) { SpringApplication.run(OrderApplication.class,args); } }十三、测试流程启动 MySQL、Redis、RabbitMQ执行上方 SQL 建表 初始化商品启动项目接口调用顺序plaintext1. 获取下单令牌GET /api/token/get?userId1 2. 创建订单POST /api/order/create?userId1productId1quantity1orderTokenxxx 3. 模拟支付POST /api/order/pay?orderNoxxxpayNoPAY666payType2 4. 手动取消POST /api/order/cancel?orderNoxxx十四、核心面试总结可直接背防重RedisLua 令牌防重复提交防超卖Redisson 商品级分布式锁 MySQL 乐观锁高性能Redis 热点缓存、短事务、异步 MQ 解耦流量削峰RabbitMQ 延时队列替代定时任务轮库数据一致下单扣库存、取消 / 超时自动回补库存事务设计核心 DB 操作为短事务日志独立事务不回滚