当前位置: 首页 > news >正文

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 注解的 typeHandlerMyEncryptTypeHandler.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非加密字段出参解密成功
SELECTQueryWrapper非加密字段出参解密成功
SELECT原生SQL加密字段入参加密成功
SELECTQueryWrapper加密字段入参加密失败
UPDATE原生SQL加密字段入参加密成功
UPDATEUpdateWrapper加密字段入参加密失败
UPDATELambdaUpdateWrapper加密字段入参加密成功
UPDATEupdateById加密字段入参加密成功
INSERTService加密字段入参加密成功

说明:

  • 官方的解答是 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 缺陷补充&#xff1a;测试实例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&#xff1a;数值的整数次方题目描述解题思路题目描述 描述&#xff1a;实现函数 double Power(double base, int exponent)&#xff0c;求base的exponent次方。 注意&#xff1a; 1.保证base和exponent不同时为0。 2.不得使用库函数&#xff0c;同时不需要…...

了解Axios及其运用方式

Axios简介 axios框架全称&#xff08;ajax – I/O – system&#xff09;&#xff1a; 基于promise用于浏览器和node.js的http客户端&#xff0c;因此可以使用Promise API 一、axios是干啥的 说到axios我们就不得不说下Ajax。在旧浏览器页面在向服务器请求数据时&#xff0c;…...

【LeetCode】剑指 Offer(7)

目录 写在前面&#xff1a; 题目剑指 Offer 17. 打印从1到最大的n位数 - 力扣&#xff08;Leetcode&#xff09; 题目的接口&#xff1a; 解题思路&#xff1a; 代码&#xff1a; 过啦&#xff01;&#xff01;&#xff01; 题目&#xff1a;剑指 Offer 18. 删除链表的节…...

Python:try except 异常处理整理

目录 一、try except异常处理的语句格式 二、获取相关异常信息 &#xff08;1&#xff09;sys.exec_info() 三、traceback模块的常用方式 &#xff08;1&#xff09;traceback.print_tb(tb, limitNone, fileNone) 打印指定堆栈异常信息 &#xff08;2&#xff09;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有什么区别呢&#xff1f; 从今天开始&#xff0c;我们将进入 Java 并发学习阶段。软件并发已经成为现代软件开发的基础能力&#xff0c;而 Java 精心设计的高效并发机制&#xff0c;正是构建大规模应用的基础之一&#xff0c;所以考察并发…...

SVHN数据集下载及使用方法

街景门牌号数据集&#xff08;SVHN&#xff09;&#xff0c;这是一个现实世界数据集&#xff0c;用于开发目标检测算法。它需要最少的数据预处理过程。它与 MNIST 数据集有些类似&#xff0c;但是有着更多的标注数据&#xff08;超过 600,000 张图像&#xff09;。这些数据是从…...

产业安全公开课:2023年DDoS攻击趋势研判与企业防护新思路

2023年&#xff0c;全球数字化正在加速发展&#xff0c;网络安全是数字化发展的重要保障。与此同时&#xff0c;网络威胁日益加剧。其中&#xff0c;DDoS攻击作为网络安全的主要威胁之一&#xff0c;呈现出连年增长的态势&#xff0c;给企业业务稳定带来巨大挑战。2月21日&…...

Docker 容器命令 和安装各种镜像环境

CentOS安装Docker 1.1.卸载&#xff08;可选&#xff09; 如果之前安装过旧版本的Docker&#xff0c;可以使用下面命令卸载&#xff1a; yum remove docker \docker-client \docker-client-latest \docker-common \docker-latest \docker-latest-logrotate \docker-logrotat…...

【数据结构】顺序表的深度剖析

&#x1f307;个人主页&#xff1a;平凡的小苏 &#x1f4da;学习格言&#xff1a;别人可以拷贝我的模式&#xff0c;但不能拷贝我不断往前的激情 &#x1f6f8;C语言专栏&#xff1a;https://blog.csdn.net/vhhhbb/category_12174730.html &#x1f680;数据结构专栏&#xff…...

当面试官问“你的SQL能力怎么样”时,怎么回答才不会掉进应聘陷阱?

在某平台看到一个比较实际的问题&#xff0c;在这里分享给职场新人。 SQL已经是职场最常用的一种编程语言&#xff0c;所以应聘技术或非技术岗位&#xff0c;都可能会被问道一个问题&#xff1a;你的SQL能力怎么样&#xff1f; 对于职场新人来说&#xff08;SQL高手可以无视下…...

AI作画—中国画之山水画

山水画&#xff0c;简称“山水”&#xff0c;中国画的一种&#xff0c;描写山川自然景色为主体的绘画。山水画在我国绘画史中占有重要的地位。 山水画形成于魏晋南北朝时期&#xff0c;但尚未从人物画中完全分离。隋唐时始终独立&#xff0c;五代、北宋时趋于成熟&#xff0c;…...

Java:Java与Python — 编码大战

Java和Python是目前市场上最热门的两种编程语言&#xff0c;因为它们具有通用性、高效性和自动化能力。两种语言都有各自的优点和缺点&#xff0c;但主要区别在于Java 是静态类型的&#xff0c;Python是动态类型的。它们有相似之处&#xff0c;因为它们都采用了“一切都是对象”…...

山东专精特新各地市扶持政策

青岛市奖励政策&#xff1a;新认定为市隐形、省“专精特新”及省瞪羚、角兽的我市企业&#xff0c;分别给予50万元、30万元、50万元、300万元的一次性奖励。奖励金额&#xff1a;省级30万济南市奖励政策&#xff1a;对被认定的国家专精特新 “小巨人”企业一次性给予200万元奖励…...

持续事务管理过程中的事件驱动

