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 返回给前端做 百分比展示 又比如ÿ…...

正则表达式
当我们需要对字符串进行判断的时候,使用正则表达式能大大提高编程效率。比如,当我们需要找出所有“像邮箱”的字符串(包含"" "." ".com",且顺序一致),我们需要一个某种模式的…...

java进阶Map 集合
通过之前的学习我们知道Map是一个双列集合,就是以键值对的形式进行数据存储 java进阶—集合 Map 下面有 三个子接口,HashMap , HashTable 以及 TreeMap 提醒一点:Map不是Collection下的集合,Collection是单列集合&am…...

Java 方法超详细整理,适合新手入门
目录 一、什么是方法呢? 二、方法的优点 三、带返回值方法定义 语法: 示例: 四、带返回值方法调用 语法: 示例: 五、结果示例 一、什么是方法呢? Java方法是语句的集合,它们在一起执行…...

软考学习笔记(题目知识记录)
答案为 概要设计阶段 本题涉及软件工程的概念 软件工程的任务是基于需求分析的结果建立各种设计模型,给出问题的解决方案 软件设计可以分为两个阶段: 概要设计阶段和详细设计阶段 结构化设计方法中,概要设计阶段进行软件体系结构的设计&…...

2021.3.3idea创建Maven项目
首先new - project - 找到Maven 然后按下图操作:先勾选使用骨架,再找到Maven-archetype-webapp,选中,然后next填写自己想要创建的项目名,然后选择自己的工作空间①、选择自己下载的Maven插件②、选择选择Maven里的sett…...

ASP.NET MVC | 创建应用程序
目录 首先 NO.1 No.2 App_Data 文件夹 Content 文件夹 Controllers 文件夹 Models 文件夹 Views 文件夹 Scripts 文件夹 最后 首先 一步一步的来,电脑上需要安装vs2019软件,版本高低无所谓,就是功能多少而已。 长这样的࿰…...

思科设备命令讲解(超基础)
♥️作者:小刘在C站 ♥️个人主页:小刘主页 ♥️每天分享云计算网络运维课堂笔记,努力不一定有收获,但一定会有收获加油!一起努力,共赴美好人生! ♥️夕阳下,是最美的绽放࿰…...

Qt-FFmpeg开发-保存视频流裸流(11)
Qt-FFmpeg开发-保存视频流裸流📀 文章目录Qt-FFmpeg开发-保存视频流裸流📀1、概述📸2、实现效果💽3、FFmpeg保存裸流代码流程💡4、主要代码🔍5、完整源代码📑更多精彩内容👉个人内容…...

Zebec官方辟谣“我们与Protradex没有任何关系”
近日,流支付协议Zebec Protocol在其官方推特上,发表了一个辟谣澄清声明。该条推特推文表示,“Zebec 与 Protradex 没有任何关系或产生关联。他们( Protradex )声称Zebec 生态正在支持他们,但这是错误的。随…...

BMS电池管理系统中的各种算法介绍
BMS电池管理系统 是一种用于电池组中的单个电池管理的系统,以确保其安全性、寿命和性能。BMS系统通过采集电池信息并对其进行分析,以确保电池组的正常运行。在BMS电池管理系统中,涉及到了许多算法,包括最大功率点追踪算法、SOC计算…...