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
以下内容源自《【秒杀项目】》
仅供学习交流使用
版权
禁止其他平台发布时删除以下此话
本文首次发布于CSDN平台
作者是CSDN@日星月云
博客主页是https://blog.csdn.net/qq_51625007
禁止其他平台发布时删除以上此话
推荐
无
V2~V3的变化
总结流程
OrderController//获取验证码order/captcha//验证码正确后//获取秒杀凭证//底层调用promotionService.generateToken()order/token//下单操作//限制单击流量//获取活动凭证//加入队列等待//调用底层的orderService.createOrderAsync()order/createOrderService
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, 1decreaseStockInCache()//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);@Autowiredprivate OrderService orderService;@Autowiredprivate PromotionService promotionService;@Autowiredprivate RedisTemplate redisTemplate;@Autowiredprivate 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)@ResponseBodypublic 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)@ResponseBodypublic 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() {@Overridepublic 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 {@Autowiredprivate RedisTemplate redisTemplate;@Autowiredprivate UserService userService;@Autowiredprivate ItemService itemService;@Overridepublic 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);@Autowiredprivate OrderMapper orderMapper;@Autowiredprivate SerialNumberMapper serialNumberMapper;@Autowiredprivate UserService userService;@Autowiredprivate ItemService itemService;@Autowiredprivate RedisTemplate redisTemplate;@Autowiredprivate 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()@Transactionalpublic 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() {@Overridepublic void onSuccess(SendResult sendResult) {logger.debug("投递增加商品销量消息成功");}@Overridepublic void onException(Throwable e) {logger.error("投递增加商品销量消息失败", e);}}, 60 * 1000);// 更新库存流水状态itemService.updateItemStockLogStatus(itemStockLogId, 1);logger.debug("更新流水完成 [" + itemStockLogId + "]");return order;}//异步创建订单//执行本地事务 @Overridepublic 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);//初始状态是0logger.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);@Autowiredprivate ItemService itemService;@Overridepublic 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);@Autowiredprivate OrderService orderService;@Autowiredprivate ItemService itemService;//执行本地事务@Overridepublic 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;}}//检查本地事务@Overridepublic 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;}}}
最后
我们都有光明的未来
祝大家考研上岸
祝大家工作顺利
祝大家得偿所愿
祝大家如愿以偿
点赞收藏关注哦