项目中引入redis
定义注解类
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
public @interface Idempotent {
/**
* 用于拼接幂等性判断的key的入参字段
*
* @return
*/
String[] fields();
/**
* 用于接口幂等性校验的Redis中Key的过期时间,单位秒
* @return
*/
long timeout() default 10l;
}
定义拦截器实现接口幂等功能
@Data
public class IdempotentInterceptor implements HandlerInterceptor {
@Autowired
private RedisCache redisCache;
private final int BUFFER_SIZE = 1024 * 8;
/**
* read string.
*
* @param reader Reader instance.
* @return String.
* @throws IOException
*/
public String read(Reader reader) throws IOException {
try (StringWriter writer = new StringWriter()) {
write(reader, writer);
return writer.getBuffer().toString();
}
}
/**
* write.
*
* @param reader Reader.
* @param writer Writer.
* @return count.
* @throws IOException
*/
public long write(Reader reader, Writer writer) throws IOException {
return write(reader, writer, BUFFER_SIZE);
}
/**
* write.
*
* @param reader Reader.
* @param writer Writer.
* @param bufferSize buffer size.
* @return count.
* @throws IOException
*/
public long write(Reader reader, Writer writer, int bufferSize) throws IOException {
int read;
long total = 0;
char[] buf = new char[bufferSize];
while ((read = reader.read(buf)) != -1) {
writer.write(buf, 0, read);
total += read;
}
return total;
}
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
Idempotent idempotent = ((HandlerMethod)handler).getMethodAnnotation(Idempotent.class);
if(idempotent == null){
return true;
}
String idempotentKey = this.idempotentKey(idempotent,request);
if(!redisCache.setIfAbsent(idempotentKey,idempotentKey, idempotent.timeout())){
throw Problem.valueOf(Status.BAD_REQUEST, "重复提交");
}
return true;
}
private String idempotentKey(Idempotent idempotent,HttpServletRequest request) throws IOException {
BufferedReader reader = new BufferedReader(new InputStreamReader(request.getInputStream()));
String read = this.read(reader);
String md5 = MD5.create().digestHex(JsonUtils.objToString(read));
return md5;
}
/**
* 接口渲染
* @param response
* @throws Exception
*/
private void render(HttpServletResponse response,String message)throws Exception {
response.setContentType("application/json;charset=UTF-8");
OutputStream out = response.getOutputStream();
out.write(message.getBytes("UTF-8"));
out.flush();
out.close();
}
}
把拦截器注入到WebMvcConfigurer 使拦截器生效
@Configuration
public class WebMvcConfig implements WebMvcConfigurer {
@Override
public void addInterceptors(InterceptorRegistry registry) {
registry.addInterceptor(getLoginHandlerInterceptor());
}
@Bean
public IdempotentInterceptor getLoginHandlerInterceptor(){
return new IdempotentInterceptor();
}
}
在controller 的方法中使用@Idempotent注解
@ApiOperation("保单录入提交接口")
@PostMapping("/submit")
@Idempotent(fields = {"info"},timeout = 10)
public OrderInfoResDTO submit(@Valid @RequestBody OrderInfoDTO info) {
throw Problem.valueOf(Status.BAD_REQUEST, "正常结束流程");
}