Rumah > Java > JavaSoalan temu bual > Penemuduga: Dalam antara muka pembayaran, wang hanya boleh ditolak sekali untuk pembayaran berulang untuk pesanan yang sama. Bagaimana untuk melakukan ini?

Penemuduga: Dalam antara muka pembayaran, wang hanya boleh ditolak sekali untuk pembayaran berulang untuk pesanan yang sama. Bagaimana untuk melakukan ini?

Lepaskan: 2023-08-22 15:57:31
ke hadapan
1410 orang telah melayarinya

. Nampak dari nada jawapannya dia sedang menghafal karangan berkaki lapan

.

Jadi, untuk membolehkan semua orang merasai dengan mudah pelaksanaan idempoten antara muka, Saudara Tian menyusun artikel ini hari ini

. Artikel ini mempunyai sembilan kandungan utama:

1. Konsep idempotence

imdempotence, dalam istilah orang awam, adalah antara muka yang memulakan permintaan yang sama beberapa kali . Ia mesti dipastikan bahawa operasi hanya boleh dilaksanakan sekali sahaja, contohnya:

  • Antaramuka pesanan, pesanan tidak boleh dibuat beberapa kali
  • Antara muka pembayaran, pembayaran berulang untuk pesanan yang sama hanya boleh ditolak sekali
  • Antara muka panggil balik Alipay, mungkin terdapat beberapa panggilan balik, panggilan balik berulang
    mesti diproses
  • Antara muka penyerahan borang biasa, disebabkan tamat masa rangkaian dan sebab lain, anda hanya boleh klik hantar beberapa kali, dan anda hanya boleh berjaya sekali dan seterusnya

2. Penyelesaian biasa

  1. Indeks unik--halang data kotor baharu
  2. mekanisme token--untuk mengelakkan penyerahan halaman berulang
  3. kunci pesimis--kunci apabila mendapatkan data (jadual kunci atau baris kunci)
  4. dilaksanakan-kunci-optimis berdasarkan nombor versi, semasa mengemas kini data Sahkan data dalam seketika
  5. Kunci teragih - redis (jedis, redisson) atau pelaksanaan penjaga zoo
keadaan mesin - keadaan perubahan, nilai status semasa mengemas kini data

3. Pelaksanaan artikel ini

Redis + token

🎜Artikel ini menggunakan cara kedua untuk melaksanakannya, iaitu melaksanakan pengesahan mati pucuk antara muka melalui mekanisme 🎜. 🎜

4. Idea pelaksanaan

Buat pengecam unik untuk setiap permintaan yang perlu memastikan hilang upayatoken, 先获取token, 并将此token存入redis, 请求接口时, 将此token放到header或者作为请求参数请求接口, 后端接口判断redis中是否存在此token:

  • Jika wujud, proses logik perniagaan seperti biasa dan padamkannya daripada redistoken, maka, jika ia adalah permintaan berulang, disebabkan oleh token telah dipadamkan, ia tidak boleh lulus pengesahan dan mengembalikan Sila jangan ulangi operasiPrompttoken, 那么, 如果是重复请求, 由于token已被删除, 则不能通过校验, 返回请勿重复操作提示
  • 如果不存在, 说明参数不合法或者是重复请求, 返回提示即可

五、项目简介

  • Spring Boot
  • Redis
  • @ApiIdempotent注解 + 拦截器对请求进行拦截
  • @ControllerAdvice全局异常处理
  • 压测工具: Jmeter
Jika ia tidak wujud, ia bermakna parameter tersebut tidak sah atau ia adalah permintaan berulang, pulangkan sahaja prompt

5. Pengenalan Projek

🎜Spring Boot🎜🎜🎜🎜Redis🎜🎜🎜🎜@ApiIdempotent anotasi + pemintas memintas permintaan🎜🎜🎜🎜@ControllerAdvicePengendalian pengecualian global🎜🎜🎜🎜Alat ujian tekanan: Jmeter🎜🎜🎜🎜Penjelasan:🎜🎜🎜Artikel ini memfokuskan pada pelaksanaan teras mati pucuk, tentang bagaimana Spring Boot Butiran penyepaduan redis, ServerResponse, ResponseCode dan butiran lain berada di luar skop artikel ini.🎜. warna latar belakang: rgba(27, 31, 35, 0.05); keluarga fon: 'Operator Mono', Consolas, Monaco, Menlo, monospace;patah perkataan: break-all;warna: rgb(239, 112, 96) ;">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>
Salin selepas log masuk

2、JedisUtil
@Component
@Slf4j
public class JedisUtil {

    @Autowired
    private JedisPool jedisPool;

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

