文章目录

一、背景

  • 本篇文章是将以前Redis实战的系列文章进行汇总,针对Redis中常用的一些数据结构,进行实战模拟。

strings

hashes

lists

sets

sorted sets

封锁一个IP地址

存储用户信息

模拟消息队列

自动排重

以某一个条件为权重,进行排序

1.1 开发环境

1. JDK 1.8
2. SpringBoot 2.2.5
3. JPA
4. Spring Security
5. Mysql 8.0
6. Redis Server 3.2.1
7. Redis Desktop Manager
8. Swagger2

1.2 项目配置

  • SpringBoot集成Redis, 添加依赖
<!--pom.xl-->
<!--Redis-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
  • 项目配置文件 添加redis连接配置
<!-- application.yml-->
server:
port: 8000

spring:
freemarker:
check-template-location: false
profiles:
active: dev
jackson:
time-zone: GMT+8
data:
redis:
repositories:
enabled: false

#配置 Jpa
jpa:
properties:
hibernate:
dialect: org.hibernate.dialect.MySQL5InnoDBDialect
open-in-view: true

redis:
database: 0
host: 127.0.0.1
port: 6379
password:
  • 增加RedisConfig配置类
  • Redis项目应用场景与实例汇总_java

/**
* Redis配置类
*
* @author zhuhuix
*/
@Configuration
@EnableCaching
@ConditionalOnClass(RedisOperations.class)
@EnableConfigurationProperties(RedisProperties.class)
public class RedisConfig extends CachingConfigurerSupport {

/**
*设置 redis 数据默认过期时间
*/
@Bean
public RedisCacheConfiguration redisCacheConfiguration(){
FastJsonRedisSerializer<Object> fastJsonRedisSerializer = new FastJsonRedisSerializer<>(Object.class);
RedisCacheConfiguration configuration = RedisCacheConfiguration.defaultCacheConfig();
configuration = configuration.serializeValuesWith(RedisSerializationContext.SerializationPair.fromSerializer(fastJsonRedisSerializer)).entryTtl(Duration.ofHours(Constant.CACHE_TIMEOUT_HOUR));
return configuration;
}


@Bean
public RedisTemplate<Object, Object> redisTemplate(RedisConnectionFactory redisConnectionFactory) {
RedisTemplate<Object, Object> template = new RedisTemplate<>();
template.setConnectionFactory(redisConnectionFactory);

FastJsonRedisSerializer fastJsonRedisSerializer = new FastJsonRedisSerializer(Object.class);
template.setValueSerializer(fastJsonRedisSerializer);
template.setHashValueSerializer(fastJsonRedisSerializer);

ParserConfig.getGlobalInstance().setAutoTypeSupport(true);

template.setKeySerializer(new StringRedisSerializer());
template.setHashKeySerializer(new StringRedisSerializer());

template.afterPropertiesSet();
return template;
}

/**
* 参考:https://blog.csdn.net/qq_15071263/article/details/84335632
* 自定义缓存key生成策略,默认将使用该策略
*/
@Bean
@Override
public KeyGenerator keyGenerator() {
return (target, method, params) -> {
Map<String,Object> container = new HashMap<>(3);
Class<?> targetClassClass = target.getClass();
// 类地址
container.put("class",targetClassClass.toGenericString());
// 方法名称
container.put("methodName",method.getName());
// 包名称
container.put("package",targetClassClass.getPackage());
// 参数列表
for (int i = 0; i < params.length; i++) {
container.put(String.valueOf(i),params[i]);
}
// 转为JSON字符串
String jsonString = JSON.toJSONString(container);
// 做SHA256 Hash计算,得到一个SHA256摘要作为Key
return DigestUtils.sha256Hex(jsonString);
};
}
  • 增加RedisUtils工具类:实现对各种数据结构的封装
/**
* Redis工具类
*
* @author zhuhuix
*/
@Component
@AllArgsConstructor
public class RedisUtils {
private RedisTemplate<Object, Object> redisTemplate;

/**
* HashGet根据键值得到对象
*
* @param key 键值 @NotNull
* @param item 项目 @NotNull
* @return 对象
*/
public Object hashGet(String key, String item) {
return redisTemplate.opsForHash().get(key, item);
}

/**
* 根据键值向hash表中写入对象
*
* @param key 键值 @NotNull
* @param item 项目 @NotNull
* @param value 对象 @NotNull
* @return true 成功 false失败
*/
public boolean hashSet(String key, String item, Object value) {
try {
redisTemplate.opsForHash().put(key, item, value);
return true;
} catch (Exception e) {
e.printStackTrace();
return false;
}

}

/**
* 根据键值向hash表中写入对象,并设置过期时间
*
* @param key 键值 @NotNull
* @param item 项目 @NotNull
* @param value 对象 @NotNull
* @param time 过期时间(秒) @NotNull
* @return true 成功 false失败
*/
public boolean hashSet(String key, String item, Object value, long time) {
try {
redisTemplate.opsForHash().put(key, item, value);
if (time > 0) {
expire(key, time);
}
return true;
} catch (Exception e) {
e.printStackTrace();
return false;
}
}

/**
* 根据键值对某一项目的进行累加计数
*
* @param key 键值
* @param l 累加数
*/
public long increment(String key, long l) {
return redisTemplate.opsForValue().increment(key, l);
}

/**
* 根据键值对某一项目的进行累加计数,并设置过期时间
*
* @param key 键值
* @param l 累加数
* @param time 过期时间(秒)
*/
public long increment(String key, long l, long time) {
long count = redisTemplate.opsForValue().increment(key, l);
if (time > 0) {
expire(key, time);
}
return count;
}

/**
* 入队
*
* @param key 队列键值
* @param value 元素
* @return 添加数量
*/
public long leftPush(String key, Object value) {
return redisTemplate.opsForList().leftPush(key, value);
}

/**
* 向队列头部添加全部集合元素
*
* @param key 队列键值
* @param list 集合
* @return 返回添加的数量
*/
public long leftPushAll(String key, List<Object> list) {
return redisTemplate.opsForList().leftPushAll(key, list);
}

/**
* 统计队列中所有元素数量
*
* @param key 队列键值
* @return 队列中元素数量
*/
public long size(String key) {
return redisTemplate.opsForList().size(key);
}

/**
* 返回队列中从起始位置到结束位置的集合元素
*
* @param key 队列键值
* @param start 起始位置
* @param end 结束位置
* @return 返回集合
*/
public List<Object> range(String key, long start, long end) {
return redisTemplate.opsForList().range(key, start, end);
}

/**
* 出队
*
* @param key 队列键值
* @return 元素
*/
public Object rightPop(String key) {
return redisTemplate.opsForList().rightPop(key);
}

/**
* 弹出队列最新元素
*
* @param key 队列键值
* @return 元素
*/
public Object leftPop(String key) {
return redisTemplate.opsForList().leftPop(key);
}

/**
* 删除队列所有元素
*
* @param key 队列键值
*/
public void deleteAll(String key) {
redisTemplate.opsForList().trim(key, 0, 0);
redisTemplate.opsForList().leftPop(key);
}

/**
* 向集合中增加元素
*
* @param key 集合键值
* @param value 元素
* @return 添加数量
*/
public long setAdd(String key, Object value) {
return redisTemplate.opsForSet().add(key,value);
}

/**
* 向集合中批量增加元素
*
* @param key 集合键值
* @param list 元素列表
* @return 添加数量
*/
public long setAdd(String key, List<Object> list) {
return redisTemplate.opsForSet().add(key,list);
}

/**
* 集合删除指定元素
*
* @param key 集合键值
* @param value 指定元素
* @return 删除数量
*/
public long setRemove(String key, Object value) {
return redisTemplate.opsForSet().remove(key, value);
}

/**
* 集合批量删除指定元素
*
* @param key 集合键值
* @param list 指定元素列表
* @return 删除数量
*/
public long setRemove(String key, List<Object> list) {
return redisTemplate.opsForSet().remove(key, list);
}

/**
* 取出两信集合的交集
*
* @param key1 集合1键值
* @param key2 集合2键值
* @return 交集
*/
public Set<Object> setInter(String key1, String key2) {
return redisTemplate.opsForSet().intersect(key1, key2);
}

/**
* 取出多个集合的交集
*
* @param keys 键值列表
* @return 交集
*/
public Set<Object> setInter(List<Object> keys) {
return redisTemplate.opsForSet().intersect(keys);
}

/**
* 取出两个集合的差集
*
* @param key1 集合1键值
* @param key2 集合2键值
* @return 差集
*/
public Set<Object> setDifference(String key1,String key2){
return redisTemplate.opsForSet().difference(key1,key2);
}

/**
* 指定缓存的失效时间
*
* @param key 键值 @NotNull
* @param time 时间(秒) @NotNull
*/
public boolean expire(String key, long time) {
try {
if (time > 0) {
redisTemplate.expire(key, time, TimeUnit.SECONDS);
}
} catch (Exception e) {
e.printStackTrace();
return false;
}
return true;
}

}

二、字符串的应用场景:封锁一个IP地址

