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

基于SpringBoot IP黑白名单的实现

业务场景

IP黑白名单是网络安全管理中常见的策略工具,用于控制网络访问权限,根据业务场景的不同,其应用范围广泛,以下是一些典型业务场景:

  1. 服务器安全防护

    • 黑名单:可以用来阻止已知的恶意IP地址或曾经尝试攻击系统的IP地址,防止这些来源对服务器进行未经授权的访问、扫描、攻击等行为。
    • 白名单:仅允许特定IP或IP段访问关键服务,比如数据库服务器、内部管理系统等,实现最小授权原则,降低被未知风险源入侵的可能性。
  2. 网站安全防护

    • 黑名单:对于频繁发起恶意请求、爬取数据、DDoS攻击等活动的IP,将其加入黑名单以限制其对网站的访问。
    • 白名单:如果只希望特定合作伙伴、内部员工或特定区域用户访问网站内容,则可通过白名单来限定合法访问者的范围。
  3. API接口保护

    • 对于对外提供的API接口,通过设置IP黑白名单,确保只有经过认证或信任的系统和客户端才能调用接口。

比如比较容易被盗刷的短信接口、文件接口,都需要添加IP黑白名单加以限制。

核心实现

获取客户端IP地址

@UtilityClass
public class IpUtils {private final String UNKNOWN = "unknown";private final String X_FORWARDED_FOR = "X-Forwarded-For";private final String PROXY_CLIENT_IP = "Proxy-Client-IP";private final String WL_PROXY_CLIENT_IP = "WL-Proxy-Client-IP";private final Pattern COMMA_SEPARATED_VALUES_PATTERN = Pattern.compile("\s*,\s*");/*** 默认情况下内网代理的子网可以是(后面有需要可以进行配置):* 1. 10/8* 2. 192.168/16* 3. 169.254/16* 4. 127/8* 5. 172.16/12* 6. ::1*/private final Pattern INTERNAL_PROXIES = Pattern.compile("10\.\d{1,3}\.\d{1,3}\.\d{1,3}|" +"192\.168\.\d{1,3}\.\d{1,3}|" +"169\.254\.\d{1,3}\.\d{1,3}|" +"127\.\d{1,3}\.\d{1,3}\.\d{1,3}|" +"172\.1[6-9]\.\d{1,3}\.\d{1,3}|" +"172\.2[0-9]\.\d{1,3}\.\d{1,3}|" +"172\.3[0-1]\.\d{1,3}\.\d{1,3}|" +"0:0:0:0:0:0:0:1|::1");/*** 获取请求的IP** @return 请求的IP*/public String getIp() {var requestAttributes = RequestContextHolder.getRequestAttributes();if (Objects.isNull(requestAttributes)) {return null;}var request = ((ServletRequestAttributes) requestAttributes).getRequest();var ip = getRemoteIp(request);if (!StringUtils.hasLength(ip) || UNKNOWN.equalsIgnoreCase(ip)) {ip = request.getHeader(PROXY_CLIENT_IP);}if (!StringUtils.hasLength(ip) || UNKNOWN.equalsIgnoreCase(ip)) {ip = request.getHeader(WL_PROXY_CLIENT_IP);}if (!StringUtils.hasLength(ip) || UNKNOWN.equalsIgnoreCase(ip)) {ip = request.getRemoteAddr();}return ip;}/*** 获取客户端真实IP地址,防止使用X-Forwarded-For进行IP伪造攻击,防御思路见类注释** @return 真实IP地址*/private String getRemoteIp(HttpServletRequest request) {var remoteIp = request.getRemoteAddr();var isInternal = INTERNAL_PROXIES.matcher(remoteIp).matches();if (isInternal) {var concatRemoteIpHeaderValue = new StringBuilder();for (var e = request.getHeaders(X_FORWARDED_FOR); e.hasMoreElements(); ) {if (concatRemoteIpHeaderValue.length() > 0) {concatRemoteIpHeaderValue.append(", ");}concatRemoteIpHeaderValue.append(e.nextElement());}var remoteIpHeaderValue = commaDelimitedListToArray(concatRemoteIpHeaderValue.toString());for (var i = remoteIpHeaderValue.length - 1; i >= 0; i--) {var currentRemoteIp = remoteIpHeaderValue[i];if (!INTERNAL_PROXIES.matcher(currentRemoteIp).matches()) {return currentRemoteIp;}}return null;} else {return remoteIp;}}private String[] commaDelimitedListToArray(String commaDelimitedStrings) {return (commaDelimitedStrings == null || commaDelimitedStrings.isEmpty())? new String[0]: COMMA_SEPARATED_VALUES_PATTERN.split(commaDelimitedStrings);}
}

