当前位置: 首页 > news >正文

网站设置的参数网站建设的一般流程排序为

网站设置的参数,网站建设的一般流程排序为,网站设计的六个因素,作文网大全#x1f4dd;个人主页#xff1a;哈__ 期待您的关注 目录 #x1f33c;前言 #x1f512;单机环境下防止接口重复提交 #x1f4d5;导入依赖 #x1f4c2;项目结构 #x1f680;创建自定义注解 ✈创建AOP切面 #x1f697;创建Conotroller #x1f4bb;分布… 个人主页哈__ 期待您的关注  目录 前言  单机环境下防止接口重复提交 导入依赖 项目结构  创建自定义注解 ✈创建AOP切面  创建Conotroller  分布式环境下防止接口重复提交 导入依赖 项目结构 创建自定义注解 创建key的生成工具类  创建Redis工具类 创建AOP切面类 创建Controller  前言  在Web应用开发过程中接口重复提交问题一直是一个需要重点关注和解决的难题。无论是由于用户误操作、网络延迟导致的重复点击还是由于恶意攻击者利用自动化工具进行接口轰炸都可能对系统造成严重的负担甚至导致数据不一致、服务不可用等严重后果。特别是在SpringBoot这样的现代化Java框架中我们更需要一套行之有效的策略来防止接口重复提交。 本文将从SpringBoot应用的角度出发探讨在单机环境和分布式环境下如何有效防止接口重复提交。单机环境虽然相对简单但基本的防护策略同样适用于分布式环境的部署。 接下来我们将首先分析接口重复提交的原因和危害然后详细介绍在SpringBoot应用中可以采取的防护策略包括前端控制、后端校验、使用令牌机制如Token、利用数据库的唯一约束等。对于分布式环境我们还将探讨如何使用分布式锁、Redis等中间件来确保数据的一致性和防止接口被重复调用。 在深入解析各种防护策略的同时我们也将结合实际案例展示如何在SpringBoot项目中具体实现这些策略并给出一些优化建议以帮助读者在实际开发中更好地应用这些技术。希望通过本文的介绍读者能够掌握在SpringBoot应用中防止接口重复提交的有效方法为Web应用的稳定性和安全性提供坚实的保障。 单机环境下防止接口重复提交 在这种单机的应用场景下我并没有使用redis进行处理而是使用了本地缓存机制。在用户对接口进行访问的时候我们获取接口的一些参数信息并且根据这些参数生成一个唯一的ID存储到缓存中下一次在发送请求的时候先判断这个缓存中是否有对应的ID若有则阻拦若没有那么就放行。 导入依赖 dependencygroupIdorg.springframework.boot/groupIdartifactIdspring-boot-starter-web/artifactId/dependencydependencygroupIdorg.springframework.boot/groupIdartifactIdspring-boot-starter-aop/artifactId/dependencydependencygroupIdcom.google.guava/groupIdartifactIdguava/artifactIdversion21.0/version/dependency 项目结构  创建自定义注解 我们也说过了要根据接口的一些信息来生成一个ID在单机环境下我定义了一个注解这个注解里边保存着一个key作为ID同时在把这个注解加到接口上那么这个接口就以这个key作为ID在访问接口的时候存储的也是这个ID值。 Target(ElementType.METHOD) Retention(value RetentionPolicy.RUNTIME) Documented Inherited public interface LockCommit {String key() default ; } ✈创建AOP切面  为了方便之后的接口限流同时也想把这件事情做一个模块化处理我使用的是AOP切面这样做可以减少代码耦合方便维护。 看过我之前文章的朋友应该都知道我喜欢使用注解来实现AOP了这里定义了一个pointCut()切入点表达式是注解类型。如果你还不会AOP的话可以来看一看我的这篇文章。【Spring】Spring中AOP的简介和基本使用SpringBoot使用AOP-CSDN博客 此外使用了一个Cache本地缓存用于存储我们接口的ID同时设置缓存的最大容量和内容的过期时间在这里我设置的是5秒钟5秒钟过后ID就会过期这个接口就可以继续访问。  主要的就是这个环绕通知了我先获取了调用的接口也就是具体的方法之后获取加在这个方法上的注解LockCommit也就是我们上边自定义的注解。之后拿到注解内的key作为ID传入缓存中。存入之前先判断是否有这个ID如果有就报错没有就加入到缓存中这个逻辑不难。 Aspect Component public class LockAspect {public static final CacheString,Object CACHES CacheBuilder.newBuilder().maximumSize(50).expireAfterWrite(5, TimeUnit.SECONDS).build();Pointcut(annotation(com.example.day_04_repeat_commit.annotation.LockCommit)execution(* com.example.day_04_repeat_commit.controller.*.*(..)))public void pointCut(){}Around(pointCut())public Object Lock(ProceedingJoinPoint joinPoint){MethodSignature methodSignature (MethodSignature) joinPoint.getSignature();Method method methodSignature.getMethod();LockCommit lockCommit method.getAnnotation(LockCommit.class);String key lockCommit.key();if(key!null !.equals(key)){if(CACHES.getIfPresent(key)!null){throw new RuntimeException(请勿重复提交);}CACHES.put(key,key);}Object object null;try {object joinPoint.proceed();} catch (Throwable e) {e.printStackTrace();}return object;} } 创建Conotroller  可以看到我在接口上加上了key是stu对接口访问后stu就作为ID保存到CACHE中。这里需要多加注意如果是多个人访问这个接口那么都会出现防止重复提交的问题所以这个key的值并不能仅仅设置的这么简单。可以加入一些用户ID参数的值IP等信息作为key的构建参数。这里我仅仅是为了演示。 RestController RequestMapping(/student) public class StudentController {RequestMapping(/get-student)LockCommit(key stu)public String getStudent(){return 张三;} } 如果你不想要后台报错而是把错误的提示信息传到前端的话那么你就可以创建一个全局的异常捕获器。我创建的这个异常捕获器捕获的是Exception异常范围比较大如果在真实的开发环境中你可能需要自定义异常来抛出和捕获。 RestControllerAdvice public class GlobalExceptionHandler {ExceptionHandler(Exception.class)public String handleException(Exception e){return e.getMessage();} } 接着我们启动项目来测试一下。为了方便截图我就不用浏览器打开了我是用PostMan进行测试。 第一次访问结果如下五秒内再次访问结果如下五秒后访问结果如下 分布式环境下防止接口重复提交 导入依赖 dependencygroupIdorg.springframework.boot/groupIdartifactIdspring-boot-starter-web/artifactId/dependencydependencygroupIdorg.springframework.boot/groupIdartifactIdspring-boot-starter-data-redis/artifactId/dependencydependencygroupIdorg.springframework.boot/groupIdartifactIdspring-boot-starter-aop/artifactId/dependency 项目结构 创建自定义注解 分布式环境下的就要复杂一些了  创建CacheLock Retention(RetentionPolicy.RUNTIME) Target(ElementType.METHOD) Documented Inherited public interface CacheLock {/*** 锁的前缀* return*/String prefix() default ;/*** 过期时间* return*/int expire() default 5;/*** 过期单位* return*/TimeUnit timeUnit() default TimeUnit.SECONDS;/*** key的分隔符* return*/String delimiter() default :; } 这个CacheLock也是加锁的注解这个注解内包含了很多的信息这些信息都要作为Redis加锁的参数。 创建CacheParam Retention(RetentionPolicy.RUNTIME) Target({ElementType.PARAMETER,ElementType.FIELD}) Documented public interface CacheParam {/*** 参数的名称* return*/String name() default ; }这个参数是需要加在具体的参数上边的代表着这个参数要作为key构建的一部分当然也可以加在一个对象的属性上边。 创建key的生成工具类  看到代码的你一定慌了吧不要急在这之前我会先给你讲一下我的思路。我们讲的防止接口重复提交是防止用户对一个接口多次传入相同的信息这种情况我要进行处理。我的构建思路是想要构建一个这样的key。加了CacheParam的参数我获取参数具体的值并且把值作为key的一部分。 倘若我们的参数都没有加CacheParam呢这个时候就会去获取这个参数的类比如说是Student类我们就去看看这个传来的Student类当中有没有属性是加了CacheParam注解的如果有就获取值。  Component public class RedisKeyGenerator {AutowiredHttpServletRequest request;public String getKey(ProceedingJoinPoint joinPoint) throws IllegalAccessException {MethodSignature methodSignature (MethodSignature) joinPoint.getSignature();// 获取方法Method method methodSignature.getMethod();// 获取参数Object [] args joinPoint.getArgs();// 获取注解final Parameter [] parameters method.getParameters();CacheLock cacheLock method.getAnnotation(CacheLock.class);String prefix cacheLock.prefix();StringBuilder sb new StringBuilder();StringBuilder sb2 new StringBuilder();sb2.append(.).append(joinPoint.getTarget().getClass().getName()).append(.).append(method.getName());for(int i 0;iargs.length;i){CacheParam cacheParam parameters[i].getAnnotation(CacheParam.class);if(cacheParam null){continue;}sb.append(cacheLock.delimiter()).append(args[i]);}// 如果方法参数没有CacheParam注解 从参数类的内部尝试获取if(StringUtils.isEmpty(sb.toString())){for(int i 0;i parameters.length;i){final Object object args[i];Field [] fields object.getClass().getDeclaredFields();for (Field field : fields) {final CacheParam annotation field.getAnnotation(CacheParam.class);if(annotationnull){continue;}field.setAccessible(true);sb.append(cacheLock.delimiter()).append(field.get(object));}}}return prefixsb2sb;} } 创建Redis工具类 以下工具类来自引用DDKK.com。 Component Configuration AutoConfigureAfter(RedisAutoConfiguration.class) public class RedisLockHelper {private static final String DELIMITER |;/*** 如果要求比较高可以通过注入的方式分配*/private static final ScheduledExecutorService EXECUTOR_SERVICE Executors.newScheduledThreadPool(10);private final StringRedisTemplate stringRedisTemplate;Autowiredpublic RedisLockHelper(StringRedisTemplate stringRedisTemplate) {this.stringRedisTemplate stringRedisTemplate;}/*** 获取锁存在死锁风险** param lockKey lockKey* param value value* param time 超时时间* param unit 过期单位* return true or false*/public boolean tryLock(final String lockKey, final String value, final long time, final TimeUnit unit) {return stringRedisTemplate.execute((RedisCallbackBoolean) connection - connection.set(lockKey.getBytes(), value.getBytes(), Expiration.from(time, unit), RedisStringCommands.SetOption.SET_IF_ABSENT));}/*** 获取锁** param lockKey lockKey* param uuid UUID* param timeout 超时时间* param unit 过期单位* return true or false*/public boolean lock(String lockKey, final String uuid, long timeout, final TimeUnit unit) {final long milliseconds Expiration.from(timeout, unit).getExpirationTimeInMilliseconds();boolean success stringRedisTemplate.opsForValue().setIfAbsent(lockKey, (System.currentTimeMillis() milliseconds) DELIMITER uuid,timeout,TimeUnit.SECONDS);if (success) {} else {String oldVal stringRedisTemplate.opsForValue().getAndSet(lockKey, (System.currentTimeMillis() milliseconds) DELIMITER uuid);final String[] oldValues oldVal.split(Pattern.quote(DELIMITER));if (Long.parseLong(oldValues[0]) 1 System.currentTimeMillis()) {return true;}}return success;}/*** see a hrefhttp://redis.io/commands/setRedis Documentation: SET/a*/public void unlock(String lockKey, String value) {unlock(lockKey, value, 0, TimeUnit.MILLISECONDS);}/*** 延迟unlock** param lockKey key* param uuid client(最好是唯一键的)* param delayTime 延迟时间* param unit 时间单位*/public void unlock(final String lockKey, final String uuid, long delayTime, TimeUnit unit) {if (StringUtils.isEmpty(lockKey)) {return;}if (delayTime 0) {doUnlock(lockKey, uuid);} else {EXECUTOR_SERVICE.schedule(() - doUnlock(lockKey, uuid), delayTime, unit);}}/*** param lockKey key* param uuid client(最好是唯一键的)*/private void doUnlock(final String lockKey, final String uuid) {String val stringRedisTemplate.opsForValue().get(lockKey);final String[] values val.split(Pattern.quote(DELIMITER));if (values.length 0) {return;}if (uuid.equals(values[1])) {stringRedisTemplate.delete(lockKey);}}} 创建Student类 public class Student {CacheParamprivate String name;CacheParamprivate Integer age;public String getName() {return name;}public void setName(String name) {this.name name;}public Integer getAge() {return age;}public void setAge(Integer age) {this.age age;} } 创建AOP切面类 注意下边我注释掉的一行代码如果加上了以后你就看不到防止重复提交的提示了下边的代码和单机环境的思路是一样的只不过加锁用的是Redis。 Aspect Component public class Lock {Autowiredprivate RedisLockHelper redisLockHelper;Autowiredprivate RedisKeyGenerator redisKeyGenerator;Pointcut(execution(* com.my.controller.*.*(..))annotation(com.my.annotation.CacheLock))public void pointCut(){}Around(pointCut())public Object interceptor(ProceedingJoinPoint joinPoint) throws IllegalAccessException {MethodSignature methodSignature (MethodSignature) joinPoint.getSignature();Method method methodSignature.getMethod();CacheLock cacheLock method.getAnnotation(CacheLock.class);if (StringUtils.isEmpty(cacheLock.prefix())) {throw new RuntimeException(锁的前缀不能为空);}int expireTime cacheLock.expire();TimeUnit timeUnit cacheLock.timeUnit();String key redisKeyGenerator.getKey(joinPoint);System.out.println(key);String value UUID.randomUUID().toString();Object object;try {final boolean tryLock redisLockHelper.lock(key,value,expireTime,timeUnit);if(!tryLock){throw new RuntimeException(重复提交);}try {object joinPoint.proceed();}catch (Throwable e){throw new RuntimeException(系统异常);}} finally {// redisLockHelper.unlock(key,value);}return object;} } 创建Controller  RestController RequestMapping(/student) public class StudentController {RequestMapping(/get-student)CacheLock(prefix stu2,expire 5,timeUnit TimeUnit.SECONDS)public String getStudent(){return 张三;}RequestMapping(/get-student2)CacheLock(prefix stu2,expire 5,timeUnit TimeUnit.SECONDS)public String getStudent2(Student student){return 张三;} } 调用get-student测试 第一次调用第二次调用  调用get-student2测试  第一次调用第二次调用 最后上边的key生成还有待商榷分布式环境下key的生成并不是一个轻松的问题。本文的内容仅建议作为学习使用。
http://www.w-s-a.com/news/167944/

