Spring Boot 接口加解密
1. 介绍
在我们日常的Java开发中,免不了和其他系统的业务交互,或者微服务之间的接口调用
如果我们想保证数据传输的安全,对接口出参加密,入参解密。
但是不想写重复代码,我们可以提供一个通用starter,提供通用加密解密功能
2. 前置知识
2.1 hutool-crypto加密解密工具
hutool-crypto提供了很多加密解密工具,包括对称加密,非对称加密,摘要加密等等,这不做详细介绍。
2.2 request流只能读取一次的问题
2.2.1 问题:
在接口调用链中,request的请求流只能调用一次,处理之后,如果之后还需要用到请求流获取数据,就会发现数据为空。
比如使用了filter或者aop在接口处理之前,获取了request中的数据,对参数进行了校验,那么之后就不能在获取request请求流了。
2.2.2 解决办法
继承HttpServletRequestWrapper
,将请求中的流copy一份,复写getInputStream
和getReader方法供外部使用。每次调用后的getInputStream
方法都是从复制出来的二进制数组中进行获取,这个二进制数组在对象存在期间一致存在。
使用Filter过滤器,在一开始,替换request为自己定义的可以多次读取流的request。
这样就实现了流的重复获取
InputStreamHttpServletRequestWrapper
package xyz.hlh.cryptotest.utils;import org.apache.commons.io.IOUtils;import javax.servlet.ReadListener;
import javax.servlet.ServletInputStream;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletRequestWrapper;
import java.io.BufferedReader;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.InputStreamReader;/*** 请求流支持多次获取*/
public class InputStreamHttpServletRequestWrapper extends HttpServletRequestWrapper {/*** 用于缓存输入流*/private ByteArrayOutputStream cachedBytes;public InputStreamHttpServletRequestWrapper(HttpServletRequest request) {super(request);}@Overridepublic ServletInputStream getInputStream() throws IOException {if (cachedBytes == null) {// 首次获取流时,将流放入 缓存输入流 中cacheInputStream();}// 从 缓存输入流 中获取流并返回return new CachedServletInputStream(cachedBytes.toByteArray());}@Overridepublic BufferedReader getReader() throws IOException {return new BufferedReader(new InputStreamReader(getInputStream()));}/*** 首次获取流时,将流放入 缓存输入流 中*/private void cacheInputStream() throws IOException {// 缓存输入流以便多次读取。为了方便, 我使用 org.apache.commons IOUtilscachedBytes = new ByteArrayOutputStream();IOUtils.copy(super.getInputStream(), cachedBytes);}/*** 读取缓存的请求正文的输入流* <p>* 用于根据 缓存输入流 创建一个可返回的*/public static class CachedServletInputStream extends ServletInputStream {private final ByteArrayInputStream input;public CachedServletInputStream(byte[] buf) {// 从缓存的请求正文创建一个新的输入流input = new ByteArrayInputStream(buf);}@Overridepublic boolean isFinished() {return false;}@Overridepublic boolean isReady() {return false;}@Overridepublic void setReadListener(ReadListener listener) {}@Overridepublic int read() throws IOException {return input.read();}}}
HttpServletRequestInputStreamFilter
package xyz.hlh.cryptotest.filter;import org.springframework.core.annotation.Order;
import org.springframework.stereotype.Component;
import xyz.hlh.cryptotest.utils.InputStreamHttpServletRequestWrapper;import javax.servlet.Filter;
import javax.servlet.FilterChain;
import javax.servlet.ServletException;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import javax.servlet.http.HttpServletRequest;
import java.io.IOException;import static org.springframework.core.Ordered.HIGHEST_PRECEDENCE;/*** @author HLH* @description:* 请求流转换为多次读取的请求流 过滤器* @email 17703595860@163.com* @date : Created in 2022/2/4 9:58*/
@Component
@Order(HIGHEST_PRECEDENCE + 1) // 优先级最高
public class HttpServletRequestInputStreamFilter implements Filter {@Overridepublic void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {// 转换为可以多次获取流的requestHttpServletRequest httpServletRequest = (HttpServletRequest) request;InputStreamHttpServletRequestWrapper inputStreamHttpServletRequestWrapper = new InputStreamHttpServletRequestWrapper(httpServletRequest);// 放行chain.doFilter(inputStreamHttpServletRequestWrapper, response);}
}
2.3 SpringBoot的参数校验validation
为了减少接口中,业务代码之前的大量冗余的参数校验代码
SpringBoot-validation
提供了优雅的参数校验,入参都是实体类,在实体类字段上加上对应注解,就可以在进入方法之前,进行参数校验,如果参数错误,会抛出错误BindException
,是不会进入方法的。
这种方法,必须要求在接口参数上加注解@Validated
或者是@Valid
但是很多清空下,我们希望在代码中调用某个实体类的校验功能,所以需要如下工具类。
ParamException
package xyz.hlh.cryptotest.exception;import lombok.Getter;import java.util.List;/*** @author HLH* @description 自定义参数异常* @email 17703595860@163.com* @date Created in 2021/8/10 下午10:56*/
@Getter
public class ParamException extends Exception {private final List<String> fieldList;private final List<String> msgList;public ParamException(List<String> fieldList, List<String> msgList) {this.fieldList = fieldList;this.msgList = msgList;}
}
ValidationUtils
package xyz.hlh.cryptotest.utils;import xyz.hlh.cryptotest.exception.CustomizeException;
import xyz.hlh.cryptotest.exception.ParamException;import javax.validation.ConstraintViolation;
import javax.validation.Validation;
import javax.validation.Validator;
import java.util.LinkedList;
import java.util.List;
import java.util.Set;/*** @author HLH* @description 验证工具类* @email 17703595860@163.com* @date Created in 2021/8/10 下午10:56*/
public class ValidationUtils {private static final Validator VALIDATOR = Validation.buildDefaultValidatorFactory().getValidator();/*** 验证数据* @param object 数据*/public static void validate(Object object) throws CustomizeException {Set<ConstraintViolation<Object>> validate = VALIDATOR.validate(object);// 验证结果异常throwParamException(validate);}/*** 验证数据(分组)* @param object 数据* @param groups 所在组*/public static void validate(Object object, Class<?> ... groups) throws CustomizeException {Set<ConstraintViolation<Object>> validate = VALIDATOR.validate(object, groups);// 验证结果异常throwParamException(validate);}/*** 验证数据中的某个字段(分组)* @param object 数据* @param propertyName 字段名称*/public static void validate(Object object, String propertyName) throws CustomizeException {Set<ConstraintViolation<Object>> validate = VALIDATOR.validateProperty(object, propertyName);// 验证结果异常throwParamException(validate);}/*** 验证数据中的某个字段(分组)* @param object 数据* @param propertyName 字段名称* @param groups 所在组*/public static void validate(Object object, String propertyName, Class<?> ... groups) throws CustomizeException {Set<ConstraintViolation<Object>> validate = VALIDATOR.validateProperty(object, propertyName, groups);// 验证结果异常throwParamException(validate);}/*** 验证结果异常* @param validate 验证结果*/private static void throwParamException(Set<ConstraintViolation<Object>> validate) throws CustomizeException {if (validate.size() > 0) {List<String> fieldList = new LinkedList<>();List<String> msgList = new LinkedList<>();for (ConstraintViolation<Object> next : validate) {fieldList.add(next.getPropertyPath().toString());msgList.add(next.getMessage());}throw new ParamException(fieldList, msgList);}}}
2.5 自定义starter
自定义starter步骤
-
创建工厂,编写功能代码
-
声明自动配置类,把需要对外提供的对象创建好,通过配置类统一向外暴露
-
在resource目录下准备一个名为
spring/spring.factories
的文件,以org.springframework.boot.autoconfigure.EnableAutoConfiguration
为key,自动配置类为value列表,进行注册
2.6 RequestBodyAdvice和ResponseBodyAdvice
-
RequestBodyAdvice
是对请求的json串进行处理, 一般使用环境是处理接口参数的自动解密 -
ResponseBodyAdvice
是对请求相应的jsoin传进行处理,一般用于相应结果的加密
3. 功能介绍
接口相应数据的时候,返回的是加密之后的数据 接口入参的时候,接收的是解密之后的数据,但是在进入接口之前,会自动解密,取得对应的数据
4. 功能细节
加密解密使用对称加密的AES算法,使用hutool-crypto模块进行实现
所有的实体类提取一个公共父类,包含属性时间戳,用于加密数据返回之后的实效性,如果超过60分钟,那么其他接口将不进行处理。
如果接口加了加密注解EncryptionAnnotation
,并且返回统一的json数据Result类,则自动对数据进行加密。如果是继承了统一父类RequestBase
的数据,自动注入时间戳,确保数据的时效性
如果接口加了解密注解DecryptionAnnotation
,并且参数使用RequestBody注解标注,传入json使用统一格式RequestData类,并且内容是继承了包含时间长的父类RequestBase
,则自动解密,并且转为对应的数据类型
功能提供Springboot的starter,实现开箱即用
5. 代码实现
5.1 项目结构
5.2 crypto-common
5.2.1 结构
5.3 crypto-spring-boot-starter
5.3.1 接口
5.3.2 重要代码
crypto.properties AES需要的参数配置
# 模式 cn.hutool.crypto.Mode
crypto.mode=CTS
# 补码方式 cn.hutool.crypto.Mode
crypto.padding=PKCS5Padding
# 秘钥
crypto.key=testkey123456789
# 盐
crypto.iv=testiv1234567890
spring.factories 自动配置文件
org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
xyz.hlh.crypto.config.AppConfig
CryptConfig AES需要的配置参数
package xyz.hlh.crypto.config;import cn.hutool.crypto.Mode;
import cn.hutool.crypto.Padding;
import lombok.Data;
import lombok.EqualsAndHashCode;
import lombok.Getter;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.PropertySource;import java.io.Serializable;/*** @author HLH* @description: AES需要的配置参数* @email 17703595860@163.com* @date : Created in 2022/2/4 13:16*/
@Configuration
@ConfigurationProperties(prefix = "crypto")
@PropertySource("classpath:crypto.properties")
@Data
@EqualsAndHashCode
@Getter
public class CryptConfig implements Serializable {private Mode mode;private Padding padding;private String key;private String iv;}
AppConfig 自动配置类
package xyz.hlh.crypto.config;import cn.hutool.crypto.symmetric.AES;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;import javax.annotation.Resource;
import java.nio.charset.StandardCharsets;/*** @author HLH* @description: 自动配置类* @email 17703595860@163.com* @date : Created in 2022/2/4 13:12*/
@Configuration
public class AppConfig {@Resourceprivate CryptConfig cryptConfig;@Beanpublic AES aes() {return new AES(cryptConfig.getMode(), cryptConfig.getPadding(), cryptConfig.getKey().getBytes(StandardCharsets.UTF_8), cryptConfig.getIv().getBytes(StandardCharsets.UTF_8));}}
DecryptRequestBodyAdvice
请求自动解密
package xyz.hlh.crypto.advice;import com.fasterxml.jackson.databind.ObjectMapper;
import lombok.SneakyThrows;
import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.core.MethodParameter;
import org.springframework.http.HttpInputMessage;
import org.springframework.http.converter.HttpMessageConverter;
import org.springframework.web.bind.annotation.ControllerAdvice;
import org.springframework.web.context.request.RequestAttributes;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;
import org.springframework.web.servlet.mvc.method.annotation.RequestBodyAdvice;
import xyz.hlh.crypto.annotation.DecryptionAnnotation;
import xyz.hlh.crypto.common.exception.ParamException;
import xyz.hlh.crypto.constant.CryptoConstant;
import xyz.hlh.crypto.entity.RequestBase;
import xyz.hlh.crypto.entity.RequestData;
import xyz.hlh.crypto.util.AESUtil;import javax.servlet.ServletInputStream;
import javax.servlet.http.HttpServletRequest;
import java.io.IOException;
import java.lang.reflect.Type;/*** @author HLH* @description: requestBody 自动解密* @email 17703595860@163.com* @date : Created in 2022/2/4 15:12*/
@ControllerAdvice
public class DecryptRequestBodyAdvice implements RequestBodyAdvice {@Autowiredprivate ObjectMapper objectMapper;/*** 方法上有DecryptionAnnotation注解的,进入此拦截器* @param methodParameter 方法参数对象* @param targetType 参数的类型* @param converterType 消息转换器* @return true,进入,false,跳过*/@Overridepublic boolean supports(MethodParameter methodParameter, Type targetType, Class<? extends HttpMessageConverter<?>> converterType) {return methodParameter.hasMethodAnnotation(DecryptionAnnotation.class);}@Overridepublic HttpInputMessage beforeBodyRead(HttpInputMessage inputMessage, MethodParameter parameter, Type targetType, Class<? extends HttpMessageConverter<?>> converterType) throws IOException {return inputMessage;}/*** 转换之后,执行此方法,解密,赋值* @param body spring解析完的参数* @param inputMessage 输入参数* @param parameter 参数对象* @param targetType 参数类型* @param converterType 消息转换类型* @return 真实的参数*/@SneakyThrows@Overridepublic Object afterBodyRead(Object body, HttpInputMessage inputMessage, MethodParameter parameter, Type targetType, Class<? extends HttpMessageConverter<?>> converterType) {// 获取requestRequestAttributes requestAttributes = RequestContextHolder.getRequestAttributes();ServletRequestAttributes servletRequestAttributes = (ServletRequestAttributes) requestAttributes;if (servletRequestAttributes == null) {throw new ParamException("request错误");}HttpServletRequest request = servletRequestAttributes.getRequest();// 获取数据ServletInputStream inputStream = request.getInputStream();RequestData requestData = objectMapper.readValue(inputStream, RequestData.class);if (requestData == null || StringUtils.isBlank(requestData.getText())) {throw new ParamException("参数错误");}// 获取加密的数据String text = requestData.getText();// 放入解密之前的数据request.setAttribute(CryptoConstant.INPUT_ORIGINAL_DATA, text);// 解密String decryptText = null;try {decryptText = AESUtil.decrypt(text);} catch (Exception e) {throw new ParamException("解密失败");}if (StringUtils.isBlank(decryptText)) {throw new ParamException("解密失败");}// 放入解密之后的数据request.setAttribute(CryptoConstant.INPUT_DECRYPT_DATA, decryptText);// 获取结果Object result = objectMapper.readValue(decryptText, body.getClass());// 强制所有实体类必须继承RequestBase类,设置时间戳if (result instanceof RequestBase) {// 获取时间戳Long currentTimeMillis = ((RequestBase) result).getCurrentTimeMillis();// 有效期 60秒long effective = 60*1000;// 时间差long expire = System.currentTimeMillis() - currentTimeMillis;// 是否在有效期内if (Math.abs(expire) > effective) {throw new ParamException("时间戳不合法");}// 返回解密之后的数据return result;} else {throw new ParamException(String.format("请求参数类型:%s 未继承:%s", result.getClass().getName(), RequestBase.class.getName()));}}/*** 如果body为空,转为空对象* @param body spring解析完的参数* @param inputMessage 输入参数* @param parameter 参数对象* @param targetType 参数类型* @param converterType 消息转换类型* @return 真实的参数*/@SneakyThrows@Overridepublic Object handleEmptyBody(Object body, HttpInputMessage inputMessage, MethodParameter parameter, Type targetType, Class<? extends HttpMessageConverter<?>> converterType) {String typeName = targetType.getTypeName();Class<?> bodyClass = Class.forName(typeName);return bodyClass.newInstance();}
}
EncryptResponseBodyAdvice
相应自动加密
package xyz.hlh.crypto.advice;import cn.hutool.json.JSONUtil;
import com.fasterxml.jackson.databind.ObjectMapper;
import lombok.SneakyThrows;
import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.core.MethodParameter;
import org.springframework.http.MediaType;
import org.springframework.http.ResponseEntity;
import org.springframework.http.converter.HttpMessageConverter;
import org.springframework.http.server.ServerHttpRequest;
import org.springframework.http.server.ServerHttpResponse;
import org.springframework.web.bind.annotation.ControllerAdvice;
import org.springframework.web.servlet.mvc.method.annotation.ResponseBodyAdvice;
import sun.reflect.generics.reflectiveObjects.ParameterizedTypeImpl;
import xyz.hlh.crypto.annotation.EncryptionAnnotation;
import xyz.hlh.crypto.common.entity.Result;
import xyz.hlh.crypto.common.exception.CryptoException;
import xyz.hlh.crypto.entity.RequestBase;
import xyz.hlh.crypto.util.AESUtil;import java.lang.reflect.Type;/*** @author HLH* @description:* @email 17703595860@163.com* @date : Created in 2022/2/4 15:12*/
@ControllerAdvice
public class EncryptResponseBodyAdvice implements ResponseBodyAdvice<Result<?>> {@Autowiredprivate ObjectMapper objectMapper;@Overridepublic boolean supports(MethodParameter returnType, Class<? extends HttpMessageConverter<?>> converterType) {ParameterizedTypeImpl genericParameterType = (ParameterizedTypeImpl)returnType.getGenericParameterType();// 如果直接是Result,则返回if (genericParameterType.getRawType() == Result.class && returnType.hasMethodAnnotation(EncryptionAnnotation.class)) {return true;}if (genericParameterType.getRawType() != ResponseEntity.class) {return false;}// 如果是ResponseEntity<Result>for (Type type : genericParameterType.getActualTypeArguments()) {if (((ParameterizedTypeImpl) type).getRawType() == Result.class && returnType.hasMethodAnnotation(EncryptionAnnotation.class)) {return true;}}return false;}@SneakyThrows@Overridepublic Result<?> beforeBodyWrite(Result<?> body, MethodParameter returnType, MediaType selectedContentType, Class<? extends HttpMessageConverter<?>> selectedConverterType, ServerHttpRequest request, ServerHttpResponse response) {// 加密Object data = body.getData();// 如果data为空,直接返回if (data == null) {return body;}// 如果是实体,并且继承了Request,则放入时间戳if (data instanceof RequestBase) {((RequestBase)data).setCurrentTimeMillis(System.currentTimeMillis());}String dataText = JSONUtil.toJsonStr(data);// 如果data为空,直接返回if (StringUtils.isBlank(dataText)) {return body;}// 如果位数小于16,报错if (dataText.length() < 16) {throw new CryptoException("加密失败,数据小于16位");}String encryptText = AESUtil.encryptHex(dataText);return Result.builder().status(body.getStatus()).data(encryptText).message(body.getMessage()).build();}
}
5.4 crypto-test
5.4.1 结构
5.4.2 重要代码
application.yml 配置文件
spring:mvc:format:date-time: yyyy-MM-dd HH:mm:ssdate: yyyy-MM-dd# 日期格式化jackson:date-format: yyyy-MM-dd HH:mm:ss
Teacher 实体类
package xyz.hlh.crypto.entity;import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.EqualsAndHashCode;
import lombok.NoArgsConstructor;
import org.hibernate.validator.constraints.Range;import javax.validation.constraints.NotBlank;
import javax.validation.constraints.NotNull;
import java.io.Serializable;
import java.util.Date;/*** @author HLH* @description: Teacher实体类,使用SpringBoot的validation校验* @email 17703595860@163.com* @date : Created in 2022/2/4 10:21*/
@Data
@NoArgsConstructor
@AllArgsConstructor
@EqualsAndHashCode(callSuper = true)
public class Teacher extends RequestBase implements Serializable {@NotBlank(message = "姓名不能为空")private String name;@NotNull(message = "年龄不能为空")@Range(min = 0, max = 150, message = "年龄不合法")private Integer age;@NotNull(message = "生日不能为空")private Date birthday;}
TestController 测试Controller
package xyz.hlh.crypto.controller;import org.springframework.http.ResponseEntity;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RestController;
import xyz.hlh.crypto.annotation.DecryptionAnnotation;
import xyz.hlh.crypto.annotation.EncryptionAnnotation;
import xyz.hlh.crypto.common.entity.Result;
import xyz.hlh.crypto.common.entity.ResultBuilder;
import xyz.hlh.crypto.entity.Teacher;/*** @author HLH* @description: 测试Controller* @email 17703595860@163.com* @date : Created in 2022/2/4 9:16*/
@RestController
public class TestController implements ResultBuilder {/*** 直接返回对象,不加密* @param teacher Teacher对象* @return 不加密的对象*/@PostMapping("/get")public ResponseEntity<Result<?>> get(@Validated @RequestBody Teacher teacher) {return success(teacher);}/*** 返回加密后的数据* @param teacher Teacher对象* @return 返回加密后的数据 ResponseBody<Result>格式*/@PostMapping("/encrypt")@EncryptionAnnotationpublic ResponseEntity<Result<?>> encrypt(@Validated @RequestBody Teacher teacher) {return success(teacher);}/*** 返回加密后的数据* @param teacher Teacher对象* @return 返回加密后的数据 Result格式*/@PostMapping("/encrypt1")@EncryptionAnnotationpublic Result<?> encrypt1(@Validated @RequestBody Teacher teacher) {return success(teacher).getBody();}/*** 返回解密后的数据* @param teacher Teacher对象* @return 返回解密后的数据*/@PostMapping("/decrypt")@DecryptionAnnotationpublic ResponseEntity<Result<?>> decrypt(@Validated @RequestBody Teacher teacher) {return success(teacher);}}
相关文章:

Spring Boot 接口加解密
1. 介绍 在我们日常的Java开发中,免不了和其他系统的业务交互,或者微服务之间的接口调用 如果我们想保证数据传输的安全,对接口出参加密,入参解密。 但是不想写重复代码,我们可以提供一个通用starter,提…...

大公司为什么禁止SpringBoot项目使用Tomcat?
前言 在SpringBoot框架中,我们使用最多的是Tomcat,这是SpringBoot默认的容器技术,而且是内嵌式的Tomcat。同时,SpringBoot也支持Undertow容器,我们可以很方便的用Undertow替换Tomcat,而Undertow的性能和内…...

2023年第十三届MathorCup高校数学建模挑战赛|A题|量子计算机在信用评分卡组合优化中的应用
目录 题目详情 最终收入 贷款利息收入 - 坏账损失 赛题说明 1:流程简化及示例 赛题说明 2:QUBO 模型简介 赛题说明 3:赛题数据 问题 题目详情 在银行信用卡或相关的贷款等业务中,对客户授信之前,需…...

linux下搭建Hbase分布式数据库
文章目录 Hbase概念1.安装Hbase1.jdk的配置2.安装hbase 2.启动和操作1.启动服务2 **web-ui访问地址:http://node01:16010/master-status** 3.简单的操作1.连接 HBase2.帮助命令3.创建一张表 create a table4.使用查看表是否存在5.describe 查看表描述6.put命令插入数据到表7. s…...

unity,射手游戏
文章目录 介绍一,制作玩家具体函数脚本PlayerCharacter三、 制作玩家控制脚本 PlayerController,调用上面的函数方法四、 制作子弹脚本 shell五、 给玩家挂载脚本六、 制作坦克脚本七、 给坦克添加组件八、 开始游戏,播放动画九、 下载 介绍 …...

摒弃单一变现手段,开拓多元商业模式,破解场景单一APP盈利难题!
工具类APP已成为人们生活、工作中不可或缺的一部分,包括天气服务、搜索、日历等细分领域,在用户中存在巨大的市场需求。但是,这类APP也面临着一些难以避免的问题。 比如功能单一、用户停留时间较短、可替代性强等,这些问题会影响…...

JavaEE-轻松了解网络原理之TCP协议
目录 TCP协议TCP协议数据格式TCP原理确认应答超时重传连接管理三次握手四次挥手 滑动窗口流量控制拥塞控制延迟应答捎带应答面向字节流异常问题 TCP协议 TCP,即Transmission Control Protocol,传输控制协议. TCP协议数据格式 16位源端口号与16位目的端…...

薪资17K是一个怎样的水平?来看看98年测试工程师的面试全过程…
我的情况 大概介绍一下个人情况,男,本科,三年多测试工作经验,懂python,会写脚本,会selenium,会性能,然而到今天都没有收到一份offer!从年后就开始准备简历,年…...

OpenCV3 和 Qt5 计算机视觉:11~12
原文:Computer Vision with OpenCV 3 and Qt5 协议:CC BY-NC-SA 4.0 译者:飞龙 本文来自【ApacheCN 计算机视觉 译文集】,采用译后编辑(MTPE)流程来尽可能提升效率。 当别人说你没有底线的时候,…...

R包编写流程
文章目录 所需工具Step 1: 创建R项目Step 2: 在R文件夹中添加函数Step 3: 编辑元数据Step 4: 文档化Step 5: 检查包Step 6: 打包重要参考: 所需工具 R包的编写需要的工具包有:devtools,Rtools Step 1: 创建R项目 devtools::create_package…...

试验GPT写文章书
试验GPT写文章书 写一本名叫《寻找人生目标的十种方法》 回答 2023/4/22 16:12:31 很高兴为您提供以下内容,这是一本关于寻找人生目标的十种方法的建议和思考。 《寻找人生目标的十种方法》 第一章:明确自己的价值观 了解自己内心真正想要追求的东…...

class与typename的异同
一、class与typename的相同点 typename关键字常用于函数模板,这里首先引入函数模板的概念:函数模板代表了一个函数家族,该函数模板与类型无关,在使用时被参数化,根据实参类型产生函数的特定 类型版本 //函数模板格式…...

OpenCV 图像处理学习手册:6~7
原文:Learning Image Processing with OpenCV 协议:CC BY-NC-SA 4.0 译者:飞龙 本文来自【ApacheCN 计算机视觉 译文集】,采用译后编辑(MTPE)流程来尽可能提升效率。 当别人说你没有底线的时候,…...

Java中的序列化与反序列化(一)
1、概述 大家好,我是欧阳方超。今天来看一下Java序列化与反序列化的问题。 2、序列化与反序列化 2.1、序列化与反序列化的概念 在Java中,序列化是将对象转换为可存储或传输的格式(一般为字节流)的过程,序列化后的字…...

3.函数、结构体、包
一、函数定义和调用 package mainimport ("fmt" )func test() {fmt.Println("hello world") } func main() {test() }二、函数的参数 1.单个参数 func test(n int) {fmt.Println("传递进来的参数是", n) } func main() {test(10) }2.多个参数…...

科普丨关于 A/B 测试的十问十答
你想知道的,都在这里!本文是神策数据「十问十答」科普系列文章的第二期,围绕 A/B 测试展开。 1 Q:什么是 A/B 测试? A:A/B 测试作为互联网企业的核心增长手段之一,其价值已在实际应用中被多次验…...

尚融宝——整合OpenFeign与Sentinel实现兜底方法——验证手机号码是否注册功能
一、整合过程 在项目添加依赖:添加位置 <!--服务调用--><dependency><groupId>org.springframework.cloud</groupId><artifactId>spring-cloud-starter-openfeign</artifactId></dependency> 在需要的服务中添加启动注…...

几种常见的激活函数
文章目录 常见的激活函数介绍Sigmoid函数ReLU函数LeakyReLU函数Tanh函数Softmax函数总结 常见的激活函数介绍 激活函数是神经网络中的重要组成部分,它决定了神经元的输出。在神经网络的前向传播中,输入数据被传递给神经元,经过加权和和激活函…...

MySQL-数据库,数据表的基本操作
数据库,数据表的基本操作 1 数据库的基本操作1.1 创建数据库1.2 删除数据库 2 数据表的基本操作2.1 创建数据表2.1.1 主键约束2.1.2 外键约束2.1.3 非空约束2.1.4 唯一性约束2.1.5 默认约束2.1.6 自增列 2.2 查看数据表结构2.2.1 查看表基本结构语句DESC2.2.2 查看表…...

IC-14W网络IC卡读写器_银河麒麟桌面操作系统V10适配测试报告
银河麒麟操作系统产品NeoCertify 认证测试报告 系统版本:银河麒麟桌面操作系统V10 厂商名称: 广州荣士电子有限公司 认证产品:IC-14W网络IC卡读写器 测试日期: 2022-11-04 …...

面试常见问题
1.自我介绍 面试官您好,我叫**,在招聘网站上看到了公司的介绍和岗位要求,觉得非常有吸引力,结合自己的过往经历,也感觉自己能够胜任,所以很高兴能有这次面试机会。 参与工作已经5年了,18年毕业于…...

matlab数据归一化与反归一化处理
假如数据实际取值范围为 X i ∈ [ − π π ] , i 1 , 2 , 3 X_i \in [-\pi \ \ \pi], i1,2,3 Xi∈[−π π],i1,2,3,变量服从正态分布 示例如下: %% 数据归一化处理及其概率密度函数 clear clc Mu [0 0 0]; % 均值 Sigma [1 1 1]; % 标准差 C…...

【杂凑算法篇】密码杂凑算法的安全强度
【杂凑算法篇】密码杂凑算法的安全强度 杂凑(哈希)算法安全强度—【蘇小沐】 文章目录 【杂凑算法篇】密码杂凑算法的安全强度(一)安全强度(Security Strength)(二)杂凑算法的安全强度与对比总…...

【RobotFramework自动化测试】
robotframework介绍 RF是一个基于Python语言开发的,可扩展的,是以关键字驱动模式的自动化测试框架。RF最新的版本是2019 年7月份发布,7月份之前只支持python2.7,7月之后支持3.X的版本 robotframework的安装 安装python环境&…...

操作系统原理 —— 什么是中断?(四)
我们先来看看早期的计算机的工作流程: 如上图,在早期的计算机假设有三个程序需要执行,执行顺序是:先执行程序1,等待程序1结束之后,再开始执行程序2,以此类推,所以它们是串行执行的…...

SA168 3BSE003389R1
SA168 3BSE003389R1 远程终端控制系统(RTU)可连接到其他设备。RTU可将设备上的电气信号转换为数字的值,例如一个开关或阀开/关的状态,或是仪器量测到的压力、流量、电压或电流。也可以借由信号转换及传送信号来控制设备࿰…...

基于Java+Springboot+Vue+elememt美食论坛平台设计实现
基于JavaSpringbootVueelememt美食论坛平台设计实现 博主介绍:5年java开发经验,专注Java开发、定制、远程、指导等,csdn特邀作者、专注于Java技术领域 作者主页 超级帅帅吴 Java项目精品实战案例《500套》 欢迎点赞 收藏 ⭐留言 文末获取源码联系方式 文…...

Go Etcd
基本操作 go get go.etcd.io/etcd/client/v3# 此处使用的 版本是: # go.etcd.io/etcd/client/v3 v3.5.8这里使用的是 "go.etcd.io/etcd/client/v3" 而不是 "go.etcd.io/etcd/clientv3" 我们不使用 etcd/clientv3,因为它与grpc 最新…...

01、Cadence使用记录之新建工程与基础操作(原理图绘制:OrCAD Capture CIS)
01、Cadence使用记录之新建工程与基础操作(原理图绘制:OrCAD Capture CIS) 硕士学电磁场去了,写点博客记录下学习过程。 参考的教程是B站的视频:allegro软件入门视频教程全集100讲 本科的时候就对Cadence有所耳闻&am…...

Redis数据结构与对象-链表和字典
1、链表 其实个人感觉redis的链表内容和其他的差不多。就是一个listNode结构,里面又指向前置节点和后置节点的指针。 然后redis链表可以保存各种不同类型的值。 链表被广泛用于实现redis的各种功能,比如列表键、发布与订阅、慢查询、监视器等。 2、字典…...