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

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

    • 背景
    • 什么是幂等性
    • 为什么要实现幂等性校验
    • 如何实现接口的幂等性校验
      • 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 {@Autowiredprivate 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); // 根据方法参数等生成唯一的keyRequestManyStrategy 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(","));// 获取请求携带的 TokenHttpServletRequest request = ((ServletRequestAttributes) RequestContextHolder.getRequestAttributes()).getRequest();String token = request.getHeader("token");// 生成唯一的 keyString 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 {@Resourceprivate RedisCache redisCache;@Overridepublic 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 {@Resourceprivate RedisCache redisCache;@Overridepublic 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
{@Autowiredpublic RedisTemplate redisTemplate;@Autowiredprivate 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 仓库地址点这里

结语

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

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

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

基于景区智慧灯杆、智能指路牌基础设施的景区建设应用

智慧景区是指运用现代信息技术手段&#xff0c;将景区内的资源、服务、管理等进行数字化、网络化和智能化整合&#xff0c;打造出高效便捷、安全舒适、互动体验和可持续发展的景区。智慧景区可以从以下几个方面进行体现&#xff1a; 智慧导览&#xff1a;通过使用智能化的导览…

二分查找:LeetCode2035:将数组分成两个数组并最小化数组和的差

本文涉及的基础知识点 二分查找算法合集 作者推荐 动态规划LeetCode2552&#xff1a;优化了6版的1324模式 题目 给你一个长度为 2 * n 的整数数组。你需要将 nums 分成 两个 长度为 n 的数组&#xff0c;分别求出两个数组的和&#xff0c;并 最小化 两个数组和之 差的绝对…

Screenshot To Code

序言 对于GPT-4我只是一个门外汉&#xff0c;至于我为什么要了解screenshot to code&#xff0c;只是因为我想知道&#xff0c;在我不懂前端设计的情况下&#xff0c;能不能通过一些工具辅助自己做一些简单的前端界面设计。如果你想通过此文深刻了解GPT-4或者该开源项目&#…

【python】保存excel

正确安装了pandas和openpyxl库。 可以通过在命令行中输入以下命令来检查&#xff1a; pip show pandas pip show openpyxl 可以使用pip安装 pip install pandas pip install openpyxl#更新 pip install --upgrade pandas pip install --upgrade openpyxl 保存excel …

pygame实现贪吃蛇小游戏

import pygame import random# 游戏初始化 pygame.init()# 游戏窗口设置 win_width, win_height 800, 600 window pygame.display.set_mode((win_width, win_height)) pygame.display.set_caption("Snake Game")# 颜色设置 WHITE (255, 255, 255) BLACK (0, 0, 0…

如何确定短线的买入卖出时机?

短线投资制胜的一个关键能力&#xff0c;就是精准地找到买入卖出时机。那么&#xff0c;怎么样才能获得这种关键能力呢&#xff1f; 在这节课里&#xff0c;我们将给大家梳理一下常见的短线买入卖出时机&#xff0c;并通过案例讲解帮助大家理解。话不多说&#xff0c;赶紧进入主…

rdf-file:SM2加解密

一&#xff1a;SM2简介 SM2是中国密码学算法标准中的一种非对称加密算法&#xff08;包括公钥和私钥&#xff09;。SM2主要用于数字签名、密钥交换和加密解密等密码学。 生成秘钥&#xff1a;用于生成一对公钥和私钥。公钥&#xff1a;用于加密数据和验证数字签名。私钥&…

javaSE学习-1-数据类型与运算符

目录 字面常量 数据类型 int Long short Byte float double char boolean 类型转换 强转 自动类型转换(隐式) 字符串类型 字符串和整形数字之间进行转换 字面常量 比如 System.Out.println("Hello World") &#xff1b; 语句&#xff0c;不论程序何时…

代码随想录第二十二天(一刷C语言)|组合总数电话号码的字母组合

创作目的&#xff1a;为了方便自己后续复习重点&#xff0c;以及养成写博客的习惯。 一、组合总数 思路&#xff1a;参考carl文档和视频 1、需要一维数组path来存放符合条件的结果&#xff0c;二维数组result来存放结果集。 2、targetSum 目标和&#xff0c;也就是题目中的…

【Python】Python给工作减负-读Excel文件生成xml文件

目录 ​前言 正文 1.Python基础学习 2.Python读取Excel表格 2.1安装xlrd模块 2.2使用介绍 2.2.1常用单元格中的数据类型 2.2.2 导入模块 2.2.3打开Excel文件读取数据 2.2.4常用函数 2.2.5代码测试 2.2.6 Python操作Excel官方网址 3.Python创建xml文件 3.1 xml语法…

自定义类型:结构体(自引用、内存对齐、位段(位域))

目录 一. 结构体类型的声明和定义 1.1结构体相关概念 1.11结构的声明 1.12成员列表 1.2定义结构体类型变量的方法 1.21先声明结构体类型再定义变量名 ​​​​1.22在声明类型的同时定义变量 1.23直接定义结构类型变量 二、结构体变量的创建、初始化​和访问 2.1结构体…

[二分查找]LeetCode2009 :使数组连续的最少操作数

本文涉及的基础知识点 二分查找算法合集 作者推荐 动态规划LeetCode2552&#xff1a;优化了6版的1324模式 题目 给你一个整数数组 nums 。每一次操作中&#xff0c;你可以将 nums 中 任意 一个元素替换成 任意 整数。 如果 nums 满足以下条件&#xff0c;那么它是 连续的 …

Java Web——动态Web开发核心-Servlet

1. 官方文档 官方文档地址&#xff1a;Overview (Servlet 4.0 API Documentation - Apache Tomcat 9.0.83) servlet 与 Tomcat 的关系&#xff1a;Tomcat 支持 Servlet Tomcat 是一个开源的 Java 服务器&#xff0c;它主要用来提供 Web 服务&#xff0c;包括 HTTP 请求和响应…

EasyExcel写入多个sheet

直接上代码&#xff1a; public static void main(String[] args) {// 设置excel工作簿ExcelWriter excelWriter EasyExcel.write("F:\\excel\\a.xls").build();List<User> userList new ArrayList<>();userList.add(new User("lisi", "…

初始数据结构(加深对旋转的理解)

力扣&#xff08;LeetCode&#xff09;官网 - 全球极客挚爱的技术成长平台备战技术面试&#xff1f;力扣提供海量技术面试资源&#xff0c;帮助你高效提升编程技能&#xff0c;轻松拿下世界 IT 名企 Dream Offer。https://leetcode.cn/problems/rotate-array/submissions/ 与字…

11.1每日一题(关于函数定义域)

f(x1)&#xff1a;自变量为x&#xff0c;x1为中间变量&#xff0c;所以f(x1)的定义域的取值范围是x的取值范围 f(x)&#xff1a;自变量为x&#xff0c;f(x)的定义域等价于f(x1)中 x1整体的定义域

深度学习记录--logistic回归损失函数向量化实现

前言 再次明确向量化的目的&#xff1a;减少for循环的使用&#xff0c;以更少的代码量和更快的速度来实现程序 正向传播的向量化 对于,用向量化来实现&#xff0c;只需要就可以完成&#xff0c;其中,, ps.这里b只是一个常数&#xff0c;但是依然可以加在每个向量里(python的…

09-命令者模式-C语言实现

命令者模式是一个高内聚的模式&#xff0c; 其定义为&#xff1a; Encapsulate a request as an object,thereby letting you parameterize clients with different requests,queue or log requests,and support undoable operations.&#xff08;将一个请求封装成一个对象&…

JDK17的安装与配置

JDK17的安装与配置 下载地址安装步骤配置环境变量验证安装是否成功 下载地址 此jdk17安装的系统是win10系统 https://www.oracle.com/java/technologies/downloads/ 这里选择JDK17进行下载 下载完成之后&#xff0c;显示如下图&#xff1a; 安装步骤 自定义的安装路径&…

智能优化算法应用:基于探路者算法无线传感器网络(WSN)覆盖优化 - 附代码

智能优化算法应用&#xff1a;基于探路者算法无线传感器网络(WSN)覆盖优化 - 附代码 文章目录 智能优化算法应用&#xff1a;基于探路者算法无线传感器网络(WSN)覆盖优化 - 附代码1.无线传感网络节点模型2.覆盖数学模型及分析3.探路者算法4.实验参数设定5.算法结果6.参考文献7.…