MyBatis Plus 数据库字段加密处理
目录
- 1.场景介绍
- 2.Maven依赖
- 2.AESUtil.java 加解密工具类
- 3.字段处理类
- 4.修改 MyBatis Plus 查询
- 4.1 修改表对应实体类
- 4.2 修改加密字段对应属性
- 4.3 修改 xml 使用 ResultMap
- 4.4 修改 xml 中 el 表达式
- 5.测试结果
- 6.MyBatis Plus 缺陷
- 补充:测试实例
- 1 查询测试
- 1.1 查询信息,SQL实现
- 1.2 查询信息,QueryWrapper实现
- 1.3 查询信息,根据加密字段查询,SQL实现
- 1.4 查询信息,根据加密字段查询,QueryWrapper实现
- 2.测试更新
- 2.1 更新信息,SQL实现
- 2.2 更新信息,UpdateWrapper实现
- 2.3 更新信息,LambdaUpdateWrapper实现
- 2.4 更新信息,updateById实现
- 3.测试插入
- 7.3.1 插入信息,SQL实现
- 3.2 插入信息,Service实现
1.场景介绍
- 当项目开发到一半,可能突然客户会要求对数据库里面比如手机号、身份证号的字段进行加密;
- 在保证开发最快、影响范围最小的情况下,我们需要选择一种介于数据库和代码之间的工具来帮我们实现自动加解密;
2.Maven依赖
<!-- mybatis-plus -->
<dependency><groupId>com.baomidou</groupId><artifactId>mybatis-plus-boot-starter</artifactId><version>3.5.3</version>
</dependency><!-- mybatis的分页插件 -->
<dependency><groupId>com.github.pagehelper</groupId><artifactId>pagehelper</artifactId><version>5.1.11</version><!-- pagehelper 包含该依赖存在版本冲突,因此不建议和 mp 一起混用 --><exclusions><exclusion><groupId>com.github.jsqlparser</groupId><artifactId>jsqlparser</artifactId></exclusion></exclusions>
</dependency>
2.AESUtil.java 加解密工具类
这里我们选用AES对称加密算法,因为它是可逆算法。
AES加密介绍: https://blog.csdn.net/qq_33204709/article/details/126930720
具体实现代码如下:
import org.apache.commons.lang3.RandomStringUtils;
import org.apache.commons.lang3.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.util.Base64Utils;import javax.crypto.Cipher;
import javax.crypto.spec.IvParameterSpec;
import javax.crypto.spec.SecretKeySpec;/*** AES加密工具类** @author ACGkaka* @since 2021-06-18 19:11:03*/
public class AESUtil {/*** 日志相关*/private static final Logger LOGGER = LoggerFactory.getLogger(AESUtil.class);/*** 编码*/private static final String ENCODING = "UTF-8";/*** 算法定义*/private static final String AES_ALGORITHM = "AES";/*** 指定填充方式*/private static final String CIPHER_PADDING = "AES/ECB/PKCS5Padding";private static final String CIPHER_CBC_PADDING = "AES/CBC/PKCS5Padding";/*** 偏移量(CBC中使用,增强加密算法强度)*/private static final String IV_SEED = "1234567812345678";/*** AES加密* @param content 待加密内容* @param aesKey 密码* @return*/public static String encrypt(String content, String aesKey){if(StringUtils.isBlank(content)){LOGGER.info("AES encrypt: the content is null!");return null;}//判断秘钥是否为16位if(StringUtils.isNotBlank(aesKey) && aesKey.length() == 16){try {//对密码进行编码byte[] bytes = aesKey.getBytes(ENCODING);//设置加密算法,生成秘钥SecretKeySpec skeySpec = new SecretKeySpec(bytes, AES_ALGORITHM);// "算法/模式/补码方式"Cipher cipher = Cipher.getInstance(CIPHER_PADDING);//选择加密cipher.init(Cipher.ENCRYPT_MODE, skeySpec);//根据待加密内容生成字节数组byte[] encrypted = cipher.doFinal(content.getBytes(ENCODING));//返回base64字符串return Base64Utils.encodeToString(encrypted);} catch (Exception e) {LOGGER.info("AES encrypt exception:" + e.getMessage());throw new RuntimeException(e);}}else {LOGGER.info("AES encrypt: the aesKey is null or error!");return null;}}/*** 解密* * @param content 待解密内容* @param aesKey 密码* @return*/public static String decrypt(String content, String aesKey){if(StringUtils.isBlank(content)){LOGGER.info("AES decrypt: the content is null!");return null;}//判断秘钥是否为16位if(StringUtils.isNotBlank(aesKey) && aesKey.length() == 16){try {//对密码进行编码byte[] bytes = aesKey.getBytes(ENCODING);//设置解密算法,生成秘钥SecretKeySpec skeySpec = new SecretKeySpec(bytes, AES_ALGORITHM);// "算法/模式/补码方式"Cipher cipher = Cipher.getInstance(CIPHER_PADDING);//选择解密cipher.init(Cipher.DECRYPT_MODE, skeySpec);//先进行Base64解码byte[] decodeBase64 = Base64Utils.decodeFromString(content);//根据待解密内容进行解密byte[] decrypted = cipher.doFinal(decodeBase64);//将字节数组转成字符串return new String(decrypted, ENCODING);} catch (Exception e) {LOGGER.info("AES decrypt exception:" + e.getMessage());throw new RuntimeException(e);}}else {LOGGER.info("AES decrypt: the aesKey is null or error!");return null;}}/*** AES_CBC加密* * @param content 待加密内容* @param aesKey 密码* @return*/public static String encryptCBC(String content, String aesKey){if(StringUtils.isBlank(content)){LOGGER.info("AES_CBC encrypt: the content is null!");return null;}//判断秘钥是否为16位if(StringUtils.isNotBlank(aesKey) && aesKey.length() == 16){try {//对密码进行编码byte[] bytes = aesKey.getBytes(ENCODING);//设置加密算法,生成秘钥SecretKeySpec skeySpec = new SecretKeySpec(bytes, AES_ALGORITHM);// "算法/模式/补码方式"Cipher cipher = Cipher.getInstance(CIPHER_CBC_PADDING);//偏移IvParameterSpec iv = new IvParameterSpec(IV_SEED.getBytes(ENCODING));//选择加密cipher.init(Cipher.ENCRYPT_MODE, skeySpec, iv);//根据待加密内容生成字节数组byte[] encrypted = cipher.doFinal(content.getBytes(ENCODING));//返回base64字符串return Base64Utils.encodeToString(encrypted);} catch (Exception e) {LOGGER.info("AES_CBC encrypt exception:" + e.getMessage());throw new RuntimeException(e);}}else {LOGGER.info("AES_CBC encrypt: the aesKey is null or error!");return null;}}/*** AES_CBC解密* * @param content 待解密内容* @param aesKey 密码* @return*/public static String decryptCBC(String content, String aesKey){if(StringUtils.isBlank(content)){LOGGER.info("AES_CBC decrypt: the content is null!");return null;}//判断秘钥是否为16位if(StringUtils.isNotBlank(aesKey) && aesKey.length() == 16){try {//对密码进行编码byte[] bytes = aesKey.getBytes(ENCODING);//设置解密算法,生成秘钥SecretKeySpec skeySpec = new SecretKeySpec(bytes, AES_ALGORITHM);//偏移IvParameterSpec iv = new IvParameterSpec(IV_SEED.getBytes(ENCODING));// "算法/模式/补码方式"Cipher cipher = Cipher.getInstance(CIPHER_CBC_PADDING);//选择解密cipher.init(Cipher.DECRYPT_MODE, skeySpec, iv);//先进行Base64解码byte[] decodeBase64 = Base64Utils.decodeFromString(content);//根据待解密内容进行解密byte[] decrypted = cipher.doFinal(decodeBase64);//将字节数组转成字符串return new String(decrypted, ENCODING);} catch (Exception e) {LOGGER.info("AES_CBC decrypt exception:" + e.getMessage());throw new RuntimeException(e);}}else {LOGGER.info("AES_CBC decrypt: the aesKey is null or error!");return null;}}public static void main(String[] args) {// AES支持三种长度的密钥:128位、192位、256位。// 代码中这种就是128位的加密密钥,16字节 * 8位/字节 = 128位。String random = RandomStringUtils.random(16, "abcdefghijklmnopqrstuvwxyz1234567890");System.out.println("随机key:" + random);System.out.println();System.out.println("---------加密---------");String aesResult = encrypt("测试AES加密12", random);System.out.println("aes加密结果:" + aesResult);System.out.println();System.out.println("---------解密---------");String decrypt = decrypt(aesResult, random);System.out.println("aes解密结果:" + decrypt);System.out.println();System.out.println("--------AES_CBC加密解密---------");String cbcResult = encryptCBC("测试AES加密12456", random);System.out.println("aes_cbc加密结果:" + cbcResult);System.out.println();System.out.println("---------解密CBC---------");String cbcDecrypt = decryptCBC(cbcResult, random);System.out.println("aes解密结果:" + cbcDecrypt);System.out.println();}
}
3.字段处理类
import com.demo.util.AESUtil;
import org.apache.ibatis.type.BaseTypeHandler;
import org.apache.ibatis.type.JdbcType;import java.sql.CallableStatement;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;/*** <p> @Title MyEncryptTypeHandler* <p> @Description 字段加密处理** @author ACGkaka* @date 2023/2/21 17:20*/
public class MyEncryptTypeHandler extends BaseTypeHandler<String> {@Overridepublic void setNonNullParameter(PreparedStatement ps, int i, String parameter, JdbcType jdbcType) throws SQLException {ps.setString(i, AESUtil.defaultEncrypt(parameter));}@Overridepublic String getNullableResult(ResultSet rs, String column) throws SQLException {return AESUtil.defaultDecrypt(rs.getString(column));}@Overridepublic String getNullableResult(ResultSet rs, int columnIndex) throws SQLException {return AESUtil.defaultDecrypt(rs.getString(columnIndex));}@Overridepublic String getNullableResult(CallableStatement cs, int columnIndex) throws SQLException {return AESUtil.defaultDecrypt(cs.getString(columnIndex));}
}
4.修改 MyBatis Plus 查询
4.1 修改表对应实体类
设置 @TableName 注解的 autoResultMap 为 true,默认 false。
import com.baomidou.mybatisplus.annotation.TableName;
import lombok.Data;import java.io.Serializable;/*** 用户表** @author ACGkaka* @date 2023/2/21 17:20*/
@Data
@TableName(value = "t_user_info", autoResultMap = true)
public class UserInfo implements Serializable {}
4.2 修改加密字段对应属性
设置 @TableField 注解的 typeHandler 为 MyEncryptTypeHandler.class。
import com.demo.encrypt.MyEncryptTypeHandler;
import com.baomidou.mybatisplus.annotation.TableField;
import com.baomidou.mybatisplus.annotation.TableName;
import lombok.Data;import java.io.Serializable;/*** 用户表** @author ACGkaka* @date 2023/2/21 17:20*/
@Data
@TableName(value = "t_user_info", autoResultMap = true)
public class UserInfo implements Serializable {/*** 手机号码*/@TableField(value = "PHONE", typeHandler = MyEncryptTypeHandler.class)private String phone;
}
4.3 修改 xml 使用 ResultMap
1)创建 ResultMap 映射,指定 typeHandler;
2)查询语句使用 ResultMap 返回。
<!-- 通用查询映射结果 -->
<resultMap id="BaseResultMap" type="com.demo.model.UserInfo"><id column="ID" property="id" /><result column="ACCOUNT" property="staffCode" /><result column="PHONE" property="phone" typeHandler="com.demo.encrypt.MyEncryptTypeHandler" />
</resultMap><!-- 查询全部 -->
<select id="findAll" resultMap="BaseResultMap">SELECT * FROM t_user_info
</select>
4.4 修改 xml 中 el 表达式
设置好 4.1 和 4.2 就可以保证
修改前:
<!-- 更新手机号 -->
<update id="updatePhoneById">update t_user_info set phone = #{phone} where id = #{id}
</update><!-- 根据手机号查询 -->
<select id="findByPhone" resultMap="BaseResultMap">SELECT * FROM t_user_info where phone = #{phone}
</select>
修改后:
<!-- 更新手机号 -->
<update id="updatePhoneById">update t_user_info set phone = #{phone, typeHandler=com.demo.encrypt.MyEncryptTypeHandler} where id = #{id}
</update><!-- 根据手机号查询 -->
<select id="findByPhone" resultMap="BaseResultMap">SELECT * FROM t_user_info where phone = #{phone, typeHandler=com.demo.encrypt.MyEncryptTypeHandler}
</select>
5.测试结果
由于测试内容较多,这里先直接展示测试结果,具体测试示例可以看 补充:测试实例
| 操作 | 实现方式 | 入参 | 测试结果 |
|---|---|---|---|
| SELECT | 原生SQL | 非加密字段 | 出参解密成功 |
| SELECT | QueryWrapper | 非加密字段 | 出参解密成功 |
| SELECT | 原生SQL | 加密字段 | 入参加密成功 |
| SELECT | QueryWrapper | 加密字段 | 入参加密失败 |
| UPDATE | 原生SQL | 加密字段 | 入参加密成功 |
| UPDATE | UpdateWrapper | 加密字段 | 入参加密失败 |
| UPDATE | LambdaUpdateWrapper | 加密字段 | 入参加密成功 |
| UPDATE | updateById | 加密字段 | 入参加密成功 |
| INSERT | Service | 加密字段 | 入参加密成功 |
说明:
- 官方的解答是 QueryWrapper、UpdateWrapper 底层是通过 @Param 来实现的,目前没有做到入参支持 typeHandler,如果做的话会影响性能。
6.MyBatis Plus 缺陷
-
QueryWrapper 不支持入参加密;
-
UpdateWrapper 不支持入参加密;
-
加密字段不支持模糊查询。
补充:测试实例
1 查询测试
1.1 查询信息,SQL实现
@Test
public void getUserInfoTest1() {UserInfo userInfo = userInfoService.findByAccount("testAccount");System.out.println("userInfo:" + userInfo);System.out.println("phone:" + userInfo.getPhone());
}
测试结果:出参解密成功
1.2 查询信息,QueryWrapper实现
@Test
public void getUserInfoTest2() {QueryWrapper<UserInfo> wrapper = new QueryWrapper<>();wrapper.eq("account", "testAccount");List<UserInfo> users = userInfoService.list(wrapper);System.out.println("userInfo:" + users);System.out.println("phone:" + users.get(0).getPhone());
}
测试结果:出参解密成功
1.3 查询信息,根据加密字段查询,SQL实现
@Test
public void getUserInfoTest3() {UserInfo user = userInfoService.findByPhone("13888888888");System.out.println("userInfo:" + user);System.out.println("phone:" + user.getPhone());
}
(注意:入参需要使用el表达式指定 typeHandler)
测试结果:入参加密成功
1.4 查询信息,根据加密字段查询,QueryWrapper实现
@Test
public void getUserInfoTest3() {QueryWrapper<UserInfo> wrapper = new QueryWrapper<>();wrapper.lambda().eq(UserInfo::getPhone, "13888888888");List<UserInfo> users = userInfoService.list(wrapper);System.out.println("userInfo:" + users);System.out.println("phone:" + users.get(0).getPhone());
}
测试结果:入参加密失败,QueryWrapper底层使用 @Param 实现,无法像 SQL 实现一样指定 typeHandler。
2.测试更新
2.1 更新信息,SQL实现
@Test
public void updateUserInfoTest1() {userInfoService.updatePhoneByAccount("testAccount", "13888888888");
}
测试结果:入参加密成功
2.2 更新信息,UpdateWrapper实现
@Test
public void updateUserInfoTest2() {UpdateWrapper<UserInfo> wrapper = new UpdateWrapper<>();wrapper.set("phone", "13888888888");wrapper.eq("account", "testAccount");userInfoService.update(wrapper);getUserInfoTest1();
}
测试结果:入参加密失败,UpdateWrapper底层使用 @Param 实现,无法像 SQL 实现一样指定 typeHandler。
2.3 更新信息,LambdaUpdateWrapper实现
@Test
public void updateUserInfoTest3() {LambdaUpdateWrapper<UserInfo> wrapper = Wrappers.<UserInfo>lambdaUpdate().set(UserInfo::getPhone, "13888888888", "typeHandler=com.demo.encrypt.MyEncryptTypeHandler");wrapper.eq(UserInfo::getAccount, "testAccount");userInfoService.update(wrapper);getUserInfoTest1();
}
测试结果:入参加密成功
2.4 更新信息,updateById实现
@Test
public void updateUserInfoTest4() {UserInfo userInfo = userInfoService.findByAccount("testAccount");userInfo.setPhone("13888888888");userInfoService.updateById(userInfo);
}
测试结果:入参加密成功
3.测试插入
7.3.1 插入信息,SQL实现
@Test
public void insertUserInfoTest1() {UserInfo userInfo = userInfoService.findByAccount("testAccount");userInfo.setAccount("testAccount_002");userInfo.setPhone("13888888888");userInfoService.save(userInfo);UserInfo newUserInfo = userInfoService.findByAccount("testAccount_002");System.out.println("userInfo:" + newUserInfo);System.out.println("phone:" + newUserInfo.getPhone());
}
测试结果:入参加密成功
3.2 插入信息,Service实现
@Test
public void insertUserInfoTest1() {UserInfo userInfo = userInfoService.findByAccount("testAccount");userInfo.setAccount("testAccount_002");userInfo.setPhone("13888888888");userInfoService.save(userInfo);UserInfo newUserInfo = userInfoService.findByAccount("testAccount_002");System.out.println("userInfo:" + newUserInfo);System.out.println("phone:" + newUserInfo.getPhone());
}
测试结果:入参加密成功
整理完毕,完结撒花~
参考地址:
1.mybaits plus 字段加密与解密,https://blog.csdn.net/qq_21134059/article/details/121752978
2.mybatis plus 官方问题页面,https://github.com/baomidou/mybatis-plus/issues
3.更新时自定义的TypeHandler不生效,https://github.com/baomidou/mybatis-plus/issues/794
4.lambdaUpdate() 无法更新Json对象字段,https://github.com/baomidou/mybatis-plus/issues/5031
5.LambdaUpdateWrapper不支持自定义BaseTypeHandler,https://github.com/baomidou/mybatis-plus/issues/3317
相关文章:
MyBatis Plus 数据库字段加密处理
目录1.场景介绍2.Maven依赖2.AESUtil.java 加解密工具类3.字段处理类4.修改 MyBatis Plus 查询4.1 修改表对应实体类4.2 修改加密字段对应属性4.3 修改 xml 使用 ResultMap4.4 修改 xml 中 el 表达式5.测试结果6.MyBatis Plus 缺陷补充:测试实例1 查询测试1.1 查询信…...
openpose在win下环境配置
1.下载OpenPose库 以下二选一进行下载源码 (1)git进行下载 打开GitHub Desktop或者Powershell git clone https://github.com/CMU-Perceptual-Computing-Lab/openpose cd openpose/ git submodule update --init --recursive --remote(2)在github上手动下载 由于下载环境问…...
【剑指offer-C++】JZ16:数值的整数次方
【剑指offer】JZ16:数值的整数次方题目描述解题思路题目描述 描述:实现函数 double Power(double base, int exponent),求base的exponent次方。 注意: 1.保证base和exponent不同时为0。 2.不得使用库函数,同时不需要…...
了解Axios及其运用方式
Axios简介 axios框架全称(ajax – I/O – system): 基于promise用于浏览器和node.js的http客户端,因此可以使用Promise API 一、axios是干啥的 说到axios我们就不得不说下Ajax。在旧浏览器页面在向服务器请求数据时,…...
【LeetCode】剑指 Offer(7)
目录 写在前面: 题目剑指 Offer 17. 打印从1到最大的n位数 - 力扣(Leetcode) 题目的接口: 解题思路: 代码: 过啦!!! 题目:剑指 Offer 18. 删除链表的节…...
Python:try except 异常处理整理
目录 一、try except异常处理的语句格式 二、获取相关异常信息 (1)sys.exec_info() 三、traceback模块的常用方式 (1)traceback.print_tb(tb, limitNone, fileNone) 打印指定堆栈异常信息 (2)tracebac…...
Redis Lua脚本的详细介绍以及使用入门
Redis Lua脚本的详细介绍以及使用入门。 文章目录Redis Lua脚本的引入开源软件的可扩展性Redis的扩展性脚本Redis Lua脚本的基本使用通过EVAL命令执行Lua脚本通过脚本与Redis交互Java中调用Redis Lua脚本Java调用Lua脚本的方式Redis Lua脚本的使用建议脚本缓存脚本缓存稳定性脚…...
synchronized和ReentrantLock有什么区别呢?
第15讲 | synchronized和ReentrantLock有什么区别呢? 从今天开始,我们将进入 Java 并发学习阶段。软件并发已经成为现代软件开发的基础能力,而 Java 精心设计的高效并发机制,正是构建大规模应用的基础之一,所以考察并发…...
SVHN数据集下载及使用方法
街景门牌号数据集(SVHN),这是一个现实世界数据集,用于开发目标检测算法。它需要最少的数据预处理过程。它与 MNIST 数据集有些类似,但是有着更多的标注数据(超过 600,000 张图像)。这些数据是从…...
产业安全公开课:2023年DDoS攻击趋势研判与企业防护新思路
2023年,全球数字化正在加速发展,网络安全是数字化发展的重要保障。与此同时,网络威胁日益加剧。其中,DDoS攻击作为网络安全的主要威胁之一,呈现出连年增长的态势,给企业业务稳定带来巨大挑战。2月21日&…...
Docker 容器命令 和安装各种镜像环境
CentOS安装Docker 1.1.卸载(可选) 如果之前安装过旧版本的Docker,可以使用下面命令卸载: yum remove docker \docker-client \docker-client-latest \docker-common \docker-latest \docker-latest-logrotate \docker-logrotat…...
【数据结构】顺序表的深度剖析
🌇个人主页:平凡的小苏 📚学习格言:别人可以拷贝我的模式,但不能拷贝我不断往前的激情 🛸C语言专栏:https://blog.csdn.net/vhhhbb/category_12174730.html 🚀数据结构专栏ÿ…...
当面试官问“你的SQL能力怎么样”时,怎么回答才不会掉进应聘陷阱?
在某平台看到一个比较实际的问题,在这里分享给职场新人。 SQL已经是职场最常用的一种编程语言,所以应聘技术或非技术岗位,都可能会被问道一个问题:你的SQL能力怎么样? 对于职场新人来说(SQL高手可以无视下…...
AI作画—中国画之山水画
山水画,简称“山水”,中国画的一种,描写山川自然景色为主体的绘画。山水画在我国绘画史中占有重要的地位。 山水画形成于魏晋南北朝时期,但尚未从人物画中完全分离。隋唐时始终独立,五代、北宋时趋于成熟,…...
Java:Java与Python — 编码大战
Java和Python是目前市场上最热门的两种编程语言,因为它们具有通用性、高效性和自动化能力。两种语言都有各自的优点和缺点,但主要区别在于Java 是静态类型的,Python是动态类型的。它们有相似之处,因为它们都采用了“一切都是对象”…...
山东专精特新各地市扶持政策
青岛市奖励政策:新认定为市隐形、省“专精特新”及省瞪羚、角兽的我市企业,分别给予50万元、30万元、50万元、300万元的一次性奖励。奖励金额:省级30万济南市奖励政策:对被认定的国家专精特新 “小巨人”企业一次性给予200万元奖励…...
持续事务管理过程中的事件驱动
比较官方的定义:事件驱动是指在持续事务管理过程中,进行决策的一种策略,即跟随当前时间点上出现的事件,调动可用资源,执行相关任务,使不断出现的问题得以解决,防止事务堆积。在计算机编程、公共…...
【手把手一起学习】(三) Altium Designer 20 原理图库添加元件
1 添加元件 元件符号是元件在原理图上的表现形式,主要由边框、管脚、名称等组成,原理图库中的元件管脚(顺序,间距等)与电子元件实物的引脚严格对应,绘制原理图库时,一定参考元件规格书和芯片数据手册中的说明…...
设计模式-行为型模式:观察者模式
目录 1、简介 2、组成部分 3、优缺点 4、使用场景 5、代码实现 1、简介 观察者模式是一种软件设计模式,它定义了一种一对多的依赖关系,让多个观察者对象同时监听一个主题对象,当主题对象发生变化时,所有的观察者对象都会得到…...
Springboot 为了偷懒,我封装了一个自适配的数据单位转换工具类
前言 平时做一些统计数据,经常从数据库或者是从接口获取出来的数据,单位是跟业务需求不一致的。 比如, 我们拿出来的 分, 实际上要是元 又比如,我们拿到的数据需要 乘以100 返回给前端做 百分比展示 又比如ÿ…...
观成科技:隐蔽隧道工具Ligolo-ng加密流量分析
1.工具介绍 Ligolo-ng是一款由go编写的高效隧道工具,该工具基于TUN接口实现其功能,利用反向TCP/TLS连接建立一条隐蔽的通信信道,支持使用Let’s Encrypt自动生成证书。Ligolo-ng的通信隐蔽性体现在其支持多种连接方式,适应复杂网…...
AI-调查研究-01-正念冥想有用吗?对健康的影响及科学指南
点一下关注吧!!!非常感谢!!持续更新!!! 🚀 AI篇持续更新中!(长期更新) 目前2025年06月05日更新到: AI炼丹日志-28 - Aud…...
Android Wi-Fi 连接失败日志分析
1. Android wifi 关键日志总结 (1) Wi-Fi 断开 (CTRL-EVENT-DISCONNECTED reason3) 日志相关部分: 06-05 10:48:40.987 943 943 I wpa_supplicant: wlan0: CTRL-EVENT-DISCONNECTED bssid44:9b:c1:57:a8:90 reason3 locally_generated1解析: CTR…...
stm32G473的flash模式是单bank还是双bank?
今天突然有人stm32G473的flash模式是单bank还是双bank?由于时间太久,我真忘记了。搜搜发现,还真有人和我一样。见下面的链接:https://shequ.stmicroelectronics.cn/forum.php?modviewthread&tid644563 根据STM32G4系列参考手…...
调用支付宝接口响应40004 SYSTEM_ERROR问题排查
在对接支付宝API的时候,遇到了一些问题,记录一下排查过程。 Body:{"datadigital_fincloud_generalsaas_face_certify_initialize_response":{"msg":"Business Failed","code":"40004","sub_msg…...
Zustand 状态管理库:极简而强大的解决方案
Zustand 是一个轻量级、快速和可扩展的状态管理库,特别适合 React 应用。它以简洁的 API 和高效的性能解决了 Redux 等状态管理方案中的繁琐问题。 核心优势对比 基本使用指南 1. 创建 Store // store.js import create from zustandconst useStore create((set)…...
逻辑回归:给不确定性划界的分类大师
想象你是一名医生。面对患者的检查报告(肿瘤大小、血液指标),你需要做出一个**决定性判断**:恶性还是良性?这种“非黑即白”的抉择,正是**逻辑回归(Logistic Regression)** 的战场&a…...
VTK如何让部分单位不可见
最近遇到一个需求,需要让一个vtkDataSet中的部分单元不可见,查阅了一些资料大概有以下几种方式 1.通过颜色映射表来进行,是最正规的做法 vtkNew<vtkLookupTable> lut; //值为0不显示,主要是最后一个参数,透明度…...
Java入门学习详细版(一)
大家好,Java 学习是一个系统学习的过程,核心原则就是“理论 实践 坚持”,并且需循序渐进,不可过于着急,本篇文章推出的这份详细入门学习资料将带大家从零基础开始,逐步掌握 Java 的核心概念和编程技能。 …...
全面解析各类VPN技术:GRE、IPsec、L2TP、SSL与MPLS VPN对比
目录 引言 VPN技术概述 GRE VPN 3.1 GRE封装结构 3.2 GRE的应用场景 GRE over IPsec 4.1 GRE over IPsec封装结构 4.2 为什么使用GRE over IPsec? IPsec VPN 5.1 IPsec传输模式(Transport Mode) 5.2 IPsec隧道模式(Tunne…...
