防止表單重復(fù)提交(springboot,redis)

   我們在web項目中經(jīng)常需要在后臺對用戶提交的表單進行校驗防止重復(fù)提交。下面通過springboot的aop逼肯、redis來解決表單重復(fù)提交的問題。
通過在controller加上CheckSubmitForm注解 桃煎,用戶訪問連接時篮幢,aop進行代理攔截
    @PostMapping("/comment/add")
    @CheckSubmitForm(delaySeconds = 6)
    public MallTradeResult comment(@RequestBody SiteUserCommentDTO siteUserCommentDTO,@User UserDTO userDTO) {
        siteUserCommentDTO.setUserId(userDTO.getUicId());
        siteUserCommentDTO.setPhone(userDTO.getPhoneNumber());
        siteUserCommentDTO.setNickName(userDTO.getUserName());
        siteUserCommentDTO.setUserHeadImg(userDTO.getAvatarUrl());
        return siteCommentFacade.add(siteUserCommentDTO);
    }
  • requesetDTO中加入formId字段,fromId可以是前端提交的fromId为迈,也可以是我們自己業(yè)務(wù)定義的一個關(guān)鍵字洲拇。fromId用做redis 的key
@Data
public class SiteUserCommentDTO  extends RequesetDTO{

    private String itemCode ;

    private String formId;

    public String getFormId() {
        return itemCode ;
    }
}
  • CheckSubmitFrom 注解是攔截哪些請求對用戶提交的表單進行校驗
    作者項目中是使用 methodname:userId:formId來作為redis key奈揍。例如下圖實例key為comment:userId:itemCode 表示用戶userId對某件商品itemCode的評價comment。如果key存在則阻止提交赋续。也可以由前端傳遞formId直接作為key男翰。key業(yè)務(wù)范圍盡量要小,太大可能對用戶的其他表單提交有影響纽乱。
