V2-V3的变化【秒杀项目】
- 前言
- 版权
- 推荐
- V2~V3的变化
- 总结流程
- 配置
- pom.xml
- application-dev.properties
- comment
- ErrorCode
- configuration
- WebMvcConfiguration
- emtity
- ItemStockLog
- mapper
- ItemStockLogMapper
- ItemStockLogMapper.xml
- controller
- OrderController
- service
- ItemService
- PromotionService
- OrderService
- rocket
- DecreaseStockConsumer
- LocalTransactionListenerImpl
- 最后
前言
2023-8-17 13:03:47
推荐
无
V2~V3的变化
总结流程
OrderController
//获取验证码
order/captcha
//验证码正确后
//获取秒杀凭证
//底层调用promotionService.generateToken()
order/token
//下单操作
//限制单击流量
//获取活动凭证
//加入队列等待
//调用底层的orderService.createOrderAsync()
order/create
OrderService
createOrderAsync()
//异步创建订单
//执行本地事务
rocket
//执行本地事务
executeLocalTransaction()
//创建订单
//调用orderService.createOrder()
createOrder()
//检查本地事务
checkLocalTransaction
//检查流水
//调用itemService.findItemStorkLogById()
checkStockStatus()
//执行Mysql中的扣减库存
//调用itemService.decreaseStock()
onMessage()
PromotionService
//判断售罄标识
//获取秒杀令牌
//v.decrement("promotion:gate:" + promotionId, 1) < 0
//设置秒杀凭证
//v.set(key, token, 10, TimeUnit.MINUTES);
generateToken()
OrderService
//订单的创建
//异步扣减库存
//调用itemService.decreaseStockInCache()
//生成订单
//更新销量
createOrder()
itemService
/*
//预扣库存
//redisTemplate.opsForValue().decrement(key, amount);
//售罄标识
//redisTemplate.opsForValue().set("item:stock:over:" + itemId, 1);
decreaseStockInCache()
*/
//使用Lua脚本
//得到stock
//判断stock>amount
//DECRBY stockKey, amount
//SET stockOverKey, 1
decreaseStockInCache()
//itemStockLogMapper.selectByPrimaryKey(id);
findItemStorkLogById()
//itemStockMapper.decreaseStock(itemId, amount);
decreaseStock()
配置
pom.xml
<dependency>
<groupId>org.apache.rocketmq</groupId>
<artifactId>rocketmq-spring-boot-starter</artifactId>
<version>2.2.0</version>
</dependency>
<dependency>
<groupId>com.github.whvcse</groupId>
<artifactId>easy-captcha</artifactId>
<version>1.6.2</version>
</dependency>
application-dev.properties
# rocketmq
rocketmq.name-server=192.168.253.160:9876
rocketmq.producer.group=seckill_producer
# ThreadPool
spring.task.execution.pool.core-size=5
spring.task.execution.pool.queue-capacity=10
spring.task.execution.pool.max-size=30
comment
ErrorCode
int CREATE_ORDER_FAILURE = 202;
int OUT_OF_LIMIT = 203;
configuration
WebMvcConfiguration
// registry.addInterceptor(loginCheckInterceptor).addPathPatterns("/order/create");
registry.addInterceptor(loginCheckInterceptor).addPathPatterns("/order/captcha","/order/token", "/order/create");
emtity
ItemStockLog
mapper
ItemStockLogMapper
ItemStockLogMapper.xml
controller
OrderController
package com.nowcoder.seckill.controller;
@Controller
@RequestMapping("/order")
@CrossOrigin(origins = "${nowcoder.web.path}", allowedHeaders = "*", allowCredentials = "true")
public class OrderController implements ErrorCode {
private Logger logger = LoggerFactory.getLogger(OrderController.class);
private RateLimiter rateLimiter = RateLimiter.create(1000);
@Autowired
private OrderService orderService;
@Autowired
private PromotionService promotionService;
@Autowired
private RedisTemplate redisTemplate;
@Autowired
private ThreadPoolTaskExecutor taskExecutor;
//获取验证码
@RequestMapping(path = "/captcha", method = RequestMethod.GET)
public void getCaptcha(String token, HttpServletResponse response) {
SpecCaptcha specCaptcha = new SpecCaptcha(130, 48, 4);
if (token != null) {
User user = (User) redisTemplate.opsForValue().get(token);
if (user != null) {
String key = "captcha:" + user.getId();
redisTemplate.opsForValue().set(key, specCaptcha.text(), 1, TimeUnit.MINUTES);
}
}
response.setContentType("image/png");
try {
OutputStream os = response.getOutputStream();
specCaptcha.out(os);
} catch (IOException e) {
logger.error("发送验证码失败:" + e.getMessage());
}
}
//获取秒杀凭证
//底层调用promotionService.generateToken()
@RequestMapping(path = "/token", method = RequestMethod.POST)
@ResponseBody
public ResponseModel generateToken(int itemId, int promotionId, String token, String captcha) {
User user = (User) redisTemplate.opsForValue().get(token);
if (StringUtils.isEmpty(captcha)) {
throw new BusinessException(PARAMETER_ERROR, "请输入正确的验证码!");
}
String key = "captcha:" + user.getId();
String realCaptcha = (String) redisTemplate.opsForValue().get(key);
if (!captcha.equalsIgnoreCase(realCaptcha)) {
throw new BusinessException(PARAMETER_ERROR, "请输入正确的验证码!");
}
String promotionToken = promotionService.generateToken(user.getId(), itemId, promotionId);//底层存入promotionToken到redis中
if (StringUtils.isEmpty(promotionToken)) {
throw new BusinessException(CREATE_ORDER_FAILURE, "下单失败!");
}
return new ResponseModel(promotionToken);
}
// @RequestMapping(path = "/create", method = RequestMethod.POST)
// @ResponseBody
// public ResponseModel create(/*HttpSession session, */
// int itemId, int amount, Integer promotionId, String token) {
User user = (User) session.getAttribute("loginUser");
// User user = (User) redisTemplate.opsForValue().get(token);
// orderService.createOrder(user.getId(), itemId, amount, promotionId);
// return new ResponseModel();
// }
//下单操作
//调用底层的orderService.createOrderAsync()
@RequestMapping(path = "/create", method = RequestMethod.POST)
@ResponseBody
public ResponseModel create(/*HttpSession session, */
int itemId, int amount, Integer promotionId, String promotionToken, String token) {
//限制单机流量
if (!rateLimiter.tryAcquire(1,TimeUnit.SECONDS)) {
throw new BusinessException(OUT_OF_LIMIT, "服务器繁忙,请稍后再试!");
}
// User user = (User) session.getAttribute("loginUser");
User user = (User) redisTemplate.opsForValue().get(token);
logger.debug("登录用户 [" + token + ": " + user + "]");
//验证活动凭证
if (promotionId != null) {
String key = "promotion:token:" + user.getId() + ":" + itemId + ":" + promotionId;
String realPromotionToken = (String) redisTemplate.opsForValue().get(key);
if (StringUtils.isEmpty(promotionToken) || !promotionToken.equals(realPromotionToken)) {
throw new BusinessException(CREATE_ORDER_FAILURE, "下单失败!");
}
}
//加入队列等待
Future future = taskExecutor.submit(new Callable() {
@Override
public Object call() throws Exception {
// orderService.createOrder(user.getId(), itemId, amount, promotionId);
orderService.createOrderAsync(user.getId(), itemId, amount, promotionId);
return null;
}
});
//验证处理结果
try {
future.get();
} catch (Exception e) {
throw new BusinessException(UNDEFINED_ERROR, "下单失败!");
}
return new ResponseModel();
}
}
service
ItemService
ItemStockLog createItemStockLog(int itemId, int amount);
void updateItemStockLogStatus(String id, int status);
ItemStockLog findItemStorkLogById(String id);
PromotionService
package com.nowcoder.seckill.service.impl;
@Service
public class PromotionServiceImpl implements PromotionService, ErrorCode {
@Autowired
private RedisTemplate redisTemplate;
@Autowired
private UserService userService;
@Autowired
private ItemService itemService;
@Override
public String generateToken(int userId, int itemId, int promotionId) {
if (userId < 0 || itemId < 0 || promotionId < 0) {
return null;
}
// 售罄标识
if (redisTemplate.hasKey("item:stock:over:" + itemId)) {
return null;
}
// 校验用户
User user = userService.findUserFromCache(userId);
if (user == null) {
return null;
}
// 校验商品
Item item = itemService.findItemInCache(itemId);
if (item == null) {
return null;
}
// 校验活动
if (item.getPromotion() == null
|| !item.getPromotion().getId().equals(promotionId)
|| item.getPromotion().getStatus() != 0) {
return null;
}
// 秒杀大闸
ValueOperations v = redisTemplate.opsForValue();
if (v.decrement("promotion:gate:" + promotionId, 1) < 0) {
return null;
}
String key = "promotion:token:" + userId + ":" + itemId + ":" + promotionId;
String token = UUID.randomUUID().toString().replace("-", "");
v.set(key, token, 10, TimeUnit.MINUTES);
return token;
}
}
OrderService
package com.nowcoder.seckill.service.impl;
@Service
public class OrderServiceImpl implements OrderService, ErrorCode {
private Logger logger = LoggerFactory.getLogger(OrderServiceImpl.class);
@Autowired
private OrderMapper orderMapper;
@Autowired
private SerialNumberMapper serialNumberMapper;
@Autowired
private UserService userService;
@Autowired
private ItemService itemService;
@Autowired
private RedisTemplate redisTemplate;
@Autowired
private RocketMQTemplate rocketMQTemplate;
/**
* 格式:日期 + 流水
* 示例:20210123000000000001
*
* @return
*/
//订单号的创建
@Transactional(propagation = Propagation.REQUIRES_NEW)
public String generateOrderID() {
StringBuilder sb = new StringBuilder();
// 拼入日期
sb.append(Toolbox.format(new Date(), "yyyyMMdd"));
// 获取流水号
SerialNumber serial = serialNumberMapper.selectByPrimaryKey("order_serial");
Integer value = serial.getValue();
// 更新流水号
serial.setValue(value + serial.getStep());
serialNumberMapper.updateByPrimaryKey(serial);
// 拼入流水号
String prefix = "000000000000".substring(value.toString().length());
sb.append(prefix).append(value);
return sb.toString();
}
//订单的创建
//调用itemService.decreaseStockInCache()
@Transactional
public Order createOrder(int userId, int itemId, int amount, Integer promotionId, String itemStockLogId) {
// 校验参数
if (amount < 1 || (promotionId != null && promotionId.intValue() <= 0)) {
throw new BusinessException(PARAMETER_ERROR, "指定的参数不合法!");
}
// 校验用户
// User user = userService.findUserById(userId);
User user = userService.findUserFromCache(userId);
if (user == null) {
throw new BusinessException(PARAMETER_ERROR, "指定的用户不存在!");
}
// 校验商品
// Item item = itemService.findItemById(itemId);
Item item = itemService.findItemInCache(itemId);
if (item == null) {
throw new BusinessException(PARAMETER_ERROR, "指定的商品不存在!");
}
// 校验库存
int stock = item.getItemStock().getStock();
if (amount > stock) {
throw new BusinessException(STOCK_NOT_ENOUGH, "库存不足!");
}
// 校验活动
if (promotionId != null) {
if (item.getPromotion() == null) {
throw new BusinessException(PARAMETER_ERROR, "指定的商品无活动!");
} else if (!item.getPromotion().getId().equals(promotionId)) {
throw new BusinessException(PARAMETER_ERROR, "指定的活动不存在!");
} else if (item.getPromotion().getStatus() != 0) {
throw new BusinessException(PARAMETER_ERROR, "指定的活动未开始!");
}
}
// 扣减库存
// 增加行锁:前提是商品主键字段上必须有索引。
// 缓存库存:异步同步数据库并保证最终一致性。
// boolean successful = itemService.decreaseStock(itemId, amount);
boolean successful = itemService.decreaseStockInCache(itemId, amount);//在redis缓存中扣减
logger.debug("预扣减库存完成 [" + successful + "]");
if (!successful) {
throw new BusinessException(STOCK_NOT_ENOUGH, "库存不足!");
}
// 生成订单
Order order = new Order();
order.setId(this.generateOrderID());
order.setUserId(userId);
order.setItemId(itemId);
order.setPromotionId(promotionId);
order.setOrderPrice(promotionId != null ? item.getPromotion().getPromotionPrice() : item.getPrice());
order.setOrderAmount(amount);
order.setOrderTotal(order.getOrderPrice().multiply(new BigDecimal(amount)));
order.setOrderTime(new Timestamp(System.currentTimeMillis()));
orderMapper.insert(order);
logger.debug("生成订单完成 [" + order.getId() + "]");
// 更新销量
// itemService.increaseSales(itemId, amount);
// logger.debug("更新销量完成 [" + itemId + "]");
JSONObject body = new JSONObject();
body.put("itemId", itemId);
body.put("amount", amount);
Message msg = MessageBuilder.withPayload(body.toString()).build();
rocketMQTemplate.asyncSend("seckill:increase_sales", msg, new SendCallback() {
@Override
public void onSuccess(SendResult sendResult) {
logger.debug("投递增加商品销量消息成功");
}
@Override
public void onException(Throwable e) {
logger.error("投递增加商品销量消息失败", e);
}
}, 60 * 1000);
// 更新库存流水状态
itemService.updateItemStockLogStatus(itemStockLogId, 1);
logger.debug("更新流水完成 [" + itemStockLogId + "]");
return order;
}
//异步创建订单
//执行本地事务
@Override
public void createOrderAsync(int userId, int itemId, int amount, Integer promotionId) {
// 售罄标识
if (redisTemplate.hasKey("item:stock:over:" + itemId)) {
throw new BusinessException(STOCK_NOT_ENOUGH, "已经售罄!");
}
// 生成库存流水
ItemStockLog itemStockLog = itemService.createItemStockLog(itemId, amount);//初始状态是0
logger.debug("生成库存流水完成 [" + itemStockLog.getId() + "]");
// 消息体
JSONObject body = new JSONObject();
body.put("itemId", itemId);
body.put("amount", amount);
body.put("itemStockLogId", itemStockLog.getId());
// 本地事务参数
JSONObject arg = new JSONObject();
arg.put("userId", userId);
arg.put("itemId", itemId);
arg.put("amount", amount);
arg.put("promotionId", promotionId);
arg.put("itemStockLogId", itemStockLog.getId());
String dest = "seckill:decrease_stock";
Message msg = MessageBuilder.withPayload(body.toString()).build();
try {
logger.debug("尝试投递扣减库存消息 [" + body.toString() + "]");
TransactionSendResult sendResult = rocketMQTemplate.sendMessageInTransaction(dest, msg, arg);
if (sendResult.getLocalTransactionState() == LocalTransactionState.UNKNOW) {
throw new BusinessException(UNDEFINED_ERROR, "创建订单失败!");
} else if (sendResult.getLocalTransactionState() == LocalTransactionState.ROLLBACK_MESSAGE) {
throw new BusinessException(CREATE_ORDER_FAILURE, "创建订单失败!");
}
} catch (MessagingException e) {
throw new BusinessException(CREATE_ORDER_FAILURE, "创建订单失败!");
}
}
}
rocket
DecreaseStockConsumer
package com.nowcoder.seckill.rocket.consumer;
@Service
@RocketMQMessageListener(topic = "seckill",
consumerGroup = "seckill_stock", selectorExpression = "decrease_stock")
public class DecreaseStockConsumer implements RocketMQListener<String> {
private Logger logger = LoggerFactory.getLogger(DecreaseStockConsumer.class);
@Autowired
private ItemService itemService;
@Override
public void onMessage(String message) {
JSONObject param = JSONObject.parseObject(message);
int itemId = (int) param.get("itemId");
int amount = (int) param.get("amount");
try {
itemService.decreaseStock(itemId, amount);
logger.debug("最终扣减库存完成 [" + param.get("itemStockLogId") + "]");
} catch (Exception e) {
logger.error("从DB扣减库存失败", e);
}
}
}
LocalTransactionListenerImpl
package com.nowcoder.seckill.rocket.producer;
@Service
@RocketMQTransactionListener
public class LocalTransactionListenerImpl implements RocketMQLocalTransactionListener {
private Logger logger = LoggerFactory.getLogger(LocalTransactionListenerImpl.class);
@Autowired
private OrderService orderService;
@Autowired
private ItemService itemService;
//执行本地事务
@Override
public RocketMQLocalTransactionState executeLocalTransaction(Message msg, Object arg) {
try {
String tag = msg.getHeaders().get("rocketmq_TAGS").toString();
if ("decrease_stock".equals(tag)) {
return this.createOrder(msg, arg);//调用方法
} else {
return RocketMQLocalTransactionState.UNKNOWN;
}
} catch (Exception e) {
logger.error("执行MQ本地事务时发生错误", e);
return RocketMQLocalTransactionState.ROLLBACK;
}
}
//检查本地事务
@Override
public RocketMQLocalTransactionState checkLocalTransaction(Message msg) {
try {
String tag = (String) msg.getHeaders().get("rocketmq_TAGS");
if ("decrease_stock".equals(tag)) {
return this.checkStockStatus(msg);//调用方法
} else {
return RocketMQLocalTransactionState.UNKNOWN;
}
} catch (Exception e) {
logger.error("检查MQ本地事务时发生错误", e);
return RocketMQLocalTransactionState.ROLLBACK;
}
}
//创建订单
private RocketMQLocalTransactionState createOrder(Message msg, Object arg) {
JSONObject param = (JSONObject) arg;
int userId = (int) param.get("userId");
int itemId = (int) param.get("itemId");
int amount = (int) param.get("amount");
int promotionId = (int) param.get("promotionId");
String itemStockLogId = (String) param.get("itemStockLogId");
try {
Order order = orderService.createOrder(userId, itemId, amount, promotionId, itemStockLogId);//调用service创建
logger.debug("本地事务提交完成 [" + order.getId() + "]");
return RocketMQLocalTransactionState.COMMIT;
} catch (Exception e) {
logger.error("创建订单失败", e);
itemService.updateItemStockLogStatus(itemStockLogId, 3);
logger.debug("更新流水完成 [" + itemStockLogId + "]");
return RocketMQLocalTransactionState.ROLLBACK;
}
}
//检查流水
private RocketMQLocalTransactionState checkStockStatus(Message msg) {
JSONObject body = JSONObject.parseObject(new String((byte[]) msg.getPayload()));
String itemStockLogId = (String) body.get("itemStockLogId");
ItemStockLog itemStockLog = itemService.findItemStorkLogById(itemStockLogId);
logger.debug("检查事务状态完成 [" + itemStockLog + "]");
if (itemStockLog == null) {
return RocketMQLocalTransactionState.ROLLBACK;
} else if (itemStockLog.getStatus() == 0) {
return RocketMQLocalTransactionState.UNKNOWN;
} else if (itemStockLog.getStatus() == 1) {
return RocketMQLocalTransactionState.COMMIT;
} else {
return RocketMQLocalTransactionState.ROLLBACK;
}
}
}
最后
我们都有光明的未来
祝大家考研上岸
祝大家工作顺利
祝大家得偿所愿
祝大家如愿以偿
点赞收藏关注哦