[Spring Cloud] gateway全局异常捕捉统一返回值
文章目录
- 处理转发失败的情况
 - 全局参数
 - 同一返回格式
 - 操作消息对象AjaxResult
 - 返回值状态描述对象AjaxStatus
 - 返回值枚举接口层StatusCode
 
- 全局异常处理器
 - 自定义通用异常
 - 定一个自定义异常
 - 覆盖默认的异常处理
 - 自定义异常处理工具
 
在上一篇章时我们有了一个简单的gateway网关
[Spring Cloud] gateway简单搭建与请求转发-CSDN博客
现在我们需要根据这个网关进行一部分的改进
现在我们需要进一步的处理一些问题,来使得网关更加完善。
 本篇文章的完整代码文件已放置在gitee。
 杉极简/gateway网关阶段学习
处理转发失败的情况
正常情况下,我们请求了一个接口,并得到了一个结果,如下:
 
 但是,我们依然要考虑,如果访问到一个不存在的接口,会得到什么样的结果?
 如果不做任何修改的时候,我们会得到以下的结果:
 
 但这不是我们想要的。
 我们想要如下返回结果。
 
 因此我们需要配置一个全局异常处理器来处理。
 为达到这个目的,我们开始构建一些基础的功能,描述如下。
全局参数
我们需要去创建一个全部配置,如下所示:
 这里统一存放着我们需要读取的配置,主要为全局使用的参数。具体配置方式如下所示:
 在网关项目中创建一个config文件夹,用于存放网关的相关配置。(当然,能想做网关的人,应该都熟练的会使用Spring Boot了,在Spring Boot项目中,这都是一些较为基础的内容)
 此时,我们先配置一个全局异常捕捉-打印堆栈异常的参数,用于本文的一些功能当中。
 
global:# 全局异常捕捉-打印堆栈异常printStackTrace: true
 
import lombok.Data;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.stereotype.Component;/*** @author fir* @date 2023/7/28 17:53*/
@Data
@Component
@ConfigurationProperties(prefix = "global")
public class GlobalConfig {/*** 全局异常捕捉-打印堆栈异常*/private boolean printStackTrace;
}
 

同一返回格式
创建一个result文件夹,用于存放统一返回值的相关配置
 该部分比较基础,暂时只说明配置方式与代码。
 
