亚洲国产日韩欧美一区二区三区,精品亚洲国产成人av在线,国产99视频精品免视看7,99国产精品久久久久久久成人热,欧美日韩亚洲国产综合乱

目錄
七、測試驗證 " >七、測試驗證
首頁 Java Java面試題 面試官:支付介面, 重複支付同一筆訂單只能扣一次錢, 怎麼做?

面試官:支付介面, 重複支付同一筆訂單只能扣一次錢, 怎麼做?

Aug 22, 2023 pm 03:57 PM
java面試題

大家好,我是田哥

#昨天,在給一位朋友做 #模擬面試的時候,關(guān)於介面冪等如何實作?從他的回答語氣中能看出,就是在背八股文。

所以,為了讓大家能輕鬆體會介面的冪等實現(xiàn),田哥今天安排了這篇文章。

本文一共有九個主要內(nèi)容:

面試官:支付介面, 重複支付同一筆訂單只能扣一次錢, 怎麼做?

一、冪等性概念

冪等性, 通俗的說就是一個介面, 多次發(fā)起同一個請求, 必須保證操作只能執(zhí)行一次例如:

  • 訂單介面, 不能多次建立訂單
  • 支付介面, 重複支付同一筆訂單只能扣一次錢
  • #支付寶回呼介面, 可能會多次回呼, 必須處理重複回呼
  • #普通表單提交介面, 因為網(wǎng)路逾時等原因多次點擊提交, 只能成功一次等等等

二、常見解決方案

  1. #唯一索引-- 防止新增髒資料
  2. token機(jī)制-- 防止頁面重複提交
  3. 悲觀鎖定-- 取得資料的時候加鎖(鎖定表或鎖定行)
  4. 樂觀鎖定-- 基於版本號version實作, 在更新資料那一刻校驗資料
  5. 分散式鎖定-- redis(jedis、redisson)或zookeeper實作
  6. 狀態(tài)機(jī)-- 狀態(tài)變更, 更新資料時判斷狀態(tài)

############################################################################## #######三、本文實作###### #########本文採用第2種方式實作, 即透過###Redis token###機(jī)制實現(xiàn)介面冪等性校驗。 ###

四、實作想法

#為需要保證冪等性的每一次請求建立一個唯一識別token, 先取得token, 並將此token存入redis, 請求介面時, 將此token放到header或作為請求參數(shù)請求介面,後端介面判斷redis中是否存在此token:

#
  • 如果存在, 正常處理業(yè)務(wù)邏輯, 並從redis中刪除此token, 那麼, 如果是重複請求, 由於token已被刪除,則不能通過校驗, 返回請勿重複操作提示
  • #如果不存在, 說明參數(shù)不合法或者是重複請求, 返回提示即可

五、專案簡介

  • Spring Boot
  • #Redis
  • #@ApiIdempotent註解攔截器對請求進(jìn)行攔截
  • #@ControllerAdvice全域例外處理
  • #壓縮工具: Jmeter

說明:

本文重點介紹冪等性核心實作, 關(guān)於Spring Boot如何整合redis 、ServerResponse 、ResponseCode 等細(xì)枝末節(jié)不在本文討論範(fàn)圍之內(nèi).

六、程式碼實作

#1、maven依賴

<!-- Redis-Jedis -->
<dependency>
   <groupId>redis.clients</groupId>
   <artifactId>jedis</artifactId>
   <version>2.9.0</version>
</dependency>

<!--lombok 本文用到@Slf4j注解, 也可不引用, 自定義log即可-->
<dependency>
    <groupId>org.projectlombok</groupId>
    <artifactId>lombok</artifactId>
    <version>1.16.10</version>
</dependency>

2、JedisUtil

@Component
@Slf4j
public class JedisUtil {

    @Autowired
    private JedisPool jedisPool;

    private Jedis getJedis() {
        return jedisPool.getResource();
    }

    /**
     * 設(shè)值
     *
     * @param key
     * @param value
     * @return
     */
    public String set(String key, String value) {
        Jedis jedis = null;
        try {
            jedis = getJedis();
            return jedis.set(key, value);
        } catch (Exception e) {
            log.error("set key:{} value:{} error", key, value, e);
            return null;
        } finally {
            close(jedis);
        }
    }

    /**
     * 設(shè)值
     *
     * @param key
     * @param value
     * @param expireTime 過期時間, 單位: s
     * @return
     */
    public String set(String key, String value, int expireTime) {
        Jedis jedis = null;
        try {
            jedis = getJedis();
            return jedis.setex(key, expireTime, value);
        } catch (Exception e) {
            log.error("set key:{} value:{} expireTime:{} error", key, value, expireTime, e);
            return null;
        } finally {
            close(jedis);
        }
    }

