日韩性视频-久久久蜜桃-www中文字幕-在线中文字幕av-亚洲欧美一区二区三区四区-撸久久-香蕉视频一区-久久无码精品丰满人妻-国产高潮av-激情福利社-日韩av网址大全-国产精品久久999-日本五十路在线-性欧美在线-久久99精品波多结衣一区-男女午夜免费视频-黑人极品ⅴideos精品欧美棵-人人妻人人澡人人爽精品欧美一区-日韩一区在线看-欧美a级在线免费观看

歡迎訪問 生活随笔!

生活随笔

當前位置: 首頁 > 前端技术 > javascript >内容正文

javascript

如何防止token被拦截_Spring Boot+Redis+拦截器+自定义Annotation实现接口自动幂等

發布時間:2023/12/15 javascript 28 豆豆
生活随笔 收集整理的這篇文章主要介紹了 如何防止token被拦截_Spring Boot+Redis+拦截器+自定义Annotation实现接口自动幂等 小編覺得挺不錯的,現在分享給大家,幫大家做個參考.

今天主要給大家介紹使用springboot和攔截器、redis來優雅的實現接口冪等。正文開始:

在實際的開發項目中,一個對外暴露的接口往往會面臨很多次請求,我們來解釋一下冪等的概念:任意多次執行所產生的影響均與一次執行的影響相同。按照這個含義,最終的含義就是 對數據庫的影響只能是一次性的,不能重復處理。如何保證其冪等性,通常有以下手段:

  • 數據庫建立唯一性索引,可以保證最終插入數據庫的只有一條數據

  • token機制,每次接口請求前先獲取一個token,然后再下次請求的時候在請求的header體中加上這個token,后臺進行驗證,如果驗證通過刪除token,下次請求再次判斷token

  • 悲觀鎖或者樂觀鎖,悲觀鎖可以保證每次for update的時候其他sql無法update數據(在數據庫引擎是innodb的時候,select的條件必須是唯一索引,防止鎖全表)

  • 先查詢后判斷,首先通過查詢數據庫是否存在數據,如果存在證明已經請求過了,直接拒絕該請求,如果沒有存在,就證明是第一次進來,直接放行。

  • redis實現自動冪等的原理圖:

    一:搭建redis的服務Api

    1:首先是搭建redis服務器。

    2:引入springboot中到的redis的stater,或者Spring封裝的jedis也可以,后面主要用到的api就是它的set方法和exists方法,這里我們使用springboot的封裝好的redisTemplate

    /**
    ?* redis工具類
    ?*/
    @Component
    public?class?RedisService {

    ????@Autowired
    ????private?RedisTemplate redisTemplate;

    ????/**
    ?????* 寫入緩存
    ?????* @param key
    ?????* @param value
    ?????* @return
    ?????*/
    ????public?boolean?set(final?String?key,?Object?value) {
    ????????boolean?result =?false;
    ????????try?{
    ????????????ValueOperations operations = redisTemplate.opsForValue();
    ????????????operations.set(key, value);
    ????????????result =?true;
    ????????}?catch?(Exception e) {
    ????????????e.printStackTrace();
    ????????}
    ????????return?result;
    ????}
    ????/**
    ?????* 寫入緩存設置時效時間
    ?????* @param key
    ?????* @param value
    ?????* @return
    ?????*/
    ????public?boolean?setEx(final?String?key,?Object?value, Long expireTime) {
    ????????boolean?result =?false;
    ????????try?{
    ????????????ValueOperations operations = redisTemplate.opsForValue();
    ????????????operations.set(key, value);
    ????????????redisTemplate.expire(key, expireTime, TimeUnit.SECONDS);
    ????????????result =?true;
    ????????}?catch?(Exception e) {
    ????????????e.printStackTrace();
    ????????}
    ????????return?result;
    ????}
    ????/**
    ?????* 判斷緩存中是否有對應的value
    ?????* @param key
    ?????* @return
    ?????*/
    ????public?boolean?exists(final?String?key) {
    ????????return?redisTemplate.hasKey(key);
    ????}
    ????/**
    ?????* 讀取緩存
    ?????* @param key
    ?????* @return
    ?????*/
    ????public?Object?get(final?String?key) {
    ????????Object?result =?null;
    ????????ValueOperations operations = redisTemplate.opsForValue();
    ????????result = operations.get(key);
    ????????return?result;
    ????}
    ????/**
    ?????* 刪除對應的value
    ?????* @param key
    ?????*/
    ????public?boolean?remove(final?String?key) {
    ????????if?(exists(key)) {
    ????????????Boolean?delete?= redisTemplate.delete(key);
    ????????????return?delete;
    ????????}
    ????????return?false;
    ????}
    }

    二:自定義注解AutoIdempotent

    自定義一個注解,定義此注解的主要目的是把它添加在需要實現冪等的方法上,凡是某個方法注解了它,都會實現自動冪等。后臺利用反射如果掃描到這個注解,就會處理這個方法實現自動冪等,使用元注解ElementType.METHOD表示它只能放在方法上,etentionPolicy.RUNTIME表示它在運行時

    @Target({ElementType.METHOD})
    @Retention(RetentionPolicy.RUNTIME)
    public?@interface?AutoIdempotent {
    ??
    }

    三:token創建和檢驗

    1:token服務接口

    我們新建一個接口,創建token服務,里面主要是兩個方法,一個用來創建token,一個用來驗證token。創建token主要產生的是一個字符串,檢驗token的話主要是傳達request對象,為什么要傳request對象呢?主要作用就是獲取header里面的token,然后檢驗,通過拋出的Exception來獲取具體的報錯信息返回給前端

    public?interface?TokenService?{

    ????/**
    ?????* 創建token
    ?????*?@return
    ?????*/
    ????public??String?createToken();

    ????/**
    ?????* 檢驗token
    ?????*?@param?request
    ?????*?@return
    ?????*/
    ????public?boolean?checkToken(HttpServletRequest request)?throws?Exception;

    }

    2:token的服務實現類

    token引用了redis服務,創建token采用隨機算法工具類生成隨機uuid字符串,然后放入到redis中(為了防止數據的冗余保留,這里設置過期時間為10000秒,具體可視業務而定),如果放入成功,最后返回這個token值。checkToken方法就是從header中獲取token到值(如果header中拿不到,就從paramter中獲取),如若不存在,直接拋出異常。這個異常信息可以被攔截器捕捉到,然后返回給前端。

    @Service
    public?class?TokenServiceImpl?implements?TokenService?{

    ????@Autowired
    ????private?RedisService redisService;


    ????/**
    ?????* 創建token
    ?????*
    ?????*?@return
    ?????*/
    ????@Override
    ????public?String?createToken()?{
    ????????String str = RandomUtil.randomUUID();
    ????????StrBuilder token =?new?StrBuilder();
    ????????try?{
    ????????????token.append(Constant.Redis.TOKEN_PREFIX).append(str);
    ????????????redisService.setEx(token.toString(), token.toString(),10000L);
    ????????????boolean?notEmpty = StrUtil.isNotEmpty(token.toString());
    ????????????if?(notEmpty) {
    ????????????????return?token.toString();
    ????????????}
    ????????}catch?(Exception ex){
    ????????????ex.printStackTrace();
    ????????}
    ????????return?null;
    ????}


    ????/**
    ?????* 檢驗token
    ?????*
    ?????*?@param?request
    ?????*?@return
    ?????*/
    ????@Override
    ????public?boolean?checkToken(HttpServletRequest request)?throws?Exception?{

    ????????String token = request.getHeader(Constant.TOKEN_NAME);
    ????????if?(StrUtil.isBlank(token)) {// header中不存在token
    ????????????token = request.getParameter(Constant.TOKEN_NAME);
    ????????????if?(StrUtil.isBlank(token)) {// parameter中也不存在token
    ????????????????throw?new?ServiceException(Constant.ResponseCode.ILLEGAL_ARGUMENT,?100);
    ????????????}
    ????????}

    ????????if?(!redisService.exists(token)) {
    ????????????throw?new?ServiceException(Constant.ResponseCode.REPETITIVE_OPERATION,?200);
    ????????}

    ????????boolean?remove = redisService.remove(token);
    ????????if?(!remove) {
    ????????????throw?new?ServiceException(Constant.ResponseCode.REPETITIVE_OPERATION,?200);
    ????????}
    ????????return?true;
    ????}
    }

    四:攔截器的配置

    1:web配置類,實現WebMvcConfigurerAdapter,主要作用就是添加autoIdempotentInterceptor到配置類中,這樣我們到攔截器才能生效,注意使用@Configuration注解,這樣在容器啟動是時候就可以添加進入context中

    @Configuration
    public?class?WebConfiguration?extends?WebMvcConfigurerAdapter?{

    ????@Resource
    ???private?AutoIdempotentInterceptor autoIdempotentInterceptor;

    ????/**
    ?????* 添加攔截器
    ?????*?@param?registry
    ?????*/
    ????@Override
    ????public?void?addInterceptors(InterceptorRegistry registry)?{
    ????????registry.addInterceptor(autoIdempotentInterceptor);
    ????????super.addInterceptors(registry);
    ????}
    }

    2:攔截處理器:主要的功能是攔截掃描到AutoIdempotent到注解到方法,然后調用tokenService的checkToken()方法校驗token是否正確,如果捕捉到異常就將異常信息渲染成json返回給前端

    /**
    ?* 攔截器
    ?*/
    @Component
    public?class?AutoIdempotentInterceptor?implements?HandlerInterceptor?{

    ????@Autowired
    ????private?TokenService tokenService;

    ????/**
    ?????* 預處理
    ?????*
    ?????*?@param?request
    ?????*?@param?response
    ?????*?@param?handler
    ?????*?@return
    ?????*?@throws?Exception
    ?????*/
    ????@Override
    ????public?boolean?preHandle(HttpServletRequest request, HttpServletResponse response, Object handler)?throws?Exception?{

    ????????if?(!(handler?instanceof?HandlerMethod)) {
    ????????????return?true;
    ????????}
    ????????HandlerMethod handlerMethod = (HandlerMethod) handler;
    ????????Method method = handlerMethod.getMethod();
    ????????//被ApiIdempotment標記的掃描
    ????????AutoIdempotent methodAnnotation = method.getAnnotation(AutoIdempotent.class);
    ????????if?(methodAnnotation !=?null) {
    ????????????try?{
    ????????????????return?tokenService.checkToken(request);// 冪等性校驗, 校驗通過則放行, 校驗失敗則拋出異常, 并通過統一異常處理返回友好提示
    ????????????}catch?(Exception ex){
    ????????????????ResultVo failedResult = ResultVo.getFailedResult(101, ex.getMessage());
    ????????????????writeReturnJson(response, JSONUtil.toJsonStr(failedResult));
    ????????????????throw?ex;
    ????????????}
    ????????}
    ????????//必須返回true,否則會被攔截一切請求
    ????????return?true;
    ????}


    ????@Override
    ????public?void?postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView)?throws?Exception?{

    ????}

    ????@Override
    ????public?void?afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex)?throws?Exception?{

    ????}

    ????/**
    ?????* 返回的json值
    ?????*?@param?response
    ?????*?@param?json
    ?????*?@throws?Exception
    ?????*/
    ????private?void?writeReturnJson(HttpServletResponse response, String json)?throws?Exception{
    ????????PrintWriter writer =?null;
    ????????response.setCharacterEncoding("UTF-8");
    ????????response.setContentType("text/html; charset=utf-8");
    ????????try?{
    ????????????writer = response.getWriter();
    ????????????writer.print(json);

    ????????}?catch?(IOException e) {
    ????????}?finally?{
    ????????????if?(writer !=?null)
    ????????????????writer.close();
    ????????}
    ????}

    }

    五:測試用例

    1:模擬業務請求類

    首先我們需要通過/get/token路徑通過getToken()方法去獲取具體的token,然后我們調用testIdempotence方法,這個方法上面注解了@AutoIdempotent,攔截器會攔截所有的請求,當判斷到處理的方法上面有該注解的時候,就會調用TokenService中的checkToken()方法,如果捕獲到異常會將異常拋出調用者,下面我們來模擬請求一下:

    @RestController
    public?class?BusinessController {


    ????@Resource
    ????private?TokenService tokenService;

    ????@Resource
    ????private?TestService testService;


    ????@PostMapping("/get/token")
    ????public?String??getToken(){
    ????????String?token = tokenService.createToken();
    ????????if?(StrUtil.isNotEmpty(token)) {
    ????????????ResultVo resultVo =?new?ResultVo();
    ????????????resultVo.setCode(Constant.code_success);
    ????????????resultVo.setMessage(Constant.SUCCESS);
    ????????????resultVo.setData(token);
    ????????????return?JSONUtil.toJsonStr(resultVo);
    ????????}
    ????????return?StrUtil.EMPTY;
    ????}


    ????@AutoIdempotent
    ????@PostMapping("/test/Idempotence")
    ????public?String?testIdempotence() {
    ????????String?businessResult = testService.testIdempotence();
    ????????if?(StrUtil.isNotEmpty(businessResult)) {
    ????????????ResultVo successResult = ResultVo.getSuccessResult(businessResult);
    ????????????return?JSONUtil.toJsonStr(successResult);
    ????????}
    ????????return?StrUtil.EMPTY;
    ????}
    }

    2:使用postman請求

    首先訪問get/token路徑獲取到具體到token:

    利用獲取到到token,然后放到具體請求到header中,可以看到第一次請求成功,接著我們請求第二次:

    第二次請求,返回到是重復性操作,可見重復性驗證通過,再多次請求到時候我們只讓其第一次成功,第二次就是失敗:

    六:總結

    本篇介紹了使用springboot和攔截器、redis來優雅的實現接口冪等,對于冪等在實際的開發過程中是十分重要的,因為一個接口可能會被無數的客戶端調用,如何保證其不影響后臺的業務處理,如何保證其只影響數據一次是非常重要的,它可以防止產生臟數據或者亂數據,也可以減少并發量,實乃十分有益的一件事。而傳統的做法是每次判斷數據,這種做法不夠智能化和自動化,比較麻煩。而今天的這種自動化處理也可以提升程序的伸縮性。

    來源:

    https://www.toutiao.com/a6822865768212857352/

    “IT大咖說”歡迎廣大技術人員投稿,投稿郵箱:aliang@itdks.com

    來都來了,走啥走,留個言唄~

    ?IT大咖說 ?|??關于版權?

    由“IT大咖說(ID:itdakashuo)”原創的文章,轉載時請注明作者、出處及微信公眾號。投稿、約稿、轉載請加微信:ITDKS10(備注:投稿),茉莉小姐姐會及時與您聯系!

    感謝您對IT大咖說的熱心支持!

    相關推薦

    推薦文章

    • MySQL的Limit 性能差?真的不能再用了?

    • 強悍!基于Vue的無渲染的富文本編輯器——tiptap!

    • if-else“煩不煩”,讓代碼簡單、高效、優雅起來

    • 用Python只需要三分鐘即可精美地可視化COVID-19數據

    • Gradle Vs Maven:Java項目構建工具如何選擇?

    • 為什么不建議把數據庫部署在docker容器內?

    總結

    以上是生活随笔為你收集整理的如何防止token被拦截_Spring Boot+Redis+拦截器+自定义Annotation实现接口自动幂等的全部內容,希望文章能夠幫你解決所遇到的問題。

    如果覺得生活随笔網站內容還不錯,歡迎將生活随笔推薦給好友。