|
|
@ -17,21 +17,28 @@ import com.xjs.mall.product.service.AttrService;
|
|
|
|
import com.xjs.mall.product.service.CategoryBrandRelationService;
|
|
|
|
import com.xjs.mall.product.service.CategoryBrandRelationService;
|
|
|
|
import com.xjs.mall.product.service.CategoryService;
|
|
|
|
import com.xjs.mall.product.service.CategoryService;
|
|
|
|
import com.xjs.mall.product.vo.Catelog2Vo;
|
|
|
|
import com.xjs.mall.product.vo.Catelog2Vo;
|
|
|
|
|
|
|
|
import lombok.extern.log4j.Log4j2;
|
|
|
|
|
|
|
|
import org.redisson.api.RLock;
|
|
|
|
|
|
|
|
import org.redisson.api.RedissonClient;
|
|
|
|
import org.springframework.beans.factory.annotation.Autowired;
|
|
|
|
import org.springframework.beans.factory.annotation.Autowired;
|
|
|
|
|
|
|
|
import org.springframework.cache.annotation.CacheEvict;
|
|
|
|
|
|
|
|
import org.springframework.cache.annotation.Cacheable;
|
|
|
|
import org.springframework.data.redis.core.StringRedisTemplate;
|
|
|
|
import org.springframework.data.redis.core.StringRedisTemplate;
|
|
|
|
|
|
|
|
import org.springframework.data.redis.core.script.DefaultRedisScript;
|
|
|
|
import org.springframework.stereotype.Service;
|
|
|
|
import org.springframework.stereotype.Service;
|
|
|
|
import org.springframework.transaction.annotation.Transactional;
|
|
|
|
import org.springframework.transaction.annotation.Transactional;
|
|
|
|
|
|
|
|
|
|
|
|
import javax.annotation.Resource;
|
|
|
|
import javax.annotation.Resource;
|
|
|
|
import java.util.*;
|
|
|
|
import java.util.*;
|
|
|
|
|
|
|
|
import java.util.concurrent.TimeUnit;
|
|
|
|
import java.util.stream.Collectors;
|
|
|
|
import java.util.stream.Collectors;
|
|
|
|
|
|
|
|
|
|
|
|
import static com.xjs.consts.RedisConst.CATALOG_AFTER;
|
|
|
|
import static com.xjs.consts.RedisConst.*;
|
|
|
|
import static com.xjs.consts.RedisConst.CATALOG_BEFORE;
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
@Service("categoryService")
|
|
|
|
@Service("categoryService")
|
|
|
|
@Transactional
|
|
|
|
@Transactional
|
|
|
|
|
|
|
|
@Log4j2
|
|
|
|
public class CategoryServiceImpl extends ServiceImpl<CategoryDao, CategoryEntity> implements CategoryService {
|
|
|
|
public class CategoryServiceImpl extends ServiceImpl<CategoryDao, CategoryEntity> implements CategoryService {
|
|
|
|
|
|
|
|
|
|
|
|
@Resource
|
|
|
|
@Resource
|
|
|
@ -49,6 +56,9 @@ public class CategoryServiceImpl extends ServiceImpl<CategoryDao, CategoryEntity
|
|
|
|
@Autowired
|
|
|
|
@Autowired
|
|
|
|
private AttrService attrService;
|
|
|
|
private AttrService attrService;
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
@Resource
|
|
|
|
|
|
|
|
private RedissonClient redissonClient;
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
@Override
|
|
|
|
@Override
|
|
|
|
public List<CategoryEntity> listWithTree() {
|
|
|
|
public List<CategoryEntity> listWithTree() {
|
|
|
@ -111,7 +121,7 @@ public class CategoryServiceImpl extends ServiceImpl<CategoryDao, CategoryEntity
|
|
|
|
categoryDao.deleteBatchIds(asList);
|
|
|
|
categoryDao.deleteBatchIds(asList);
|
|
|
|
|
|
|
|
|
|
|
|
//删除后清除缓存
|
|
|
|
//删除后清除缓存
|
|
|
|
stringRedisTemplate.delete(Arrays.asList(CATALOG_BEFORE,CATALOG_AFTER));
|
|
|
|
stringRedisTemplate.delete(Arrays.asList(CATALOG_BEFORE, CATALOG_AFTER));
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
@Override
|
|
|
|
@Override
|
|
|
@ -126,6 +136,7 @@ public class CategoryServiceImpl extends ServiceImpl<CategoryDao, CategoryEntity
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
@Override
|
|
|
|
@Override
|
|
|
|
|
|
|
|
@CacheEvict(value = {"mall:catalog"},key = "'getLevel1Categorys'")
|
|
|
|
public void updateCascade(CategoryEntity category) {
|
|
|
|
public void updateCascade(CategoryEntity category) {
|
|
|
|
//更新自己
|
|
|
|
//更新自己
|
|
|
|
super.updateById(category);
|
|
|
|
super.updateById(category);
|
|
|
@ -134,10 +145,11 @@ public class CategoryServiceImpl extends ServiceImpl<CategoryDao, CategoryEntity
|
|
|
|
categoryBrandRelationService.updateCategory(category.getCatId(), category.getName());
|
|
|
|
categoryBrandRelationService.updateCategory(category.getCatId(), category.getName());
|
|
|
|
|
|
|
|
|
|
|
|
//删除缓存
|
|
|
|
//删除缓存
|
|
|
|
stringRedisTemplate.delete(Arrays.asList(CATALOG_BEFORE,CATALOG_AFTER));
|
|
|
|
stringRedisTemplate.delete(Arrays.asList(CATALOG_BEFORE, CATALOG_AFTER));
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
@Override
|
|
|
|
@Override
|
|
|
|
|
|
|
|
@Cacheable(value = {"mall:catalog"},key = "#root.method.name",sync = true) //sync同步会锁方法,单机锁
|
|
|
|
public List<CategoryEntity> getLevel1Categorys() {
|
|
|
|
public List<CategoryEntity> getLevel1Categorys() {
|
|
|
|
LambdaQueryWrapper<CategoryEntity> wrapper = new LambdaQueryWrapper<>();
|
|
|
|
LambdaQueryWrapper<CategoryEntity> wrapper = new LambdaQueryWrapper<>();
|
|
|
|
wrapper.eq(CategoryEntity::getParentCid, 0);
|
|
|
|
wrapper.eq(CategoryEntity::getParentCid, 0);
|
|
|
@ -146,14 +158,19 @@ public class CategoryServiceImpl extends ServiceImpl<CategoryDao, CategoryEntity
|
|
|
|
|
|
|
|
|
|
|
|
@Override
|
|
|
|
@Override
|
|
|
|
public Map<String, List<Catelog2Vo>> getCatalogJson() {
|
|
|
|
public Map<String, List<Catelog2Vo>> getCatalogJson() {
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
|
|
|
* 1、空结果缓存,解决缓存穿透
|
|
|
|
|
|
|
|
* 2、设置过期时间加随机值,解决缓存雪崩
|
|
|
|
|
|
|
|
* 3、加锁,解决缓存击穿
|
|
|
|
|
|
|
|
*/
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
//先查缓存
|
|
|
|
//先查缓存
|
|
|
|
String catalogJSON = stringRedisTemplate.opsForValue().get(CATALOG_BEFORE);
|
|
|
|
String catalogJSON = stringRedisTemplate.opsForValue().get(CATALOG_BEFORE);
|
|
|
|
if (StringUtils.isEmpty(catalogJSON)) {
|
|
|
|
if (StringUtils.isEmpty(catalogJSON)) {
|
|
|
|
//缓存没有查数据库并且放入
|
|
|
|
|
|
|
|
Map<String, List<Catelog2Vo>> catalogJsonFormDb = this.getCatalogJsonFormDb();
|
|
|
|
return this.getCatalogJsonFormDbWithRedissonLock();
|
|
|
|
String jsonString = JSON.toJSONString(catalogJsonFormDb);
|
|
|
|
|
|
|
|
stringRedisTemplate.opsForValue().set(CATALOG_BEFORE, jsonString);
|
|
|
|
|
|
|
|
return catalogJsonFormDb;
|
|
|
|
|
|
|
|
} else {
|
|
|
|
} else {
|
|
|
|
return JSON.parseObject(catalogJSON, new TypeReference<Map<String, List<Catelog2Vo>>>() {
|
|
|
|
return JSON.parseObject(catalogJSON, new TypeReference<Map<String, List<Catelog2Vo>>>() {
|
|
|
|
});
|
|
|
|
});
|
|
|
@ -161,13 +178,19 @@ public class CategoryServiceImpl extends ServiceImpl<CategoryDao, CategoryEntity
|
|
|
|
|
|
|
|
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
//可优化
|
|
|
|
|
|
|
|
private Map<String, List<Catelog2Vo>> getCatalogJsonFormDb() {
|
|
|
|
private Map<String, List<Catelog2Vo>> getCatalogJsonFormDb() {
|
|
|
|
|
|
|
|
//获取锁之后再去缓存确认,如果没有就继续查询
|
|
|
|
|
|
|
|
String catalogJSON = stringRedisTemplate.opsForValue().get(CATALOG_BEFORE);
|
|
|
|
|
|
|
|
if (StringUtils.isNotEmpty(catalogJSON)) {
|
|
|
|
|
|
|
|
return JSON.parseObject(catalogJSON, new TypeReference<Map<String, List<Catelog2Vo>>>() {
|
|
|
|
|
|
|
|
});
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
//查出所有1级分类
|
|
|
|
//查出所有1级分类
|
|
|
|
List<CategoryEntity> level1Categorys = getLevel1Categorys();
|
|
|
|
List<CategoryEntity> level1Categorys = getLevel1Categorys();
|
|
|
|
|
|
|
|
|
|
|
|
//封装数据
|
|
|
|
//封装数据
|
|
|
|
return level1Categorys.stream().collect(Collectors.toMap(k -> k.getCatId().toString(), v -> {
|
|
|
|
Map<String, List<Catelog2Vo>> collect = level1Categorys.stream().collect(Collectors.toMap(k -> k.getCatId().toString(), v -> {
|
|
|
|
//每一个的一级分类,查到这个一级分类的二级分类
|
|
|
|
//每一个的一级分类,查到这个一级分类的二级分类
|
|
|
|
List<CategoryEntity> categoryEntities = super.baseMapper.selectList(new LambdaQueryWrapper<CategoryEntity>().eq(CategoryEntity::getParentCid, v.getCatId()));
|
|
|
|
List<CategoryEntity> categoryEntities = super.baseMapper.selectList(new LambdaQueryWrapper<CategoryEntity>().eq(CategoryEntity::getParentCid, v.getCatId()));
|
|
|
|
|
|
|
|
|
|
|
@ -195,6 +218,83 @@ public class CategoryServiceImpl extends ServiceImpl<CategoryDao, CategoryEntity
|
|
|
|
|
|
|
|
|
|
|
|
return catelog2Vos;
|
|
|
|
return catelog2Vos;
|
|
|
|
}));
|
|
|
|
}));
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
//缓存没有查数据库并且放入
|
|
|
|
|
|
|
|
String jsonString = JSON.toJSONString(collect);
|
|
|
|
|
|
|
|
stringRedisTemplate.opsForValue().set(CATALOG_BEFORE, jsonString);
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
return collect;
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
//使用redisson分布式锁
|
|
|
|
|
|
|
|
private Map<String, List<Catelog2Vo>> getCatalogJsonFormDbWithRedissonLock() {
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
RLock lock = redissonClient.getLock(LOCK + ":catalog");
|
|
|
|
|
|
|
|
lock.lock();
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
log.info("获取分布式锁成功");
|
|
|
|
|
|
|
|
Map<String, List<Catelog2Vo>> catalogJsonFormDb;
|
|
|
|
|
|
|
|
try {
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
catalogJsonFormDb = getCatalogJsonFormDb();
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
} finally {
|
|
|
|
|
|
|
|
lock.unlock();
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
return catalogJsonFormDb;
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
//使用Java本地可重入锁synchronized
|
|
|
|
|
|
|
|
private Map<String, List<Catelog2Vo>> getCatalogJsonFormDbWithSynLock() {
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
//加锁,解决缓存击穿 (本地锁/单机锁 分布式服务锁不住)
|
|
|
|
|
|
|
|
synchronized (this) {
|
|
|
|
|
|
|
|
return this.getCatalogJsonFormDb();
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
//使用redis分布式锁
|
|
|
|
|
|
|
|
private Map<String, List<Catelog2Vo>> getCatalogJsonFormDbWithRedisLock() {
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
//加锁,解决缓存击穿 分布式锁 去redis占坑
|
|
|
|
|
|
|
|
String uuid = UUID.randomUUID().toString(); //添加唯一标识符
|
|
|
|
|
|
|
|
Boolean lock = stringRedisTemplate.opsForValue().setIfAbsent(LOCK, uuid, LOCK_EXPIRE, TimeUnit.SECONDS);
|
|
|
|
|
|
|
|
if (Boolean.TRUE.equals(lock)) {
|
|
|
|
|
|
|
|
log.info("获取分布式锁成功:{}", uuid);
|
|
|
|
|
|
|
|
Map<String, List<Catelog2Vo>> catalogJsonFormDb = null;
|
|
|
|
|
|
|
|
try {
|
|
|
|
|
|
|
|
catalogJsonFormDb = this.getCatalogJsonFormDb();
|
|
|
|
|
|
|
|
} finally {
|
|
|
|
|
|
|
|
//lua脚本解锁
|
|
|
|
|
|
|
|
String script = "if redis.call('get', KEYS[1]) == ARGV[1] then return redis.call('del', KEYS[1]) else return 0 end";
|
|
|
|
|
|
|
|
Long ret = stringRedisTemplate.execute(new DefaultRedisScript<Long>(script, Long.class), Arrays.asList(LOCK), uuid);
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
//加锁成功
|
|
|
|
|
|
|
|
//设置过期时间必须和加锁同步(原子性)
|
|
|
|
|
|
|
|
//stringRedisTemplate.expire(LOCK, LOCK_EXPIRE, TimeUnit.SECONDS);
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
//删除锁(判断是否是自己的锁)
|
|
|
|
|
|
|
|
/*String lockValue = stringRedisTemplate.opsForValue().get(LOCK);
|
|
|
|
|
|
|
|
if (uuid.equals(lockValue)) {
|
|
|
|
|
|
|
|
stringRedisTemplate.delete(LOCK);
|
|
|
|
|
|
|
|
}*/
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
return catalogJsonFormDb;
|
|
|
|
|
|
|
|
} else {
|
|
|
|
|
|
|
|
log.warn("获取分布式锁失败:{}", uuid);
|
|
|
|
|
|
|
|
//加锁失败...重试 自旋
|
|
|
|
|
|
|
|
try {
|
|
|
|
|
|
|
|
Thread.sleep(100);
|
|
|
|
|
|
|
|
} catch (InterruptedException e) {
|
|
|
|
|
|
|
|
e.printStackTrace();
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
return this.getCatalogJsonFormDbWithRedisLock();
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
//225,25,2
|
|
|
|
//225,25,2
|
|
|
|