ps年
title: 优惠卷发布平台
author: John Doe
date: 2024-10-03 20:22:30
categories:
- 后端实战
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索引优化
设计思路-
数据库 id 账号 密码
逻辑 -判断账号密码-给与seioon-拦截器进行判断
技术方案-
redis+// SpringCache下一个项目再使用
MQ+Canal-同步数据库
xxl-job预热缓存//
cookie 加密登录逻辑
调优方案
jvm-
查看堆-gc时间-查看高并发下运行时间
ps:
01案例-只需要搭建起查看时间,不需要进行各类jvm调优
1.环境搭建
太久没搭建环境了
创建的maven项目-导入的各类依赖
1.创建数据库
三个字段
主键 id (雪花算法) 用户名String --去重 密码String
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.插入数据库数据
insert into user(id,account,password) values(1,2,3),(2,3,4);6.修改用户账号索引
alter table user add constraint account_unique Unique(account);3.登录逻辑
1.controller
@RestController@RequestMapping("/api")publicclassExampleController{@AutowiredIUserServiceiUserService;@GetMapping("/login")@ApiOperation(value="登录")publicRhello(@ApiParam(value="用户ID",required=true)Stringaccount,@ApiParam(value="用户密码",required=true)Stringpassword,HttpServletRequestreq){Rr=iUserService.login(account,password);if(Objects.nonNull(r.getData())){//登录成功了的SessionwebUserDtodata=(SessionwebUserDto)r.getData();HttpSessionsession=req.getSession();session.setAttribute("session_account",data);}returnr;}}2.service
@ServicepublicclassUserServiceImplextendsServiceImpl<UserMapper,User>implementsIUserService{@AutowiredRedisTemplateredisTemplate;@OverridepublicRlogin(Stringaccount,Stringpassword){//查询redis缓存Objecto=redisTemplate.opsForValue().get(account);if(o==null){//弹出验证码-或者其他策略 查mysql//mysql也为空LambdaQueryWrapper<User>lambdaQueryWrapper=newLambdaQueryWrapper<>();lambdaQueryWrapper.eq(User::getAccount,account);// 使用 Lambda 表达式引用字段 lambdaQueryWrapper.ge(User::getAge, 18);Useruser=this.baseMapper.selectOne(lambdaQueryWrapper);o=user;if(user==null){returnR.error("账号或密码错误");}//插入user缓存到redisredisTemplate.opsForValue().set(account,user);}//账号对了Useruser=(User)o;if(!user.getPassword().equals(password)){returnR.error("账号或密码错误");}//登录成功给sessioSessionwebUserDtosessionwebUserDto=newSessionwebUserDto();sessionwebUserDto.setAccountId(user.getAccount());returnR.success(sessionwebUserDto);}}demo 登录注册抢单-核销lua-redis原子性
主要是的redis进行操作-看看redis的延迟任务能不能加入进来
加密token编写
对明文seiion转json后加密
非对称和对称
加密解密都在服务器-选择对称加密
采用base64编码,
需要手动解析data-赋值给对象
@Configuration@DatapublicclassTokenEncryption{@Value("${token.encryption.key}")privateStringkey;@Value("${token.encryption.iv}")privateStringiv;@Value("${token.encryption.algorithm}")privateStringalgorithm;privateSecretKeySpecAES;privateIvParameterSpecivSpec;privateCiphercipher;publicStringgetKey(){returnkey;}publicStringgetIv(){returniv;}publicStringgetAlgorithm(){returnalgorithm;}publicStringgetTokenEncryption(Stringdata)throwsException{//用于包装密钥 --包装向量extracted();//指定加密//加密cipher.init(Cipher.ENCRYPT_MODE,AES,ivSpec);byte[]encrypted=cipher.doFinal(data.getBytes());StringencryptedBase64=Base64.getEncoder().encodeToString(encrypted);returnencryptedBase64;}privatevoidextracted()throwsException{if(AES==null){byte[]rawKey=adjustKeyLength(key,16);// 16字节=128位AES=newSecretKeySpec(rawKey,algorithm);}if(ivSpec==null){byte[]ivBytes=adjustKeyLength(iv,16);ivSpec=newIvParameterSpec(ivBytes);}if(cipher==null){cipher=Cipher.getInstance("AES/CBC/PKCS5Padding");}}privateSecretKeySpecsecretKey;publicStringretToken(Stringdata)throwsException{if(secretKey==null){byte[]rawKey=adjustKeyLength(key,16);// 16字节=128位secretKey=newSecretKeySpec(rawKey,algorithm);}if(ivSpec==null){byte[]ivBytes=adjustKeyLength(iv,16);ivSpec=newIvParameterSpec(ivBytes);}// 指定解密算法和模式Ciphercipher=Cipher.getInstance("AES/CBC/PKCS5Padding");// 初始化解密器cipher.init(Cipher.DECRYPT_MODE,secretKey,ivSpec);// 解密Base64编码的加密数据byte[]decodedEncryptedData=Base64.getDecoder().decode(data);byte[]decryptedData=cipher.doFinal(decodedEncryptedData);// 返回解密后的原始字符串returnnewString(decryptedData,"UTF-8");}privatebyte[]adjustKeyLength(Stringkey,intlength)throwsException{byte[]keyBytes=key.getBytes("UTF-8");if(keyBytes.length==length){returnkeyBytes;}elseif(keyBytes.length<length){// 如果长度不足,使用0填充byte[]paddedKey=newbyte[length];System.arraycopy(keyBytes,0,paddedKey,0,keyBytes.length);returnpaddedKey;}else{// 如果长度超出,截取前面的部分byte[]shortenedKey=newbyte[length];System.arraycopy(keyBytes,0,shortenedKey,0,length);returnshortenedKey;}}}脑子一抽就写出来了,本来准备套JWT的。。
我脑子抽了-
然后-关于过期就加入时间搓效验-我没有加-后续能人再加把-嘻嘻
拦截器编写
@Configuration public class WebConfig implements WebMvcConfigurer { public void addInterceptors(InterceptorRegistry registry){ registry.addInterceptor(new MyInterceptor()) .addPathPatterns("/**") // 拦截所有请求 .excludePathPatterns("/api/login", "/error"); // 排除某些路径不拦截 } }@Component public class MyInterceptor implements HandlerInterceptor { @Autowired TokenEncryption tokenEncryption; @Override public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception { /* HttpSession session = request.getSession(); String token= (String) session.getAttribute("session_account");*/ if(tokenEncryption==null) { tokenEncryption=new TokenEncryption(); /* key: "w7HqL+Jz3Kt0J3u6fYT3Ow==" iv: "77b07a672d57d64c" algorithm: AES */ 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地址-笑死我了
@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()); } }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; } /** * 重写fillInStackTrace 业务异常不需要堆栈信息,提高效率. */ @Override public Throwable fillInStackTrace() { return this; } }10/4号代码
注册编写
以后碰到异常我自己捕获了,不能老是抛抛抛
使用IdType.ASSIGN_ID(雪花算法)
MyBatis-Plus 的内置雪花算法来生成一个Long类型的唯一 ID
@TableId(value = "id", type = IdType.ASSIGN_UUID) private Long id;@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 /* String keyRegister = "register"+user.getAccount(); redisTemplate.opsForValue().set(keyRegister,user.getPassword());*/ 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)
createtablecoupon(idBIGINTAUTO_INCREMENTPRIMARYKEY,coupon_codevarchar(255)notnullUNIQUE,total_stockint,available_stockintnotnull,start_timedatetimenotnull,end_timedatetimenotnull,statusTINYINTNOTNULLDEFAULT0COMMENT'0: 未开始, 1: 进行中, 2: 已结束',indexidex_coupon_status(status));Query OK,0rowsaffected(0.03sec)createtableCoupon_Redemption(idbigintAUTO_INCREMENTprimarykey,user_idVarchar(255),coupon_idbigintnotnull,redemption_timeDATETIMEnotnull,statusTINYINTNOTNULLDEFAULT1COMMENT'0: 失败, 1: 成功',foreignkey(coupon_id)referencescoupon(id)ONDELETECASCADE,uniquekeyuk_user_coupon(user_id,coupon_id));发卷思路
reids抢卷结构
1.redis抢卷表记录
redisTemplate.opsForValue().set(coupon.getId(), coupon);id+整个表-
再次设计问题
redis+加上前缀-例如-登录+账户-优惠卷+id
发卷service
@RestController @RequestMapping("/coupon") public class CouponController { @Autowired ICouponService iCouponService; @PostMapping("/publish") @ApiOperation(value = "发卷") public R publish(@RequestBody Coupon coupon) { return iCouponService.publish(coupon); } }@Service public class CouponServiceImpl extends ServiceImpl<CouponMapper, Coupon> implements ICouponService { @Autowired RedisTemplate redisTemplate; @Override public R publish(Coupon coupon) { //判断--太多硬编码了-懒得用常量-谁喜欢改谁改 coupon= this.ifCoupon(coupon); //xxl-job每日扫描-防止错误 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.搭建调度中心-创建调度器-创建任务
配置调度中心-添加任务执行调度代码
@Component public class issueJob { @Autowired ICouponService iCouponService; /* 创建一个与 issueJob 类关联的日志记录器。 日志记录器用于输出日志信息,以帮助开发者在运行时了解程序的执行状态或排查问题。 */ @XxlJob("fajuan") public void testJob() { Coupon coupon=new Coupon(); ///coupon iCouponService.publish(coupon); } }抢卷逻辑
////判断请求参数
@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的事务
lua脚本 --抢卷 --队列-抢卷成功队列 -设计库存-修改库存表的可用库存 --优惠卷是否抢过 local couponNum=redis.call("HGET",KEYS[3],ARGV[2]) -- hget 获取不到数据返回false而不是nil 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失败
@Service public class CouponRedemptionServiceImpl extends ServiceImpl<CouponRedemptionMapper, CouponRedemption> implements ICouponRedemptionService { @Autowired RedisTemplate redisTemplate; ///@Resource(name = "Lua_test01") /// DefaultRedisScript<Integer> seizeCouponScript; @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("活动查询失败"); } //查询活动是否开始 // redisTemplate.opsForValue().set("coupon"+coupon.getCouponCode().toString(), coupon); 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("库存不足"); } // 同步队列redisKey // 资源库存redisKey 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);// 同步队列redisKey list.add(resourceStockRedisKey); // 资源库存redisKey list.add(couponSeizeListRedisKey); // 抢券成功列表 用户id 优惠卷id */ List<String> list=new ArrayList<>(); list.add(couponSeizeSyncRedisKey);// 同步队列redisKey list.add(resourceStockRedisKey); // 资源库存redisKey list.add(couponSeizeListRedisKey); // 抢券成功列表 //开始抢卷 // Object execute=redisTemplate.execute(seizeCouponScript,list,userId,coupon.getCouponId()); return null; } }优惠卷库存同步
ps:以前跟着别人写简简单单,自己真的就老实了-逆天时间复杂度
@Configuration public class kcyure { @Autowired RedisTemplate redisTemplate; @XxlJob(value = "kcyure") public void kcyure() { try { //Set<String> couponKeys = redisTemplate.keys("*"); // 假设你的优惠券键以 "coupon:" 开头 //拿出同步库存表的-进行id过滤掉已经同步过的库存 //拿去同步表ids 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); // 转换为字符串 // 提取 coupon 后面的部分 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 内容
抢卷脚本
--抢卷 --队列-抢卷成功队列 -设计库存-修改库存表的可用库存 ---- key: 抢券同步队列,资源库存,抢券成功列表 -- argv:活动id,用户id --优惠卷是否抢过--抢卷成功队列--key coupon local couponNum = redis.call("HGET", KEYS[3], ARGV[2]) -- hget 获取不到数据返回false而不是nil 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"抢卷开发
@Configuration public class kcyure { @Autowired RedisTemplate redisTemplate; @XxlJob(value = "kcyure") public void kcyure() { try { //Set<String> couponKeys = redisTemplate.keys("*"); // 假设你的优惠券键以 "coupon:" 开头 //拿出同步库存表的-进行id过滤掉已经同步过的库存 //拿去同步表ids 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); // 转换为字符串 // 提取 coupon 后面的部分 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
@Configuration public class syncThreadPool { @Bean("syncThreadPool") public ThreadPoolExecutor syncThreadPool(){ int corePoolSize = 1; // 核心线程数 int maxPoolSize = 10; // 最大线程数 long keepAliveTime = 120; // 线程空闲时间 TimeUnit unit = TimeUnit.SECONDS; // 时间单位 // 指定拒绝策略为 DiscardPolicy RejectedExecutionHandler rejectedHandler = new ThreadPoolExecutor.DiscardPolicy(); // 任务队列,使用SynchronousQueue容量为1,在没有线程去消费时不会保存任务 ThreadPoolExecutor executor = new ThreadPoolExecutor (corePoolSize, maxPoolSize, keepAliveTime, unit, new SynchronousQueue<>(),rejectedHandler); return executor; } }xxl-job
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) { //锁内执行 最大等待时间 持锁时间 分钟数 //-2看门狗机制-只要执行完成后才会放锁 String quene=String.format("ying:%s",index); jgtb(quene); } }catch (Exception e) { // 处理中断异常 }finally { if(lock!=null &&) } } public void jgtb(String quene) { } }库存同步代码
@XxlJob(value = "kctb") public void qjtb_syncThreadPool() { for (int i=0;i<10;i++) { threadPool.execute(getData); } }@ComponentpublicclassgetDataimplementsRunnable{@AutowiredprivateRedisTemplateredisTemplate;@AutowiredprivateICouponRedemptionServiceiCouponRedemptionService;@Overridepublicvoidrun(){//读取任务-加锁-免得重复读取//创建示例RedissonClientredissonClient=Redisson.create();//获取锁RLockloc=redissonClient.getLock("ying");try{booleanisLock=loc.tryLock(3,-1,TimeUnit.SECONDS);if(isLock){//锁内执行 最大等待时间 持锁时间 分钟数//-2看门狗机制-只要执行完成后才会放锁intindex=(int)(Math.random()*10)+1;Stringquene=String.format("ying:%s",index);jgtb(quene);}}catch(Exceptione){// 处理中断异常}finally{if(loc!=null&&loc.isLocked()){loc.unlock();}}}publicvoidjgtb(Stringquene){/// Cursor<byte[]> couponKeys = redisTemplate.getConnectionFactory()// .getConnection().scan(// ScanOptions.scanOptions().match("*coupon*").count(100).build());//获取成功数据 -游标拿去try{List<String>stocks=newArrayList<>();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();StringkeyStr=newString(key,StandardCharsets.UTF_8);// 转换为字符串-队列名字Map<String,Object>hashEntries=redisTemplate.opsForHash().entries(keyStr);//根据队列名字找key集合for(Stringkey1:hashEntries.keySet()){stocks.add(key1);}}if(stocks.size()==0){return;}for(Stringkey:stocks){CouponRedemptioncouponRedemption=newCouponRedemption();couponRedemption.setUserId(String.valueOf(key));//真是神仙代码Longo=(Long)redisTemplate.opsForHash().get(quene,key);couponRedemption.setCouponId(String.valueOf(o));couponRedemption.setRedemptionTime(newDate());couponRedemption.setStatus(1);iCouponRedemptionService.save(couponRedemption);redisTemplate.opsForHash().delete(quene,key);}}catch(Exceptione){e.printStackTrace();}}}mysql外键错误
商品卷约束到父表user去了-笑死我了
alter table coupon_redemption add constraint coupon_redemption_coupon_coupon_code_fk foreign key (coupon_id) references coupon (coupon_code);优惠卷展示
@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
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception { /* HttpSession session = request.getSession(); String token= (String) session.getAttribute("session_account");*/ HandlerMethod handlerMethod=(HandlerMethod)handler; //判断如果请求的类是swagger的控制器,直接通行。 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