【Springboot】フォームからのコメントの重複送信防止(前後分離&単一ノード)
記事の目次クライテリア 装備 Core-Code 新規注記@AvoidDuplicateFormToken 異常処理 キャッシュクラス カスタムフォームブロッカー SpringBoot構成ブロッキング Controller使用 総括 注意点 個人推奨 github 著者 前言
最近、マイクロサービスとweb関連を更新しました.ビッグデータ補完
SpringBootはフォームの重複コミットを防止します.ブロッキングに基づいて注釈付きリクエストをブロックし,処理する.
なぜこのように使うのか、後でまとめます.
適用シーン:ブラウザの戻るボタンを使用して、以前の操作を繰り返し、フォームを繰り返しコミットします.重要なビジネスでは、最も一般的な注文シーンなど、大きな問題が発生します.次の2つのリストでは、計算された金額が異なります. 私たちのプログラムはそんなに忙しくても、重複したHTTPリクエストを処理する必要はありません.
注意:単一ノード(マルチノードの非適用) 前後端分離(前後端が分離しない方が簡単.後述) に装備を施す SpringBoot 2.0.3
Core-Code
新規注記@AvoidDuplicateFormTokenの場合一:単一ノードアプリケーション の場合2:前後端分離=>このときフロントエンドは一般的に要求ヘッダを介して検査済みのUserToken に伝達される.ケース3:前後端非分離=>userIdにより取得したユーザ情報をsessionに保存して検証する 以下の場合は前後端分離です.
前後が離れないのは簡単です.request.getSession()は後続操作をすればOK
注意点 SpringBoot 2.xはimplements WebMvcConfigurer{}実装ブロッキング機能 を使用する 【DuplicateSubmitInterceptor】 HandlerMethod handlerMethod = (HandlerMethod) handler;java.lang.ClassCastException:org.springframework.web.servlet.resource.D e f a l t v e r t H t p RequestHandler cannot be cast to org.springframework.web.method.Handler Method.ここで明らかにswaggerの静的リソースマッチング要求の問題に起因する.この方法は静的リソースの処理としてデフォルトで使用されるため、.excludePathPatterns("/swagger-resources/"、/webjars/"、/v 2/"、"/swagger-ui.html/")を除外する必要があります.ここで参考にして、この友达のソース分析、とても感謝します:https://yq.aliyun.com/articles/515182 なぜhashmapでストレージができないのかという友人もいます.ここで私はいつremoveしますか?制御できません.最良の実施形態は、echacheを使用してexpireTimeタイムアウト時間を構成することです. このスキームは単一ノードである.分布式の場合はredis、弱いもの、databaseなどを使ってもいいですが、tokenを記録することに重点を置いています.
個人的なアドバイスフォームの重複コミットがビジネスに基づいて行われることを防止します.各システムにこの機能がある必要はありません.最も古典的な場面はショッピングカートが注文を提出することです.実際のビジネスこそ、カスタマイズされた機能とアーキテクチャのベンチマーク です.フォームを使用して繰り返しコミットするかどうかにかかわらず、データベースは一意の制約を行い、一般的な繰り返しコミットの問題を解決します.
github
https://github.com/ithuhui/hui-base-java
【hui-base-common】の下のcom.hui.base.comon.interceptor
作成者
最近、マイクロサービスとweb関連を更新しました.ビッグデータ補完
SpringBootはフォームの重複コミットを防止します.ブロッキングに基づいて注釈付きリクエストをブロックし,処理する.
なぜこのように使うのか、後でまとめます.
適用シーン:
注意:
Core-Code
新規注記@AvoidDuplicateFormToken
/**
* AvoidDuplicateSubmit
*
* Description:
*
* Creation Time: 2018/11/28 19:27.
*
* @author Hu weihui
*/
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface AvoidDuplicateFormToken {
}
異常処理/**
* FormTokenException
*
* Description:
*
* Creation Time: 2018/12/3 15:26.
*
* @author Hu weihui
*/
public class FormTokenException extends RuntimeException{
private static final long serialVersionUID = 512936007428810210L;
private String errorCode;
private String errorMsg;
public FormTokenException(String errorCode,String errorMsg) {
super(errorMsg);
this.errorCode = errorCode;
}
public FormTokenException(String errorCode,String errorMsg,Throwable cause) {
super(errorMsg,cause);
this.errorCode = errorCode;
}
public FormTokenException(FormTokenExceptionEnum formTokenExceptionEnum) {
super(formTokenExceptionEnum.getErrorMsg());
this.errorCode = formTokenExceptionEnum.getErrorCode();
}
public FormTokenException(FormTokenExceptionEnum formTokenExceptionEnum,Throwable cause) {
super(formTokenExceptionEnum.getErrorMsg(),cause);
this.errorCode = errorCode;
}
/**
* FormExceptionEnum
*
* Description:
*
* Creation Time: 2018/11/29 14:15.
*
* @author Hu weihui
*/
@Getter
public enum FormTokenExceptionEnum {
DUPLICATE_SUBMIT("FT-001", ErrorConstant.NETWORK_ERROR, " "),
ILLEGAL_SUBMIT("FT-002",ErrorConstant.NETWORK_ERROR," "),
SERVER_TOKEN_ERROR("FT-003",ErrorConstant.NETWORK_ERROR," "),
UNKONW_ERROR("FT-004", ErrorConstant.NETWORK_ERROR, " ");
private String errorCode;
private String errorType;
private String errorMsg;
FormTokenExceptionEnum(String errorCode, String errorType, String errorMsg) {
this.errorCode = errorCode;
this.errorType = errorType;
this.errorMsg = errorMsg;
}
}
/**
* ErrorConstant
*
* Description:
*
* Creation Time: 2018/12/3 15:28.
*
* @author Hu weihui
*/
public class ErrorConstant {
public static final String SYSTEM_ERROR = " ";
public static final String UNKNOW_ERROR = " ";
public static final String NETWORK_ERROR = " ";
public static final String BUSINESS_ERROR = " ";
public static final String VALID_ERROR = " ";
}
キャッシュクラス/**
* UserCache
*
* Description:
*
* Creation Time: 2018/12/3 11:00.
*
* @author Hu weihui
*/
public class UserCache {
/**
* cache, 2 .
*
* @return the cache
* @author : Hu weihui
*/
@Bean
public Cache<String,String> getUserCache(){
return CacheBuilder.newBuilder().expireAfterAccess(2L,TimeUnit.SECONDS).build();
}
}
カスタムフォームブロッカー前後が離れないのは簡単です.request.getSession()は後続操作をすればOK
/**
* DuplicateSubmitInterceptor
*
* Description: ( , )
* -> USER_TOKEN
* -> Session
*
* Creation Time: 2018/12/3 14:25.
*
* @author Hu weihui
*/
@Slf4j
public class DuplicateSubmitInterceptor extends HandlerInterceptorAdapter {
private static final String USER_TOKEN_KEY = "token";
@Autowired
private Cache<String, String> cache;
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
if (handler instanceof ResourceHttpRequestHandler) {
return true;
}
HandlerMethod handlerMethod = (HandlerMethod) handler;
Method method = handlerMethod.getMethod();
AvoidDuplicateFormToken annotation = method.getAnnotation(AvoidDuplicateFormToken.class);
//
if (annotation != null) {
boolean result = !isDuplicateSubmit(request);
return result;
}
return super.preHandle(request, response, handler);
}
/**
* .
*
* @param request the request
* @return the boolean
* @author : Hu weihui
*/
private boolean isDuplicateSubmit(HttpServletRequest request) {
try {
// token,
String userToken = request.getHeader(USER_TOKEN_KEY);
if (StringUtils.isEmpty(userToken)) {
throw new FormTokenException(FormTokenExceptionEnum.ILLEGAL_SUBMIT);
}
String clientoken = cache.getIfPresent(userToken);
// cache token,token2 ,
if (null != clientoken){
log.info(" : token: {}, token: {}", userToken);
throw new FormTokenException(FormTokenExceptionEnum.DUPLICATE_SUBMIT);
}else {
// token / , cache
cache.put(userToken,UUID.randomUUID().toString());
}
} catch (Exception e) {
log.info(" ,{}", e.getMessage());
throw new FormTokenException(FormTokenExceptionEnum.SERVER_TOKEN_ERROR);
}
return false;
}
}
SpringBootブロッキングの構成/**
* WebConfig
*
* Description:
*
* Creation Time: 2018/12/3 15:31.
*
* @author Hu weihui
*/
public class WebConfig implements WebMvcConfigurer {
//
@Override
public void addInterceptors(InterceptorRegistry registry) {
registry.addInterceptor(new DuplicateSubmitInterceptor())
.addPathPatterns("/**")
.excludePathPatterns("/swagger-resources/**", "/webjars/**", "/v2/**", "/swagger-ui.html/**");
}
}
コントローラ使用 @AvoidDuplicateFormToken
@GetMapping("/test")
public ResponseEntity<?> test() {
return null;
}
締め括りをつける注意点
個人的なアドバイス
github
https://github.com/ithuhui/hui-base-java
【hui-base-common】の下のcom.hui.base.comon.interceptor
作成者
:HuHui
: web , ,