# springboot+rabbitMQ+redis秒杀系统 **Repository Path**: markYao98/Spike_system ## Basic Information - **Project Name**: springboot+rabbitMQ+redis秒杀系统 - **Description**: 基于springboot+rabbitMQ+redis的秒杀系统 - **Primary Language**: Unknown - **License**: Not specified - **Default Branch**: master - **Homepage**: None - **GVP Project**: No ## Statistics - **Stars**: 2 - **Forks**: 0 - **Created**: 2022-07-22 - **Last Updated**: 2023-10-22 ## Categories & Tags **Categories**: Uncategorized **Tags**: None ## README ## 写在前面 最近刚刚学习完rabbitMQ的概念以及使用,于是想找点项目练练手。找了许多发现要么缺失数据库Sql文件要么就是代码水土不服。想到自己之前刚学springboot时写过一个电商项目(简易版),于是想着将其改造改造,应该可以增加一个秒杀的业务。 老项目的问题挺多的,之前登录用的是session,这次我给改成token了,但是这么一改之前的很多按钮点击事件由于没有发送token,所以出现了点击不生效的现象。= = 又由于当时这个项目的前端部分,请求js都是分开写的,每个页面写各自的请求,冗余极大,一大堆请求我都分不清了。真要改我得干很久,所以我只是将秒杀的功能那一部分修改修改了。为求它能够跑通秒杀的接口。 详细可查看csdn:https://blog.csdn.net/Onthr/article/details/125938825 ## 可以使用的功能 首先浏览器访问localhost:80 ![1658478554120](C:\Users\81471\AppData\Roaming\Typora\typora-user-images\1658478554120.png) 第一步先登录 ![1658478570479](C:\Users\81471\AppData\Roaming\Typora\typora-user-images\1658478570479.png) 登录成功后会跳转到首页 ![1658478669121](C:\Users\81471\AppData\Roaming\Typora\typora-user-images\1658478669121.png) 点击秒杀(只能在这一页面点击,其它页面没有绑定点击事件) ![1658478704607](C:\Users\81471\AppData\Roaming\Typora\typora-user-images\1658478704607.png) 这些商品都是可以秒杀的,其实我在后台就是查询了'笔记本'这一类的商品 ![1658478792346](C:\Users\81471\AppData\Roaming\Typora\typora-user-images\1658478792346.png) 点击秒杀即可。 ## 模拟秒杀 以id为15的这款商品测试秒杀 ### 1.查询mysql ![1658476504824](C:\Users\81471\AppData\Roaming\Typora\typora-user-images\1658476504824.png) ### 2.查询redis ![1658476568102](C:\Users\81471\AppData\Roaming\Typora\typora-user-images\1658476568102.png) ### 3.前端页面情况 ![1658476660067](C:\Users\81471\AppData\Roaming\Typora\typora-user-images\1658476660067.png) ### 4.开始模拟秒杀 开启800条用户线程,将模拟秒杀的商品类id写好 ``` static final int goodid=10000015; //将模拟类的商品id改为15 static int count=800;//模拟用户数(线程数) ``` 结束之后查看mysql,redis,mq(不到3秒钟) mysql已经为0 ![1658476879020](C:\Users\81471\AppData\Roaming\Typora\typora-user-images\1658476879020.png) redis出现了-2 ![1658476901637](C:\Users\81471\AppData\Roaming\Typora\typora-user-images\1658476901637.png) mq的折线图 ![1658476868125](C:\Users\81471\AppData\Roaming\Typora\typora-user-images\1658476868125.png) ![1658476767185](C:\Users\81471\AppData\Roaming\Typora\typora-user-images\1658476767185.png) 后台日志: 第一步获取path没有打印出日志 ![1658477063082](C:\Users\81471\AppData\Roaming\Typora\typora-user-images\1658477063082.png) 第二步日志最多 ![1658477089129](C:\Users\81471\AppData\Roaming\Typora\typora-user-images\1658477089129.png) 第三步日志 ![1658477103430](C:\Users\81471\AppData\Roaming\Typora\typora-user-images\1658477103430.png) #### 针对redis的情况查看数据库是否有超卖的现象 查看秒杀订单表 ![1658477222995](C:\Users\81471\AppData\Roaming\Typora\typora-user-images\1658477222995.png) 没有超卖. 后面我又测试了多款商品的秒杀,发现redis都会出现误判的情况,但是mysql那边没有问题 ![1658477555176](C:\Users\81471\AppData\Roaming\Typora\typora-user-images\1658477555176.png) ![1658477563856](C:\Users\81471\AppData\Roaming\Typora\typora-user-images\1658477563856.png) ## 秒杀流程 ![1](C:\Users\81471\Desktop\1.png) ![2](C:\Users\81471\Desktop\2.png) ### 1.点击秒杀开始,获取秒杀路径path 1.1检查是否登录 1.1.1先获取登录token,再发送token通过redis获取到登录信息user 1.2获取秒杀路径,创建秒杀路径 1.2.1 通过md5创建秒杀路径,再将秒杀路径存入redis ``` 存入redis: prefix: SeckillKey.getSeckillPath --> new SeckillKey("mp"); key: ""+user.getId() + "_"+ goodsId value: md5生成的path -->MD5Util.md5(UUID.randomUUID()+"123456"); exTime: Const.RedisCacheExtime.GOODS_ID --> int GOODS_ID = 60;//1分钟 最终的key: new SeckillKey("mp")+""+user.getId() + "_"+ goodsId 最终的value: md5生成的path ``` ### 2.消息队列操作 2.1判断是否登录 2.2验证path,从redis进行验证 ``` boolean check = seckillOrderService.checkPath(user, goodsId, path); public boolean checkPath(User user, long goodsId, String path) { if(user == null || path == null) { return false; } String pathOld = redisService.get(SeckillKey.getSeckillPath, ""+user.getId() + "_"+ goodsId, String.class); return path.equals(pathOld); } ``` 2.3map标记(查看活动是否已经结束) ``` boolean over = localOverMap.get(goodsId); ``` 2.4预减库存(redis) 此前应该在redis有过存储库存量,通过秒杀商品的key获取库存量并且-1 ``` long stock = redisService.decr(GoodsKey.getSeckillGoodsStock, "" + goodsId); getSeckillGoodsStock --> getSeckillGoodsStock= new GoodsKey( "gs"); public Long decr(KeyPrefix prefix, String key) { Jedis jedis = null; try { jedis = jedisPool.getResource(); //生成真正的key String realKey = prefix.getPrefix() + key; return jedis.decr(realKey); }finally { returnToPool(jedis); } } ``` 2.5判断是否已经秒杀到了(访问mapper) ``` SeckillOrder order = seckillOrderService.getSeckillOrderByUserIdGoodsId(user.getId(), goodsId); //如果可以获取到订单则提示不能重复秒杀 ``` 2.6进入消息队列排队 ``` SeckillMessage mm = new SeckillMessage(); mm.setUser(user); mm.setGoodsId(goodsId); mqSender.sendSeckillMessage(mm);//加入秒杀队列 return Result.success(0);//排队中 ``` 2.7秒杀队列消费者细节: ``` @RabbitListener(queues=MQConfig.MIAOSHA_QUEUE) public void receive(String message) { log.info("receive message:"+message); SeckillMessage mm = RedisService.stringToBean(message, SeckillMessage.class); User user = mm.getUser(); long goodsId = mm.getGoodsId(); GoodsBo goods = goodsService.getseckillGoodsBoByGoodsId(goodsId); int stock = goods.getStockCount(); if(stock <= 0) { return; } //判断是否已经秒杀到了 SeckillOrder order = seckillOrderService.getSeckillOrderByUserIdGoodsId(user.getId(), goodsId); if(order != null) { return; } //减库存 下订单 写入秒杀订单 seckillOrderService.insert(user, goods); } ``` 1.从消息里面获取用户以及商品信息 2.通过redis查看商品库存量,做判断 3.通过mapper做判断是否已经秒杀到了 4.减库存,下订单,写入秒杀订单(事务) ``` @Transactional @Override public OrderInfo insert(User user, GoodsBo goods) { //秒杀商品库存减一 int success = seckillGoodsService.reduceStock(goods.getId()); if(success == 1) { OrderInfo orderInfo = new OrderInfo(); orderInfo.setCreateDate(new Date()); orderInfo.setAddrId(0L); orderInfo.setGoodsCount(1); orderInfo.setGoodsId(goods.getId()); orderInfo.setGoodsName(goods.getGoodsName()); orderInfo.setGoodsPrice(goods.getSeckillPrice()); orderInfo.setOrderChannel(1); orderInfo.setStatus(0); orderInfo.setUserId((long)user.getId()); //添加信息进订单 long orderId = orderService.addOrder(orderInfo); log.info("orderId -->" +orderId+""); SeckillOrder seckillOrder = new SeckillOrder(); seckillOrder.setGoodsId(goods.getId()); seckillOrder.setOrderId(orderInfo.getId()); seckillOrder.setUserId((long)user.getId()); //插入秒杀表 seckillOrderMapper.insertSelective(seckillOrder); return orderInfo; }else { setGoodsOver(goods.getId()); return null; } } ``` ### 3.判断是否秒杀成功(轮询) 3.1判断是否登录(redis) 3.2查询秒杀结果 ``` long result = seckillOrderService.getSeckillResult((long) user.getId(), goodsId); public long getSeckillResult(Long userId, long goodsId) { SeckillOrder order = getSeckillOrderByUserIdGoodsId(userId, goodsId);//访问mapper if(order != null) {//秒杀成功 return order.getOrderId(); }else { boolean isOver = getGoodsOver(goodsId);//这个redis主要用于判断秒杀活动是否已经结束 if(isOver) { return -1; }else { return 0; } } } private boolean getGoodsOver(long goodsId) { //判断key是否存在 return redisService.exists(SeckillKey.isGoodsOver, ""+goodsId); } ``` ### 遇到的问题 1.修改登录逻辑为JWT签发token,登陆时发送到response里的headers。前端接收这个token之后访问页面时携带这个token在request的headers里面。在拦截器那里一直出现空指针异常,一开始以为是前端那边的token没有处理好,后来发现是拦截器里面注入的service为空。于是把拦截器注册为bean,再通过addInterceptor添加就解决了此问题。 ```java registry.addInterceptor(getLoginInterceptor()) //加载下面那个bean注解的拦截器 .addPathPatterns("/**") .excludePathPatterns(patterns); } //使用bean提前加载,不然拦截器那边注入为空 @Bean public HandlerInterceptor getLoginInterceptor(){ return new LoginInterceptor(); } ``` 前端的js代码: ```javascript var token = xhr.getResponseHeader('Authorization');//获取后端服务发过来的token console.log("获取到的token==>"+token); window.location.href="index.html?token="+token;//直接发送给下一个请求页面 ``` ```javascript $.ajax({ headers:{ "Authorization":token }, //后面有需要登录的页面就携带token发起请求即可 ``` **2.redis 出现ERR value is not an integer or out of range(increment(key**) **报错原理** 使用的RedisTemplate,做读写操作时候,都是要经过序列化和反序列化。这时你使用redisTemplate.opsForValue().increment()【自增和自减操作】就可能报错redis.clients.jedis.exceptions.JedisDataException: ERR value is not an integer or out of range了。 **解决** ``` @Bean public RedisTemplate redisTemplate(RedisConnectionFactory redisConnectionFactory) { // 1.创建 redisTemplate 模版 用以 RedisTemplate template = new RedisTemplate<>(); // 2.关联 redisConnectionFactory template.setConnectionFactory(redisConnectionFactory); // 3.创建 序列化类 GenericToStringSerializer genericToStringSerializer = new GenericToStringSerializer(Object.class); // 6.序列化类,对象映射设置 // 7.设置 value 的转化格式和 key 的转化格式 template.setValueSerializer(genericToStringSerializer); template.setKeySerializer(new StringRedisSerializer()); template.afterPropertiesSet(); return template; } ``` **3.模拟上千个用户发起请求时出现的问题** 1.com.alibaba.fastjson.JSONException: syntax error, pos 1, line 1, column 2 这个错误是由于生成的token和用户信息没匹配上,导致登录验证过不去被拦截器重定向到登录界面导致. 重新生成一下token可以解决. 2.java.net.SocketException: Software caused connection abort: recv failed 主要是模拟用户发起请求时,服务端关闭太快导致客户端正在读的时候,服务端已经关闭了,导致报错。 解决: 模拟登录:在关闭请求后面阻塞线程0.5s 模拟抢单:设置多个线程进行模拟请求,每个用户应该是独立的一条线程 ``` for(int i=2;i{ try { //通过用户token和id进行模拟秒杀 startMiaosha(token,id); } catch (IOException e) { e.printStackTrace(); } },"->"+id).start(); } ``` ## 改造工作 ### 1.改造数据库表,创建秒杀实体类 原商品表: ![1658322190173](C:\Users\81471\AppData\Roaming\Typora\typora-user-images\1658322190173.png) **增加属性:** 1.秒杀库存数量(不能为0) 2.秒杀开始时间 3.秒杀结束时间 ![1658340803572](C:\Users\81471\AppData\Roaming\Typora\typora-user-images\1658340803572.png) **修改原先的商品实体类** ![1658322566144](C:\Users\81471\AppData\Roaming\Typora\typora-user-images\1658322566144.png) ### 2.创建秒杀三层架构(controller,service,dao) #### 2.1 MiaoshaController ```java package com.markyao.controller; import com.markyao.dao.pojo.*; import com.markyao.miaosha.Const; import com.markyao.miaosha.GoodsKey; import com.markyao.mq.MiaoshaMessage; import com.markyao.mq.MqSender; import com.markyao.service.*; import com.markyao.service.impl.UserServiceImpl; import com.markyao.vo.Result; import org.springframework.beans.factory.InitializingBean; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.ui.Model; import org.springframework.web.bind.annotation.*; import javax.servlet.http.HttpServletRequest; import java.util.HashMap; import java.util.List; @RestController @RequestMapping("miaosha") public class MiaoshaController implements InitializingBean { private static HashMap isOverMap=new HashMap<>(); @Autowired private MiaoshaGoodService miaoshaGoodService; @Autowired private RedisService redisService; @Autowired UserService userService; @Autowired MiaoshaService miaoshaService; @Autowired OrderService orderService; @Autowired MqSender mqSender; /* * 系统初始化 -->implements InitializingBean */ public void afterPropertiesSet() throws Exception { List goodsList = miaoshaGoodService.getSeckillGoodsList(); if (goodsList == null) { return; } for (Product goods : goodsList) { //对redis进行初始化 redisService.set(GoodsKey.getSeckillGoodsStock, "" + goods.getId(), goods.getMiaoshaCount(), Const.RedisCacheExtime.GOODS_LIST); //对是否结束进行初始化 isOverMap.put(goods.getId(), false); } } /* NO.1,点击秒杀开始,获取秒杀路径path 1.1检查是否登录 1.1.1先获取登录token,再发送token通过redis获取到登录信息user 1.2获取秒杀路径,创建秒杀路径 1.2.1 通过md5创建秒杀路径,再将秒杀路径存入redis */ @GetMapping("getpath/{id}") public Result getMiaoshaPath(@PathVariable long id,HttpServletRequest request ) { String token = request.getHeader("Authorization"); User user = userService.checkT(token); if (user == null) { return Result.fail(-999,"未登录"); } String path = miaoshaService.createMiaoshaPath(user, id); return Result.success(0,"获取秒杀资格成功",path); } /* N0.2 获取成功之后,将path作为请求路径进入到此, 2.1判断是否登录,通过redis进行判断获取 2.2验证path,通过第一步 2.3map标记 2.4预减库存(redis) 2.5判断是否已经秒杀到了 2.6进入消息队列排队 */ @PostMapping(value = "/{path}/seckill") public Result list(Model model, @RequestParam("goodsId") Integer goodsId, @PathVariable("path") String path, HttpServletRequest request) { String loginToken = request.getHeader("Authorization"); User user = userService.checkT(loginToken); if (user == null) { return Result.fail(-999,"未登录"); } //验证path boolean check = miaoshaService.checkPath(user, goodsId, path); if (!check) { return Result.fail(-777,"path不正确!请检查你的账号是否登录正确"); } //减少redis访问 boolean over = isOverMap.get(goodsId); if (over) { return Result.fail(-555,"活动已经结束!"); }/**/ //预减库存 long stock = redisService.decr(GoodsKey.getSeckillGoodsStock, "" + goodsId);//10 if (stock < 0) { isOverMap.put( goodsId, true); return Result.fail(-555,"已经被抢光啦!活动已经结束!"); } //判断是否已经秒杀到了 -->订单表 MiaoshaOrder order = miaoshaService.findOrderByUidAndGoodId(user.getUid(),goodsId); if (order != null) { return Result.fail(-666,"不要重复抢单哦!"); } //加入消息队列 MiaoshaMessage mm = new MiaoshaMessage(); mm.setUser(user); mm.setGoodsId(goodsId); mqSender.sendSeckillMessage(mm); return Result.success(123321,"排队中",null);//排队中 } /* No.3 判断是否秒杀成功(客户端轮询) 3.1判断是否登录 3.2查询秒杀结果 */ @GetMapping(value = "/result/{gid}") public Result miaoshaResult(@PathVariable("gid") Integer gid, HttpServletRequest request) { String loginToken = request.getHeader("Authorization"); User user = userService.checkT(loginToken); if (user == null) { return Result.fail(-999,"你还未登录!"); } MiaoshaOrder order = miaoshaService.findOrderByUidAndGoodId(user.getUid(), gid); if (order!=null){ return Result.success(111,"秒杀成功啦!",null); } if (isOverMap.get(gid)){ return Result.fail(-555,"已经被抢光啦!活动已经结束!"); } return Result.success(123321,"排队中",null);//排队中 } } ``` #### 2.2 MiaoshaService ```java package com.markyao.service.impl; import com.markyao.dao.mapper.MiaoshaMapper; import com.markyao.dao.pojo.MiaoshaOrder; import com.markyao.dao.pojo.User; import com.markyao.miaosha.Const; import com.markyao.miaosha.SeckillKey; import com.markyao.service.MiaoshaService; import com.markyao.service.OrderService; import com.markyao.service.ProductService; import com.markyao.service.RedisService; import org.apache.commons.codec.digest.DigestUtils; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; @Service public class MiaoshaServiceImpl implements MiaoshaService { private static final String md5ForPath="123qaz@key!"; @Autowired RedisService redisService; @Autowired MiaoshaMapper miaoshaMapper; @Autowired ProductService productService; @Autowired OrderService orderService; @Override public String createMiaoshaPath(User user, long goodsId) { //通过md5生成秒杀路径path if (user==null || goodsId<=0){ return null; } String path=DigestUtils.md5Hex(user.getUid()+""+goodsId+""+md5ForPath); //以SeckillKey.getSeckillPath+user.getUid()+"-"+goodsId 为key //以path为值存入redis redisService.set( SeckillKey.getSeckillPath, user.getUid()+"-"+goodsId, path, Const.RedisCacheExtime.GOODS_ID); return path; } @Override public boolean checkPath(User user, long goodsId, String path) { String realPath=redisService.getPath(SeckillKey.getSeckillPath,user.getUid(),goodsId); return path.equals(realPath); } @Override public MiaoshaOrder findOrderByUidAndGoodId(Integer uid, Integer goodsId) { return miaoshaMapper.findByUidAndGoodId(uid,goodsId); } @Transactional //开启事务 @Override //生成订单 public void insertMiaoshaOrder(User user, Integer goodsId) { int cnt=productService.reduceMiaoshaCnt(goodsId);//秒杀库存量减1 if (cnt==1){ //说明此时数据库中库存量大于等于0,秒杀成功了,生成对应订单-->order与orderItem表 orderService.addMiaoshaOrder(user.getUid(),goodsId); //插入秒杀订单表 miaoshaMapper.insert(user.getUid(),goodsId); } } } ``` #### 2.3 MiaoshaDao ```java package com.markyao.dao.mapper; import com.markyao.dao.pojo.MiaoshaOrder; import com.markyao.dao.pojo.Product; import org.apache.ibatis.annotations.Mapper; import org.apache.ibatis.annotations.Param; import java.util.List; @Mapper public interface MiaoshaMapper { //通过uid和商品id查找秒杀订单 MiaoshaOrder findByUidAndGoodId(@Param("uid") Integer uid, @Param("pid") Integer goodsId); //生成秒杀订单 void insert(@Param("uid") Integer uid, @Param("pid")Integer goodsId); //查找秒杀商品列表,这里我选用了"笔记本类"的商品 List findMiaoshaList(); } ``` ### 2.创建redis业务 ```java package com.markyao.service; import com.alibaba.fastjson.JSON; import com.markyao.miaosha.GoodsKey; import com.markyao.miaosha.KeyPrefix; import java.util.concurrent.TimeUnit; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.data.redis.core.RedisTemplate; import org.springframework.data.redis.core.ValueOperations; import org.springframework.stereotype.Service; @Service public class RedisService { @Autowired RedisTemplate redisTemplate; public boolean set(KeyPrefix prefix, String key, T value , int exTime) { //存入redis(秒杀路径path以及商品的库存量) //key的规则: prefix +key String valueStr = beanToString(value); if (valueStr==null||valueStr.length()==0){ return false; } //生成key String realKey=prefix.getPrefix()+key; if (exTime==0){ //要是时间过期则活动已经结束 redisTemplate.opsForValue().set(realKey,valueStr); }else{ //设置过期时间 redisTemplate.opsForValue().set(realKey,valueStr,exTime, TimeUnit.SECONDS); } return true; } //将实体类转化为string用以存入redis public static String beanToString(T value) { if(value == null) { return null; } Class clazz = value.getClass(); if(clazz == int.class || clazz == Integer.class) { return ""+value; }else if(clazz == String.class) { return (String)value; }else if(clazz == long.class || clazz == Long.class) { return ""+value; }else { return JSON.toJSONString(value); } } //获取秒杀路径path public String getPath(KeyPrefix prefix, Integer uid, long goodsId) { String realKey=prefix.getPrefix()+uid+"-"+goodsId; ValueOperations valueOperations = redisTemplate.opsForValue(); String realPath = valueOperations.get(realKey); return realPath; } //用以减少秒杀货品的库存 public long decr(GoodsKey getSeckillGoodsStock, String s) { //key的规则: prefix +key String prefix = getSeckillGoodsStock.getPrefix(); String key=prefix+s; //从redis减少库存 Long decrement = redisTemplate.opsForValue().decrement(key); return decrement; } } ``` ### 3.创建rabbitMQ业务 消息队列初始化: 交换机选择"amq.direct",绑定好对应的秒杀队列即可 消息队列这块生产者比较简单,将秒杀信息存入秒杀队列即可,需要实现创建秒杀信息实体类 ``` @Data @AllArgsConstructor @NoArgsConstructor public class MiaoshaMessage { //秒杀队列实体类 private User user; private Integer goodsId; } ``` 主要是消费者的编写 ```java package com.markyao.mq; import com.markyao.dao.pojo.MiaoshaOrder; import com.markyao.dao.pojo.Product; import com.markyao.dao.pojo.User; import com.markyao.service.MiaoshaService; import com.markyao.service.ProductService; import org.springframework.amqp.core.Message; import org.springframework.amqp.rabbit.annotation.RabbitListener; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Component; @Component public class MqConsumer { @Autowired ProductService productService; @Autowired MiaoshaService miaoshaService; //mq消费端,主要用于插入秒杀订单,监听秒杀队列 //注意将消息转化为json对象 @RabbitListener(queues = MQConfig.MIAOSHA_QUEUE, messageConverter = "jacksonConverter") public void recevie(MiaoshaMessage message){ User user = message.getUser(); Integer goodsId = message.getGoodsId(); //通过商品的id查询此商品此时库存量多少 Product product=productService.getProductByid(goodsId); Integer miaoshaCount = product.getMiaoshaCount(); if(miaoshaCount<=0){ return; } MiaoshaOrder miaoshaOrder = miaoshaService.findOrderByUidAndGoodId(user.getUid(), goodsId); if(miaoshaOrder!=null){ //已经秒杀到了 return; } //插入订单之中,生成订单 miaoshaService.insertMiaoshaOrder(user,goodsId); } } ``` ## 秒杀初始化 初始化写在秒杀的控制层里面,继承InitializingBean接口重写afterPropertiesSet方法即可 1.获取秒杀商品list 2.1遍历list,redis每款商品的goodsid为key设置库存量和商品秒杀的过期时间 2.2遍历list,以每款商品的goodsid为key设置map,表示活动还未结束 ``` public void afterPropertiesSet() throws Exception { List goodsList = seckillGoodsService.getSeckillGoodsList(); if (goodsList == null) { return; } for (GoodsBo goods : goodsList) { redisService.set(GoodsKey.getSeckillGoodsStock, "" + goods.getId(), goods.getStockCount(), Const.RedisCacheExtime.GOODS_LIST); localOverMap.put(goods.getId(), false); } } ``` #### 生成1000个用户模拟 ```java //插入mysql @Test public void createUsers(){ List users = new ArrayList(); //生成用户 for(int i=0;i<1000;i++) { User user = new User(); user.setUid(0); user.setUsername("user"+i); user.setCreatedUser("admin"); user.setPhone((1581333333l+i)+""); user.setCreatedTime(new Date()); user.setSalt("9d5b364d"); user.setPassword(DigestUtils.md5Hex(123456+""+ md5mm)); users.add(user); userMapper.addUser(user); System.out.println("新增用户-->"+user.getUid()); } } ``` ![1658411634168](C:\Users\81471\AppData\Roaming\Typora\typora-user-images\1658411634168.png)