Redis实现计数器功能(接口最大访问次数检查)

Redis实现计数器功能(接口最大访问次数检查)

1.前言

备注:不要全部粘贴代码,这个是我练习项目的代码(基于spring-boot),主要看23的逻辑
1.还是一个简单的redis使用Demo,包含setIfAbsent(不存在则插入),setIfPresent(存在则更新),getExpire(获取剩余有效时间)
2.大体业务逻辑如下:检查用户请求系统的最大次数,每日最大请求次数为10.
3.也可以使用定时任务实现时效性配置(定时任务00:00执行,清空所有用户访问次数)

2.Redis配置

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=2000

redis序列化配置:
配置序列化,防止插入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;
    }
}

3.redis判断访问次数

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());
    }
}

4.切面调用检查逻辑

切面注解----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);
    }

4.执行结果

redis中:userId 的值:
在这里插入图片描述
postman收到的返回值:
在这里插入图片描述
最后:本次写的比较仓促,我要接我老婆了。有不足的地方望大家指正和包涵!

评论 5
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值