在真实的网络环境中,都会存在网络情况不好的情况,如果不保证接口的幂等性,用户可能会对同一结果提交多次,接口的幂等性就是要保证那个时间段不管用户提交多少次,最终产生的结果都是一样的,当然了可能每个人的理解幂等都不一样,大同小异。
使用说明
这里我们只借助的Google的Guava
包中LoadingCache
缓存,其它的自己实现
gradle项目
// https://mvnrepository.com/artifact/com.google.guava/guava
compile group: 'com.google.guava', name: 'guava', version: '21.0'
maven项目
<!-- https://mvnrepository.com/artifact/com.google.guava/guava -->
<dependency>
<groupId>com.google.guava</groupId>
<artifactId>guava</artifactId>
<version>21.0</version>
</dependency>
核心类
public class IdempotencyInterceptor implements HandlerInterceptor{
private static Logger LOGGER = LoggerFactory.getLogger(IdempotencyInterceptor.class);
private String[] excludePathPatterns = {};
private String[] pathPatterns = {"/**"};
private static final String[] RID_METHODS = {"POST","PUT","PATCH","DELETE"};
private static final LoadingCache<String,Info> loadingCache = CacheBuilder.newBuilder()
.expireAfterWrite(1, TimeUnit.HOURS)
.expireAfterAccess(3,TimeUnit.MINUTES)
.build(new CacheLoader<String, Info>() {
@Override
public Info load(String key) throws Exception {
if(true){
throw new RuntimeException("请求已失效,请重新操作");
}
return null;
}
});
private boolean checkMethod(HttpServletRequest httpServletRequest){
for(String type : RID_METHODS){
if(type.equals(httpServletRequest.getMethod())){
return true;
}
}
return false;
}
private Info getInfo(HttpServletRequest httpServletRequest) throws Exception{
String rid = httpServletRequest.getHeader("rtoken");
if(StringUtils.isBlank(rid)){
throw new RuntimeException("请求头rtoken不能为空");
}
try {
return loadingCache.get(rid);
}catch (Exception e){
throw new RuntimeException(e.getCause().getMessage());
}
}
@Override
public boolean preHandle(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, Object o) throws Exception {
if(checkMethod(httpServletRequest)){
ActResult actResult = new ActResult();
actResult.setCode(5);
Info info = null;
try {
info = getInfo(httpServletRequest);
}catch (Exception e){
actResult.setMsg(e.getMessage());
ResponseContext.writeData(actResult.toString());
return false;
}
if(null==info.getStatus()){
info.setStatus(Info.Status.PRE);
info.setUrl(RequestContext.get().getRequestURI());
}else if(Info.Status.PRE.equals(info.getStatus())){
if(!info.getUrl().equals(RequestContext.get().getRequestURI())){
actResult.setMsg("请求已失效,请重新操作");
ResponseContext.writeData(actResult.toString());
return false;
}
actResult.setMsg("请求处理中,请稍后");
ResponseContext.writeData(actResult.toString());
return false;
}else if(Info.Status.AFTER.equals(info.getStatus())){
if(!info.getUrl().equals(RequestContext.get().getRequestURI())){
actResult.setMsg("请求已失效,请重新操作");
ResponseContext.writeData(actResult.toString());
return false;
}
LOGGER.info("请求重复提交,忽略处理");
actResult.setMsg("请不要重复提交");
if(null!=info.getResult()){
actResult = (ActResult) info.getResult();
actResult.setCode(0);
}
ResponseContext.writeData(actResult);
return false;
}
}
return true;
}
@Override
public void postHandle(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, Object o, ModelAndView modelAndView) throws Exception {
}
@Override
public void afterCompletion(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, Object o, Exception e) throws Exception {
}
public static LoadingCache<String,Info> getLoadingCache(){
return loadingCache;
}
public static void UpdateRepeatResult(HttpServletRequest request,ActResult actResult) {
String rtoken = request.getHeader("rtoken");
if(StringUtils.isNotBlank(rtoken)){
try {
Info info = getLoadingCache().get(rtoken);
info.setResult(actResult);
info.setStatus(Info.Status.AFTER);
} catch (ExecutionException e) {
e.printStackTrace();
}
}
}
public String[] getExcludePathPatterns() {
return excludePathPatterns;
}
public void setExcludePathPatterns(String[] excludePathPatterns) {
this.excludePathPatterns = excludePathPatterns;
}
public String[] getPathPatterns() {
return pathPatterns;
}
public void setPathPatterns(String[] pathPatterns) {
this.pathPatterns = pathPatterns;
}
}
Web配置
@Component
public class WebConfig extends WebMvcConfigurerAdapter {
@Autowired(required = false)
private IdempotencyInterceptor idempotencyFilter;
@Override
public void addInterceptors(InterceptorRegistry registry) {
// 多个拦截器组成一个拦截器链
if(null!=idempotencyFilter){
registry.addInterceptor(idempotencyFilter).excludePathPatterns(idempotencyFilter.getExcludePathPatterns()).addPathPatterns(idempotencyFilter.getPathPatterns());//幂等请求
}
super.addInterceptors(registry);
}
}
ResponseContext.java
public final class ResponseContext {
private static final Logger LOGGER = LoggerFactory.getLogger(ResponseContext.class);
private ResponseContext(){}
public static HttpServletResponse get(){
return ((ServletRequestAttributes) RequestContextHolder.getRequestAttributes()).getResponse();
}
private static HttpServletResponse init(){
HttpServletResponse response = get();
response.setContentType("application/json;charset=UTF-8");
response.setCharacterEncoding("utf-8");
return response;
}
public static void writeData(String data){
try {
init().getWriter().print(data);
} catch (IOException e) {
LOGGER.error(e.getMessage());
}
}
public static void writeData(Object data){
try {
init().getWriter().print(JSON.toJSON(data));
} catch (IOException e) {
LOGGER.error(e.getMessage());
}
}
public static void writeData(HttpServletResponse response,Object data){
try {
init().getWriter().print(JSON.toJSON(data));
} catch (IOException e) {
LOGGER.error(e.getMessage());
}
}
public static void writeData(HttpServletResponse response,String data){
try {
init().getWriter().print(data);
} catch (IOException e) {
LOGGER.error(e.getMessage());
}
}
}
ActResult.java
public class ActResult implements Result {
private int code = 0;
private String msg;
private Object data;
public ActResult() {
}
public ActResult(String msg) {
this.msg = msg;
}
public ActResult(int code, String msg) {
this.code = code;
this.msg = msg;
}
public ActResult(String msg, Object data) {
this.msg = msg;
this.data = data;
}
public ActResult(int code, String msg, Object data) {
this.code = code;
this.msg = msg;
this.data = data;
}
@Override
public int getCode() {
return code;
}
public void setCode(int code) {
this.code = code;
}
@Override
public String getMsg() {
return msg;
}
public void setMsg(String msg) {
this.msg = msg;
}
@Override
public Object getData() {
return data;
}
public void setData(Object data) {
this.data = data;
}
public static ActResult initialize(Object data) {
return new ActResult(null,data);
}
}
IdemAct.java
@RestController
public class IdemAct {
@Autowired(required = false)
private IdempotencyInterceptor idempotencyFilter;
@GetMapping("rtoken")
public Result idem(HttpServletRequest request, HttpServletResponse response){
if(null==idempotencyFilter){
ActResult actResult = new ActResult("请联系管理员开启请求幂等功能",null);
actResult.setCode(1);
return actResult;
}
String uuid = UUID.randomUUID().toString();
IdempotencyInterceptor.getLoadingCache().put(uuid,new Info());
return new ActResult(null,uuid);
}
}
请求使用方法
只要是POST
,PUT
,PATCH
,DELETE
请求方法,请求头都必需带有rtoken
字段,获取rtoken
值通过 http://项目路径/rtoken
获取,rtoken的值只能使用一次。