002 springboot redis 防止表单重复提交

文章目录

    • RedisConfig.java
    • WebConfiguration.java
    • AutoIdempotentTokenController.java
    • MyOrderController.java
    • MyOrder.java
    • AutoIdempotentInterceptor.java
    • AutoIdempotent
    • IdempotentTokenService.java
    • IdempotentTokenServiceImpl.java
    • SpringbootRedisDemoApplication.java
    • application.yaml
    • order_save.jsp

RedisConfig.java



package com.example.config;

import com.fasterxml.jackson.annotation.JsonAutoDetect;
import com.fasterxml.jackson.annotation.PropertyAccessor;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.SerializationFeature;
import com.fasterxml.jackson.datatype.jsr310.JavaTimeModule;
import com.fasterxml.jackson.datatype.jsr310.deser.LocalDateDeserializer;
import com.fasterxml.jackson.datatype.jsr310.deser.LocalDateTimeDeserializer;
import com.fasterxml.jackson.datatype.jsr310.ser.LocalDateSerializer;
import com.fasterxml.jackson.datatype.jsr310.ser.LocalDateTimeSerializer;
import org.springframework.cache.annotation.CachingConfigurerSupport;
import org.springframework.cache.annotation.EnableCaching;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.redis.cache.RedisCacheConfiguration;
import org.springframework.data.redis.cache.RedisCacheManager;
import org.springframework.data.redis.connection.RedisConnectionFactory;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.listener.RedisMessageListenerContainer;
import org.springframework.data.redis.serializer.*;

import java.time.Duration;
import java.time.LocalDate;
import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;

/**
 * 配置redistemplate序列化
 */
@Configuration
@EnableCaching
public class RedisConfig extends CachingConfigurerSupport {
    //过期时间-1天
    private Duration timeToLive = Duration.ofDays(-1);

    /**
     * RedisTemplate 先关配置
     *
     * @param factory
     * @return
     */
    @Bean
    @SuppressWarnings("all")
    public RedisTemplate<String, Object> redisTemplate(RedisConnectionFactory factory) {
        RedisTemplate<String, Object> template = new RedisTemplate<String, Object>();
        template.setConnectionFactory(factory);
        Jackson2JsonRedisSerializer jackson2JsonRedisSerializer = new Jackson2JsonRedisSerializer(Object.class);
        ObjectMapper om = new ObjectMapper();
        om.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.ANY);
        om.enableDefaultTyping(ObjectMapper.DefaultTyping.NON_FINAL);

