自拍偷在线精品自拍偷,亚洲欧美中文日韩v在线观看不卡

在 SpringBoot3.3 中攔截修改請求 Body 的多種正確方式

開發(fā) 前端
經(jīng)處理的用戶輸入可能會包含惡意的 HTML 或 JavaScript 代碼,攻擊者可以利用這些代碼在用戶瀏覽器中執(zhí)行惡意腳本,導致跨站腳本攻擊(XSS)。

在現(xiàn)代Web應用中,安全性和數(shù)據(jù)完整性是至關(guān)重要的,尤其是在處理用戶提交的數(shù)據(jù)時。請求的Body部分通常包含了關(guān)鍵的數(shù)據(jù),如用戶輸入的表單信息、JSON數(shù)據(jù)、XML數(shù)據(jù)等,這些數(shù)據(jù)在傳輸和處理過程中如果沒有經(jīng)過適當?shù)尿炞C和安全檢查,可能會導致嚴重的安全漏洞。

例如,未經(jīng)處理的用戶輸入可能會包含惡意的 HTML 或 JavaScript 代碼,攻擊者可以利用這些代碼在用戶瀏覽器中執(zhí)行惡意腳本,導致跨站腳本攻擊(XSS)。此外,數(shù)據(jù)的完整性和準確性也可能受到篡改,這可能會導致應用程序在處理過程中出現(xiàn)錯誤或異常。

為了應對這些挑戰(zhàn),開發(fā)人員通常需要攔截并修改請求 Body 的內(nèi)容,對其進行驗證、過濾和格式化,以確保其安全性和可靠性。在Spring Boot 框架中,攔截和修改請求 Body 的方式有多種,常見的包括使用過濾器(Filter)、攔截器(Interceptor)、自定義HttpMessageConverter,以及直接在Controller中處理。

