|
@@ -3,31 +3,33 @@ package com.ylx.home.hot.service.impl;
|
|
|
import cn.hutool.core.collection.CollUtil;
|
|
import cn.hutool.core.collection.CollUtil;
|
|
|
import cn.hutool.core.util.ObjectUtil;
|
|
import cn.hutool.core.util.ObjectUtil;
|
|
|
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
|
|
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
|
|
|
-import com.ylx.common.core.domain.R;
|
|
|
|
|
|
|
+import com.google.common.collect.Lists;
|
|
|
import com.ylx.common.core.domain.entity.SysDictData;
|
|
import com.ylx.common.core.domain.entity.SysDictData;
|
|
|
import com.ylx.common.utils.DictUtils;
|
|
import com.ylx.common.utils.DictUtils;
|
|
|
import com.ylx.common.utils.DistanceUtil;
|
|
import com.ylx.common.utils.DistanceUtil;
|
|
|
import com.ylx.home.hot.domain.dto.HotRecommendDTO;
|
|
import com.ylx.home.hot.domain.dto.HotRecommendDTO;
|
|
|
import com.ylx.home.hot.domain.vo.HotRecommendVO;
|
|
import com.ylx.home.hot.domain.vo.HotRecommendVO;
|
|
|
-//import com.ylx.home.hot.domain.vo.ProjectSalesVO;
|
|
|
|
|
|
|
+import com.ylx.home.hot.domain.vo.ProjectSalesVO;
|
|
|
import com.ylx.home.hot.service.HomeHotRecommendService;
|
|
import com.ylx.home.hot.service.HomeHotRecommendService;
|
|
|
import com.ylx.massage.domain.MaTechnician;
|
|
import com.ylx.massage.domain.MaTechnician;
|
|
|
-import com.ylx.massage.mapper.MaTechnicianMapper;
|
|
|
|
|
import com.ylx.massage.service.IMaTechnicianService;
|
|
import com.ylx.massage.service.IMaTechnicianService;
|
|
|
import com.ylx.merchant.domain.vo.MerchantWithAddressVO;
|
|
import com.ylx.merchant.domain.vo.MerchantWithAddressVO;
|
|
|
import com.ylx.project.domain.Project;
|
|
import com.ylx.project.domain.Project;
|
|
|
-import com.ylx.project.mapper.ProjectMapper;
|
|
|
|
|
import com.ylx.project.service.ProjectService;
|
|
import com.ylx.project.service.ProjectService;
|
|
|
import lombok.extern.slf4j.Slf4j;
|
|
import lombok.extern.slf4j.Slf4j;
|
|
|
|
|
+import org.apache.commons.collections4.CollectionUtils;
|
|
|
|
|
+import org.springframework.data.redis.connection.ReturnType;
|
|
|
|
|
+import org.springframework.data.redis.core.DefaultTypedTuple;
|
|
|
|
|
+import org.springframework.data.redis.core.RedisCallback;
|
|
|
import org.springframework.data.redis.core.RedisTemplate;
|
|
import org.springframework.data.redis.core.RedisTemplate;
|
|
|
|
|
+import org.springframework.data.redis.core.ZSetOperations;
|
|
|
import org.springframework.stereotype.Service;
|
|
import org.springframework.stereotype.Service;
|
|
|
|
|
+import org.springframework.transaction.annotation.Transactional;
|
|
|
|
|
|
|
|
import javax.annotation.Resource;
|
|
import javax.annotation.Resource;
|
|
|
import java.math.BigDecimal;
|
|
import java.math.BigDecimal;
|
|
|
-import java.util.ArrayList;
|
|
|
|
|
-import java.util.Arrays;
|
|
|
|
|
-import java.util.List;
|
|
|
|
|
-import java.util.Set;
|
|
|
|
|
|
|
+import java.time.Duration;
|
|
|
|
|
+import java.util.*;
|
|
|
import java.util.stream.Collectors;
|
|
import java.util.stream.Collectors;
|
|
|
|
|
|
|
|
import static com.ylx.order.constant.OrderConstant.DICT_UNIT_TYPE;
|
|
import static com.ylx.order.constant.OrderConstant.DICT_UNIT_TYPE;
|
|
@@ -42,23 +44,31 @@ public class HomeHotRecommendServiceImpl implements HomeHotRecommendService {
|
|
|
private ProjectService projectService;
|
|
private ProjectService projectService;
|
|
|
@Resource
|
|
@Resource
|
|
|
private IMaTechnicianService maTechnicianService;
|
|
private IMaTechnicianService maTechnicianService;
|
|
|
- @Resource
|
|
|
|
|
- private MaTechnicianMapper techMapper;
|
|
|
|
|
- @Resource
|
|
|
|
|
- private ProjectMapper projectMapper;
|
|
|
|
|
|
|
|
|
|
private static final String HOT_MERCHANT_RANK_KEY = "hot:merchant:rank";
|
|
private static final String HOT_MERCHANT_RANK_KEY = "hot:merchant:rank";
|
|
|
private static final String HOT_PROJECT_RANK_KEY = "hot:project:rank";
|
|
private static final String HOT_PROJECT_RANK_KEY = "hot:project:rank";
|
|
|
|
|
|
|
|
|
|
+ private static final String SYNC_LOCK_KEY = "lock:hot_rank_sync";
|
|
|
|
|
+ private static final int BATCH_SIZE = 200;
|
|
|
|
|
+
|
|
|
|
|
+ private static final Map<String, String> unitDictCache = new HashMap<>();
|
|
|
|
|
+
|
|
|
|
|
+ static {
|
|
|
|
|
+ List<SysDictData> dictList = DictUtils.getSortedDictCache(DICT_UNIT_TYPE);
|
|
|
|
|
+ if (CollUtil.isNotEmpty(dictList)) {
|
|
|
|
|
+ dictList.forEach(dict -> unitDictCache.put(dict.getDictValue(), dict.getDictLabel()));
|
|
|
|
|
+ } else {
|
|
|
|
|
+ log.warn("项目计量单位字典未加载,请检查字典配置:{}", DICT_UNIT_TYPE);
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
@Override
|
|
@Override
|
|
|
public Page<HotRecommendVO> getHotRecommendPage(HotRecommendDTO dto) {
|
|
public Page<HotRecommendVO> getHotRecommendPage(HotRecommendDTO dto) {
|
|
|
-
|
|
|
|
|
long pageSize = dto.getSize();
|
|
long pageSize = dto.getSize();
|
|
|
long pageNum = dto.getCurrent();
|
|
long pageNum = dto.getCurrent();
|
|
|
BigDecimal userLng = dto.getLongitude();
|
|
BigDecimal userLng = dto.getLongitude();
|
|
|
BigDecimal userLat = dto.getLatitude();
|
|
BigDecimal userLat = dto.getLatitude();
|
|
|
|
|
|
|
|
-
|
|
|
|
|
long offset = (pageNum - 1) * pageSize;
|
|
long offset = (pageNum - 1) * pageSize;
|
|
|
// 优化:多预取一倍ID,解决交替翻页断层
|
|
// 优化:多预取一倍ID,解决交替翻页断层
|
|
|
long fetchCount = pageSize * 2;
|
|
long fetchCount = pageSize * 2;
|
|
@@ -71,7 +81,7 @@ public class HomeHotRecommendServiceImpl implements HomeHotRecommendService {
|
|
|
Set<String> techIdStrSet = redisTemplate.opsForZSet().reverseRange(HOT_MERCHANT_RANK_KEY, offset, end);
|
|
Set<String> techIdStrSet = redisTemplate.opsForZSet().reverseRange(HOT_MERCHANT_RANK_KEY, offset, end);
|
|
|
Set<String> projectIdStrSet = redisTemplate.opsForZSet().reverseRange(HOT_PROJECT_RANK_KEY, offset, end);
|
|
Set<String> projectIdStrSet = redisTemplate.opsForZSet().reverseRange(HOT_PROJECT_RANK_KEY, offset, end);
|
|
|
|
|
|
|
|
- // 转Long ID集合
|
|
|
|
|
|
|
+ // 转ID集合
|
|
|
List<Integer> techIds = techIdStrSet.stream().map(Integer::valueOf).collect(Collectors.toList());
|
|
List<Integer> techIds = techIdStrSet.stream().map(Integer::valueOf).collect(Collectors.toList());
|
|
|
List<Integer> projectIds = projectIdStrSet.stream().map(Integer::valueOf).collect(Collectors.toList());
|
|
List<Integer> projectIds = projectIdStrSet.stream().map(Integer::valueOf).collect(Collectors.toList());
|
|
|
|
|
|
|
@@ -80,9 +90,7 @@ public class HomeHotRecommendServiceImpl implements HomeHotRecommendService {
|
|
|
projectList = this.projectService.listByIds(projectIds);
|
|
projectList = this.projectService.listByIds(projectIds);
|
|
|
|
|
|
|
|
// 3. 内存二次过滤脏数据(缓存不一致兜底)
|
|
// 3. 内存二次过滤脏数据(缓存不一致兜底)
|
|
|
- techWithAddrList = techWithAddrList.stream()
|
|
|
|
|
- .filter(t -> t.getNStar() != null)
|
|
|
|
|
- .collect(Collectors.toList());
|
|
|
|
|
|
|
+ techWithAddrList = techWithAddrList.stream().filter(t -> t.getNStar() != null).collect(Collectors.toList());
|
|
|
projectList = projectList.stream().filter(p -> p.getIsDelete() == 0 && p.getStatus() == 0).collect(Collectors.toList());
|
|
projectList = projectList.stream().filter(p -> p.getIsDelete() == 0 && p.getStatus() == 0).collect(Collectors.toList());
|
|
|
} catch (Exception e) {
|
|
} catch (Exception e) {
|
|
|
// Redis异常降级,切回纯MySQL分页方案
|
|
// Redis异常降级,切回纯MySQL分页方案
|
|
@@ -98,38 +106,38 @@ public class HomeHotRecommendServiceImpl implements HomeHotRecommendService {
|
|
|
// 5. 分页总数
|
|
// 5. 分页总数
|
|
|
Long totalTech, totalProject;
|
|
Long totalTech, totalProject;
|
|
|
if (!redisDown) {
|
|
if (!redisDown) {
|
|
|
- totalTech = redisTemplate.opsForZSet().zCard(HOT_MERCHANT_RANK_KEY);
|
|
|
|
|
- totalProject = redisTemplate.opsForZSet().zCard(HOT_PROJECT_RANK_KEY);
|
|
|
|
|
|
|
+ totalTech = ObjectUtil.defaultIfNull(redisTemplate.opsForZSet().zCard(HOT_MERCHANT_RANK_KEY), 0L);
|
|
|
|
|
+ totalProject = ObjectUtil.defaultIfNull(redisTemplate.opsForZSet().zCard(HOT_PROJECT_RANK_KEY), 0L);
|
|
|
} else {
|
|
} else {
|
|
|
totalTech = this.maTechnicianService.countValidTech();
|
|
totalTech = this.maTechnicianService.countValidTech();
|
|
|
totalProject = this.projectService.countValidProject();
|
|
totalProject = this.projectService.countValidProject();
|
|
|
}
|
|
}
|
|
|
Page<HotRecommendVO> page = new Page<>(dto.getCurrent(), dto.getSize());
|
|
Page<HotRecommendVO> page = new Page<>(dto.getCurrent(), dto.getSize());
|
|
|
page.setRecords(mixResult);
|
|
page.setRecords(mixResult);
|
|
|
|
|
+ // 注意:混合交替分页total仅作展示,请勿用于前端分页页码计算,存在数据穿插断层
|
|
|
page.setTotal(totalTech + totalProject);
|
|
page.setTotal(totalTech + totalProject);
|
|
|
return page;
|
|
return page;
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
@Override
|
|
@Override
|
|
|
public void syncAllHotRank() {
|
|
public void syncAllHotRank() {
|
|
|
- /*redisTemplate.delete(HOT_MERCHANT_RANK_KEY);
|
|
|
|
|
- List<MaTechnician> validTechList = techMapper.selectAllValidTech();
|
|
|
|
|
- for (MaTechnician tech : validTechList) {
|
|
|
|
|
- double score = tech.getNNum() != null ? tech.getNNum() : 0;
|
|
|
|
|
- redisTemplate.opsForZSet().add(HOT_MERCHANT_RANK_KEY, tech.getId().toString(), score);
|
|
|
|
|
|
|
+ long start = System.currentTimeMillis();
|
|
|
|
|
+ // 锁超时10分钟,适配大批量同步
|
|
|
|
|
+ Boolean locked = redisTemplate.opsForValue().setIfAbsent(SYNC_LOCK_KEY, "1", Duration.ofMinutes(10));
|
|
|
|
|
+ if (!Boolean.TRUE.equals(locked)) {
|
|
|
|
|
+ log.warn("热门排行同步任务正在执行中,本次跳过");
|
|
|
|
|
+ return;
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
- redisTemplate.delete(HOT_PROJECT_RANK_KEY);
|
|
|
|
|
- List<ProjectSalesVO> allProjectSales = projectMapper.selectAllProjectSalesCount();
|
|
|
|
|
- for (ProjectSalesVO vo : allProjectSales) {
|
|
|
|
|
- Project project = new Project();
|
|
|
|
|
- project.setId(vo.getId());
|
|
|
|
|
- project.setSalesCompleted(vo.getSalesCount());
|
|
|
|
|
- projectMapper.updateById(project);
|
|
|
|
|
- long salesCount = vo.getSalesCount() != null ? vo.getSalesCount() : 0L;
|
|
|
|
|
- redisTemplate.opsForZSet().add(HOT_PROJECT_RANK_KEY, vo.getId().toString(), salesCount);
|
|
|
|
|
|
|
+ try {
|
|
|
|
|
+ syncMerchantRank();
|
|
|
|
|
+ calibrateAndSyncProjectRank();
|
|
|
|
|
+ log.info("热门排行ZSet全量同步完成,总耗时{}ms", System.currentTimeMillis() - start);
|
|
|
|
|
+ } catch (Exception e) {
|
|
|
|
|
+ log.error("热门排行同步异常,总耗时{}ms", System.currentTimeMillis() - start, e);
|
|
|
|
|
+ } finally {
|
|
|
|
|
+ releaseLock(SYNC_LOCK_KEY, "1");
|
|
|
}
|
|
}
|
|
|
- log.info("热门排行ZSet全量同步完成,商户数量:{}, 项目数量:{}", validTechList.size(), allProjectSales.size());*/
|
|
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
/**
|
|
@@ -160,25 +168,19 @@ public class HomeHotRecommendServiceImpl implements HomeHotRecommendService {
|
|
|
|
|
|
|
|
/**
|
|
/**
|
|
|
* 商户实体转VO(包含距离计算、标签拆分)
|
|
* 商户实体转VO(包含距离计算、标签拆分)
|
|
|
- *
|
|
|
|
|
- * @param tech 商户实体
|
|
|
|
|
- * @param userLat 用户纬度
|
|
|
|
|
- * @param userLng 用户经度
|
|
|
|
|
- * @return 商户卡片VO
|
|
|
|
|
*/
|
|
*/
|
|
|
private HotRecommendVO convertTechToVO(MerchantWithAddressVO tech, BigDecimal userLat, BigDecimal userLng) {
|
|
private HotRecommendVO convertTechToVO(MerchantWithAddressVO tech, BigDecimal userLat, BigDecimal userLng) {
|
|
|
HotRecommendVO vo = new HotRecommendVO();
|
|
HotRecommendVO vo = new HotRecommendVO();
|
|
|
vo.setCardType(1);
|
|
vo.setCardType(1);
|
|
|
vo.setTechId(tech.getId());
|
|
vo.setTechId(tech.getId());
|
|
|
- // 封面优先取形象照,无则头像
|
|
|
|
|
vo.setCoverImg(tech.getAvatar() != null ? tech.getAvatar() : tech.getTeAvatar());
|
|
vo.setCoverImg(tech.getAvatar() != null ? tech.getAvatar() : tech.getTeAvatar());
|
|
|
vo.setTeNickName(tech.getTeNickName());
|
|
vo.setTeNickName(tech.getTeNickName());
|
|
|
vo.setNStar(tech.getNStar());
|
|
vo.setNStar(tech.getNStar());
|
|
|
vo.setServiceTotal(tech.getNNum());
|
|
vo.setServiceTotal(tech.getNNum());
|
|
|
- // 计算距离
|
|
|
|
|
|
|
+ // 距离兜底
|
|
|
String distance = DistanceUtil.formatDisplay(userLat, userLng, tech.getLatitude(), tech.getLongitude());
|
|
String distance = DistanceUtil.formatDisplay(userLat, userLng, tech.getLatitude(), tech.getLongitude());
|
|
|
- vo.setDistance(distance);
|
|
|
|
|
- // 拆分标签字符串为集合 te_project="调理,SPA"
|
|
|
|
|
|
|
+ vo.setDistance(ObjectUtil.isNotEmpty(distance) ? distance : "距离未知");
|
|
|
|
|
+
|
|
|
String tagStr = tech.getTeProject();
|
|
String tagStr = tech.getTeProject();
|
|
|
if (tagStr != null && tagStr.trim().length() > 0) {
|
|
if (tagStr != null && tagStr.trim().length() > 0) {
|
|
|
List<String> tagList = Arrays.stream(tagStr.split(","))
|
|
List<String> tagList = Arrays.stream(tagStr.split(","))
|
|
@@ -192,9 +194,6 @@ public class HomeHotRecommendServiceImpl implements HomeHotRecommendService {
|
|
|
|
|
|
|
|
/**
|
|
/**
|
|
|
* 项目实体转卡片VO
|
|
* 项目实体转卡片VO
|
|
|
- *
|
|
|
|
|
- * @param project 项目实体
|
|
|
|
|
- * @return 项目卡片VO
|
|
|
|
|
*/
|
|
*/
|
|
|
private HotRecommendVO convertProjectToVO(Project project) {
|
|
private HotRecommendVO convertProjectToVO(Project project) {
|
|
|
HotRecommendVO vo = new HotRecommendVO();
|
|
HotRecommendVO vo = new HotRecommendVO();
|
|
@@ -205,21 +204,95 @@ public class HomeHotRecommendServiceImpl implements HomeHotRecommendService {
|
|
|
vo.setHighlight(project.getHighlight());
|
|
vo.setHighlight(project.getHighlight());
|
|
|
vo.setPriceMin(project.getPriceMin());
|
|
vo.setPriceMin(project.getPriceMin());
|
|
|
vo.setSalesCount(project.getSalesCompleted());
|
|
vo.setSalesCount(project.getSalesCompleted());
|
|
|
- // 组装时长展示文案:standard_duration + unit_type
|
|
|
|
|
- String durationText;
|
|
|
|
|
|
|
+
|
|
|
Integer unitType = project.getUnitType();
|
|
Integer unitType = project.getUnitType();
|
|
|
- String unitTypeName = "未知单位";
|
|
|
|
|
- List<SysDictData> dictList = DictUtils.getSortedDictCache(DICT_UNIT_TYPE);
|
|
|
|
|
- if (CollUtil.isNotEmpty(dictList) && ObjectUtil.isNotNull(unitType)) {
|
|
|
|
|
- unitTypeName = dictList.stream()
|
|
|
|
|
- .filter(dict -> unitType.toString().equals(dict.getDictValue()))
|
|
|
|
|
- .map(SysDictData::getDictLabel)
|
|
|
|
|
- .findFirst()
|
|
|
|
|
- .orElse("未知单位");
|
|
|
|
|
- }
|
|
|
|
|
- durationText = project.getStandardDuration() + unitTypeName;
|
|
|
|
|
|
|
+ String unitKey = ObjectUtil.isNotNull(unitType) ? unitType.toString() : "";
|
|
|
|
|
+ String unitTypeName = unitDictCache.getOrDefault(unitKey, "未知单位");
|
|
|
|
|
+ // 时长空值兜底
|
|
|
|
|
+ Integer duration = ObjectUtil.defaultIfNull(project.getStandardDuration(), 0);
|
|
|
|
|
+ String durationText = duration + unitTypeName;
|
|
|
vo.setDurationText(durationText);
|
|
vo.setDurationText(durationText);
|
|
|
return vo;
|
|
return vo;
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
-}
|
|
|
|
|
|
|
+ /**
|
|
|
|
|
+ * 同步商户排行ZSet
|
|
|
|
|
+ */
|
|
|
|
|
+ private void syncMerchantRank() {
|
|
|
|
|
+ List<MaTechnician> validTechList = maTechnicianService.selectAllValidTech();
|
|
|
|
|
+ Map<String, Double> scores = validTechList.stream()
|
|
|
|
|
+ .collect(Collectors.toMap(
|
|
|
|
|
+ t -> String.valueOf(t.getId()),
|
|
|
|
|
+ t -> t.getNNum() != null ? t.getNNum().doubleValue() : 0D,
|
|
|
|
|
+ (a, b) -> a
|
|
|
|
|
+ ));
|
|
|
|
|
+ replaceRankZSet(HOT_MERCHANT_RANK_KEY, scores);
|
|
|
|
|
+ log.info("商户热门排行同步完成,数量:{}", scores.size());
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ /**
|
|
|
|
|
+ * DB校准项目销量 + 同步项目排行ZSet
|
|
|
|
|
+ */
|
|
|
|
|
+ private void calibrateAndSyncProjectRank() {
|
|
|
|
|
+ // 查询最新销量(事务外读取,缩短事务持有时间)
|
|
|
|
|
+ List<ProjectSalesVO> allSales = this.projectService.selectAllProjectSalesCount();
|
|
|
|
|
+ // DB批量校准(独立事务)
|
|
|
|
|
+ batchCalibrateProjectSales(allSales);
|
|
|
|
|
+
|
|
|
|
|
+ // Redis同步,异常隔离不影响DB数据
|
|
|
|
|
+ try {
|
|
|
|
|
+ Map<String, Double> scores = allSales.stream()
|
|
|
|
|
+ .collect(Collectors.toMap(
|
|
|
|
|
+ vo -> String.valueOf(vo.getId()),
|
|
|
|
|
+ vo -> vo.getSalesCount() != null ? vo.getSalesCount().doubleValue() : 0D,
|
|
|
|
|
+ (a, b) -> a
|
|
|
|
|
+ ));
|
|
|
|
|
+ replaceRankZSet(HOT_PROJECT_RANK_KEY, scores);
|
|
|
|
|
+ log.info("项目热门排行同步完成,数量:{}", scores.size());
|
|
|
|
|
+ } catch (Exception e) {
|
|
|
|
|
+ log.error("项目热门排行Redis同步失败,DB已校准完成,等待下次定时任务修复缓存", e);
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ /**
|
|
|
|
|
+ * 独立事务:批量更新项目销量冗余字段
|
|
|
|
|
+ */
|
|
|
|
|
+ @Transactional(rollbackFor = Exception.class)
|
|
|
|
|
+ public void batchCalibrateProjectSales(List<ProjectSalesVO> salesList) {
|
|
|
|
|
+ if (CollectionUtils.isEmpty(salesList)) return;
|
|
|
|
|
+ Lists.partition(salesList, BATCH_SIZE).forEach(batch -> this.projectService.batchUpdateSales(batch));
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ /**
|
|
|
|
|
+ * 通用方法:原子替换排行ZSet,临时key中转避免线上空白列表
|
|
|
|
|
+ */
|
|
|
|
|
+ private void replaceRankZSet(String rankKey, Map<String, Double> scores) {
|
|
|
|
|
+ String tempKey = rankKey + ":temp";
|
|
|
|
|
+ redisTemplate.delete(tempKey);
|
|
|
|
|
+ if (scores.isEmpty()) {
|
|
|
|
|
+ redisTemplate.delete(rankKey);
|
|
|
|
|
+ return;
|
|
|
|
|
+ }
|
|
|
|
|
+ Set<ZSetOperations.TypedTuple<String>> tuples = scores.entrySet().stream()
|
|
|
|
|
+ .map(entry -> new DefaultTypedTuple<>(entry.getKey(), entry.getValue()))
|
|
|
|
|
+ .collect(Collectors.toSet());
|
|
|
|
|
+ redisTemplate.opsForZSet().add(tempKey, tuples);
|
|
|
|
|
+ redisTemplate.rename(tempKey, rankKey);
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ /**
|
|
|
|
|
+ * Lua脚本原子释放分布式锁,防止误删其他线程锁
|
|
|
|
|
+ */
|
|
|
|
|
+ private void releaseLock(String lockKey, String expectedValue) {
|
|
|
|
|
+ String script = "if redis.call('get', KEYS[1]) == ARGV[1] then return redis.call('del', KEYS[1]) else return 0 end";
|
|
|
|
|
+ try {
|
|
|
|
|
+ redisTemplate.execute((RedisCallback<Object>) connection -> {
|
|
|
|
|
+ byte[] keyBytes = redisTemplate.getStringSerializer().serialize(lockKey);
|
|
|
|
|
+ byte[] valBytes = redisTemplate.getStringSerializer().serialize(expectedValue);
|
|
|
|
|
+ return connection.eval(script.getBytes(), ReturnType.INTEGER, 1, keyBytes, valBytes);
|
|
|
|
|
+ });
|
|
|
|
|
+ } catch (Exception e) {
|
|
|
|
|
+ log.error("释放分布式锁失败,key={}", lockKey, e);
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+}
|