    /**
     * 设值
     *
     * @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);
        }
    }

    /**
     * 设值
     *
     * @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);
        }
    }

    /**
     * 设值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();
        }
    }

}
Salin selepas log masuk
3、自定义注解@ApiIdempotent
/**
 * 在需要保证 接口幂等性 的Controller的方法上使用此注解
 */
@Target({ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
public @interface ApiIdempotent {
}
Salin selepas log masuk
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);// 幂等性校验, 校验通过则放行, 校验失败则抛出异常, 并通过统一异常处理返回友好提示
        }

        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 {
    }
}
Salin selepas log masuk
5、TokenServiceImpl< /code></h2><div class="code" style="position:relative; padding:0px; margin:0px;"><pre class="brush:php;toolbar:false;">@Service public class TokenServiceImpl implements TokenService { private static final String TOKEN_NAME = &quot;token&quot;; @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 &lt;= 0) { throw new ServiceException(ResponseCode.REPETITIVE_OPERATION.getMsg()); } } }</pre><div class="contentsignin">Salin selepas log masuk</div></div><p data-tool="mdnice编辑器" style="padding-top: 8px;padding-bottom: 8px;line-height: 26px;margin-top: 1px;margin-bottom: 1px;">6、<code style="font-size: 14px;padding: 2px 4px;border-radius: 4px;margin-right: 2px;margin-left: 2px;background-color: rgba(27, 31, 35, 0.05);font-family: 'Operator Mono', Consolas, Monaco, Menlo, monospace;word-break: break-all;color: rgb(239, 112, 96);">TestApplication maven依赖

@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();
    }

}
Salin selepas log masuk

2、JedisUtil

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

    @Autowired
    private TokenService tokenService;

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

}
Salin selepas log masuk
Salin selepas log masuk

3、自定义注解@ApiIdempotent

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

    @Autowired
    private TestService testService;

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

}
Salin selepas log masuk
Salin selepas log masuk

4、ApiIdempotentInterceptor 拦截器

rrreee

5、TokenServiceImpl

rrreee

6、TestApplication

rrreee

好了,以上便是代码的实现部分,下面我们就来验证一下。

七、测试验证

获取token的控制器TokenControllerrrreee

好了,以上便是代码的实现部分,下面我们就来验证一下。🎜🎜🎜耶和华🎜🎜 🎜🎜🎜获取token的控制器TokenController :🎜
@RestController
@RequestMapping("/token")
public class TokenController {

    @Autowired
    private TokenService tokenService;

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

}
Salin selepas log masuk
Salin selepas log masuk

TestController, 注意@ApiIdempotent注解, 在需要幂等性校验的方法上声明此注解即可, 不需要校验的无影响:

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

    @Autowired
    private TestService testService;

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

}
Salin selepas log masuk
Salin selepas log masuk

获取token

Penemuduga: Dalam antara muka pembayaran, wang hanya boleh ditolak sekali untuk pembayaran berulang untuk pesanan yang sama. Bagaimana untuk melakukan ini?

查看Redis

Penemuduga: Dalam antara muka pembayaran, wang hanya boleh ditolak sekali untuk pembayaran berulang untuk pesanan yang sama. Bagaimana untuk melakukan ini?

测试接口安全性: 利用Jmeter测试工具模拟50个并发请求, 将上一步获取到的token作为参数

Penemuduga: Dalam antara muka pembayaran, wang hanya boleh ditolak sekali untuk pembayaran berulang untuk pesanan yang sama. Bagaimana untuk melakukan ini?
Penemuduga: Dalam antara muka pembayaran, wang hanya boleh ditolak sekali untuk pembayaran berulang untuk pesanan yang sama. Bagaimana untuk melakukan ini?

header或参数均不传token, 或者token值为空, 或者token值乱填, 均无法通过校验, 如token值为abcd

Penemuduga: Dalam antara muka pembayaran, wang hanya boleh ditolak sekali untuk pembayaran berulang untuk pesanan yang sama. Bagaimana untuk melakukan ini?

8. Nota (sangat penting)

Penemuduga: Dalam antara muka pembayaran, wang hanya boleh ditolak sekali untuk pembayaran berulang untuk pesanan yang sama. Bagaimana untuk melakukan ini?

Dalam gambar di atas, anda tidak boleh memadamkan token secara terus tanpa mengesahkan sama ada pemadaman akan berjaya atau tidak. kerana, Ada kemungkinan beberapa utas boleh mencapai baris 46 pada masa yang sama Pada masa ini, token belum dipadamkan, jadi pelaksanaan diteruskan Jika hasil pemadaman jedisUtil.del(token) tidak disahkan dan dikeluarkan secara langsung, masalah berulang penyerahan masih akan berlaku, walaupun sebenarnya hanya terdapat operasi pemadaman sebenar, hasilkan semula di bawah. . Isu konkurensi, oleh itu, mesti disahkan

.

Baiklah, saya akan kongsikan di sini hari ini.

Atas ialah kandungan terperinci Penemuduga: Dalam antara muka pembayaran, wang hanya boleh ditolak sekali untuk pembayaran berulang untuk pesanan yang sama. Bagaimana untuk melakukan ini?. Untuk maklumat lanjut, sila ikut artikel berkaitan lain di laman web China PHP!

Label berkaitan:
sumber:Java后端技术全栈
Kenyataan Laman Web ini
Kandungan artikel ini disumbangkan secara sukarela oleh netizen, dan hak cipta adalah milik pengarang asal. Laman web ini tidak memikul tanggungjawab undang-undang yang sepadan. Jika anda menemui sebarang kandungan yang disyaki plagiarisme atau pelanggaran, sila hubungi admin@php.cn
Tutorial Popular
Lagi>
Muat turun terkini
Lagi>
kesan web
Kod sumber laman web
Bahan laman web
Templat hujung hadapan