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

从 Zuul 迁移到 Spring Cloud Gateway:一步步实现服务网关的升级

从 Zuul 迁移到 Spring Cloud Gateway:一步步实现服务网关的升级

    • 迁移前的准备工作
    • 迁移步骤详解
      • 第一步:查看源码
      • 第二步:启动类迁移
      • 第三步:引入 Gateway 依赖
      • 第四步 编写bootstrap.yaml
      • 第五步:替换路由配置
      • 第六步:迁移过滤器逻辑
      • 第七步:测试与调优
    • 迁移过程中常见问题及解决方案
    • 真实问题:
      • **注意事项:Nginx 转发配置的调整**
        • **问题背景**
        • **解决方法**
    • 总结

公司的项目之前使用的是Zuul,然后使用的是以前传下来的jar包,JDK1.8,spring1.*,都是比较老了,然后因为这些原因,要把Zuul替换成Gateway。
本文将详细介绍如何从 Zuul 迁移到 Gateway。

迁移前的准备工作

在开始迁移之前,需要做好以下准备:

  1. 确认现有的 Zuul 配置
    收集 Zuul 的路由配置、过滤器逻辑和插件依赖。

  2. 学习 Gateway 的基本概念
    熟悉 Gateway 的核心概念,例如:

    • Route(路由)
    • Predicate(断言)
    • Filter(过滤器)
  3. 确保系统支持响应式编程模型
    检查项目中的依赖库和代码是否与 Spring WebFlux 的非阻塞模型兼容。

  4. 升级到支持 Gateway 的 Spring Boot 版本
    确保 Spring Boot 版本 >= 2.1。


迁移步骤详解

第一步:查看源码

由于项目使用的是预先打包好的 Jar 文件,源码不可直接查看,因此需要通过反编译工具提取代码。我使用的是 jd-gui 工具,界面如图所示:

反编译工具界面

从反编译的结果可以看到,代码量相对简单,主要包含两个部分:启动类和核心过滤器。相对比较容易。

第二步:启动类迁移

原 Zuul 启动类:

@EnableZuulProxy
@SpringBootApplication
public class ZuulServerApplication {public static void main(String[] args) {(new SpringApplicationBuilder(ZuulServerApplication.class)).web(true).run(args);}@Beanpublic PathRewriteHeaderFilter customAddHeaderFilter(RouteLocator routeLocator) {return new PathRewriteHeaderFilter(routeLocator);}
}

迁移后的 Gateway 启动类:

@SpringBootApplication
@EnableDiscoveryClient
@ComponentScan(basePackages = {"com.aspire.gateway.gatewayservice"})
public class GatewayServiceApplication {public static void main(String[] args) {SpringApplication.run(GatewayServiceApplication.class, args);}
}
  • Spring Boot 2.x 后,@EnableZuulProxy 不再需要,Gateway 默认支持路由功能。
  • 由于项目的特殊需求,需要添加 @ComponentScan 手动指定 Bean 扫描路径,确保组件能够被正确加载。
  • 因为spring2之后的版本不需要再显示指定Gateway了,其实理论上只需要一个SpringBootApplication就够了,其他其实都不用。但是我这里不知道为啥,扫描不到我的bean,所以我就写了扫描当前启动类。@ComponentScan(basePackages = {"com.aspire.gateway.gatewayservice"})这里你可以换成自己的扫描包路径。

第三步:引入 Gateway 依赖

pom.xml 中移除 Zuul 相关依赖,替换为 Gateway 依赖:

以下是我使用的版本控制,就是这些版本之间是兼容的,我使用的也是这些版本。

<properties><java.version>17</java.version><project.build.sourceEncoding>UTF-8</project.build.sourceEncoding><project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding><spring-cloud.version>2024.0.0</spring-cloud.version> <!-- Spring Cloud 2024.x --><spring-cloud-alibaba.version>2022.0.0.0-RC2</spring-cloud-alibaba.version> <!-- Spring Cloud Alibaba 对应版本 --><keycloak.version>22.0.4</keycloak.version>   <!-- 非必须,我的项目需要,你不用就删掉 --></properties>
<dependency><groupId>org.springframework.cloud</groupId><artifactId>spring-cloud-starter-gateway</artifactId>
</dependency>

