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>