获取到客户端IP后,我们只要比对客户端IP是否在配置的白名单/黑名单中即可。
为了简化使用,可以采用注解的方式进行拦截。

新增注解@IpCheck

/*** IP白名单校验*/
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.METHOD})
@Documented
@Inherited
public @interface IpCheck {/*** 白名单IP列表,支持${...}*/@AliasFor("whiteList")String value() default "";/*** 白名单IP列表,支持${...}*/@AliasFor("value")String whiteList() default "";/*** 黑名单IP列表,支持${...}*/String blackList() default "";}

新增IpCheckHandlerInterceptorImpl

我们实现HandlerInterceptor,在接口上进行拦截,如果不满足配置的黑白名单,则抛出异常。

/*** @author <a href="mailto:gcwm99@gmail.com">gcdd1993</a>* Created by gcdd1993 on 2023/9/20*/
@Component
public class IpCheckHandlerInterceptorImpl implements HandlerInterceptor, EmbeddedValueResolverAware {private StringValueResolver stringValueResolver;@Overridepublic boolean preHandle(@NonNull HttpServletRequest request,@NonNull HttpServletResponse response,@NonNull Object handler) {// 检查是否有IpWhitelistCheck注解,并且是否开启IP白名单检查if (!(handler instanceof HandlerMethod)) {return true;  // 如果没有注解或者注解中关闭了IP白名单检查,则继续处理请求}var handlerMethod = (HandlerMethod) handler;var method = handlerMethod.getMethod();var annotation = AnnotationUtils.getAnnotation(method, IpCheck.class);if (annotation == null) {return true;}var clientIp = IpUtils.getIp();// 检查客户端IP是否在白名单中var whiteList = Stream.of(Optional.ofNullable(stringValueResolver.resolveStringValue(annotation.whiteList())).map(it -> it.split(",")).orElse(new String[]{})).filter(StringUtils::hasText).map(String::trim).collect(Collectors.toUnmodifiableSet());if (!whiteList.isEmpty() && whiteList.contains(clientIp)) {return true; // IP在白名单中,继续处理请求}var blackList = Stream.of(Optional.ofNullable(stringValueResolver.resolveStringValue(annotation.blackList())).map(it -> it.split(",")).orElse(new String[]{})).filter(StringUtils::hasText).map(String::trim).collect(Collectors.toUnmodifiableSet());if (!blackList.isEmpty() && !blackList.contains(clientIp)) {return true; // IP不在黑名单中,继续处理请求}// IP不在白名单中,可以返回错误响应或者抛出异常// 例如,返回一个 HTTP 403 错误throw new RuntimeException("Access denied, remote ip " + clientIp + " is not allowed.");}@Overridepublic void setEmbeddedValueResolver(StringValueResolver resolver) {this.stringValueResolver = resolver;}
}

自动装配

核心逻辑写完了,该怎么使用呢?为了达到开箱即用的效果,我们可以接着新增自动装配的代码

新建IpCheckConfig

实现WebMvcConfigurer接口,添加接口拦截器

/*** @author <a href="mailto:gcwm99@gmail.com">gcdd1993</a>* Created by gcdd1993 on 2024/1/24*/
public class IpCheckConfig implements WebMvcConfigurer {@Resourceprivate IpCheckHandlerInterceptorImpl ipCheckHandlerInterceptor;@Overridepublic void addInterceptors(InterceptorRegistry registry) {registry.addInterceptor(ipCheckHandlerInterceptor);}}

新建@EnableIpCheck

参考@EnableScheduling的实现,自己实现一个@EnableIpCheck,该注解可以控制功能是否启用

/*** @author <a href="mailto:gcwm99@gmail.com">gcdd1993</a>* Created by gcdd1993 on 2024/1/24*/
@Retention(value = RetentionPolicy.RUNTIME)
@Target(value = ElementType.TYPE)
@Documented
@ComponentScan("xxx.ip") // 这里是IpCheckConfig的包名
@Import(IpCheckConfig.class)
public @interface EnableIpCheck {
}

业务测试

简单地用代码来试验下效果

新建SampleApplication

@SpringBootApplication
@EnableIpCheck
public class SampleApplication {public static void main(String[] args) {SpringApplication.run(SampleApplication.class, args);}}

新建测试接口

@RestController
@RequestMapping("/sample/ip-checker")
public class IpCheckSample {@GetMapping("/white")@IpCheck(value = "0:0:0:0:0:0:0:1")String whiteList() {return "127.0.0.1";}@GetMapping("/black")@IpCheck(blackList = "0:0:0:0:0:0:0:1")String blackList() {return "127.0.0.1";}/*** 同时配置白名单和黑名单,要求IP既在白名单,并且不在黑名单,否则抛出异常*/@GetMapping("/all")@IpCheck(value = "0:0:0:0:0:0:0:1", blackList = "0:0:0:0:0:0:0:1")String all() {return "127.0.0.1";}/*** 同时配置白名单和黑名单,要求IP既在白名单,并且不在黑名单,否则抛出异常* 支持解析Spring 配置文件*/@GetMapping("/config")@IpCheck(value = "${digit.ip.check.white-list}", blackList = "${digit.ip.check.black-list}")String config() {return "127.0.0.1";}/*** 同时配置白名单和黑名单,要求IP既在白名单,并且不在黑名单,否则抛出异常* 支持解析Spring 配置文件*/@GetMapping("/black-config")@IpCheck(blackList = "${digit.ip.check.black-list}")String blackConfig() {return "127.0.0.1";}}

由于本机请求IP地址是0:0:0:0:0:0:0:1,所以这里使用0:0:0:0:0:0:0:1而不是127.0.0.1

访问/sample/ip-checker/white

接口返回127.0.0.1

访问/sample/ip-checker/black

java.lang.RuntimeException: Access denied, remote ip 0:0:0:0:0:0:0:1 is not allowed.

访问/sample/ip-checker/all

接口返回127.0.0.1

