通用接口开放平台设计与实现——(31)API服务线程安全问题确认与修复

背景

在本系列的前面一篇博客评论中,有小伙伴指出,API服务存在线程安全问题:

https://blog.csdn.net/seawaving/article/details/122905199#comments_34477405

今天来确认下,线程是否安全?如不安全,如何修复?

回顾

先来回顾下先前的实现,可能存在线程安全的是自己实现的过滤器链,如下:

package tech.abc.platform.cip.api.framework;import tech.abc.platform.cip.common.entity.ApiRequest;
import tech.abc.platform.cip.common.entity.ApiResponse;import java.util.Arrays;
import java.util.Collections;
import java.util.Iterator;
import java.util.List;/*** API服务过滤器链条** @author wqliu* @date 2022-2-12**/
public class ApiFilterChain {/*** 请求*/private ApiRequest request;/*** 响应*/private ApiResponse response = new ApiResponse();/*** 过滤器集合*/private final List<ApiFilter> filters;/*** 过滤器迭代器*/private Iterator<ApiFilter> iterator;public ApiFilterChain() {filters = Collections.EMPTY_LIST;}public ApiFilterChain(ApiFilter... filters) {this.filters = Arrays.asList(filters);}/*** 获取请求** @return {@link ApiRequest}*/public ApiRequest getRequest() {return this.request;}/*** 获取响应** @return {@link ApiResponse}*/public ApiResponse getResponse() {return this.response;}/*** 执行过滤** @param request  请求* @param response 响应*/public void doFilter(ApiRequest request, ApiResponse response) {// 如迭代器为空,则初始化if (this.iterator == null) {this.iterator = this.filters.iterator();}// 集合中还有过滤器,则继续往下传递if (this.iterator.hasNext()) {ApiFilter nextFilter = this.iterator.next();nextFilter.doFilter(request, response, this);}// 将处理结果更新到属性中this.request = request;this.response = response;}/*** 重置*/public void reset() {this.request = null;this.response = null;this.iterator = null;}}

该类是参照官方的MockFilterChain实现的,源码如下:

/** Copyright 2002-2018 the original author or authors.** Licensed under the Apache License, Version 2.0 (the "License");* you may not use this file except in compliance with the License.* You may obtain a copy of the License at**      https://www.apache.org/licenses/LICENSE-2.0** Unless required by applicable law or agreed to in writing, software* distributed under the License is distributed on an "AS IS" BASIS,* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.* See the License for the specific language governing permissions and* limitations under the License.*/package org.springframework.mock.web;import java.io.IOException;
import java.util.Arrays;
import java.util.Collections;
import java.util.Iterator;
import java.util.List;import javax.servlet.Filter;
import javax.servlet.FilterChain;
import javax.servlet.FilterConfig;
import javax.servlet.Servlet;
import javax.servlet.ServletException;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;import org.springframework.lang.Nullable;
import org.springframework.util.Assert;
import org.springframework.util.ObjectUtils;/*** Mock implementation of the {@link javax.servlet.FilterChain} interface.** <p>A {@link MockFilterChain} can be configured with one or more filters and a* Servlet to invoke. The first time the chain is called, it invokes all filters* and the Servlet, and saves the request and response. Subsequent invocations* raise an {@link IllegalStateException} unless {@link #reset()} is called.** @author Juergen Hoeller* @author Rob Winch* @author Rossen Stoyanchev* @since 2.0.3* @see MockFilterConfig* @see PassThroughFilterChain*/
public class MockFilterChain implements FilterChain {@Nullableprivate ServletRequest request;@Nullableprivate ServletResponse response;private final List<Filter> filters;@Nullableprivate Iterator<Filter> iterator;/*** Register a single do-nothing {@link Filter} implementation. The first* invocation saves the request and response. Subsequent invocations raise* an {@link IllegalStateException} unless {@link #reset()} is called.*/public MockFilterChain() {this.filters = Collections.emptyList();}/*** Create a FilterChain with a Servlet.* @param servlet the Servlet to invoke* @since 3.2*/public MockFilterChain(Servlet servlet) {this.filters = initFilterList(servlet);}/*** Create a {@code FilterChain} with Filter's and a Servlet.* @param servlet the {@link Servlet} to invoke in this {@link FilterChain}* @param filters the {@link Filter}'s to invoke in this {@link FilterChain}* @since 3.2*/public MockFilterChain(Servlet servlet, Filter... filters) {Assert.notNull(filters, "filters cannot be null");Assert.noNullElements(filters, "filters cannot contain null values");this.filters = initFilterList(servlet, filters);}private static List<Filter> initFilterList(Servlet servlet, Filter... filters) {Filter[] allFilters = ObjectUtils.addObjectToArray(filters, new ServletFilterProxy(servlet));return Arrays.asList(allFilters);}/*** Return the request that {@link #doFilter} has been called with.*/@Nullablepublic ServletRequest getRequest() {return this.request;}/*** Return the response that {@link #doFilter} has been called with.*/@Nullablepublic ServletResponse getResponse() {return this.response;}/*** Invoke registered {@link Filter Filters} and/or {@link Servlet} also saving the* request and response.*/@Overridepublic void doFilter(ServletRequest request, ServletResponse response) throws IOException, ServletException {Assert.notNull(request, "Request must not be null");Assert.notNull(response, "Response must not be null");Assert.state(this.request == null, "This FilterChain has already been called!");if (this.iterator == null) {this.iterator = this.filters.iterator();}if (this.iterator.hasNext()) {Filter nextFilter = this.iterator.next();nextFilter.doFilter(request, response, this);}this.request = request;this.response = response;}/*** Reset the {@link MockFilterChain} allowing it to be invoked again.*/public void reset() {this.request = null;this.response = null;this.iterator = null;}/*** A filter that simply delegates to a Servlet.*/private static final class ServletFilterProxy implements Filter {private final Servlet delegateServlet;private ServletFilterProxy(Servlet servlet) {Assert.notNull(servlet, "servlet cannot be null");this.delegateServlet = servlet;}@Overridepublic void doFilter(ServletRequest request, ServletResponse response, FilterChain chain)throws IOException, ServletException {this.delegateServlet.service(request, response);}@Overridepublic void init(FilterConfig filterConfig) throws ServletException {}@Overridepublic void destroy() {}@Overridepublic String toString() {return this.delegateServlet.toString();}}}

这个类用在了我们API服务中,如下:

package tech.abc.platform.cip.api.service.impl;import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import tech.abc.platform.cip.api.exception.ApiException;
import tech.abc.platform.cip.api.framework.ApiFilterChain;
import tech.abc.platform.cip.api.framework.BasicValidateFilter;
import tech.abc.platform.cip.api.framework.BusinessFilter;
import tech.abc.platform.cip.api.framework.FrameworkValidateFilter;
import tech.abc.platform.cip.api.service.ApiRestService;
import tech.abc.platform.cip.common.entity.ApiRequest;
import tech.abc.platform.cip.common.entity.ApiResponse;
import tech.abc.platform.cip.enums.ApiServiceExecuteResultEnum;
import tech.abc.platform.cip.service.ApiServiceLogService;
import tech.abc.platform.common.exception.CustomException;import java.time.LocalDateTime;/*** '* API服务技术框架实现** @author wqliu* @date 2022-2-10**/
@Service
public class ApiRestServiceImpl implements ApiRestService {private ApiFilterChain filterChain;@Autowiredprivate ApiServiceLogService apiServiceLogService;public ApiRestServiceImpl(FrameworkValidateFilter frameworkValidateFilter,BusinessFilter businessFilter, BasicValidateFilter basicValidateFilter) {filterChain = new ApiFilterChain(basicValidateFilter, frameworkValidateFilter, businessFilter);}@Overridepublic ApiResponse handle(ApiRequest apiRequest) {LocalDateTime receiveTime = LocalDateTime.now();ApiResponse apiResponse = new ApiResponse();try {filterChain.doFilter(apiRequest, apiResponse);apiResponse = this.filterChain.getResponse();apiResponse.setExecuteResult(ApiServiceExecuteResultEnum.SUCCESS.name());} catch (CustomException ex) {// 自定义异常处理apiResponse.setExecuteResult(ApiServiceExecuteResultEnum.ERROR.name());apiResponse.setErrorCode("S00");apiResponse.setErrorMessage(ex.getMessage());} catch (ApiException ex) {// API异常处理apiResponse.setExecuteResult(ApiServiceExecuteResultEnum.ERROR.name());apiResponse.setErrorCode(ex.getErrorCode());apiResponse.setErrorMessage(ex.getMessage());} catch (Exception ex) {// 非预期异常处理apiResponse.setExecuteResult(ApiServiceExecuteResultEnum.ERROR.name());apiResponse.setErrorCode("S99");apiResponse.setErrorMessage("未定义异常:" + ex.getMessage());} finally {// 需要重置,为下次请求服务filterChain.reset();// 记录日志apiServiceLogService.recordLog(apiRequest, apiResponse, receiveTime);}return apiResponse;}
}

分析

其中ApiRestServiceImpl使用的@Service注解,Spring的默认处理是单例模式,ApiFilterChain是在构造函数中new出来的。线程安全的关键点,在于ApiFilterChain持有和保存了ApiRequest和ApiResponse对象。

从代码层面分析了一下,ApiFilterChain确实没有持有ApiRequest和ApiResponse对象的必要,通过方法接收ApiRequest对象,然后处理过程中修改ApiResponse对象,都是引用,没必要再保存一份。

至于当时为什么这么写,大概是参照官方MockFilterChain写法,高度信任而没有深度思考是否需要这么做。

验证

上面是从代码层面分析,接下来就动手验证下,线程安全问题是否存在,借此也夯实下实际工作中很少用到的多线程并发及线程安全基础。

如何验证呢?

其实思路也挺简单,发起多次接口调用,通过日志输出对象的HashCode,看看HashCode是否是同一个就好了。

使用Postman来做接口测试,调用平台内置的查询待处理消息的服务接口platform.message.query,如下:

注:为方便测试,把签名验证处理临时注释掉了,因此入参sign属性随便写了个1111,以通过非空验证。

在服务接口处理中添加日志输出,这里用error而不是info目的是更容易找到,如下:

然后使用postman发起两次接口调用,查看日志,如下:

可以看到,无论是ApiRestService,还是其所属的filterChain,哈希码是完全相同的,已经足以说明就是同一个对象,因此存在线程安全问题。当接口同时收到多个请求时,也就是多线程并发时,持有的请求对象和响应对象会混乱掉,是个大问题。

修正

既然问题已经确认,那接下来就修正它。

在前面分析环节,实际已经分析出来,filterChain并不需要持有请求对象和响应对象,去除掉后,就从根本上解决了线程安全问题,调整如下:

package tech.abc.platform.cip.api.framework;import tech.abc.platform.cip.common.entity.ApiRequest;
import tech.abc.platform.cip.common.entity.ApiResponse;import java.util.Arrays;
import java.util.Collections;
import java.util.Iterator;
import java.util.List;/*** API服务过滤器链条** @author wqliu* @date 2022-2-12**/
public class ApiFilterChain {/*** 过滤器集合*/private final List<ApiFilter> filters;/*** 过滤器迭代器*/private Iterator<ApiFilter> iterator;public ApiFilterChain() {filters = Collections.EMPTY_LIST;}public ApiFilterChain(ApiFilter... filters) {this.filters = Arrays.asList(filters);}/*** 执行过滤** @param request  请求* @param response 响应*/public void doFilter(ApiRequest request, ApiResponse response) {// 如迭代器为空,则初始化if (this.iterator == null) {this.iterator = this.filters.iterator();}// 集合中还有过滤器,则继续往下传递if (this.iterator.hasNext()) {ApiFilter nextFilter = this.iterator.next();nextFilter.doFilter(request, response, this);}}/*** 重置*/public void reset() {this.iterator = null;}}

测试功能正常,如下:

新的疑问点

在调整ApiFilterChain的过程中,去除了存在线程安全的ApiRequest和ApiResponse对象,同时发现还持有一个过滤器集合对象

private final List filters,该对象在构造方法中初始化,在接口服务的finally里执行reset清理工作,不过reset方法是重置过滤器集合的迭代器,而不是清空过滤器集合本身。

假设是多线程并发情况下,A、B两个请求先后到达,A请求处理结束了,调用reset清空了过滤器的迭代器,而B请求还在只走完了3个过滤器中的2个,会不会有问题呢?

按照前面的验证方法,输出哈希码,确定是同一个对象。

要做并发测试,比较麻烦,得辅助Jmeter等工具来实现了

这个地方高度怀疑存在线程安全问题,比较彻底的解决办法,就是把API服务变更为非单例模式。

彻底改造

接口服务对应的控制器中直接new对象,不使用依赖注入,如下;

@RestController
@RequestMapping("/api")
@Slf4j
public class ApiRestController {@PostMapping("/rest")@AllowAllpublic ResponseEntity<ApiResponse> post(@RequestBody ApiRequest apiRequest) {ApiRestService apiRestService = new ApiRestServiceImpl();ApiResponse apiResponse = apiRestService.handle(apiRequest);return new ResponseEntity<ApiResponse>(apiResponse, HttpStatus.OK);}}

ApiRestServiceImpl去除@Service注解,从而也不再是单例模式,调整构造方法,以及内部获取类的方式,如下:

package tech.abc.platform.cip.api.service.impl;import lombok.extern.slf4j.Slf4j;
import tech.abc.platform.cip.api.exception.ApiException;
import tech.abc.platform.cip.api.framework.ApiFilterChain;
import tech.abc.platform.cip.api.framework.BasicValidateFilter;
import tech.abc.platform.cip.api.framework.BusinessFilter;
import tech.abc.platform.cip.api.framework.FrameworkValidateFilter;
import tech.abc.platform.cip.api.service.ApiRestService;
import tech.abc.platform.cip.common.entity.ApiRequest;
import tech.abc.platform.cip.common.entity.ApiResponse;
import tech.abc.platform.cip.enums.ApiServiceExecuteResultEnum;
import tech.abc.platform.cip.service.ApiServiceLogService;
import tech.abc.platform.common.exception.CustomException;
import tech.abc.platform.common.utils.SpringUtil;import java.time.LocalDateTime;/*** '* API服务技术框架实现** @author wqliu* @date 2022-2-10**/@Slf4j
public class ApiRestServiceImpl implements ApiRestService {private ApiFilterChain filterChain;public ApiRestServiceImpl() {BusinessFilter businessFilter = SpringUtil.getBean(BusinessFilter.class);FrameworkValidateFilter frameworkValidateFilter = SpringUtil.getBean(FrameworkValidateFilter.class);BasicValidateFilter basicValidateFilter = SpringUtil.getBean(BasicValidateFilter.class);filterChain = new ApiFilterChain(basicValidateFilter, frameworkValidateFilter, businessFilter);}@Overridepublic ApiResponse handle(ApiRequest apiRequest) {LocalDateTime receiveTime = LocalDateTime.now();ApiResponse apiResponse = new ApiResponse();try {filterChain.doFilter(apiRequest, apiResponse);apiResponse.setExecuteResult(ApiServiceExecuteResultEnum.SUCCESS.name());} catch (CustomException ex) {// 自定义异常处理apiResponse.setExecuteResult(ApiServiceExecuteResultEnum.ERROR.name());apiResponse.setErrorCode("S00");apiResponse.setErrorMessage(ex.getMessage());} catch (ApiException ex) {// API异常处理apiResponse.setExecuteResult(ApiServiceExecuteResultEnum.ERROR.name());apiResponse.setErrorCode(ex.getErrorCode());apiResponse.setErrorMessage(ex.getMessage());} catch (Exception ex) {// 非预期异常处理apiResponse.setExecuteResult(ApiServiceExecuteResultEnum.ERROR.name());apiResponse.setErrorCode("S99");apiResponse.setErrorMessage("未定义异常:" + ex.getMessage());} finally {ApiServiceLogService apiServiceLogService = SpringUtil.getBean(ApiServiceLogService.class);// 记录日志apiServiceLogService.recordLog(apiRequest, apiResponse, receiveTime);}return apiResponse;}
}

运行,发现多次接口调用进行测试,每次接口调用,无论是ApiRestService还是ApiFilterChain,都不是同一个对象了,因此线程肯定是安全的了。

开源平台资料

平台名称:一二三开发平台
简介: 企业级通用开发平台
设计资料:[csdn专栏]
开源地址:[Gitee]
开源协议:MIT
如果您在阅读本文时获得了帮助或受到了启发,希望您能够喜欢并收藏这篇文章,为它点赞~
请在评论区与我分享您的想法和心得,一起交流学习,不断进步,遇见更加优秀的自己!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.xdnf.cn/news/1536961.html

如若内容造成侵权/违法违规/事实不符,请联系一条长河网进行投诉反馈,一经查实,立即删除!

相关文章

【AIGC】CFG:基于扩散模型分类器差异引导

摘要 分类器指导是最近引入的一种方法&#xff0c;在训练后在条件扩散模型中权衡模式覆盖率和样本保真度&#xff0c;在精神上与其他类型的生成模型中的低温采样或截断相同。分类器引导将扩散模型的分数估计与图像分类器的梯度相结合&#xff0c;因此需要训练与扩散模型分离的…

C#语言依然是主流的编程语言之一,不容置疑

C#语言是由微软在2000年发布的现代面向对象编程语言。尽管在编程语言市场中的占有率相对较低&#xff0c;但C#依然保持了强大的存在感&#xff0c;并未像一些其他语言那样逐渐被淘汰。C#语言不仅有其存在的独特理由&#xff0c;而且拥有许多令人无法忽视的优势。以下从多个方面…

积分电路和滤波电路的主要区别点和应用场合

文章目录 前言一、滤波电路的分类二、有源滤波器和无源滤波器的优缺点和实用范围三、积分电路3.1 无源积分电路3.2 RC充放电的电路响应3.2.1 RC电路的零状态响应3.2.2 RC电路的零输入响应3.2.3 RC电路的全响应3.2.4 选取合适的时间常数四 、无源RC低通滤波器4.3.1 截止频率推导…

kubernetes技术详解,带你深入了解k8s

目录 一、Kubernetes简介 1.1 容器编排应用 1.2 Kubernetes简介 1.3 k8s的设计架构 1.3.1 k8s各个组件的用途 1.3.2 k8s各组件之间的调用关系 1.3.3 k8s的常用名词概念 1.3.4 k8s的分层结构 二、k8s集群环境搭建 2.1 k8s中容器的管理方式 2.2 k8s环境部署 2.2.1 禁用…

VuePress搭建文档网站/个人博客(详细配置)主题配置-导航栏配置

天行健&#xff0c;君子以自强不息&#xff1b;地势坤&#xff0c;君子以厚德载物。 每个人都有惰性&#xff0c;但不断学习是好好生活的根本&#xff0c;共勉&#xff01; 文章均为学习整理笔记&#xff0c;分享记录为主&#xff0c;如有错误请指正&#xff0c;共同学习进步。…

针对国内AIGC市场,国内目前出台了那些法律法规?

针对国内AIGC市场&#xff0c;特别是AI生成与合成内容方面&#xff0c;中国已经出台了一系列法律法规来规范其发展和应用。 图片源自“央视新闻” 以下是一些主要的法律法规&#xff1a; 一、国家层面的法律法规 《中华人民共和国网络安全法》 施行时间&#xff1a;2017年6月…

进程监控与管理详解

一、进程的定义: 进程process是正在运行的程序,包括: 分配的内存地址空间 安全属性、包括所有权和特权 一个或多个线程 进程状态 进程的环境包括: 本地和全局变量 当前调度上下文…

java多线程编程示例

程序功能 程序展示了 Java 中如何使用多线程来并行执行任务。具体功能如下&#xff1a; 程序创建了三个线程&#xff0c;每个线程执行相同的任务类 Task。 每个线程在运行时输出自身名称&#xff0c;并模拟执行五次任务&#xff0c;每次任务间隔 1 秒。 主线程在启动这三个线程…

专业学习|系统动力学概观(方法特色、构成要素、建模步骤)

一、系统动力学概览 &#xff08;一&#xff09;系统动力学介绍 系统动力学是一门综合交叉学科&#xff0c;其基础理论涵盖了控制论、信息论和决策论等多个领域。它利用计算机仿真技术对复杂系统进行定量研究&#xff0c;尤其是那些具有非线性、高阶次和多重反馈特征的系统。这…

【可视化大屏系列】数据列表自动滚动效果

要实现列表的自动滚动效果&#xff0c;这里提供两种解决方案&#xff1a; 1.vue插件 官方文档&#xff1a;链接: vue-seamless-scroll &#xff08;1&#xff09;安装依赖 npm install vue-seamless-scroll --save&#xff08;2&#xff09;全局注册&#xff08;main.js中&a…

Mysql调优之性能监控(一)

前言&#xff1a; 官网就是最好的老师&#xff1a;MySQL&#xff0c;里面各种语法跟参数跟性能调试工具 一、使用show profile查询剖析工具 -- 开启 SET profiling 1; -- 关闭 SET profiling 0; -- 显示查询的性能分析信息 show profiles; -- 显示具体查询id的执行步骤耗时 S…

linux文件系统权限详解

注:目录的执行权限代表是否可以进入。 一、文件权限控制对文件的访问: 可以针对文件所属用户、所属组和其他用户可以设置不同的权限 权限具有优先级。user权限覆盖group权限,后者覆盖other权限。 有三种权限类别:读取、写入和执行 读权限:对文件:可读取文件…

Django_Vue3_ElementUI_Release_003_前端Vue3项目初始化

1. 概念扫盲 Node.js是基于ChromeV8引擎&#xff0c;让JS在服务端运行的开发平台&#xff0c;就是JS的一种解释器WebPack就是模块打包机&#xff0c;把浏览器不能直接运行的拓展语言找到并打包为合适的格式给浏览器直接使用Vue基于WebPack构件项目的&#xff0c;并带有合理默认…

【网络】TCP/IP 五层网络模型:网络层

最核心的就是 IP 协议&#xff0c;是一个相当复杂的协议 TCP 详细展开讲解&#xff0c;是因为 TCP 确实在开发中非常关键&#xff0c;经常用到&#xff0c;IP 则不同&#xff0c;和普通程序猿联系比较浅。和专门开发网络的程序猿联系比较紧密&#xff08;开发路由器&#xff0…

2022高教社杯全国大学生数学建模竞赛C题 问题一(2) Python代码演示

目录 1.2 结合玻璃的类型,分析文物样品表面有无风化化学成分含量的统计规律数据预处理绘图热力图相关系数图百分比条形图箱线图小提琴图直方图KED图描述性统计分析偏度系数峰度系数其它统计量1.2 结合玻璃的类型,分析文物样品表面有无风化化学成分含量的统计规律 数据预处理 …

使用java程序生成具有3D效果的图片

程序功能 图像初始化&#xff1a;创建一个800x800像素的 BufferedImage 对象&#xff0c;并使用 Graphics2D 进行绘图操作。 光照效果&#xff1a;使用 RadialGradientPaint 实现径向渐变效果&#xff0c;模拟从图片中心向外扩散的光照&#xff0c;使图片有深度感&#xff0c;…

Maven 解析:打造高效、可靠的软件工程

Apache Maven【简称maven】 是一个用于 Java 项目的构建自动化工具&#xff0c; 通过提供一组规则来管理项目的构建、依赖关系和文档。 1.Pre-预备知识&#xff1a; 1.1.Maven是什么&#xff1f; [by Maven是什么&#xff1f;有什么作用&#xff1f;Maven的核心内容简述_ma…

【数据结构与算法 | 灵神题单 | 自顶向下DFS篇】力扣1022,623

1. 力扣1022&#xff1a;从根到叶的二进制之和 1.1 题目&#xff1a; 给出一棵二叉树&#xff0c;其上每个结点的值都是 0 或 1 。每一条从根到叶的路径都代表一个从最高有效位开始的二进制数。 例如&#xff0c;如果路径为 0 -> 1 -> 1 -> 0 -> 1&#xff0c;那…

通过sshd_config限制用户登录

在CentOS Stream或其他现代的Linux发行版中&#xff0c;你可能会发现传统的hosts.deny和 hosts.allow文件已经不存在或不被使用。这是因为随着时间的推移&#xff0c;系统的安全策略和网络管理工具已经发生了演变&#xff0c;许多系统管理员和发行版维护者选择使用更现代、更灵…

Mac 上,终端如何开启 proxy

前提 确保你的浏览器可以访问 google&#xff0c;就是得先有这个能力 步骤 查看网络的 http/https 还有 socks5 的 port配置 .zshrc 查看 port 点击 wifi 设置 以我的为例&#xff0c;我的 http/https 都是 7890&#xff0c; socks5 是 7891 查看代理的port 以我的软件…