操作消息对象AjaxResult
import java.io.Serializable;
import java.util.HashMap;/*** 操作消息-JSON** @author fir*/
public class AjaxResult extends HashMap<String, Object> implements Serializable {private static final long serialVersionUID = 1L;/*** 状态码*/public static final String CODE_TAG = "code";/*** 返回内容*/public static final String MSG_TAG = "msg";/*** 数据对象*/public static final String DATA_TAG = "data";/*** 初始化一个新创建的 AjaxResult 对象,使其表示一个空消息。*/public AjaxResult() {}/*** 初始化一个新创建的 AjaxResult 对象** @param code 状态码* @param msg  状态描述*/public AjaxResult(int code, String msg) {super.put(CODE_TAG, code);super.put(MSG_TAG, msg);}/*** 初始化一个新创建的 AjaxResult 对象** @param code 状态码* @param msg  状态描述* @param data 数据对象*/public AjaxResult(int code, String msg, Object data) {super.put(CODE_TAG, code);super.put(MSG_TAG, msg);if (data != null) {super.put(DATA_TAG, data);}}/*** 返回成功消息** @return 成功消息*/public static AjaxResult success() {AjaxStatus success = AjaxStatus.SUCCESS;return AjaxResult.success(success.getCode(), success.getMsg());}/*** 返回成功数据** @return 成功消息*/public static AjaxResult success(Object data) {AjaxStatus success = AjaxStatus.SUCCESS;return AjaxResult.success(success.getMsg(), data);}/*** 返回成功数据** @return 成功消息*/public static AjaxResult success(AjaxStatus success) {return AjaxResult.success(success.getMsg(), new HashMap<>(0));}/*** 返回成功消息** @param code 状态吗* @param msg  状态描述* @return 消息体*/public static AjaxResult success(Integer code, String msg) {return new AjaxResult(code, msg);}/*** 返回成功消息** @param msg  状态描述* @param data 数据对象* @return 成功消息*/public static AjaxResult success(String msg, Object data) {AjaxStatus success = AjaxStatus.SUCCESS;return new AjaxResult(success.getCode(), msg, data);}/*** 返回特定状态描述** @param statusCode 特定的枚举结果* @param data       数据对象* @return 请求结果*/public static AjaxResult success(StatusCode statusCode, Object data) {return new AjaxResult(statusCode.getCode(), statusCode.getMsg(), data);}/*** 返回错误消息** @return 警告消息*/public static AjaxResult error() {return AjaxResult.error(AjaxStatus.LOSE_OPERATION.getMsg());}/*** 返回错误消息** @param msg 状态描述* @return 警告消息*/public static AjaxResult error(String msg) {return AjaxResult.error(msg, new HashMap<>(0));}/*** 返回错误消息** @param msg  状态描述* @param data 数据对象* @return 警告消息*/public static AjaxResult error(String msg, Object data) {AjaxStatus loseEfficacy = AjaxStatus.LOSE_EFFICACY;return new AjaxResult(loseEfficacy.getCode(), msg, data);}/*** 返回错误消息** @param code 状态码* @param msg  状态描述* @return 警告消息*/public static AjaxResult error(int code, String msg) {return new AjaxResult(code, msg, new HashMap<>(0));}/*** 返回特定状态描述** @param statusCode 特定的枚举结果* @param data       数据对象* @return 请求结果*/public static AjaxResult error(StatusCode statusCode, Object data) {return new AjaxResult(statusCode.getCode(), statusCode.getMsg(), data);}/*** 返回特定状态描述** @param statusCode 特定的枚举结果* @return 请求结果*/public static AjaxResult error(StatusCode statusCode) {return new AjaxResult(statusCode.getCode(), statusCode.getMsg(), new HashMap<>(0));}
} 
返回值状态描述对象AjaxStatus
import lombok.Getter;/*** 返回值状态与描述* * @author fir*/@Getter
public enum AjaxStatus implements StatusCode {/*** 请求成功*/SUCCESS(200, "请求成功"),/*** 登录成功*/SUCCESS_LOGIN(200, "登录成功"),/*** 登出成功*/SUCCESS_LOGOUT(200, "登出成功"),/*** 启动成功*/SUCCESS_FLOW_START(200, "启动成功"),/*** 暂无数据*/NO_DATA(200, "暂无数据"),/*** 错误请求*/BAD_REQUEST(400, "错误请求"),/*** 登录过期*/EXPIRATION_TOKEN(401, "登录过期"),/*** 服务不存在*/FAILED_SERVICE_DOES(404, "服务不存在"),/*** 账号或密码为空*/NULL_LOGIN_DATA(480, "账号或密码为空"),/*** 建立通信-通信建立失败*/FAILED_COMMUNICATION(481, "通信建立失败"),/*** 接口不存在*/NULL_API(404, "接口不存在"),/*** 建立通信-非法请求*/ILLEGAL_REQUEST(482, "非法请求"),/*** 请求失败*/LOSE_EFFICACY(490, "请求失败"),/*** 操作失败*/LOSE_OPERATION(491, "操作失败"),/*** 请求失败*/FAILED(500, "请求失败"),/*** 服务不可用(gateway网关总定义-所有未定义处理的异常都返回该异常)*/SERVICE_UNAVAILABLE(500, "服务不可用"),/*** 请求整体加密-无效会话*/SESSION_INVALID(601, "无效会话"),/*** 请求整体加密-会话过期*/SESSION_EXPIRE(602, "会话过期"),/*** 防重放校验失败*/ANTI_REPLAY_VERIFY_FAILED(701, "防重放校验失败"),/*** 防重放校验失败*/INTEGRITY_VERIFY_FAILED(801, "完整性校验失败"),/*** 预留*/PASS(1000, "请求失败");private final int code;private final String msg;AjaxStatus(int code, String msg) {this.code = code;this.msg = msg;}
}
 
返回值枚举接口层StatusCode
package com.fir.gateway.config.result;/*** 返回值枚举接口层* @author 18714*/
public interface StatusCode {/*** 获取code信息** @return code码*/int getCode();/*** 获取msg信息** @return msg描述*/String getMsg();
}
 
全局异常处理器
做两步
- 覆盖默认的异常处理。
 - 自定义异常处理。
 
