备注:不要全部粘贴代码,这个是我练习项目的代码(基于spring-boot),主要看2和3的逻辑 1.还是一个简单的redis使用Demo,包含setIfAbsent(不存在则插入),setIfPresent(存在则更新),getExpire(获取剩余有效时间) 2.大体业务逻辑如下:检查用户请求系统的最大次数,每日最大请求次数为10. 3.也可以使用定时任务实现时效性配置(定时任务00:00执行,清空所有用户访问次数)
pom文件redis配置:
<dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-data-redis</artifactId> </dependency>properties文件redis配置:
#redis 配置 spring.redis.host=127.0.0.1 spring.redis.port=6379 spring.redis.jedis.pool.max-active=8 spring.redis.timeout=2000redis序列化配置: 配置序列化,防止插入redis乱码(默认二进制)
import com.fasterxml.jackson.annotation.JsonAutoDetect; import com.fasterxml.jackson.annotation.PropertyAccessor; import com.fasterxml.jackson.databind.ObjectMapper; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.data.redis.connection.RedisConnectionFactory; import org.springframework.data.redis.core.RedisTemplate; import org.springframework.data.redis.serializer.GenericJackson2JsonRedisSerializer; import org.springframework.data.redis.serializer.Jackson2JsonRedisSerializer; import org.springframework.data.redis.serializer.StringRedisSerializer; @Configuration public class RedisUtil { /** * 因为序列化使用的jdkSerializeable ,redis存储数据默认为二进制,简单来说key和value是乱码 * 所以自定义序列化类 */ @Bean public RedisTemplate<Object, Object> redisTemplate(RedisConnectionFactory redisConnectionFactory) { RedisTemplate<Object, Object> redisTemplate = new RedisTemplate<>(); redisTemplate.setConnectionFactory(redisConnectionFactory); // 使用Jackson2JsonRedisSerialize 替换默认序列化 Jackson2JsonRedisSerializer jackson2JsonRedisSerializer = new Jackson2JsonRedisSerializer(Object.class); ObjectMapper objectMapper = new ObjectMapper(); objectMapper.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.ANY); objectMapper.enableDefaultTyping(ObjectMapper.DefaultTyping.NON_FINAL); jackson2JsonRedisSerializer.setObjectMapper(objectMapper); // 设置value的序列化规则和 key的序列化规则 redisTemplate.setValueSerializer(new GenericJackson2JsonRedisSerializer()); redisTemplate.setKeySerializer(new StringRedisSerializer()); redisTemplate.afterPropertiesSet(); return redisTemplate; } }RedisService代码:
public interface RedisService { /** * 更新时效性数据--String类型 **/ Boolean updateVaildValue(String key, String value); /** * 判断当前用户是否达到访问最大次数 **/ Boolean judgeMaxTimesByUserId(String key, String min, String max); }RedisServiceImpl代码:
import com.example.demo.util.redisUtil.RedisService.RedisService; import lombok.extern.slf4j.Slf4j; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.data.redis.core.StringRedisTemplate; import org.springframework.stereotype.Service; import org.springframework.util.StringUtils; import java.math.BigDecimal; import java.util.concurrent.TimeUnit; @Slf4j @Service public class RedisServiceImpl implements RedisService { @Autowired private StringRedisTemplate stringRedisTemplate; /** * @Author longtao * @Date 2020/10/20 * @Describe 更新失效性数据 保留剩余有效时间 **/ @Override public synchronized Boolean updateVaildValue(String key, String value) { //获取剩余时间 单位秒 Long balanceSeconds = stringRedisTemplate.opsForValue().getOperations().getExpire(key); if (balanceSeconds > 0) { return stringRedisTemplate.opsForValue().setIfPresent(key, value, balanceSeconds, TimeUnit.SECONDS); } return false; } /** * @Author longtao * @Date 2020/10/20 * @Describe 判断 最大访问次数 > userId当前访问次数 **/ @Override public synchronized Boolean judgeMaxTimesByUserId(String key, String min, String max) { //获取key的值,为null时则插入新的 //不为null时,取出数据进行判断:是否达到最大值 String value = stringRedisTemplate.opsForValue().get(key); if(StringUtils.isEmpty(value)){ //这里时间暂用的24小时,也可以计算得到当前时间到24:00点的毫秒时间。 stringRedisTemplate.opsForValue().setIfAbsent(key,min,24,TimeUnit.HOURS); return true; } //最大次数 <= 当前访问次数 if(new BigDecimal(max).compareTo(new BigDecimal(value)) <= 0 ){ return false; } return updateVaildValue(key,new BigDecimal(value).add(new BigDecimal(1)).toString()); } }切面注解----CheckVisitTimesAroundAnnotation代码:
import java.lang.annotation.ElementType; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; @Target(ElementType.METHOD) @Retention(RetentionPolicy.RUNTIME) public @interface CheckVisitTimesAroundAnnotation { }切面注解----CheckVisitTimesAroundAspection代码:
//返回异常对象 return new BaseResponse<>(ResultEnum.CHECK_USER_ID_VISIT_TIMES); 这个可以换成日志输出,这里直接粘贴了我的Demo项目代码。
import com.alibaba.excel.util.StringUtils; import com.alibaba.fastjson.JSON; import com.alibaba.fastjson.JSONObject; import com.example.demo.base.Enum.ResultEnum; import com.example.demo.base.model.baseModel.BaseModel; import com.example.demo.base.model.baseRequest.BaseRequest; import com.example.demo.base.model.baseResponse.BaseResponse; import com.example.demo.util.redisUtil.RedisService.RedisService; import lombok.extern.slf4j.Slf4j; import org.aspectj.lang.ProceedingJoinPoint; import org.aspectj.lang.annotation.Around; import org.aspectj.lang.annotation.Aspect; import org.aspectj.lang.annotation.Pointcut; import org.aspectj.lang.reflect.MethodSignature; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.core.annotation.Order; import org.springframework.stereotype.Component; import java.lang.reflect.Method; @Aspect @Component @Slf4j @Order(2) public class CheckVisitTimesAroundAspection { @Autowired private RedisService redisService; /** * 切入点 */ @Pointcut("@annotation(com.example.demo.base.annonation.CheckVisitTimesAroundAnnotation) ") public void entryPoint() { } /** * @return * @Describe 前置切面 */ @Around("entryPoint()") public Object aroundMethod(ProceedingJoinPoint proceedingJoinPoint) { log.info("------检查userId访问次数限制------start"); Object[] args = proceedingJoinPoint.getArgs(); //打印入口方法以及入参 try { for (Object arg : args) { Boolean flag = judgeVisitTimesByUserId(arg); if(!flag){ //返回异常对象 return new BaseResponse<>(ResultEnum.CHECK_USER_ID_VISIT_TIMES); } } } catch (Exception e) { e.printStackTrace(); log.debug("不能打印请求参数"); } //执行请求方法 Object o = null; try { o = proceedingJoinPoint.proceed(); } catch (Throwable throwable) { throwable.printStackTrace(); } log.info("------检查userId访问次数限制------end"); return o; } //检查userId请求接口限制 public Boolean judgeVisitTimesByUserId(Object object){ BaseModel baseModel = JSONObject.parseObject(JSONObject.toJSONString(object),BaseModel.class); String userId = baseModel.getUserId(); if(!StringUtils.isEmpty(userId)){ return redisService.judgeMaxTimesByUserId(userId,"1","10"); } return true; } }在controller层方法上增加注解代码: 增加@CheckVisitTimesAroundAnnotation注解
@BaseBeforeAnnotation @CheckVisitTimesAroundAnnotation @RequestMapping("selectOne") public BaseResponse selectReaderInfo(@RequestBody ReaderInfoModel model) { ReaderInfoModel bookInfoModel = readerInfoService.selectOne(model.getId()); return new BaseResponse(ResultEnum.SUCCESS,bookInfoModel); }redis中:userId 的值: postman收到的返回值: 最后:本次写的比较仓促,我要接我老婆了。有不足的地方望大家指正和包涵!