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

Springboot 使用【过滤器】实现在请求到达 Controller 之前修改请求体参数和在结果返回之前修改响应体

文章目录

    • 前情提要
    • 解决方案
      • 自定义 HttpServletRequest 包装类 RequestWrapper
      • 自定义 HttpServletResponse 包装类 ResponseWrapper
      • 自定义过滤器 MiddlewareFilter
      • 配置过滤器
        • 注解
        • 配置类
      • 编写 Controller 测试

前情提要

在项目中需要使用过滤器 在请求调用 Controller 方法前修改请求参数和在结果返回之前修改返回结果

在 Controller 中定义如下接口:

@PostMapping("/hello")
public JSONObject hello(@RequestBody Map<String, Object> params) {return JSONObject.parseObject(JSON.toJSONString(params));
}

定义的过滤器如下:

public class ServNoFilter extends OncePerRequestFilter {@Overrideprotected void doFilterInternal(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, FilterChain filterChain) throws ServletException, IOException {// 获取请求体内容String requestBody = getRequestBody(httpServletRequest);// 业务处理......// 放行filterChain.doFilter(httpServletRequest, httpServletResponse);}private String getRequestBody(HttpServletRequest request) throws IOException {BufferedReader reader = new BufferedReader(request.getReader());StringBuilder sb = new StringBuilder();String line;while ((line = reader.readLine()) != null) {sb.append(line);}return sb.toString();}
}

此时启动项目,访问接口,则会在控制台打印如下异常信息:Request processing failed; nested exception is java.lang.IllegalStateException: getReader() has already been called for this request

表示在过滤器中已经通过 request.getReader() 方法将请求流读取。

如果在过滤器中将 getReader() 换成 getInputStream() 就会报请求体为空异常:org.springframework.http.converter.HttpMessageNotReadableException: Required request body is missing

这是因为在 Servlet 中,请求对象的输入流只能被读取一次。而在第一次读取请求体时,Servlet 容器会将请求体保存在内存中,并将其解析成相应的请求参数和请求头信息。如果在后续的处理中再次读取请求体,就可能会导致数据错误或异常。

解决方案

自定义 HttpServletRequest 包装类 RequestWrapper

在 Servlet 中,原始的 HttpServletRequest 对象中的请求流(即请求体)只能读取一次。这是因为 HTTP 协议是基于流的协议,服务器在读取请求流时会将其消耗掉,一旦读取完毕,就无法再次读取

当 Servlet 容器读取完请求流后,会将请求的内容解析并储存在相应的属性中,如请求参数、请求头信息等。在后续的处理过程中,Servlet 可以从这些属性中获取请求内容,而不必再次读取请求流。

因此,我们需要自定义 RequestWrapper 将请求流保存下来,并提供方法来多次读取请求体的内容。

自定义 HttpServletRequest 包装类 RequestWrapper 如下:

/*** HttpServletRequest 包装类,允许在 Servlet 中多次读取请求体内容* 重写了 getInputStream()方法和 getReader() 方法,返回可以多次读取的流。*/
public class RequestWrapper extends HttpServletRequestWrapper {private final byte[] body;/*** 构造 RequestWrapper 对象** @param request 原始 HttpServletRequest 对象* @param context 请求体内容*/public RequestWrapper(HttpServletRequest request, String context) {super(request);this.body = context.getBytes(StandardCharsets.UTF_8);}/*** 重写 getInputStream 方法,返回经过包装后的 ServletInputStream 对象** @return 经过包装后的 ServletInputStream 对象*/@Overridepublic ServletInputStream getInputStream() {return new ServletInputStreamWrapper(new ByteArrayInputStream(body));}/*** 重写 getReader 方法,返回经过包装后的 BufferedReader 对象** @return 经过包装后的 BufferedReader 对象*/@Overridepublic BufferedReader getReader() {return new BufferedReader(new InputStreamReader(getInputStream(), StandardCharsets.UTF_8));}/*** 私有内部类,用于包装 ServletInputStream 对象*/private static class ServletInputStreamWrapper extends ServletInputStream {private final ByteArrayInputStream inputStream;/*** 构造函数,传入待包装的 ByteArrayInputStream 对象** @param inputStream 待包装的 ByteArrayInputStream 对象*/public ServletInputStreamWrapper(ByteArrayInputStream inputStream) {this.inputStream = inputStream;}/*** 重写 read 方法,读取流中的下一个字节** @return 读取到的下一个字节,如果已达到流的末尾,则返回-1*/@Overridepublic int read() {return inputStream.read();}/*** 覆盖 isFinished 方法,指示流是否已完成读取数据** @return 始终返回 false,表示流未完成读取数据*/@Overridepublic boolean isFinished() {return false;}/*** 重写 isReady 方法,指示流是否准备好进行读取操作** @return 始终返回 false,表示流未准备好进行读取操作*/@Overridepublic boolean isReady() {return false;}/*** 重写 setReadListener 方法,设置读取监听器** @param readListener 读取监听器*/@Overridepublic void setReadListener(ReadListener readListener) {}}
}

自定义 HttpServletResponse 包装类 ResponseWrapper

与请求流(即请求体)一样,原始的 HttpServletResponse 对象中的响应流(即响应体)只能写入一次。当服务器在向客户端发送响应时,会将响应流写入到网络传输通道中,一旦写入完毕,就无法再次修改或写入。

因此我们需要通过自定义 ResponseWrapper 包装原始的 HttpServletResponse 对象并重写其输出流或者输出写方法,从而实现对响应流的修改和控制。

自定义 HttpServletResponse 包装类 ResponseWrapper 如下:

/*** HttpServletResponse 包装类对,提供对响应数据的处理和操作。*/
public class ResponseWrapper extends HttpServletResponseWrapper {private final ByteArrayOutputStream outputStream;private ServletOutputStream servletOutputStream;private PrintWriter writer;/*** 构造函数,传入原始的 HttpServletResponse 对象** @param response 原始的 HttpServletResponse 对象*/public ResponseWrapper(HttpServletResponse response) {super(response);this.outputStream = new ByteArrayOutputStream();}/*** 重写 getOutputStream 方法,返回经过包装后的 ServletOutputStream 对象** @return 经过包装后的 ServletOutputStream 对象*/@Overridepublic ServletOutputStream getOutputStream() {if (servletOutputStream == null) {servletOutputStream = new ServletOutputStreamWrapper(outputStream);}return servletOutputStream;}/*** 重写 getWriter 方法,返回经过包装后的 PrintWriter 对象** @return 经过包装后的 PrintWriter 对象*/@Overridepublic PrintWriter getWriter() {if (writer == null) {writer = new PrintWriter(getOutputStream());}return writer;}/*** 获取响应数据,并指定字符集** @param charsetName 字符集名称* @return 响应数据字符串*/public String getResponseData(String charsetName) {Charset charset = Charset.forName(charsetName);byte[] bytes = outputStream.toByteArray();return new String(bytes, charset);}/*** 设置响应数据,并指定字符集** @param responseData 响应数据字符串* @param charsetName  字符集名称*/public void setResponseData(String responseData, String charsetName) {Charset charset = Charset.forName(charsetName);byte[] bytes = responseData.getBytes(charset);outputStream.reset();try {outputStream.write(bytes);} catch (IOException e) {// 处理异常}setCharacterEncoding(charsetName);}/*** 私有内部类,用于包装 ServletOutputStream 对象*/private static class ServletOutputStreamWrapper extends ServletOutputStream {private final ByteArrayOutputStream outputStream;/*** 构造函数,传入待包装的 ByteArrayOutputStream 对象** @param outputStream 待包装的 ByteArrayOutputStream 对象*/public ServletOutputStreamWrapper(ByteArrayOutputStream outputStream) {this.outputStream = outputStream;}/*** 重写 write 方法,将指定字节写入输出流** @param b 字节*/@Overridepublic void write(int b) {outputStream.write(b);}/*** 重写 isReady 方法,指示输出流是否准备好接收写入操作** @return 始终返回 false,表示输出流未准备好接收写入操作*/@Overridepublic boolean isReady() {return false;}/*** 重写 setWriteListener 方法,设置写入监听器** @param writeListener 写入监听器*/@Overridepublic void setWriteListener(WriteListener writeListener) {}}
}

自定义过滤器 MiddlewareFilter

我们的需求是:在请求到达服务器之前,对请求参数进行修改;在响应返回之前,对响应结果进行处理。

对于这样的需求,我们可以通过自定义过滤器来实现。大致实现思路如下:

  • 修改请求参数(请求体),我们可以:

    1. 获取请求体内容。
    2. 修改请求体内容。
    3. 将修改后的请求对象替换原来的请求对象,以便后续获取修改后的参数。
  • 修改响应结果(响应体),我们可以:

    1. 获取响应数据。
    2. 对响应数据进行处理。
    3. 将修改后的数据作为最终结果返回。

同时为了确保每个请求在请求时只会被过滤一次,我们可以继承 OncePerRequestFilter 来定义自己的过滤器。

最终,自定义过滤器如下:

public class MiddlewareFilter extends OncePerRequestFilter {@Overrideprotected void doFilterInternal(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, FilterChain filterChain) throws ServletException, IOException {// 1. 从 HttpServletRequest 对象中获取请求体内容String requestBody = getRequestBody(httpServletRequest);// 2. 解析请求体内容为JSON对象JSONObject jsonBody = JSONObject.parseObject(requestBody);// 3. 修改请求体内容jsonBody.put("paramKey","paramValue");// 4. 包装 HttpServletRequest 对象为自定义的 RequestWrapper 对象,以便后续的处理RequestWrapper requestWrapper = new RequestWrapper(httpServletRequest, jsonBody.toJSONString());// 5. 包装 HttpServletResponse 对象为自定义的 ResponseWrapper 对象,以便后续的处理ResponseWrapper responseWrapper = new ResponseWrapper(httpServletResponse);// 6. 调用下一个过滤器或 ServletfilterChain.doFilter(requestWrapper, responseWrapper);// 7. 获取响应数据String responseData = responseWrapper.getResponseData(StandardCharsets.UTF_8.name());// 8. 解析响应数据为JSON对象JSONObject jsonData = JSONObject.parseObject(responseData);// 9. 在这里可以对响应数据进行处理jsonData.put("responseKey", "responseValue");// 10. 将修改后的 JSON 对象转换为字符串responseData = jsonData.toJSONString();// 11. 将修改后的 JSON 对象设置为最终的响应数据responseWrapper.setResponseData(responseData, StandardCharsets.UTF_8.name());// 12. 将响应数据写入原始的响应对象,解决响应数据无法被多个过滤器处理问题OutputStream outputStream = httpServletResponse.getOutputStream();outputStream.write(responseData.getBytes(StandardCharsets.UTF_8));outputStream.flush();}/*** 获取请求体内容。** @param request HttpServletRequest对象* @return 请求体内容* @throws IOException 如果读取请求体内容时发生I/O异常*/private String getRequestBody(HttpServletRequest request) throws IOException {BufferedReader reader = new BufferedReader(new InputStreamReader(request.getInputStream()));StringBuilder sb = new StringBuilder();String line;while ((line = reader.readLine()) != null) {sb.append(line);}return sb.toString();}
}

配置过滤器

注解

通过 Java Servlet 3.0 规范中引入的 @WebFilter 注解配置过滤器。

@WebFilter 注解可以应用在实现了 Filter 接口或继承自 OncePerRequestFilter 的类上,标识该类为过滤器,并指定过滤器的相关配置,包括拦截的 URL 路径、执行顺序以及初始化参数等。

我们可以在 MiddlewareFilter 过滤器上使用 @WebFilter 注解注册该过滤器并指定执行该过滤器执行的顺序和拦截的 URL:

@WebFilter(value = "1000", urlPatterns = "/hello")
public class MiddlewareFilter extends OncePerRequestFilter {......
}
  • value:设置过滤器的执行顺序,数字越小,优先级越高。
  • urlPatterns:指定要拦截的 URL 路径,允许指定多个 URL 路径urlPatterns = {"/hello","/hello1"}

还需要再启动类上使用@ServletComponentScan注解扫描和注册带有 @WebServlet@WebFilter@WebListener 注解的组件:

@ServletComponentScan
@SpringBootApplication
public class Demo1Application {public static void main(String[] args) {SpringApplication.run(Demo1Application.class, args);}}
配置类

除了注解的形式配置过滤器,我们还可以通过配置类的形式进行配置。

创建 FilterConfig 类用于配置需要注册的过滤器,同时在类上添加 @Configuration 注解,标识该类为配置类,在项目启动时 Spring 会自动扫描该类中的 Bean 定义,并将其加载到容器中:

@Configuration
public class FilterConfig {@Beanpublic FilterRegistrationBean<MiddlewareFilter> middlewareFilter() {FilterRegistrationBean<MiddlewareFilter> registration = new FilterRegistrationBean<>();registration.setFilter(new MiddlewareFilter()); // 设置过滤器实例registration.addUrlPatterns("/hello"); // 拦截的 URL 路径registration.setOrder(1000); // 设置过滤器执行顺序(数字越小,越先执行)return registration;}}

在类中我们定义了名为 middlewareFilter 的方法,用于注册我们自定义的 MiddlewareFilter 过滤器。

在 方法中,创建了一个 FilterRegistrationBean 对象用于注册和配置过滤器,并设置 MiddlewareFilter 对象作为过滤器实例,指定了过滤器要拦截的 URL 路径,滤器执行顺序。

最后将 FilterRegistrationBean 对象返回,以便 Spring 自动进行注册和管理。

编写 Controller 测试

创建两个接口,同样的逻辑,接收一个请求体参数 params,再将接收的参数以 JSON 格式返回:

@RestController
public class BasicController {/*** 处理 /hello 请求的方法* @param params 请求体参数,以键值对的形式传递* @return 经过转换后的 JSONObject 对象*/@PostMapping("/hello")public JSONObject hello(@RequestBody Map<String, Object> params) {return JSONObject.parseObject(JSON.toJSONString(params));}@PostMapping("/hello1")public JSONObject hello1(@RequestBody Map<String,Object> params) {return JSONObject.parseObject(JSON.toJSONString(params));}
}

启动项目,在 ApiFox 中分别以同样的请求参数发送 POST 请求调用 /hello/hello1 接口:

  • 请求参数:

    {"name": "hello","age": 20
    }
    
  • /hello 接口返回结果:

    {"paramKey": "paramValue","responseKey": "responseValue","name": "hello","age": 20
    }
    
  • /hello1 接口返回结果:

    {"name": "hello","age": 20
    }
    

复制多个 MiddlewareFilter 过滤器模拟多层过滤器修改请求体参数和返回结果,测试结果如下:

{"paramKey": "paramValue",	//过滤器1"responseKey2": "responseValue2",	//过滤器2"responseKey": "responseValue",	//过滤器2"paramKey2": "paramValue2",	//过滤器1"name": "hello","age": 20
}

相关文章:

Springboot 使用【过滤器】实现在请求到达 Controller 之前修改请求体参数和在结果返回之前修改响应体

文章目录 前情提要解决方案自定义 HttpServletRequest 包装类 RequestWrapper自定义 HttpServletResponse 包装类 ResponseWrapper自定义过滤器 MiddlewareFilter配置过滤器注解配置类 编写 Controller 测试 前情提要 在项目中需要使用过滤器 在请求调用 Controller 方法前修改…...

Unity 3D常用的数据结构

目录 数组使用场景 ArrayList数组ArrayList的缺点 List\<T\>数组List\<T\>有以下3点好处 链表链表与数组的不同之处链表的优势数组和链表的应用场景 LinkedList\<T\>C#中内置的双向链表LinkedList使用场景 队列&#xff08;Queue\<T\>&#xff09;和栈…...

h5唤起微信小程序

wx-open-launch-weapp 就用这个 开放标签属于自定义标签&#xff0c;Vue会给予未知标签的警告&#xff0c;可通过配置Vue.config.ignoredElements [wx-open-launch-weapp] 来忽略Vue对开放标签的检查。 sdk授权。 调试打开时iOS会弹窗 noPermissionJsApi: []&#xff0c;confi…...

面向对象(精髓)变继承关系为组和关系(_Decorator模式)

在软件开发中&#xff0c;设计模式是解决常见问题的可重用解决方案。在面向对象编程中&#xff0c;继承和组合是两种常用的代码复用方式。然而&#xff0c;随着软件需求的不断变化&#xff0c;我们需要更灵活的设计方式来应对不断变化的需求。在本文中&#xff0c;我们将讨论从…...

MES系统在智能生产中的重要作用

在未来智能制造的发展趋势中&#xff0c;制造执行系统&#xff08;MES&#xff09;作为关键技术和工具&#xff0c;扮演着至关重要的角色。随着科技的不断进步和制造业的数字化转型&#xff0c;MES的地位将愈发凸显&#xff0c;对于企业实现智能化生产、提高效率、降低成本具有…...

2024.3.13每日一题

LeetCode 最大二进制奇数 题目链接&#xff1a;2864. 最大二进制奇数 - 力扣&#xff08;LeetCode&#xff09; 题目描述 给你一个 二进制 字符串 s &#xff0c;其中至少包含一个 1 。 你必须按某种方式 重新排列 字符串中的位&#xff0c;使得到的二进制数字是可以由该组…...

YOLOv5 | 涨点复现!YOLOv5添加BiFPN有效提升目标检测精度

目录 &#x1f680;&#x1f680;&#x1f680;订阅专栏&#xff0c;更新及时查看不迷路&#x1f680;&#x1f680;&#x1f680; 介绍&#xff1a; BiFPN 代码实现 ⭐欢迎大家订阅我的专栏一起学习⭐ &#x1f680;&#x1f680;&#x1f680;订阅专栏&#xff0c;更新及…...

【Nut3】nuxt.config.ts项目nuxt配置文件介绍

简言 记录下nuxt3的nuxt.config.ts文件的介绍和使用。 Nuxt Configuration nuxt.config.ts Nuxt可以通过一个单独的nuxt.config文件进行简单配置。 配置文件创建 nuxt.config文件的扩展名可以是.js、.ts或.mjs。 然后默认导出全局函数defineNuxtConfig的返回值&#xff0c…...

区块链技术的革命性影响

1. 区块链技术的基本原理&#xff1a; 区块链是一种去中心化的分布式数据库技术&#xff0c;通过不断增长的记录&#xff08;块&#xff09;构成一个链式结构。每个区块包含了交易数据的加密信息以及上一个区块的哈希值&#xff0c;从而形成了不可篡改的交易记录。这种去中心化…...

多线程(volatile)

volatile的功能 保证内存可见性禁止指令重排序 内存可见性 简单的理解 两(多)个线程同时针对一个变量进行操作, 一个线程读, 一个线程修改, 此时读到的值不一定是修改过后的值 即读线程没有感知到变量的变化 (其实是 编译器/JVM 对于代码在多线程情况下的优化进行了误判) 从 J…...

蓝桥杯 填空 卡片

蓝桥杯 填空题 卡片 解题思路&#xff1a; 我们只需要消耗完卡片的个数即可。 代码示例&#xff1a; #include<bits/stdc.h> using namespace std; int a[10]; bool isEnd(){for(int i0;i<10;i){if(a[i]-1)return false;}return true; } bool getN(int x){while(x){i…...

ELK介绍使用

文章目录 一、ELK介绍二、Elasticsearch1. ElasticSearch简介&#xff1a;2. Elasticsearch核心概念3. Elasticsearch安装4. Elasticsearch基本操作1. 字段类型介绍2. 索引3. 映射4. 文档 5. Elasticsearch 复杂查询 三、LogStash1. LogStash简介2. LogStash安装 四、kibana1. …...

【UE5】非持枪状态蹲姿移动的动画混合空间

项目资源文末百度网盘自取 在BlendSpace文件夹中单击右键选择动画(Animation)中的混合空间(Blend Space) &#xff0c;选择SK_Female_Skeleton&#xff0c;命名为BS_NormalCrouch 打开BS_NormalCrouch 水平轴表示角色的方向&#xff0c;命名为Direction&#xff0c;方向的最…...

Windows C++ TCP开发(使用select函数以及设置非阻塞/Reuse属性)

1、select官方函数说明&#xff1a; 语法 C int WSAAPI select([in] int nfds,[in, out] fd_set *readfds,[in, out] fd_set *writefds,[in, out] fd_set *exceptfds,[in] const timeval *timeout );参数 [in] nfds 已忽略。 包含 nf…...

ARM TrustZone技术解析:构建嵌入式系统的安全扩展基石

&#x1f308;个人主页: Aileen_0v0 &#x1f525;热门专栏: 华为鸿蒙系统学习|计算机网络|数据结构与算法|MySQL| ​&#x1f4ab;个人格言:“没有罗马,那就自己创造罗马~” #mermaid-svg-LOdvohfCEnd8eKyd {font-family:"trebuchet ms",verdana,arial,sans-serif;f…...

初识Python语言-课堂练习【pyhton123题库】

初识Python语言-课堂练习【pyhton123题库】 一、单项选择题 1、Guido van Rossum正式对外发布Python版本的年份是&#xff1a; A 2008B 1998C 1991D 2002 【答案】C 【解析】暂无解析2、下面不是Python语言特点的是&#xff1a;‪‬‪‬‪‬‪‬‪‬‮‬‪‬‫‬‪‬‪‬‪…...

chrome高内存占用问题

chrome号称内存杀手不是盖的&#xff0c;不设设置的话&#xff0c;经常被它内存耗尽死机是常事。以下自用方法 1 自带的memory saver chrome://settings/performance PerformanceMemory Saver When on, Chromium frees up memory from inactive tabs. This gives active tab…...

【C语言】文件操作篇-----程序文件和数据文件,文件的打开和关闭,二进制文件和文本文件,fopen,fclose【图文详解】

欢迎来CILMY23的博客喔&#xff0c;本篇为【C语言】文件操作篇-----程序文件和数据文件&#xff0c;文件的打开和关闭&#xff0c;二进制文件和文本文件【图文详解】&#xff0c;感谢观看&#xff0c;支持的可以给个一键三连&#xff0c;点赞关注收藏。 前言 在了解完动态内存管…...

知识碎片收集

目录 1. 如何计算两点经纬度之间的距离2. 加权随机采样3.什么时LLDB和GDB 1. 如何计算两点经纬度之间的距离 1.知乎-如何计算两点经纬度间距离 2.根据两点经纬度坐标计算距离 3.根据经纬度计算两点之间的距离的公式推导过程以及google.maps的测距函数 4.根据经纬度点计算经…...

不可不知!用例图的绘制与应用全指南深度解析

在软件开发领域中&#xff0c;用例图是一种强大的工具&#xff0c;用于描述系统的功能需求以及系统与外部实体之间的交互。无论是在需求分析阶段还是在系统设计过程中&#xff0c;用例图都扮演着至关重要的角色。本文将全面介绍用例图的绘制方法和其在软件开发中的应用&#xf…...

Linux链表操作全解析

Linux C语言链表深度解析与实战技巧 一、链表基础概念与内核链表优势1.1 为什么使用链表&#xff1f;1.2 Linux 内核链表与用户态链表的区别 二、内核链表结构与宏解析常用宏/函数 三、内核链表的优点四、用户态链表示例五、双向循环链表在内核中的实现优势5.1 插入效率5.2 安全…...

Oracle查询表空间大小

1 查询数据库中所有的表空间以及表空间所占空间的大小 SELECTtablespace_name,sum( bytes ) / 1024 / 1024 FROMdba_data_files GROUP BYtablespace_name; 2 Oracle查询表空间大小及每个表所占空间的大小 SELECTtablespace_name,file_id,file_name,round( bytes / ( 1024 …...

【入坑系列】TiDB 强制索引在不同库下不生效问题

文章目录 背景SQL 优化情况线上SQL运行情况分析怀疑1:执行计划绑定问题?尝试:SHOW WARNINGS 查看警告探索 TiDB 的 USE_INDEX 写法Hint 不生效问题排查解决参考背景 项目中使用 TiDB 数据库,并对 SQL 进行优化了,添加了强制索引。 UAT 环境已经生效,但 PROD 环境强制索…...

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

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

2.Vue编写一个app

1.src中重要的组成 1.1main.ts // 引入createApp用于创建应用 import { createApp } from "vue"; // 引用App根组件 import App from ./App.vue;createApp(App).mount(#app)1.2 App.vue 其中要写三种标签 <template> <!--html--> </template>…...

全球首个30米分辨率湿地数据集(2000—2022)

数据简介 今天我们分享的数据是全球30米分辨率湿地数据集&#xff0c;包含8种湿地亚类&#xff0c;该数据以0.5X0.5的瓦片存储&#xff0c;我们整理了所有属于中国的瓦片名称与其对应省份&#xff0c;方便大家研究使用。 该数据集作为全球首个30米分辨率、覆盖2000–2022年时间…...

sqlserver 根据指定字符 解析拼接字符串

DECLARE LotNo NVARCHAR(50)A,B,C DECLARE xml XML ( SELECT <x> REPLACE(LotNo, ,, </x><x>) </x> ) DECLARE ErrorCode NVARCHAR(50) -- 提取 XML 中的值 SELECT value x.value(., VARCHAR(MAX))…...

深入解析C++中的extern关键字:跨文件共享变量与函数的终极指南

&#x1f680; C extern 关键字深度解析&#xff1a;跨文件编程的终极指南 &#x1f4c5; 更新时间&#xff1a;2025年6月5日 &#x1f3f7;️ 标签&#xff1a;C | extern关键字 | 多文件编程 | 链接与声明 | 现代C 文章目录 前言&#x1f525;一、extern 是什么&#xff1f;&…...

第 86 场周赛:矩阵中的幻方、钥匙和房间、将数组拆分成斐波那契序列、猜猜这个单词

Q1、[中等] 矩阵中的幻方 1、题目描述 3 x 3 的幻方是一个填充有 从 1 到 9 的不同数字的 3 x 3 矩阵&#xff0c;其中每行&#xff0c;每列以及两条对角线上的各数之和都相等。 给定一个由整数组成的row x col 的 grid&#xff0c;其中有多少个 3 3 的 “幻方” 子矩阵&am…...

重启Eureka集群中的节点,对已经注册的服务有什么影响

先看答案&#xff0c;如果正确地操作&#xff0c;重启Eureka集群中的节点&#xff0c;对已经注册的服务影响非常小&#xff0c;甚至可以做到无感知。 但如果操作不当&#xff0c;可能会引发短暂的服务发现问题。 下面我们从Eureka的核心工作原理来详细分析这个问题。 Eureka的…...