一 springDataRedis 直接使用redis
1 maven配置
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
2 配置 application.yaml
spring:
data:
redis:
host: 192.168.1.44
port: 6379
database: 0
password: 19910129
timeout: 10s
lettuce:
pool:
min-idle: 0
max-idle: 8
max-active: 8
max-wait: -1ms
3 创建配置文件
@SuppressWarnings("deprecation")
@Configuration
@EnableCaching
public class RedisConfig extends CachingConfigurerSupport
{
@Bean
@SuppressWarnings(value = { "unchecked", "rawtypes" })
public RedisTemplate<Object, Object> redisTemplate(RedisConnectionFactory connectionFactory)
{
RedisTemplate<Object, Object> template = new RedisTemplate<>();
template.setConnectionFactory(connectionFactory);
FastJson2JsonRedisSerializer serializer = new FastJson2JsonRedisSerializer(Object.class);
template.setKeySerializer(new StringRedisSerializer());
template.setValueSerializer(serializer);
template.setHashKeySerializer(new StringRedisSerializer());
template.setHashValueSerializer(serializer);
template.afterPropertiesSet();
return template;
}
@Bean
public DefaultRedisScript<Long> limitScript()
{
DefaultRedisScript<Long> redisScript = new DefaultRedisScript<>();
redisScript.setScriptText(limitScriptText());
redisScript.setResultType(Long.class);
return redisScript;
}
private String limitScriptText()
{
return "local key = KEYS[1]\n" +
"local count = tonumber(ARGV[1])\n" +
"local time = tonumber(ARGV[2])\n" +
"local current = redis.call('get', key);\n" +
"if current and tonumber(current) > count then\n" +
" return tonumber(current);\n" +
"end\n" +
"current = redis.call('incr', key)\n" +
"if tonumber(current) == 1 then\n" +
" redis.call('expire', key, time)\n" +
"end\n" +
"return tonumber(current);";
}
}
4 封装直接使用redis 的方法
@SuppressWarnings(value = { "unchecked", "rawtypes" })
@Component
public class RedisCache
{
@Autowired
public RedisTemplate redisTemplate;
public <T> void setCacheObject(final String key, final T value)
{
redisTemplate.opsForValue().set(key, value);
}
public <T> void setCacheObject(final String key, final T value, final Integer timeout, final TimeUnit timeUnit)
{
redisTemplate.opsForValue().set(key, value, timeout, timeUnit);
}
public boolean expire(final String key, final long timeout)
{
return expire(key, timeout, TimeUnit.SECONDS);
}
public boolean expire(final String key, final long timeout, final TimeUnit unit)
{
return redisTemplate.expire(key, timeout, unit);
}
public Long getExpire(final String key)
{
return redisTemplate.getExpire(key, TimeUnit.MINUTES);
}
public Boolean hasKey(String key)
{
return redisTemplate.hasKey(key);
}
public <T> T getCacheObject(final String key)
{
if (StringUtils.isEmpty(key)){
return null;
}
ValueOperations<String, T> operation = redisTemplate.opsForValue();
return operation.get(key);
}
public boolean deleteObject(final String key)
{
return redisTemplate.delete(key);
}
public boolean deleteObject(final Collection collection)
{
return redisTemplate.delete(collection) > 0;
}
public <T> long setCacheList(final String key, final List<T> dataList)
{
Long count = redisTemplate.opsForList().rightPushAll(key, dataList);
return count == null ? 0 : count;
}
public <T> List<T> getCacheList(final String key)
{
return redisTemplate.opsForList().range(key, 0, -1);
}
public <T> BoundSetOperations<String, T> setCacheSet(final String key, final Set<T> dataSet)
{
BoundSetOperations<String, T> setOperation = redisTemplate.boundSetOps(key);
Iterator<T> it = dataSet.iterator();
while (it.hasNext())
{
setOperation.add(it.next());
}
return setOperation;
}
public <T> Set<T> getCacheSet(final String key)
{
return redisTemplate.opsForSet().members(key);
}
public <T> void setCacheMap(final String key, final Map<String, T> dataMap)
{
if (dataMap != null) {
redisTemplate.opsForHash().putAll(key, dataMap);
}
}
public <T> Map<String, T> getCacheMap(final String key)
{
return redisTemplate.opsForHash().entries(key);
}
public <T> void setCacheMapValue(final String key, final String hKey, final T value)
{
redisTemplate.opsForHash().put(key, hKey, value);
}
public <T> T getCacheMapValue(final String key, final String hKey)
{
HashOperations<String, String, T> opsForHash = redisTemplate.opsForHash();
return opsForHash.get(key, hKey);
}
public <T> List<T> getMultiCacheMapValue(final String key, final Collection<Object> hKeys)
{
return redisTemplate.opsForHash().multiGet(key, hKeys);
}
public boolean deleteCacheMapValue(final String key, final String hKey)
{
return redisTemplate.opsForHash().delete(key, hKey) > 0;
}
public Collection<String> keys(final String pattern)
{
return redisTemplate.keys(pattern);
}
}
5 业务使用redis
@Component
public class TokenService
{
private static final Logger log = LoggerFactory.getLogger(TokenService.class);
@Value("${token.header}")
private String header;
@Value("${token.secret}")
private String secret;
@Value("${token.expireTime}")
private int expireTime;
@Value("${token.soloLogin}")
private boolean soloLogin;
protected static final long MILLIS_SECOND = 1000;
protected static final long MILLIS_MINUTE = 60 * MILLIS_SECOND;
private static final Long MILLIS_MINUTE_TEN = 20 * 60 * 1000L;
@Autowired
private RedisCache redisCache;
private String getTokenKey(String token)
{
return CacheConstants.LOGIN_TOKEN_KEY + token;
}
private String getUserIdKey(Long userId)
{
return CacheConstants.LOGIN_USERID_KEY + userId;
}
public String getTokenInTokenKey(String userKey)
{
if (StringUtils.isEmpty(userKey))
{
return null;
}
return userKey.replaceFirst(CacheConstants.LOGIN_TOKEN_KEY, "");
}
private String getRemoteLoginKey(String token)
{
return CacheConstants.REMOTE_LOGIN_KEY + token;
}
private String getForceQuitKey(String token)
{
return CacheConstants.FORCE_QUIT_KEY + token;
}
public LoginUser getLoginUser(HttpServletRequest request)
{
String token = getToken(request);
if (StringUtils.isNotEmpty(token))
{
try
{
Claims claims = parseToken(token);
String uuid = (String) claims.get(Constants.LOGIN_USER_KEY);
String userKey = getTokenKey(uuid);
LoginUser user = redisCache.getCacheObject(userKey);
return user;
}
catch (Exception e)
{
log.error("获取用户信息异常:'{}'", e.getMessage());
return null;
}
}
return null;
}
public String getUuid(HttpServletRequest request)
{
String token = getToken(request);
if (StringUtils.isNotEmpty(token))
{
try
{
Claims claims = parseToken(token);
return (String) claims.get(Constants.LOGIN_USER_KEY);
}
catch (Exception e)
{
log.error("获取用户信息异常:'{}'", e.getMessage());
return null;
}
}
return null;
}
public void setLoginUser(LoginUser loginUser)
{
if (StringUtils.isNotNull(loginUser) && StringUtils.isNotEmpty(loginUser.getToken()))
{
refreshToken2(loginUser);
}
}
public String createToken(LoginUser loginUser)
{
String token = IdUtils.fastUUID();
loginUser.setToken(token);
setUserAgent(loginUser);
refreshToken2(loginUser);
Map<String, Object> claims = new HashMap<>();
claims.put(Constants.LOGIN_USER_KEY, token);
return generateToken(claims);
}
private String generateToken(Map<String, Object> claims)
{
String token = Jwts.builder()
.setClaims(claims)
.signWith(SignatureAlgorithm.HS512, secret).compact();
return token;
}
private String getToken(HttpServletRequest request)
{
String token = request.getHeader(header);
if (StringUtils.isNotEmpty(token) && token.startsWith(Constants.TOKEN_PREFIX))
{
token = token.replace(Constants.TOKEN_PREFIX, "");
}
return token;
}
private Claims parseToken(String token)
{
return Jwts.parser()
.setSigningKey(secret)
.parseClaimsJws(token)
.getBody();
}
public String getUsernameFromToken(String token)
{
Claims claims = parseToken(token);
return claims.getSubject();
}
public void verifyToken(LoginUser loginUser)
{
long expireTime = loginUser.getExpireTime();
long currentTime = System.currentTimeMillis();
if (expireTime - currentTime <= MILLIS_MINUTE_TEN)
{
refreshToken2(loginUser);
}
}
public void refreshToken2(LoginUser loginUser)
{
loginUser.setLoginTime(System.currentTimeMillis());
loginUser.setExpireTime(loginUser.getLoginTime() + expireTime * MILLIS_MINUTE);
String userKey = getTokenKey(loginUser.getToken());
redisCache.setCacheObject(userKey, loginUser, expireTime, TimeUnit.MINUTES);
if (!soloLogin)
{
String userIdKey = getUserIdKey(loginUser.getUser().getUserId());
redisCache.setCacheObject(userIdKey, userKey, expireTime, TimeUnit.MINUTES);
}
}
public void refreshToken(LoginUser loginUser)
{
loginUser.setLoginTime(System.currentTimeMillis());
loginUser.setExpireTime(loginUser.getLoginTime() + expireTime * MILLIS_MINUTE);
String userKey = getTokenKey(loginUser.getToken());
redisCache.setCacheObject(userKey, loginUser, expireTime, TimeUnit.MINUTES);
}
public void setUserAgent(LoginUser loginUser)
{
UserAgent userAgent = UserAgent.parseUserAgentString(ServletUtils.getRequest().getHeader("User-Agent"));
String ip = IpUtils.getIpAddr();
loginUser.setIpaddr(ip);
loginUser.setLoginLocation(AddressUtils.getRealAddressByIP(ip));
loginUser.setBrowser(userAgent.getBrowser().getName());
loginUser.setOs(userAgent.getOperatingSystem().getName());
}
public void delLoginUser(String token, Long userId)
{
if (StringUtils.isNotEmpty(token))
{
String userKey = getTokenKey(token);
redisCache.deleteObject(userKey);
}
if (!soloLogin && StringUtils.isNotNull(userId))
{
String userIdKey = getUserIdKey(userId);
redisCache.deleteObject(userIdKey);
}
}
public void delLoginUser(String token)
{
if (StringUtils.isNotEmpty(token))
{
try
{
String userKey = getTokenKey(token);
LoginUser loginUser = redisCache.getCacheObject(userKey);
if (loginUser != null)
{
String userIdKey = getUserIdKey(loginUser.getUser().getUserId());
redisCache.deleteObject(userIdKey);
redisCache.deleteObject(userKey);
setForceQuitToken(token);
}
}
catch (Exception e)
{
log.error("强制退出登录异常:'{}'", e.getMessage());
}
}
}
public void setRemoteLoginToken(String token){
if (StringUtils.isNotEmpty(token))
{
try
{
String remoteLoginKey = getRemoteLoginKey(token);
String currentTime = DateUtils.getTime();
redisCache.setCacheObject(remoteLoginKey, currentTime, 1, TimeUnit.DAYS);
}
catch (Exception e)
{
log.error("异地登录的token记录异常:'{}'", e.getMessage());
}
}
}
public String getRemoteLoginToken(String token)
{
if (StringUtils.isEmpty(token))
{
return null;
}
try
{
String remoteLoginKey = getRemoteLoginKey(token);
String currentTime = redisCache.getCacheObject(remoteLoginKey);
if (StringUtils.isNotEmpty(currentTime))
{
return currentTime;
}
return null;
}
catch (Exception e)
{
log.error("获取异地登录的token异常:'{}'", e.getMessage());
return null;
}
}
public void removeRemoteLoginToken(String token)
{
if (StringUtils.isNotEmpty(token))
{
try
{
String remoteLoginKey = getRemoteLoginKey(token);
redisCache.deleteObject(remoteLoginKey);
}
catch (Exception e)
{
log.error("删除异地登录的记录异常:'{}'", e.getMessage());
}
}
}
public void setForceQuitToken(String token)
{
if (StringUtils.isNotEmpty(token))
{
try
{
String forceQuitKey = getForceQuitKey(token);
String currentTime = DateUtils.getTime();
redisCache.setCacheObject(forceQuitKey, currentTime,1, TimeUnit.DAYS);
}
catch (Exception e)
{
log.error("强制退出的token记录异常:'{}'", e.getMessage());
}
}
}
public String getForceQuitToken(String token)
{
if (StringUtils.isEmpty(token))
{
return null;
}
try
{
String forceQuitKey = getForceQuitKey(token);
String currentTime = redisCache.getCacheObject(forceQuitKey);
if (StringUtils.isNotEmpty(currentTime))
{
return currentTime;
}
return null;
}
catch (Exception e)
{
log.error("获取强制退出的token异常:'{}'", e.getMessage());
return null;
}
}
public void removeForceQuitKey(String token)
{
if (StringUtils.isNotEmpty(token))
{
try
{
String forceQuitKey = getForceQuitKey(token);
redisCache.deleteObject(forceQuitKey);
}
catch (Exception e)
{
log.error("删除异地登录的记录异常:'{}'", e.getMessage());
}
}
}
}
二 springCache 注解方式使用redis
1 maven配置
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-cache</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
2 开启注解缓存功能
@SpringBootApplication(exclude = { DataSourceAutoConfiguration.class })
@EnableCaching
public class OkYunApplication{......}
3 配置 application.yaml
custom:
cache:
ttl:
tenantCostMethod: 600
userCache: 3600
menuCache: 300
4 创建一个配置类 CustomCacheProperties.java
package com.okyun.framework.config;
import lombok.Data;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.stereotype.Component;
import java.util.HashMap;
import java.util.Map;
@Data
@Component
@ConfigurationProperties(prefix = "custom.cache")
public class CustomCacheProperties {
private Map<String, Long> ttl = new HashMap<>();
}
5 RedisConfig.java 中新增 cacheManager 方法
@Bean
public CacheManager cacheManager(RedisConnectionFactory redisConnectionFactory, CustomCacheProperties customCacheProperties) {
RedisCacheConfiguration defaultConfig = RedisCacheConfiguration.defaultCacheConfig()
.entryTtl(Duration.ofMinutes(30))
.serializeKeysWith(RedisSerializationContext.SerializationPair.fromSerializer(new StringRedisSerializer()))
.serializeValuesWith(RedisSerializationContext.SerializationPair.fromSerializer(new FastJson2JsonRedisSerializer<>(Object.class)))
.disableCachingNullValues()
.computePrefixWith(cacheName -> cacheName + ":");
Map<String, RedisCacheConfiguration> configMap = new HashMap<>();
customCacheProperties.getTtl().forEach((cacheName, ttlSeconds) -> {
configMap.put(cacheName, defaultConfig.entryTtl(Duration.ofSeconds(ttlSeconds)));
});
return RedisCacheManager.builder(redisConnectionFactory)
.cacheDefaults(defaultConfig)
.withInitialCacheConfigurations(configMap)
.transactionAware()
.build();
}
6 常用注解
@EnableCaching
@Cacheable(cacheNames = "tenantCostMethod", key = "'tenantId_' + #tenantId")
@CachePut(cacheNames = "tenantCostMethod", key = "'tenantId_' + #tenantId")
@CacheEvict(cacheNames = "tenantCostMethod", key = "'tenantId_' + #tenantId")
@CacheEvict(cacheNames = "tenantCostMethod", allEntries = true)
三 项目实战应用
1 需求
1 多租户
2 商品缓存
3 关键字过滤商品(商品名称模糊查询,商品编码精确查询)
2 查询所有商品并缓存
@Service
@RequiredArgsConstructor
public class ProductSkuCache {
private final ProductSkuMapper productSkuMapper;
@Cacheable(value = "skuListCache", key = "'tenant_' + #tenantId")
public List<ProductSkuVo> getAllProductSkuByTenant(Long tenantId) {
ProductSku query = new ProductSku();
query.setTenantId(tenantId);
return productSkuMapper.selectProductSkuList(query);
}
}
3 封装 增/删/改 预热刷新缓存
@Service
@RequiredArgsConstructor
@Slf4j
public class ProductSkuCacheManager {
private final RedisTemplate redisTemplate;
private final ProductSkuCache productSkuCache;
public void clearCacheByTenantId(Long tenantId) {
String cacheKey = "skuListCache:tenant_" + tenantId;
redisTemplate.delete(cacheKey);
}
public void refreshCacheByTenant(Long tenantId) {
if (tenantId == null){
return;
}
clearCacheByTenantId(tenantId);
productSkuCache.getAllProductSkuByTenant(tenantId);
}
}
3 处理 增/删/改 刷新缓存
@Service
@RequiredArgsConstructor
@Slf4j
public class ProductSkuServiceImpl implements IProductSkuService
{
private final ProductSkuMapper productSkuMapper;
private final ProductSkuCacheManager productSkuCacheManager;
@Override
@TenantId(tableAlias = "k")
public int insertProductSku(ProductSku productSku)
{
checkSkuCodeUnique(productSku);
int res = productSkuMapper.insertProductSku(productSku);
if (res > 0){
productSkuCacheManager.refreshCacheByTenant(productSku.getTenantId());
}
return res;
}
}
4 关键字查询数据
@PreAuthorize("@ss.hasPermi('product:sku:list')")
@GetMapping("/suggest")
public AjaxResult suggest(@RequestParam String keyword) {
Long tenantId = SecurityUtils.getLoginUser().getTenantId();
if (tenantId == null){
log.error("当前用户的公司ID为空!!");
return AjaxResult.error("当前用户的公司ID为空!!");
}
List<ProductSkuVo> cacheList = productSkuCache.getAllProductSkuByTenant(tenantId);
if (cacheList == null){
log.error("当前用户没有sku数据!!");
return AjaxResult.error("当前用户没有sku数据!!");
}
if (StringUtils.isBlank(keyword)){
return AjaxResult.success(cacheList);
}
String lowerKeyword = keyword.toLowerCase();
List<ProductSkuVo> resultList = cacheList.stream()
.filter(sku -> sku.getSkuName().toLowerCase().contains(lowerKeyword)
|| sku.getSkuCode().toLowerCase().equals(lowerKeyword))
.limit(20)
.collect(Collectors.toList());
return AjaxResult.success(resultList);
}
5 注意事项
// key = "skuListCache:tenant_" + tenantId, 拼接时 value 与 key 之间自动拼接":"
@Cacheable(value = ErpCacheConstants.SKU_LIST, key = "'tenant_' + #tenantId")
// 直接手动拼接key, 需要添加层级":"
String cacheKey = ErpCacheConstants.SKU_LIST + ":tenant_" + tenantId
log.info("-------------清除缓存:{}", cacheKey)
redisTemplate.delete(cacheKey)