  • 创建SpringBoot后台服务程序,实现用户登录及JWT认证;
  • 通过Redis缓存限制在1分钟内同一IP请求登录不能超过5次。

Redis项目应用场景与实例汇总_键值_02

  • 登录实现类增加Redis计数判断
/**
* 授权登录接口实现类
*
* @author zhuhuix
*/
@Slf4j
@Service
@Transactional(propagation = Propagation.SUPPORTS, readOnly = true, rollbackFor = Exception.class)
public class AuthServiceImpl implements AuthService {

@Value("${wxMini.appId}")
private String appId;
@Value("${wxMini.secret}")
private String secret;

private final JwtTokenUtils jwtTokenUtils;
private final WxMiniApi wxMiniApi;
private final UserService userService;
private final JwtSecurityProperties properties;
private final RedisUtils redisUtils;

public AuthServiceImpl(JwtTokenUtils jwtTokenUtils, WxMiniApi wxMiniApi, UserService userService, JwtSecurityProperties properties, RedisUtils redisUtils) {
this.jwtTokenUtils = jwtTokenUtils;
this.wxMiniApi = wxMiniApi;
this.userService = userService;
this.properties = properties;
this.redisUtils = redisUtils;
}

@Override
@Transactional(rollbackFor = Exception.class)
public Result<AuthUserDto> login(AuthUserDto authUserDto, HttpServletRequest request) {
// 通过缓存判断同一IP某一时间段内的登录次数是否超出限定次数
String ip = NetworkUtils.getIp(request);
String requestLoginIp = "request_login_".concat(ip);
long loginCount = redisUtils.increment(requestLoginIp, 1L);
if (loginCount == 1) {
redisUtils.expire(requestLoginIp, Constant.REQUEST_LOGIN_LIMIT_TIME);
}
if (loginCount > Constant.REQUEST_LOGIN_LIMIT_COUNT) {
log.warn("IP:[".concat(ip).concat("]已超出限定次数"));
throw new RuntimeException("时间段内已超出限定次数,请不要频繁登录!");
}

...
...
}
}
  • 测试与验证
  • Redis项目应用场景与实例汇总_键值_03


