Spring Boot学习总结(21)——SpringBoot集成Redis等缓存以注解的方式优雅实现幂等,防千万次重复提交实例代码

前言

在实际的开发项目中,一个对外暴露的接口往往会面临很多次请求,我们来解释一下幂等的概念:任意多次执行所产生的影响均与一次执行的影响相同。按照这个含义,最终的含义就是 对数据库的影响只能是一次性的,不能重复处理。如何保证其幂等性,通常有以下手段:

  1. 数据库建立唯一性索引,可以保证最终插入数据库的只有一条数据
  2. token机制,每次接口请求前先获取一个token,然后再下次请求的时候在请求的header体中加上这个token,后台进行验证,如果验证通过删除token,下次请求再次判断token
  3. 悲观锁或者乐观锁,悲观锁可以保证每次for update的时候其他sql无法update数据(在数据库引擎是innodb的时候,select的条件必须是唯一索引,防止锁全表)
  4. 先查询后判断,首先通过查询数据库是否存在数据,如果存在证明已经请求过了,直接拒绝该请求,如果没有存在,就证明是第一次进来,直接放行。

redis实现自动幂等的原理图:

一、自定义注解AutoIdempotent

/**
 * @ClassName AutoIdempotent
 * @Description (自动幂等注解:把它添加在需要实现幂等的方法上,凡是某个方法注解了它,都会实现自动幂等。后台利用反射如果扫描到这个注解,就会处理这个方法实现自动幂等) 
 * @author ZHY 
 * @date 2020年4月1日 上午10:51:10 
 * @Copyright © 2020【www.zhy.com Inc. All rights reserved.】
 */
@Target({ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
public @interface AutoIdempotent {

}

二、Token的创建与校验

/**
	 * @method checkIdempotentToken(校验幂等的token) 
	 * @param request
	 * @return Boolean 
	 * @author ZHY
	 * @date 2020年4月1日 下午6:58:48
	 */
	public static Boolean checkIdempotentToken(HttpServletRequest request) {
		String token = request.getHeader(SysConstants.HTTP_IDEMPOTENT_HEADER_NAME);
		// header中不存在Idempotent Token
		if (StringUtils.isBlank(token)) {
			throw new VipException(ServiceErrorEnum.IDEMPOTENT_TOKEN_FAILURE);
		}
		boolean exists = J2CacheUtil.existsKey(SysConstants.IDEMPOTENT_TOKEN_REGION, token);
		if (!exists) {
			// 重复操作
			throw new VipException(ServiceErrorEnum.REPETITIVE_OPERATION);
		}
		J2CacheUtil.remove(SysConstants.IDEMPOTENT_TOKEN_REGION, token);
		return true;
	}
	
	/**
	 * @method createIdempotentToken(创建幂等校验用的token并且缓存) 
	 * @return String 
	 * @author ZHY
	 * @date 2020年4月1日 下午7:21:21
	 */
	public static String createIdempotentToken() {
		String idepotentToken = TokenUtil.generateToken();
		J2CacheUtil.set(SysConstants.IDEMPOTENT_TOKEN_REGION, idepotentToken, idepotentToken);
		return idepotentToken;
	}

四、拦截器的配置

/**
 * @ClassName AutoIdempotentInterceptor
 * @Description (自动幂等拦截器)
 * @author ZHY
 * @date 2020年4月1日 上午10:57:12
 * @Copyright © 2020【www.zhy.com Inc. All rights reserved.】
 */
@Component
public class AutoIdempotentInterceptor implements HandlerInterceptor {

	@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) {
			Boolean checkIdempotentToken = VipCoreUtil.checkIdempotentToken(request);
			// 幂等性校验, 校验通过则放行, 校验失败则抛出异常, 并通过统一异常处理返回友好提示
			if (!checkIdempotentToken) {
				throw new VipException(ServiceErrorEnum.IDEMPOTENT_CHECK_FAIL);
			}
		}
		return true;
	}
}
/**
 * @ClassName WebMvcConfig
 * @Description (webmvc 配置) 
 * @author ZHY 
 * @date 2020年4月1日 上午9:21:12 
 * @Copyright © 2020【www.zhy.com Inc. All rights reserved.】
 */
@Configuration
public class WebMvcConfig implements WebMvcConfigurer {

	@Autowired
	private AuthorizationInterceptor authorizationInterceptor;
	
	@Autowired
	private AutoIdempotentInterceptor autoIdempotentInterceptor;