  • 既配置白名单,也配置黑名单,需要既不在白名单,同时在黑名单里,才会拦截。

修改配置

digit:ip:check:white-list: 127.0.0.1, 192.168.1.1, 192.168.1.2black-list: 127.0.0.1, 192.168.1.1, 192.168.1.2,0:0:0:0:0:0:0:1

访问/sample/ip-checker/black-config

java.lang.RuntimeException: Access denied, remote ip 0:0:0:0:0:0:0:1 is not allowed.

最后,可以结合配置中心,以便配置后立即生效。

业务场景

IP黑白名单是网络安全管理中常见的策略工具,用于控制网络访问权限,根据业务场景的不同,其应用范围广泛,以下是一些典型业务场景:

  1. 服务器安全防护

    • 黑名单:可以用来阻止已知的恶意IP地址或曾经尝试攻击系统的IP地址,防止这些来源对服务器进行未经授权的访问、扫描、攻击等行为。
    • 白名单:仅允许特定IP或IP段访问关键服务,比如数据库服务器、内部管理系统等,实现最小授权原则,降低被未知风险源入侵的可能性。
  2. 网站安全防护

    • 黑名单:对于频繁发起恶意请求、爬取数据、DDoS攻击等活动的IP,将其加入黑名单以限制其对网站的访问。
    • 白名单:如果只希望特定合作伙伴、内部员工或特定区域用户访问网站内容,则可通过白名单来限定合法访问者的范围。
  3. API接口保护

