标签:很多 就是 rip version 目的 head 键值 classname redis
接口幂等性就是用户对于同一操作发起的一次请求或者多次请求的结果是一致的,不会因为多次点击而产生了副作用。举个最简单的例子,支付过程中,用户购买商品后支付,支付扣款成功,但是返回结果的时候网络异常,此时钱已经扣了,用户再次点击按钮,此时会进行第二次扣款,返回结果成功,用户查询余额返发现多扣钱了,流水记录也变成了两条,这就没有保证接口的幂等性。
简单的说就是 一个用户对于同一个操作发起一次或多次的请求,请求的结果一致。不会因为多次点击而产生多条数据。
1、创建一个springboot项目。
2、maven依赖:
<dependencies>
<!-- mysql:MyBatis相关依赖 -->
<dependency>
<groupId>org.mybatis.spring.boot</groupId>
<artifactId>mybatis-spring-boot-starter</artifactId>
<version>2.0.0</version>
</dependency>
<!-- mysql:mysql驱动 -->
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
</dependency>
<!-- mysql:阿里巴巴数据库连接池 -->
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>druid</artifactId>
<version>1.1.12</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter</artifactId>
</dependency>
<!--springboot data redis-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
</dependencies>
3、redis配置
redis: host: 127.0.0.1 port: 6379 database: 0 timeout: 1000 password: 961230 lettuce: pool: max-active: 100 max-wait: -1 min-idle: 0 max-idle: 20 ssl: false
4、创建controller层
package com.dongliang.controller; import com.dongliang.service.TokenUtilService; import lombok.extern.slf4j.Slf4j; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.PostMapping; import org.springframework.web.bind.annotation.RequestHeader; import org.springframework.web.bind.annotation.RestController; /** * @author D-L * @version 1.0.0 * @ClassName TokenController.java * @Description TODO * @createTime 2021-05-24 16:40:00 */ @Slf4j @RestController public class TokenController { @Autowired private TokenUtilService tokenService; /** * 获取 Token 接口 * @return Token 串 */ @GetMapping("/token") public String getToken() { // 获取用户信息(这里使用模拟数据) // 注:这里存储该内容只是举例,其作用为辅助验证,使其验证逻辑更安全,如这里存储用户信息,其目的为: // - 1)、使用"token"验证 Redis 中是否存在对应的 Key // - 2)、使用"用户信息"验证 Redis 的 Value 是否匹配。 String userInfo = "mydlq"; // 获取 Token 字符串,并返回 return tokenService.generateToken(userInfo); } /** * 接口幂等性测试接口 * * @param token 幂等 Token 串 * @return 执行结果 */ @PostMapping("/test") public String test(@RequestHeader(value = "token") String token) { // 获取用户信息(这里使用模拟数据) String userInfo = "mydlq"; // 根据 Token 和与用户相关的信息到 Redis 验证是否存在对应的信息 boolean result = tokenService.validToken(token, userInfo); // 根据验证结果响应不同信息 return result ? "正常调用" : "重复调用"; } }
5、创建service类
package com.dongliang.service; import lombok.extern.slf4j.Slf4j; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.data.redis.core.StringRedisTemplate; import org.springframework.data.redis.core.script.DefaultRedisScript; import org.springframework.data.redis.core.script.RedisScript; import org.springframework.stereotype.Service; import java.util.Arrays; import java.util.UUID; import java.util.concurrent.TimeUnit; /** * @author D-L * @version 1.0.0 * @ClassName TokenUtilService.java * @Description TODO * @createTime 2021-05-24 16:27:00 */ @Slf4j @Service public class TokenUtilService { @Autowired private StringRedisTemplate redisTemplate; /** * 存入 Redis 的 Token 键的前缀 */ private static final String IDEMPOTENT_TOKEN_PREFIX = "idempotent_token:"; /** * 创建 Token 存入 Redis,并返回该 Token * * @param value 用于辅助验证的 value 值 * @return 生成的 Token 串 */ public String generateToken(String value) { // 实例化生成 ID 工具对象 String token = UUID.randomUUID().toString(); // 设置存入 Redis 的 Key String key = IDEMPOTENT_TOKEN_PREFIX + token; // 存储 Token 到 Redis,且设置过期时间为5分钟 redisTemplate.opsForValue().set(key, value, 5, TimeUnit.MINUTES); // 返回 Token return token; } /** * 验证 Token 正确性 * * @param token token 字符串 * @param value value 存储在Redis中的辅助验证信息 * @return 验证结果 */ public boolean validToken(String token, String value) { // 设置 Lua 脚本,其中 KEYS[1] 是 key,KEYS[2] 是 value String script = "if redis.call(‘get‘, KEYS[1]) == KEYS[2] then return redis.call(‘del‘, KEYS[1]) else return 0 end"; RedisScript<Long> redisScript = new DefaultRedisScript<>(script, Long.class); // 根据 Key 前缀拼接 Key String key = IDEMPOTENT_TOKEN_PREFIX + token; // 执行 Lua 脚本 Long result = redisTemplate.execute(redisScript, Arrays.asList(key, value)); // 根据返回结果判断是否成功成功匹配并删除 Redis 键值对,若果结果不为空和0,则验证通过 if (result != null && result != 0L) { log.info("验证 token={},key={},value={} 成功", token, key, value); return true; } log.info("验证 token={},key={},value={} 失败", token, key, value); return false; } }
标签:很多 就是 rip version 目的 head 键值 classname redis
原文地址:https://www.cnblogs.com/dongl961230/p/14817223.html