	@Override
	public void addInterceptors(InterceptorRegistry registry) {
		registry.addInterceptor(authorizationInterceptor).addPathPatterns("/**");
		registry.addInterceptor(autoIdempotentInterceptor);
	}
	
	@Bean
	public HttpMessageConverters fastJsonHttpMessageConverters() {
		
		FastJsonHttpMessageConverter fastJsonHttpMessageConverter = new FastJsonHttpMessageConverter();
		FastJsonConfig fastJsonConfig = new FastJsonConfig();
		fastJsonConfig.setSerializerFeatures(SerializerFeature.WriteMapNullValue);
		List<MediaType> fastMediaTypes = new ArrayList<>();
		fastMediaTypes.add(MediaType.APPLICATION_JSON_UTF8);
		fastJsonHttpMessageConverter.setSupportedMediaTypes(fastMediaTypes);
		fastJsonHttpMessageConverter.setFastJsonConfig(fastJsonConfig);
		HttpMessageConverter<?> converter = fastJsonHttpMessageConverter;
		return new HttpMessageConverters(converter);
	}
}

五、测试用例

@RestController
public class TestAutoIdempotentController {

	@PostMapping("/get/token")
	public String getToken() {
		return ZhyUtil.createIdempotentToken();
	}

	@AutoIdempotent
	@PostMapping("/test/Idempotence")
	public String testIdempotence() {
		
		return "success";
	}
}

六、总结

对于幂等在实际的开发过程中是十分重要的,因为一个接口可能会被无数的客户端调用,如何保证其不影响后台的业务处理,如何保证其只影响数据一次是非常重要的,它可以防止产生脏数据或者乱数据,也可以减少并发量,实乃十分有益的一件事。而传统的做法是每次判断数据,这种做法不够智能化和自动化,比较麻烦。通过注解的这种自动化处理也可以提升程序的伸缩性。

  • 1
    点赞
  • 9
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
好的,下面是一个简单的 Spring Boot Redis 缓存代码实例: 首先需要在 pom.xml 中添加 Redis 相关依赖: ```xml <dependencies> <!-- Spring Boot Redis 相关依赖 --> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-data-redis</artifactId> </dependency> <dependency> <groupId>redis.clients</groupId> <artifactId>jedis</artifactId> </dependency> <!-- 其他依赖省略 --> </dependencies> ``` 然后在 application.properties 文件中配置 Redis 连接信息: ```properties # Redis 连接信息 spring.redis.host=127.0.0.1 spring.redis.port=6379 spring.redis.password= spring.redis.database=0 ``` 接下来定义一个 Redis 缓存管理器的 Bean: ```java import org.springframework.cache.CacheManager; import org.springframework.cache.annotation.EnableCaching; import org.springframework.cache.concurrent.ConcurrentMapCacheManager; import org.springframework.cache.interceptor.KeyGenerator; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; @Configuration @EnableCaching public class RedisConfig { // 定义 Redis 缓存管理器的 Bean @Bean public CacheManager cacheManager() { return new ConcurrentMapCacheManager(); // 使用内存缓存 } // 定义 Redis 缓存的 Key 生成器的 Bean @Bean public KeyGenerator keyGenerator() { return (target, method, params) -> { StringBuilder sb = new StringBuilder(); sb.append(target.getClass().getName()); sb.append(":"); sb.append(method.getName()); for (Object obj : params) { sb.append(":"); sb.append(obj.toString()); } return sb.toString(); }; } } ``` 最后在需要使用缓存的 Service 类中添加 @Cacheable、@CachePut、@CacheEvict 等注解即可: ```java import org.springframework.cache.annotation.CacheEvict; import org.springframework.cache.annotation.Cacheable; import org.springframework.stereotype.Service; @Service public class UserService { // 使用 @Cacheable 注解实现缓存 @Cacheable(value = "userCache", keyGenerator = "keyGenerator") public User getUserById(Long id) { // 从数据库中查询用户信息 User user = userRepository.findById(id).orElse(null); return user; } // 使用 @CachePut 注解更新缓存 @CachePut(value = "userCache", keyGenerator = "keyGenerator") public User updateUser(User user) { // 更新用户信息到数据库 user = userRepository.save(user); return user; } // 使用 @CacheEvict 注解清除缓存 @CacheEvict(value = "userCache", keyGenerator = "keyGenerator") public void deleteUser(Long id) { // 从数据库中删除用户信息 userRepository.deleteById(id); } } ``` 以上就是一个简单的 Spring Boot Redis 缓存代码实例

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

科技D人生

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值