ps
优惠卷发布平台 1.提供登录注册 登录注册采用redis缓存 减缓mysql压力 2.登录验证 采用cookie 对ID进行aes加密,拦截器解密 3.异常捕获 未知异常—全部补获—发送邮箱提醒开发者 4.优惠卷定时发布 采用xll—job 5.优惠券发布 mysql redis各一份 6.抢优惠卷
lua-redis-ecle
7.优惠卷同步
同步 xxl—job 配合线程池异步同步8.使用优惠券 redis 分布式锁 事务问题 9.优惠券展示 redis缓存问题—mysql索引优化
01案例,不想想太多,简简单单上个手
设计思路-
数据库 id 账号 密码
逻辑 -判断账号密码-给与seioon-拦截器进行判断
技术方案-
redis+// SpringCache下一个项目再使用
MQ+Canal-同步数据库
xxl-job预热缓存//
cookie 加密登录逻辑
调优方案
jvm-
查看堆-gc时间-查看高并发下运行时间
ps:
01案例-只需要搭建起查看时间,不需要进行各类jvm调优
1.环境搭建 太久没搭建环境了
创建的maven项目-导入的各类依赖
1.创建数据库 三个字段
主键 id (雪花算法) 用户名String –去重 密码String
1 2 create table user (id BigInt Primary key ,account varchar (255 ) not null , password varchar (255 ) not null ); Query OK, 0 rows affected (0.03 sec)
2.配置环境 1.配置maven 导入spring起步依赖 导入redis-导入knif34i 导入工具类
2.编写启动类-编写knif4i配置
3.导入mybutsplus-代码生成
4.自动生成mvc三层架构模式
5.插入数据库数据
1 insert into user(id,account,password) values(1 ,2 ,3 ),(2 ,3 ,4 );
6.修改用户账号索引
1 alter table user add constraint account_unique Unique (account);
3.登录逻辑 1.controller 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 @RestController @RequestMapping("/api") public class ExampleController {@Autowired IUserService iUserService; @GetMapping("/login") @ApiOperation(value = "登录") public R hello (@ApiParam(value = "用户ID", required = true) String account, @ApiParam(value = "用户密码", required = true) String password , HttpServletRequest req) { R r=iUserService.login(account,password); if (Objects.nonNull(r.getData())) { SessionwebUserDto data = (SessionwebUserDto) r.getData(); HttpSession session = req.getSession(); session.setAttribute("session_account" ,data); } return r; } }
2.service 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 @Service public class UserServiceImpl extends ServiceImpl <UserMapper, User> implements IUserService {@Autowired RedisTemplate redisTemplate; @Override public R login (String account, String password) { Object o = redisTemplate.opsForValue().get(account); if (o==null ) { LambdaQueryWrapper<User> lambdaQueryWrapper = new LambdaQueryWrapper <>(); lambdaQueryWrapper.eq(User::getAccount, account); User user = this .baseMapper.selectOne(lambdaQueryWrapper); o=user; if (user==null ) { return R.error("账号或密码错误" ); } redisTemplate.opsForValue().set(account,user); } User user = (User) o; if (!user.getPassword().equals(password)) { return R.error("账号或密码错误" ); } SessionwebUserDto sessionwebUserDto = new SessionwebUserDto (); sessionwebUserDto.setAccountId(user.getAccount()); return R.success(sessionwebUserDto); } }
demo 登录注册 抢单-核销 lua-redis原子性
主要是的redis进行操作-看看redis的延迟任务 能不能加入进来
加密token编写 对明文seiion转json后加密
非对称和对称
加密解密都在服务器-选择对称加密
采用base64编码,
需要手动解析data-赋值给对象
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 @Configuration @Data public class TokenEncryption { @Value("${token.encryption.key}") private String key; @Value("${token.encryption.iv}") private String iv; @Value("${token.encryption.algorithm}") private String algorithm; private SecretKeySpec AES; private IvParameterSpec ivSpec; private Cipher cipher; public String getKey () { return key; } public String getIv () { return iv; } public String getAlgorithm () { return algorithm; } public String getTokenEncryption (String data) throws Exception { extracted(); cipher.init(Cipher.ENCRYPT_MODE, AES, ivSpec); byte [] encrypted = cipher.doFinal(data.getBytes()); String encryptedBase64 = Base64.getEncoder().encodeToString(encrypted); return encryptedBase64; } private void extracted () throws Exception { if (AES==null ) { byte [] rawKey = adjustKeyLength(key, 16 ); AES=new SecretKeySpec (rawKey,algorithm); } if (ivSpec==null ) { byte [] ivBytes = adjustKeyLength(iv, 16 ); ivSpec = new IvParameterSpec (ivBytes); } if (cipher == null ) { cipher = Cipher.getInstance("AES/CBC/PKCS5Padding" ); } } private SecretKeySpec secretKey; public String retToken (String data) throws Exception { if (secretKey==null ) { byte [] rawKey = adjustKeyLength(key, 16 ); secretKey = new SecretKeySpec (rawKey, algorithm); } if (ivSpec==null ) { byte [] ivBytes = adjustKeyLength(iv, 16 ); ivSpec = new IvParameterSpec (ivBytes); } Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5Padding" ); cipher.init(Cipher.DECRYPT_MODE, secretKey, ivSpec); byte [] decodedEncryptedData = Base64.getDecoder().decode(data); byte [] decryptedData = cipher.doFinal(decodedEncryptedData); return new String (decryptedData, "UTF-8" ); } private byte [] adjustKeyLength(String key, int length) throws Exception { byte [] keyBytes = key.getBytes("UTF-8" ); if (keyBytes.length == length) { return keyBytes; } else if (keyBytes.length < length) { byte [] paddedKey = new byte [length]; System.arraycopy(keyBytes, 0 , paddedKey, 0 , keyBytes.length); return paddedKey; } else { byte [] shortenedKey = new byte [length]; System.arraycopy(keyBytes, 0 , shortenedKey, 0 , length); return shortenedKey; } } }
脑子一抽就写出来了,本来准备套JWT的。。
我脑子抽了-
然后-关于过期就加入时间搓效验-我没有加-后续能人再加把-嘻嘻
拦截器编写 1 2 3 4 5 6 7 8 9 10 11 @Configuration public class WebConfig implements WebMvcConfigurer { public void addInterceptors (InterceptorRegistry registry ){ registry.addInterceptor (new MyInterceptor ()) .addPathPatterns ("/**" ) .excludePathPatterns ("/api/login" , "/error" ); } }
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 @Component public class MyInterceptor implements HandlerInterceptor { @Autowired TokenEncryption tokenEncryption; @Override public boolean preHandle (HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception { if (tokenEncryption==null ) { tokenEncryption=new TokenEncryption (); tokenEncryption.setKey("w7HqL+Jz3Kt0J3u6fYT3Ow==" ); tokenEncryption.setIv("77b07a672d57d64c" ); tokenEncryption.setAlgorithm("AES" ); } Cookie[] cookies = request.getCookies(); if (cookies == null ) { throw new BusinessException ("用户错误" ); } for (Cookie cookie : cookies) { if ("username" .equals(cookie.getName())) { String username = cookie.getValue(); String userid = tokenEncryption.retToken(username); return true ; } } throw new BusinessException ("用户错误" ); } }
全局异常编写 我redis忘记启动了,报错信息直接给我出了-redis地址-笑死我了
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 @RestControllerAdvice public class ExceptionHandler { @org .springframework.web.bind.annotation .ExceptionHandler(value = Exception.class ) public R handleException(Exception e) { e.getMessage(); return R.error("对不起,操作失败,请联系管理员" ); } @org .springframework.web.bind.annotation .ExceptionHandler(value = BusinessException.class ) public R handleException(BusinessException e) { return R.error( e.getMessage()); } }
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 public class BusinessException extends RuntimeException { private ResponseCodeEnum codeEnum; private Integer code; private String message; public BusinessException(String message, Throwable e) { super (message, e); this .message = message; } public BusinessException(String message) { super (message); this .message = message; } public BusinessException(Throwable e) { super (e); } public BusinessException(ResponseCodeEnum codeEnum) { super (codeEnum.getMsg()); this .codeEnum = codeEnum; this .code = codeEnum.getCode(); this .message = codeEnum.getMsg(); } public BusinessException(Integer code, String message) { super (message); this .code = code; this .message = message; } public ResponseCodeEnum getCodeEnum() { return codeEnum; } public Integer getCode() { return code; } @Override public String getMessage() { return message; } @Override public Throwable fillInStackTrace() { return this ; } }
10/4号代码
注册编写 以后碰到异常我自己捕获了,不能老是抛抛抛
使用 IdType.ASSIGN_ID(雪花算法)
MyBatis-Plus 的内置雪花算法来生成一个 Long 类型的唯一 ID
1 2 @TableId(value = "id" , type = IdType.ASSIGN_UUID) private Long id;
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 @Override public R register(User user ) { if (user ==null ||user .getAccount() == null ||user .getPassword()==null ) { return R.error("请补全参数"); } //各类校验 Object o = redisTemplate.opsForValue().get (user .getAccount()); if (o!=null ) { return R.error("账号已存在"); } ///后续做xxl-job int insert = this.baseMapper.insert (user ); if (insert ==0 ) { return R.error("注册失败"); } //即时缓存 redisTemplate.opsForValue().set (user .getAccount(),user .getPassword()); return R.success("注册成功"); }
设计出错 redis设计结构出错-应该是id-配合整个类()
不然拿不到id-主键搜索本身就是聚集索引快-而账户搜索就不是
前端编写 抢卷发布0 -redis各类技术 设计思路-
我发布抢卷mysql
mysql-canlce—redis进行缓存–解决缓存不一致
redis各类击穿问题
reids-spring cache
redis原子性操作
抢卷发布 数据库设计 抢卷ID -抢卷库存
抢卷表(Coupon)
抢卷记录表(Coupon_Redemption)
1 2 3 4 5 6 7 create table coupon( id BIGINT AUTO_INCREMENT PRIMARY KEY, coupon_code varchar (255 ) not null UNIQUE ,total_stock int ,available_stock int not null ,start_time datetime not null ,end_time datetime not null ,status TINYINT NOT NULL DEFAULT 0 COMMENT '0: 未开始, 1: 进行中, 2: 已结束' , index idex_coupon_status(status)); Query OK, 0 rows affected (0.03 sec)
1 2 3 4 5 6 7 8 9 10 create table Coupon_Redemption ( id bigint AUTO_INCREMENT primary key, user_id Varchar (255 ), coupon_id bigint not null , redemption_time DATETIME not null , status TINYINT NOT NULL DEFAULT 1 COMMENT '0: 失败, 1: 成功' , foreign key(coupon_id) references coupon(id) ON DELETE CASCADE, unique key uk_user_coupon (user_id, coupon_id) );
发卷思路
reids抢卷结构 1.redis抢卷表记录
1 redisTemplate.opsForValue().set (coupon.getId(), coupon);
id+整个表-
再次设计问题 redis+加上前缀-例如-登录+账户-优惠卷+id
发卷service 1 2 3 4 5 6 7 8 9 10 11 12 13 14 @RestController @RequestMapping ("/coupon" ) public class CouponController {@Autowired ICouponService iCouponService; @PostMapping ("/publish" ) @ApiOperation (value = "发卷" ) public R publish (@RequestBody Coupon coupon) { return iCouponService .publish (coupon); } }
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 @Service public class CouponServiceImpl extends ServiceImpl<CouponMapper , Coupon> implements ICouponService {@Autowired RedisTemplate redisTemplate; @Override public R publish(Coupon coupon) { coupon= this .ifCoupon(coupon); boolean i=save(coupon); if (!i) { return R .error("发布失败" ); } redisTemplate.opsForValue().set("coupon" +coupon.getId().toString(), coupon); return R .success(coupon); } public Coupon ifCoupon(Coupon coupon) { if (coupon.getTotalStock()==null ) { coupon.setTotalStock(10 ); } if (coupon.getAvailableStock()==null ) { coupon.setAvailableStock(coupon.getTotalStock()); } if (coupon.getStartTime()==null ) { coupon.setStartTime(LocalDateTime .now()); } if (coupon.getEndTime()==null ) { coupon.setEndTime(LocalDateTime .now().plusDays(1 )); } if (coupon.getCouponCode()==null ) { coupon.generateCouponCode(); } if (coupon.getStatus()==null ) { coupon.setStatus(0 ); } return coupon; } }
xxl-job 定时发布优惠卷 步骤 1.搭建调度中心-创建调度器-创建任务
配置调度中心-添加任务执行调度代码
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 @Component public class issueJob { @Autowired ICouponService iCouponService; @XxlJob ("fajuan" ) public void testJob ( ) { Coupon coupon=new Coupon (); iCouponService.publish (coupon); } }
抢卷逻辑 ////判断请求参数
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 @Service public class CouponRedemptionServiceImpl extends ServiceImpl<CouponRedemptionMapper , CouponRedemption> implements ICouponRedemptionService {@Autowired RedisTemplate redisTemplate; @Override public R qiangjuan(CouponRedemption coupon) { String userId = (String ) RequestContextHolder .getRequestAttributes().getAttribute("userId" , RequestAttributes .SCOPE_REQUEST ); if (coupon.getUserId() == null ) { return R .error("用户id不能为空" ); } if (coupon.getCouponId() == null ) { return R .error("活动查询失败" ); } ValueOperations valueOperations = redisTemplate.opsForValue(); Object o = valueOperations.get("coupon" + coupon.getCouponId()); Coupon coupon2 = new Coupon (); coupon2 = (Coupon ) o; if (coupon2.getStatus() == 0 ) { return R .error("活动未开始" ); } LocalDateTime now = LocalDateTime .now(); if (now.isBefore(coupon2.getStartTime())) { System .out.println("活动尚未开始" ); } else if (now.isAfter(coupon2.getEndTime())) { System .out.println("活动已结束" ); } if (coupon2.getTotalStock() <= 0 ) { return R .error("库存不足" ); } return null ; } }
///执行抢卷脚本
eval执行lua脚本-redistempplate替换 MULTL开始redis的事务
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 lua脚本local couponNum=redis.call("HGET" ,KEYS[3 ],ARGV[2 ])if couponNum ~= false and tonumber (couponNum) >= 1 then return "-1" ;end local stockNum=redis.call("HGET" ,KEYS[1 ],ARGV[1 ])if stockNum ~= false or tonumber (stockNum) < 1 then return "-2" ;end
///返回抢卷结果
-1: 限领一张
-2: 已抢光
-3: 写入抢券成功队列失败,返回给用户为:抢券失败
-4: 已抢光
-5: 写入抢券同步队列失败,返回给用户为:抢券失败
///线程池xxl-job进行抢卷结果同步
崩溃一天 做redis-给redis加了个序列化器-导致后续代码一直获取redis失败
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 @Service public class CouponRedemptionServiceImpl extends ServiceImpl <CouponRedemptionMapper , CouponRedemption > implements ICouponRedemptionService {@Autowired RedisTemplate redisTemplate; @Override public R qiangjuan(CouponRedemption coupon) { String userId = (String ) RequestContextHolder.getRequestAttributes().getAttribute("userId" , RequestAttributes.SCOPE_REQUEST); if (userId == null ) { return R.error("用户id不能为空" ); } coupon.setUserId(userId); if (coupon.getCouponId() == null ) { return R.error("活动查询失败" ); } Object o= redisTemplate.opsForValue().get ("coupon" +coupon.getCouponId().toString()); if (o==null ) { return R.error("活动查询失败" ); } Coupon coupon2 = new Coupon(); coupon2 = (Coupon) o; if (coupon2.getStatus() == 0 ) { return R.error("活动未开始" ); } LocalDateTime now = LocalDateTime.now(); if (now.isBefore(coupon2.getStartTime())) { System.out.println("活动尚未开始" ); } else if (now.isAfter(coupon2.getEndTime())) { System.out.println("活动已结束" ); } if (coupon2.getTotalStock() <= 0 ) { return R.error("库存不足" ); } int index = (int) (UUID.randomUUID().hashCode() % 10 ); String resourceStockRedisKey = String .format("coupon%s" , index); String couponSeizeSyncRedisKey = String .format("COUPON_SEIZE_SYNC_QUEUE_NAME%s" , index); String couponSeizeListRedisKey = String .format("COUPON_SEIZE%s%s" ,coupon.getCouponId(), index); List<String > list=new ArrayList<>(); list.add(couponSeizeSyncRedisKey); list.add(resourceStockRedisKey); list.add(couponSeizeListRedisKey); return null ; } }
优惠卷库存同步 ps:以前跟着别人写简简单单,自己真的就老实了-逆天时间复杂度
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 @Configurationpublic class kcyure { @Autowired RedisTemplate redisTemplate; @XxlJob (value = "kcyure" ) public void kcyure () { try { Cursor<byte []> couponKeys = redisTemplate.getConnectionFactory ().getConnection ().scan (ScanOptions.scanOptions ().match ("*coupon*" ).count (100 ).build ()); Set<String > couponKeyss = new HashSet <>(); while (couponKeys.hasNext ()) { byte [] key = couponKeys.next (); String keyStr = new String (key , StandardCharsets.UTF_8 ); if (keyStr.contains ("coupon" )) { String suffix = keyStr.substring (keyStr.indexOf ("coupon" ) + "coupon" .length ()); couponKeyss.add (suffix); } } Set<String > stocks = new HashSet <>(); Cursor<byte []> stocksi = redisTemplate.getConnectionFactory ().getConnection () .scan (ScanOptions.scanOptions ().match ("stock*" ).count (100 ).build ()); while (stocksi.hasNext ()) { byte [] key = stocksi.next (); String keyStr = new String (key , StandardCharsets.UTF_8 ); Map<Object , Object > hashEntries = redisTemplate.opsForHash ().entries (keyStr); for (Object key1 : hashEntries.keySet ()) { stocks.add (keyStr); } } if (couponKeyss != null ) { for (String key : couponKeyss) { if (stocks.contains (key )) { continue ; } Coupon coupon= (Coupon) redisTemplate.opsForValue ().get ("coupon" +key .toString ()); int index = (int ) (coupon.getId () % 10 ); String resourceStockRedisKey = String .format ("Stock:%s" , index); redisTemplate.opsForHash ().put (resourceStockRedisKey, key , coupon.getAvailableStock ()); } } }catch (Exception e) { e.printStackTrace (); System.out .println ("库存同步失败" ); }}}
设计问题 优惠卷id-我设置的String-用的uuid-一直有— 我设置了LOng的-后续抢卷脚本转long才能找到数据-设计序列化-哈希计算的问题
redis序列化-更换日期类型-设置日期格式
10/5 内容
抢卷脚本 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 local couponNum = redis.call ("HGET", KEYS[3 ], ARGV[2 ])if couponNum ~= false and tonumber(couponNum) >= 1 then return "-1";end local stockNum=redis.call ("HGET",KEYS[2 ],ARGV[1 ])if stockNum == false or tonumber(stockNum) < 1 then return "-2";end local listNum = redis.call ("HSET",KEYS[3 ], ARGV[2 ], 1 )if listNum == false or tonumber(listNum) < 1 then return "-3";end stockNum=redis.call ("HINCRBY",KEYS[2 ],ARGV[1 ],-1 )if tonumber(stockNum)<0 then return "-4";end local result=redis.call ("HSETNX",KEYS[1 ],ARGV[2 ],ARGV[1 ])if result>0 then return ARGV[1 ]..""end return "-5"
抢卷开发 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 @Configurationpublic class kcyure { @Autowired RedisTemplate redisTemplate; @XxlJob (value = "kcyure" ) public void kcyure () { try { Cursor<byte []> couponKeys = redisTemplate.getConnectionFactory ().getConnection ().scan (ScanOptions.scanOptions ().match ("*coupon*" ).count (100 ).build ()); Set<String > couponKeyss = new HashSet <>(); while (couponKeys.hasNext ()) { byte [] key = couponKeys.next (); String keyStr = new String (key , StandardCharsets.UTF_8 ); if (keyStr.contains ("coupon" )) { String suffix = keyStr.substring (keyStr.indexOf ("coupon" ) + "coupon" .length ()); couponKeyss.add (suffix); } } Set<String > stocks = new HashSet <>(); Cursor<byte []> stocksi = redisTemplate.getConnectionFactory ().getConnection () .scan (ScanOptions.scanOptions ().match ("stock*" ).count (100 ).build ()); while (stocksi.hasNext ()) { byte [] key = stocksi.next (); String keyStr = new String (key , StandardCharsets.UTF_8 ); Map<Object , Object > hashEntries = redisTemplate.opsForHash ().entries (keyStr); for (Object key1 : hashEntries.keySet ()) { stocks.add (keyStr); } } if (couponKeyss != null ) { for (String key : couponKeyss) { if (stocks.contains (key )) { continue ; } Coupon coupon= (Coupon) redisTemplate.opsForValue ().get ("coupon" +key .toString ()); int index = (int ) (coupon.getId () % 10 ); String resourceStockRedisKey = String .format ("Stock:%s" , index); redisTemplate.opsForHash ().put (resourceStockRedisKey, key , coupon.getAvailableStock ()); } } }catch (Exception e) { e.printStackTrace (); System.out .println ("库存同步失败" ); }}}
库存同步 线程池-配合xxl-job
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 @Configuration public class syncThreadPool { @Bean("syncThreadPool") public ThreadPoolExecutor syncThreadPool () { int corePoolSize = 1 ; int maxPoolSize = 10 ; long keepAliveTime = 120 ; TimeUnit unit = TimeUnit.SECONDS; RejectedExecutionHandler rejectedHandler = new ThreadPoolExecutor .DiscardPolicy(); ThreadPoolExecutor executor = new ThreadPoolExecutor (corePoolSize, maxPoolSize, keepAliveTime, unit, new SynchronousQueue <>(),rejectedHandler); return executor; } }
xxl-job
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 public class getData implements Runnable { private int index; public getData (int index ){ this .index = index; } @Override public void run ( ) { RedissonClient redissonClient = Redisson .create (); RLock loc=redissonClient.getLock ("ying" ); try { boolean isLock=loc.tryLock (3 , -1 , TimeUnit .SECONDS ); if (isLock) { String quene=String .format ("ying:%s" ,index); jgtb (quene); } }catch (Exception e) { }finally { if (lock!=null &&) } } public void jgtb (String quene ) { } }
库存同步代码 1 2 3 4 5 6 7 8 9 @XxlJob (value = "kctb" )public void qjtb_syncThreadPool () { for (int i=0 ;i<10 ;i++) { threadPool.execute(getData); } }
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 @Component public class getData implements Runnable { @Autowired private RedisTemplate redisTemplate; @Autowired private ICouponRedemptionService iCouponRedemptionService; @Override public void run () { RedissonClient redissonClient = Redisson.create(); RLock loc=redissonClient.getLock("ying" ); try { boolean isLock=loc.tryLock(3 , -1 , TimeUnit.SECONDS); if (isLock) { int index = (int )(Math.random() * 10 ) + 1 ; String quene=String.format("ying:%s" ,index); jgtb(quene); } }catch (Exception e) { }finally { if (loc != null && loc.isLocked()) { loc.unlock(); } } } public void jgtb (String quene) { try { List<String> stocks = new ArrayList <>(); Cursor<byte []> stocksi = redisTemplate.getConnectionFactory(). getConnection().scan(ScanOptions.scanOptions() .match(quene).count(100 ).build()); if (stocksi==null ) { return ; } while (stocksi.hasNext()) { byte [] key = stocksi.next(); String keyStr = new String (key, StandardCharsets.UTF_8); Map<String, Object> hashEntries = redisTemplate.opsForHash().entries(keyStr); for (String key1 : hashEntries.keySet()) { stocks.add(key1); } } if (stocks.size()==0 ) { return ; } for ( String key:stocks) { CouponRedemption couponRedemption = new CouponRedemption (); couponRedemption.setUserId(String.valueOf(key)); Long o= (Long) redisTemplate.opsForHash().get(quene,key); couponRedemption.setCouponId(String.valueOf(o)); couponRedemption.setRedemptionTime(new Date ()); couponRedemption.setStatus(1 ); iCouponRedemptionService.save(couponRedemption); redisTemplate.opsForHash().delete(quene,key); } }catch (Exception e) { e.printStackTrace(); } } }
mysql外键错误 商品卷约束到父表user去了-笑死我了
1 2 3 4 5 alter table coupon_redemption add constraint coupon_redemption_coupon_coupon_code_fk foreign key (coupon_id) references coupon (coupon_code);
优惠卷展示 1 2 3 4 5 6 7 8 9 10 11 12 13 @Override public R lsit ( ) { Set <String > keys = redisTemplate.keys ("coupon*" ); List <Object > values = new ArrayList <>(); for (String key : keys) { values.add (redisTemplate.opsForValue ().get (key)); } return R.success (values); }
拦截器修复拦截knife4i 1 2 3 4 5 6 7 8 9 10 public boolean preHandle (HttpServletRequest request, HttpServletResponse response, Object handler ) throws Exception { HandlerMethod handlerMethod=(HandlerMethod)handler ; if (handlerMethod.getBean().getClass().getName().equals("springfox.documentation.swagger.web.ApiResourceController" )){ return true ; }
总结 项目地址 https://gitee.com/laomaodu/anli01
api地址
https://apifox.com/apidoc/shared-20ef25e6-5dfe-482f-88a0-ae48011048a3
心得 对于代码逻辑来讲简单-但是总会涉及到很多bug-很多的bug都是再设计时候埋下的坑,下一次项目就会注重设计了
https://www.bilibili.com/video/BV14N1qYNEbU/?spm_id_from=333.999.0.0