  • Redis项目应用场景与实例汇总_java_04


  • Redis项目应用场景与实例汇总_redis_05

三、Hash的应用场景:存储用户信息

– 创建SpringBoot后台服务程序,实现微信小程序登录及JWT认证;
– 通过Redis缓存记录该用户最后一次登录时间及登录累计次数。

/**
* 授权登录接口实现类--增加redis缓存哈希表应用
*
* @author zhuhuix
*/
@Slf4j
@Service
@Transactional(propagation = Propagation.SUPPORTS, readOnly = true, rollbackFor = Exception.class)
public class AuthServiceImpl implements AuthService {
....
....

// 将当前用户信息与登录时间写入Redis缓存的哈希表
// 以微信登录用户的openId作为哈希键值
String key = authUserDto.getUserInfo().getOpenId();

redisUtils.hashSet(key, "id", authUserDto.getUserInfo().getId());
redisUtils.hashSet(key, "nickName", authUserDto.getUserInfo().getNickName());
redisUtils.hashSet(key, "getAvatarUrl", authUserDto.getUserInfo().getAvatarUrl());
redisUtils.hashSet(key, "lastLoginTime", Timestamp.valueOf(LocalDateTime.now()));

// 读取缓存中当前哈希值对应的用户的登录次数
Long loginCount = 1L;
Object obj = redisUtils.hashGet(key, "loginCount");
if (obj != null) {
loginCount += Long.valueOf(String.valueOf(obj));
}
// 累加后回写到哈希表中
redisUtils.hashSet(key, "loginCount", loginCount);
...
}
  • 测试与验证
  • Redis项目应用场景与实例汇总_redis_06


  • Redis项目应用场景与实例汇总_java_07

四、List的应用场景:队列实现

