在真實的網(wǎng)絡(luò)環(huán)境中,都會存在網(wǎng)絡(luò)情況不好的情況倦春,如果不保證接口的冪等性户敬,用戶可能會對同一結(jié)果提交多次,接口的冪等性就是要保證那個時間段不管用戶提交多少次睁本,最終產(chǎn)生的結(jié)果都是一樣的尿庐,當(dāng)然了可能每個人的理解冪等都不一樣,大同小異呢堰。
使用說明
這里我們只借助的Google的Guava
包中LoadingCache
緩存抄瑟,其它的自己實現(xiàn)
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("請求重復(fù)提交,忽略處理");
actResult.setMsg("請不要重復(fù)提交");
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("請聯(lián)系管理員開啟請求冪等功能",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的值只能使用一次。