    /**
     * 取值
     *
     * @param key
     * @return
     */
    public String get(String key) {
        Jedis jedis = null;
        try {
            jedis = getJedis();
            return jedis.get(key);
        } catch (Exception e) {
            log.error("get key:{} error", key, e);
            return null;
        } finally {
            close(jedis);
        }
    }

    /**
     * 刪除key
     *
     * @param key
     * @return
     */
    public Long del(String key) {
        Jedis jedis = null;
        try {
            jedis = getJedis();
            return jedis.del(key.getBytes());
        } catch (Exception e) {
            log.error("del key:{} error", key, e);
            return null;
        } finally {
            close(jedis);
        }
    }

    /**
     * 判斷key是否存在
     *
     * @param key
     * @return
     */
    public Boolean exists(String key) {
        Jedis jedis = null;
        try {
            jedis = getJedis();
            return jedis.exists(key.getBytes());
        } catch (Exception e) {
            log.error("exists key:{} error", key, e);
            return null;
        } finally {
            close(jedis);
        }
    }

    /**
     * 設(shè)值key過期時間
     *
     * @param key
     * @param expireTime 過期時間, 單位: s
     * @return
     */
    public Long expire(String key, int expireTime) {
        Jedis jedis = null;
        try {
            jedis = getJedis();
            return jedis.expire(key.getBytes(), expireTime);
        } catch (Exception e) {
            log.error("expire key:{} error", key, e);
            return null;
        } finally {
            close(jedis);
        }
    }

    /**
     * 獲取剩余時間
     *
     * @param key
     * @return
     */
    public Long ttl(String key) {
        Jedis jedis = null;
        try {
            jedis = getJedis();
            return jedis.ttl(key);
        } catch (Exception e) {
            log.error("ttl key:{} error", key, e);
            return null;
        } finally {
            close(jedis);
        }
    }

    private void close(Jedis jedis) {
        if (null != jedis) {
            jedis.close();
        }
    }

}

3、自訂註解#@ApiIdempotent

/**
 * 在需要保證 接口冪等性 的Controller的方法上使用此注解
 */