比较官方的定义&#xff1a;事件驱动是指在持续事务管理过程中&#xff0c;进行决策的一种策略&#xff0c;即跟随当前时间点上出现的事件&#xff0c;调动可用资源&#xff0c;执行相关任务&#xff0c;使不断出现的问题得以解决&#xff0c;防止事务堆积。在计算机编程、公共…...

【手把手一起学习】(三) Altium Designer 20 原理图库添加元件

1 添加元件 元件符号是元件在原理图上的表现形式&#xff0c;主要由边框、管脚、名称等组成&#xff0c;原理图库中的元件管脚(顺序&#xff0c;间距等)与电子元件实物的引脚严格对应&#xff0c;绘制原理图库时&#xff0c;一定参考元件规格书和芯片数据手册中的说明&#xf…...

设计模式-行为型模式:观察者模式

目录 1、简介 2、组成部分 3、优缺点 4、使用场景 5、代码实现 1、简介 观察者模式是一种软件设计模式&#xff0c;它定义了一种一对多的依赖关系&#xff0c;让多个观察者对象同时监听一个主题对象&#xff0c;当主题对象发生变化时&#xff0c;所有的观察者对象都会得到…...

Springboot 为了偷懒,我封装了一个自适配的数据单位转换工具类

前言 平时做一些统计数据&#xff0c;经常从数据库或者是从接口获取出来的数据&#xff0c;单位是跟业务需求不一致的。 比如&#xff0c; 我们拿出来的 分&#xff0c; 实际上要是元 又比如&#xff0c;我们拿到的数据需要 乘以100 返回给前端做 百分比展示 又比如&#xff…...

(LeetCode 每日一题) 3442. 奇偶频次间的最大差值 I (哈希、字符串)

题目&#xff1a;3442. 奇偶频次间的最大差值 I 思路 &#xff1a;哈希&#xff0c;时间复杂度0(n)。 用哈希表来记录每个字符串中字符的分布情况&#xff0c;哈希表这里用数组即可实现。 C版本&#xff1a; class Solution { public:int maxDifference(string s) {int a[26]…...

Flask RESTful 示例

目录 1. 环境准备2. 安装依赖3. 修改main.py4. 运行应用5. API使用示例获取所有任务获取单个任务创建新任务更新任务删除任务 中文乱码问题&#xff1a; 下面创建一个简单的Flask RESTful API示例。首先&#xff0c;我们需要创建环境&#xff0c;安装必要的依赖&#xff0c;然后…...

Qt/C++开发监控GB28181系统/取流协议/同时支持udp/tcp被动/tcp主动

一、前言说明 在2011版本的gb28181协议中&#xff0c;拉取视频流只要求udp方式&#xff0c;从2016开始要求新增支持tcp被动和tcp主动两种方式&#xff0c;udp理论上会丢包的&#xff0c;所以实际使用过程可能会出现画面花屏的情况&#xff0c;而tcp肯定不丢包&#xff0c;起码…...

Python:操作 Excel 折叠

💖亲爱的技术爱好者们,热烈欢迎来到 Kant2048 的博客!我是 Thomas Kant,很开心能在CSDN上与你们相遇~💖 本博客的精华专栏: 【自动化测试】 【测试经验】 【人工智能】 【Python】 Python 操作 Excel 系列 读取单元格数据按行写入设置行高和列宽自动调整行高和列宽水平…...

MFC内存泄露

1、泄露代码示例 void X::SetApplicationBtn() {CMFCRibbonApplicationButton* pBtn GetApplicationButton();// 获取 Ribbon Bar 指针// 创建自定义按钮CCustomRibbonAppButton* pCustomButton new CCustomRibbonAppButton();pCustomButton->SetImage(IDB_BITMAP_Jdp26)…...

可靠性+灵活性:电力载波技术在楼宇自控中的核心价值

可靠性灵活性&#xff1a;电力载波技术在楼宇自控中的核心价值 在智能楼宇的自动化控制中&#xff0c;电力载波技术&#xff08;PLC&#xff09;凭借其独特的优势&#xff0c;正成为构建高效、稳定、灵活系统的核心解决方案。它利用现有电力线路传输数据&#xff0c;无需额外布…...

visual studio 2022更改主题为深色

visual studio 2022更改主题为深色 点击visual studio 上方的 工具-> 选项 在选项窗口中&#xff0c;选择 环境 -> 常规 &#xff0c;将其中的颜色主题改成深色 点击确定&#xff0c;更改完成...

CocosCreator 之 JavaScript/TypeScript和Java的相互交互

引擎版本&#xff1a; 3.8.1 语言&#xff1a; JavaScript/TypeScript、C、Java 环境&#xff1a;Window 参考&#xff1a;Java原生反射机制 您好&#xff0c;我是鹤九日&#xff01; 回顾 在上篇文章中&#xff1a;CocosCreator Android项目接入UnityAds 广告SDK。 我们简单讲…...

【JavaWeb】Docker项目部署

引言 之前学习了Linux操作系统的常见命令&#xff0c;在Linux上安装软件&#xff0c;以及如何在Linux上部署一个单体项目&#xff0c;大多数同学都会有相同的感受&#xff0c;那就是麻烦。 核心体现在三点&#xff1a; 命令太多了&#xff0c;记不住 软件安装包名字复杂&…...

vue3+vite项目中使用.env文件环境变量方法

vue3vite项目中使用.env文件环境变量方法 .env文件作用命名规则常用的配置项示例使用方法注意事项在vite.config.js文件中读取环境变量方法 .env文件作用 .env 文件用于定义环境变量&#xff0c;这些变量可以在项目中通过 import.meta.env 进行访问。Vite 会自动加载这些环境变…...