自定义通用异常
通常的处理过程为抛出异常->全局异常捕捉->返回前端
 通常在代码中,对于某个特定的条件,我们抛出一个自定义异常,并携带特定的状态码与状态描述
if (session == null) {throw new CustomException(AjaxStatus.SESSION_INVALID);
}
 
而此时,我们在开发环境中通常需要显示堆栈异常,但是生成环境中,大多数是不需要的,此时我们就是用了全局参数配置在自定义异常工具这个类中,我们配置了定义异常默认不打印堆栈异常。
定一个自定义异常
import com.fir.gateway.config.result.AjaxStatus;
import lombok.Getter;/*** 自定义通用异常* 抛出异常->全局异常捕捉->返回前端** @author fir*/
@Getter
public class CustomException extends RuntimeException {/*** code状态码*/private final int code;/*** 错误状态码*/private final AjaxStatus ajaxStatus;public CustomException(AjaxStatus ajaxStatus) {super(ajaxStatus.getMsg());this.code = ajaxStatus.getCode();this.ajaxStatus = ajaxStatus;}}
 
覆盖默认的异常处理
此时我们覆盖默认的异常处理,并使用自定义的异常处理工具JsonExceptionHandler。
import org.springframework.beans.factory.ObjectProvider;
import org.springframework.boot.autoconfigure.web.ServerProperties;
import org.springframework.boot.autoconfigure.web.WebProperties;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.boot.web.reactive.error.ErrorAttributes;
import org.springframework.boot.web.reactive.error.ErrorWebExceptionHandler;
import org.springframework.context.ApplicationContext;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.Ordered;
import org.springframework.core.annotation.Order;
import org.springframework.http.codec.ServerCodecConfigurer;
import org.springframework.web.reactive.result.view.ViewResolver;import java.util.Collections;
import java.util.List;/*** 覆盖默认的异常处理* @author fir*/
@Configuration
@EnableConfigurationProperties({ServerProperties.class, WebProperties.class})
public class ErrorHandlerConfiguration {private final ServerProperties serverProperties;private final ApplicationContext applicationContext;private final WebProperties webProperties;private final List<ViewResolver> viewResolvers;private final ServerCodecConfigurer serverCodecConfigurer;public ErrorHandlerConfiguration(ServerProperties serverProperties,WebProperties webProperties,ObjectProvider<List<ViewResolver>> viewResolversProvider,ServerCodecConfigurer serverCodecConfigurer,ApplicationContext applicationContext) {this.serverProperties = serverProperties;this.applicationContext = applicationContext;this.webProperties = webProperties;this.viewResolvers = viewResolversProvider.getIfAvailable(Collections::emptyList);this.serverCodecConfigurer = serverCodecConfigurer;}@Bean@Order(Ordered.HIGHEST_PRECEDENCE)public ErrorWebExceptionHandler errorWebExceptionHandler(ErrorAttributes errorAttributes) {JsonExceptionHandler exceptionHandler = new JsonExceptionHandler(errorAttributes,this.webProperties,this.serverProperties.getError(),this.applicationContext);exceptionHandler.setViewResolvers(this.viewResolvers);exceptionHandler.setMessageWriters(this.serverCodecConfigurer.getWriters());exceptionHandler.setMessageReaders(this.serverCodecConfigurer.getReaders());return exceptionHandler;}}
 
自定义异常处理工具
解决三个问题
- 处理自定义异常
 - 处理不存在的接口
 - 对于其他的异常,统一返回一个指定的错误描述AjaxStatus.SERVICE_UNAVAILABLE。
 
