首页
归档
留言
广告合作
友链
美女主播
Search
1
博瑞GE车机升级/降级
5,146 阅读
2
Mac打印机设置黑白打印
4,517 阅读
3
修改elementUI中el-table树形结构图标
4,516 阅读
4
Mac客户端添加腾讯企业邮箱方法
4,351 阅读
5
intelliJ Idea 2022.2.X破解
4,060 阅读
Java
HarmonyOS Next
Web前端
微信开发
开发辅助
App开发
数据库
随笔日记
登录
/
注册
Search
标签搜索
Spring Boot
Java
Spring Cloud
Mac
mybatis
WordPress
Nacos
Spring Cloud Alibaba
Mybatis-Plus
jQuery
Java Script
asp.net
微信小程序
Sentinel
UniApp
MySQL
asp.net core
IntelliJ IDEA
Jpa
树莓派
Laughing
累计撰写
570
篇文章
累计收到
1,424
条评论
首页
栏目
Java
HarmonyOS Next
Web前端
微信开发
开发辅助
App开发
数据库
随笔日记
页面
归档
留言
广告合作
友链
美女主播
搜索到
6
篇与
的结果
2023-03-05
SprintBoot切面+Redis防止前端重复提交
最近项目上遇到重复提交的情况,虽然前端对按钮进行了禁用,但是不知道是什么原因,后端仍然接收到了多个请求,因为是分布式系统,所以不能简单的使用lock,最终考虑决定使用redis实现。一、环境准备MySql:测试数据库Redis:使用Redis实现Another Redis Desktop Manager:跟踪Redis信息ApiFox:模拟请求,单线程循环及多线程循环Spring Boot:2.7.4二、准备测试数据及接口2.1、创建表创建一个最简单的用户表,只包含id、name两列create table User ( id int null, name varchar(200) null );2.2、创建接口2.2.1、配置依赖及数据库、Redis连接信息项目依赖<?xml version="1.0" encoding="UTF-8"?> <project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd"> <modelVersion>4.0.0</modelVersion> <parent> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-parent</artifactId> <version>2.7.4</version> <relativePath/> <!-- lookup parent from repository --> </parent> <groupId>net.xiangcaowuyu</groupId> <artifactId>RepeatSubmit</artifactId> <version>0.0.1-SNAPSHOT</version> <name>RepeatSubmit</name> <description>RepeatSubmit</description> <properties> <java.version>8</java.version> </properties> <dependencies> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-data-redis</artifactId> </dependency> <dependency> <groupId>org.projectlombok</groupId> <artifactId>lombok</artifactId> <version>1.18.2</version> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-aop</artifactId> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> </dependency> <!-- Druid --> <dependency> <groupId>com.alibaba</groupId> <artifactId>druid-spring-boot-starter</artifactId> <version>1.1.16</version> </dependency> <!--jdbc--> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-jdbc</artifactId> </dependency> <!-- mysql --> <dependency> <groupId>mysql</groupId> <artifactId>mysql-connector-java</artifactId> </dependency> <!-- mybatis-plus --> <dependency> <groupId>com.baomidou</groupId> <artifactId>mybatis-plus-boot-starter</artifactId> <version>3.5.1</version> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-test</artifactId> <scope>test</scope> </dependency> </dependencies> <build> <plugins> <plugin> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-maven-plugin</artifactId> <configuration> <excludes> <exclude> <groupId>org.projectlombok</groupId> <artifactId>lombok</artifactId> </exclude> </excludes> </configuration> </plugin> </plugins> </build> </project> yaml文件配置数据库及Redis连接信息spring: redis: host: 192.168.236.2 port: 6379 password: datasource: #使用阿里的Druid type: com.alibaba.druid.pool.DruidDataSource driver-class-name: com.mysql.cj.jdbc.Driver url: jdbc:mysql://192.168.236.2/TestRepeatSubmit?serverTimezone=UTC username: root password: root2.2.2、创建实体@Data @TableName("User") public class User { private Long id; private String name; }2.2.3、创建数据访问层public interface UserMapper extends BaseMapper<User> { }2.2.4、创建异常处理类@Data @NoArgsConstructor @AllArgsConstructor public class ResultRet<T> { private Integer code; private String msg; private T data; //成功码 public static final Integer SUCCESS_CODE = 200; //成功消息 public static final String SUCCESS_MSG = "SUCCESS"; //失败 public static final Integer ERROR_CODE = 201; public static final String ERROR_MSG = "系统异常,请联系管理员"; //没有权限的响应码 public static final Integer NO_AUTH_COOD = 999; //执行成功 public static <T> ResultRet<T> success(T data){ return new ResultRet<>(SUCCESS_CODE,SUCCESS_MSG,data); } //执行失败 public static <T> ResultRet failed(String msg){ msg = StringUtils.isEmpty(msg)? ERROR_MSG : msg; return new ResultRet(ERROR_CODE,msg,""); } //传入错误码的方法 public static <T> ResultRet failed(int code,String msg){ msg = StringUtils.isEmpty(msg)? ERROR_MSG : msg; return new ResultRet(code,msg,""); } //传入错误码的数据 public static <T> ResultRet failed(int code,String msg,T data){ msg = StringUtils.isEmpty(msg)? ERROR_MSG : msg; return new ResultRet(code,msg,data); } }2.2.5、简单的全局异常@Slf4j @RestControllerAdvice public class GlobalExceptionHandler { @ExceptionHandler(value = Throwable.class) public ResultRet handleException(Throwable throwable){ log.error("错误",throwable); return ResultRet.failed(500, throwable.getCause().getMessage()); } }2.2.6、配置模拟接口模拟一个get请求的接口,用户新增用户,orm框架使用mybatis-plus,使用最简单的插入@RestController @RequestMapping("/user") public class UserController { @Resource private UserMapper userMapper; @GetMapping("/add") public ResultRet<User> add() { User user = new User(); user.setId(1L); user.setName("张三"); userMapper.insert(user); return ResultRet.success(user); } }以上配置完成后,当我们访问/user/add接口时,肯定访问几次,数据库就会重复插入多少信息。三、改造接口,防止重复提交改造的原理起始很简单,我们前端访问接口时,首先在头部都会携带token信息,我们通过切面,拦截请求,获取到token及请求的url,拼接后作为redis的key值,通过redis锁的方式写入key值,如果写入成功,设置一个过期时间,在有效期时间内,多次请求,先判断redis中是否有对应的key,如果有,抛出异常,禁止再次写入。3.1、配置RedisTemplate@Configuration public class RedisConfig { @Bean @SuppressWarnings(value = { "unchecked", "rawtypes" }) public RedisTemplate<Object, Object> redisTemplate(RedisConnectionFactory connectionFactory) { RedisTemplate<Object, Object> template = new RedisTemplate<>(); template.setConnectionFactory(connectionFactory); Jackson2JsonRedisSerializer serializer = new Jackson2JsonRedisSerializer(Object.class); // 使用StringRedisSerializer来序列化和反序列化redis的key值 template.setKeySerializer(new StringRedisSerializer()); template.setValueSerializer(serializer); // Hash的key也采用StringRedisSerializer的序列化方式 template.setHashKeySerializer(new StringRedisSerializer()); template.setHashValueSerializer(serializer); template.afterPropertiesSet(); return template; } }3.2、增加Redis工具类@Component @Slf4j public class RedisUtils { @Resource private StringRedisTemplate stringRedisTemplate; /** * Redis分布式锁 * * @return 加锁成功返回true,否则返回false */ public boolean tryLock(String key, String value, long timeout) { Boolean isSuccess = stringRedisTemplate.opsForValue().setIfAbsent(key, value); //设置过期时间,防止死锁 if (Boolean.TRUE.equals(isSuccess)) { stringRedisTemplate.expire(key, timeout, TimeUnit.SECONDS); } return Boolean.TRUE.equals(isSuccess); } /** * Redis 分布式锁释放 * * @param key * @param value */ public void unLock(String key, String value) { try { String currentValue = stringRedisTemplate.opsForValue().get(key); if (StringUtils.isNotEmpty(currentValue) && StringUtils.equals(currentValue, value)) { stringRedisTemplate.opsForValue().getOperations().delete(key); } } catch (Exception e) { //这个是我的自定义异常,你可以删了 log.info("报错了"); } } }3.3、添加注解@Target(ElementType.METHOD) // 注解只能用于方法 @Retention(RetentionPolicy.RUNTIME) // 修饰注解的生命周期 @Documented public @interface RepeatSubmitAnnotation { /** * 防重复操作过期时间,默认1s */ long expireTime() default 1; }3.4、添加切面@Slf4j @Component @Aspect public class RepeatSubmitAspect { @Resource private RedisUtils redisUtils; /** * 定义切点 */ @Pointcut("@annotation(net.xiangcaowuyu.repeatsubmit.annotation.RepeatSubmitAnnotation)") public void repeatSubmit() { } @Around("repeatSubmit()") public Object around(ProceedingJoinPoint joinPoint) throws Throwable { ServletRequestAttributes attributes = (ServletRequestAttributes) RequestContextHolder .getRequestAttributes(); HttpServletRequest request = attributes.getRequest(); Method method = ((MethodSignature) joinPoint.getSignature()).getMethod(); // 获取防重复提交注解 RepeatSubmitAnnotation annotation = method.getAnnotation(RepeatSubmitAnnotation.class); // 获取token当做key,小编这里是新后端项目获取不到哈,先写死 String token = request.getHeader("token"); if (StringUtils.isBlank(token)) { throw new RuntimeException("token不存在,请登录!"); } String url = request.getRequestURI(); /** * 通过前缀 + url + token 来生成redis上的 key * 可以在加上用户id,小编这里没办法获取,大家可以在项目中加上 */ String redisKey = "repeat_submit_key:" .concat(url) .concat(token); log.info("==========redisKey ====== {}", redisKey); boolean lock = redisUtils.tryLock(redisKey, redisKey, annotation.expireTime()); if (lock) { log.info("获取分布式锁成功"); try { //正常执行方法并返回 return joinPoint.proceed(); } catch (Throwable throwable) { throw new Throwable(throwable); } finally { //释放锁 // redisUtils.unLock(redisKey, redisKey); // System.out.println("释放分布式锁成功"); } } else { // 抛出异常 throw new Throwable("请勿重复提交"); } } }3.5、接口添加注解这里为了方便演示,我们把提交间隔时间设置为30s。@RestController @RequestMapping("/user") public class UserController { @Resource private UserMapper userMapper; @GetMapping("/add") @RepeatSubmitAnnotation(expireTime = 30L) public ResultRet<User> add() { User user = new User(); user.setId(1L); user.setName("张三"); userMapper.insert(user); return ResultRet.success(user); } }至此,我们所有的配置都完成了,接下来使用ApiFox模拟一下接口访问。3.6、模拟测试我们先把数据库及Redis清空(本来其实就是空的)配置好自动化测试接口3.6.1、单线程测试先模拟单线程操作,循环50次查看Redis,查看有一个key打开数据库,可以看到只成功插入了一条3.6.3、模拟多线程先把数据库清空,Redis等待过期后自动删除再次模拟,10个线程,循环10次此时查看数据库,仍然只有一条插入成功了
2023年03月05日
1,109 阅读
1 评论
0 点赞
2020-10-02
spring boot使用Redis
什么是RedisRedis(Remote Dictionary Server ),即远程字典服务,是一个开源的使用ANSI C语言编写、支持网络、可基于内存亦可持久化的日志型、Key-Value数据库,并提供多种语言的API。从2010年3月15日起,Redis的开发工作由VMware主持。从2013年5月开始,Redis的开发由Pivotal赞助。Redis官网:https://redis.io/spring boot 使用Redis添加依赖 <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-data-redis</artifactId> </dependency>修改application.yaml在配置文件中,增加redis配置信息增加Redis配置类/** * Redis配置 * @author laughing * @date 2020/10/2 * @site https://www.xiangcaowuyu.net */ @Configuration public class RedisConfig { /** * RedisTemplate配置,放置存储数据乱码 * @param redisConnectionFactory * @return */ @Bean public RedisTemplate<String,Object> redisTemplate(RedisConnectionFactory redisConnectionFactory){ RedisTemplate<String,Object> redisTemplate = new RedisTemplate<>(); redisTemplate.setConnectionFactory(redisConnectionFactory); // key采用String的序列化方式 redisTemplate.setKeySerializer(new StringRedisSerializer()); // hash的key也采用String的序列化方式 redisTemplate.setHashKeySerializer(new StringRedisSerializer()); // value序列化方式采用fastjson redisTemplate.setValueSerializer(new GenericJackson2JsonRedisSerializer()); // hash的value序列化方式采用jackson redisTemplate.setHashValueSerializer(new GenericJackson2JsonRedisSerializer()); redisTemplate.afterPropertiesSet(); return redisTemplate; } }编写Redis工具类/** * Redis工具类 * @author laughing * @date 2020/10/2 * @site https://www.xiangcaowuyu.net */ @Component public class RedisUtils { @Resource RedisTemplate<String, Object> redisTemplate; /** * 判断是否存在 * * @return */ public boolean hasKey(String key) { return redisTemplate.hasKey(key); } /** * 获取值 * @param key * @return */ public Object getValue(String key){ return redisTemplate.opsForValue().get(key); } /** * 设置值 * * @param key * @param value */ public void setValue(String key, Object value) { redisTemplate.opsForValue().set(key, value); } /** * 获取过期时间 * * @param key * @return */ public Long getExpire(String key) { return redisTemplate.getExpire(key); } /** * 获取过期时间 * * @param key * @param timeUnit * @return */ public Long getExpire(String key, TimeUnit timeUnit) { return redisTemplate.getExpire(key, timeUnit); } /** * 设置过期时间 * * @param key * @param timeOut * @return */ public Boolean setExpire(String key, Duration timeOut) { return redisTemplate.expire(key, timeOut); } /** * 删除 * * @param key * @return */ public Boolean delete(String key) { return redisTemplate.delete(key); } /** * 删除 * @param keys * @return */ public Long deleteAll(List<String> keys){ return redisTemplate.delete(keys); } }使用这里进行简单的测试。@SpringBootTest class RedisApplicationTests { @Resource RedisUtils redisUtils; @Test void contextLoads() { } @Test void setValue(){ String key = "name"; String value = "香草物语"; redisUtils.setValue(key,value); } }这里只演示一个设置值,通过Another Redis Desktop查看数据库
2020年10月02日
1,531 阅读
0 评论
0 点赞
2020-10-01
在windows上安装Redis
前言Redis官方不建议在windows下使用Redis,所以官网没有windows版本可以下载。还好微软团队维护了开源的window版本,虽然只有3.2版本,对于普通测试使用足够了。Windows Redis下载地址https://github.com/MicrosoftArchive/redis/releases注意点击Asset,展开就能看到对应的安装包。Windows Redis使用我相信没有任何人在实际项目中,会在Win下使用Redis。(如果有,就当我这句话没说)如上所述,所以我不讲解如果通过.msi的方式进行安装。(尽管都是一样的),我们这里介绍.zip的方式进行安装(使用)。解压下载的.zip文件将redis路径添加到环境变量cmd启动redis如果已经将redis路径添加到path,执行redis-server.exeredis会自动加载redis目录下的redis.windows.conf配置文件,当然,你也可以执行配置文件位置,就像下面这样redis-server.exe C:\Users\laughing\Documents\Redis-x64-3.2.100\redis.windows.conf如果很不幸,你没有将redis添加到path,那么你必须先cd到redis目录下,然后执行上面的命令。redis启动成功后,界面如下使用命令行启动Redis服务进入redis目录,执行以下命令:redis-server --service-install redis.windows.conf执行完成后,打开windows 服务,如果看到Redis,则代表安装成功。服务的方式运行,不需要我们每次都通过cmd打开窗口,并可以配置redis开机启动。redis服务启动执行以下命令启动redis:redis-server --service-start当然,你也可以在windows服务界面,通过图形化的方式启动。停止redis服务redis-server --service-stop当然,你也可以在windows服务界面,通过图形化的方式启动。测试redis能够连接首先执行redis-cli打开客户端界面,输入ping,如果提示'PONG',则代表打开成功。
2020年10月01日
1,390 阅读
0 评论
1 点赞
2020-10-01
另一个 Redis 桌面管理工具- Another-Redis-Desktop-Manager
AnotherRedisDesktopManager是国人开源的一个Redis客户端。它是一款更快、更好、更稳定的 Redis 桌面管理工具,兼容 Linux, windows, mac。更重要的是,在加载大量的键时它不会崩溃!AnotherRedisDesktopManager也是我最近一直使用的redis客户端。项目地址:https://github.com/qishibo/AnotherRedisDesktopManager新建连接点击“新建链接”,输入redis信息,点击确定,如果配置都正确,即可建立链接控制台使用如下图,点击控制台图标,输入相应命令,即可执行新增、查看键值
2020年10月01日
2,142 阅读
0 评论
1 点赞
2019-07-22
Spring Boot通过Redis共享Session
温馨提示以下内容基于SpringBoot 1.2.7版本<parent> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-parent</artifactId> <version>1.2.7.RELEASE</version> </parent>添加依赖主要是添加如下两个依赖<!-- 添加redis--> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-redis</artifactId> <version>1.3.8.RELEASE</version> </dependency> <!-- session依赖--> <dependency> <groupId>org.springframework.session</groupId> <artifactId>spring-session-data-redis</artifactId> <version>1.3.5.RELEASE</version> </dependency> 安装Redis具体操作方法请自行百度,不是本文重点配置Redis找到application.yml文件,添加如下内容#redis配置 redis: host: 我是IP port: 6379 password: 我是密码 timeout: 0 pool: max-active: 100 max-idle: 10 max-wait: 100000 database: 0 增加Redis配置类RedisConfiguration.javapackage Net.XiangCaoWuYu.Configurations; import com.fasterxml.jackson.annotation.JsonAutoDetect; import com.fasterxml.jackson.annotation.PropertyAccessor; import com.fasterxml.jackson.databind.ObjectMapper; import org.springframework.boot.autoconfigure.EnableAutoConfiguration; import org.springframework.boot.context.properties.ConfigurationProperties; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.data.redis.connection.RedisConnectionFactory; import org.springframework.data.redis.connection.jedis.JedisConnectionFactory; import org.springframework.data.redis.core.RedisTemplate; import org.springframework.data.redis.core.StringRedisTemplate; import org.springframework.data.redis.serializer.Jackson2JsonRedisSerializer; import org.springframework.data.redis.serializer.StringRedisSerializer; import redis.clients.jedis.JedisPoolConfig; /** * ClassName: RedisConfiguration <br/> * Description: <br/> * date: 2019/7/22 9:17<br/> * * @author lisen01<br /> * @since JDK 1.8 */ @Configuration @EnableAutoConfiguration public class RedisConfiguration { /* @Bean @ConfigurationProperties(prefix = "spring.redis.pool") public JedisPoolConfig getJedisPoolConfig() { JedisPoolConfig jedisPoolConfig = new JedisPoolConfig(); return jedisPoolConfig; } @Bean @ConfigurationProperties(prefix = "spring.redis") public JedisConnectionFactory getConnectionFactory() { JedisConnectionFactory factory = new JedisConnectionFactory(); factory.setUsePool(true); JedisPoolConfig jedisPoolConfig = getJedisPoolConfig(); factory.setPoolConfig(jedisPoolConfig); return factory; } @Bean public RedisTemplate<?,?> redisTemplate(){ JedisConnectionFactory factory=getConnectionFactory(); RedisTemplate<?,?> redisTemplate=new StringRedisTemplate(factory); return redisTemplate; } */ /** * redisTemplate 序列化使用的jdkSerializeable, 存储二进制字节码, 所以自定义序列化类 * @param redisConnectionFactory * @return */ @Bean public RedisTemplate<Object, Object> redisTemplate(RedisConnectionFactory redisConnectionFactory) { RedisTemplate<Object, Object> redisTemplate = new RedisTemplate<Object, Object>(); redisTemplate.setConnectionFactory(redisConnectionFactory); // 使用Jackson2JsonRedisSerialize 替换默认序列化 Jackson2JsonRedisSerializer jackson2JsonRedisSerializer = new Jackson2JsonRedisSerializer(Object.class); ObjectMapper objectMapper = new ObjectMapper(); objectMapper.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.ANY); objectMapper.enableDefaultTyping(ObjectMapper.DefaultTyping.NON_FINAL); jackson2JsonRedisSerializer.setObjectMapper(objectMapper); // 设置value的序列化规则和 key的序列化规则 redisTemplate.setKeySerializer(new StringRedisSerializer()); redisTemplate.setValueSerializer(jackson2JsonRedisSerializer); redisTemplate.afterPropertiesSet(); return redisTemplate; } }增加Session配置SessionConfiguration.javapackage Net.XiangCaoWuYu.Configurations; import org.springframework.context.annotation.Configuration; import org.springframework.session.data.redis.config.annotation.web.http.EnableRedisHttpSession; /** * ClassName: SessionConfiguration <br/> * Description: <br/> * date: 2019/7/22 10:45<br/> * * @author lisen01<br /> * @since JDK 1.8 */ @Configuration @EnableRedisHttpSession public class SessionConfiguration { }简单测试UUID uid = (UUID) session.getAttribute("uid"); if (uid == null) { uid = UUID.randomUUID(); } session.setAttribute("uid", uid); request.getSession().setAttribute("user",JSON.toJSONString(checkUser));
2019年07月22日
1,317 阅读
0 评论
0 点赞
2017-11-25
ASP.NET Core使用Redis
Redis是优秀的缓存组件,我们在DotNetCore中也能够很方便的使用。本文主要介绍在虚拟机安装Window Server作为缓存服务器进行简单使用的方法。使用准备为了使用Redis,我们需要一个缓存服务器,这里我们所有的东西都是通过Nuget的方式进行安装的。需要注意的是,Redis官方没有Win版本的,但是我们可以使用微软第三方的我们首先需要安装一下的Nuget包Microsoft.AspNetCore.Session Microsoft.AspNetCore.Http.Extensions Microsoft.Extensions.Caching.Redis Redis-64其中Redis位于C:\Users\Administrator.nuget\packages\redis-64将Redis-64文件夹拷贝到服务器,然后打开\3.0.503\tools\redis-server.exe 开启Redis
2017年11月25日
1,423 阅读
0 评论
2 点赞