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>


04-24 06:52