当前位置: 首页 > news >正文

郴州制作网站设计较好的公司成都网站建设创新互联

郴州制作网站设计较好的公司,成都网站建设创新互联,网站排名提升软件,wordpress keyword文章目录 基于Redis实现短信登录商户查询缓存优惠券秒杀一人一单 分布式锁Redis分布式锁误删情况说明解决Redis分布式锁误删问题使用lua脚本解决分布式锁的原子性问题 基于阻塞队列实现秒杀优化Redis消息队列优化秒杀业务达人探店参考 本文是根据黑马程序员的视频课程 黑马程序… 文章目录 基于Redis实现短信登录商户查询缓存优惠券秒杀一人一单 分布式锁Redis分布式锁误删情况说明解决Redis分布式锁误删问题使用lua脚本解决分布式锁的原子性问题 基于阻塞队列实现秒杀优化Redis消息队列优化秒杀业务达人探店参考 本文是根据黑马程序员的视频课程 黑马程序员Redis入门到实战教程深度透析redis底层原理redis分布式锁企业解决方案黑马点评实战项目整理而来。 模仿大众点评的项目 基于Redis实现短信登录 代码 Overridepublic Result login(LoginFormDTO loginForm, HttpSession session) {//1. 校验手机号String phone loginForm.getPhone();if (RegexUtils.isPhoneInvalid(phone)) {return Result.fail(手机号格式错误);}//2. 从redis获取验证码并校验String cacheCode stringRedisTemplate.opsForValue().get(LOGIN_CODE_KEY phone);String code loginForm.getCode();if (cacheCode null || !cacheCode.equals(code)){//3. 不一致报错return Result.fail(验证码错误);}//4.一致根据手机号查询用户User user query().eq(phone, phone).one();//5. 判断用户是否存在if (user null){//6. 不存在创建新用户user createUserWithPhone(phone);}//7.保存用户信息到redis// 7.1 随机生成token作为登录令牌String token UUID.randomUUID().toString(true);// 7.2 将User转为HashMap存储UserDTO userDTO BeanUtil.copyProperties(user, UserDTO.class);MapString, Object userMap BeanUtil.beanToMap(userDTO, new HashMap(),CopyOptions.create().setIgnoreNullValue(true).setFieldValueEditor((fieldName, fieldValue) - fieldValue.toString()));// 7.3 存储String tokenKey LOGIN_USER_KEY token;stringRedisTemplate.opsForHash().putAll(tokenKey, userMap);// 7.4 设置token有效期stringRedisTemplate.expire(tokenKey, LOGIN_USER_TTL, TimeUnit.MINUTES);// 8. 返回tokenreturn Result.ok(token);}商户查询缓存 防止缓存穿透在缓存和数据库中都不存在的信息多次查询会给数据库带来压力采用返回空值到redis的方案下一次查询直接显示为空。还有一种方法是布隆过滤。 代码如下 Resourceprivate StringRedisTemplate stringRedisTemplate;Overridepublic Result queryById(Long id) {String key CACHE_SHOP_KEY id;// 1. 从redis查询商户缓存String shopJson stringRedisTemplate.opsForValue().get(key);// 2. 判断redis缓存中是否存在if (StrUtil.isNotBlank(shopJson)) {// 3. 存在直接返回Shop shop JSONUtil.toBean(shopJson, Shop.class); // Json数据转换为java对象return Result.ok(shop);}// 判断命中的是否是空值if (shopJson ! null) {// 返回错误信息return Result.fail(店铺不存在);}// 4. 不存在根据id查询数据库Shop shop getById(id);// 5. 数据库中不存在返回错误if (shop null) {// 将空值写入redisstringRedisTemplate.opsForValue().set(key, , CACHE_NULL_TTL, TimeUnit.MINUTES);// 返回错误信息return Result.fail(店铺不存在);}// 6. 数据库中存在写入redisstringRedisTemplate.opsForValue().set(key, JSONUtil.toJsonStr(shop), CACHE_SHOP_TTL, TimeUnit.MINUTES);// 7. 返回return Result.ok(shop);} 店铺对应的数据存入redis中 Redis和Mysql数据库数据同步 根据id修改店铺时先修改数据库再删除缓存我们确定了采用删除策略来解决双写问题当我们修改了数据之后然后把缓存中的数据进行删除查询时发现缓存中没有数据则会从mysql中加载最新的数据从而避免数据库和缓存不一致的问题 OverrideTransactionalpublic Result update(Shop shop) {Long id shop.getId();if (id null) {return Result.fail(店铺id不能为空);}// 1. 更新数据库updateById(shop);// 2. 删除缓存stringRedisTemplate.delete(CACHE_SHOP_KEY shop.getId());return Result.ok();}如果你在redis中都找不到就说明你查看的不是热点数据啊就直接返回你查看的热点不存在就行了这个是根据业务场景来实现的跟普通的击穿不一样的 优惠券秒杀 mysql数据库中tb_voucher优惠券的表 实现优惠券秒杀的基本代码 Resourceprivate ISeckillVoucherService seckillVoucherService;Resourceprivate RedisIdWorker redisIdWorker;OverrideTransactionalpublic Result seckillVoucher(Long voucherId) {// 1. 查询优惠券SeckillVoucher voucher seckillVoucherService.getById(voucherId);// 2. 判断秒杀是否开始if (voucher.getBeginTime().isAfter(LocalDateTime.now())) {// 尚未开始return Result.fail(秒杀尚未开始);}// 3. 判断秒杀是否结束if (voucher.getEndTime().isBefore(LocalDateTime.now())) {return Result.fail(秒杀已经开始);}// 4. 判断库存是否充足if (voucher.getStock() 1) {// 库存不足return Result.fail(库存不足);}// 5. 扣减库存boolean success seckillVoucherService.update().setSql(stock stock - 1).eq(voucher_id, voucherId).update();if (!success) {// 库存不足return Result.fail(库存不足);}// 6. 创建订单VoucherOrder voucherOrder new VoucherOrder();// 6.1 订单idlong orderId redisIdWorker.nextId(order);voucherOrder.setId(orderId);// 6.2 用户idLong userId UserHolder.getUser().getId();voucherOrder.setUserId(userId);// 6.3 代金券idvoucherOrder.setVoucherId(voucherId);save(voucherOrder);// 7. 返回订单idreturn Result.ok(orderId);}以上代码存在一人可以领取多个优惠券的情形下面实现一人一单的功能。 一人一单 Transactionalpublic Result createVoucherOrder(Long voucherId) {// 5. 一人一单Long userId UserHolder.getUser().getId();// 5.1 查询订单int count query().eq(user_id, userId).eq(voucher_id, voucherId).count();// 5.2 判断是否存在if (count 0) {// 用户已经购买过return Result.fail(用户已经购买过一次!);}// 6. 扣减库存boolean success seckillVoucherService.update().setSql(stock stock - 1) // set stock stock - 1.eq(voucher_id, voucherId).gt(stock, 0) // where id ? and stock 0.update();if (!success) {// 库存不足return Result.fail(库存不足);}// 7. 创建订单VoucherOrder voucherOrder new VoucherOrder();// 7.1 订单idlong orderId redisIdWorker.nextId(order);voucherOrder.setId(orderId);// 7.2 用户idvoucherOrder.setUserId(userId);// 7.3 代金券idvoucherOrder.setVoucherId(voucherId);save(voucherOrder);// 8. 返回订单idreturn Result.ok(orderId);}同时加锁保证事务的特性同时也控制了锁的粒度。这样可以解决单机情况下的一人一单安全问题但是在集群模式下失效。 Long userId UserHolder.getUser().getId();synchronized (userId.toString().intern()) {// 获取代理对象事务IVoucherOrderService proxy (IVoucherOrderService) AopContext.currentProxy();return proxy.createVoucherOrder(voucherId);}有关锁失效原因分析 由于现在我们部署了多个tomcat每个tomcat都有一个属于自己的jvm那么假设在服务器A的tomcat内部有两个线程这两个线程由于使用的是同一份代码那么他们的锁对象是同一个是可以实现互斥的但是如果现在是服务器B的tomcat内部又有两个线程但是他们的锁对象写的虽然和服务器A一样但是锁对象却不是同一个所以线程3和线程4可以实现互斥但是却无法和线程1和线程2实现互斥这就是 集群环境下syn锁失效的原因在这种情况下我们就需要使用分布式锁来解决这个问题。 分布式锁 分布式锁满足分布式系统或集群模式下多进程可见并且互斥的锁。 分布式锁的核心思想就是让大家都使用同一把锁只要大家使用的是同一把锁那么我们就能锁住线程不让线程进行让程序串行执行这就是分布式锁的核心思路 基于Redis实现分布式锁redis作为分布式锁是非常常见的一种使用方式现在企业级开发中基本都使用redis或者zookeeper作为分布式锁利用setnx这个方法如果插入key成功则表示获得到了锁如果有人插入成功其他人插入失败则表示无法获得到锁利用这套逻辑来实现分布式锁 实现分布式锁时需要实现的两个基本方法 获取锁 互斥确保只能有一个线程获取锁非阻塞尝试一次成功返回true失败返回false 释放锁 手动释放超时释放获取锁时添加一个超时时间 SimpleRedisLock 利用setnx方法进行加锁同时增加过期时间防止死锁此方法可以保证加锁和增加过期时间具有原子性 Override public boolean tryLock(long timeoutSec) {// 获取线程标示String threadId ID_PREFIX Thread.currentThread().getId();// 获取锁Boolean success stringRedisTemplate.opsForValue().setIfAbsent(KEY_PREFIX name, threadId, timeoutSec, TimeUnit.SECONDS); //SETNX 实现互斥效果return Boolean.TRUE.equals(success); }public void unlock() {//通过del删除锁stringRedisTemplate.delete(KEY_PREFIX name); }修改业务代码 Overridepublic Result seckillVoucher(Long voucherId) {// 1.查询优惠券SeckillVoucher voucher seckillVoucherService.getById(voucherId);// 2.判断秒杀是否开始if (voucher.getBeginTime().isAfter(LocalDateTime.now())) {// 尚未开始return Result.fail(秒杀尚未开始);}// 3.判断秒杀是否已经结束if (voucher.getEndTime().isBefore(LocalDateTime.now())) {// 尚未开始return Result.fail(秒杀已经结束);}// 4.判断库存是否充足if (voucher.getStock() 1) {// 库存不足return Result.fail(库存不足);}Long userId UserHolder.getUser().getId();//创建锁对象(新增代码)SimpleRedisLock lock new SimpleRedisLock(order: userId, stringRedisTemplate);//获取锁对象boolean isLock lock.tryLock(1200);//加锁失败if (!isLock) {return Result.fail(不允许重复下单);}try {//获取代理对象(事务)IVoucherOrderService proxy (IVoucherOrderService) AopContext.currentProxy();return proxy.createVoucherOrder(voucherId);} finally {//释放锁lock.unlock();}}Redis分布式锁误删情况说明 持有锁的线程在锁的内部出现了阻塞导致他的锁自动释放这时其他线程线程2来尝试获得锁就拿到了这把锁然后线程2在持有锁执行过程中线程1反应过来继续执行而线程1执行过程中走到了删除锁逻辑此时就会把本应该属于线程2的锁进行删除这就是误删别人锁的情况说明 解决方案解决方案就是在每个线程释放锁的时候去判断一下当前这把锁是否属于自己如果属于自己则不进行锁的删除假设还是上边的情况线程1卡顿锁自动释放线程2进入到锁的内部执行逻辑此时线程1反应过来然后删除锁但是线程1一看当前这把锁不是属于自己于是不进行删除锁逻辑当线程2走到删除锁逻辑时如果没有卡过自动释放锁的时间点则判断当前这把锁是属于自己的于是删除这把锁。 解决Redis分布式锁误删问题 需求修改之前的分布式锁实现满足在获取锁时存入线程标示用UUID 线程id表示 在释放锁时先获取锁中的线程标示判断是否与当前线程标示一致 如果一致则释放锁如果不一致则不释放锁 核心逻辑在存入锁时放入自己线程的标识在删除锁时判断当前这把锁的标识是不是自己存入的如果是则进行删除如果不是则不进行删除。 uuid用来区分jvm的jvm内部用线程id区分 具体代码如下加锁 private static final String ID_PREFIX UUID.randomUUID().toString(true) -; Override public boolean tryLock(long timeoutSec) {// 获取线程标示String threadId ID_PREFIX Thread.currentThread().getId();// 获取锁Boolean success stringRedisTemplate.opsForValue().setIfAbsent(KEY_PREFIX name, threadId, timeoutSec, TimeUnit.SECONDS);return Boolean.TRUE.equals(success); }释放锁 public void unlock() {// 获取线程标示String threadId ID_PREFIX Thread.currentThread().getId();// 获取锁中的标示String id stringRedisTemplate.opsForValue().get(KEY_PREFIX name);// 判断标示是否一致if(threadId.equals(id)) {// 释放锁stringRedisTemplate.delete(KEY_PREFIX name);} }使用lua脚本解决分布式锁的原子性问题 释放锁的lua脚本如下unlock.lua -- 这里的 KEYS[1] 就是锁的key这里的ARGV[1] 就是当前线程标示 -- 获取锁中的标示判断是否与当前线程标示一致 if (redis.call(GET, KEYS[1]) ARGV[1]) then-- 一致则删除锁return redis.call(DEL, KEYS[1]) end -- 不一致则直接返回 return 0我们的RedisTemplate中可以利用execute方法去执行lua脚本 private static final DefaultRedisScriptLong UNLOCK_SCRIPT;static {UNLOCK_SCRIPT new DefaultRedisScript();UNLOCK_SCRIPT.setLocation(new ClassPathResource(unlock.lua));UNLOCK_SCRIPT.setResultType(Long.class);}Override public void unlock() {// 调用lua脚本stringRedisTemplate.execute(UNLOCK_SCRIPT,Collections.singletonList(KEY_PREFIX name),ID_PREFIX Thread.currentThread().getId()); }基于阻塞队列实现秒杀优化 秒杀优化-异步秒杀思路 我们来回顾一下下单流程 当用户发起请求此时会请求nginxnginx会访问到tomcat而tomcat中的程序会进行串行操作分成如下几个步骤 1、查询优惠卷 2、判断秒杀库存是否足够 3、查询订单 4、校验是否是一人一单 5、扣减库存 6、创建订单 在这六步操作中又有很多操作是要去操作数据库的而且还是一个线程串行执行 这样就会导致我们的程序执行的很慢所以我们需要异步程序执行那么如何加速呢 需求 新增秒杀优惠券的同时将优惠券信息保存到Redis中 基于Lua脚本判断秒杀库存、一人一单决定用户是否抢购成功 如果抢购成功将优惠券id和用户id封装后存入阻塞队列 开启线程任务不断从阻塞队列中获取信息实现异步下单功能 秒杀优化-基于阻塞队列实现秒杀优化 seckill.lua文件实现上图中的逻辑 -- 1.参数列表 -- 1.1.优惠券id local voucherId ARGV[1] -- 1.2.用户id local userId ARGV[2]-- 2.数据key -- 2.1.库存key local stockKey seckill:stock: .. voucherId -- 2.2.订单key local orderKey seckill:order: .. voucherId-- 3.脚本业务 -- 3.1.判断库存是否充足 get stockKey if(tonumber(redis.call(get, stockKey)) 0) then-- 3.2.库存不足返回1return 1 end -- 3.2.判断用户是否下单 SISMEMBER orderKey userId if(redis.call(sismember, orderKey, userId) 1) then-- 3.3.存在说明是重复下单返回2return 2 end -- 3.4.扣库存 incrby stockKey -1 redis.call(incrby, stockKey, -1) -- 3.5.下单保存用户sadd orderKey userId redis.call(sadd, orderKey, userId) return 0VoucherOrderServiceImpl 修改下单动作现在我们去下单时是通过lua表达式去原子执行判断逻辑如果判断我出来不为0 则要么是库存不足要么是重复下单返回错误信息如果是0则把下单的逻辑保存到队列中去然后异步执行 //异步处理线程池 private static final ExecutorService SECKILL_ORDER_EXECUTOR Executors.newSingleThreadExecutor();//在类初始化之后执行因为当这个类初始化好了之后随时都是有可能要执行的 PostConstruct private void init() {SECKILL_ORDER_EXECUTOR.submit(new VoucherOrderHandler()); } // 用于线程池处理的任务 // 当初始化完毕后就会去从队列中去拿信息private class VoucherOrderHandler implements Runnable{Overridepublic void run() {while (true){try {// 1.获取队列中的订单信息VoucherOrder voucherOrder orderTasks.take();// 2.创建订单handleVoucherOrder(voucherOrder);} catch (Exception e) {log.error(处理订单异常, e);}}}private void handleVoucherOrder(VoucherOrder voucherOrder) {//1.获取用户Long userId voucherOrder.getUserId();// 2.创建锁对象RLock redisLock redissonClient.getLock(lock:order: userId);// 3.尝试获取锁boolean isLock redisLock.lock();// 4.判断是否获得锁成功if (!isLock) {// 获取锁失败直接返回失败或者重试log.error(不允许重复下单);return;}try {//注意由于是spring的事务是放在threadLocal中此时的是多线程事务会失效proxy.createVoucherOrder(voucherOrder);} finally {// 释放锁redisLock.unlock();}}private BlockingQueueVoucherOrder orderTasks new ArrayBlockingQueue(1024 * 1024);Overridepublic Result seckillVoucher(Long voucherId) {Long userId UserHolder.getUser().getId();long orderId redisIdWorker.nextId(order);// 1.执行lua脚本Long result stringRedisTemplate.execute(SECKILL_SCRIPT,Collections.emptyList(),voucherId.toString(), userId.toString(), String.valueOf(orderId));int r result.intValue();// 2.判断结果是否为0if (r ! 0) {// 2.1.不为0 代表没有购买资格return Result.fail(r 1 ? 库存不足 : 不能重复下单);}VoucherOrder voucherOrder new VoucherOrder();// 2.3.订单idlong orderId redisIdWorker.nextId(order);voucherOrder.setId(orderId);// 2.4.用户idvoucherOrder.setUserId(userId);// 2.5.代金券idvoucherOrder.setVoucherId(voucherId);// 2.6.放入阻塞队列orderTasks.add(voucherOrder);//3.获取代理对象proxy (IVoucherOrderService)AopContext.currentProxy();//4.返回订单idreturn Result.ok(orderId);}Transactionalpublic void createVoucherOrder(VoucherOrder voucherOrder) {Long userId voucherOrder.getUserId();// 5.1.查询订单int count query().eq(user_id, userId).eq(voucher_id, voucherOrder.getVoucherId()).count();// 5.2.判断是否存在if (count 0) {// 用户已经购买过了log.error(用户已经购买过了);return ;}// 6.扣减库存boolean success seckillVoucherService.update().setSql(stock stock - 1) // set stock stock - 1.eq(voucher_id, voucherOrder.getVoucherId()).gt(stock, 0) // where id ? and stock 0.update();if (!success) {// 扣减失败log.error(库存不足);return ;}save(voucherOrder);} 秒杀业务的优化思路是什么 先利用Redis完成库存余量、一人一单判断完成抢单业务再将下单业务放入阻塞队列利用独立线程异步下单基于阻塞队列的异步秒杀存在哪些问题 内存限制问题数据安全问题 Redis消息队列优化秒杀业务 什么是消息队列字面意思就是存放消息的队列。最简单的消息队列模型包括3个角色 消息队列存储和管理消息也被称为消息代理Message Broker生产者发送消息到消息队列消费者从消息队列获取消息并处理消息 基于Redis的Stream结构作为消息队列实现异步秒杀下单 需求 创建一个Stream类型的消息队列名为stream.orders修改之前的秒杀下单Lua脚本在认定有抢购资格后直接向stream.orders中添加消息内容包含voucherId、userId、orderId项目启动时开启一个线程任务尝试获取stream.orders中的消息完成下单 127.0.0.1:6379 XGROUP CREATE stream.orders g1 0 MKSTREAM OKseckill.lua脚本中添加发送到消息队列的内容 -- 1.参数列表 -- 1.1.优惠券id local voucherId ARGV[1] -- 1.2.用户id local userId ARGV[2] -- 1.3.订单id local orderId ARGV[3]-- 2.数据key -- 2.1.库存key local stockKey seckill:stock: .. voucherId -- 2.2.订单key local orderKey seckill:order: .. voucherId-- 3.脚本业务 -- 3.1.判断库存是否充足 get stockKey if(tonumber(redis.call(get, stockKey)) 0) then-- 3.2.库存不足返回1return 1 end -- 3.2.判断用户是否下单 SISMEMBER orderKey userId if(redis.call(sismember, orderKey, userId) 1) then-- 3.3.存在说明是重复下单返回2return 2 end -- 3.4.扣库存 incrby stockKey -1 redis.call(incrby, stockKey, -1) -- 3.5.下单保存用户sadd orderKey userId redis.call(sadd, orderKey, userId) -- 3.6.发送消息到队列中 XADD stream.orders * k1 v1 k2 v2 ... redis.call(xadd, stream.orders, *, userId, userId, voucherId, voucherId, id, orderId) return 0VoucherOrderServiceImpl private class VoucherOrderHandler implements Runnable {Overridepublic void run() {while (true) {try {// 1.获取消息队列中的订单信息 XREADGROUP GROUP g1 c1 COUNT 1 BLOCK 2000 STREAMS s1 ListMapRecordString, Object, Object list stringRedisTemplate.opsForStream().read(Consumer.from(g1, c1),StreamReadOptions.empty().count(1).block(Duration.ofSeconds(2)),StreamOffset.create(stream.orders, ReadOffset.lastConsumed()));// 2.判断订单信息是否为空if (list null || list.isEmpty()) {// 如果为null说明没有消息继续下一次循环continue;}// 解析数据MapRecordString, Object, Object record list.get(0);MapObject, Object value record.getValue();VoucherOrder voucherOrder BeanUtil.fillBeanWithMap(value, new VoucherOrder(), true);// 3.创建订单createVoucherOrder(voucherOrder);// 4.确认消息 XACKstringRedisTemplate.opsForStream().acknowledge(s1, g1, record.getId());} catch (Exception e) {log.error(处理订单异常, e);//处理异常消息handlePendingList();}}}private void handlePendingList() {while (true) {try {// 1.获取pending-list中的订单信息 XREADGROUP GROUP g1 c1 COUNT 1 BLOCK 2000 STREAMS s1 0ListMapRecordString, Object, Object list stringRedisTemplate.opsForStream().read(Consumer.from(g1, c1),StreamReadOptions.empty().count(1),StreamOffset.create(stream.orders, ReadOffset.from(0)));// 2.判断订单信息是否为空if (list null || list.isEmpty()) {// 如果为null说明没有异常消息结束循环break;}// 解析数据MapRecordString, Object, Object record list.get(0);MapObject, Object value record.getValue();VoucherOrder voucherOrder BeanUtil.fillBeanWithMap(value, new VoucherOrder(), true);// 3.创建订单createVoucherOrder(voucherOrder);// 4.确认消息 XACKstringRedisTemplate.opsForStream().acknowledge(s1, g1, record.getId());} catch (Exception e) {log.error(处理pendding订单异常, e);try{Thread.sleep(20);}catch(Exception e){e.printStackTrace();}}}} } 达人探店 发布探店笔记 探店笔记类似点评网站的评价往往是图文结合。对应的表有两个 tb_blog探店笔记表包含笔记中的标题、文字、图片等 tb_blog_comments其他用户对探店笔记的评价 上传接口 Slf4j RestController RequestMapping(upload) public class UploadController {PostMapping(blog)public Result uploadImage(RequestParam(file) MultipartFile image) {try {// 获取原始文件名称String originalFilename image.getOriginalFilename();// 生成新文件名String fileName createNewFileName(originalFilename);// 保存文件image.transferTo(new File(SystemConstants.IMAGE_UPLOAD_DIR, fileName));// 返回结果log.debug(文件上传成功{}, fileName);return Result.ok(fileName);} catch (IOException e) {throw new RuntimeException(文件上传失败, e);}}}点赞功能 需求 同一个用户只能点赞一次再次点击则取消点赞如果当前用户已经点赞则点赞按钮高亮显示前端已实现判断字段Blog类的isLike属性 实现步骤 给Blog类中添加一个isLike字段标示是否被当前用户点赞修改点赞功能利用Redis的set集合判断是否点赞过未点赞过则点赞数1已点赞过则点赞数-1修改根据id查询Blog的业务判断当前登录用户是否点赞过赋值给isLike字段修改分页查询Blog业务判断当前登录用户是否点赞过赋值给isLike字段 在探店笔记的详情页面应该把给该笔记点赞的人显示出来比如最早点赞的TOP5形成点赞排行榜 之前的点赞是放到set集合但是set集合是不能排序的所以这个时候咱们可以采用一个可以排序的set集合就是咱们的sortedSet 具体步骤 1、在Blog 添加一个字段 TableField(exist false) private Boolean isLike;2、修改代码 Overridepublic Result likeBlog(Long id) {// 1.获取登录用户Long userId UserHolder.getUser().getId();// 2.判断当前登录用户是否已经点赞String key BLOG_LIKED_KEY id;Double score stringRedisTemplate.opsForZSet().score(key, userId.toString());if (score null) {// 3.如果未点赞可以点赞// 3.1.数据库点赞数 1boolean isSuccess update().setSql(liked liked 1).eq(id, id).update();// 3.2.保存用户到Redis的set集合 zadd key value scoreif (isSuccess) {stringRedisTemplate.opsForZSet().add(key, userId.toString(), System.currentTimeMillis());}} else {// 4.如果已点赞取消点赞// 4.1.数据库点赞数 -1boolean isSuccess update().setSql(liked liked - 1).eq(id, id).update();// 4.2.把用户从Redis的set集合移除if (isSuccess) {stringRedisTemplate.opsForZSet().remove(key, userId.toString());}}return Result.ok();}private void isBlogLiked(Blog blog) {// 1.获取登录用户UserDTO user UserHolder.getUser();if (user null) {// 用户未登录无需查询是否点赞return;}Long userId user.getId();// 2.判断当前登录用户是否已经点赞String key blog:liked: blog.getId();Double score stringRedisTemplate.opsForZSet().score(key, userId.toString());blog.setIsLike(score ! null);}BlogService Override public Result queryBlogLikes(Long id) {String key BLOG_LIKED_KEY id;// 1.查询top5的点赞用户 zrange key 0 4SetString top5 stringRedisTemplate.opsForZSet().range(key, 0, 4);if (top5 null || top5.isEmpty()) {return Result.ok(Collections.emptyList());}// 2.解析出其中的用户idListLong ids top5.stream().map(Long::valueOf).collect(Collectors.toList());String idStr StrUtil.join(,, ids);// 3.根据用户id查询用户 WHERE id IN ( 5 , 1 ) ORDER BY FIELD(id, 5, 1)ListUserDTO userDTOS userService.query().in(id, ids).last(ORDER BY FIELD(id, idStr )).list().stream().map(user - BeanUtil.copyProperties(user, UserDTO.class)).collect(Collectors.toList());// 4.返回return Result.ok(userDTOS); }点赞排行榜显示 参考 [1] https://www.bilibili.com/video/BV1cr4y1671t?p1vd_sourcec3b6e654ba39ea63bbf8fe47e7e98899
http://www.zqtcl.cn/news/388250/

相关文章:

  • 做网站可以不用框架吗网站301做下
  • 萍乡做网站深圳市福田区住房和建设局官网
  • 网站架构需求wordpress过去指定分类文章
  • 房管局备案查询网站功能型网站开发
  • 聊城手机网站建设服务自己开网站做职称论文可以吗
  • 企业网站禁忌手机端网站开发页
  • 深圳外贸商城网站建设wordpress 空搜索
  • 做微信的网站有哪些shop商城系统
  • 网站落地页如何做优化大师免费下载安装
  • 本地计算机做网站服务器做算命网站
  • 广州网站建设公司万齐网络科技做围棋题网站
  • 运动服装商城网站建设引流推广
  • 武进区城乡建设局网站聊城商城网站建设
  • 做网站开发赚钱吗网站建设电子书资料
  • wordpress 回收站在哪个文件夹建站之星模板好吗
  • 怎么用dw做博客网站天使投资平台官网
  • 淮安市网站建设crm网站
  • 门户网站主要特点和功能深圳地铁优化
  • 银川网站推广方式湖南建工交通建设有限公司网站
  • 知道网站域名怎么联系怎么创建自己的公司网站
  • 淘宝网站开发多少金额网站优化 福州
  • 百度推广让我先做虚拟网站后进一步优化落实
  • 好的网站建设启示汕头网页设计网站方案
  • 深圳网站制作开发免费精准客户软件
  • 网站超链接用什么南宁行业平台开发公司
  • 注册门户网站襄樊seo快速排名
  • 优秀的手机网站iis 设置此网站的访问权限
  • 用nat123做自己的网站深圳市建设工程质量检测中心官网
  • 网上做衣服的网站废旧网站哪个做的最好
  • 网站开发设置网页端口wordpress 知识库