package com.fir.gateway.config.exception;import com.fir.gateway.config.GlobalConfig;
import com.fir.gateway.config.result.AjaxResult;
import com.fir.gateway.config.result.AjaxStatus;
import lombok.extern.slf4j.Slf4j;
import org.springframework.boot.autoconfigure.web.ErrorProperties;
import org.springframework.boot.autoconfigure.web.WebProperties;
import org.springframework.boot.autoconfigure.web.reactive.error.DefaultErrorWebExceptionHandler;
import org.springframework.boot.web.reactive.error.ErrorAttributes;
import org.springframework.context.ApplicationContext;
import org.springframework.http.MediaType;
import org.springframework.web.reactive.function.BodyInserters;
import org.springframework.web.reactive.function.server.ServerRequest;
import org.springframework.web.reactive.function.server.ServerResponse;
import reactor.core.publisher.Mono;import javax.annotation.Resource;/*** 自定义异常处理工具* 异常时用JSON代替HTML异常信息** @author fir*/
@Slf4j
public class JsonExceptionHandler extends DefaultErrorWebExceptionHandler {/*** 网关参数配置*/@Resourceprivate GlobalConfig globalConfig;public JsonExceptionHandler(ErrorAttributes errorAttributes, WebProperties webProperties, ErrorProperties errorProperties, ApplicationContext applicationContext) {super(errorAttributes, webProperties.getResources(), errorProperties, applicationContext);log.info(String.valueOf(errorProperties));log.info(String.valueOf(errorAttributes));}/*** 重构方法,设置返回属性格式*/@Overrideprotected Mono<ServerResponse> renderErrorResponse(ServerRequest request) {Throwable errorThrowable = getError(request);// 自定义异常默认不打印堆栈异常// 决定是否打印堆栈异常boolean printStackTrace = globalConfig.isPrintStackTrace();if (printStackTrace) {errorThrowable.printStackTrace();}// 打印全局异常log.error(errorThrowable.getMessage());Class<?> errorClass = errorThrowable.getClass();String simpleName = errorClass.getSimpleName();AjaxStatus ajaxStatus;switch (simpleName) {case "CustomException":// 处理自定义异常CustomException customException = (CustomException) errorThrowable;ajaxStatus = customException.getAjaxStatus();break;case "NotFoundException":case "ResponseStatusException":// 处理404ajaxStatus = AjaxStatus.NULL_API;break;default:// 统一返回一个服务错误描述ajaxStatus = AjaxStatus.SERVICE_UNAVAILABLE;break;}AjaxResult result = AjaxResult.error(ajaxStatus);return ServerResponse.status(200).contentType(MediaType.APPLICATION_JSON).body(BodyInserters.fromValue(result));}
}
相关文章:
[Spring Cloud] gateway全局异常捕捉统一返回值
文章目录 处理转发失败的情况全局参数同一返回格式操作消息对象AjaxResult返回值状态描述对象AjaxStatus返回值枚举接口层StatusCode 全局异常处理器自定义通用异常定一个自定义异常覆盖默认的异常处理自定义异常处理工具 在上一篇章时我们有了一个简单的gateway网关 [Spring C…...
网络基础二——TCP可靠性实现机制补充2
验证客户端和服务端三次握手和四次挥手时的状态 三次握手 #include <sys/types.h> #include <sys/socket.h> int listen(int sockfd, int backlog);netstat ntp //查看连接的状态 将TCP服务端套接字设置为listen状态之后,此时服务端是处于L…...
SSM项目实战——哈哈音乐(四)前台模块开发
1、项目准备 ①导入依赖和前端资源 <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 http://maven.apache.org/maven-v4_0_0.x…...
Hadoop-入门
资料来源:尚硅谷-Hadoop 一、Hadoop 概述 1.1 Hadoop 是什么 1)Hadoop是一个由Apache基金会所开发的分布式系统基础架构。 2)主要解决:海量数据的存储和海量数据的分析计算问题。 3)广义上来说,Hadoop…...
HarmonyOS(鸿蒙)——单击事件
2.4 实现ClickedListener接口并重写onClick方法 2.5 实现onClick方法中的具体逻辑,以此完成点击事件的相关业务操作 三、测试 3.1 登录远程模拟器 3.2 运行项目 四、精选好文 一、简介 1.1 什么是组件 组件就是文本、按钮、图片等元素的统称 1.2 什么是事件 …...
c# wpf template itemtemplate+dataGrid
1.概要 2.代码 <Window x:Class"WpfApp2.Window8"xmlns"http://schemas.microsoft.com/winfx/2006/xaml/presentation"xmlns:x"http://schemas.microsoft.com/winfx/2006/xaml"xmlns:d"http://schemas.microsoft.com/expression/blend…...
总结UDP协议各类知识点
前言 本篇博客博主将详细地介绍UDP有关知识点,坐好板凳发车啦~ 一.UDP特点 1.无连接 UDP传输的过程类似于发短信,知道对端的IP和端口号就直接进行传输,不需要建立连接; 2.不可靠传输 没有任何的安全机制,发送端发…...
设计模式 --5观察者模式
观察者模式 观察者模式的优缺点 优点 当一个对象改变的时候 需要同时改变其他对象的相关动作的时候 ,而且它不知道有多少具体的对象需要改变 应该考虑使用观察者模式 。观察者模式的工作就是解除耦合 让耦合双方都依赖与抽象 而不是具体 是的各自改变都不会影响另…...
跨平台的组播测试工具mping、udp_sender及udp_reciver的源码及使用教程
文章目录 1.前言2.mping工具编译3.mping工具使用3.1 参数说明3.1 组播播发(-s)3.1 组播播发(-r)3.3 Linux下mping测试 4.Linux组播udp_sender及udp_reciver使用4.1 udp_sender源码4.1 udp_reciver源码4.3 编译方法4.4 测试使用4.4…...
Linux基础篇:文件系统介绍——根目录下文件夹含义与作用介绍
Linux文件系统介绍——文件夹含义与作用 Linux文件系统是一个组织和管理文件的层次结构。它包括了目录、子目录和文件,这些都是按照一定的规则和标准进行组织的。以下是Linux文件系统的一些关键组成部分: 1./bin: 该目录包含了系统启动和运…...
vulhub中Apache Solr RemoteStreaming 文件读取与SSRF漏洞复现
Apache Solr 是一个开源的搜索服务器。在Apache Solr未开启认证的情况下,攻击者可直接构造特定请求开启特定配置,并最终造成SSRF或任意文件读取。 访问http://your-ip:8983即可查看Apache Solr后台 1.访问http://your-ip:8983/solr/admin/cores?indexI…...
PHP在线加密系统网站源码
源码介绍 PHP在线加密系统网站源码,这个是sg的加密,免费可用(目前)并不会收费 源码说明:下载直接上传即可 下载地址 蓝奏云下载:https://wfr.lanzout.com/i6c331togiji...
【C++】哈希思想的应用(位图、布隆过滤器)及海量数据处理方法
文章目录 前言位图什么是位图简单实现一个自己的位图位图的应用场景 布隆过滤器位图的缺陷及布隆过滤器的提出布隆过滤器的概念简单实现一个自己的布隆过滤器布隆过滤器的优缺点布隆过滤器的应用场景 海量数据处理 前言 哈希思想的在实际中的应用除了哈希表这个数据结构之外还…...
蓝桥杯(5):python动态规划DF[2:背包问题]
1 0-1背包介绍【每件物品只能拿1件或者不拿】 1.1 简介 贪心是不可以的!!! 1.2 状态 及状态转移 转移解释:要么不选 则上一个直接转移过来【dp[i-1][j]】,要么是选这个之后体积为j 则上一个对应的就是【dp[i-1][j-wi]…...
臻奶惠无人售货机:新零售时代的便捷消费革命
臻奶惠无人售货机:新零售时代的便捷消费革命 在新零售的浪潮中,智能无人售货机作为一个创新的消费模式,已经成为距离消费者最近的便捷购物点之一。这种模式不仅能够满足居民对消费升级的需求,还能通过建立多样化和多层次的消费体…...
4月04日,每日信息差
🎖 素材来源官方媒体/网络新闻 🎄 地震预警App被曝收10元年费,回应称仅限苹果系统 🌍 2024清明档首日票房破2亿 🌋 浙江省杭州市余杭区设立2亿元网络微短剧发展基金 🎁 抖音拟以超 7.5 亿元收购海联金汇旗下…...
C++数据结构——顺序表——数值统计
C数据结构——顺序表——数值统计 接着上一篇的顺序表模板。 输入数组,统计数组中的负数、零、正数的个数。第一个数字,表示数组有几个数,当n为0时,输入结束,不做处理。 例如: 输入6 0 1 2 3 -1 0 输出1 2 3 int main() {int n;…...
Linux+HA高可用24X7的安全保证
一. 介绍作为服务器,需要提供一定的24X7的安全保证,这样可以防止关键节点的宕机引起系统的全面崩溃。利用OpenSource开源软件,完成系统的高可靠双机热备方案。基于linux的 HA软件可靠稳定,比使用商业版本的HA软件降低成…...
【Tomcat】Apache官方结束Tomcat 8.5分支版本技术支持
根据 Apache 官方发布的声明,Apache官方将于2024年3月31日后正式结束对于Tomcat 8.5这个分支版本的技术支持,包括以下几点: 1)不太可能继续为 8.5 分支发布新的版本; 2)仅影响 8.5 分支的漏洞将不会被解决&…...
Go 源码之读写锁 sync.RWMutex
Go 源码之读写锁 sync.RWMutex 文章目录 Go 源码之读写锁 sync.RWMutex一、简介二、源码(一)RWMutex数据结构(二)Lock(三)Unlock(四)TryRLock(五)Rlock(六)RUnlock三、常见问题1. 什么是CAS,什么是原子操作2. 写操作是如何阻止写操作的3. 写操作是如何阻止读操作的…...
Qt/C++开发监控GB28181系统/取流协议/同时支持udp/tcp被动/tcp主动
一、前言说明 在2011版本的gb28181协议中,拉取视频流只要求udp方式,从2016开始要求新增支持tcp被动和tcp主动两种方式,udp理论上会丢包的,所以实际使用过程可能会出现画面花屏的情况,而tcp肯定不丢包,起码…...
【人工智能】神经网络的优化器optimizer(二):Adagrad自适应学习率优化器
一.自适应梯度算法Adagrad概述 Adagrad(Adaptive Gradient Algorithm)是一种自适应学习率的优化算法,由Duchi等人在2011年提出。其核心思想是针对不同参数自动调整学习率,适合处理稀疏数据和不同参数梯度差异较大的场景。Adagrad通…...
日语学习-日语知识点小记-构建基础-JLPT-N4阶段(33):にする
日语学习-日语知识点小记-构建基础-JLPT-N4阶段(33):にする 1、前言(1)情况说明(2)工程师的信仰2、知识点(1) にする1,接续:名词+にする2,接续:疑问词+にする3,(A)は(B)にする。(2)復習:(1)复习句子(2)ために & ように(3)そう(4)にする3、…...
1688商品列表API与其他数据源的对接思路
将1688商品列表API与其他数据源对接时,需结合业务场景设计数据流转链路,重点关注数据格式兼容性、接口调用频率控制及数据一致性维护。以下是具体对接思路及关键技术点: 一、核心对接场景与目标 商品数据同步 场景:将1688商品信息…...
DBAPI如何优雅的获取单条数据
API如何优雅的获取单条数据 案例一 对于查询类API,查询的是单条数据,比如根据主键ID查询用户信息,sql如下: select id, name, age from user where id #{id}API默认返回的数据格式是多条的,如下: {&qu…...
JDK 17 新特性
#JDK 17 新特性 /**************** 文本块 *****************/ python/scala中早就支持,不稀奇 String json “”" { “name”: “Java”, “version”: 17 } “”"; /**************** Switch 语句 -> 表达式 *****************/ 挺好的ÿ…...
聊一聊接口测试的意义有哪些?
目录 一、隔离性 & 早期测试 二、保障系统集成质量 三、验证业务逻辑的核心层 四、提升测试效率与覆盖度 五、系统稳定性的守护者 六、驱动团队协作与契约管理 七、性能与扩展性的前置评估 八、持续交付的核心支撑 接口测试的意义可以从四个维度展开,首…...
10-Oracle 23 ai Vector Search 概述和参数
一、Oracle AI Vector Search 概述 企业和个人都在尝试各种AI,使用客户端或是内部自己搭建集成大模型的终端,加速与大型语言模型(LLM)的结合,同时使用检索增强生成(Retrieval Augmented Generation &#…...
Go 并发编程基础:通道(Channel)的使用
在 Go 中,Channel 是 Goroutine 之间通信的核心机制。它提供了一个线程安全的通信方式,用于在多个 Goroutine 之间传递数据,从而实现高效的并发编程。 本章将介绍 Channel 的基本概念、用法、缓冲、关闭机制以及 select 的使用。 一、Channel…...
MacOS下Homebrew国内镜像加速指南(2025最新国内镜像加速)
macos brew国内镜像加速方法 brew install 加速formula.jws.json下载慢加速 🍺 最新版brew安装慢到怀疑人生?别怕,教你轻松起飞! 最近Homebrew更新至最新版,每次执行 brew 命令时都会自动从官方地址 https://formulae.…...
