SpringMVC 冪等請求

在真實的網(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的值只能使用一次。


最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末骂维,一起剝皮案震驚了整個濱河市惹资,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌航闺,老刑警劉巖褪测,帶你破解...
    沈念sama閱讀 218,451評論 6 506
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異潦刃,居然都是意外死亡侮措,警方通過查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,172評論 3 394
  • 文/潘曉璐 我一進(jìn)店門乖杠,熙熙樓的掌柜王于貴愁眉苦臉地迎上來分扎,“玉大人,你說我怎么就攤上這事胧洒∥废牛” “怎么了环揽?”我有些...
    開封第一講書人閱讀 164,782評論 0 354
  • 文/不壞的土叔 我叫張陵,是天一觀的道長庵佣。 經(jīng)常有香客問我,道長汛兜,這世上最難降的妖魔是什么巴粪? 我笑而不...
    開封第一講書人閱讀 58,709評論 1 294
  • 正文 為了忘掉前任,我火速辦了婚禮粥谬,結(jié)果婚禮上肛根,老公的妹妹穿的比我還像新娘。我一直安慰自己漏策,他們只是感情好派哲,可當(dāng)我...
    茶點故事閱讀 67,733評論 6 392
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著掺喻,像睡著了一般芭届。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上感耙,一...
    開封第一講書人閱讀 51,578評論 1 305
  • 那天褂乍,我揣著相機(jī)與錄音,去河邊找鬼即硼。 笑死逃片,一個胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的只酥。 我是一名探鬼主播褥实,決...
    沈念sama閱讀 40,320評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼裂允!你這毒婦竟也來了损离?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 39,241評論 0 276
  • 序言:老撾萬榮一對情侶失蹤叫胖,失蹤者是張志新(化名)和其女友劉穎草冈,沒想到半個月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體瓮增,經(jīng)...
    沈念sama閱讀 45,686評論 1 314
  • 正文 獨居荒郊野嶺守林人離奇死亡怎棱,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 37,878評論 3 336
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了绷跑。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片拳恋。...
    茶點故事閱讀 39,992評論 1 348
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖砸捏,靈堂內(nèi)的尸體忽然破棺而出谬运,到底是詐尸還是另有隱情隙赁,我是刑警寧澤,帶...
    沈念sama閱讀 35,715評論 5 346
  • 正文 年R本政府宣布梆暖,位于F島的核電站伞访,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏轰驳。R本人自食惡果不足惜厚掷,卻給世界環(huán)境...
    茶點故事閱讀 41,336評論 3 330
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望级解。 院中可真熱鬧冒黑,春花似錦、人聲如沸勤哗。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,912評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽芒划。三九已至冬竟,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間民逼,已是汗流浹背诱咏。 一陣腳步聲響...
    開封第一講書人閱讀 33,040評論 1 270
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機(jī)就差點兒被人妖公主榨干…… 1. 我叫王不留缴挖,地道東北人袋狞。 一個月前我還...
    沈念sama閱讀 48,173評論 3 370
  • 正文 我出身青樓,卻偏偏與公主長得像映屋,于是被迫代替她去往敵國和親苟鸯。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 44,947評論 2 355

推薦閱讀更多精彩內(nèi)容