这一步主要就是导入你的依赖嘛。

第四步 编写bootstrap.yaml

这一块里面其实主要就是你的nacos的配置文件,反正我用的是nacos,因为Zuul是网关嘛,Gateway也是网关,然后你实际的服务和网关都是要在同一个服务发现下面的,我之前是eureka,现在是nacos,所以要在这里说明的。

spring:main:allow-circular-references: trueallow-bean-definition-overriding: trueapplication:name: rbac-gatewaycloud:nacos:username: ${ENV_CONFIG_USERNAME:nacos}password: ${ENV_CONFIG_PASSWORD:}server-addr: ${ENV_CONFIG_IP:10.*.*.*}:${ENV_CONFIG_PORT:*}# Nacos 服务发现配置discovery:enabled: true  # 启用服务发现service: ${spring.application.name}  # 使用应用名作为服务名server-addr: ${ENV_CONFIG_IP:*}:${ENV_CONFIG_PORT:*}namespace: ${NAMESPACE:*}#group: ${spring.cloud.nacos.discovery.group:*}group: *metadata:version: v1env: prod# Nacos 配置中心配置config:enabled: trueserver-addr: ${ENV_CONFIG_IP:*}:${ENV_CONFIG_PORT:*}
#        group: ${spring.cloud.nacos.discovery.group:*}group: *namespace: ${NAMESPACE:*}file-extension: ymlshared-configs:- data-id: ${CONFIG_DATA_ID:ms-gateway.yml}group: *refresh: truetimeout: 600000config-long-poll-timeout: 5000config-retry-time: 2000max-retry: 3refresh-enabled: true

第五步:替换路由配置

将 Zuul 的 application.yml 配置迁移为 Gateway 的路由配置。这一块实际上就比较复杂了,因为他们之间的切换还是很麻烦的,所以我这里是直接使用AI帮我替换的,你也可以这样。

反正差不多样子就是如下吧。直接让AI帮你替换,然后你看一眼就行了。我反正是这么搞的,然后也没啥问题。

Zuul 配置:

zuul:#semaphore:max-semaphores: 1000servlet-path: /host:connect-timeout-millis: 60000socket-timeout-millis: 60000#routes:smartdata-check:path: /smartCheckservice-id: rbacstrip-prefix: falsesmartdata-token-init:path: /v1/smartdata/tokenservice-id: rbacstrip-prefix: falsecomposite-roles:path: /v1/roles/**service-id: rbacstrip-prefix: false

Gateway 配置:

spring:cloud:gateway:routes:- id: ssouri: lb://rbacpredicates:- Path=/v1/alerts/sso/**- id: smartdata-checkuri: lb://rbacpredicates:- Path=/smartCheck- id: smartdata-token-inituri: lb://rbacpredicates:- Path=/v1/smartdata/token- id: composite-rolesuri: lb://rbacpredicates:- Path=/v1/roles/**

其实没有全局过滤器,已经可以用了,就是网关服务已经是可以用了。到这里其实就已经结束了。服务能用。不看后面也行,我为什么要替换呢,因为我想完美迁移。

第六步:迁移过滤器逻辑

Zuul 使用过滤器机制来处理请求,而 Gateway 则使用过滤器工厂。这一块就比较复杂了,也是我花的最多时间的一步了。

原本的Zuul 过滤器:


import com.netflix.zuul.ZuulFilter;
import com.netflix.zuul.context.RequestContext;
import java.util.Map;
import javax.servlet.http.HttpServletRequest;
import org.keycloak.KeycloakSecurityContext;
import org.keycloak.representations.AccessToken;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.cloud.netflix.zuul.filters.Route;
import org.springframework.cloud.netflix.zuul.filters.RouteLocator;
import org.springframework.web.util.UrlPathHelper;public class PathRewriteHeaderFilter extends ZuulFilter {private static final Logger log = LoggerFactory.getLogger(com.migu.tsg.microservice.zuul.PathRewriteHeaderFilter.class);private RouteLocator routeLocator;private final UrlPathHelper urlPathHelper = new UrlPathHelper();private static final String EMPLOYEE_TYPE = "employeeType";private static final String ORG_ACCOUNT = "head_orgAccount";private static final String IS_ADMIN = "head_isAdmin";private static final String IS_SUPERUSER = "head_isSuperUser";private static final String USER_NAME = "head_userName";private static final String FALSE = "false";private static final String TRUE = "true";private static final String ADMIN = "admin";private static final String ROOT = "root";private static final Integer SIX = Integer.valueOf(6);private static final String COLON = ":";public PathRewriteHeaderFilter() {}public PathRewriteHeaderFilter(RouteLocator routeLocator) {this.routeLocator = routeLocator;}public int filterOrder() {return SIX.intValue();}public String filterType() {return "pre";}public boolean shouldFilter() {return true;}public Object run() {RequestContext requestContext = RequestContext.getCurrentContext();String requestURI = this.urlPathHelper.getPathWithinApplication(requestContext.getRequest());Route route = this.routeLocator.getMatchingRoute(requestURI);try {if (route != null) {String location = route.getLocation();log.info("location: {}", location);if (location != null) {HttpServletRequest request = requestContext.getRequest();KeycloakSecurityContext securityContext = (KeycloakSecurityContext)request.getAttribute(KeycloakSecurityContext.class.getName());handleRewriteHeader(securityContext, requestContext);if (location.startsWith("http:") || location.startsWith("https:"))log.info("forward url is : " + location); } } } catch (Exception e) {requestContext.set("error.status_code", Integer.valueOf(500));requestContext.set("error.message", e.getCause());requestContext.set("error.exception", e);} return null;}private void handleRewriteHeader(KeycloakSecurityContext securityContext, RequestContext requestContext) {log.info("keycloak securityContext = {}", securityContext);if (securityContext == null)return; AccessToken token = securityContext.getToken();Map<String, Object> otherClaims = token.getOtherClaims();log.info("keycloak token = {}, otherClaims = {}", token, otherClaims);String employeeType = (String)otherClaims.get("employeeType");String userName = (String)otherClaims.get("userName");String orgAccount = "";String isSupperUser = "false";String isAdmin = "false";if (employeeType.equals("root")) {isSupperUser = "true";orgAccount = userName;} if (employeeType.equals("admin")) {isAdmin = "true";orgAccount = userName;} if (!employeeType.equals("root") && !employeeType.equals("admin")) {int index = employeeType.indexOf(".") + 1;orgAccount = employeeType.substring(index, employeeType.length());} log.info("employeeType: {}, head_userName: {}, head_isSuperUser: {}, head_orgAccount: {}, head_isAdmin: {}", new Object[] { employeeType, userName, isSupperUser, orgAccount, isAdmin });requestContext.addZuulRequestHeader("head_userName", userName);requestContext.addZuulRequestHeader("head_isSuperUser", isSupperUser);requestContext.addZuulRequestHeader("head_orgAccount", orgAccount);requestContext.addZuulRequestHeader("head_isAdmin", isAdmin);}
}

但是我是想完美的等量替换,所以这里就把原本的过滤器也给拿过来了。可以看到是少了一些东西了,因为原本的方法有很多东西是用不到的,我就把那些东西给删掉了。只保留了用到的东西

反正我测下来,是没啥问题,反正就是实现起来差别真的很大,首先是extends ZuulFilter不用了,改成了GlobalFilter ,然后里面的实现也从public Object run() 变成了public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) ,然后具体的实现逻辑也变了,这一块呢,就是自己琢磨着改吧。每一个过滤器都不一样,反正大体逻辑就是实现的方法不一样了,然后重写的方法不一样了。这两个是最主要的。

  1. 实现的接口不一样
  2. 重写的方法不一样

