使用Redis实现记录访问次数(三种方案)

0. 前言

      准备一个Controller,用来测试。

@RestController
@RequestMapping("test")
@Tag(name = "测试",description = "测试springMVC拦截器实现记录访问次数")
public class TestController {
    @GetMapping("getInfo/{id}")
    public String test(@PathVariable Integer id) {
        switch (id){
            case 1:
                return "1";
            case 2:
                return "2";
            default:
                return "3";
        }
    }
}

      访问次数,记录用户访问的次数,一般有如下几种方案:

1. 使用AOP实现

      在springboot中使用AOP

      1. 导入依赖

<!--        AOP依赖-->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-aop</artifactId>
        </dependency>

      2. 写一个切面类,实现统计访问次数。

@Aspect // 表示这是一个切面
@Component // 托管到spring容器中
public class AccessRecordAspect {
    @Autowired
    private RedisTemplate<String,Object> redisTemplate;
    // 定义切点 匹配TestController中的test*方法
    @Pointcut("execution(* com.huan.web.controllers.TestController.test*(..))")
    public void a(){}

    // 使用后置增强,在方法正确执行后执行
    @AfterReturning("a()")
    public void record(JoinPoint joinPoint){
        System.out.println("记录访问记录");
        // 获取目标方法参数
        Object[] args = joinPoint.getArgs();
        System.out.println(args[0]);
        redisTemplate.opsForValue().increment("访问的数据:"+args[0]);
    }
}

      3. 开启AOP

@Configuration
@ComponentScan
@EnableCaching // 开启缓存功能
@EnableAspectJAutoProxy // 开启aop功能
public class AppConfig {
}

      4. 测试

      访问http://localhost:8080/test/getInfo/1 进行测试,测试结果。
在这里插入图片描述
在这里插入图片描述在这里插入图片描述

      5. plus版本

      使用循环增强实现,并将将redis中的记录的数据返回给前端。

      (1) 新建一个bean类
@Data
@AllArgsConstructor
@NoArgsConstructor
@ToString
public class TestBean {
    private Integer id;
    private Long praise;
}
      (2) 新增一个controller方法

      在新增的controller方法中,返回一个map,将数据返回给前端。

@GetMapping("getInfo-getPraise/{id}")
public Map<String,Object> testPraise(@PathVariable Integer id) {
    Map<String,Object> map = new HashMap<>();
    TestBean testBean = null;
    testBean.setId(id);
    map.put("code",1);
    map.put("obj",testBean);
    return map;
}
      (3) 新增一个循环增强方法

      在新增的循环增强方法中,统计访问次数,并将访问次数返回给前端。

@Pointcut("execution(* com.huan.web.controllers.TestController.testPraise*(..))")
public void c(){}
@Around("c()")
public Object showPraise_test(ProceedingJoinPoint joinPoint){
    System.out.println("showPraise_test显示点赞数");
    //运行目标方法返回的值
    Object proceed = null;
    try {
        proceed = joinPoint.proceed();
    } catch (Throwable e) {
        throw new RuntimeException(e);
    }
    Map<String,Object> map = (Map<String, Object>) proceed;
    TestBean testBean = (TestBean) map.get("obj");
    testBean.setPraise(redisTemplate.opsForValue().increment("PraiseNumById-test:"+testBean.getId()));
    return proceed;
}
      (4) 测试

在这里插入图片描述

2. 使用Filter实现

      emm有点麻烦,以后再说。

      来了来了。

      1. 导入json依赖

      <!-- json工具包 -->
      <dependency>
        <groupId>com.google.code.gson</groupId>
        <artifactId>gson</artifactId>
        <version>2.8.6</version>
      </dependency>

      2. 创建一个过滤器

      这里使用spring提供的OncePerRequestFilter实现,并在doFilterInternal方法中使用ContentCachingResponseWrapper包装响应对象,更新响应内容中的点赞数,并将其写入响应对象中。

@Component
public class TestCountFilter extends OncePerRequestFilter {
    @Autowired
    private RedisTemplate<String,Object> redisTemplate;
    @Override
    protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException {
        // 设置请求和响应的字符编码,不设置可能会导致乱码
        request.setCharacterEncoding("UTF-8");
        response.setCharacterEncoding("UTF-8");

        System.out.println("TestCountFilter  "+request.getRequestURI());
        // 包装响应对象
        //ContentCachingResponseWrapper 会将响应内容缓存到一个内部缓冲区中,支持在响应被提交之前或之后读取和修改响应内容,支持多次读取。
        ContentCachingResponseWrapper wrappedResponse = new ContentCachingResponseWrapper(response);
        // 执行过滤器链,并在过滤器链执行结束后,将响应内容写入 wrappedResponse 中
        filterChain.doFilter(request,wrappedResponse);
        // 获取响应内容
        byte[] responseArray = wrappedResponse.getContentAsByteArray();
        String responseBody = new String(responseArray, wrappedResponse.getCharacterEncoding());

        // 在这里可以对响应内容进行处理
        System.out.println("Response Body: " + responseBody);

        //将记录的请求数写入响应内容
        Gson gson = new Gson();
        // 将响应内容转换为 HashMap 对象
        HashMap<String,Object> map = gson.fromJson(responseBody, HashMap.class);
        System.out.println(map);
        // 将响应内容转换为 TestBean 对象
        TestBean bean = gson.fromJson(map.get("obj").toString(), TestBean.class);
        System.out.println(bean);
        // 更新点赞数
        bean.setPraise(redisTemplate.opsForValue().increment("PraiseNumById:"+bean.getId()));
        // 将更新后的点赞数重新写入 map 对象
        map.put("obj",bean);

        //将原有的响应内容清空,避免重复写入
        wrappedResponse.resetBuffer();
        // 将响应内容写回客户端
        //将 jsonModel 对象转换为 JSON 格式的字符串,并将其写入 wrappedResponse 的输出流中
        wrappedResponse.getWriter().write(gson.toJson(map));
        //将 wrappedResponse 的内容复制到原始响应中,如果缺少该语句会导致响应内容丢失
        wrappedResponse.copyBodyToResponse();
    }
}

      注意:如果没有定义编码集置,则默认为 ISO-8859-1 编码,因此需要手动设置编码为 UTF-8,避免乱码。

在这里插入图片描述

      3. 配置过滤器

@Configuration
@ComponentScan
public class FilterConfig {
    @Autowired
    private TestCountFilter testCountFilter;
    @Bean
    public FilterRegistrationBean<TestCountFilter> loggingFilter(){
        FilterRegistrationBean<TestCountFilter> registrationBean = new FilterRegistrationBean<>();
        // 注册过滤器
        registrationBean.setFilter(testCountFilter);
        // 过滤器路径 这里配置可以过滤到的url /resfood/getById-map/a/b/..
        registrationBean.addUrlPatterns("/test/getInfo-getPraise/*");
        return registrationBean;
    }
}

3. 使用springMVC拦截器实现

      在我之前的文章里写过,这里就不重复了。

在这里插入图片描述

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

睆小白

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值