redis处理接口幂等性

redis使用token令牌处理接口幂等性

1.方案描述

针对客户端连续点击或者调用方的超时重试等情况,例如提交订单,此种操作就可以用 Token 的机制实现防止重复提交。简单的说就是调用方在调用接口的时候先向后端请求一个全局 ID(Token),请求的时候携带这个全局 ID 一起请求(Token 最好将其放到 Headers 中),后端需要对这个 Token 作为 Key,用户信息作为 Value 到 Redis 中进行键值内容校验,如果 Key 存在且 Value 匹配就执行删除命令,然后正常执行后面的业务逻辑。如果不存在对应的 Key 或 Value 不匹配就返回重复执行的错误信息,这样来保证幂等操作。

2.注意事项

需要生成全局唯一 Token 串;

需要使用第三方组件 Redis 进行数据效验;

3.主要流程

① 服务端提供获取 Token 的接口,该 Token 可以是一个序列号,也可以是一个分布式 ID 或者 UUID 串。

② 客户端调用接口获取 Token,这时候服务端会生成一个 Token 串。

③ 然后将该串存入 Redis 数据库中,以该 Token 作为 Redis 的键(注意设置过期时间)。

④ 将 Token 返回到客户端,客户端拿到后应存到表单隐藏域中。

⑤ 客户端在执行提交表单时,把 Token 存入到 Headers 中,执行业务请求带上该 Headers。

⑥ 服务端接收到请求后从 Headers 中拿到 Token,然后根据 Token 到 Redis 中查找该 key 是否存在。

⑦ 服务端根据 Redis 中是否存该 key 进行判断,如果存在就将该 key 删除,然后正常执行业务逻辑。如果不存在就抛异常,返回重复提交的错误信息。

4.具体实现

一:在配置文件中配置redis信息,这里使用的是yaml来配置

在这里插入图片描述

二:创建一个用来处理幂等性的注解,将来哪个接口需要处理幂等性,哪个接口就添加这个注解

package com.jdzh.enterprise.framework.service.redis;

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 Idempotent {

}

三:service层代码

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.data.redis.core.ValueOperations;
import org.springframework.stereotype.Service;

import java.util.concurrent.TimeUnit;

/**
 * @author Heb
 * @version 1.0
 * @description: TODO
 * @date 2023/3/6 14:28
 */

@Service
public class RedisService {

    @Autowired
    StringRedisTemplate redisTemplate;

    public boolean set(String key, String value, int time, TimeUnit timeoutUtils){
        try {
            ValueOperations<String, String> ops = redisTemplate.opsForValue();
            ops.set(key, value);
            redisTemplate.expire(key, time, timeoutUtils);
        } catch (Exception exception) {
            exception.printStackTrace();
            return false;
        }
        return true;
    }

    public Boolean exists(String key){
        return redisTemplate.hasKey(key);
    }



    public boolean delete(String key){
        if (exists(key)) {
            Boolean delete = redisTemplate.delete(key);
            return delete;

        }
        return false;
    }
}
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

import javax.servlet.http.HttpServletRequest;
import java.util.UUID;
import java.util.concurrent.TimeUnit;

/**
 * @author Heb
 * @version 1.0
 * @description: TODO
 * @date 2023/3/6 14:37
 */
@Service
public class TokenService {

    @Autowired
    RedisService redisService;

    /**
     * 生成令牌并存入redis并返回
     * @return
     */
    public String generateToken(){
        String token = UUID.randomUUID().toString();
        redisService.set(token,token,30, TimeUnit.MINUTES);
        return token;
    }

    /**
     * 检查前端请求传来的token
     * @param req
     * @return
     */
    public boolean checkToken(HttpServletRequest req){
        String token = req.getHeader("token");
        if(token==null||"".equals("token")){
            token=req.getParameter("token");
                if(token==null||"".equals(token)) {
                    throw  new RuntimeException("请求令牌不合法");
            }
        }
        Boolean exists = redisService.exists(token);
        if(exists){
            //说明redis有令牌
            redisService.delete(token);
            return true;
        }
        //redis没有令牌,说明是非法请求或者是请求已被处理
        return false;
    }




}

四:编写一个过滤器,通过这个过滤器, 获取被拦截方法的注解,判断该方法是否需要去处理接口幂等性


import com.jdzh.enterprise.framework.service.redis.Idempotent;
import com.jdzh.enterprise.framework.service.redis.TokenService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import org.springframework.web.method.HandlerMethod;
import org.springframework.web.servlet.HandlerInterceptor;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

/**
 * @author Heb
 * @version 1.0
 * @description: TODO
 * @date 2023/3/6 14:51
 */
@Component
public class IdemponentIntercepter implements HandlerInterceptor {

    @Autowired
    TokenService tokenService;


    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
        if(handler instanceof HandlerMethod){
            HandlerMethod handlerMethod = (HandlerMethod) handler;
            //获取被拦截方法的注解
            Idempotent methodAnnotation = handlerMethod.getMethodAnnotation(Idempotent.class);
            if(methodAnnotation!=null){
                //说明方法有这个注解

                boolean b = tokenService.checkToken(request);
                if(!b){
                    response.setContentType("text/html;charset=utf-8");
                    response.getWriter().write("请求重复");
                }
                return b;
            }else{
                //没有这个注解
                return true;
            }
        }else{
            return true;
        }



    }


}

五:编写配置类,拦截所有请求

import com.jdzh.enterprise.framework.intercepter.IdemponentIntercepter;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;

/**
 * @author Heb
 * @version 1.0
 * @description: TODO
 * @date 2023/3/6 15:00
 */
@Configuration
public class WebConfig implements WebMvcConfigurer {
    @Autowired
    IdemponentIntercepter intercepter;

    @Override
    public void addInterceptors(InterceptorRegistry registry) {
        registry.addInterceptor(intercepter).addPathPatterns("/**");
    }
}

5.测试

一:在需要处理接口幂等性的接口上添加注解

在这里插入图片描述

二:前端定义一个token变量,发起请求从后端获取token,并将token赋值

<template>
	<view>
		 <button @click="gettoken()">获取token</button>
	</view>
</template>

		data() {
			return {
				title: 'Hello',
				token:'',
			},
			
				methods: {
			gettoken(){
			uni.request({
				method:'GET',
			    url: 'http://192.168.1.13:8085/wxPay/token', //仅为示例,并非真实接口地址。
			    data: {
			        text: 'uni.request'
			    },
			    header: {
			        'custom-header': 'hello' //自定义请求头信息
			    },
			    success: (res) => {
					this.token=res.data;
			    }
			});
			}}

三;将这个token作为请求头,添加在我们需要处理幂等性的请求上

在这里插入图片描述

四:这样就处理完成了

  • 2
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值