方式系统接口的重复调用,在分布式系统中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<JSONObject> 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<JSONObject> 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 分布式锁处理接口幂等性