  • 创建SpringBoot上传文件WebApi服务接口;
  • 通过Redis缓存队列记录最新10笔用户上传文件的信息。

Redis项目应用场景与实例汇总_键值_08

  • 图片上传服务源码中增加Redis队列
/**
* 微信小程序CRM实现类:实现Redis队列
*
* @author zhuhuix
*/
@Slf4j
@AllArgsConstructor
@Service
@Transactional(propagation = Propagation.SUPPORTS, readOnly = true, rollbackFor = Exception.class)
public class WxMiniCrmImpl implements WxMiniCrm {

...

@Override
@Transactional(rollbackFor = Exception.class)
public Result<CrmIndex> uploadCrmIndex(String json, String openId, String realName, MultipartFile multipartFile) {
try {
JSONObject jsonObject = JSONObject.parseObject(json);

String createTime = jsonObject.getString("create");
String employeeCode = jsonObject.getString("employeeCode");
String customerCode = jsonObject.getString("customerCode");
String customerName = jsonObject.getString("customerName");
String type = jsonObject.getString("type");

if (StringUtils.isEmpty(createTime) || StringUtils.isEmpty(employeeCode) || StringUtils.isEmpty(customerCode)
|| StringUtils.isEmpty(customerName) || StringUtils.isEmpty(type)) {
throw new RuntimeException("上传信息中缺少关键资料");
}

UploadFile uploadFile = uploadFileTool.upload(openId, realName, multipartFile);
if (uploadFile == null) {
throw new RuntimeException("上传文件失败!");
}
CrmIndex crmIndex = new CrmIndex();
DateTimeFormatter dateTimeFormatter = DateTimeFormatter.ofPattern("yyyy/MM/dd HH:mm");
crmIndex.setCreateTime(Timestamp.valueOf(LocalDateTime.parse(createTime, dateTimeFormatter)));
crmIndex.setEmployeeCode(employeeCode);
crmIndex.setCustomerCode(customerCode);
crmIndex.setCustomerName(customerName);
crmIndex.setType(type);
crmIndex.setJson(json);
crmIndex.setOpenId(openId);
crmIndex.setPath(uploadFile.getPath());

// 将最新10条上传的信息放入redis缓存
if (redisUtils.size(Constant.REDIS_UPLOAD_QUEUE_NAME) >= Constant.REDIS_UPLOAD_QUEUE_COUNT) {
log.warn(Constant.REDIS_UPLOAD_QUEUE_NAME.concat("队列已满,移除最旧上传信息:") + redisUtils.rightPop(Constant.REDIS_UPLOAD_QUEUE_NAME));
}
log.info(Constant.REDIS_UPLOAD_QUEUE_NAME.concat("队列增加上传信息:").concat(crmIndex.toString()));
redisUtils.leftPush(Constant.REDIS_UPLOAD_QUEUE_NAME, crmIndex);

return new Result<CrmIndex>().ok(crmIndexRepository.save(crmIndex));

} catch (JSONException ex) {
throw new RuntimeException("json转换失败:" + ex.getMessage());
}

}
...
}

文件上传的原理与实现可参考该文章​《SpringBoot实现微信小程序文件上传的完整案例》​

  • 测试与验证
  • 微信小程序端
    – 前端将识别信息与图片上传至服务器
  • Redis项目应用场景与实例汇总_java_09


  • Redis项目应用场景与实例汇总_java_10

  • Redis缓存队列
    – 队列中只保存最新10条(数量可自行调整)信息.:

Redis项目应用场景与实例汇总_redis_11

五、Set的应用场景:自动去重

  • 创建SpringBoot添加客户信息服务接口;
  • 通过Redis集合缓存客户信息,要求自动去重,不得重复记录。

Redis项目应用场景与实例汇总_json_12

