如何使用注解实现接口的幂等性校验

如何使用注解实现接口的幂等性校验

    • 背景
    • 什么是幂等性
    • 为什么要实现幂等性校验
    • 如何实现接口的幂等性校验
      • 1. 数据库唯一主键
      • 2. 数据库乐观锁
      • 3. 防重 Token 令牌
      • 4. redis
    • 如何将这几种方式都组装到一起
    • 结语

背景

最近在小组同学卷的受不了的情况下,我决定换一个方向卷去,在算法上还是认命吧,跟他们差距太大了, 在最近一段时间偶然看到网上关于接口幂等性校验的文章,在我一番思索下,发现他们的实现原理各有不同而且每种实现原理各有不同,加之最近恰好在学设计模式,我就在想怎样利用设计模式让我们可以随意选择不同的实现方式。在此声明一下,笔者仅仅是一个学生,对于正式的业务流程开发并不太懂,只是利用自己现有的知识储备,打造一个让自己使用起来更方便的小demo, 如果有大佬觉得哪儿有问题,欢迎指出。
在这里插入图片描述

什么是幂等性

在数学领域中对于幂等性的解释是
f(f(x)) = f(x)
即幂等元素x在函数f的多次作用下,其效果和在f的一次作用下相同。在编程上可以理解为,如果某个函数(方法)或接口被调用多次其行为结果和被调用一次相同,则这种函数或接口就具有幂等性。
简单举个例子,天然幂等性

假设对象Person中有个name属性,有个

setName(String name){
   this.name = name
}

的方法,那这个方法就是天然幂等的哦,你输入相同的“小明”参数,不论你重复调用多少次都是将名字设置为“小明”,其对对象Person的影响都是一样的。这就是天然幂等性。

非幂等性
还是拿对象Person举例子,假设对象中有个age属性,有个

increaseAge(){
   this.age++;
}

方法,我们按正常的步骤一次一次调用是不会有问题的,如果调用者没有控制好逻辑,一次流程重复调用好几次,这时候影响效果和一次是有非常大区别,代码编写者以为它只会调用一次,结果出现了意外调用了很多次,恰好方法不具有幂等性,于是就会出现和预期不一样的效果。这个方法本身是不具备幂等性的,我们可以修改这个方法,让其传入一个标识符,每一次重复的请求会有相同的标识符,方法内部可以根据标识符查数据库是不是已经处理过,如果处理过就不重复处理。这样方法就具备了幂等性。

更通俗一点就是:
当在进行转账的时候,我们分了两个系统来处理这个转账的流程:

①系统A负责收集转账人和接收人还有金额的信息然后传给系统B进行转账,将控制逻辑留在系统A。

②系统B读取系统A传过来的信息,负责更改数据库的金额。如果操作成功,就回复系统A成功,如果失败就回复系统A失败。

③系统A可以接受系统B操作成功或失败的回复,但是我们知道,系统A这个交易流程是有等待时间的,如果等待超时,它不确认是否是转账成功或失败,于是系统A会重试调用直到得到一个明确的回复。

这是系统大致的交易流程。这个流程是有问题的,系统B提供的操作接口不是幂等性的,因为A会重复调用接口,导致出现一个接口被同一个数据源发送相同数据切想要达到请求一次接口的效果的现象。
在这里插入图片描述
常见请求方式的幂等性

  • √ 满足幂等
  • x 不满足幂等
  • 可能满足也可能不满足幂等,根据实际业务逻辑有关
方法类型是否幂等描述
GetGet 方法用于获取资源。其一般不会也不应当对系统资源进行改变,所以是幂等的。
PostxPost 方法一般用于创建新的资源。其每次执行都会新增数据,所以不是幂等的。
Put_Put 方法一般用于修改资源。该操作则分情况来判断是不是满足幂等,更新操作中直接根据某个值进行更新,也能保持幂等。不过执行累加操作的更新是非幂等。
Delete_Delete 方法一般用于删除资源。该操作则分情况来判断是不是满足幂等,当根据唯一值进行删除时,删除同一个数据多次执行效果一样。不过需要注意,带查询条件的删除则就不一定满足幂等了。例如在根据条件删除一批数据后,这时候新增加了一条数据也满足条件,然后又执行了一次删除,那么将会导致新增加的这条满足条件数据也被删除。