    • 对于对外提供的API接口,通过设置IP黑白名单,确保只有经过认证或信任的系统和客户端才能调用接口。

比如比较容易被盗刷的短信接口、文件接口,都需要添加IP黑白名单加以限制。

核心实现

获取客户端IP地址

@UtilityClass
public class IpUtils {private final String UNKNOWN = "unknown";private final String X_FORWARDED_FOR = "X-Forwarded-For";private final String PROXY_CLIENT_IP = "Proxy-Client-IP";private final String WL_PROXY_CLIENT_IP = "WL-Proxy-Client-IP";private final Pattern COMMA_SEPARATED_VALUES_PATTERN = Pattern.compile("\s*,\s*");/*** 默认情况下内网代理的子网可以是(后面有需要可以进行配置):* 1. 10/8* 2. 192.168/16* 3. 169.254/16* 4. 127/8* 5. 172.16/12* 6. ::1*/private final Pattern INTERNAL_PROXIES = Pattern.compile("10\.\d{1,3}\.\d{1,3}\.\d{1,3}|" +"192\.168\.\d{1,3}\.\d{1,3}|" +"169\.254\.\d{1,3}\.\d{1,3}|" +"127\.\d{1,3}\.\d{1,3}\.\d{1,3}|" +"172\.1[6-9]\.\d{1,3}\.\d{1,3}|" +"172\.2[0-9]\.\d{1,3}\.\d{1,3}|" +"172\.3[0-1]\.\d{1,3}\.\d{1,3}|" +"0:0:0:0:0:0:0:1|::1");/*** 获取请求的IP** @return 请求的IP*/public String getIp() {var requestAttributes = RequestContextHolder.getRequestAttributes();if (Objects.isNull(requestAttributes)) {return null;}var request = ((ServletRequestAttributes) requestAttributes).getRequest();var ip = getRemoteIp(request);if (!StringUtils.hasLength(ip) || UNKNOWN.equalsIgnoreCase(ip)) {ip = request.getHeader(PROXY_CLIENT_IP);}if (!StringUtils.hasLength(ip) || UNKNOWN.equalsIgnoreCase(ip)) {ip = request.getHeader(WL_PROXY_CLIENT_IP);}if (!StringUtils.hasLength(ip) || UNKNOWN.equalsIgnoreCase(ip)) {ip = request.getRemoteAddr();}return ip;}/*** 获取客户端真实IP地址,防止使用X-Forwarded-For进行IP伪造攻击,防御思路见类注释** @return 真实IP地址*/private String getRemoteIp(HttpServletRequest request) {var remoteIp = request.getRemoteAddr();var isInternal = INTERNAL_PROXIES.matcher(remoteIp).matches();if (isInternal) {var concatRemoteIpHeaderValue = new StringBuilder();for (var e = request.getHeaders(X_FORWARDED_FOR); e.hasMoreElements(); ) {if (concatRemoteIpHeaderValue.length() > 0) {concatRemoteIpHeaderValue.append(", ");}concatRemoteIpHeaderValue.append(e.nextElement());}var remoteIpHeaderValue = commaDelimitedListToArray(concatRemoteIpHeaderValue.toString());for (var i = remoteIpHeaderValue.length - 1; i >= 0; i--) {var currentRemoteIp = remoteIpHeaderValue[i];if (!INTERNAL_PROXIES.matcher(currentRemoteIp).matches()) {return currentRemoteIp;}}return null;} else {return remoteIp;}}private String[] commaDelimitedListToArray(String commaDelimitedStrings) {return (commaDelimitedStrings == null || commaDelimitedStrings.isEmpty())? new String[0]: COMMA_SEPARATED_VALUES_PATTERN.split(commaDelimitedStrings);}
}

获取到客户端IP后,我们只要比对客户端IP是否在配置的白名单/黑名单中即可。
为了简化使用,可以采用注解的方式进行拦截。

新增注解@IpCheck

/*** IP白名单校验*/
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.METHOD})
@Documented
@Inherited
public @interface IpCheck {/*** 白名单IP列表,支持${...}*/@AliasFor("whiteList")String value() default "";/*** 白名单IP列表,支持${...}*/@AliasFor("value")String whiteList() default "";/*** 黑名单IP列表,支持${...}*/String blackList() default "";}

新增IpCheckHandlerInterceptorImpl

我们实现HandlerInterceptor,在接口上进行拦截,如果不满足配置的黑白名单,则抛出异常。