  • 客户实体类
/**
* CRM客户信息
*
* @author zhuhuix
*/
@Entity
@Getter
@Setter
@Table(name = "customer")
public class Customer implements Serializable {

@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
@NotNull(groups = Update.class)
private Long id;

@Column(name = "open_id")
private String openId;

/**
* 客户代码
*/
@Column(name = "customer_code")
private String customerCode;

/**
* 客户名称
*/
@Column(name = "customer_name")
private String customerName;

/**
* 首字母
*/
@Column(name = "first_letter")
private String firstLetter;

/**
* 创建时间
*/
@Column(name = "create_time")
@CreationTimestamp
private Timestamp createTime;

/**
* 更新时间
*/
@Column(name = "update_time")
@UpdateTimestamp
private Timestamp updateTime;

@Override
public String toString() {
return "Customer{" +
"customerCode='" + customerCode + '\'' +
", customerName='" + customerName + '\'' +
'}';
}
}
  • 客户信息WebApi
@ApiOperation(value = "通过扫一扫功能上传客户信息")
@PostMapping(value = "/crmScan/{openId}")
public ResponseEntity crmScan(@RequestBody WxScanDto wxScanDto, @PathVariable String openId) {

return ResponseEntity.ok(wxMiniCrm.wxScan(wxScanDto, openId));

}
  • 记录并缓存客户信息实现类
/**
* 微信小程序CRM实现类
*
* @author zhuhuix
*/
@Slf4j
@AllArgsConstructor
@Service
@Transactional(propagation = Propagation.SUPPORTS, readOnly = true, rollbackFor = Exception.class)
public class WxMiniCrmImpl implements WxMiniCrm {

private final UploadFileTool uploadFileTool;
private final CrmIndexRepository crmIndexRepository;
private final CustomerRepository customerRepository;
private final UserService userService;
private final RedisUtils redisUtils;

...

@Override
@Transactional(rollbackFor = Exception.class)
public Result<WxScanDto> wxScan(WxScanDto wxScanDto, String openId) {

//微信扫一扫保存客户信息
if (Constant.SAVE_CUSTOMER_INFO.equals(wxScanDto.getScanType()) && wxScanDto.getJsonObject() != null) {
try {
Customer customer = JSONObject.parseObject(wxScanDto.getJsonObject().toJSONString(), Customer.class);
Customer target = customerRepository.findByCustomerCodeAndOpenId(customer.getCustomerCode(), openId);
if (target != null) {
BeanUtils.copyProperties(customer, target, RepositoryUtil.getNullPropertyNames(customer));
} else {
target = customer;
target.setOpenId(openId);
}
wxScanDto.setReturnObject(customerRepository.save(target));
// 将用户增加的客户信息添加到redis集合中
redisUtils.setAdd(openId.concat("_customer"),customer.toString());

return new Result<WxScanDto>().ok(wxScanDto);
} catch (JSONException ex) {
throw new RuntimeException("json转换失败:" + ex.getMessage());
}

}
return new Result<WxScanDto>().error("无法处理扫一扫功能");
}


}
  • 测试与验证
  1. 相同信息自动去重:通过swagger2进行接口测试,多次提交相同的客户信息
  2. Redis项目应用场景与实例汇总_键值_13

  • Redis Desktop Manager验证数据
    – 查看集合中的数据,实现自动去重
  • Redis项目应用场景与实例汇总_json_14

  1. 不同集合之间的交集与差集:用户1通过接口添加4个客户
  2. Redis项目应用场景与实例汇总_键值_15

  3. –用户2通过接口添加3个客户
  4. Redis项目应用场景与实例汇总_json_16

  5. – 获取用户1与用户2相同及不同的客户信息
/**
* Redis测试
*
* @author zhuhuix
*/
@SpringBootTest
@Slf4j
public class TestSet {
@Test
void test() {
RedisUtils redisUtils = SpringContextHolder.getBean(RedisUtils.class);
//获取交集:相同客户
Set<Object> setInter=redisUtils.setInter("openId1_customer","openId2_customer");
Iterator iterator = setInter.iterator();
log.info("openId1_customer与openId2_customer相同的客户为:");
while(iterator.hasNext()){
log.info(iterator.next().toString());
}
//获取差集:不同客户
Set<Object> setDiff=redisUtils.setDifference("openId1_customer","openId2_customer");
iterator = setDiff.iterator();
log.info("openId1_customer与openId2_customer不同的客户为:");
while(iterator.hasNext()){
log.warn(iterator.next().toString());
}

//获取差集:不同客户
Set<Object> setDiff1=redisUtils.setDifference("openId2_customer","openId1_customer");
iterator = setDiff1.iterator();
log.info("openId2_customer与openId1_customer不同的客户为:");
while(iterator.hasNext()){
log.warn(iterator.next().toString());
}
}
}
  • 测试结果
  • Redis项目应用场景与实例汇总_java_17