        //LocalDatetime序列化
        JavaTimeModule timeModule = new JavaTimeModule();
        timeModule.addDeserializer(LocalDate.class,
                new LocalDateDeserializer(DateTimeFormatter.ofPattern("yyyy-MM-dd")));
        timeModule.addDeserializer(LocalDateTime.class,
                new LocalDateTimeDeserializer(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss")));
        timeModule.addSerializer(LocalDate.class,
                new LocalDateSerializer(DateTimeFormatter.ofPattern("yyyy-MM-dd")));
        timeModule.addSerializer(LocalDateTime.class,
                new LocalDateTimeSerializer(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss")));

        om.disable(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS);
        om.registerModule(timeModule);

        jackson2JsonRedisSerializer.setObjectMapper(om);
        StringRedisSerializer stringRedisSerializer = new StringRedisSerializer();
        // key采用String的序列化方式
        template.setKeySerializer(stringRedisSerializer);
        // hash的key也采用String的序列化方式
        template.setHashKeySerializer(stringRedisSerializer);
        // value序列化方式采用jackson
        template.setValueSerializer(jackson2JsonRedisSerializer);
        // hash的value序列化方式采用jackson
        template.setHashValueSerializer(jackson2JsonRedisSerializer);
        template.afterPropertiesSet();
        return template;
    }


    @Bean
    public RedisCacheManager cacheManager(RedisConnectionFactory connectionFactory) {
        //默认1
        RedisCacheConfiguration config = RedisCacheConfiguration.defaultCacheConfig()
                .entryTtl(timeToLive)
                .serializeKeysWith(RedisSerializationContext.SerializationPair.fromSerializer(keySerializer()))
                .serializeValuesWith(RedisSerializationContext.SerializationPair.fromSerializer(valueSerializer()))
                .disableCachingNullValues();
        RedisCacheManager redisCacheManager = RedisCacheManager.builder(connectionFactory)
                .cacheDefaults(config)
                .transactionAware()
                .build();
        return redisCacheManager;
    }

    @Bean
    RedisMessageListenerContainer listenerContainer(RedisConnectionFactory connectionFactory) {
        RedisMessageListenerContainer listenerContainer = new RedisMessageListenerContainer();
        listenerContainer.setConnectionFactory(connectionFactory);
        return listenerContainer;
    }


    /**
     * key 类型
     * @return
     */
    private RedisSerializer<String> keySerializer() {
        return  new StringRedisSerializer();
    }

    /**
     * 值采用JSON序列化
     * @return
     */
    private RedisSerializer<Object> valueSerializer() {
        return new GenericJackson2JsonRedisSerializer();
    }

}


WebConfiguration.java



package com.example.config;

import com.example.interceptor.AutoIdempotentInterceptor;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.config.annotation.EnableWebMvc;
import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
import org.springframework.web.servlet.config.annotation.ResourceHandlerRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;

@EnableWebMvc
@Configuration
public class WebConfiguration implements WebMvcConfigurer {
    @Autowired
    private AutoIdempotentInterceptor autoIdempotentInterceptor;
    @Override
    public void addInterceptors(InterceptorRegistry registry) {
        // "/" 项目根目录(http://localhost:80/app)

        registry.addInterceptor(autoIdempotentInterceptor).addPathPatterns("/**") //所有资源,包括静态资源
                .excludePathPatterns("/js/**").excludePathPatterns("/error").excludePathPatterns("/order_save.jsp");
    }


    // 静态资源配置
    @Override
    public void addResourceHandlers(ResourceHandlerRegistry registry) {
        registry.addResourceHandler("/**").addResourceLocations("/","classpath:webapp/");
        WebMvcConfigurer.super.addResourceHandlers(registry);
    }

}

AutoIdempotentTokenController.java


package com.example.controller;

import com.example.util.IdempotentTokenService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.ResponseBody;

@Controller
public class AutoIdempotentTokenController {
    @Autowired
    private IdempotentTokenService tokenService;
    //获得token
    @GetMapping("getIdempodentToken")
    @ResponseBody
    public String getIdempodentToken(){
        System.out.println("创建token");
        return tokenService.createToken();
    }


}


MyOrderController.java


package com.example.controller;


import com.example.entity.MyOrder;
import com.example.util.AutoIdempotent;
import org.springframework.web.bind.annotation.PatchMapping;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

@RestController
@RequestMapping
public class MyOrderController {

    @AutoIdempotent  // 检查表单是否重复提交 IdempotentTokenService.checkToken(..) true 放行
    @PostMapping("myorder")
    public String save(MyOrder myOrder){
        System.out.println("接收到了订单信息,正在生成订单service-mapper");
        return myOrder.toString();
    }


}


MyOrder.java


package com.example.entity;

import java.time.LocalDateTime;

public class MyOrder {
    private Integer orderId;
    private Integer custId;
    private Integer proId;
    private String proName;
    private Float proPrice;
    private String proColor;
    private Float proCapacity;
    private Integer mycouponId;
    private Float couponPrice;
    private Float orderPrice;
    private Integer proCount;
    private Integer status;
    private Integer version;
    private LocalDateTime createTime;
    private LocalDateTime updateTime;
    private String other1;
    private String other2;






}


AutoIdempotentInterceptor.java


package com.example.interceptor;


import com.example.util.AutoIdempotent;
import com.example.util.IdempotentTokenService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import org.springframework.web.method.HandlerMethod;
import org.springframework.web.servlet.HandlerInterceptor;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.lang.reflect.Method;


@Component
public class AutoIdempotentInterceptor implements HandlerInterceptor {
    //在这个AutoIdempotentInterceptor拦截器中,preHandle方法用于在请求处理之前执行一些逻辑。
    //
    //if( !(handler instanceof HandlerMethod)) { return true; } 这一行代码的作用是检查当前的处理器handler是否是一个HandlerMethod的实例。
    //
    //handler instanceof HandlerMethod: 这是一个Java的类型检查表达式,用于检查handler是否是HandlerMethod类型或其子类型的实例。
    //!: 这是逻辑非运算符,用于取反。因此,!(handler instanceof HandlerMethod) 表示handler不是HandlerMethod类型或其子类型的实例。
    //return true;: 如果handler不是HandlerMethod类型或其子类型的实例,则直接返回true,表示放行,不执行后续的拦截逻辑。
    //简而言之,这行代码的意思是:如果当前的处理器handler不是一个HandlerMethod的实例,那么就不执行后续的拦截逻辑,直接放行。这通常用于排除一些不需要进行幂等性检查的请求处理器。

//    @Autowired
//    private IdempotentTokenService idempotentTokenService;
//
//
//
//
//    public boolean preHandle(HttpServletRequest request,Object handler) throws Exception {
//        if(!(handler instanceof HandlerMethod)) {
//            return true;
//        }
//
//
//        Method method = ((HandlerMethod) handler).getMethod();
//        if(method.getAnnotation(AutoIdempotent.class) != null) {
//            boolean result = idempotentTokenService.checkToken(request);
//            if(result){//true:放行,继续执行后面的业务,false:已拦截,不继续执行业务
//                System.out.println("这是第一次提交表单,可以放行");
//                return true;
//            }else {
//                System.out.println("这是重复提交表单,不可以放行");
//                return false;
//            }
//        }
//        return true;

    //}


    @Autowired
    private IdempotentTokenService tokenService;
    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
        System.out.println("======AutoIdempotentInterceptor 拦截器正在拦截=======");
        if (!(handler instanceof HandlerMethod))
            return true; // 放行,不拦截
        HandlerMethod handlerMethod = (HandlerMethod) handler;

//        Method method = handlerMethod.getMethod();
//        AutoIdempotent methodAnnotation =  method.getAnnotation(AutoIdempotent.class);
//        if(methodAnnotation !=null){
//            // 验证token 是否有效
//            return tokenService.checkToken(request);
//        }

        Method method = ((HandlerMethod) handler).getMethod();
        if (method.getAnnotation(AutoIdempotent.class) != null) {
            boolean result = tokenService.checkToken(request);
            if (result) {//true:放行,继续执行后面的业务,false:已拦截,不继续执行业务
                System.out.println("这是第一次提交表单,可以放行");
                return true;
            } else {
                System.out.println("这是重复提交表单,不可以放行");
                return false;
            }




        }
        return true;

    }

}


AutoIdempotent


package com.example.util;

import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface AutoIdempotent {
}


IdempotentTokenService.java


package com.example.util;


import javax.servlet.http.HttpServletRequest;

/**
 * 服务器生成幂等token值与验证 幂等token
 */
public interface IdempotentTokenService {
    //1.生成幂等token
    public String createToken();

    //2.验证 幂等token是否有效
    public boolean checkToken(HttpServletRequest request);
}


IdempotentTokenServiceImpl.java


package com.example.util;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Service;

import javax.servlet.http.HttpServletRequest;
import java.util.UUID;
import java.util.concurrent.TimeUnit;

@Service
public class IdempotentTokenServiceImpl implements IdempotentTokenService {

    @Autowired
    private RedisTemplate<String,Object> redisTemplate;


    //1.生成幂等token:(1)生成唯一的token(2)redis setnx token...
    @Override
    public String createToken() {

        String token = UUID.randomUUID().toString().replace("-","");
        redisTemplate.opsForValue().set(token,token,1, TimeUnit.MINUTES);

        return token;
    }

    //2.验证幂等token是否有效
    @Override
    public boolean checkToken(HttpServletRequest request) {
        System.out.println("=====正在验证token========");
        //(1)获得token
        String token = request.getParameter("toke");
        System.out.println("获得到的幂等token的值是:"+token);
        //(2)Redis 验证token是否存在,若存在,说明有效的请求,并Redis中删除该token
        if(redisTemplate.hasKey(token)){
            redisTemplate.delete(token);
            return true;
        }else {
            //(3)若token不存在,说明是重复的请求,重定向到指定的页面(或抛异常)
            System.out.println("该请求是重复的,token不在Redis中");
            return false;
        }


    }
}


SpringbootRedisDemoApplication.java


package com.example;

import org.mybatis.spring.annotation.MapperScan;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cache.annotation.EnableCaching;
import org.springframework.scheduling.annotation.EnableScheduling;


@SpringBootApplication
@MapperScan("com.example.mapper")
@EnableCaching
@EnableScheduling  // 开启任务调度器
public class SpringbootRedisDemoApplication {

    public static void main(String[] args) {
        SpringApplication.run(SpringbootRedisDemoApplication.class, args);
    }

}


application.yaml


server:
  servlet:
    context-path: /app  #项目名
  port: 8080


spring:
  datasource:
    driver-class-name: com.mysql.cj.jdbc.Driver
    url: jdbc:mysql://localhost:3306/dict?useSSL=true&useUnicode=true&characterEncoding=utf-8&serverTimezone=Asia/Shanghai
    username: root
    password: 123456
  redis:
    host: localhost
    port: 6379
    database: 0
    connect-timeout: 1800000
  jackson:
    serialization:
      write-dates-as-timestamps: false


logging:
  file:
    path: d://logger #日志记录
  level:
    com.example: debug


order_save.jsp



<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
<head>
    <title>Title</title>
</head>
<body>


<script src="js/jquery-3.7.0.min.js"  ></script>
<html>
<head>
    <title>Title</title>
</head>
<body>

<h2>下订单</h2>
<form action="myorder" method="post">
    用户id: <input type="text" name="custId">
    商品id: <input type="text" name="proId">
    商品名称: <input type="text" name="proName">
    商品价格: <input type="text" name="proPrice">
    购买数量: <input type="text" name="proCount">
    <input type="hidden" name="toke" class="token">
    <input type="submit" value="去下订单">
</form>

<script>
    createIdempodentToken();
    function createIdempodentToken() {
        $.ajax({
            type: "get",
            url: "getIdempodentToken",
            success: function (result) {
                console.log(result)
                $(".token").val(result)
            }
        })
    }
</script>



</body>
</html>


本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mfbz.cn/a/568697.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

iOS - 多线程-GCD-队列组

文章目录 iOS - 多线程-GCD-队列组1. 队列组1.1 基本使用步骤 iOS - 多线程-GCD-队列组 开发过程中&#xff0c;有时候想实现这样的效果 多个任务并发执行所有任务执行完成后&#xff0c;进行下一步处理&#xff08;比如回到主线程刷新UI&#xff09; 1. 队列组 可以使用GC…

探索亚马逊云科技「生成式 AI 精英速成计划」

目录 前言「生成式 AI 精英速成计划」技术开发课程学习课程学习 总结 前言 亚马逊云科技&#xff08;Amazon Web Services&#xff0c;简称AWS&#xff09;作为全球领先的云计算服务提供商&#xff0c;一直以来在推动人工智能&#xff08;AI&#xff09;领域的发展中扮演着重要…

调度问题变形的贪心算法分析与实现

调度问题变形的贪心算法分析与实现 一、问题背景与算法描述二、算法正确性证明三、算法实现与分析四、结论 一、问题背景与算法描述 带截止时间和惩罚的单位时间任务调度问题是一个典型的贪心算法应用场景。该问题的目标是最小化超过截止时间导致的惩罚总和。给定一组单位时间…

element plus:tree拖动节点交换位置和改变层级

图层list里有各种组件&#xff0c;用element plus的tree来渲染&#xff0c;可以把图片等组件到面板里&#xff0c;面板是容器&#xff0c;非容器组件&#xff0c;比如图片、文本等&#xff0c;就不能让其他组件拖进来。 主要在于allow-drop属性的回调函数编写&#xff0c;要理清…

毕业撒花 流感服务小程序的设计与实现

目录 1.1 总体页面设计 1.1.1 用户首页 1.1.2 新闻页面 1.1.3 我的页面 1.1.5 管理员登陆页面 1.1.6 管理员首页 1.2 用户模块 1.2.1 体检预约功能 1.2.2 体检报告功能 1.2.4 流感数据可视化功能 1.2.5 知识科普功能 1.2.6 疾病判断功能 1.2.7 出示个人就诊码功能 …

java实现解析html获取图片或视频url

一、前言 有时在实际项目中&#xff0c;比如发布某篇文章&#xff0c;需要取文章中的某张图片作为封面&#xff0c;那么此时需要文章内容&#xff0c;获取html内容中的图片地址作为封面&#xff0c;下面讲下如何获取html中的图片或视频地址。 二、实现 1.先定义一个工具类&…

Elasticsearch集群部署(Linux)

1. 准备环境 这里准备三台Linux虚拟机&#xff0c;用于配置Elasticsearch集群和部署可视化工具Kibana。 角色IP域名集群名称节点名称版本操作系统ES192.168.243.100linux100cluster-eses-node-1007.12.0CentOS 7192.168.243.101linux101cluster-eses-node-101192.168.243.102…

IDEA 常规设置,让工作便利化

1、自动提示&#xff0c;不区分大小写 File-->Settings-->Editor-->Code completion 然后把Match Case前面的勾选去掉&#xff0c;点击OK保存 2.快速生成main方法设置 idea快速生成main方法的快捷键是psvm (public static void main(String[] args) {}) &#xff1b;…

C语言入门课程学习笔记1

C语言入门课程学习笔记1 第1课 - 概论第2课 -helloworld第3课 -数据输出第4课 -数据类型与变量第5课 - 深入数据类型与变量第6课 - 类型与变量编程练习第7课 - 程序中的数据输入 本文学习自狄泰软件学院 唐佐林老师的 C语言入门课程&#xff0c;图片全部来源于课程PPT&#xff…

分割链表和回文链表习题

感谢大佬的光临各位&#xff0c;希望和大家一起进步&#xff0c;望得到你的三连&#xff0c;互三支持&#xff0c;一起进步 个人主页&#xff1a;LaNzikinh-CSDN博客 收入专栏:初阶数据结构_LaNzikinh篮子的博客-CSDN博客 文章目录 一.回文链表LCR 027. 回文链表 - 力扣&#x…

BUUCTF---[SWPU2019]神奇的二维码

1、下载附件是一张二维码&#xff0c;拿去扫描得到了flag 2、拿去提交是错的&#xff08;不会这么简单哈哈哈&#xff09;&#xff0c;常规操作在kali中分析 3、分离发现图片里面有东西 4、查看txt&#xff0c;发现里面有一串字符&#xff0c;解码后为 5、查看文档&#xff0c…

比特币之路:技术突破、创新思维与领军人物

比特币的兴起是一段充满技术突破、创新思维和领军人物的传奇之路。在这篇文章中&#xff0c;我们将探讨比特币发展的历程&#xff0c;以及那些在这一过程中发挥重要作用的关键人物。 技术突破与前奏 比特币的诞生并非凭空而来&#xff0c;而是建立在先前的技术储备之上。在密码…

小程序 rich-text 解析富文本 图片过大时如何自适应?

在微信小程序中&#xff0c;用rich-text 解析后端返回的数据&#xff0c;当图片尺寸太大时&#xff0c;会溢出屏幕&#xff0c;导致横向出现滚动 查看富文本代码 图片是用 <img 标签&#xff0c;所以写个正则匹配一下图片标签&#xff0c;手动加上样式即可 // content 为后…

(一)、SQL进阶——神奇的SQL

一、CASE表达式 1、CASE表达式概述 case表达式有简单case表达式和搜索case表达式两种写法 -- 简单case表达式 case sex when 1 then 男 when 0 then 女 else 其他 end -- 搜索case表达式 case when sex1 then 男 when sex1 then 男 else 其他 end 这两种写法执行的结…

(5)步态识别论文研读——GaitDAN:基于对抗域适应的跨视角步态识别

GaitDAN: Cross-view Gait Recognition via Adversarial Domain Adaptation | IEEE Journals & Magazine | IEEE Xplore GaitDAN: Cross-view Gait Recognition via Adversarial Domain Adaptation 基于对抗与适应 摘要&#xff1a;视角变化导致步态外观存在显着差异。因…

黄金行情下跌有投资机会吗?

尽管黄金价格的波动常常引起投资者的高度关注&#xff0c;但行情的下跌未必只是警讯&#xff0c;亦可能蕴藏着某些难得的投资机会。总之&#xff0c;答案是肯定的——在黄金行情下跌时&#xff0c;依旧有适宜的投资机会&#xff0c;只是这需要投资者具备相应的应对知识和策略。…

python基础知识点(蓝桥杯python科目个人复习计划66)

今日复习内容&#xff1a;算法双周赛 第一题&#xff1a;疯狂星期六 题目描述&#xff1a; 麦肯鸡是一家名声在外的汉堡店&#xff0c;他们最近推出了一份名为vivo50的套餐&#xff0c;只需要在门口大声喊出vivo50&#xff0c;就可以获得这个套餐。 现在&#xff0c;请你打…

经典案例|使用Supabase解决可视化大屏项目的常见问题

敏博科技专业致力于应急管理行业&#xff0c;提供以物联网技术和感知预警算法模型为核心的先进产品和解决方案。应急管理行业的业务非常繁多和复杂&#xff0c;很多时候都需要在短时间内交付出稳定高效的业务系统。如下两张图某市的安全生产监测预警系统 MemFire Cloud应用开…

WordPress自动采集发布AutoPostPro汉化版插件

WP-AutoPostPro 是一款极为出色的WordPress自动采集发布插件&#xff0c;其显著优势在于能够从任何网站抓取内容并自动将其发布到你的WordPress网站上。它实现了对任何网页内容的自动采集和发布&#xff0c;整个采集过程完全自动化&#xff0c;无需手动操作。 项 目 地 址 &…

BM25检索算法 python

1.简介 BM25&#xff08;Best Matching 25&#xff09;是一种经典的信息检索算法&#xff0c;是基于 TF-IDF算法的改进版本&#xff0c;旨在解决、TF-IDF算法的一些不足之处。其被广泛应用于信息检索领域的排名函数&#xff0c;用于估计文档D与用户查询Q之间的相关性。它是一种…
最新文章