@Target({ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
public @interface ApiIdempotent {
}

4、ApiIdempotentInterceptor?攔截器

/**
 * 接口冪等性攔截器
 */
public class ApiIdempotentInterceptor implements HandlerInterceptor {

    @Autowired
    private TokenService tokenService;

    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) {
        if (!(handler instanceof HandlerMethod)) {
            return true;
        }

        HandlerMethod handlerMethod = (HandlerMethod) handler;
        Method method = handlerMethod.getMethod();

        ApiIdempotent methodAnnotation = method.getAnnotation(ApiIdempotent.class);
        if (methodAnnotation != null) {
            check(request);// 冪等性校驗, 校驗通過則放行, 校驗失敗則拋出異常, 并通過統(tǒng)一異常處理返回友好提示
        }

        return true;
    }

    private void check(HttpServletRequest request) {
        tokenService.checkToken(request);
    }

    @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 {
    }
}

5、TokenServiceImpl

@Service
public class TokenServiceImpl implements TokenService {

    private static final String TOKEN_NAME = "token";

    @Autowired
    private JedisUtil jedisUtil;

    @Override
    public ServerResponse createToken() {
        String str = RandomUtil.UUID32();
        StrBuilder token = new StrBuilder();
        token.append(Constant.Redis.TOKEN_PREFIX).append(str);

        jedisUtil.set(token.toString(), token.toString(), Constant.Redis.EXPIRE_TIME_MINUTE);

        return ServerResponse.success(token.toString());
    }

    @Override
    public void checkToken(HttpServletRequest request) {
        String token = request.getHeader(TOKEN_NAME);
        if (StringUtils.isBlank(token)) {// header中不存在token
            token = request.getParameter(TOKEN_NAME);
            if (StringUtils.isBlank(token)) {// parameter中也不存在token
                throw new ServiceException(ResponseCode.ILLEGAL_ARGUMENT.getMsg());
            }
        }

        if (!jedisUtil.exists(token)) {
            throw new ServiceException(ResponseCode.REPETITIVE_OPERATION.getMsg());
        }

        Long del = jedisUtil.del(token);
        if (del <= 0) {
            throw new ServiceException(ResponseCode.REPETITIVE_OPERATION.getMsg());
        }
    }

}

6、TestApplication

@SpringBootApplication
@MapperScan("com.wangzaiplus.test.mapper")
public class TestApplication  extends WebMvcConfigurerAdapter {

    public static void main(String[] args) {
        SpringApplication.run(TestApplication.class, args);
    }

    /**
     * 跨域
     * @return
     */
    @Bean
    public CorsFilter corsFilter() {
        final UrlBasedCorsConfigurationSource urlBasedCorsConfigurationSource = new UrlBasedCorsConfigurationSource();
        final CorsConfiguration corsConfiguration = new CorsConfiguration();
        corsConfiguration.setAllowCredentials(true);
        corsConfiguration.addAllowedOrigin("*");
        corsConfiguration.addAllowedHeader("*");
        corsConfiguration.addAllowedMethod("*");
        urlBasedCorsConfigurationSource.registerCorsConfiguration("/**", corsConfiguration);
        return new CorsFilter(urlBasedCorsConfigurationSource);
    }

    @Override
    public void addInterceptors(InterceptorRegistry registry) {
        // 接口冪等性攔截器
        registry.addInterceptor(apiIdempotentInterceptor());
        super.addInterceptors(registry);
    }

    @Bean
    public ApiIdempotentInterceptor apiIdempotentInterceptor() {
        return new ApiIdempotentInterceptor();
    }

}

好了,以上便是程式碼的實作部分,下面我們就來驗證一下。

七、測試驗證

#取得token的控制器TokenController

@RestController
@RequestMapping("/token")
public class TokenController {

    @Autowired
    private TokenService tokenService;

    @GetMapping
    public ServerResponse token() {
        return tokenService.createToken();
    }

}

TestController, 注意@ApiIdempotent注解, 在需要冪等性校驗的方法上聲明此注解即可, 不需要校驗的無影響:

@RestController
@RequestMapping("/test")
@Slf4j
public class TestController {

    @Autowired
    private TestService testService;

    @ApiIdempotent
    @PostMapping("testIdempotence")
    public ServerResponse testIdempotence() {
        return testService.testIdempotence();
    }

}

獲取token

面試官:支付介面, 重複支付同一筆訂單只能扣一次錢, 怎麼做?

查看Redis

面試官:支付介面, 重複支付同一筆訂單只能扣一次錢, 怎麼做?

測試接口安全性: 利用Jmeter測試工具模擬50個并發(fā)請求, 將上一步獲取到的token作為參數(shù)

面試官:支付介面, 重複支付同一筆訂單只能扣一次錢, 怎麼做?
面試官:支付介面, 重複支付同一筆訂單只能扣一次錢, 怎麼做?

header或參數(shù)均不傳token, 或者token值為空, 或者token值亂填, 均無法通過校驗, 如token值為abcd。

面試官:支付介面, 重複支付同一筆訂單只能扣一次錢, 怎麼做?

八、注意點(非常重要)

面試官:支付介面, 重複支付同一筆訂單只能扣一次錢, 怎麼做?

上圖中, 不能單純的直接刪除token而不校驗是否刪除成功, 會出現(xiàn)並發(fā)安全性問題, 因為, 有可能多個線程同時走到第46行, 此時token還未被刪除,所以繼續(xù)往下執(zhí)行, 如果不校驗jedisUtil.del(token)的刪除結(jié)果而直接放行, 那麼還是會出現(xiàn)重複提交問題, 即使實際上只有一次真正的刪除操作, 下面重現(xiàn)一下。

稍微修改一下程式碼:

面試官:支付介面, 重複支付同一筆訂單只能扣一次錢, 怎麼做?

再一次要求

面試官:支付介面, 重複支付同一筆訂單只能扣一次錢, 怎麼做?

#再看看控制臺

面試官:支付介面, 重複支付同一筆訂單只能扣一次錢, 怎麼做?

雖然只有一個真正刪除掉token, 但由於沒有對刪除結(jié)果進(jìn)行校驗, 所以還是有並發(fā)問題, 因此, 必須校驗

九、總結(jié)

其實思路很簡單, 就是每次請求保證唯一性, 從而保證冪等性, 透過攔截器註解, 就不用每次請求都寫重複程式碼, 其實也可以利用Spring AOP實作。

好了,今天就分享到這裡。??

#

以上是面試官:支付介面, 重複支付同一筆訂單只能扣一次錢, 怎麼做?的詳細(xì)內(nèi)容。更多資訊請關(guān)注PHP中文網(wǎng)其他相關(guān)文章!

本網(wǎng)站聲明
本文內(nèi)容由網(wǎng)友自願投稿,版權(quán)歸原作者所有。本站不承擔(dān)相應(yīng)的法律責(zé)任。如發(fā)現(xiàn)涉嫌抄襲或侵權(quán)的內(nèi)容,請聯(lián)絡(luò)admin@php.cn

熱AI工具

Undress AI Tool

Undress AI Tool

免費脫衣圖片

Undresser.AI Undress

Undresser.AI Undress

人工智慧驅(qū)動的應(yīng)用程序,用於創(chuàng)建逼真的裸體照片

AI Clothes Remover

AI Clothes Remover

用於從照片中去除衣服的線上人工智慧工具。

Clothoff.io

Clothoff.io

AI脫衣器

Video Face Swap

Video Face Swap

使用我們完全免費的人工智慧換臉工具,輕鬆在任何影片中換臉!

熱工具

記事本++7.3.1

記事本++7.3.1

好用且免費的程式碼編輯器

SublimeText3漢化版

SublimeText3漢化版

中文版,非常好用

禪工作室 13.0.1

禪工作室 13.0.1

強(qiáng)大的PHP整合開發(fā)環(huán)境

Dreamweaver CS6

Dreamweaver CS6

視覺化網(wǎng)頁開發(fā)工具

SublimeText3 Mac版

SublimeText3 Mac版

神級程式碼編輯軟體(SublimeText3)

熱門話題

Laravel 教程
1597
29
PHP教程
1488
72
面試官:Spring Aop 常見註解和執(zhí)行順序 面試官:Spring Aop 常見註解和執(zhí)行順序 Aug 15, 2023 pm 04:32 PM

你一定知道 Spring , 那說說 Aop 的去全部通知順序, Spring Boot 或 Spring Boot 2 對 aop 的執(zhí)行順序影響?說說你在 AOP 中遇到的那些坑?

某團(tuán)面試:如果線上遇到了OOM,該如何檢查?如何解決?哪些方案? 某團(tuán)面試:如果線上遇到了OOM,該如何檢查?如何解決?哪些方案? Aug 23, 2023 pm 02:34 PM

OOM 意味著程式存在漏洞,可能是程式碼或 JVM 參數(shù)配置引起的。這篇文章跟讀者聊聊,Java 進(jìn)程觸發(fā)了 OOM 後如何排查。

餓了麼筆試題,看似簡單,難倒一批人 餓了麼筆試題,看似簡單,難倒一批人 Aug 24, 2023 pm 03:29 PM

在很多公司的筆試題中,千萬別小看,都是有坑的,一不小心自己就掉進(jìn)去了。遇到這種關(guān)於循環(huán)的筆試題,建議,自己冷靜思考,一步一步來。

上週,XX保險面試,涼了! ! ! 上週,XX保險面試,涼了! ! ! Aug 25, 2023 pm 03:44 PM

上週,一位群組裡的朋友去平安保險面試了,結(jié)果有些遺憾,蠻可惜的,但希望你不要氣餒,正如你所說的,面試中遇到的問題,基本上都是可以通過背面試題解決的,所以請加油!

5道String面試題,能全答對的人不到10%! (附答案) 5道String面試題,能全答對的人不到10%! (附答案) Aug 23, 2023 pm 02:49 PM

這篇來看看 Java String類別的 5 題面試題,這五題,我自己在面試過程中親身經(jīng)歷過幾題目,本篇就帶你了解這些題的答案為什麼是這樣。

小白也能與BAT面試官對線:CAS 小白也能與BAT面試官對線:CAS Aug 24, 2023 pm 03:09 PM

Java並發(fā)程式設(shè)計系列番外篇C A S(Compare and swap),文章風(fēng)格依然是圖文並茂,簡單易懂,讓讀者們也能與面試官瘋狂對線。

幾乎所有Java面試都會問到的問題:說ArrayList和LinkedList的差別 幾乎所有Java面試都會問到的問題:說ArrayList和LinkedList的差別 Jul 26, 2023 pm 03:11 PM

Java的資料結(jié)構(gòu)是面試考察的重點,只要參與Java面試的同學(xué)相信都有所體會。面試官問這類問題的時候往往是想檢視你是否研究過Java中常用資料類型的底層結(jié)構(gòu),而不是只是簡單的停留在"會使用"的層次。

面試官:說一下類別載入的過程(10張圖解) 面試官:說一下類別載入的過程(10張圖解) Aug 23, 2023 pm 03:05 PM

當(dāng)我們要使用一個類別的時候,要透過ClassLoader將類別載入到記憶體中。

See all articles