如果想对一个接口防止重复提交问题,可以使用token,访问前端页面之前先传给前端一个token值,然后前端调用在调用后端接口时携带token提交。

先贴出用到的工具类:

1.Redis key枚举类


public class RedisKeyUtils {private final static String PHONECODE_KEY="PHONECODE"; // 验证码private final static String SESSION_TOKEN_KEY="SESSIONTOKEN"; // sessionId/*** type类型*/public enum Type{PHONECODE(PHONECODE_KEY),SESSIONTOKEN(SESSION_TOKEN_KEY),;private final String value;Type(String value) {this.value=value;}}/*** 备注:根据type获取value值* @date 2023/1/16 19:51* @param type* @return* @author 录何聪*/public static String getVal(Type type){return type.value;}/*** 备注:生成key* @date 2023/1/16 19:52* @param type* @param obj* @return* @author 录何聪*/public static String getRedisKey(Type type,Object... obj){StringBuffer sb = new StringBuffer();sb.append(type.value);if (obj!=null || obj.length>0){for (Object o : obj) {sb.append("_").append(o);}return sb.toString();}return null;}
}

2.Redis类

2.1 序列化配置类

/*** Redis使用FastJson序列化* * @author yamyang*/
public class FastJson2JsonRedisSerializer<T> implements RedisSerializer<T>
{public static final Charset DEFAULT_CHARSET = Charset.forName("UTF-8");private Class<T> clazz;public FastJson2JsonRedisSerializer(Class<T> clazz){super();this.clazz = clazz;}@Overridepublic byte[] serialize(T t) throws SerializationException{if (t == null){return new byte[0];}return JSON.toJSONString(t, JSONWriter.Feature.WriteClassName).getBytes(DEFAULT_CHARSET);}@Overridepublic T deserialize(byte[] bytes) throws SerializationException{if (bytes == null || bytes.length <= 0){return null;}String str = new String(bytes, DEFAULT_CHARSET);return JSON.parseObject(str, clazz, JSONReader.Feature.SupportAutoType);}
}
2.2 redis配置类
@Configuration
@EnableCaching
@AutoConfigureBefore(RedisAutoConfiguration.class)
public class RedisConfig extends CachingConfigurerSupport
{@Bean@SuppressWarnings(value = { "unchecked", "rawtypes" })public RedisTemplate<Object, Object> redisTemplate(RedisConnectionFactory connectionFactory){RedisTemplate<Object, Object> template = new RedisTemplate<>();template.setConnectionFactory(connectionFactory);FastJson2JsonRedisSerializer serializer = new FastJson2JsonRedisSerializer(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;}
}
2.3 redis工具类
@SuppressWarnings(value = { "unchecked", "rawtypes" })
@Component
public class RedisService
{@Autowiredpublic RedisTemplate redisTemplate;/*** 缓存基本的对象,Integer、String、实体类等** @param key 缓存的键值* @param value 缓存的值*/public <T> void setCacheObject(final String key, final T value){redisTemplate.opsForValue().set(key, value);}/*** 缓存基本的对象,Integer、String、实体类等** @param key 缓存的键值* @param value 缓存的值* @param timeout 时间* @param timeUnit 时间颗粒度*/public <T> void setCacheObject(final String key, final T value, final Long timeout, final TimeUnit timeUnit){redisTemplate.opsForValue().set(key, value, timeout, timeUnit);}/*** 设置有效时间** @param key Redis键* @param timeout 超时时间* @return true=设置成功;false=设置失败*/public boolean expire(final String key, final long timeout){return expire(key, timeout, TimeUnit.SECONDS);}/*** 设置有效时间** @param key Redis键* @param timeout 超时时间* @param unit 时间单位* @return true=设置成功;false=设置失败*/public boolean expire(final String key, final long timeout, final TimeUnit unit){return redisTemplate.expire(key, timeout, unit);}/*** 获取有效时间** @param key Redis键* @return 有效时间*/public long getExpire(final String key){return redisTemplate.getExpire(key);}/*** 判断 key是否存在** @param key 键* @return true 存在 false不存在*/public Boolean hasKey(String key){return redisTemplate.hasKey(key);}/*** 获得缓存的基本对象。** @param key 缓存键值* @return 缓存键值对应的数据*/public <T> T getCacheObject(final String key){ValueOperations<String, T> operation = redisTemplate.opsForValue();return operation.get(key);}/*** 删除单个对象** @param key*/public boolean deleteObject(final String key){return redisTemplate.delete(key);}/*** 删除集合对象** @param collection 多个对象* @return*/public boolean deleteObject(final Collection collection){return redisTemplate.delete(collection) > 0;}/*** 缓存List数据** @param key 缓存的键值* @param dataList 待缓存的List数据* @return 缓存的对象*/public <T> long setCacheList(final String key, final List<T> dataList){Long count = redisTemplate.opsForList().rightPushAll(key, dataList);return count == null ? 0 : count;}/*** 获得缓存的list对象** @param key 缓存的键值* @return 缓存键值对应的数据*/public <T> List<T> getCacheList(final String key){return redisTemplate.opsForList().range(key, 0, -1);}/*** 缓存Set** @param key 缓存键值* @param dataSet 缓存的数据* @return 缓存数据的对象*/public <T> BoundSetOperations<String, T> setCacheSet(final String key, final Set<T> dataSet){BoundSetOperations<String, T> setOperation = redisTemplate.boundSetOps(key);Iterator<T> it = dataSet.iterator();while (it.hasNext()){setOperation.add(it.next());}return setOperation;}/*** 获得缓存的set** @param key* @return*/public <T> Set<T> getCacheSet(final String key){return redisTemplate.opsForSet().members(key);}/*** 缓存Map** @param key* @param dataMap*/public <T> void setCacheMap(final String key, final Map<String, T> dataMap){if (dataMap != null) {redisTemplate.opsForHash().putAll(key, dataMap);}}/*** 获得缓存的Map** @param key* @return*/public <T> Map<String, T> getCacheMap(final String key){return redisTemplate.opsForHash().entries(key);}/*** 往Hash中存入数据** @param key Redis键* @param hKey Hash键* @param value 值*/public <T> void setCacheMapValue(final String key, final String hKey, final T value){redisTemplate.opsForHash().put(key, hKey, value);}/*** 获取Hash中的数据** @param key Redis键* @param hKey Hash键* @return Hash中的对象*/public <T> T getCacheMapValue(final String key, final String hKey){HashOperations<String, String, T> opsForHash = redisTemplate.opsForHash();return opsForHash.get(key, hKey);}/*** 获取多个Hash中的数据** @param key Redis键* @param hKeys Hash键集合* @return Hash对象集合*/public <T> List<T> getMultiCacheMapValue(final String key, final Collection<Object> hKeys){return redisTemplate.opsForHash().multiGet(key, hKeys);}/*** 删除Hash中的某条数据** @param key Redis键* @param hKey Hash键* @return 是否成功*/public boolean deleteCacheMapValue(final String key, final String hKey){return redisTemplate.opsForHash().delete(key, hKey) > 0;}/*** 获得缓存的基本对象列表** @param pattern 字符串前缀* @return 对象列表*/public Collection<String> keys(final String pattern){return redisTemplate.keys(pattern);}
}

正片开始:

1.访问前端之前先获取token

