开发的时候,人与人之间还是要少点儿信任之--注解方式防止重复请求

package com.example.student_flower;

import com.example.student_flower.service.StudentFlowerService;

import org.junit.jupiter.api.Test;

import org.springframework.beans.factory.annotation.Autowired;

import org.springframework.boot.test.context.SpringBootTest;

import java.util.concurrent.TimeUnit;

@SpringBootTest

class StudentFlowerApplicationTests {

@Autowired

StudentFlowerService service;

@Test

void sendFlower() throws InterruptedException {

final Long classroomId = 2L;

final Long studengId = 102L;

Thread thread1 = new Thread(() -> {

service.SendFlower(classroomId, studengId);

System.out.println(“thread1执行完了”);

});

Thread thread2 = new Thread(() -> {

service.SendFlower(classroomId, studengId);

System.out.println(“thread2执行完了”);

});

Thread thread3 = new Thread(() -> {

service.SendFlower(classroomId, studengId);

System.out.println(“thread3执行完了”);

});

thread1.start();

thread2.start();

thread3.start();

// 睡会儿 等三个线程跑完 很low? 做测试凑活用吧

Thread.sleep(TimeUnit.SECONDS.toMillis(20));

}

}

执行完看一下数据库结果:

v2-2ab009b7fd5f1dd186b671e8cd53cabd_b.png

这肯定是有问题的 多三条要出问题的,要扣钱绩效的

三、解决方案


解决方案有很多,我今天介绍一种自定义注解的方式(其实就是用了分布redis锁)

方案看似很简单:

v2-23d97c62336a7b18c5f26cb423c0e80b_b.jpg

自定义注解MyAnotation

package com.example.student_flower.common.anotation;

import java.lang.annotation.ElementType;

import java.lang.annotation.Retention;

import java.lang.annotation.RetentionPolicy;

import java.lang.annotation.Target;

/**

  • @author 发现更多精彩 关注公众号:木子的昼夜编程 分享一个生活在互联网底层做着增删改查的码农的感悟与学习

  • 关于自定义注解 后边有机会专门写一写 先会用

  • @create 2021-09-11 15:26

*/

@Target({ElementType.METHOD}) // 方法上使用的注解

@Retention(RetentionPolicy.RUNTIME) // 运行时通过反射访问

public @interface MyAnotation {

/**

  • 获取锁时默认等待多久

*/

int waitTime() default 3;

/**

  • 锁过期时间

*/

int expireTime() default 20;

/**

  • 锁key值

*/

String redisKey() default “”;

/**

  • 锁key后拼接的动态参数的值

*/

String[] params() default {};

}

自定义切面处理逻辑,进行放重复提交校验MyAspect

package com.example.student_flower.common.aspect;

import com.example.student_flower.common.anotation.MyAnotation;

import com.example.student_flower.util.HttpContextUtils;

import com.example.student_flower.util.SpelUtil;

import io.micrometer.core.instrument.util.StringUtils;

import lombok.extern.slf4j.Slf4j;

import org.aspectj.lang.ProceedingJoinPoint;

import org.aspectj.lang.annotation.Around;

import org.aspectj.lang.annotation.Aspect;

import org.aspectj.lang.annotation.Pointcut;

import org.aspectj.lang.reflect.MethodSignature;

import org.redisson.api.RLock;

import org.redisson.api.RedissonClient;

import org.springframework.beans.factory.annotation.Autowired;

import org.springframework.stereotype.Component;

import javax.servlet.http.HttpServletRequest;

import java.lang.reflect.Method;

import java.util.concurrent.TimeUnit;

/**

  • @author 发现更多精彩 关注公众号:木子的昼夜编程

  • 一个生活在互联网底层,做着增删改查的码农,不谙世事的造作

  • 关于spring面向切面的知识 等以后文章有机会我写一写(自己也不太熟 暂时会用)

  • @create 2021-09-11 15:29

*/

@Slf4j

@Aspect

@Component