为什么要实现幂等性校验

在接口调用时一般情况下都能正常返回信息不会重复提交,不过在遇见以下情况时可以就会出现问题,如:

  • 前端重复提交表单: 在填写一些表格时候,用户填写完成提交,很多时候会因网络波动没有及时对用户做出提交成功响应,致使用户认为没有成功提交,然后一直点提交按钮,这时就会发生重复提交表单请求。
  • 用户恶意进行刷单: 例如在实现用户投票这种功能时,如果用户针对一个用户进行重复提交投票,这样会导致接口接收到用户重复提交的投票信息,这样会使投票结果与事实严重不符。
  • 接口超时重复提交: 很多时候 HTTP 客户端工具都默认开启超时重试的机制,尤其是第三方调用接口时候,为了防止网络波动超时等造成的请求失败,都会添加重试机制,导致一个请求提交多次。
  • 消息进行重复消费: 当使用 MQ 消息中间件时候,如果发生消息中间件出现错误未及时提交消费信息,导致发生重复消费。

使用幂等性最大的优势在于使接口保证任何幂等性操作,免去因重试等造成系统产生的未知的问题。

在这里插入图片描述

如何实现接口的幂等性校验

网上流传最多的应该是四种方式去实现接口的幂等性校验,接下来我们来一个个盘点。

1. 数据库唯一主键

方案描述
数据库唯一主键的实现主要是利用数据库中主键唯一约束的特性,一般来说唯一主键比较适用于“插入”时的幂等性,其能保证一张表中只能存在一条带该唯一主键的记录。
使用数据库唯一主键完成幂等性时需要注意的是,该主键一般来说并不是使用数据库中自增主键,而是使用分布式 ID 充当主键(或者使用其他算法生成的全局唯一的id),这样才能能保证在分布式环境下 ID 的全局唯一性。

适用操作:
插入操作
删除操作

使用限制:
需要生成全局唯一主键 ID;

主要流程:
① 客户端执行创建请求,调用服务端接口。
② 服务端执行业务逻辑,生成一个分布式 ID,将该 ID 充当待插入数据的主键,然后执数据插入操作,运行对应的 SQL 语句。
③ 服务端将该条数据插入数据库中,如果插入成功则表示没有重复调用接口。如果抛出主键重复异常,则表示数据库中已经存在该条记录,返回错误信息到客户端。

2. 数据库乐观锁

方案描述:
数据库乐观锁方案一般只能适用于执行“更新操作”的过程,我们可以提前在对应的数据表中多添加一个字段,充当当前数据的版本标识。这样每次对该数据库该表的这条数据执行更新时,都会将该版本标识作为一个条件,值为上次待更新数据中的版本标识的值。
适用操作:
更新操作

使用限制:
需要数据库对应业务表中添加额外字段;

3. 防重 Token 令牌

方案描述:
针对客户端连续点击或者调用方的超时重试等情况,例如提交订单,此种操作就可以用 Token 的机制实现防止重复提交。简单的说就是调用方在调用接口的时候先向后端请求一个全局 ID(Token),请求的时候携带这个全局 ID 一起请求(Token 最好将其放到 Headers 中),后端需要对这个 Token 作为 Key,用户信息作为 Value 到 Redis 中进行键值内容校验,如果 Key 存在且 Value 匹配就执行删除命令,然后正常执行后面的业务逻辑。如果不存在对应的 Key 或 Value 不匹配就返回重复执行的错误信息,这样来保证幂等操作。

适用操作:
插入操作
更新操作
删除操作

使用限制:
需要生成全局唯一 Token 串;
需要使用第三方组件 Redis 进行数据效验;

4. redis

方案描述:

第四种是我觉着用着挺方便的,但是实用性应该不大,而且和第三种类似,我们可以把接口名加请求参数通过算法生成一个全局唯一的id,然后 存到redis中,如果在一定时间请求多次,我们就直接拒绝。

适用操作:
插入操作
更新操作
删除操作

使用限制:
需要使用第三方组件 Redis 进行数据效验;

如何将这几种方式都组装到一起

我使用了Java自带的注解以及设计模式中的策略模式,我们可以在注解中直接指定幂等性校验的方式,当然也可以在配置文件中指定,但是直接在注解中指定更加灵活。
在这里插入图片描述
但是,由于最近时间比较忙,天天被某些人卷,很少有时间去完善,目前只是实现了redis和防重 Token 令牌两种方式的。
以下是部分代码

自定义注解

package org.example.annotation;

import java.lang.annotation.*;

/**
 * @author zrq
 */
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
@Documented
public @interface RequestMany {
    /**
     * 策略
     * @return
     */
    String value() default "";

    /**
     * 过期时间
     * @return
     */
    long expireTime() default 0;
}

定义切面

package org.example.aop;

import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.reflect.MethodSignature;
import org.example.annotation.RequestMany;
import org.example.factory.RequestManyStrategy;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import org.springframework.util.DigestUtils;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;

import javax.servlet.http.HttpServletRequest;
import java.lang.reflect.Method;
import java.util.Arrays;
import java.util.Map;
import java.util.stream.Collectors;

/**
 * @author zrq
 * @ClassName RequestManyValidationAspect
 * @date 2023/11/22 9:14
 * @Description TODO
 */
@Aspect
@Component
public class RequestManyValidationAspect {
    @Autowired
    private Map<String, RequestManyStrategy> idempotentStrategies;

    @Around("@annotation(org.example.annotation.RequestMany)")
    public Object validateIdempotent(ProceedingJoinPoint joinPoint) throws Throwable {
        MethodSignature methodSignature = (MethodSignature) joinPoint.getSignature();
        Method method = methodSignature.getMethod();

        RequestMany requestMany = method.getAnnotation(RequestMany.class);
        String strategy = requestMany.value(); // 获取注解中配置的策略名称
        Integer time = (int)requestMany.expireTime(); // 获取注解中配置的策略名称
        if (!idempotentStrategies.containsKey(strategy)) {
            throw new IllegalArgumentException("Invalid idempotent strategy: " + strategy);
        }
        String key = generateKey(joinPoint); // 根据方法参数等生成唯一的key
        RequestManyStrategy idempotentStrategy = idempotentStrategies.get(strategy);
        idempotentStrategy.validate(key, time);
        return joinPoint.proceed();
    }

    private String generateKey(ProceedingJoinPoint joinPoint) {
        // 获取类名
        String className = joinPoint.getTarget().getClass().getSimpleName();

        // 获取方法名
        MethodSignature methodSignature = (MethodSignature) joinPoint.getSignature();
        String methodName = methodSignature.getMethod().getName();

        // 获取方法参数
        Object[] args = joinPoint.getArgs();
        String argString = Arrays.stream(args)
                .map(Object::toString)
                .collect(Collectors.joining(","));
        // 获取请求携带的 Token
        HttpServletRequest request = ((ServletRequestAttributes) RequestContextHolder.getRequestAttributes()).getRequest();
        String token = request.getHeader("token");
        // 生成唯一的 key
        String key = className + ":" + methodName + ":" + argString + ":" + token;
        String md5Password = DigestUtils.md5DigestAsHex(key.getBytes());
        return md5Password;
    }

}

处理异常

package org.example.exception;

/**
 * 运行时异常
 * @author binbin.hou
 * @since 0.0.1
 */
public class RequestManyValidationException extends RuntimeException {

    public RequestManyValidationException() {
    }

    public RequestManyValidationException(String message) {
        super(message);
    }

    public RequestManyValidationException(String message, Throwable cause) {
        super(message, cause);
    }

    public RequestManyValidationException(Throwable cause) {
        super(cause);
    }