/*** @author <a href="mailto:gcwm99@gmail.com">gcdd1993</a>* Created by gcdd1993 on 2023/9/20*/
@Component
public class IpCheckHandlerInterceptorImpl implements HandlerInterceptor, EmbeddedValueResolverAware {private StringValueResolver stringValueResolver;@Overridepublic boolean preHandle(@NonNull HttpServletRequest request,@NonNull HttpServletResponse response,@NonNull Object handler) {// 检查是否有IpWhitelistCheck注解,并且是否开启IP白名单检查if (!(handler instanceof HandlerMethod)) {return true;  // 如果没有注解或者注解中关闭了IP白名单检查,则继续处理请求}var handlerMethod = (HandlerMethod) handler;var method = handlerMethod.getMethod();var annotation = AnnotationUtils.getAnnotation(method, IpCheck.class);if (annotation == null) {return true;}var clientIp = IpUtils.getIp();// 检查客户端IP是否在白名单中var whiteList = Stream.of(Optional.ofNullable(stringValueResolver.resolveStringValue(annotation.whiteList())).map(it -> it.split(",")).orElse(new String[]{})).filter(StringUtils::hasText).map(String::trim).collect(Collectors.toUnmodifiableSet());if (!whiteList.isEmpty() && whiteList.contains(clientIp)) {return true; // IP在白名单中,继续处理请求}var blackList = Stream.of(Optional.ofNullable(stringValueResolver.resolveStringValue(annotation.blackList())).map(it -> it.split(",")).orElse(new String[]{})).filter(StringUtils::hasText).map(String::trim).collect(Collectors.toUnmodifiableSet());if (!blackList.isEmpty() && !blackList.contains(clientIp)) {return true; // IP不在黑名单中,继续处理请求}// IP不在白名单中,可以返回错误响应或者抛出异常// 例如,返回一个 HTTP 403 错误throw new RuntimeException("Access denied, remote ip " + clientIp + " is not allowed.");}@Overridepublic void setEmbeddedValueResolver(StringValueResolver resolver) {this.stringValueResolver = resolver;}
}

自动装配

核心逻辑写完了,该怎么使用呢?为了达到开箱即用的效果,我们可以接着新增自动装配的代码

新建IpCheckConfig

实现WebMvcConfigurer接口,添加接口拦截器

/*** @author <a href="mailto:gcwm99@gmail.com">gcdd1993</a>* Created by gcdd1993 on 2024/1/24*/
public class IpCheckConfig implements WebMvcConfigurer {@Resourceprivate IpCheckHandlerInterceptorImpl ipCheckHandlerInterceptor;@Overridepublic void addInterceptors(InterceptorRegistry registry) {registry.addInterceptor(ipCheckHandlerInterceptor);}}

新建@EnableIpCheck

参考@EnableScheduling的实现,自己实现一个@EnableIpCheck,该注解可以控制功能是否启用

/*** @author <a href="mailto:gcwm99@gmail.com">gcdd1993</a>* Created by gcdd1993 on 2024/1/24*/
@Retention(value = RetentionPolicy.RUNTIME)
@Target(value = ElementType.TYPE)
@Documented
@ComponentScan("xxx.ip") // 这里是IpCheckConfig的包名
@Import(IpCheckConfig.class)
public @interface EnableIpCheck {
}

业务测试

简单地用代码来试验下效果

新建SampleApplication

@SpringBootApplication
@EnableIpCheck
public class SampleApplication {public static void main(String[] args) {SpringApplication.run(SampleApplication.class, args);}}

新建测试接口

@RestController
@RequestMapping("/sample/ip-checker")
public class IpCheckSample {@GetMapping("/white")@IpCheck(value = "0:0:0:0:0:0:0:1")String whiteList() {return "127.0.0.1";}@GetMapping("/black")@IpCheck(blackList = "0:0:0:0:0:0:0:1")String blackList() {return "127.0.0.1";}/*** 同时配置白名单和黑名单,要求IP既在白名单,并且不在黑名单,否则抛出异常*/@GetMapping("/all")@IpCheck(value = "0:0:0:0:0:0:0:1", blackList = "0:0:0:0:0:0:0:1")String all() {return "127.0.0.1";}/*** 同时配置白名单和黑名单,要求IP既在白名单,并且不在黑名单,否则抛出异常* 支持解析Spring 配置文件*/@GetMapping("/config")@IpCheck(value = "${digit.ip.check.white-list}", blackList = "${digit.ip.check.black-list}")String config() {return "127.0.0.1";}/*** 同时配置白名单和黑名单,要求IP既在白名单,并且不在黑名单,否则抛出异常* 支持解析Spring 配置文件*/@GetMapping("/black-config")@IpCheck(blackList = "${digit.ip.check.black-list}")String blackConfig() {return "127.0.0.1";}}

由于本机请求IP地址是0:0:0:0:0:0:0:1,所以这里使用0:0:0:0:0:0:0:1而不是127.0.0.1

访问/sample/ip-checker/white

接口返回127.0.0.1

访问/sample/ip-checker/black

java.lang.RuntimeException: Access denied, remote ip 0:0:0:0:0:0:0:1 is not allowed.

访问/sample/ip-checker/all

接口返回127.0.0.1