public class MyAspect {

@Autowired

RedissonClient redissonClient;

// 这个是那些方法需要被切 – 被标记注解MyAnotation的方法要被切

@Pointcut(“@annotation(com.example.student_flower.common.anotation.MyAnotation)”)

public void whichMethodAspect() {

}

/**

  • 切面 执行业务逻辑 在实际业务方法执行前 后 都可以进行一些额外的操作

  • 切面的好处就是对你不知不觉

*/

@Around(“whichMethodAspect()”)

public Object around(ProceedingJoinPoint joinPoint) throws Throwable {

// 1. 获取注解

MethodSignature signature = (MethodSignature) joinPoint.getSignature();

Method method = signature.getMethod();

MyAnotation myAnotation = method.getAnnotation(MyAnotation.class);

// 2. 锁等待时间

int waitTime = myAnotation.waitTime();

// 2. 锁超时时间 怕万一finally没有被执行到的时候 多长时间自动释放锁(基本不会不执行finnaly 除非那个点机器down了)

final int lockSeconds = myAnotation.expireTime();

// 3. 特殊业务自定义key

String key = myAnotation.redisKey();

// 自定义redisKey是否使用参数

String[] params = myAnotation.params();

// 4.获取HttpServletRequest

HttpServletRequest request = HttpContextUtils.getRequest();

if (request == null) {

throw new Exception(“错误的请求 request为null”);

}

assert request != null;

// 5. 组合redis锁key

// 5.1 如果没有自定义 用默认的 url+token

if (StringUtils.isBlank(key) && (params == null || params.length == 0)) {

// 这里怎么获取token 主要看自己项目用的什么框架 token在哪个位置存储着

String token = request.getHeader(“Authorization”);

String requestURI = request.getRequestURI();

key = requestURI+token;

} else {

// 5.2 自定义key

key = SpelUtil.generateKeyBySpEL(key, params, joinPoint);

}

// 6. 获取key

// 获取锁 获取不到最多等waitTime秒 lockSeconds秒后自动释放锁

// 每个项目组应该会有自己的redisUtil的封装 我这里就用最简单的方式

// 怎么使用锁不是重点 重点是这个思想

RLock lock = redissonClient.getLock(key);

log.info(“tryLock key = {}”, key);

boolean b = lock.tryLock(waitTime, lockSeconds, TimeUnit.SECONDS);

// 获取锁成功

if (b) {

try {

log.info(“tryLock success, key = {}”, key);

// 7. 执行业务代码 返回结果

return joinPoint.proceed();

} finally {

lock.unlock();

}

} else {

// 获取锁失败

log.info(“tryLock fail, key = {}”, key);

throw new Exception(“请求频繁,请稍后重试”);

}

}

}

Redisson配置RedissonConfig

package com.example.student_flower;

import org.redisson.Redisson;

import org.redisson.api.RedissonClient;

import org.redisson.config.Config;

import org.springframework.context.annotation.Bean;

/**

  • @author 发现更多精彩 关注公众号:木子的昼夜编程

  • 一个生活在互联网底层,做着增删改查的码农,不谙世事的造作

  • @create 2021-09-11 16:31

*/