    public RequestManyValidationException(String message, Throwable cause, boolean enableSuppression, boolean writableStackTrace) {
        super(message, cause, enableSuppression, writableStackTrace);
    }
}

模式工厂

package org.example.factory;

import org.example.exception.RequestManyValidationException;

/**
 * @author zrq
 * @ClassName RequestManyStrategy
 * @date 2023/11/22 9:04
 * @Description TODO
 */
public interface RequestManyStrategy {
    void validate(String key, Integer time) throws RequestManyValidationException;
}

模式实现01

package org.example.factory.impl;

import org.example.exception.RequestManyValidationException;
import org.example.factory.RequestManyStrategy;
import org.example.utils.RedisCache;
import org.springframework.stereotype.Component;

import javax.annotation.Resource;
import java.util.concurrent.TimeUnit;

/**
 * @author zrq
 * @ClassName RedisIdempotentStrategy
 * @date 2023/11/22 9:07
 * @Description TODO
 */
@Component
public class RedisIdempotentStrategy implements RequestManyStrategy {
    @Resource
    private RedisCache redisCache;

    @Override
    public void validate(String key, Integer time) throws RequestManyValidationException {
        if (redisCache.hasKey(key)) {
            throw new RequestManyValidationException("请求次数过多");
        } else {
            redisCache.setCacheObject(key,"1", time, TimeUnit.MINUTES);
        }
    }
}

模式实现02

package org.example.factory.impl;

import org.example.exception.RequestManyValidationException;
import org.example.factory.RequestManyStrategy;
import org.example.utils.RedisCache;
import org.springframework.data.redis.connection.RedisConnectionFactory;
import org.springframework.data.redis.connection.jedis.JedisConnectionFactory;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.serializer.StringRedisSerializer;
import org.springframework.stereotype.Component;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;

import javax.annotation.Resource;
import javax.servlet.http.HttpServletRequest;

/**
 * @author zrq
 * @ClassName TokenIdempotentStrategy
 * @date 2023/11/22 9:13
 * @Description TODO
 */
@Component
public class TokenIdempotentStrategy implements RequestManyStrategy {
    @Resource
    private RedisCache redisCache;
    @Override
    public void validate(String key, Integer time) throws RequestManyValidationException {
        HttpServletRequest request = ((ServletRequestAttributes) RequestContextHolder.getRequestAttributes()).getRequest();
        String token = request.getHeader("token");
        if (token == null || token.isEmpty()) {
            throw new RequestManyValidationException("未授权的token");
        }
        // 根据 key 和 token 执行幂等性校验
        boolean isDuplicateRequest = performTokenValidation(key, token);
        if (!isDuplicateRequest) {
            throw new RequestManyValidationException("多次请求");
        }
    }
    private boolean performTokenValidation(String key, String token) {
        // 执行根据 Token 进行幂等性校验的逻辑
        // 这里可以使用你选择的合适的方法,比如将 Token 存储到数据库或缓存中,然后检查是否已存在
        String storedToken = redisCache.getCacheObject(key);
        // 比较存储的 Token 和当前请求的 Token 是否一致
        return token.equals(storedToken);
    }

}

redisutil类

package org.example.utils;

import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.connection.BitFieldSubCommands;
import org.springframework.data.redis.core.*;
import org.springframework.stereotype.Component;

import java.util.*;
import java.util.concurrent.TimeUnit;

@SuppressWarnings(value = { "unchecked", "rawtypes" })
@Component
@Slf4j
public class RedisCache
{
    @Autowired
    public RedisTemplate redisTemplate;
    @Autowired
    private StringRedisTemplate stringRedisTemplate;

    /**
     * 缓存基本的对象,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 Integer 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);
    }
    public boolean hasKey(final String key)
    {
        return Boolean.TRUE.equals(redisTemplate.hasKey(key));
    }

    /**
     * 设置有效时间
     *
     * @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 缓存键值
     * @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 long deleteObject(final Collection collection)
    {
        return redisTemplate.delete(collection);
    }

    /**
     * 缓存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<Integer, 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
     * @param hkey
     */
    public void delCacheMapValue(final String key, final String hkey)
    {
        HashOperations hashOperations = redisTemplate.opsForHash();
        hashOperations.delete(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);
    }

