[Spring Cloud] (6)gateway整体加解密
文章目录
- 简述
- 整体效果
- 后端
- 增加配置
- nacos增加配置
- GlobalConfig
- 添加请求整体解密拦截器DecryptionFilter
- 添加响应整体解密拦截器EncryptionFilter
- 前端
- 请求拦截器
- 添加整体加密逻辑
- 请求头中添加sessionId
- 响应拦截器
- 添加整体解密逻辑
简述
本文网关gateway,微服务,vue已开源到gitee
杉极简/gateway网关阶段学习
在经历前面5章的铺垫下,终于进入到了最重要的环节——拦截器功能实现,此时我们需要看的东西就比较集中而且简单了。
在Java分布式系统中,使用Spring Cloud Gateway进行整体的加解密处理可以起到以下几个关键作用:
- 安全性增强:通过加密客户端与服务端之间传输的数据,可以防止敏感信息在网络传输过程中被截获和窃取。即使数据包被拦截,没有相应的密钥也无法解读数据内容,从而保护了用户隐私和系统安全。
- 数据完整性校验:在数据传输过程中,除了加密保护数据外,还可以通过签名机制来确保数据的完整性。签名可以验证数据在传输过程中是否被篡改,增强了数据的可靠性。
- 减轻后端服务负担:通过在网关层统一进行加解密处理,后端微服务不需要再集成额外的安全模块来进行数据的加解密工作,从而降低了后端服务的复杂性和资源消耗。
- 统一的安全管理:网关作为所有请求的入口点,可以实现统一的安全管理策略。所有的加解密操作按照统一的规则进行,便于管理和维护,同时方便对安全策略进行升级和维护。
- 提高开发效率:开发者可以专注于业务逻辑的实现,而不必关注数据传输的安全细节,提高了开发效率和项目的迭代速度。
- 跨服务数据保护:在微服务架构中,服务间可能需要相互调用和数据交换。网关加解密确保了即使在内部服务间传输的数据也是安全的,防止了潜在的内部安全风险。
- 应对合规性要求:某些行业或地区的法律法规要求对用户数据进行加密处理,使用网关加解密可以更好地符合这些合规性要求,避免法律风险。
- 灵活的策略调整:网关层的加解密策略可以根据业务需求灵活调整,比如可以针对不同的请求路径或请求方法应用不同的加密算法和密钥,而不需要修改每个微服务的代码。
- 减少敏感信息泄露风险:对于某些对数据安全性要求极高的应用场景,如金融交易、个人隐私信息等,网关加解密可以有效减少敏感信息泄露的风险。
- 提高系统的可扩展性:随着业务的发展,系统的安全需求可能会变化。在网关层实现加解密可以方便地根据新的安全需求进行扩展和更新,而不影响现有的业务流程和后端服务。
Spring Cloud Gateway的加解密功能为分布式系统提供了一个安全、高效、灵活的数据传输解决方案,有助于提升系统的安全性和开发效率。
整体效果
像不需要加密的接口,请求参数与响应数据都是未加密的
像数据接口,发送的参数是加密数据,接收到的都是是加密数据。但是经过前后端的拦截器处理之后,显示出的依然是正常数据
后端
增加配置
nacos增加配置
# 整体对称加解密aes: true# 整体非对称加解密rsa: true
GlobalConfig
/*** 整体对称加解密*/private boolean aes;/*** 整体非对称加解密*/private boolean rsa;
添加请求整体解密拦截器DecryptionFilter
package com.fir.gateway.filter.request;import com.alibaba.fastjson.JSONObject;
import com.fir.gateway.config.GlobalConfig;
import com.fir.gateway.config.exception.CustomException;
import com.fir.gateway.config.result.AjaxStatus;
import com.fir.gateway.dto.ConnectDTO;
import com.fir.gateway.utils.AESUtils;
import com.fir.gateway.utils.RSAUtils;
import lombok.SneakyThrows;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.springframework.cloud.gateway.filter.GatewayFilterChain;
import org.springframework.cloud.gateway.filter.GlobalFilter;
import org.springframework.core.Ordered;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.http.HttpMethod;
import org.springframework.http.server.reactive.ServerHttpRequest;
import org.springframework.stereotype.Component;
import org.springframework.util.LinkedMultiValueMap;
import org.springframework.util.MultiValueMap;
import org.springframework.web.server.ServerWebExchange;
import org.springframework.web.util.UriComponentsBuilder;
import reactor.core.publisher.Mono;import javax.annotation.Resource;
import java.net.URI;
import java.net.URLDecoder;
import java.net.URLEncoder;
import java.nio.charset.StandardCharsets;
import java.util.List;
import java.util.Map;
import java.util.Set;;/*** 请求整理解密-请求拦截器** @author fir*/
@Slf4j
@Component
public class DecryptionFilter implements Ordered, GlobalFilter {/*** 网关参数配置*/@Resourceprivate GlobalConfig globalConfig;@Resourceprivate RedisTemplate<String, Object> redisTemplate;@Overridepublic int getOrder() {return -280;}@Overridepublic Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {log.info("整体解密:start");ServerHttpRequest req = exchange.getRequest();String method = req.getMethodValue();boolean rsa = globalConfig.isRsa();boolean aes = globalConfig.isAes();if (rsa || aes) {// 白名单路由判断ServerHttpRequest request = exchange.getRequest();String path = request.getPath().toString();List<String> whiteUrls = globalConfig.getWhiteUrls();if(!whiteUrls.contains(path)){ServerHttpRequest builder = req.mutate().build();if (HttpMethod.GET.matches(method)) {log.info("当前请求参数为: {}", req.getQueryParams());builder = changeGet(exchange, builder);} else if (HttpMethod.POST.matches(method)) {log.info("当前请求参数为: {}", req.getQueryParams());builder = changeGet(exchange, builder);}exchange = exchange.mutate().request(builder).build();log.info("整体解密:true");}else {log.info("整体解密:true,白名单");}}else {log.info("整体解密:true,验证已关闭");}return chain.filter(exchange);}/*** 获取请求参数等信息进行过滤处理** @param exchange 请求* @param serverHttpRequest 请求* @return 处理结束的参数*/@SneakyThrowsprivate ServerHttpRequest changeGet(ServerWebExchange exchange, ServerHttpRequest serverHttpRequest) {String session = exchange.getRequest().getHeaders().getFirst("s");if (session == null) {throw new CustomException(AjaxStatus.SESSION_INVALID);}JSONObject jsonObject = (JSONObject) redisTemplate.opsForValue().get(session);if (jsonObject == null) {throw new CustomException(AjaxStatus.SESSION_EXPIRE);}ConnectDTO connectDTO = jsonObject.toJavaObject(ConnectDTO.class);String privateKey = connectDTO.getPrivateKey();String secretKey = connectDTO.getSecretKey();// 获取原参数URI uri = serverHttpRequest.getURI();String originalQuery = uri.getRawQuery();String decodedQuery = null;if(StringUtils.isNotBlank(originalQuery)){decodedQuery = URLDecoder.decode(originalQuery, "UTF-8");}// 更改参数MultiValueMap<String, String> newQueryParams = new LinkedMultiValueMap<>();if (StringUtils.isNotBlank(originalQuery) && org.springframework.util.StringUtils.hasText(decodedQuery)) {// 修改请求参数,String[] array只能处理前端特定加密 {data:加密内容的形式}, 传递到后端,会变更为 data=加密内容。// 除此以外的所有方式不能通过本方法进行解密String[] array = decodedQuery.split("=");if (array.length > 1) {decodedQuery = array[1];if (decodedQuery != null) {boolean rsa = globalConfig.isRsa();boolean aes = globalConfig.isAes();if (rsa) {// 对数据进行非对称解密originalQuery = RSAUtils.decryptSection(decodedQuery, privateKey);}if (aes) {// 对数据进行对称解密originalQuery = AESUtils.decrypt(originalQuery, secretKey);}}Map<String, Object> dataMap = JSONObject.parseObject(originalQuery, Map.class);if (dataMap != null) {Set<String> strings = dataMap.keySet();for (String key : strings) {String encodedString = URLEncoder.encode(dataMap.get(key).toString(), StandardCharsets.UTF_8.toString());newQueryParams.add(key, encodedString);}}}}// 替换查询参数URI newUri = UriComponentsBuilder.fromUri(uri).query(null).queryParams(newQueryParams).build(true).toUri();ServerHttpRequest request = exchange.getRequest().mutate().uri(newUri).build();// 将解密后的参数重新设置到请求中uri = request.getURI();log.info("更改后的当前请求参数为: {}", uri.getRawQuery());return request;}
}
添加响应整体解密拦截器EncryptionFilter
package com.fir.gateway.filter.request;import com.alibaba.fastjson.JSONObject;
import com.fir.gateway.config.GlobalConfig;
import com.fir.gateway.config.exception.CustomException;
import com.fir.gateway.config.result.AjaxStatus;
import com.fir.gateway.dto.ConnectDTO;
import com.fir.gateway.utils.AESUtils;
import com.fir.gateway.utils.RSAUtils;
import lombok.SneakyThrows;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.springframework.cloud.gateway.filter.GatewayFilterChain;
import org.springframework.cloud.gateway.filter.GlobalFilter;
import org.springframework.core.Ordered;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.http.HttpMethod;
import org.springframework.http.server.reactive.ServerHttpRequest;
import org.springframework.stereotype.Component;
import org.springframework.util.LinkedMultiValueMap;
import org.springframework.util.MultiValueMap;
import org.springframework.web.server.ServerWebExchange;
import org.springframework.web.util.UriComponentsBuilder;
import reactor.core.publisher.Mono;import javax.annotation.Resource;
import java.net.URI;
import java.net.URLDecoder;
import java.net.URLEncoder;
import java.nio.charset.StandardCharsets;
import java.util.List;
import java.util.Map;
import java.util.Set;;/*** 请求整理解密-请求拦截器** @author fir*/
@Slf4j
@Component
public class DecryptionFilter implements Ordered, GlobalFilter {/*** 网关参数配置*/@Resourceprivate GlobalConfig globalConfig;@Resourceprivate RedisTemplate<String, Object> redisTemplate;@Overridepublic int getOrder() {return -280;}@Overridepublic Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {log.info("整体解密:start");ServerHttpRequest req = exchange.getRequest();String method = req.getMethodValue();boolean rsa = globalConfig.isRsa();boolean aes = globalConfig.isAes();if (rsa || aes) {// 白名单路由判断ServerHttpRequest request = exchange.getRequest();String path = request.getPath().toString();List<String> whiteUrls = globalConfig.getWhiteUrls();if(!whiteUrls.contains(path)){ServerHttpRequest builder = req.mutate().build();if (HttpMethod.GET.matches(method)) {log.info("当前请求参数为: {}", req.getQueryParams());builder = changeGet(exchange, builder);} else if (HttpMethod.POST.matches(method)) {log.info("当前请求参数为: {}", req.getQueryParams());builder = changeGet(exchange, builder);}exchange = exchange.mutate().request(builder).build();log.info("整体解密:true");}else {log.info("整体解密:true,白名单");}}else {log.info("整体解密:true,验证已关闭");}return chain.filter(exchange);}/*** 获取请求参数等信息进行过滤处理** @param exchange 请求* @param serverHttpRequest 请求* @return 处理结束的参数*/@SneakyThrowsprivate ServerHttpRequest changeGet(ServerWebExchange exchange, ServerHttpRequest serverHttpRequest) {String session = exchange.getRequest().getHeaders().getFirst("s");if (session == null) {throw new CustomException(AjaxStatus.SESSION_INVALID);}JSONObject jsonObject = (JSONObject) redisTemplate.opsForValue().get(session);if (jsonObject == null) {throw new CustomException(AjaxStatus.SESSION_EXPIRE);}ConnectDTO connectDTO = jsonObject.toJavaObject(ConnectDTO.class);String privateKey = connectDTO.getPrivateKey();String secretKey = connectDTO.getSecretKey();// 获取原参数URI uri = serverHttpRequest.getURI();String originalQuery = uri.getRawQuery();String decodedQuery = null;if(StringUtils.isNotBlank(originalQuery)){decodedQuery = URLDecoder.decode(originalQuery, "UTF-8");}// 更改参数MultiValueMap<String, String> newQueryParams = new LinkedMultiValueMap<>();if (StringUtils.isNotBlank(originalQuery) && org.springframework.util.StringUtils.hasText(decodedQuery)) {// 修改请求参数,String[] array只能处理前端特定加密 {data:加密内容的形式}, 传递到后端,会变更为 data=加密内容。// 除此以外的所有方式不能通过本方法进行解密String[] array = decodedQuery.split("=");if (array.length > 1) {decodedQuery = array[1];if (decodedQuery != null) {boolean rsa = globalConfig.isRsa();boolean aes = globalConfig.isAes();if (rsa) {// 对数据进行非对称解密originalQuery = RSAUtils.decryptSection(decodedQuery, privateKey);}if (aes) {// 对数据进行对称解密originalQuery = AESUtils.decrypt(originalQuery, secretKey);}}Map<String, Object> dataMap = JSONObject.parseObject(originalQuery, Map.class);if (dataMap != null) {Set<String> strings = dataMap.keySet();for (String key : strings) {String encodedString = URLEncoder.encode(dataMap.get(key).toString(), StandardCharsets.UTF_8.toString());newQueryParams.add(key, encodedString);}}}}// 替换查询参数URI newUri = UriComponentsBuilder.fromUri(uri).query(null).queryParams(newQueryParams).build(true).toUri();ServerHttpRequest request = exchange.getRequest().mutate().uri(newUri).build();// 将解密后的参数重新设置到请求中uri = request.getURI();log.info("更改后的当前请求参数为: {}", uri.getRawQuery());return request;}
}
前端
请求拦截器
添加整体加密逻辑
// 请求整体加密
if (AESKey) {const secretKey = this.get("secretKey");let date = JSON.stringify(request.params);date = this.encryptAES(date, secretKey);request.params = {"data": date};
}
if (AESKey && RSAKey) {const serverPublicKey = this.get("serverPublicKey");let date = request.params.data;date = this.rsaEncrypt(date, serverPublicKey);request.params = {"data": date};
} else if (RSAKey) {const serverPublicKey = this.get("serverPublicKey");let date = JSON.stringify(request.params);date = this.rsaEncrypt(date, serverPublicKey);request.params = {"data": date};
}
请求头中添加sessionId
let s = this.get("sessionId")// 请求中增加会话信息if (s) {request.headers.s = s;}
响应拦截器
添加整体解密逻辑
修改一下内容:
return securityUtils.gatewayResponse(response);
securityUtils.js中添加响应处理函数
/*** gateway网关验证信息处理(响应头)*/
gatewayResponse(response) {let key = true;// 放置业务逻辑代码// response是服务器端返回来的数据信息,与Promise获得数据一致let data = response.data// config包含请求信息let config = response.config// 判断 data 是否为对象if (typeof data === 'object' && data !== null) {// 判断 data 是否匹配特定格式if (Object.prototype.hasOwnProperty.call(data, 'msg') &&Object.prototype.hasOwnProperty.call(data, 'code') &&typeof data.msg === 'string' &&typeof data.code === 'number') {// 数据匹配特定格式if (data.code === 401) {sessionStorage.clear()}return data;}}// 获取当前请求的urllet url = config.urlwhiteList.find(function (value) {if (value === url) {key = false;}});// 对非白名单数据进行整体解密处理if (key) {// 获取加密密钥,并传入解密组件进行解密if (RSAKey) {const privateKey = this.get("privateKey")data = this.rsaDecrypt(data, privateKey)}if (AESKey) {let securityKey = this.get("secretKey")data = this.decryptAES(data, securityKey)}if (data != null && data !== "") {data = JSON.parse(data);}else {data = new Promise(() => {});}}return data;
},
相关文章:

[Spring Cloud] (6)gateway整体加解密
文章目录 简述整体效果后端增加配置nacos增加配置GlobalConfig 添加请求整体解密拦截器DecryptionFilter添加响应整体解密拦截器EncryptionFilter 前端请求拦截器添加整体加密逻辑请求头中添加sessionId 响应拦截器添加整体解密逻辑 简述 本文网关gateway,微服务&a…...
RUST编程语言入门基础2024
庄晓立,2024年3月。 Rust简介 A language empowering everyone to build reliable and efficient software. Rust编程语言赋能所有人开发高可靠且高性能的软件。 性能 Rust is blazingly fast and memory-efficient: with no runtime or garbage collector, it can…...

Linux进程控制——Linux进程终止
前言:前面了解完前面的Linux进程基础概念后,我们算是解决了Linux进程中的一大麻烦,现在我们准备更深入的了解Linux进程——Linux进程控制! 我们主要介绍的Linux进程控制内容包括:进程终止,进程等待与替换&a…...

利用IP地址查询解决被“薅羊毛”的方法
在互联网时代,随着各种网络诈骗手段的不断更新和演变,“薅羊毛”成为了一种常见的网络犯罪行为。其中,利用查询IP地址进行欺诈活动已经成为一种普遍的手段。当个人或组织的IP地址被不法分子查询后,可能会面临虚假注册、盗取个人信…...

Tomcat7+ 弱口令 后台getshell漏洞
1 漏洞背景 Tomcat 是一个流行的开源Web应用服务器,用于部署和运行Java Web应用程序。Tomcat 7 版本中存在一个安全隐患,即默认的管理员密码可能较弱或者未被修改,攻击者可以利用这一漏洞登录到Tomcat的管理后台,并上传恶意的WAR…...

香港虚拟主机哪里可以试用?用于企业建站的
香港虚拟主机适合个人、企业建站,包括外贸企业网站、个人博客网站、中小企业官网等,那么作为新手不知道哪家香港虚拟主机好用的时候,该如何找到可以试用的香港虚拟主机呢? 香港虚拟主机也称作香港空间、香港虚拟空间,…...
C# 集合(四) —— Set类
总目录 C# 语法总目录 集合四 Set 1. Set 1. Set 有 HashSet 和 SortedSet, 它们都不包含重复元素忽略添加重复值的请求无法根据位置访问元素使用Contains方法均使用散列查找,所以速度快 SortedSet 按照一定顺序保存元素,使用红黑树实现&a…...

C#实现多线程的几种方式
前言 多线程是C#中一个重要的概念,多线程指的是在同一进程中同时运行多个线程的机制。多线程适用于需要提高系统并发性、吞吐量和响应速度的场景,可以充分利用多核处理器和系统资源,提高应用程序的性能和效率。 多线程常用场景 CPU 密集型任务…...

C语言—控制语句
控制语句就是用来实现对流程的选择、循环、转向和返回等控制行为。 分支语句 if语句 基本结构 if(表达式) { 语句块1; } else { 语句块2; } 执行顺序: 如果表达式判断成立(即表达式为真),则执行语句块…...

三. TensorRT基础入门-ONNX注册算子的方法
目录 前言0. 简述1. 执行一下我们的python程序2.转换swin-tiny时候出现的不兼容op的例子3. 当出现导出onnx不成功的时候,我们需要考虑的事情4. unsupported asinh算子5. unsupported deformable conv算子总结参考 前言 自动驾驶之心推出的 《CUDA与TensorRT部署实战…...

01、什么是ip、协议、端口号知道吗?计算机网络通信的组成是什么?
声明:本教程不收取任何费用,欢迎转载,尊重作者劳动成果,不得用于商业用途,侵权必究!!! 目录 前言 计算机网络 网络ip地址 网络协议 网络端口号 前言 最近有个项目要用到相关文章…...
答题套路2 阅读理解 说明文某个词是否能去掉
观点 回答:不能 解词 某个词什么意思需要解释一下 反证法 如果去掉了,会怎么样 总结 使用这个词体现了说明文的科学性,严谨性...
Pytorch图像分类模型模型实时在线验证代码
1.训练并保存自己的模型 保存的模型格式为:XXX.pth torch.save(model, "./weight/last.pth")if best_acc <(validation_acc / len_val):torch.save(model, "./weight/best.pth")2.转化为ONNX格式 2.1环境安装(window10&#x…...
Java高并发场景(银行转账问题)
最近面试问到了银行转账的高并发问题,回答的不是很理想,小编整理了下,题目大概如下: 有一张银行账号表(银行账号字段、金额字段),A账号要给B账号转账,A扣款,B收款&#x…...
TypeScript 工具类型
这些工具类型是 TypeScript 提供的强大功能,用于操作和转换类型。下面是每个工具类型的简要说明和示例: 1、Record let value: Record<string, any> { name: "", age: 0, desc: [] }; let value2: { [key: string]: any } { name: &…...

[Kotlin]创建一个私有包并使用
1.创建Kotlin测试项目 在Android Studio或其他IDE中选择“Create New Project”。选择Kotlin和Gradle作为项目类型和构建系统。指定项目名称和位置,完成设置。 2.创建Android Library模块 官方文档:创建 Android 库 | Android Studio | Android De…...
鸿蒙应用开发者高级认证指南及参考资料整理(含详细参考答案)
如何报名鸿蒙应用开发者高级认证 报名链接:点击这里进行报名。报名步骤: 点击上述链接进入报名页面。选择“立即报名”。在课程内容中找到“HarmonyOS应用开发者高级认证”,点击进入。点击“参加考试”,随后即可开始考试。考试注意事项 实名认证:考试前,请务必完成实名认…...
数据匿名化技术
不同的数据匿名化技术可用于多种行业,旨在从数据流中获取有用的见解,同时确保满足数据保护标准和法规的合规要求。 数据脱敏(Data Masking) 数据脱敏,又称数据漂白、数据去隐私化或数据变形,指的是对数据集…...
HTML学习笔记汇总
整理一些常见问题的Links,不定期更新。 Html生成自定义函数的图形(2024/5/10)-CSDN博客 HTML中插入图片(2024/5/10)-CSDN博客 Html给网页加icon图标_html icon-CSDN博客...
初始JSVMP
1.初始JSVMP JSVMP是"JavaScript Virtual Machine Protection"的缩写,是一种前端代码虚拟化保护技术。它的核心思想是在JavaScript代码保护过程中引入代码虚拟化,将目标代码转换成自定义的字节码,这些字节码只有特殊的解释器才能识…...

LBE-LEX系列工业语音播放器|预警播报器|喇叭蜂鸣器的上位机配置操作说明
LBE-LEX系列工业语音播放器|预警播报器|喇叭蜂鸣器专为工业环境精心打造,完美适配AGV和无人叉车。同时,集成以太网与语音合成技术,为各类高级系统(如MES、调度系统、库位管理、立库等)提供高效便捷的语音交互体验。 L…...

突破不可导策略的训练难题:零阶优化与强化学习的深度嵌合
强化学习(Reinforcement Learning, RL)是工业领域智能控制的重要方法。它的基本原理是将最优控制问题建模为马尔可夫决策过程,然后使用强化学习的Actor-Critic机制(中文译作“知行互动”机制),逐步迭代求解…...
k8s从入门到放弃之Ingress七层负载
k8s从入门到放弃之Ingress七层负载 在Kubernetes(简称K8s)中,Ingress是一个API对象,它允许你定义如何从集群外部访问集群内部的服务。Ingress可以提供负载均衡、SSL终结和基于名称的虚拟主机等功能。通过Ingress,你可…...
FastAPI 教程:从入门到实践
FastAPI 是一个现代、快速(高性能)的 Web 框架,用于构建 API,支持 Python 3.6。它基于标准 Python 类型提示,易于学习且功能强大。以下是一个完整的 FastAPI 入门教程,涵盖从环境搭建到创建并运行一个简单的…...
大语言模型如何处理长文本?常用文本分割技术详解
为什么需要文本分割? 引言:为什么需要文本分割?一、基础文本分割方法1. 按段落分割(Paragraph Splitting)2. 按句子分割(Sentence Splitting)二、高级文本分割策略3. 重叠分割(Sliding Window)4. 递归分割(Recursive Splitting)三、生产级工具推荐5. 使用LangChain的…...
大模型多显卡多服务器并行计算方法与实践指南
一、分布式训练概述 大规模语言模型的训练通常需要分布式计算技术,以解决单机资源不足的问题。分布式训练主要分为两种模式: 数据并行:将数据分片到不同设备,每个设备拥有完整的模型副本 模型并行:将模型分割到不同设备,每个设备处理部分模型计算 现代大模型训练通常结合…...
CMake控制VS2022项目文件分组
我们可以通过 CMake 控制源文件的组织结构,使它们在 VS 解决方案资源管理器中以“组”(Filter)的形式进行分类展示。 🎯 目标 通过 CMake 脚本将 .cpp、.h 等源文件分组显示在 Visual Studio 2022 的解决方案资源管理器中。 ✅ 支持的方法汇总(共4种) 方法描述是否推荐…...

【开发技术】.Net使用FFmpeg视频特定帧上绘制内容
目录 一、目的 二、解决方案 2.1 什么是FFmpeg 2.2 FFmpeg主要功能 2.3 使用Xabe.FFmpeg调用FFmpeg功能 2.4 使用 FFmpeg 的 drawbox 滤镜来绘制 ROI 三、总结 一、目的 当前市场上有很多目标检测智能识别的相关算法,当前调用一个医疗行业的AI识别算法后返回…...

学习STC51单片机32(芯片为STC89C52RCRC)OLED显示屏2
每日一言 今天的每一份坚持,都是在为未来积攒底气。 案例:OLED显示一个A 这边观察到一个点,怎么雪花了就是都是乱七八糟的占满了屏幕。。 解释 : 如果代码里信号切换太快(比如 SDA 刚变,SCL 立刻变&#…...
Typeerror: cannot read properties of undefined (reading ‘XXX‘)
最近需要在离线机器上运行软件,所以得把软件用docker打包起来,大部分功能都没问题,出了一个奇怪的事情。同样的代码,在本机上用vscode可以运行起来,但是打包之后在docker里出现了问题。使用的是dialog组件,…...