@Target({ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface CheckSubmitForm {
    int delaySeconds() default 5;
}
  • ReSubmitAspect 定義aop切面
/**
 * Description:
 *
 * @author lixj on 2019/10/11.
 */
@Slf4j
@Component
@Aspect
public class ReSubmitAspect {
    @Autowired
    private StringRedisTemplate stringRedisTemplate;

    private String keyId = "formId";

    @Around("@annotation(com.fcbox.mall.web.support.CheckSubmitForm)")
    public Object around(ProceedingJoinPoint joinPoint) throws Throwable{
        log.info("resubmitApsec do around");
        MethodSignature methodSignature = (MethodSignature) joinPoint.getSignature();
        CheckSubmitForm checkSubmitForm = methodSignature.getMethod().getAnnotation(CheckSubmitForm.class);

        if (checkSubmitForm == null) {
            return joinPoint.proceed();
        } else {
            Object[] args = joinPoint.getArgs();
            String formId = null;
            String userId = null;
            for (Object arg : args) {
                if (StringUtils.isEmpty(formId)) {
                    JSONObject jsonObject = JSONObject.parseObject(JSONObject.toJSONString(arg));
                    if (!StringUtils.isEmpty(jsonObject.getString(keyId))) {
                        formId = jsonObject.getString(keyId);
                    }
                }
                if (StringUtils.isEmpty(userId)) {
                    if (arg.getClass().getAnnotation(User.class) != null) {
                        UserDTO userDTO = (UserDTO)arg;
                        userId = userDTO.getUicId().toString();
                    }
                }

            }
            if (!StringUtils.isEmpty(formId) && !StringUtils.isEmpty(userId)) {
                Class<?> returnType = ((MethodSignature) joinPoint.getSignature()).getMethod().getReturnType();

                String redisKey = ((MethodSignature) joinPoint.getSignature()).getMethod().getName().concat(":").concat(userId)
                        .concat(":").concat(formId);
                log.info("resubmit {}", redisKey, checkSubmitForm.delaySeconds());
                boolean submitAble = stringRedisTemplate.opsForValue().setIfAbsent(redisKey, "1");
                if (!submitAble) {
                    long ttl = stringRedisTemplate.getExpire(redisKey);
                    if (ttl > 0) {
                            return MallTradeResult.fail(ResultCode.REPEAT_SUBMIT_ERROR.getCode(), ResultCode.REPEAT_SUBMIT_ERROR.getMsg());
                        }
                    }
                }
                stringRedisTemplate.expire(redisKey, checkSubmitForm.delaySeconds(), TimeUnit.SECONDS);
                return joinPoint.proceed();
            } else {
                log.error("重復(fù)表單提交檢驗 失效: 參數(shù)錯誤:fromId-{},uicId-{}",formId,userId);
            }

        }

        return joinPoint.proceed();
    }
}

** 注意:代碼中stringRedisTemplate的setNx 和 expire 不是原子操作蛾绎。可以使用lua腳本實現(xiàn)或者Jedis開源組件來實現(xiàn)原子操作鸦列。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末租冠,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子薯嗤,更是在濱河造成了極大的恐慌顽爹,老刑警劉巖,帶你破解...
    沈念sama閱讀 216,470評論 6 501
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件骆姐,死亡現(xiàn)場離奇詭異镜粤,居然都是意外死亡,警方通過查閱死者的電腦和手機玻褪,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,393評論 3 392
  • 文/潘曉璐 我一進店門肉渴,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人带射,你說我怎么就攤上這事同规。” “怎么了窟社?”我有些...
    開封第一講書人閱讀 162,577評論 0 353
  • 文/不壞的土叔 我叫張陵券勺,是天一觀的道長。 經(jīng)常有香客問我灿里,道長关炼,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 58,176評論 1 292
  • 正文 為了忘掉前任钠四,我火速辦了婚禮盗扒,結(jié)果婚禮上跪楞,老公的妹妹穿的比我還像新娘缀去。我一直安慰自己,他們只是感情好甸祭,可當(dāng)我...
    茶點故事閱讀 67,189評論 6 388
  • 文/花漫 我一把揭開白布缕碎。 她就那樣靜靜地躺著,像睡著了一般池户。 火紅的嫁衣襯著肌膚如雪咏雌。 梳的紋絲不亂的頭發(fā)上凡怎,一...
    開封第一講書人閱讀 51,155評論 1 299
  • 那天,我揣著相機與錄音赊抖,去河邊找鬼统倒。 笑死,一個胖子當(dāng)著我的面吹牛氛雪,可吹牛的內(nèi)容都是我干的房匆。 我是一名探鬼主播,決...
    沈念sama閱讀 40,041評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼报亩,長吁一口氣:“原來是場噩夢啊……” “哼浴鸿!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起弦追,我...
    開封第一講書人閱讀 38,903評論 0 274
  • 序言:老撾萬榮一對情侶失蹤岳链,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后劲件,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體掸哑,經(jīng)...
    沈念sama閱讀 45,319評論 1 310
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 37,539評論 2 332
  • 正文 我和宋清朗相戀三年寇仓,在試婚紗的時候發(fā)現(xiàn)自己被綠了举户。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 39,703評論 1 348
  • 序言:一個原本活蹦亂跳的男人離奇死亡遍烦,死狀恐怖俭嘁,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情服猪,我是刑警寧澤供填,帶...
    沈念sama閱讀 35,417評論 5 343
  • 正文 年R本政府宣布,位于F島的核電站罢猪,受9級特大地震影響近她,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜膳帕,卻給世界環(huán)境...
    茶點故事閱讀 41,013評論 3 325
  • 文/蒙蒙 一粘捎、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧危彩,春花似錦攒磨、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,664評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至谒府,卻和暖如春拼坎,著一層夾襖步出監(jiān)牢的瞬間浮毯,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 32,818評論 1 269
  • 我被黑心中介騙來泰國打工泰鸡, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留债蓝,地道東北人。 一個月前我還...
    沈念sama閱讀 47,711評論 2 368
  • 正文 我出身青樓盛龄,卻偏偏與公主長得像惦蚊,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子讯嫂,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 44,601評論 2 353