相关文章:

  • 重庆忠县网站建设报价网页构建
  • 怎么自己做单页网站怎么在阿里做网站
  • 公司网站重新备案做电商没几个能赚钱的
  • 网站开发我们都能解决怎样做网站吸引客户
  • 网站首页图片切换代码wordpress minfy
  • 什么程序做网站收录好企业搭建网站的必要性
  • 建设网站主题建站必须要域名吗
  • 网站建设海报设计购物平台网站建设框架
  • 湖北在线网站建设建一个网站迈年
  • 上班自己花钱做的网站网站首页的动态怎么做
  • 台州网站建设哪家便宜沧州最新消息今天
  • 建设网站 请示 报告wordpress会员制
  • 青岛建网站人做网站怎么赚钱广告
  • 网站建设哪家好公司跨境电商展会2023
  • 设计大神云集的网站是南通市 网站设计
  • 心理咨询网站模板企业画册封面设计
  • 做网站 南京网站建设的重难点分析
  • 深圳做网站980移动网站开发语言
  • 网站评论怎么做seo关键词优化方法
  • 市级部门网站建设自评报告网站优化文章怎么做
  • 可不可以异地建设网站学做网站培训班要多少钱
  • 茌平网站建设公司免费的云服务器有哪些
  • 手机网站单页面铜陵网站制作公司
  • 网站logo怎么做才清晰千库网官网首页登录
  • 山西省建设银行网站首页长沙网站建设制作
  • 襄阳市做网站 优帮云百度搜索次数统计
  • 自己做视频直播网站盐城做网站多少钱
  • 买个网站服务器多少钱重庆做的好的房产网站
  • 深圳定制建站网站建设推广关键词怎么设置
  • 宝山网站建设 网站外包修改wordpress版权