其实主要把握这两个就行,里面就是具体的代码逻辑了。

替换后的Gateway 全局过滤器:


@Component
@Order(6)  // 这里使用 @Order 注解来设置过滤器顺序
public class PathRewriteHeaderFilter implements GlobalFilter {private static final Logger log = LoggerFactory.getLogger(PathRewriteHeaderFilter.class);@Overridepublic Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {try {String requestURI = exchange.getRequest().getURI().getPath();log.info("Request URI: {}", requestURI);// 在此处你可以获取并处理 Keycloak 的 securityContext 和 tokenKeycloakSecurityContext securityContext = exchange.getAttribute(KeycloakSecurityContext.class.getName());if (securityContext != null) {AccessToken token = securityContext.getToken();Map<String, Object> otherClaims = token.getOtherClaims();log.info("keycloak token = {}, otherClaims = {}", token, otherClaims);String employeeType = (String) otherClaims.get("employeeType");String userName = (String) otherClaims.get("userName");String orgAccount = "";String isSuperUser = "false";String isAdmin = "false";if ("root".equals(employeeType)) {isSuperUser = "true";orgAccount = userName;} else if ("admin".equals(employeeType)) {isAdmin = "true";orgAccount = userName;} else {int index = employeeType.indexOf(".") + 1;orgAccount = employeeType.substring(index);}log.info("employeeType: {}, head_userName: {}, head_isSuperUser: {}, head_orgAccount: {}, head_isAdmin: {}",employeeType, userName, isSuperUser, orgAccount, isAdmin);exchange.getRequest().mutate().header("head_userName", userName).header("head_isSuperUser", isSuperUser).header("head_orgAccount", orgAccount).header("head_isAdmin", isAdmin).build();}} catch (Exception e) {log.error("Error processing request", e);}return chain.filter(exchange);}
}

第七步:测试与调优

  1. 功能测试
    验证迁移后的路由和过滤器逻辑是否正常工作。
    在这里插入图片描述
    我这里是正常的,没有问题的。

  2. 性能测试
    测试 Gateway 的吞吐量和延迟,确保性能满足要求。

  3. 监控与日志
    配置 Gateway 的监控和日志,及时捕获异常和瓶颈。


迁移过程中常见问题及解决方案

  1. 问题:某些依赖库与 WebFlux 不兼容
    解决方案: 更新相关依赖或寻找替代方案,确保与 WebFlux 模型兼容。

  2. 问题:路由配置规则变更导致服务无法访问
    解决方案: 仔细对比 Zuul 和 Gateway 的配置方式,确保路径匹配规则正确。