    /**
     * 获得缓存的基本对象列表
     *
     * @param pattern 字符串前缀
     * @return 对象列表
     */
    public Collection<String> keys(final String pattern)
    {
        return redisTemplate.keys(pattern);
    }

    public Boolean sign(String key, int day, boolean sign) {
      return  redisTemplate.opsForValue().setBit(key, day, sign);
    }

    public List<Long> result(String key, int day, int num){
        List<Long> result = stringRedisTemplate.opsForValue().bitField(key, BitFieldSubCommands.create()
                .get(BitFieldSubCommands.BitFieldType.unsigned(day)).valueAt(0)
        );
        return result;
    }

    public Long bigCount(String key) {
        Long execute = (Long)redisTemplate.execute((RedisCallback) cbk -> cbk.bitCount(key.getBytes()));
        return execute;
    }
    public <T> void setCacheZSet(String name,T key, double num) {
        if (key != null) {
            redisTemplate.opsForZSet().add(name,key,num);
        }
    }
    public <T> Long getCacheZSetRanking(String name,String key) {
        Long aLong = null;
        if (key != null) {
            aLong = redisTemplate.opsForZSet().reverseRank(name, key);
        }
        return aLong;
    }
    public <T> Double getCacheZSetScore(String name,T key) {
        Double score = null;
        if (key != null) {
            score = redisTemplate.opsForZSet().score(name, key);
        }
        return score;
    }
    public  Set getCacheZSetLookTop(String name,int nums) {
        Set set = null;
        if (name != null) {
            set = redisTemplate.opsForZSet().reverseRange(name, 0, nums);
        }
        return set;
    }
    public Long getCacheZSetSize(String key) {
        Long aLong = null;
        if (key != null) {
            aLong = redisTemplate.opsForZSet().zCard(key);
        }
        return aLong;
    }

    public <T> Long deleteCacheZSet(String key, T value) {
        Long remove = null;
        if (key != null) {
            remove = redisTemplate.opsForZSet().remove(key, value);
        }
        return remove;
    }
    public List<Long> getBitMap(String key,Integer day) {
        List<Long> bitFieldList = (List<Long>) redisTemplate.execute((RedisCallback<List<Long>>) cbk
                -> cbk.bitField(key.getBytes(), BitFieldSubCommands.create().get(BitFieldSubCommands.BitFieldType.unsigned(day)).valueAt(0)));
        return bitFieldList;
    }
    public Long incrExpire(String key, long time) {
        Long count = redisTemplate.opsForValue().increment(key, 1);
        if (count != null && count == 1) {
            redisTemplate.expire(key, time, TimeUnit.SECONDS);
        }
        return count;
    }
    public boolean removeList(String listName, Integer count, String value) {
        redisTemplate.opsForList().remove(listName,count,value);
        return true;
    }
}

配置文件
在这里插入图片描述
如果要实现其他方式的话只需要实现下RequestManyStrategy模板方法,然后编写自己的校验逻辑就可以。欢迎大佬指正错误。
以上代码已经上传到github 仓库地址点这里

结语

大学过的可真快,转眼就大三了,自己的技术还是不行,跟别人的差距还有很大距离,希望自己能在有限的时间里学到更多有用的知识,同时也希望在明年的这个时候可以坐在办公室里敲代码。突然想到高中时中二的一句话“听闻少年二字,应与平庸相斥”,谁不希望这样呢,奈何身边大佬太多,现在只能追赶别人的脚步。。。
在这里插入图片描述

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

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

相关文章

人工智能轨道交通行业周刊-第67期(2023.11.27-12.3)

