img
img

网上学习资料一大堆,但如果学到的知识不成体系,遇到问题时只是浅尝辄止,不再深入研究,那么很难做到真正的技术提升。

需要这份系统化的资料的朋友,可以戳这里获取

一个人可以走的很快,但一群人才能走的更远!不论你是正从事IT行业的老鸟或是对IT行业感兴趣的新人,都欢迎加入我们的的圈子(技术交流、学习资源、职场吐槽、大厂内推、面试辅导),让我们一起学习成长!

接口的幂等性:接口幂等性就是用户对于同一操作发起的一次请求或者多次请求的结果是一致的,不会因为多次点击而产生了副作用;

@RestController
@RequestMapping("/order")
public class OrderController {
    private final RedisTemplate redisTemplate;
    public OrderController(RedisTemplate redisTemplate) {
        this.redisTemplate = redisTemplate;
    }
    @PostMapping("/make")
    public String makeOrder(OrderDto orderDto){
        if(redisTemplate.hasKey("ORDER\_" + orderDto.getOrderNum())){
           return "请求已经处理,请勿重复提交!";
        }
        //setnx key value 做到:判断key是否存在,如果key则不能加入到redis中去,称为:redis的分布式锁
        redisTemplate.opsForValue().setIfAbsent("ORDER\_" + orderDto.getOrderNum(), "",10, TimeUnit.MINUTES);
        //调用业务层:完成订单的创建
        System.out.println("orderDto = " + orderDto);
        return "ok";
    }
}

接口幂等性

if(redisTemplate.hasKey("ORDER\_" + orderDto.getOrderNum())){
    return "请求已经处理,请勿重复提交!";
}

redis的分布锁,缺陷:业务如果超出了上锁的时间,可能会导致数据不准确

Redisson框架

Redisson框架:它是Redis的封装框架,提供了Redis的所有操作

<!--引入redis的封装框架redission-->
<dependency>
    <groupId>org.redisson</groupId>
    <artifactId>redisson-spring-boot-starter</artifactId>
    <version>3.10.0</version>
</dependency>

在这里插入图片描述

添加配置类:

@Configuration
public class RedissonConfig {
    @Autowired
    private Environment env;
    
    //解决redission 和 Springcache的兼容问题
    @Bean
    public RedisCacheManager cacheManager(RedisConnectionFactory connectionFactory) {
        RedisCacheConfiguration config = RedisCacheConfiguration.defaultCacheConfig()
                .serializeKeysWith(RedisSerializationContext.SerializationPair.fromSerializer(new StringRedisSerializer())) // 设置缓存数据的key序列化方式为StringRedisSerializer
                .serializeValuesWith(RedisSerializationContext.SerializationPair.fromSerializer(new GenericJackson2JsonRedisSerializer())); // 设置缓存数据的value序列化方式为GenericJackson2JsonRedisSerializer
        RedisCacheManager cacheManager = RedisCacheManager.builder(connectionFactory)
                .cacheDefaults(config)
                .build();
        return cacheManager;
    }
    
    @Bean
    public RedissonClient redissonClient() {
        Config config = new Config();
        SingleServerConfig serverConfig = config.useSingleServer();
        // 设置 Redis 单节点地址和密码
        String address = "redis://" + env.getProperty("spring.redis.host") + ":" + env.getProperty("spring.redis.port");
        serverConfig.setAddress(address);
        serverConfig.setPassword(env.getProperty("spring.redis.password"));
        return Redisson.create(config);
    }
}

Redis上锁的方式:

//第1种
redisTemplate.opsForValue.setIfAbsent(key,value,time,TimeUnit);
    
//第2种 
RLock lock = redissonClient.getLock(key);
lock.lock(time,TimeUnit)//上锁

不管是第1种,还是第2种,底层都是:setnx key value px time;上面的2种有可能有问题,问题在于:业务可能超过:上锁的时间,导致业务还没完成,锁就已经失效

解决方案:看门狗机制

RLock lock = redissonClient.getLock(key);
try {
    //获得一把锁,锁的时间默认是30S,间隔10S检测一次锁的状态,如果超过10S锁的状态,仍旧是在使用,就把锁的时间重置为30S
    //配置 看门狗
    lock = redissonClient.getLock(lock.getName());
    lock.lock();//上锁 
    //写:上锁的以后的业务代码
}catch (Exception e){
}finally {
    lock.unlock();//立即解锁
}

接口幂等改进

使用 注解 + 拦截器完成对于 接口 进行幂等处理

HTML页面

在这里插入图片描述

OrderController

在这里插入图片描述


添加接口幂等前缀常量:

public class BookConstant {
    public static final String BOOK\_TYPE\_LIST = "BOOKTYPELIST";
    public static final String TOKEN\_PREFIX= "TOKEN\_";
    public static final String USER\_CART\_PREFIX = "USER\_CART\_";
    //接口幂等
    public static final String INTERFACE\_IDEA\_PREFIX="INTERFACE\_IDEM\_";
}

自定义注解

@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface Idempotent {
    /\*\*
 \* 幂等过期时间,即:在此时间段内,对API进行幂等处理。
 \*/
    long expireTime();
    /\*\*
 \* 时间单位
 \* @return
 \*/
    TimeUnit timeunit();
}

自定义拦截器

public class IdempotentInterceptor implements HandlerInterceptor {
    
    @Autowired
    private RedisTemplate redisTemplate;
    
    /\*\*
 \* 控制层方法,执行之前进行拦截
 \* @param request 请求对象
 \* @param response 响应对象
 \* @param handler 方法对象
 \* @return
 \* @throws Exception
 \*/
    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
        System.out.println("handler = " + handler.getClass());
        //不是拦截方法,则放行
        if(!(handler instanceof HandlerMethod))return true;
        //判断方法对象是否添加 幂等注解
        HandlerMethod hm = (HandlerMethod) handler;
        boolean flag = hm.hasMethodAnnotation(Idempotent.class);
        if(!flag) return true;//如果没有使用幂等注解,直接放行
        //如果使用幂等注解,启动幂等配置
        Idempotent idem = hm.getMethodAnnotation(Idempotent.class);
        //identification = 前端:请求的唯一标识
        String identification = request.getHeader("Identification");
        System.out.println("Identification = " + identification);
        System.out.println("idem.expireTime() = " + idem.expireTime());
        System.out.println("idem.timeunit() = " + idem.timeunit());
        Boolean b = redisTemplate.hasKey(identification);
        if(b){
            extracted(response);
            return false;
        }else{
            redisTemplate.opsForValue().setIfAbsent(identification,"",idem.expireTime(),idem.timeunit());
        }
        return true;
    }
    
    private static void extracted(HttpServletResponse response) throws IOException {
        //告诉前端:请求是重复请求,请勿重复提交!
        ResponseResult res = ResponseResult.fail(BusinessEnum.HTTP\_ALREADY\_HANDLE);
        String s = JSONUtil.toJsonStr(res);
        response.setContentType("application/json;charset=utf-8");
        PrintWriter out = response.getWriter();
        out.write(s);
    }
}

配置拦截器

@Configuration
public class WebMvcConfiguration implements WebMvcConfigurer {
    //……
    @Bean
    public IdempotentInterceptor idempotentInterceptor(){
        return new IdempotentInterceptor();
    }
    //……
    @Override
    public void addInterceptors(InterceptorRegistry registry) {
    //……
    //向SpringMVC中 注册 幂等拦截器 
        registry.addInterceptor(idempotentInterceptor()).addPathPatterns("/order/sumbitOrder/\*\*");
    }
    //……
}

前端axios提交时,添加identification请求头:

在这里插入图片描述

submitOrder(){
    axios.post('/order/sumbitOrder/'+this.addressId+"/"+this.orderNum,this.cartVo,{
        headers: {
            'identification': this.identification, // 设置请求头的Content-Type为application/json
        }
    }).then(res=>{
        if(res.data.code == 200){
            this.$message.success("订单提交成功!");
            setTimeout(()=>{
                location.href = "/order.html";
            },3000)
        }else{
            this.$message.error(res.data.msg);
        }
    });
}

在这里插入图片描述

img
img
img

既有适合小白学习的零基础资料,也有适合3年以上经验的小伙伴深入学习提升的进阶课程,涵盖了95%以上软件测试知识点,真正体系化!

由于文件比较多,这里只是将部分目录截图出来,全套包含大厂面经、学习笔记、源码讲义、实战项目、大纲路线、讲解视频,并且后续会持续更新

需要这份系统化的资料的朋友,可以戳这里获取

715282693751)]
[外链图片转存中…(img-AS9FHLaM-1715282693751)]
[外链图片转存中…(img-5cizbnJY-1715282693752)]

既有适合小白学习的零基础资料,也有适合3年以上经验的小伙伴深入学习提升的进阶课程,涵盖了95%以上软件测试知识点,真正体系化!

由于文件比较多,这里只是将部分目录截图出来,全套包含大厂面经、学习笔记、源码讲义、实战项目、大纲路线、讲解视频,并且后续会持续更新

需要这份系统化的资料的朋友,可以戳这里获取

更多推荐