  3. 问题:过滤器执行顺序混乱
    解决方案: 合理设置过滤器的 Order 值,并明确其执行逻辑。

真实问题:

注意事项:Nginx 转发配置的调整

在迁移过程中,有一个细节需要特别注意,那就是 Nginx 的转发规则。以下是我遇到的问题和解决方法,希望能对你有所帮助。

问题背景

在原有的 Zuul 部署环境中,我使用的是 IP+端口 的形式进行服务转发。由于迁移初期 IP 和端口并未发生改变,所以 Nginx 的配置无需修改,服务能够正常使用。然而,当将 Gateway 部署到云原生环境(如 Kubernetes)后,问题随之出现。

云原生环境中,服务之间的通信通常使用 服务名:端口 的形式,而不是 IP 地址。因此,原本在 Nginx 中配置的 qams-zuul-server 服务名需要进行修改,否则转发规则无法正确匹配,导致请求失败。


解决方法
  1. 检查原有的 Nginx 配置
    原有配置通常类似以下形式:
location ^~/v1/ {proxy_pass   http://zuulServer;}location ^~/v2/ {#proxy_pass   http://10.24.88.160:5566;proxy_pass   http://10.12.7.115:5566;}location ^~/zuul/ {#proxy_pass   http://10.24.88.160:5566;proxy_pass   http://10.12.7.115:5566;}location ^~/download/ {proxy_pass   http://10.24.88.160:2222;}

这种配置基于固定的 IP 和端口,在云原生环境下无法适用。

  1. 修改为基于服务名的配置
    在云原生环境中,需要将 10.12.7.115 的地址替换为服务名,示例如下:
  location ^~/v1/ {                                                                                         proxy_pass   http://qams-gateway-server:5566;}location ^~/v2/ {                                                                                       proxy_pass   http://qams-gateway-server:5566;                                                                 } location ^~/gateway/ {                                                                                       proxy_pass   http://qams-gateway-server:5566;                                                                 }location ^~/download/ {                                                                                       proxy_pass   http://qams-gateway-server:2222;                                                                 }

注意:

  • 服务名 qams-gateway-service 必须与云原生环境中定义的服务名称一致。
  • 确保 Nginx 能够解析服务名。通常情况下,Nginx 部署在同一 Kubernetes 集群内,DNS 解析应当是自动支持的。
  1. 重启 Nginx 并测试
    完成修改后,重启 Nginx 并通过实际访问测试转发是否正常。

迁移的时候注意nginx的转发,我之前呢,是因为我使用的是IP+端口的形式,然后我的IP和端口实际上并没有发生改变,所以我的Nginx没改然后服务依旧能正常使用。
但是当我把Gateway迁移到云原生环境下的时候,就不太行了,因为云原生环境使用的是服务名:端口的格式,所以他原本的服务名称为:qams-zuul-server,要换成下面的格式,就是nginx也要需要,这个不要忘记了。
在这里插入图片描述


总结

从 Zuul 迁移到 Spring Cloud Gateway 是一次提升系统性能和功能的好机会。通过合理规划和逐步迁移,可以平稳完成网关的升级,并充分利用 Gateway 的新特性来优化系统架构。

希望这篇文章能为你的迁移过程提供有价值的参考!如果你在迁移过程中遇到问题,欢迎留言讨论。

相关文章:

从 Zuul 迁移到 Spring Cloud Gateway:一步步实现服务网关的升级

从 Zuul 迁移到 Spring Cloud Gateway&#xff1a;一步步实现服务网关的升级 迁移前的准备工作迁移步骤详解第一步&#xff1a;查看源码第二步&#xff1a;启动类迁移第三步&#xff1a;引入 Gateway 依赖第四步 编写bootstrap.yaml第五步&#xff1a;替换路由配置第六步&#…...

qt之插件编译

QtXlsxWriter sudo apt install qtbase5-private-dev git clone https://github.com/dbzhang800/QtXlsxWriter.git cd QtXlsxWriter/ qmake make -j6 sudo make install #将生成的lib 及 include copy至项目路径的lib 及include里项目配置&#xff1a; QT xlsxbluetoo…...

pandas一行拆成多行

import pandas as pd df pd.DataFrame({Country:[China,US,Japan,EU,UK/Australia, UK/Netherland],Number:[100, 150, 120, 90, 30, 2],Value: [1, 2, 3, 4, 5, 6],label: list(abcdef)})# 法一 推荐 df2df.drop(Country, axis1).join(df[Country].str.split(/, expandTrue).…...

今天调了个转速的小BUG

同事说转速表有个bug&#xff0c;转速停止后&#xff0c;继电器没有恢复到初始状态。若停止之前是报警&#xff0c;继电器吸合&#xff0c;则停止后继电器还是吸合。我心想不会啊&#xff0c;这软件都弄了好几年了&#xff0c;一直也没出现过状况。 经过与调试同事的沟通&#…...

第三节、电机定速转动【51单片机-TB6600驱动器-步进电机教程】

摘要&#xff1a;本节介绍用定时器定时的方式&#xff0c;精准控制脉冲时间&#xff0c;从而控制步进电机速度 一、计算过程 1.1 电机每一步的角速度等于走这一步所花费的时间&#xff0c;走一步角度等于步距角&#xff0c;走一步的时间等于一个脉冲的时间 w s t e p t … ……...

从一个Bug谈前端响应拦截器的应用

一、问题场景 今天在开发商品管理系统时&#xff0c;遇到了一个有趣的问题&#xff1a;当添加重复的商品编号时&#xff0c;页面同时弹出了两条 "商品编号已存在" 错误提示&#xff1a; 这个问题暴露了前端错误处理机制的混乱&#xff0c;让我们从这个问题出发&…...

JS进阶DAY4|节点操作

嘿&#x1f44b; 今天我们要一起深入探索JavaScript中的DOM操作&#xff0c;这是前端开发中不可或缺的技能。&#x1f31f; 准备好了吗&#xff1f;让我们一起跳进DOM的海洋&#xff0c;看看怎么用代码操控网页的结构吧&#xff01; 目录 1. 增加节点 1.1 使用 appendChild 方…...

【Web】2023安洵杯第六届网络安全挑战赛 WP

目录 Whats my name easy_unserialize signal Swagger docs 赛题链接&#xff1a;GitHub - D0g3-Lab/i-SOON_CTF_2023: 2023 第六届安洵杯 题目环境/源码 Whats my name 第一段正则用于匹配以 include 结尾的字符串&#xff0c;并且在 include 之前&#xff0c;可以有任…...

go 语言中协程和GMP模型

为什么需要协程&#xff1f; 协程用来更加精细地利用线程&#xff0c;支撑超高的并发的。协程&#xff0c;从 runtime 的角度看&#xff0c;协程就是一个被调度的 g 结构体。 G 就是协程&#xff0c;M 是线程&#xff0c;P 是为了优化多线程并发时&#xff0c;会抢夺协程队列的…...

coco数据集转换SAM2格式

coco是一个大json汇总了所有train的标签 SAM2训练一张图对应一个json标签 import json import os from pycocotools import mask as mask_utils import numpy as np import cv2def poly2mask(points, width, height):points_array np.array(points, dtypenp.int32).reshape(-…...

【CMD、PowerShell和Bash设置代理】

【CMD、PowerShell和Bash设置代理】 1. CMD&#xff08;命令提示符&#xff09;临时设置代理&#xff08;只对当前会话有效&#xff09;&#xff1a;查看当前代理设置&#xff1a;清除临时代理设置&#xff1a;永久设置代理&#xff08;对所有新的 CMD 会话有效&#xff09;&am…...

22智能 代码作业集合

3-2 #include <stdio.h>int main() {int a 21;int b 10;int c ;c a b;printf("Line 1 - c 的值是 %d\n", c );c a - b;printf("Line 2 - c 的值是 %d\n", c );c a * b;printf("Line 3 - c 的值是 %d\n", c );c a / b;printf("…...

实现一个简单的后台架子(侧边栏菜单渲染,折叠,黑白主题,组件主题色,全屏,路由快捷栏)

目录 侧边栏菜单渲染 侧边栏折叠 黑白主题 全屏切换 切换组件主题色 tab快捷栏 代码 侧边栏菜单渲染 结合ElementPlus组件库进行实现 新建的Vue3项目,引入了格式化样式normalize.css和ElementPlus,并进行了全局引入 并进行了全局引入 设置高度为100% 粘贴ElementPlus的…...

vue3-canvas实现在图片上框选标记(放大,缩小,移动,删除)

双图版本&#xff08;模板对比&#xff09; 业务描述&#xff1a;模板与图片对比&#xff0c;只操作模板框选的位置进行色差对比&#xff0c;传框选坐标位置给后端&#xff0c;返回对比结果显示 draw.js文件&#xff1a; 新增了 createUuid&#xff0c;和求取两个数组差集的方…...

unity3d—demo(2d人物左右移动发射子弹)

目录 人物代码示例&#xff1a; 子弹代码示例&#xff1a; 总结上面代码&#xff1a; 注意点&#xff1a; 人物代码示例&#xff1a; using System.Collections; using System.Collections.Generic; using UnityEngine;public class PlayerTiao : MonoBehaviour {public f…...

【ETCD】【源码阅读】 深入解析 raftNode.start`函数:Raft 核心启动逻辑剖析

raftNode.start方法 是 etcd 中 Raft 模块的核心启动点&#xff0c;其职责是管理 Raft 状态机的状态变迁、日志处理及集群通信等逻辑。通过对源码的逐行分析&#xff0c;我们将全面揭示其运行机制&#xff0c;探讨其设计背后的分布式系统理念。 函数核心结构 raftNode.start 方…...

Robust Depth Enhancement via Polarization Prompt Fusion Tuning

paper&#xff1a;论文地址 code&#xff1a;github项目地址 今天给大家分享一篇2024CVPR上的文章&#xff0c;文章是用偏振做提示学习&#xff0c;做深度估计的。模型架构图如下 这篇博客不是讲这篇论文的内容&#xff0c;感兴趣的自己去看paper&#xff0c;主要是分享环境&…...

NEFTune,SFT训练阶段给Embedding加噪音

仿照CV里&#xff0c;数据增强的思路&#xff08;给图像做旋转、反转、改变亮度等&#xff09;&#xff1b;NLP里&#xff0c;SFT训练数据较少时&#xff0c;也可往embedding上加噪音&#xff0c;来增加训练数据的丰富程度。进而提升最终训练效果。 前提假设&#xff1a;Embed…...

uniapp -- 实现页面滚动触底加载数据

效果 首选,是在pages.json配置开启下拉刷新 {"path": "pages/my/document/officialDocument","style": {"navigationStyle":</...

L22.【LeetCode笔记】相交链表(新版)

目录 1.题目 代码模板 2.分析 ​编辑 算法误区 正确方法1 但不能通过所有的测试用例 修改后 提交结果 正确方法2 节省代码的技巧 1.题目 https://leetcode.cn/problems/3u1WK4/description/ 给定两个单链表的头节点 headA 和 headB &#xff0c;请找出并返回两个单…...

智能时代网络空间认知安全新观察

文章目录 前言一、历史上的四次认知革命二、人工智能革命掀起认知安全新浪潮三、人工智能技术塑造认知安全新范式四、人工智能治理应对认知安全新思考 前言 12月5日&#xff0c;在2024第三届北外滩网络安全论坛上以“智能时代网络空间认知安全新观察”为主题作主旨演讲&#x…...

游戏如何应对模拟器作弊

模拟器是指能在PC端模拟出安卓手机系统的软件&#xff0c;市面上比较常见的安卓模拟器有&#xff1a;雷电模拟器、MuMu模拟器、夜神模拟器等。 市面上常见的模拟器 模拟器既可以节省手机内存空间&#xff0c;避免长时间玩游戏手机发烫发热的尴尬&#xff0c;也可以用键盘鼠标对…...

c++ 判断一个 IP 地址(可能是 IPv6 或 IPv4)是否属于特定范围

在 C 中&#xff0c;判断一个 IP 地址&#xff08;可能是 IPv6 或 IPv4&#xff09;是否属于特定范围时&#xff0c;需要考虑两种不同的地址格式和它们的范围比较。IPv6 和 IPv4 地址结构完全不同&#xff0c;因此需要分别处理这两种地址类型。 实现思路&#xff1a; 识别 IP…...

计算机视觉——相机标定(Camera Calibration)

文章目录 1. 简介2. 原理3. 相机模型3.1 四大坐标系3.2 坐标系间的转换关系3.2.1 世界坐标系到相机坐标系3.2.2 相机坐标系到图像坐标系3.2.3 像素坐标系转换为图像坐标系3.2.4 世界坐标转换为像素坐标 3.3 畸变3.3.1 畸变类型3.3.1.1 径向畸变&#xff08;Radial Distortion&a…...

【qt环境配置】windows下的qt与vs工具集安装\版本对应关系

vs工具集安装通过vs的在线安装器勾选工具集即可 工具包下载路径&#xff1a;https://www.microsoft.com/zh-cn/download/details.aspx?id40784 配置工具集在qt中可以自动扫描到 《正确在 Windows 上配置 MSVC(2019) 作为 Qt 编译器》https://b3logfile.com/pdf/article/15922…...

GitHub使用

太久不用GitHub发现自己又有些不会了&#xff0c;突发奇想为何不把每次看到的有指导意义的博客收录一下以便下次查阅呢 如何上传文件夹到GitHub上&#xff08;配图详解&#xff09;&#xff1f;_github上傳資料夾-CSDN博客 github上如何删除自己的仓库_github删除仓库-CSDN博…...

元宇宙时代的社交平台:Facebook的愿景与实践

随着科技的不断进步&#xff0c;元宇宙&#xff08;Metaverse&#xff09;这一概念逐渐走进了人们的视野。作为全球最大的社交平台之一&#xff0c;Facebook&#xff08;现Meta&#xff09;在这场元宇宙革命中扮演着重要角色。Meta不仅在不断扩展其社交平台的边界&#xff0c;还…...

vue2中各种钩子函数的总结以及使用场景

在 Vue 2 中&#xff0c;生命周期钩子函数是 Vue 实例在不同阶段自动调用的函数。这些钩子允许开发者在组件的创建、更新和销毁的特定时刻插入自定义逻辑。以下是 Vue 2 中的各种生命周期钩子函数的总结及其使用场景。 生命周期钩子函数总结 1、beforeCreate 调用时机&#…...

软件架构:从传统单体到现代微服务的技术演变

1.引言 在软件开发中&#xff0c;架构设计不仅仅是程序员的技术任务&#xff0c;它更是一个项目成功的关键。无论是小型应用还是大型分布式系统&#xff0c;软件架构都直接影响着系统的可维护性、可扩展性、性能和稳定性。理解软件架构的必要性&#xff0c;能够帮助开发人员做…...

git新建远程分支后,无法切换

git remote # 列出所有远程主机 git remote update origin --prune # 更新远程主机origin 整理分支 git branch -r # 列出远程分支 git branch -vv # 查看本地分支和远程分支对应关系 git checkout -b gpf origin/gpf # 新建本地分支gpf与远程gpf分支相关…...

C语言做网站需要创建窗口吗/百度答主招募入口官网

原文地址&#xff1a;http://blog.csdn.net/hitwengqi/article/details/8008203 最近一直在自学Hadoop&#xff0c;今天花点时间搭建一个开发环境&#xff0c;并整理成文。 首先要了解一下Hadoop的运行模式&#xff1a; 单机模式&#xff08;standalone&#xff09; 单机…...

网站建设首页突出什么/看广告赚钱的平台

/*********************************************************************** Buildroot stress-ng Linux系统压力测试* 说明&#xff1a;* 之前有使用lmbench对整板进行一些测试&#xff0c;如果相对某一部分专门进行测试&#xff0c;* 就可以参考使用stree-ng…...

微信微网站开发报价单/百度人工服务24小时热线电话

...

企业自己怎么做网站推广/百度网登录入口

这篇文章主要介绍了PHP PDOStatement对象bindpram()、bindvalue()和bindcolumn之间的区别,需要的朋友可以参考下PDOStatement::bindParam — 绑定一个参数到指定的变量名。绑定一个PHP变量到用作预处理的SQL语句中的对应命名占位符或问号占位符。 不同于 PDOStatement::bindVal…...

自已建网站微信登录/app推广实名认证接单平台

windows 远程桌面连接使用性技术分享 1、mstsc的介绍&#xff1f; Mstsc (Microsoft terminal services client) Mstsc还有一种说法&#xff0c;Microsoft Telnet Screen Control &#xff0c;即“微软远程桌面控制”。 mstsc 与远程客户端之间是用Microsoft的远程桌面协议(…...

wordpress 空间推荐/网络营销的工具和方法

多年以后&#xff0c;面对办公室的屏幕&#xff0c;我会回忆起开始肝第二周OO作业的那个遥远的下午。那时的程序是一个一两百行的符号求导&#xff0c;基类与接口在包里一字排开&#xff0c;工整的注释一望到底 谁能想到&#xff0c;接下来的十几个小时我要经历什么样的噩梦&am…...