本期关键词&#xff1a;列车巡检机器人、城轨智慧管控、制动梁、断路器、AICC大会、Qwen-72B 1 整理涉及公众号名单 1.1 行业类 RT轨道交通人民铁道世界轨道交通资讯网铁路信号技术交流北京铁路轨道交通网上榜铁路视点ITS World轨道交通联盟VSTR铁路与城市轨道交通RailMetro…

使用Prometheus监控Padavan路由器

Prometheus监控Padavan路由器 1、背景 近期在Synology&#xff08;群辉&#xff09;中安装一套Prometheus监控程序&#xff0c;目前已经监控Synology&#xff0c;然后家中有有路由器&#xff08;Padavan&#xff09;型号&#xff0c;也准备使用PrometheusGrafan进行监控。 ‍…

Java基本数据类型详解

✨个人主页&#xff1a;全栈程序猿的CSDN博客 &#x1f4a8;系列专栏&#xff1a;Java从入门到精通 ✌座右铭&#xff1a;编码如诗&#xff0c;Bug似流星&#xff0c;持续追求优雅的代码&#xff0c;解决问题如同星辰般自如 Java是一种强类型语言&#xff0c;数据类型在程序中起…

如何成为一名高效的前端开发者(10X开发者)

如今&#xff0c;每个人都想成为我们所说的“10倍开发者”。然而&#xff0c;这个术语经常被误解和高估。 本质上&#xff0c;一个高效或者10倍开发者&#xff0c;在我看来&#xff0c;是指那些能够充分利用所有可用工具的人&#xff0c;通过让这些工具处理冗余和重复的任务&am…

Java线程池的使用和最佳实践

第1章&#xff1a;引言 处理并发问题时&#xff0c;如果每次都新建线程&#xff0c;那系统的压力得有多大&#xff1f;这时候&#xff0c;线程池就像一个英雄一样出现了&#xff0c;它帮我们有效地管理线程&#xff0c;提高资源利用率&#xff0c;降低开销。那么&#xff0c;为…

iOS 自动签名打包,并用脚本上传appstore

背景&#xff1a; 1&#xff09;测试环境给测试&#xff0c;产品&#xff0c;或者其他业务人员打测试包时&#xff0c;经常存在需要添加设备&#xff0c;不得不重新生成描述文件&#xff0c;手动去更新打包机描述文件配置 2&#xff09;证书&#xff0c;描述文件过期造成打包失…

Lag-Llama:基于 LlaMa 的单变量时序预测基础模型

文章构建了一个通用单变量概率时间预测模型 Lag-Llama&#xff0c;在来自Monash Time Series库中的大量时序数据上进行了训练&#xff0c;并表现出良好的零样本预测能力。在介绍Lag-Llama之前&#xff0c;这里简单说明什么是概率时间预测模型。概率预测问题是指基于历史窗口内的…

目标检测算法改进系列之添加变核卷积AKConv模块

AKConv变核卷积 KConv的主要思想&#xff1a;AKConv&#xff08;可变核卷积&#xff09;主要提供一种灵活的卷积机制&#xff0c;允许卷积核具有任意数量的参数和采样形状。这种方法突破了传统卷积局限于固定局部窗口和固定采样形状的限制&#xff0c;从而使得卷积操作能够更加…

【栈和队列(2)】

文章目录 前言队列队列方法队列模拟实现循环队列练习1 队列实现栈 前言 队列和栈是相反的&#xff0c;栈是先进后出&#xff0c;队列是先进先出&#xff0c;相当于排队打饭&#xff0c;排第一的是最先打到饭出去的。 队列 队列&#xff1a;只允许在一端进行插入数据操作&…

Python练习题(二)

&#x1f4d1;前言 本文主要是【Python】——Python练习题的文章&#xff0c;如果有什么需要改进的地方还请大佬指出⛺️ &#x1f3ac;作者简介&#xff1a;大家好&#xff0c;我是听风与他&#x1f947; ☁️博客首页&#xff1a;CSDN主页听风与他 &#x1f304;每日一句&am…

Pytest测试攻略:探寻pytest.main()隐藏的利器