  • 既配置白名单,也配置黑名单,需要既不在白名单,同时在黑名单里,才会拦截。

修改配置

digit:ip:check:white-list: 127.0.0.1, 192.168.1.1, 192.168.1.2black-list: 127.0.0.1, 192.168.1.1, 192.168.1.2,0:0:0:0:0:0:0:1

访问/sample/ip-checker/black-config

java.lang.RuntimeException: Access denied, remote ip 0:0:0:0:0:0:0:1 is not allowed.

最后,可以结合配置中心,以便配置后立即生效。

相关文章:

基于SpringBoot IP黑白名单的实现

业务场景 IP黑白名单是网络安全管理中常见的策略工具&#xff0c;用于控制网络访问权限&#xff0c;根据业务场景的不同&#xff0c;其应用范围广泛&#xff0c;以下是一些典型业务场景&#xff1a; 服务器安全防护&#xff1a; 黑名单&#xff1a;可以用来阻止已知的恶意IP地…...

Redis客户端之Redisson(二)Redisson分布式锁

一、原理&#xff1a; Redisson并没有通过setNx命令来实现加锁&#xff0c;而是基于 Redis 看⻔狗机制&#xff0c;自己实现了一套分布式锁逻辑。 1、加锁机制&#xff1a; 二、使用方法&#xff1a;...

掌握大语言模型技术: 推理优化

掌握大语言模型技术_推理优化 堆叠 Transformer 层来创建大型模型可以带来更好的准确性、少样本学习能力&#xff0c;甚至在各种语言任务上具有接近人类的涌现能力。 这些基础模型的训练成本很高&#xff0c;并且在推理过程中可能会占用大量内存和计算资源&#xff08;经常性成…...

git如何导出提交记录及修改的文件清单?

导出git提交日志及修改文件 # 所有人的提交记录 git log --pretty=format:"%ai,%an:%s" --since="10 day ago" >> ~/Desktop/commit10.log#某一个人的提交记录 git log --pretty=format:"%ai,%an:%s" --since="30 day ago" |...

从零开始:Ubuntu Server中MySQL 8.0的安装与Django数据库配置详解

Ubuntu系统纯净安装MySQL8.0 1、安装Mysql8.0 sudo apt install mysql-server2、检查MySQL状态 sudo systemctl status mysql如下所示看见Active: active (running)说明mysql状态正常 ● mysql.service - MySQL Community ServerLoaded: loaded (/lib/systemd/system/mysql…...

Vue基础知识

Vue Vue基础知识 v-bind:动态绑定属性值 Vue 修改&#xff0c;标签内也修改 在methods 中可以定义很多函数 在 data 中可以定义很多变量 v-if / v-show&#xff1a;对符合条件的元素进行展示 v-for:把数据遍历出现在网页中 案例 <!DOCTYPE html><html lang"e…...

瀑布流布局 (初版)

瀑布流布局 文章目录 瀑布流布局前言1. 背景2. 点⬇️&#x1f517;去体验效果如下图所示&#xff1a; 一、初版waterfall布局和问题暴露&#xff1f;1.效果图如下&#xff1a;2.暴露问题如下图所示&#xff1a;第一张问题图&#xff1a;第二张问题图&#xff1a; 3.HTML代码如…...

硕士毕业论文写作笔记

一、写作顺序 1.标题、研究问题、研究方法 2.文献综述&#xff08;占比1/5-1/6&#xff09; 3.论证章节 4.结论、不足、启示 5.处理图表、参考文献的格式 6.绪论或引言 7.摘要、关键词 8.查重、装订 http://【硕士毕业论文写不下去&#xff0c;多亏听了张博士的论文写…...

成本更低、更可控,云原生可观测新计费模式正式上线

云布道师 在上云开始使用云产品过程中&#xff0c;企业一定遇见过两件“讨厌”事&#xff1a; 难以理解的复杂计费逻辑&#xff0c;时常冒出“这也能收费”的感叹&#xff1b; 某个配置参数调节之后&#xff0c;云产品使用成本不可预估的暴涨。 可观测作为企业 IT 运维必须品…...

5.列表选择弹窗(BottomListPopup)

愿你出走半生,归来仍是少年&#xff01; 环境&#xff1a;.NET 7、MAUI 从底部弹出的列表选择弹窗。 1.布局 <?xml version"1.0" encoding"utf-8" ?> <toolkit:Popup xmlns"http://schemas.microsoft.com/dotnet/2021/maui"xmlns…...

(十三)Head first design patterns原型模式(c++)

原型模式 原型模式就是就是对对象的克隆。有一些私有变量外界难以访问&#xff0c;而原型模式可以做到对原型一比一的复刻。 其关键代码为下面的clone方法。此方法将本对象进行复制传递出去。 class ConcretePrototype1 : public Prototype{ public:ConcretePrototype1(stri…...

Python基础之数据库操作

一、安装第三方库PyMySQL 1、在PyCharm中通过 【File】-【setting】-【Python Interpreter】搜索 PyMySQL进行安装 2、通过PyCharm中的 Terminal 命令行 输入: pip install PyMySQL 注&#xff1a;通过pip安装&#xff0c;可能会提示需要更新pip&#xff0c;这时可执行&#…...

redis-发布缓存

一.redis的发布订阅 什么 是发布和订阅 Redis 发布订阅 (pub/sub) 是一种消息通信模式&#xff1a;发送者 (pub) 发送消息&#xff0c;订阅者 (sub) 接收消息。 Redis 客户端可以订阅任意数量的频道。 Redis的发布和订阅 客户端订阅频道发布的消息 频道发布消息 订阅者就可…...

Stata17安装教程

文章目录 **Stata17安装教程**前言系统要求Windows&#xff1a;macOS&#xff1a;Linux&#xff1a; 软件下载正式安装1.下载Stata 17安装包2.双击Stata17.exe开启安装3.接受同意条款&#xff0c;然后继续安装4.选择想要安装的版本&#xff0c;Stata BE为基础版、Stata SE为特别…...

Java PDFBox 提取页数、PDF转图片

PDF 提取 使用Apache 的pdfbox组件对PDF文件解析读取和转图片。 Maven 依赖 导入下面的maven依赖&#xff1a; <dependency><groupId>org.apache.pdfbox</groupId><artifactId>pdfbox</artifactId><version>2.0.30</version> &l…...

【代码随想录14】104.二叉树的最大深度 111.二叉树的最小深度 222.完全二叉树的节点个数

目录 104.二叉树的最大深度题目描述参考代码 111.二叉树的最小深度题目描述参考代码 222.完全二叉树的节点个数题目描述参考代码 104.二叉树的最大深度 题目描述 给定一个二叉树 root &#xff0c;返回其最大深度。 二叉树的 最大深度 是指从根节点到最远叶子节点的最长路径…...

AWS 专题学习 P10 (Databases、 Data Analytics)

文章目录 专题总览1. Databases1.1 选择合适的数据库1.2 数据库类型1.3 AWS 数据库服务概述Amazon RDSAmazon AuroraAmazon ElastiCacheAmazon DynamoDBAmazon S3DocumentDBAmazon NeptuneAmazon Keyspaces (for Apache Cassandra)Amazon QLDBAmazon Timestream 2. Data & …...

一键拥有你的GPT4

这几天我一直在帮朋友升级ChatGPT&#xff0c;现在已经可以闭眼操作了哈哈&#x1f61d;。我原本以为大家都已经用上GPT4&#xff0c;享受着它带来的巨大帮助时&#xff0c;但结果还挺让我吃惊的&#xff0c;还是有很多人仍苦于如何进行升级。所以就想着写篇教程来教会大家如何…...

幻兽帕鲁服务器数据备份

搭建幻兽帕鲁个人服务器&#xff0c;最近不少用户碰到内存不足、游戏坏档之类的问题。做好定时备份&#xff0c;才能轻松快速恢复游戏进度 这里讲一下如何定时将服务器数据备份到腾讯云轻量对象存储服务&#xff0c;以及如何在有需要的时候进行数据恢复。服务器中间的数据迁移…...

【Digester解析XML文件的三种方式】

Digester解析XML文件的三种方式 1. Digester解析XML文件的三种方式1.1 作用及依赖jar包 2. 重点和难点3. XML文件4. 通过不同的方式解析这个xml文件4.1 通过java编码方式解析&#xff08;javabean存储&#xff09;4.2 通过java编码方式解析&#xff08;list和map存储&#xff0…...

地震勘探——干扰波识别、井中地震时距曲线特点

目录 干扰波识别反射波地震勘探的干扰波 井中地震时距曲线特点 干扰波识别 有效波&#xff1a;可以用来解决所提出的地质任务的波&#xff1b;干扰波&#xff1a;所有妨碍辨认、追踪有效波的其他波。 地震勘探中&#xff0c;有效波和干扰波是相对的。例如&#xff0c;在反射波…...

Appium+python自动化(十六)- ADB命令

简介 Android 调试桥(adb)是多种用途的工具&#xff0c;该工具可以帮助你你管理设备或模拟器 的状态。 adb ( Android Debug Bridge)是一个通用命令行工具&#xff0c;其允许您与模拟器实例或连接的 Android 设备进行通信。它可为各种设备操作提供便利&#xff0c;如安装和调试…...

黑马Mybatis

Mybatis 表现层&#xff1a;页面展示 业务层&#xff1a;逻辑处理 持久层&#xff1a;持久数据化保存 在这里插入图片描述 Mybatis快速入门 ![在这里插入图片描述](https://i-blog.csdnimg.cn/direct/6501c2109c4442118ceb6014725e48e4.png //logback.xml <?xml ver…...

shell脚本--常见案例

1、自动备份文件或目录 2、批量重命名文件 3、查找并删除指定名称的文件&#xff1a; 4、批量删除文件 5、查找并替换文件内容 6、批量创建文件 7、创建文件夹并移动文件 8、在文件夹中查找文件...

解决Ubuntu22.04 VMware失败的问题 ubuntu入门之二十八

现象1 打开VMware失败 Ubuntu升级之后打开VMware上报需要安装vmmon和vmnet&#xff0c;点击确认后如下提示 最终上报fail 解决方法 内核升级导致&#xff0c;需要在新内核下重新下载编译安装 查看版本 $ vmware -v VMware Workstation 17.5.1 build-23298084$ lsb_release…...

[10-3]软件I2C读写MPU6050 江协科技学习笔记(16个知识点)

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16...

OPenCV CUDA模块图像处理-----对图像执行 均值漂移滤波(Mean Shift Filtering)函数meanShiftFiltering()

操作系统&#xff1a;ubuntu22.04 OpenCV版本&#xff1a;OpenCV4.9 IDE:Visual Studio Code 编程语言&#xff1a;C11 算法描述 在 GPU 上对图像执行 均值漂移滤波&#xff08;Mean Shift Filtering&#xff09;&#xff0c;用于图像分割或平滑处理。 该函数将输入图像中的…...

云原生玩法三问:构建自定义开发环境

云原生玩法三问&#xff1a;构建自定义开发环境 引言 临时运维一个古董项目&#xff0c;无文档&#xff0c;无环境&#xff0c;无交接人&#xff0c;俗称三无。 运行设备的环境老&#xff0c;本地环境版本高&#xff0c;ssh不过去。正好最近对 腾讯出品的云原生 cnb 感兴趣&…...

Proxmox Mail Gateway安装指南:从零开始配置高效邮件过滤系统

&#x1f49d;&#x1f49d;&#x1f49d;欢迎莅临我的博客&#xff0c;很高兴能够在这里和您见面&#xff01;希望您在这里可以感受到一份轻松愉快的氛围&#xff0c;不仅可以获得有趣的内容和知识&#xff0c;也可以畅所欲言、分享您的想法和见解。 推荐&#xff1a;「storms…...

OD 算法题 B卷【正整数到Excel编号之间的转换】

文章目录 正整数到Excel编号之间的转换 正整数到Excel编号之间的转换 excel的列编号是这样的&#xff1a;a b c … z aa ab ac… az ba bb bc…yz za zb zc …zz aaa aab aac…; 分别代表以下的编号1 2 3 … 26 27 28 29… 52 53 54 55… 676 677 678 679 … 702 703 704 705;…...