yml文件
redis:
host: 127.0.0.1
port: 40197
password: 123456
timeout: 5000
database: 0
jedis:
pool:
min-idle: 0
max-idle: 8
max-active: 8
max-wait: -1
RedisConfig.java
import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.support.spring.FastJsonRedisSerializer;
import com.fasterxml.jackson.annotation.JsonAutoDetect;
import com.fasterxml.jackson.annotation.PropertyAccessor;
import com.fasterxml.jackson.databind.ObjectMapper;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.cache.CacheManager;
import org.springframework.cache.annotation.CachingConfigurerSupport;
import org.springframework.cache.annotation.EnableCaching;
import org.springframework.cache.interceptor.KeyGenerator;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.redis.cache.RedisCacheConfiguration;
import org.springframework.data.redis.cache.RedisCacheManager;
import org.springframework.data.redis.cache.RedisCacheWriter;
import org.springframework.data.redis.connection.RedisConnectionFactory;
import org.springframework.data.redis.connection.RedisPassword;
import org.springframework.data.redis.connection.RedisStandaloneConfiguration;
import org.springframework.data.redis.connection.jedis.JedisClientConfiguration;
import org.springframework.data.redis.connection.jedis.JedisConnectionFactory;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.serializer.*;
import redis.clients.jedis.JedisPoolConfig;
import java.io.Serializable;
import java.time.Duration;
/**
* @author
*/
@Configuration
@ConditionalOnProperty("spring.redis.host")
@EnableCaching
@Slf4j
public class RedisConfig extends CachingConfigurerSupport {
@Value("${spring.redis.host}")
String redisHost;
@Value("${spring.redis.port}")
int redisPort;
@Value("${spring.redis.password}")
String redisPassword;
@Value("${spring.redis.timeout}")
int redisTimeout;
@Value("${spring.redis.database}")
int redisDatabase;
@Value("${spring.redis.jedis.pool.min-idle}")
int jedisMinIdle;
@Value("${spring.redis.jedis.pool.max-idle}")
int jedisMaxIdle;
@Value("${spring.redis.jedis.pool.max-active}")
int jedisMaxActive;
@Value("${spring.redis.jedis.pool.max-wait}")
int jedisMaxWait;
/**
* 自定义缓存key生成策略
* @return KeyGenerator
*/
@Bean
@Override
public KeyGenerator keyGenerator() {
return (target, method, params) -> {
StringBuilder sb = new StringBuilder();
sb.append(target.getClass().getName());
sb.append(method.getName());
for (Object obj : params) {
sb.append(obj.toString());
}
return sb.toString();
};
}
@Bean
public RedisConnectionFactory connectionFactory() {
JedisPoolConfig poolConfig = new JedisPoolConfig();
poolConfig.setMaxTotal(jedisMaxActive);
poolConfig.setMaxIdle(jedisMaxIdle);
poolConfig.setMaxWaitMillis(jedisMaxWait);
poolConfig.setMinIdle(jedisMinIdle);
poolConfig.setTestOnBorrow(true);
poolConfig.setTestOnReturn(false);
poolConfig.setTestWhileIdle(true);
JedisClientConfiguration clientConfig = JedisClientConfiguration.builder().usePooling().poolConfig(poolConfig).and().readTimeout(Duration.ofMillis(redisTimeout)).build();
// 单点redis
RedisStandaloneConfiguration redisStandaloneConfiguration = new RedisStandaloneConfiguration();
log.debug(JSON.toJSONString(redisStandaloneConfiguration));
redisStandaloneConfiguration.setHostName(redisHost);
redisStandaloneConfiguration.setPassword(RedisPassword.of(redisPassword));
redisStandaloneConfiguration.setPort(redisPort);
redisStandaloneConfiguration.setDatabase(redisDatabase);
return new JedisConnectionFactory(redisStandaloneConfiguration, clientConfig);
}
/**
* 缓存管理器
* @param redisConnectionFactory RedisConnectionFactory
* @return CacheManager
*/
@Bean
public CacheManager cacheManager(RedisConnectionFactory redisConnectionFactory) {
// 设置缓存有效期一小时
int hour = 1;
RedisCacheConfiguration redisCacheConfiguration = RedisCacheConfiguration.defaultCacheConfig().entryTtl(Duration.ofHours(hour));
return RedisCacheManager.builder(RedisCacheWriter.nonLockingRedisCacheWriter(redisConnectionFactory)).cacheDefaults(redisCacheConfiguration).build();
}
@Bean
public RedisTemplate<String, String> redisTemplate(RedisConnectionFactory factory){
RedisTemplate<String, String> template = new RedisTemplate<>();
template.setConnectionFactory(factory);
//设置序列化工具
setSerializer(template);
template.afterPropertiesSet();
return template;
}
private void setSerializer(RedisTemplate<String, String> template) {
RedisSerializer<String> stringRedisSerializer = new StringRedisSerializer();
RedisSerializer<Serializable> genericToStringSerializer = new GenericToStringSerializer<>(Serializable.class);
log.debug(String.valueOf(genericToStringSerializer));
RedisSerializer<Object> jdkSerializationRedisSerializer = new JdkSerializationRedisSerializer();
log.debug(String.valueOf(jdkSerializationRedisSerializer));
Jackson2JsonRedisSerializer<Serializable> jackson2JsonRedisSerializer = new Jackson2JsonRedisSerializer<>(Serializable.class);
ObjectMapper om = new ObjectMapper();
om.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.ANY);
om.enableDefaultTyping(ObjectMapper.DefaultTyping.NON_FINAL);
jackson2JsonRedisSerializer.setObjectMapper(om);
RedisSerializer<Object> genericJackson2JsonRedisSerializer = new GenericJackson2JsonRedisSerializer();
RedisSerializer<Serializable> fastJsonRedisSerializer = new FastJsonRedisSerializer<>(Serializable.class);
template.setEnableDefaultSerializer(true);
template.setDefaultSerializer(fastJsonRedisSerializer);
template.setStringSerializer(stringRedisSerializer);
template.setKeySerializer(stringRedisSerializer);
template.setValueSerializer(genericJackson2JsonRedisSerializer);
template.setHashKeySerializer(stringRedisSerializer);
template.setHashValueSerializer(fastJsonRedisSerializer);
}
}
应用Java
import com.alibaba.fastjson.JSON;
import com.chinamobile.framework.redis.enums.BaseRedisEnum;
import com.chinamobile.framework.redis.service.RedisObjectService;
import com.chinamobile.framework.redis.vo.BaseRedisVo;
import com.chinamobile.scm.order.service.RedisService;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.support.atomic.RedisAtomicLong;
import org.springframework.stereotype.Service;
import org.springframework.util.Assert;
import java.io.Serializable;
import java.util.*;
import java.util.concurrent.TimeUnit;
@Service
@ConditionalOnProperty("spring.redis.host")
@Slf4j
public class RedisServiceImpl<T extends Serializable> implements RedisService<T> {
@Value("${spring.application.name}")
String app;
@Autowired
RedisTemplate<String, String> redisTemplate;
@Override
public final String getApp() {
return app;
}
@Override
public final RedisTemplate<String, String> getRedisTemplate() {
return redisTemplate;
}
@Override
public void set(String key, String value) {
Assert.notNull(key, "参数id不能为空");
log.debug("set key:" + key + ",value:" + value);
redisTemplate.opsForValue().set(key, value);
}
@Override
public String get(String key) {
Assert.notNull(key, "参数key不能为空");
log.info("get id:" + key +" start get value");
Object obv ="";
try {
obv = redisTemplate.opsForValue().get(key);
}
catch (Exception exp)
{
String messageerr=exp.getMessage();
if(messageerr.indexOf("loginUserId")>-1)
{
int idxloginUserId=messageerr.indexOf("loginUserId");
String loginstartidx=messageerr.substring(idxloginUserId-1);
int idxsencond=loginstartidx.indexOf(",");
String endindexstr=loginstartidx.substring(0,idxsencond);
String valuestr="{"+endindexstr.replaceAll(",","")+"}";
obv=valuestr;
}
}
// Could not read JSON: Missing type id when trying to resolve subtype of [simple type, class java.lang.Object]: missing type id property '@class'\n at [Source: (byte[])\"{\"timestamp\":\"1607414522055\",\"loginUserId\":\"zhanghuan6@tj.cmcc\",\"loginIp\":\"220.196.49.26\",\"loginAddrCode\":\"TJ\",\"loginType\":\"LOGINPAGE\"}\
String vo=(String)obv;
log.info("get id:" + key + ",value:" + vo);
return vo;
}
public static void main(String[] args)
{
String messageerr="Could not read JSON: Missing type id when trying to resolve subtype of [simple type, class java.lang.Object]: missing type id property '@class'\n at [Source: (byte[])\"{\"timestamp\":\"1607414522055\",\"loginUserId\":\"zhanghuan6@tj.cmcc\",\"loginIp\":\"220.196.49.26\",\"loginAddrCode\":\"TJ\",\"loginType\":\"LOGINPAGE\"}\"; line: 1, column: 135]; nested exception is com.fasterxml.jackson.databind.exc.InvalidTypeIdException: Missing type id when trying to resolve subtype of [simple type, class java.lang.Object]: missing type id property '@class'\n at [Source: (byte[])\"{\"timestamp\":\"1607414522055\",\"loginUserId\":\"zhanghuan6@tj.cmcc\",\"loginIp\":\"220.196.49.26\",\"loginAddrCode\":\"TJ\",\"loginType\":\"LOGINPAGE\"}\"; line: 1, column: 135]";
if(messageerr.indexOf("loginUserId")>-1)
{
int idxloginUserId=messageerr.indexOf("loginUserId");
String loginstartidx=messageerr.substring(idxloginUserId-1);
int idxsencond=loginstartidx.indexOf(",");
String endindexstr=loginstartidx.substring(0,idxsencond);
String valuestr="{"+endindexstr.replaceAll(",","")+"}";
System.out.println(valuestr);
}
}
}
基础Service Redis类
import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONObject;
import com.chinamobile.framework.common.constant.CommonConstant;
import com.chinamobile.framework.redis.enums.BaseRedisEnum;
import com.chinamobile.framework.redis.vo.BaseRedisVo;
import lombok.SneakyThrows;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.util.Assert;
import org.springframework.util.CollectionUtils;
import org.springframework.util.StringUtils;
import java.io.Serializable;
import java.util.*;
import java.util.concurrent.TimeUnit;
@SuppressWarnings("unused")
public interface BaseRedisService {
String getApp();
RedisTemplate<String, String> getRedisTemplate();
/**
* 根据module和id生成redis的key
* @param module 业务场景code
* @param id 主键
* @return key
*/
default String generateKey(BaseRedisEnum module, String id) {
StringBuilder buffer = new StringBuilder();
Assert.hasText(getApp(), "配置spring.application.name错误");
Assert.notNull(module, "参数module错误");
Assert.notNull(id, "参数id不能为空");
Assert.isTrue(!id.startsWith("\"") && !id.endsWith("\""), "id不能以引号开始且不能以引号结束");
buffer.append(getApp()).append(CommonConstant.COLON).append(module.getKey());
if (!StringUtils.isEmpty(id)) {
return buffer.append(CommonConstant.COLON).append(id).toString();
}
return "\"" + buffer.toString() + "\"";//必须加引号,超过32个字符
}
default Collection<String> generateKeys(BaseRedisEnum module, Collection<String> ids) {
if (ids == null) {
return null;
}
Collection<String> keys = new ArrayList<>();
for (String id : ids) {
keys.add(generateKey(module, id));
}
return keys;
}
/**
* 删除缓存 根据key精确匹配删除
* @param module 业务场景code
* @param id 主键
*/
default void delete(BaseRedisEnum module, String id) {
this.delete(module, new String[]{id});
}
/**
* 删除缓存 根据keys批量精确匹配删除
* @param module 业务场景code
* @param ids 主键数组
*/
default void delete(BaseRedisEnum module, String[] ids) {
if (ids != null && ids.length > 0) {
List<String> delKeys = new ArrayList<>();
for (String id : ids) {
if (!StringUtils.hasText(id)) {
delKeys.add(generateKey(module, id));
}
}
getRedisTemplate().delete(delKeys);
}
}
/**
* 指定缓存的失效时间
* @param module 业务场景code
* @param id 主键
* @param second 有效时间,单位秒
* @return 是否成功
*/
default Boolean expire(BaseRedisEnum module, String id, long second) {
return expire(module, id, second, TimeUnit.SECONDS);
}
default Boolean expire(BaseRedisEnum module, String id, long time, TimeUnit unit) {
Assert.notNull(id, "参数id不能为空");
String key = generateKey(module, id);
if (time > 0) {
return getRedisTemplate().expire(key, time, unit);
}
return Boolean.FALSE;
}
/**
* 查看key是否存在
* @param module 业务场景code
* @param id 主键
* @return redis的key集合
*/
default Set<String> keys(BaseRedisEnum module, String id) {
StringBuilder pattern = new StringBuilder();
Assert.hasText(getApp(), "配置spring.application.name错误");
Assert.notNull(module, "参数module错误");
pattern.append(getApp()).append(CommonConstant.COLON).append(module.getKey());
if (!StringUtils.isEmpty(id)) {
pattern.append(CommonConstant.COLON).append(id);
}
pattern.append("*");
return getRedisTemplate().keys(pattern.toString());
}
/**
* 检查key是否存在
* @param module 业务场景code
* @param id 主键
* @return 是否存在,true存在,false不存在
*/
default boolean exist(BaseRedisEnum module, String id) {
Assert.notNull(id, "参数id不能为空");
String key = generateKey(module, id);
Boolean bool = getRedisTemplate().hasKey(key);
if (bool == null) {
return false;
}
return bool;
}
default <T extends Serializable> Map<String, String> generateMap(BaseRedisEnum module, Map<String, T> data) {
Map<String, String> map = new HashMap<>();
for (Map.Entry<String, T> entry : data.entrySet()) {
BaseRedisVo<T> vo = new BaseRedisVo<>(entry.getValue());
map.put(generateKey(module, entry.getKey()), JSON.toJSONString(vo));
}
return map;
}
@SuppressWarnings("unchecked")
@SneakyThrows
default <T> T transfer(String vo) {
if (vo == null) {
return null;
}
JSONObject json = JSON.parseObject(vo);
BaseRedisVo<JSONObject> v = json.toJavaObject(BaseRedisVo.class);
Class<T> clazz = (Class<T>) Class.forName(v.getClazzT());
JSONObject data = v.getData();
return data.toJavaObject(clazz);
}
@SneakyThrows
default <T extends Serializable> List<T> transfer(List<String> list) {
List<T> result = new ArrayList<>();
if (CollectionUtils.isEmpty(list)) {
return result;
}
for (String vo : list) {
if (vo != null) {
result.add(transfer(vo));
}
}
return result;
}
@SneakyThrows
default <T extends Serializable> Set<T> transfer(Set<String> set) {
Set<T> result = new HashSet<>();
if (CollectionUtils.isEmpty(set)) {
return result;
}
for (String vo : set) {
if (vo != null) {
result.add(transfer(vo));
}
}
return result;
}
}
调用方分类
import com.alibaba.fastjson.JSON;
import com.chinamobile.framework.redis.enums.BaseRedisEnum;
import com.chinamobile.framework.redis.service.RedisObjectService;
import com.chinamobile.framework.redis.vo.BaseRedisVo;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.support.atomic.RedisAtomicLong;
import org.springframework.stereotype.Service;
import org.springframework.util.Assert;
import java.io.Serializable;
import java.util.*;
import java.util.concurrent.TimeUnit;
@Service
@ConditionalOnProperty("spring.redis.host")
@Slf4j
public final class RedisObjectServiceImpl<T extends Serializable> implements RedisObjectService<T> {
@Value("${spring.application.name}")
String app;
@Autowired
RedisTemplate<String, String> redisTemplate;
@Override
public final String getApp() {
return app;
}
@Override
public final RedisTemplate<String, String> getRedisTemplate() {
return redisTemplate;
}
@Override
public final void set(BaseRedisEnum module, String id, T value) {
Assert.notNull(id, "参数id不能为空");
BaseRedisVo<T> vo = new BaseRedisVo<>(value);
String v = JSON.toJSONString(vo);
log.debug("set id:" + id + ",value:" + v);
redisTemplate.opsForValue().set(generateKey(module, id), v);
}
@Override
public final void set(BaseRedisEnum module, String id, T value, long second) {
Assert.notNull(id, "参数id不能为空");
BaseRedisVo<T> vo = new BaseRedisVo<>(value);
String key = generateKey(module, id);
redisTemplate.opsForValue().set(key, JSON.toJSONString(vo), second, TimeUnit.SECONDS);
}
@Override
public final Boolean setIfAbsent(BaseRedisEnum module, String id, T value) {
Assert.notNull(id, "参数id不能为空");
BaseRedisVo<T> vo = new BaseRedisVo<>(value);
return redisTemplate.opsForValue().setIfAbsent(generateKey(module, id), JSON.toJSONString(vo));
}
@Override
public final void multiSet(BaseRedisEnum module, Map<String, T> data) {
Assert.notEmpty(data, "参数data不能为空");
redisTemplate.opsForValue().multiSet(generateMap(module, data));
}
@Override
public final Boolean multiSetIfAbsent(BaseRedisEnum module, Map<String, T> data) {
Assert.notEmpty(data, "参数data不能为空");
return redisTemplate.opsForValue().multiSetIfAbsent(generateMap(module, data));
}
@Override
public final T get(BaseRedisEnum module, String id) {
Assert.notNull(id, "参数id不能为空");
String vo = redisTemplate.opsForValue().get(generateKey(module, id));
log.debug("get id:" + id + ",value:" + vo);
return transfer(vo);
}
@Override
public final T getAndSet(BaseRedisEnum module, String id, T value) {
Assert.notNull(id, "参数id不能为空");
BaseRedisVo<T> v = new BaseRedisVo<>(value);
String vo = redisTemplate.opsForValue().getAndSet(generateKey(module, id), JSON.toJSONString(v));
return transfer(vo);
}
@Override
public final List<T> multiGet(BaseRedisEnum module, Collection<String> ids) {
Assert.notEmpty(ids, "参数ids不能为空");
List<String> list = redisTemplate.opsForValue().multiGet(generateKeys(module, ids));
return transfer(list);
}
@Override
public final long generate(BaseRedisEnum module, String id) {
Assert.notNull(id, "参数id不能为空");
return generate(module, id, 1);
}
@Override
public final long generate(BaseRedisEnum module, String id, Date expireTime) {
Assert.notNull(id, "参数id不能为空");
return generate(module, id, 1, expireTime);
}
@Override
public final long generate(BaseRedisEnum module, String id, int increment) {
Assert.notNull(id, "参数id不能为空");
RedisAtomicLong counter = new RedisAtomicLong(generateKey(module, id), Objects.requireNonNull(redisTemplate.getConnectionFactory()));
return counter.addAndGet(increment);
}
@Override
public final long generate(BaseRedisEnum module, String id, int increment, Date expireTime) {
Assert.notNull(id, "参数id不能为空");
RedisAtomicLong counter = new RedisAtomicLong(generateKey(module, id), Objects.requireNonNull(redisTemplate.getConnectionFactory()));
counter.expireAt(expireTime);
return counter.addAndGet(increment);
}
}
分布式锁
import com.chinamobile.framework.common.constant.CommonConstant;
import com.chinamobile.framework.common.exception.CommonException;
import com.chinamobile.framework.redis.enums.CommonDistributedLockEnum;
import com.chinamobile.framework.redis.service.CommonDistributedLockService;
import com.chinamobile.framework.redis.service.RedisObjectService;
import com.chinamobile.framework.utils.ProjectUtil;
import lombok.SneakyThrows;
import lombok.extern.slf4j.Slf4j;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.context.ApplicationContext;
import org.springframework.stereotype.Service;
import org.springframework.util.CollectionUtils;
import javax.annotation.Resource;
import java.util.ArrayList;
import java.util.List;
import java.util.Set;
import java.util.concurrent.TimeUnit;
@Service
@ConditionalOnProperty("spring.redis.host")
@Slf4j
public class CommonDistributedLockServiceImpl implements CommonDistributedLockService {
private static final long DEFAULT_EXPIRE = -1;
@Resource
private ApplicationContext applicationContext;
@Resource
private RedisObjectService<String> redisObjectService;
@Override
public boolean lock(String id) {
log.debug("lock:id=" + id);
return this.lock(id, DEFAULT_EXPIRE);
}
@SneakyThrows
@Override
public boolean lock(String id, long second) {
log.debug("lock:id=" + id + ",time(s)=" + second);
if (!isValidLock(id)) {
throw new CommonException(CommonConstant.TOO_FREQUENT_CODE, "操作太频繁,请稍候再试。");
}
String ip = ProjectUtil.getIp(applicationContext.getEnvironment());
String port = ProjectUtil.getPort(applicationContext.getEnvironment());
String value = ip + "_" + port;
if (!redisObjectService.setIfAbsent(CommonDistributedLockEnum.COMMON_DISTRIBUTED_LOCK, id, value)) {
throw new CommonException(CommonConstant.TOO_FREQUENT_CODE, "操作太频繁,请稍候再试。");
}
if (second > 0) {
return redisObjectService.expire(CommonDistributedLockEnum.COMMON_DISTRIBUTED_LOCK, id, second);
}
return true;
}
@Override
public void unlock(String id) {
log.debug("unlock:id=" + id);
// redisObjectService.delete(CommonDistributedLockEnum.COMMON_DISTRIBUTED_LOCK, id);
redisObjectService.expire(CommonDistributedLockEnum.COMMON_DISTRIBUTED_LOCK, id, 1, TimeUnit.NANOSECONDS);//改成1毫秒过期
}
@SneakyThrows
@Override
public void cleanLock() {
List<String> deletes = new ArrayList<>();
Set<String> keys = redisObjectService.keys(CommonDistributedLockEnum.COMMON_DISTRIBUTED_LOCK, null);
if (!CollectionUtils.isEmpty(keys)) {
String ip = ProjectUtil.getIp(applicationContext.getEnvironment());
String port = ProjectUtil.getPort(applicationContext.getEnvironment());
String data = ip + "_" + port;
for (String key : keys) {
String vo = redisObjectService.getRedisTemplate().opsForValue().get(key);
if (vo != null && data.equals(redisObjectService.transfer(vo))) {
deletes.add(key);
redisObjectService.getRedisTemplate().expire(key, 1, TimeUnit.NANOSECONDS);
}
}
log.info("cleanLock:ids=" + deletes);
// redisObjectService.getRedisTemplate().delete(deletes);
}
}
@Override
public boolean isValidLock(String id) {
log.debug("isValidLock:id=" + id);
return !redisObjectService.exist(CommonDistributedLockEnum.COMMON_DISTRIBUTED_LOCK, id);
}
}