更多资料获取 &#x1f4da; 个人网站&#xff1a;ipengtao.com 在Pytest测试框架中&#xff0c;pytest.main()是一个重要的功能&#xff0c;用于启动测试执行。它允许以不同方式运行测试&#xff0c;传递参数和配置选项。本文将深入探讨pytest.main()的核心功能&#xff0c;提…

栈和队列OJ题——15.循环队列

15.循环队列 622. 设计循环队列 - 力扣&#xff08;LeetCode&#xff09; * 解题思路&#xff1a; 通过一个定长数组实现循环队列 入队&#xff1a;首先要判断队列是否已满&#xff0c;再进行入队的操作&#xff0c;入队操作需要考虑索引循环的问题&#xff0c;当索引越界&…

Qt/QML编程学习之心得:如何添加资源文件到QML工程(十一)

Qt作为一种GUI界面编辑工具&#xff0c;在嵌入式编程中也大受欢迎&#xff0c;而进一步QML出现了&#xff0c;QML我理解也是一种资源文件&#xff0c;因为像其他资源文件一样添加进工程的。那么一个图片如何增加进资源文件呢&#xff1f;这个的确很基础&#xff0c;就是把资源文…

IdleStateHandler 心跳机制源码详解

优质博文&#xff1a;IT-BLOG-CN 一、心跳机制 Netty支持心跳机制&#xff0c;可以检测远程服务端是否存活或者活跃。心跳是在TCP长连接中&#xff0c;客户端和服务端定时向对方发送数据包通知对方自己还在线&#xff0c;保证连接的有效性的一种机制。在服务器和客户端之间一…

C++实现DFS、BFS、Kruskal算法和Prim算法、拓扑排序、Dijkstra算法

背景&#xff1a; 实现要求&#xff1a; 根据图的抽象数据类型的定义&#xff0c;请采用邻接矩阵来存储图1&#xff0c;采用邻接表来存储图2&#xff0c;并完成如下操作&#xff1a;对图1无向图进行深度优先遍历和广度优先遍历。对图1无向图采用Kruskal算法和Prim算法得出最小…

<蓝桥杯软件赛>零基础备赛20周--第8周第2讲--排序的应用

报名明年4月蓝桥杯软件赛的同学们&#xff0c;如果你是大一零基础&#xff0c;目前懵懂中&#xff0c;不知该怎么办&#xff0c;可以看看本博客系列&#xff1a;备赛20周合集 20周的完整安排请点击&#xff1a;20周计划 每周发1个博客&#xff0c;共20周&#xff08;读者可以按…

c语言-归并排序

目录 1、归并排序基本思想 2、归并排序的实现&#xff08;递归法&#xff09; 2.1 代码实现递归法归并排序 3、归并排序的实现&#xff08;非递归法&#xff09; 3.1 修正边界问题 3.2 代码实现非递归法归并排序 结语&#xff1a; 前言&#xff1a; 归并排序是一种把数…

万界星空科技灯具行业MES介绍

中国是LED照明产品最大的生产制造国&#xff0c;如今&#xff0c;我国初步形成了包括LED外延片的生产、LED芯片的制备、LED芯片的封装以及LED产品应用在内的较为完超为产业链&#xff0c;随着LED照明市场渗诱率的快速警升&#xff0c;LED下游应用市场将会越来越广阔。这也将推动…

硬件基础:二极管

基本定义 二极管的内部其实就是一个PN结。 把PN结封装起来&#xff0c;两边加上两个电极&#xff0c;就组成了半导体二极管。简称二极管&#xff08;Diode&#xff09; 二极管和PN结一样&#xff0c;具有单向导通性&#xff1a; 外观和正负极 常见芯片封装如下&#xff1a; 一般…

MDETR 论文翻译及理解

题目Abstract1. Introduction2. Method2.1. Background2.2. MDETR2.2.1 Architecture2.2.2 Training 3. Experiments3.1. Pre-training Modulated Detection 预训练调制检测3.2. Downstream Tasks3.2.1 Few-shot transfer for long-tailed detection 4. Related work5. Conclus…