本文將深入探討在 Spring Boot 中攔截和修改請求 Body 的多種正確方式,結(jié)合代碼示例對每種方式進行詳細講解,并特別強調(diào)如何通過格式化和內(nèi)容安全性檢測來防止 XSS 攻擊,確保應用程序的安全性和數(shù)據(jù)的完整性。我們還將介紹如何在這些方法中集成內(nèi)容安全策略,增強對 HTML 和 JavaScript 標簽的檢測和處理,以防止?jié)撛诘陌踩{。這些技術(shù)不僅適用于一般的 Web 應用開發(fā),還對構(gòu)建高安全性的企業(yè)級應用有著重要的指導意義。

運行效果:

圖片

若想獲取項目完整代碼以及其他文章的項目源碼,且在代碼編寫時遇到問題需要咨詢交流,歡迎加入下方的知識星球。

項目結(jié)構(gòu)

我們將創(chuàng)建一個 Spring Boot 項目,其中包含以下文件和配置:

  • pom.xml:項目依賴配置
  • application.yml:項目屬性配置
  • 前端頁面:使用 Thymeleaf 模板引擎,結(jié)合 Bootstrap 進行樣式美化
  • 控制器、過濾器、中間件:實現(xiàn)攔截和修改請求 Body 的功能

項目依賴配置(pom.xml)

首先,在pom.xml文件中添加必要的依賴項。我們的項目需要以下依賴:

<?xml versinotallow="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
	xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
	<modelVersion>4.0.0</modelVersion>
	<parent>
		<groupId>org.springframework.boot</groupId>
		<artifactId>spring-boot-starter-parent</artifactId>
		<version>3.3.3</version>
		<relativePath/> <!-- lookup parent from repository -->
	</parent>
	<groupId>com.icoderoad</groupId>
	<artifactId>request-body</artifactId>
	<version>0.0.1-SNAPSHOT</version>
	<name>request-body</name>
	<description>Demo project for Spring Boot</description>
	
	<properties>
		<java.version>17</java.version>
	</properties>
	<dependencies>

		<!-- Spring Boot Starter Web -->
	    <dependency>
	        <groupId>org.springframework.boot</groupId>
	        <artifactId>spring-boot-starter-web</artifactId>
	    </dependency>
	
	    <!-- Thymeleaf -->
	    <dependency>
	        <groupId>org.springframework.boot</groupId>
	        <artifactId>spring-boot-starter-thymeleaf</artifactId>
	    </dependency>
		
		<!-- Apache Commons Text (for string escape operations) -->
	    <dependency>
	        <groupId>org.apache.commons</groupId>
	        <artifactId>commons-text</artifactId>
	        <version>1.9</version>
	    </dependency>
	

	    <!-- Lombok (optional for reducing boilerplate code) -->
	    <dependency>
	        <groupId>org.projectlombok</groupId>
	        <artifactId>lombok</artifactId>
	        <scope>provided</scope>
	    </dependency>

		<dependency>
			<groupId>org.springframework.boot</groupId>
			<artifactId>spring-boot-starter-test</artifactId>
			<scope>test</scope>
		</dependency>
	</dependencies>

	<build>
		<plugins>
			<plugin>
				<groupId>org.springframework.boot</groupId>
				<artifactId>spring-boot-maven-plugin</artifactId>
			</plugin>
		</plugins>
	</build>

</project>

項目配置(application.yml)

接下來,在application.yml文件中進行一些基本配置。通常,我們可以在這里配置服務器端口、日志級別等信息:

server:
  port: 8080

spring:
  thymeleaf:
    cache: false
    mode: HTML
    suffix: .html
    prefix: classpath:/templates/

前端頁面(Thymeleaf模板)

為了演示請求攔截和修改的效果,我們可以創(chuàng)建一個簡單的表單頁面index.html,用戶可以在此頁面上提交數(shù)據(jù)。

<!DOCTYPE html>
<html lang="zh">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>請求表單</title>
    <!-- 引入Bootstrap CSS -->
    <link  rel="stylesheet">
    <!-- 引入jQuery -->
    <script src="https://code.jquery.com/jquery-3.6.0.min.js"></script>
</head>
<body>
    <div class="container">
        <h2>提交請求表單</h2>
        <form id="requestForm">
            <div class="mb-3">
                <label for="inputData" class="form-label">輸入數(shù)據(jù)</label>
                <textarea class="form-control" id="inputData" rows="3" required></textarea>
            </div>
            <button type="submit" class="btn btn-primary">提交</button>
        </form>

        <!-- 提示信息 -->
        <div id="resultMessage" class="alert mt-3" role="alert" style="display:none;"></div>
    </div>

    <script>
        $(document).ready(function() {
            $("#requestForm").on("submit", function(event) {
                event.preventDefault(); // 阻止表單的默認提交行為

                // 獲取用戶輸入的數(shù)據(jù)
                var inputData = $("#inputData").val();

                // 使用 jQuery 發(fā)送 AJAX 請求
                $.ajax({
                    url: "/submit",
                    type: "POST",
                    contentType: "application/json",
                    data: JSON.stringify({data: inputData}),
                    success: function(response) {
                        // 成功后在頁面上顯示提示信息
                        $("#resultMessage").removeClass("alert-danger").addClass("alert-success")
                            .text("提交成功: " + response.message)
                            .show();
                    },
                    error: function(xhr, status, error) {
                        // 失敗時顯示錯誤信息
                        $("#resultMessage").removeClass("alert-success").addClass("alert-danger")
                            .text("提交失敗: " + xhr.responseText)
                            .show();
                    }
                });
            });
        });
    </script>
</body>
</html>

攔截和修改請求Body的實現(xiàn)方式

創(chuàng)建過濾器配置類

創(chuàng)建一個 FilterConfig 配置類,在該類中注冊 RequestBodyFilter 過濾器。

import org.springframework.boot.web.servlet.FilterRegistrationBean;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.Ordered;

@Configuration
public class FilterConfig {

    @Bean
    public FilterRegistrationBean<RequestBodyFilter> requestBodyFilterRegistration() {
        FilterRegistrationBean<RequestBodyFilter> registrationBean = new FilterRegistrationBean<>();

        // 將自定義過濾器注冊為Bean
        registrationBean.setFilter(new RequestBodyFilter());
        
        // 過濾器應用于所有URL
        registrationBean.addUrlPatterns("/*");
        
        // 設置過濾器的優(yōu)先級
        registrationBean.setOrder(Ordered.HIGHEST_PRECEDENCE);

        return registrationBean;
    }
}
使用過濾器(Filter)攔截請求Body

過濾器是一種常見的攔截HTTP請求的方式。我們可以通過實現(xiàn)jakarta.servlet.Filter接口來攔截請求并修改請求體。

package com.icoderoad.request_body.filter;

import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.nio.charset.StandardCharsets;

import org.apache.commons.text.StringEscapeUtils;
import org.springframework.stereotype.Component;

import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.ObjectMapper;

import jakarta.servlet.Filter;
import jakarta.servlet.FilterChain;
import jakarta.servlet.FilterConfig;
import jakarta.servlet.ServletException;
import jakarta.servlet.ServletInputStream;
import jakarta.servlet.ServletRequest;
import jakarta.servlet.ServletResponse;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletRequestWrapper;
import jakarta.servlet.http.HttpServletResponse;

@Component
public class RequestBodyFilter implements Filter {

    @Override
    public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {
        if (request instanceof HttpServletRequest && response instanceof HttpServletResponse) {
            HttpServletRequest httpRequest = (HttpServletRequest) request;

            // 使用自定義 HttpServletRequestWrapper 包裝請求
            CustomHttpServletRequestWrapper wrappedRequest = new CustomHttpServletRequestWrapper(httpRequest);

            // 確保請求體內(nèi)容被讀取并緩存
            String originalBody = wrappedRequest.getBody();

            // 確認請求體內(nèi)容
            System.out.println("Original Request Body: " + originalBody);

            // 對內(nèi)容進行安全性處理:轉(zhuǎn)義HTML和JavaScript標簽
            String sanitizedBody = sanitizeBody(originalBody);

            // 將處理后的內(nèi)容作為新的輸入流返回
            ByteArrayInputStream byteArrayInputStream = new ByteArrayInputStream(sanitizedBody.getBytes(StandardCharsets.UTF_8));
            HttpServletRequest sanitizedRequest = new CustomHttpServletRequestWrapper(wrappedRequest) {
                @Override
                public ServletInputStream getInputStream() throws IOException {
                    return new CustomServletInputStream(byteArrayInputStream);
                }
            };

            // 繼續(xù)過濾鏈
            chain.doFilter(sanitizedRequest, response);
        } else {
            chain.doFilter(request, response);
        }
    }

    @Override
    public void init(FilterConfig filterConfig) throws ServletException {
        // 初始化Filter所需資源
    }

    @Override
    public void destroy() {
        // 釋放Filter所占用的資源
    }

    // 用于安全處理請求體內(nèi)容
    private String sanitizeBody(String originalBody) {
        String sanitizedBody = StringEscapeUtils.escapeHtml4(originalBody);
        sanitizedBody = sanitizedBody.replaceAll("(?i)<script", "<script")
                                     .replaceAll("(?i)</script", "</script");
        
        ObjectMapper objectMapper = new ObjectMapper();
        try {
            JsonNode jsonNode = objectMapper.readTree(originalBody);
            // 對 JSON 數(shù)據(jù)進行處理(比如移除不必要的字段)
            return objectMapper.writeValueAsString(jsonNode);
        } catch (IOException e) {
            e.printStackTrace();
            // 處理 JSON 解析異常
            return sanitizedBody;
        }
    }

    // 自定義ServletInputStream類,簡化流操作
    private static class CustomServletInputStream extends ServletInputStream {
        private final ByteArrayInputStream inputStream;

        public CustomServletInputStream(ByteArrayInputStream inputStream) {
            this.inputStream = inputStream;
        }

        @Override
        public int read() throws IOException {
            return inputStream.read();
        }

        @Override
        public boolean isFinished() {
            return inputStream.available() == 0;
        }

        @Override
        public boolean isReady() {
            return true;
        }

        @Override
        public void setReadListener(jakarta.servlet.ReadListener readListener) {
            // 讀取監(jiān)聽器設置,當前未實現(xiàn)
        }
    }

    // 自定義 HttpServletRequestWrapper 類
    private static class CustomHttpServletRequestWrapper extends HttpServletRequestWrapper {
        private final byte[] body;

        public CustomHttpServletRequestWrapper(HttpServletRequest request) throws IOException {
            super(request);
            // 讀取請求體內(nèi)容并緩存
            InputStream inputStream = request.getInputStream();
            body = inputStream.readAllBytes();
        }

        @Override
        public ServletInputStream getInputStream() throws IOException {
            return new CustomServletInputStream(new ByteArrayInputStream(body));
        }

        public String getBody() {
            return new String(body, StandardCharsets.UTF_8);
        }
    }
}
使用Spring Interceptor攔截請求Body

Spring的攔截器(Interceptor)是另一種攔截HTTP請求的方式。它比過濾器更接近Spring的處理機制。

package com.icoderoad.request_body.interceptor;

import java.io.BufferedReader;
import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.io.InputStreamReader;
import java.nio.charset.StandardCharsets;

import org.apache.commons.text.StringEscapeUtils;
import org.springframework.web.servlet.HandlerInterceptor;
import org.springframework.web.util.ContentCachingRequestWrapper;

import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.ObjectMapper;

import jakarta.servlet.ReadListener;
import jakarta.servlet.ServletInputStream;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletRequestWrapper;
import jakarta.servlet.http.HttpServletResponse;

public class RequestBodyInterceptor implements HandlerInterceptor {

    private static final ObjectMapper objectMapper = new ObjectMapper();

    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws IOException {
        // 使用 ContentCachingRequestWrapper 包裝 HttpServletRequest
        ContentCachingRequestWrapper cachingRequest = new ContentCachingRequestWrapper(request);

        // 讀取請求體內(nèi)容
        String originalBody = readRequestBody(cachingRequest);
        if (originalBody == null || originalBody.isEmpty()) {
            return true; // 如果沒有請求體,直接返回
        }

        System.out.println("Original Request Body: " + originalBody);

        // 處理請求體內(nèi)容
        String sanitizedBody = sanitizeBody(originalBody);

        // 使用自定義 HttpServletRequestWrapper 包裝請求
        HttpServletRequest wrappedRequest = new CustomHttpServletRequestWrapper(cachingRequest, sanitizedBody);

        // 替換請求對象
        request.setAttribute("wrappedRequest", wrappedRequest);

        return true;
    }

    private String readRequestBody(HttpServletRequest request) throws IOException {
        ContentCachingRequestWrapper wrapper = (ContentCachingRequestWrapper) request;
        byte[] buf = wrapper.getContentAsByteArray();
        if (buf.length > 0) {
            return new String(buf, 0, buf.length, wrapper.getCharacterEncoding());
        }
        return null;
    }

    private String sanitizeBody(String originalBody) {
        // 如果請求體是 JSON 格式,則對其進行特殊處理
        if (isJson(originalBody)) {
            try {
                JsonNode jsonNode = objectMapper.readTree(originalBody);
                // 對 JSON 數(shù)據(jù)進行處理(比如移除不必要的字段)
                return objectMapper.writeValueAsString(jsonNode);
            } catch (IOException e) {
                // 捕獲 JSON 解析異常,記錄錯誤并返回原始內(nèi)容
                e.printStackTrace();
                return originalBody; // 返回原始內(nèi)容
            }
        } else {
            // 對內(nèi)容進行安全性處理:轉(zhuǎn)義HTML和JavaScript標簽
            String sanitizedBody = StringEscapeUtils.escapeHtml4(originalBody);
            sanitizedBody = sanitizedBody.replaceAll("(?i)<script", "<script")
                                         .replaceAll("(?i)</script", "</script>");
            return sanitizedBody;
        }
    }

    private boolean isJson(String content) {
        // 簡單檢查內(nèi)容是否是 JSON 格式
        return content.trim().startsWith("{") || content.trim().startsWith("[");
    }

    private static class CustomHttpServletRequestWrapper extends HttpServletRequestWrapper {
        private final ByteArrayInputStream inputStream;

        public CustomHttpServletRequestWrapper(HttpServletRequest request, String body) {
            super(request);
            this.inputStream = new ByteArrayInputStream(body.getBytes(StandardCharsets.UTF_8));
        }

        @Override
        public ServletInputStream getInputStream() throws IOException {
            return new CustomServletInputStream(inputStream);
        }

        @Override
        public BufferedReader getReader() throws IOException {
            return new BufferedReader(new InputStreamReader(getInputStream(), StandardCharsets.UTF_8));
        }
    }

    private static class CustomServletInputStream extends ServletInputStream {
        private final ByteArrayInputStream inputStream;

        public CustomServletInputStream(ByteArrayInputStream inputStream) {
            this.inputStream = inputStream;
        }

        @Override
        public int read() throws IOException {
            return inputStream.read();
        }

        @Override
        public boolean isFinished() {
            return inputStream.available() == 0;
        }

        @Override
        public boolean isReady() {
            return true;
        }

        @Override
        public void setReadListener(ReadListener readListener) {
            // 讀取監(jiān)聽器設置,當前未實現(xiàn)
        }
    }
}

創(chuàng)建配置類來注冊攔截器

在你的 Spring Boot 項目中創(chuàng)建一個配置類,配置 RequestBodyInterceptor:

package com.icoderoad.request_body.config;

import java.util.List;

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.http.converter.AbstractHttpMessageConverter;
import org.springframework.http.converter.HttpMessageConverter;
import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;

import com.fasterxml.jackson.databind.ObjectMapper;
import com.icoderoad.request_body.converter.CustomHttpMessageConverter;
import com.icoderoad.request_body.interceptor.RequestBodyInterceptor;

@Configuration
public class WebConfig implements WebMvcConfigurer {

    @Override
    public void addInterceptors(InterceptorRegistry registry) {
        registry.addInterceptor(new RequestBodyInterceptor());
    }
    
    @Bean
    public CustomHttpMessageConverter customHttpMessageConverter(ObjectMapper objectMapper) {
        return new CustomHttpMessageConverter(objectMapper);
    }

    @Override
    public void configureMessageConverters(List<HttpMessageConverter<?>> converters) {
        // 移除默認的 Jackson 2 HttpMessageConverter
        converters.removeIf(converter -> converter instanceof AbstractHttpMessageConverter);
        // 添加自定義的 HttpMessageConverter
        converters.add(customHttpMessageConverter(new ObjectMapper()));
    }
}
使用自定義HttpMessageConverter

Spring提供了HttpMessageConverter來處理請求體的轉(zhuǎn)換。我們可以自定義一個HttpMessageConverter來攔截和修改請求體。

package com.icoderoad.request_body.converter;

import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.ObjectMapper;
import org.apache.commons.text.StringEscapeUtils;
import org.springframework.http.HttpInputMessage;
import org.springframework.http.HttpOutputMessage;
import org.springframework.http.MediaType;
import org.springframework.http.converter.AbstractHttpMessageConverter;

import java.io.IOException;
import java.nio.charset.StandardCharsets;

public class CustomHttpMessageConverter extends AbstractHttpMessageConverter<Object> {

    private final ObjectMapper objectMapper;

    public CustomHttpMessageConverter(ObjectMapper objectMapper) {
        super(MediaType.APPLICATION_JSON);
        this.objectMapper = objectMapper;
    }

    @Override
    protected boolean supports(Class<?> clazz) {
        return true; // 支持所有類
    }

    @Override
    protected Object readInternal(Class<? extends Object> clazz, HttpInputMessage inputMessage) throws IOException {
        String body = new String(inputMessage.getBody().readAllBytes(), StandardCharsets.UTF_8);

        // 處理請求體內(nèi)容
        String sanitizedBody = sanitizeBody(body);

        // 將處理后的內(nèi)容轉(zhuǎn)換為對象
        return objectMapper.readValue(sanitizedBody, clazz);
    }

    @Override
    protected void writeInternal(Object object, HttpOutputMessage outputMessage) throws IOException {
        // 將對象轉(zhuǎn)換為 JSON 字符串
        String json = objectMapper.writeValueAsString(object);

        // 輸出處理后的 JSON 字符串
        outputMessage.getBody().write(json.getBytes(StandardCharsets.UTF_8));
    }

    private String sanitizeBody(String originalBody) {
        // 如果請求體是 JSON 格式,則對其進行特殊處理
        if (originalBody.trim().startsWith("{") || originalBody.trim().startsWith("[")) {
            try {
                JsonNode jsonNode = objectMapper.readTree(originalBody);
                // 對 JSON 數(shù)據(jù)進行處理(比如移除不必要的字段)
                return objectMapper.writeValueAsString(jsonNode);
            } catch (IOException e) {
                // 捕獲 JSON 解析異常,記錄錯誤并返回原始內(nèi)容
                e.printStackTrace();
                return originalBody; // 返回原始內(nèi)容
            }
        } else {
            // 對內(nèi)容進行安全性處理:轉(zhuǎn)義HTML和JavaScript標簽
            String sanitizedBody = StringEscapeUtils.escapeHtml4(originalBody);
            sanitizedBody = sanitizedBody.replaceAll("(?i)<script", "<script")
                                         .replaceAll("(?i)</script", "</script>");
            return sanitizedBody;
        }
    }
}
在Controller中直接修改請求Body

最后一種方式是在Controller中直接讀取并修改請求體。

package com.icoderoad.request_body.controller;

import java.util.HashMap;
import java.util.Map;

import org.apache.commons.text.StringEscapeUtils;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RestController;

@RestController
public class RequestController {

    @PostMapping("/submit")
    public ResponseEntity<Map<String, String>> submit(@RequestBody Map<String, String> requestData) {
        String data = requestData.get("data");
        
        String sanitizedBody = StringEscapeUtils.escapeHtml4(data);
        sanitizedBody = sanitizedBody.replaceAll("(?i)<script", "<script")
                                     .replaceAll("(?i)</script", "</script");

        
        // 在此處理接收到的數(shù)據(jù)(例如存儲、驗證等)
        // 這里我們假設處理成功并返回一條消息

        Map<String, String> response = new HashMap<>();
        response.put("message", "接收到的數(shù)據(jù): " + sanitizedBody);

        // 返回200 OK響應和響應消息
        return ResponseEntity.ok(response);
    }
}

總結(jié)

以上介紹了在 Spring Boot3.3 中攔截和修改請求 Body 的多種方式,包括使用過濾器、攔截器、HttpMessageConverter 以及在控制器中直接修改請求體。每種方式都有其適用場景,可以根據(jù)實際需求選擇合適的方式。希望本文對大家在實際開發(fā)中有所幫助。

責任編輯:武曉燕 來源: 路條編程
相關(guān)推薦

2024-09-04 11:16:44

端口Spring配置類

2024-08-30 11:28:09

2024-09-06 10:05:47

SpELSpring權(quán)限

2024-09-03 10:44:32

2020-03-25 17:55:30

SpringBoot攔截器Java

2024-08-02 08:21:52

Spring項目方式

2011-02-23 10:35:04

Konqueror

2012-08-13 10:23:33

IBMdW

2024-09-09 11:35:35

2024-10-15 10:38:32

2023-03-10 22:14:49

KustomizeKubernetes

2024-09-05 09:35:58

CGLIBSpring動態(tài)代理

2024-09-26 09:28:06

內(nèi)存Spring

2018-09-17 08:31:08

容器Docker雪球

2018-06-19 08:12:25

2024-04-09 08:04:42

C#結(jié)構(gòu)await

2024-09-29 10:39:48

RSocketWebSocket通信

2009-11-23 17:16:54

PHP獲取IP

2024-01-23 08:47:13

BeanSpring加載方式

2023-09-14 08:16:51

點贊
收藏

51CTO技術(shù)棧公眾號