大家好,我是工藤学编程 🦉 | 一个正在努力学习的小博主,期待你的关注 |
|---|---|
作业侠系列最新文章😉 | Java实现聊天程序 |
SpringBoot实战系列🐷 | 【【SpringBoot实战系列】AOP+自定义注解-接口防重提交多场景设计实战 |
环境搭建大集合 | 环境搭建大集合(持续更新) |
在本栏中,我们之前已经完成了: 【SpringBoot实战系列】之发送短信验证码 【SpringBoot实战系列】之从Async组件应用实战到ThreadPoolTaskExecutor⾃定义线程池 【SpringBoot实战系列】之图形验证码开发并池化Redis6存储 【SpringBoot实战系列】阿里云OSS接入上传图片实战 【SpringBoot实战系列】Sharding-Jdbc实现分库分表到分布式ID生成器Snowflake自定义wrokId实战 【SpringBoot实战系列】RabbitMQ实现消息发送并实现邮箱发送异常监控报警实战
本片速览: 1.AOP简介及好处 2.Spring⾥⾯的AOP常⻅概念 3.java核心知识-⾃定义注解 4.防重提交自定义注解实战 5.分布式锁 6.切面开发 7.测试结果
AOP简介及好处
Aspect Oriented Program ⾯向切⾯编程, 在不改变原有逻辑上增加额外的功能AOP思想把功能分两个部分,分离系统中的各种关注点 好处
Spring⾥⾯的AOP常⻅概念
java核心知识-⾃定义注解
防重提交自定义注解实战
@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
public @interface RepeatSubmit {
enum Type {PARAM,TOKEN}
Type limitType() default Type.PARAM;
long lockTime() default 5;
}分布式锁 redission依赖
<dependency>
<groupId>org.redisson</groupId>
<artifactId>redisson</artifactId>
<version>3.10.1</version>
</dependency>配置类
@Configuration
public class RedissionConfiguration {
@Value("${spring.redis.host}")
private String redisHost;
@Value("${spring.redis.port}")
private String redisPort;
@Value("${spring.redis.password}")
private String redisPwd;
/**
* 配置分布式锁的redisson
*
* @return
*/
@Bean
public RedissonClient redissonClient() {
Config config = new Config();
//单机⽅式
config.useSingleServer().setPassword(redisPwd).setAddress("redis://" + redisHost + ":" + redisPort);
//集群
RedissonClient redissonClient = Redisson.create(config);
return redissonClient;
}
/**
* 集群模式
* 备注:可以⽤"rediss://"来启⽤SSL连接
*/
/*@Bean
public RedissonClient redissonClusterClient() {
Config config = new Config();
config.useClusterServers().setScanInterval(2000) //
集群状态扫描间隔时间,单位是毫秒
.addNodeAddress("redis://127.0.0.1:7000")
.addNodeAddress("redis://127.0.0.1:7002");
RedissonClient redisson =
Redisson.create(config);
return redisson;
}*/
}切面开发:
@Aspect
@Component
@Slf4j
public class RepeatSubmitAspect {
@Autowired
private StringRedisTemplate redisTemplate;
@Autowired
private RedissonClient redissonClient;
/**
* 定义 @Pointcut注解表达式,
* ⽅式⼀:@annotation:当执⾏的⽅法上拥有指定的注解时
⽣效(我们采⽤这)
* ⽅式⼆:execution:⼀般⽤于指定⽅法的执⾏
*
* @param repeatSubmit
*/
@Pointcut("@annotation(repeatSubmit)")
public void pointCutNoRepeatSubmit(RepeatSubmit repeatSubmit) {
}
/**
* 环绕通知, 围绕着⽅法执⾏
* @Around 可以⽤来在调⽤⼀个具体⽅法前和调⽤后来完成⼀些具体的任务。
*
* ⽅式⼀:单⽤ @Around("execution(*net.xdclass.controller.*.*(..))")可以
* ⽅式⼆:⽤@Pointcut和@Around联合注解也可以(我们采⽤这个)
*
*
* 两种⽅式
* ⽅式⼀:加锁 固定时间内不能᯿复提交
* <p>
* ⽅式⼆:先请求获取token,这边再删除token,删除成功则是第⼀次提交
*
* @param joinPoint
* @param noRepeatSubmit
* @return
* @throws Throwable
*/
@Around("pointCutNoRepeatSubmit(repeatSubmit)")
public Object around(ProceedingJoinPoint joinPoint, RepeatSubmit repeatSubmit) throws Throwable {
HttpServletRequest request = ((ServletRequestAttributes) RequestContextHolder.getRequestAttributes()).getRequest();
Long accountNo = LoginInterceptor.threadLocal.get().getAccountNo();
boolean res = false;
String type = repeatSubmit.limitType().name();
if (type.equalsIgnoreCase(RepeatSubmit.Type.PARAM.name())) {
long lockTime = repeatSubmit.lockTime();
String ippAddr = CommonUtil.getIpAddr(request);
MethodSignature methodSignature = (MethodSignature) joinPoint.getSignature();
Method method = methodSignature.getMethod();
String className = method.getDeclaringClass().getName();
String key ="order-server-repeat-submit:"+CommonUtil.MD5(String.format("%s-%s-%s-%s", ippAddr, className, method, accountNo)) ;
//res=redisTemplate.opsForValue().setIfAbsent(key,"1",lockTime, TimeUnit.SECONDS);
RLock lock = redissonClient.getLock(key);
res = lock.tryLock(0, lockTime, TimeUnit.SECONDS);
} else {
String requestToken = request.getHeader("request-token");
if (StringUtils.isBlank(requestToken)) {
throw new BizException(BizCodeEnum.ORDER_CONFIRM_TOKEN_EQUAL_FAIL);
}
String key = String.format(RedisKey.SUBMIT_ORDER_TOKEN_KEY, accountNo, requestToken);
res = redisTemplate.delete(key);
}
if (!res) {
log.error("订单重复提交");
return null;
}
log.info("环绕通知前:{}", CommonUtil.getCurrentTimestamp());
Object obj = joinPoint.proceed();
log.info("环绕通知后:{}", CommonUtil.getCurrentTimestamp());
return obj;
}
}将自定义的注解加在对应想要防重提交的方法上即可 @PostMapping("page")
@RepeatSubmit
public JsonData page(@RequestBody OrderPageRequest orderPageRequest){
Map<String,Object>pageResult = productOrderService.page(orderPageRequest);
return JsonData.buildSuccess(pageResult);
}访问对应接口

本篇完!