public class RedissonConfig {

// 这里就简单设置 真实项目中会做到配置文件或配置中心

@Bean

public RedissonClient getRedisson() {

Config config = new Config();

config.useSingleServer().setAddress(“redis://127.0.0.1:6379”);

return Redisson.create(config);

}

}

获取request对象HttpContextUtils

package com.example.student_flower.util;

import org.springframework.web.context.request.RequestContextHolder;

import org.springframework.web.context.request.ServletRequestAttributes;

import javax.servlet.http.HttpServletRequest;

import javax.servlet.http.HttpServletResponse;

/**

  • @author 发现更多精彩 关注公众号:木子的昼夜编程

  • 一个生活在互联网底层,做着增删改查的码农,不谙世事的造作

  • @create 2021-09-11 16:17

  • 获取springboot环境中的request/response对象

*/

public class HttpContextUtils {

// 获取request

public static HttpServletRequest getRequest(){

ServletRequestAttributes servletRequestAttributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();

HttpServletRequest request = servletRequestAttributes.getRequest();

return request;

}

// 获取response

public static HttpServletResponse getResponse(){

ServletRequestAttributes servletRequestAttributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();

HttpServletResponse response = servletRequestAttributes.getResponse();

return response;

}

}

El表达式解析 SpelUtil

package com.example.student_flower.util;

import java.lang.reflect.Method;

import org.aspectj.lang.ProceedingJoinPoint;

import org.aspectj.lang.reflect.MethodSignature;

import org.springframework.core.DefaultParameterNameDiscoverer;

import org.springframework.expression.EvaluationContext;

import org.springframework.expression.Expression;

import org.springframework.expression.spel.standard.SpelExpressionParser;

import org.springframework.expression.spel.support.StandardEvaluationContext;

/**

  • @author 发现更多精彩 关注公众号:木子的昼夜编程

  • 一个生活在互联网底层,做着增删改查的码农,不谙世事的造作

  • @create 2021-09-11 15:35

*/

/**

  • EL表达式解析

*/

public class SpelUtil {

/**

  • 用于SpEL表达式解析.

*/

private static SpelExpressionParser parser = new SpelExpressionParser();

/**

  • 用于获取方法参数定义名字.

*/

private static DefaultParameterNameDiscoverer nameDiscoverer = new DefaultParameterNameDiscoverer();

/**

  • 解析表达式

*/

public static String generateKeyBySpEL(String key, String[] params, ProceedingJoinPoint joinPoint) {

StringBuilder spELString = new StringBuilder();

if (params != null && params.length > 0) {

spELString.append(“'” + key + “'”);

for (int i = 0; i < params.length; i++) {

spELString.append(“+#” + params[i]);

}

} else {

return key;

}

// 通过joinPoint获取被注解方法

MethodSignature methodSignature = (MethodSignature) joinPoint.getSignature();

Method method = methodSignature.getMethod();

// 使用spring的DefaultParameterNameDiscoverer获取方法形参名数组

String[] paramNames = nameDiscoverer.getParameterNames(method);

// 解析过后的Spring表达式对象

Expression expression = parser.parseExpression(spELString.toString());

// spring的表达式上下文对象

EvaluationContext context = new StandardEvaluationContext();

// 通过joinPoint获取被注解方法的形参

Object[] args = joinPoint.getArgs();

// 给上下文赋值

for (int i = 0; i < args.length; i++) {

context.setVariable(paramNames[i], args[i]);

}

return expression.getValue(context).toString();

}

}

controller使用注解:

package com.example.student_flower.controller;

import com.example.student_flower.common.anotation.MyAnotation;

import com.example.student_flower.service.StudentFlowerService;

import com.sun.istack.internal.NotNull;

import org.springframework.beans.factory.annotation.Autowired;

import org.springframework.web.bind.annotation.GetMapping;

import org.springframework.web.bind.annotation.PathVariable;

import org.springframework.web.bind.annotation.RestController;

/**

  • @author 发现更多精彩 关注公众号:木子的昼夜编程

  • 一个生活在互联网底层,做着增删改查的码农,不谙世事的造作

  • @create 2021-09-11 10:35

*/

@RestController

public class StudentFlowerController {

@Autowired

StudentFlowerService studentFlowerService;

/**

  • @param classroomId 教师ID

  • @param studentId 学生ID

*/

@MyAnotation(redisKey = “/test/sendflower”, params = {“classroomId”, “studentId”})

@GetMapping(value = “/test/sendflower/{classroomId}/{studentId}”)

public void sendFlower(@NotNull @PathVariable(“classroomId”) Long classroomId , @NotNull @PathVariable(“studentId”) Long studentId){

studentFlowerService.SendFlower(classroomId,studentId);

}

}

测试类(这里用了MockMvc直接测试controller)

package com.example.student_flower;

import org.junit.jupiter.api.Test;

import org.springframework.beans.factory.annotation.Autowired;

import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvc;

import org.springframework.boot.test.context.SpringBootTest;

import org.springframework.http.MediaType;

自我介绍一下,小编13年上海交大毕业,曾经在小公司待过,也去过华为、OPPO等大厂,18年进入阿里一直到现在。

深知大多数Java工程师,想要提升技能,往往是自己摸索成长或者是报班学习,但对于培训机构动则几千的学费,着实压力不小。自己不成体系的自学效果低效又漫长,而且极易碰到天花板技术停滞不前!

因此收集整理了一份《2024年Java开发全套学习资料》,初衷也很简单,就是希望能够帮助到想自学提升又不知道该从何学起的朋友,同时减轻大家的负担。img

既有适合小白学习的零基础资料,也有适合3年以上经验的小伙伴深入学习提升的进阶课程,基本涵盖了95%以上Java开发知识点,真正体系化!

由于文件比较大,这里只是将部分目录截图出来,每个节点里面都包含大厂面经、学习笔记、源码讲义、实战项目、讲解视频,并且会持续更新!

如果你觉得这些内容对你有帮助,可以扫码获取!!(备注Java获取)

img

技术学习总结

学习技术一定要制定一个明确的学习路线,这样才能高效的学习,不必要做无效功,既浪费时间又得不到什么效率,大家不妨按照我这份路线来学习。

最后面试分享

大家不妨直接在牛客和力扣上多刷题,同时,我也拿了一些面试题跟大家分享,也是从一些大佬那里获得的,大家不妨多刷刷题,为金九银十冲一波!

《一线大厂Java面试题解析+核心总结学习笔记+最新讲解视频+实战项目源码》点击传送门即可获取!
[外链图片转存中…(img-mmEjC8qF-1712435399477)]

既有适合小白学习的零基础资料,也有适合3年以上经验的小伙伴深入学习提升的进阶课程,基本涵盖了95%以上Java开发知识点,真正体系化!

由于文件比较大,这里只是将部分目录截图出来,每个节点里面都包含大厂面经、学习笔记、源码讲义、实战项目、讲解视频,并且会持续更新!

如果你觉得这些内容对你有帮助,可以扫码获取!!(备注Java获取)

img

技术学习总结

学习技术一定要制定一个明确的学习路线,这样才能高效的学习,不必要做无效功,既浪费时间又得不到什么效率,大家不妨按照我这份路线来学习。

[外链图片转存中…(img-9FbUFo6P-1712435399477)]

[外链图片转存中…(img-DMzRbyyc-1712435399478)]

[外链图片转存中…(img-DM6yMAP4-1712435399478)]

最后面试分享

大家不妨直接在牛客和力扣上多刷题,同时,我也拿了一些面试题跟大家分享,也是从一些大佬那里获得的,大家不妨多刷刷题,为金九银十冲一波!

[外链图片转存中…(img-n6cng17S-1712435399478)]

[外链图片转存中…(img-KA5d024t-1712435399478)]

《一线大厂Java面试题解析+核心总结学习笔记+最新讲解视频+实战项目源码》点击传送门即可获取!

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值