    @Autowiredprivate HttpSession session;public void setSession(HttpSession session) {this.session = session;}/*** 备注:访问前端之前先给前端传一个token。*      生成token的Key方式有很多,在此只演示一种,此种是通过sessionId生成。* @date 2023/1/30 15:21* @return* @author 录何聪*/@RequestMapping("/getToken")public AjaxResult getToken(){String id = session.getId();// 生成keyString redisKey = RedisKeyUtils.getRedisKey(Type.SESSIONTOKEN, id);// uuid当做tokenString token = UUID.randomUUID().toString().replace("-", "");redisService.setCacheObject(redisKey,token,5L, TimeUnit.MINUTES);return AjaxResult.success(token);}

2. 调用接口(正片开始)

    /*** 备注:测试接口* @date 2023/1/30 15:38* @param token* @return* @author 录何聪*/@RequestMapping("/hello2")public AjaxResult hello2( String token,String message){// 根据sessionId生成keyString id = session.getId();// 生成keyString redisKey = RedisKeyUtils.getRedisKey(Type.SESSIONTOKEN, id);// 查询keyString cacheToken = redisService.getCacheObject(redisKey);// 对比token是否一致if(!Objects.equals(cacheToken, token)){// 不一致,说明重复,自定义返回return AjaxResult.error("操作太快!");}// 一致,先失效掉tokenboolean result = redisService.deleteObject(redisKey);// 防止高并发的问题,判断一下是否失效成功:提示,第一次删除会是true,再已删除的情况下再删一次,会是falseif(result){// 模拟执行业务逻辑:假设5秒执行完try {Thread.sleep(5000);} catch (InterruptedException e) {throw new RuntimeException(e);}// 再次,假设调用接口出现了个问题,前端再次点击的时候,是会显示操作太快的,因为token已经失效了!// 如果想在前端页面不刷新的情况下再次点击的话,可以在失败的情况下把刚才的token再恢复,确保下次点击不用刷新页面// 如,用户提交一个留言的业务:if(StringUtils.isEmpty(message)){// 恢复刚才的tokenredisService.setCacheObject(redisKey,token,5L,TimeUnit.MINUTES);return AjaxResult.error("留言不能为空!");}else{// 继续其他业务return AjaxResult.success("操作成功!");}}else{return AjaxResult.error("操作太快!");}}

测试:
先拿到token

然后开多个窗口同时运行这个接口(当然是在同一个浏览器上开多个窗口了,不然sssionId都不一样)

只有最先访问的窗口可以执行成功!

springboot结合redis解决重复提交问题的实际应用相关推荐

  1. Spring AOP + Redis解决重复提交的问题

    Spring AOP + Redis解决重复提交的问题 用户在点击操作的时候,可能会连续点击多次,虽然前端可以通过设置按钮的disable的属性来控制按钮不可连续点击,但是如果别人拿到请求进行模拟,依 ...

  2. Spring Boot + Redis 解决重复提交问题,一定用的到

    点击关注

  3. 一招教你使用注解处理幂等问题 8种方案解决重复提交

    一招教你使用注解处理幂等问题 8种方案解决重复提交 参考文章: (1)一招教你使用注解处理幂等问题 8种方案解决重复提交 (2)https://www.cnblogs.com/xxmyz/p/1116 ...

  4. SpringBoot结合redis解决PV、UV亿级流量

    SpringBoot结合redis解决PV.UV亿级流量 文章目录 SpringBoot结合redis解决PV.UV亿级流量 一 背景 1. 初级开发视角 2. 解决方案 二 上代码 1. 关系数据库 ...

  5. java后端解决重复提交问题

    一.为什么会出现重复提交? 主要是由于网络的延迟问题以及页面刷新的操作. 二.表单的重复提交会导致的问题? 主要能够造成很多脏数据. 三.解决的办法: 3.1 前端解决办法:通过前端的方法将提交按钮变 ...

  6. PHP 使用Redis防止重复提交

    PHP 使用Redis防止重复提交 最近工作中遇到了用户申请数据会出现同一秒提交上来两条一样的数据, 对数据的分析造成了很大的影响,然后就开始想到使用Redis重复提交,给动作加锁,在一定时间内不能再 ...

  7. SpringBoot集成Redis解决乱码问题 - \xAC\xED\x00\x05t\x00

    SpringBoot集成Redis解决乱码问题 - \xAC\xED\x00\x05t\x00 SpringBoot集成Redis解决乱码问题 问题概述 使用redisTemplate.opsForV ...

  8. spring项目使用redis分布式锁解决重复提交问题

    场景演示 假设有一个录入学生信息的功能,为了便于演示,要求不能有重名的学生,并且数据库对应字段没有做唯一限制. @GetMapping("/student/{name}")publ ...

  9. 8种方案解决重复提交问题

    点击上方"方志朋",选择"设为星标" 回复"666"获取新整理的面试资料 作者:锦成同学 链接:juejin.im/post/5d31928 ...

最新文章

  1. Vue混入mixins
  2. Scanner方法中nextLine()和next()区别
  3. 逻辑io 物理io oracle,Oracle体系结构之SQL语句的执行过程
  4. 安装.net5.0后怎么在vs里选择_Visual Stdio 2019(或其它版本)怎样全部安装到非系统盘的实现方法(小白版)...
  5. MacOS 系统使用命令安装软件包
  6. Excel的VBA连接数据库方法
  7. git(9)--- 如何使用MTK GIT发布分支
  8. tensorflow对应的numpy版本_版本更新 | TensorFlow 2.4.0 候选版本发布
  9. java 植入 form_pdf form表单制作以及用java程序填充表单
  10. Ansible 书写我的playbook
  11. mac系统如何转换python版本_Mac上如何切换python版本
  12. Linux日志系统与日志库zlog
  13. δ星 丨 读书笔记 notes-凭什么《只放一只羊》:干掉沃尔玛10个亿并将其逼出德国的“平民超市”品牌阿尔迪...
  14. 数独游戏思路html,全民数独游戏规则介绍 数独技巧及题目解答思路
  15. 14款开源或免费的GIS软件
  16. JS实现放大镜特效原理解析
  17. 计算机教案封面设计,四年级信息技术《精彩封面巧设计》教学设计
  18. 恒源云(GPUSHARE)_未闻Prompt名(论文学习笔记)
  19. 信号完整性分析6——信号的振铃
  20. Orcale数据库简介

热门文章

  1. 上半年要写的博客文章21
  2. python中对字符串进行左、中、右对齐操作
  3. 新博客移步:https://www.cnblogs.com/nineep/
  4. 自我规范模版A要求2:展示数据列表 (无从表)和导出数据页面
  5. php 微信公众号客服,微信公众平台开发 多客服
  6. 小米usb当前设备已被临时限制3-2
  7. 【网络游戏植入营销案例】
  8. JVM上篇:内存与垃圾回收篇十四--垃圾回收器
  9. 强制域名使用 HTTPS(SSL)
  10. MySQL数据库下载与安装