token 方式处理接口幂等性

方式系统接口的重复调用,在分布式系统中A服务重复向B服务发送指令,导致B服务重复消费消息。
在单体系统中类似新增操作重复指令,导致系统参数多天重复数据。

方式一
数据库增加唯一索引,保证数据的唯一性
方式二
接口增加幂等性校验
具体方式如下:
①定义注解

@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface Ide {
}

②aop 实现

@Aspect
@Component
public class IdeAspect {
    protected Logger logger = LoggerFactory.getLogger(getClass());

    @Autowired
    private RedisTemplate redisTemplate;

    private final  String msg  ="token 失效,请刷新页面后再进行提交!";

    @Pointcut("@annotation(com.laiease.common.annotation.Ide)")
    public void idePointCut() {

    }

    @Around("idePointCut()")
    public Object around(ProceedingJoinPoint joinPoint) throws Throwable {
        String methodName = joinPoint.getSignature().getName();
        HttpServletRequest request = HttpContextUtils.getHttpServletRequest();
        JSONObject json = JSONObject.parseObject(HttpHelper.getBodyString(request));
        Optional optional = Optional.ofNullable(json);
        String ide = optional.map(item -> item.getString("ide")).orElseThrow(() -> new RuntimeException(msg));
        Map map = (Map) redisTemplate.opsForValue().get(ide);
        if (map!=null&&(methodName.equals(map.get("method"))&&1==(int)map.get("status"))) {
            map.put("status",0);
            redisTemplate.opsForValue().set(ide, map);
        } else {
            throw new RuntimeException(msg);
        }
        R r  = (R) joinPoint.proceed();
        Map result = (Map) r.get("data");
        if(null!= result.get("status") && (boolean)result.get("status")){
            redisTemplate.delete(ide);
        }else{
            map.put("status",1);
            redisTemplate.opsForValue().set(ide, map);
        }
        return r;
    }

    @AfterThrowing(pointcut = "idePointCut()", throwing = "ex")
    public void afterThrowing(Throwable ex) {
        if (!msg.equals(ex.getMessage())) {
            HttpServletRequest request = HttpContextUtils.getHttpServletRequest();
            JSONObject json = JSONObject.parseObject(HttpHelper.getBodyString(request));
            String ide = json.getString("ide");
            Map map = (Map) redisTemplate.opsForValue().get(ide);
            if (map != null) {
                map.put("status", 1);
                redisTemplate.opsForValue().set(ide, map);
            }
        }
    }

}

③定义获取token的接口供前端调用,实现接口可以使用雪花算法或者其他

    @Override
    public String getMethodsToken(JSONObject jsonObject) {
        Map map = new HashMap();
        Optional optional = Optional.ofNullable(jsonObject);
        String method = optional.map(item -> item.getString("method")).orElseThrow(() -> new RuntimeException("异常操作"));
        String ide = CmUtil.getUUID();
        map.put("method",method);
        map.put("status",1);
        redisTemplate.opsForValue().set(ide, map);
        return ide;
    }

④使用
在Controller 层 新增接口或者其他方法接口上增加注解

    @Ide
    @Lelog("保存用户")
    @ApiOperation(value = "保存用户")
    @PostMapping(value = "/save")
    public R save(@RequestBody User user) throws Exception {
        return R.ok(userService.saveOne(user));
    }

补充:HttpHelper 工具类


@Slf4j
public class HttpHelper {

    private final static String xssWhitelist = PropertiesUtil.builder("config.properties").getProperty("sys.xss.whitelist");

    public static String getBodyString(HttpServletRequest request) {
        StringBuilder sb = new StringBuilder();
        String result = "";

        try (InputStream inputStream = request.getInputStream();
             BufferedReader reader = new BufferedReader(new InputStreamReader(inputStream, Charset.forName("UTF-8")))) {
            String line;
            while ((line = reader.readLine()) != null) {
                sb.append(line);
            }
            result = sb.toString();

            if (!isUrlPass(request.getRequestURI())) {
                // xss sql 过滤
                result = xssSqlLeach(result);
            }
        } catch (IOException e) {
            log.error(e.toString());
        }
        return result;
    }

    private static String xssSqlLeach(String body) {


        if (body == null || body.isEmpty()) {
            return body;
        }
        StringBuilder sb = new StringBuilder(body.length());
        for (int i = 0; i < body.length(); i++) {
            char c = body.charAt(i);
            switch (c) {
                case '>':
                    sb.append("》");// 转义大于号
                    break;
                case '<':
                    sb.append("《");// 转义小于号
                    break;
                case '\'':
                    sb.append("‘");// 转义单引号
                    break;
                case '\"':
                    sb.append('"');// 转义双引号
                    break;
                case '&':
                    sb.append("&");// 转义&
                    break;
                default:
                    String s1 = c + "";
                    String s = s1.replaceAll(".*([';]+|(--)+).*", "");
                    sb.append(s);
                    break;
            }

        }
        return sb.toString();
    }

    private static boolean isUrlPass(String url) {
        String[] urlList = xssWhitelist.split(";");

        return ArrayUtils.contains(urlList, url);
    }

}

使用redis 分布式锁处理接口幂等性 redis 分布式锁处理接口幂等性

你可能感兴趣的:(token 方式处理接口幂等性)