gateway网关入门级学习
gateway
目录旁边可以查询具体的目录结构和跳转
一.网关介绍
这样的架构,会存在着诸多的问题:
1.每个业务都会需要鉴权、限流、权限校验、跨域等逻辑,如果每个业务都各自为战,自己造轮子实现一遍,会很蛋疼,完全可以抽出来,放到一个统一的地方去做。
2.如果业务量比较简单的话,这种方式前期不会有什么问题,但随着业务越来越复杂,比如淘宝、亚马逊打开一个页面可能会涉及到数百个微服务协同工作,如果每一个微服务都分配一个域名的话,一方面客户端代码会很难维护,涉及到数百个域名,另一方面是连接数的瓶颈,想象一下你打开一个APP,通过抓包发现涉及到了数百个远程调用,这在移动端下会显得非常低效。
3.后期如果需要对微服务进行重构的话,也会变的非常麻烦,需要客户端配合你一起进行改造,比如商品服务,随着业务变的越来越复杂,后期需要进行拆分成多个微服务,这个时候对外提供的服务也需要拆分成多个,同时需要客户端配合你进行改造,非常蛋疼。
上面的这些问题可以借助API网关来解决
所谓的 API网关,就是指系统的统一入口,它封装了应用程序的内部结构,为客户端提供统一服务,一些与业务本身功能无关的公共逻辑可以在这里实现,诸如认证、鉴权、监控、路由转发等等。
添加上API网关之后,系统的架构图变成了如下所示:
二.什么是Spring Cloud Gateway
网关作为流量的入口,常用的功能包括路由转发,权限校验,限流等。
Spring Cloud Gateway 是Spring Cloud官方推出的第二代网关框架,定位于取代 Netflix Zuul1.0。相比 Zuul 来说,Spring Cloud Gateway 提供更优秀的性能,更强大的有功能,非阻塞的。(Zuul1.0是阻塞,Zuul2.0是非阻塞,2.0的性能和gateway差不多)
Spring Cloud Gateway 是由 WebFlux + Netty + Reactor 实现的响应式的 API 网关。它不能在传统的 servlet 容器中工作,也不能构建成 war 包。
Spring Cloud Gateway 旨在为微服务架构提供一种简单且有效的 API 路由的管理方式,并基于 Filter 的方式提供网关的基本功能,例如说安全认证、监控、限流等等。
2.1 Spring Cloud Gateway 功能特征
- 基于Spring Framework 5, Project Reactor 和 Spring Boot 2.0 进行构建;
- 动态路由:能够匹配任何请求属性;
- 支持路径重写;
- 集成 Spring Cloud 服务发现功能(Nacos、Eruka);
- 可集成流控降级功能(Sentinel、Hystrix);
- 可以对路由指定易于编写的 Predicate(断言)和 Filter(过滤器);
2.2 核心概念
- 路由(route)
路由是网关中最基础的部分,路由信息包括一个ID、一个目的URI、一组断言工厂、一组Filter组成。如果断言为真,则说明请求的URL和
配置的路由匹配。
- 断言(predicates)
Java8中的断言函数,SpringCloud Gateway中的断言函数类型是Spring5.0框架中的ServerWebExchange。断言函数允许开发者去定义
匹配Http request中的任何信息,比如请求头和参数等。
- 过滤器(Filter)
SpringCloud Gateway中的filter分为Gateway FilIer和Global Filter。Filter可以对请求和响应进行处理。
2.3 工作原理
执行流程大体如下:
-
Gateway Client向Gateway Server发送请求
-
请求首先会被HttpWebHandlerAdapter进行提取组装成网关上下文
-
然后网关的上下文会传递到DispatcherHandler,它负责将请求分发给RoutePredicateHandlerMapping
-
RoutePredicateHandlerMapping负责路由查找,并根据路由断言判断路由是否可用
-
如果过断言成功,由FilteringWebHandler创建过滤器链并调用
-
请求会一次经过PreFilter--微服务--PostFilter的方法,最终返回响应
三.Spring Cloud Gateway快速开始
3.1引入依赖
<!‐‐ gateway网关 Spring Cloud开发 ‐‐> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring‐cloud‐starter‐gateway</artifactId> </dependency>
注意:会和spring-webmvc的依赖冲突,需要排除spring-webmvc
3.2yml配置
server: port: 8088 spring: application: name: api-gateway cloud: #gateway gateway: #路由规则 routes: - id: order_route #路由的唯一标识,例如路由到order uri: http://localhost:8020 #需要转发的地址 #断言规则 用于路由规则的匹配 predicates: - Path=/order‐serv/** #当请求路径满足Path指定的规则时,才进行路由转发 #http://localhost:8088/order‐serv/order/add ——>路由到 #http://localhost:8020/order‐serv/order/add filters: - StripPrefix=1 #路由转发之前去掉第一层路径,即/order‐serv/ #- id: stock_route
四.Gateway整合nacos
4.1导入依赖
<dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-gateway</artifactId> </dependency> <dependency> <groupId>com.alibaba.cloud</groupId> <artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId> </dependency>
四.Gateway整合nacos
方法一.(更灵活,有更高的使用性)
server: port: 8088 spring: application: name: api-gateway cloud: nacos: discovery: server-addr: 127.0.0.1:8848 username: nacos password: nacos #gateway gateway: #路由规则 routes: - id: order_route #路由的唯一标识,例如路由到order uri: lb://order-service #需要转发的地址 lb: 使用nacos中的本地负载均衡策略 #(我们直接写服务地址维护十分麻烦,通过nacos使用服务调用) #断言规则 用于路由规则的匹配 predicates: - Path=/order‐serv/** #当请求路径满足Path指定的规则时,才进行路由转发 #http://localhost:8088/order‐serv/order/add ——>路由到 #http://order-service/order‐serv/order/add ->filter后 #http://order-service/order/add filters: - StripPrefix=1 #路由转发之前去掉第一层路径,即/order‐serv/ #- id: stock_route
方法二.(更方便,nacos的简单配置方法)
server: port: 8088 spring: application: name: api-gateway cloud: nacos: discovery: server-addr: 127.0.0.1:8848 username: nacos password: nacos #gateway gateway: discovery: locator: enabled: true #是否启动自动识别nacos服务 #以nacos中注册的服务名为断言,且不需要配置filter,会自动去除对于的第一次断言。 #http://localhost:8088/order-service/order/add ->路由到 #http://order-service/order-service/order/add ->filter后 #http://order-service/order/add
五.内置路由断言工厂配置
(Route Predicate Factories)
作用: 当请求gateway的时候, 使用断言对请求进行匹配, 如果匹配成功就路由转发, 如果匹配失败就返回404类型:内置,自定义。
#gateway gateway: #路由规则 routes: - id: order_route #路由的唯一标识,例如路由到order uri: lb://order-service #需要转发的地址 lb: 使用nacos中的本地负载均衡策略 predicates: - Path=/order/** #http://localhost:8088/order/add ——>路由到 #http://order-service/order/add #- After=2027-12-31T23:59:59.789+08:00[Asia/Shanghai] #- Header=X-Request-Id,\d+ #请求头断言,例如这个:key为X-Request-Id,value为数字 #- Method=GET #请求方法断言 #- Query=name,zxy #http://localhost:8088/order/add?name=zxy
1.基于Datetime类型的断言工厂
此类型的断言根据时间做判断,主要有三个:
AfterRoutePredicateFactory: 接收一个日期参数,判断请求日期是否晚于指定日期
BeforeRoutePredicateFactory: 接收一个日期参数,判断请求日期是否早于指定日期
BetweenRoutePredicateFactory: 接收两个日期参数,判断请求日期是否在指定时间段内
ZonedDateTime.now()可以得到当下地区的时间
- After=2019-12-31T23:59:59.789+08:00[Asia/Shanghai]
2.基于远程地址的断言工厂
RemoteAddrRoutePredicateFactory:接收一个IP地址段,判断请求主机地址是否在地址段中。
- RemoteAddr=192.168.1.1/24
3.基于Cookie的断言工厂
CookieRoutePredicateFactory:接收两个参数,cookie 名字和一个正则表达式。 判断请求
cookie是否具有给定名称且值与正则表达式匹配。
- Cookie=chocolate, ch.
4.基于Header的断言工厂
HeaderRoutePredicateFactory:接收两个参数,标题名称和正则表达式。 判断请求Header是否具有给定名称且值与正则表达式匹配。
- Header=X-Request-Id,\d+ #后面正则表达式之前不能有无用的空格
5.基于Host的断言工厂
HostRoutePredicateFactory:接收一个参数,主机名模式。判断请求的Host是否满足匹配规则。
- Host=**.testhost.org
6.基于Method请求方法的断言工厂
MethodRoutePredicateFactory:接收一个参数,判断请求类型是否跟指定的类型匹配。
- Method=GET
7.基于Path请求路径的断言工厂
PathRoutePredicateFactory:接收一个参数,判断请求的URI部分是否满足路径规则。
- Path=/order/** #http://localhost:8088/order/add ——>路由到 #http://order-service/order/add
8.基于Query请求参数的断言工厂
QueryRoutePredicateFactory :接收两个参数,请求param和正则表达式, 判断请求参数是否具有给定名称且值与正则表达式匹配。
- Query=name,zxy #http://localhost:8088/order/add?name=zxy
9.基于路由权重的断言工厂
WeightRoutePredicateFactory:接收一个[组名,权重], 然后对于同一个组内的路由按照权重转发。
gateway: routes: - id: weight_route1 uri: host1 predicates: - Path=/product/** - Weight=group3, 1 - id: weight_route2 uri: host2 predicates: # 路径一致,组一致,权重不同 - Path=/product/** - Weight= group3, 9
六.简单的自定义断言
自定义路由断言工厂需要继承 AbstractRoutePredicateFactory 类,重写 apply 方法的逻辑。在 apply 方法中可以通过 exchange.getRequest() 拿到 ServerHttpRequest 对象,从而可以获取到请求的参数、请求方式、请求头等信息。
-
必须spring组件 bean
-
类必须加上RoutePredicateFactory作为结尾
-
必须继承AbstractRoutePredicateFactory
-
必须声明静态内部类 声明属性来接收 配置文件中对应的断言的信息
-
需要结合shortcutFieldOrder进行绑定
-
通过apply进行逻辑判断 true就是匹配成功 false匹配失败
ctrl+h 对AbstractRoutePredicateFactory找到任意子类作为模板修改自己的自定义断言
!!!自定义断言类名需要以 RoutePredicateFactory 结尾
@Component public class CheckAuthRoutePredicateFactory extends AbstractRoutePredicateFactory<CheckAuthRoutePredicateFactory.Config> { public CheckAuthRoutePredicateFactory() { super(CheckAuthRoutePredicateFactory.Config.class); } public List<String> shortcutFieldOrder() { //接受config的值 return Arrays.asList("name"); } public Predicate<ServerWebExchange> apply(CheckAuthRoutePredicateFactory.Config config) { return new GatewayPredicate() { public boolean test(ServerWebExchange exchange) { if(config.getName().equals("zxy")){ return true; } return false; } }; } //用于接受配置文件中 断言的信息 @Validated public static class Config { private String name; public String getName() { return name; } public void setName(String name) { this.name = name; } } }
yml配置
server: port: 8088 spring: application: name: api-gateway cloud: nacos: discovery: server-addr: 127.0.0.1:8848 username: nacos password: nacos #gateway gateway: #路由规则 routes: - id: order_route #路由的唯一标识,例如路由到order uri: lb://order-service #需要转发的地址 lb: 使用nacos中的本地负载均衡策略 predicates: - Path=/order/** - CheckAuth=zxy #自定义的简单断言
七.过滤器工厂配置
( GatewayFilter Factories)
1.内置的局部过滤器工厂配置
!!!一共有三十中配置
https://docs.spring.io/spring-cloud-gateway/docs/current/reference/html/#gatewayfilter-factories
(33条消息) 网关 Spring Cloud Gateway 内置过滤器工厂(超级全30种)_lakernote的博客-CSDN博客
常用的几种:
2.自定义局部过滤器工厂
@Component public class CheckAuthGatewayFilterFactory extends AbstractGatewayFilterFactory<CheckAuthGatewayFilterFactory.Config> { public CheckAuthGatewayFilterFactory() { super(Config.class); } @Override public List<String> shortcutFieldOrder() { return Arrays.asList("value"); } @Override public GatewayFilter apply(Config config) { return new GatewayFilter() { @Override public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) { String name=exchange.getRequest().getQueryParams().getFirst("name"); if(StringUtils.isNotBlank(name)){ if(config.getValue().equals(name)){ return chain.filter(exchange); } else { // 返回404 exchange.getResponse().setStatusCode(HttpStatus.NOT_FOUND); return exchange.getResponse().setComplete(); } } // 正常请求 return chain.filter(exchange); } }; } public static class Config { private String value; public String getValue() { return value; } public void setValue(String value) { this.value = value; } } }
filters: #- AddRequestHeader=X-Request-color,red #添加请求头 (请求过程) - PrefixPath=/mall‐order #添加前缀http://localhost:8088/order/add——> #http://localhost:8088/mall‐order/order/add #- RedirectTo=302,https://www.baidu.com/ #重定向 #- SetStatus=404 #修改状态码 - CheckAuth=xxx #自定义过滤器
3.全局过滤器(Global Filters)配置
局部过滤器和全局过滤器区别:
局部:局部针对某个路由, 需要在路由中进行配置
全局:针对所有路由请求, 一旦定义就会投入使用
GlobalFilter 接口和 GatewayFilter 有一样的接口定义,只不过, GlobalFilter 会作用于所有路由
LoadBalancerClientFilter
LoadBalancerClientFilter 会查看exchange的属性ServerWebExchangeUtils.GATEWAY_REQUEST_URL_ATTR 的值(一个URI),如果该值的scheme是 lb,比如:lb://myservice ,它将会使用Spring Cloud的LoadBalancerClient 来将 myservice 解析成实际的host和port,并替换掉 ServerWebExchangeUtils.GATEWAY_REQUEST_URL_ATTR 的内容。
routes: - id: order_route uri: lb://order-service #lb: 使用nacos中的本地负载均衡策略 predicates: - Path=/** filters: - PrefixPath=/mall‐order
还可以基于ReactiveLoadBalancerClientFilter用来整合负载均衡器Ribbon的。
还没写
自定义全局过滤器
@Component public class LogFilter implements GlobalFilter { Logger log= LoggerFactory.getLogger(this.getClass()); @Override public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) { log.info(exchange.getRequest().getPath().value()); return chain.filter(exchange); } }
八.访问日志和跨域配置
访问日志
要启用 Reactor Netty 访问日志,请设置
-Dreactor.netty.http.server.accessLogEnabled=true
它必须是 Java 系统属性,而不是 Spring Boot 属性。
您可以将日志记录系统配置为具有单独的访问日志文件。以下示例创建一个.logback.xml配置:
<appender name="accessLog" class="ch.qos.logback.core.FileAppender"> <file>access_log.log</file> <encoder> <pattern>%msg%n</pattern> </encoder> </appender> <appender name="async" class="ch.qos.logback.classic.AsyncAppender"> <appender‐ref ref="accessLog" /> </appender> <logger name="reactor.netty.http.server.AccessLog" level="INFO" additivity="false"> <appender‐ref ref="async"/> </logger> <!--输出为日志文本(可以搞也可以不搞,建议学习springboot日志)-->
开启日志后:
2023-04-13 23:22:27.946 INFO 18884 --- [ctor-http-nio-2] reactor.netty.http.server.AccessLog : 0:0:0:0:0:0:0:1 - - [13/Apr/2023:23:22:26 +0800] "GET /order/add HTTP/1.1" 200 29 8088 976 ms
跨域配置
方法一(yml配置)
spring: cloud: gateway: globalcors: cors-configurations: '[/**]': #允许跨域的资源 allowedOrigins: "https://docs.spring.io" #允许跨域的来源url allowedMethods: #允许跨域的请求方法 - GET allowedHeaders: '*' #允许跨域的请求头 # 预检请求的缓存时间(秒),即在这个时间段里对于相同的跨域请求不会再预检 maxAge: 180
方法二(java)
@Configuration public class CorsConfig { @Bean public CorsWebFilter corsFilter() { CorsConfiguration config = new CorsConfiguration(); // 允许cookies跨域 config.setAllowCredentials(true); // #允许向该服务器提交请求的URI,*表示全部允许,在SpringMVC中,如果设成*,会自动转成当前请求头中的Origin config.addAllowedOrigin("*"); // #允许访问的头信息,*表示全部 config.addAllowedHeader("*"); // 预检请求的缓存时间(秒)Long型,即在这个时间段里,对于相同的跨域请求不会再预检了 config.setMaxAge(180L); //允许的方法 可设置* 即允许全部http请求方法类型 config.addAllowedMethod("OPTIONS"); config.addAllowedMethod("HEAD"); config.addAllowedMethod("GET"); config.addAllowedMethod("PUT"); config.addAllowedMethod("POST"); config.addAllowedMethod("DELETE"); config.addAllowedMethod("PATCH"); UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource(new PathPatternParser()); source.registerCorsConfiguration("/**", config); return new CorsWebFilter(source); } }
九.Gateway整合sentinel
1.导入依赖
<dependency> <groupId>com.alibaba.cloud</groupId> <artifactId>spring-cloud-starter-alibaba-sentinel</artifactId> </dependency> <!--Sentinel整合gateway alibaba2.1之后一个依赖足够--> <dependency> <groupId>com.alibaba.cloud</groupId> <artifactId>spring-cloud-alibaba-sentinel-gateway</artifactId> </dependency>
2.添加yml配置
sentinel: transport: dashboard: 127.0.0.1:8858
控制台实现方式:
Sentinel 1.6.3 引入了网关流控控制台的支持,用户可以直接在 Sentinel 控制台上查看 API Gateway 实时的 route 和自定义 API 分组监控,管理网关规则和 API 分组配置。
从 1.6.0 版本开始,Sentinel 提供了 Spring Cloud Gateway 的适配模块,可以提供两种资源维度的限流:
route 维度:即在 Spring 配置文件中配置的路由条目,资源名为对应的 routeId
自定义 API 维度:用户可以利用 Sentinel 提供的 API 来自定义一些 API 分组
十.整合sentinel的详细配置
流控规则(根据路由ID)
1.属性限流Client IP
精确比如:127.0.0.1
2.属性限流Header
3.属性限流URL
4.属性限流Remote Host
5.属性限流 Cookie
降级规则(根据路由ID)
其他规则和sentinel一致
API分组
1.设置API分组
2.API分组流控
和根据路由ID流控规则一致
3.API分组降级
和根据路由ID降级规则一致
自定义流控降级返回
方法一(配置类)
@Configuration public class GatewayConfig { @PostConstruct public void init(){ BlockRequestHandler blockRequestHandler = new BlockRequestHandler() { @Override public Mono<ServerResponse> handleRequest(ServerWebExchange serverWebExchange, Throwable throwable) { System.out.println(throwable); HashMap<String,String> map = new HashMap<>(); map.put("code", HttpStatus.TOO_MANY_REQUESTS.toString()); map.put("message","限流了"); //自定义异常处理 return ServerResponse.status(HttpStatus.OK) .contentType(MediaType.APPLICATION_JSON) .body(BodyInserters.fromValue(map)); } }; GatewayCallbackManager.setBlockHandler(blockRequestHandler); } }
@Configuration public class GatewayConfig { @PostConstruct public void init(){ BlockRequestHandler blockRequestHandler = new BlockRequestHandler() { @Override public Mono<ServerResponse> handleRequest(ServerWebExchange exchange, Throwable e) { Result r = null; if (e instanceof FlowException) { r = Result.error(100,"接口限流了"); } else if (e instanceof DegradeException) { r = Result.error(101,"服务降级了"); } else if (e instanceof ParamFlowException) { r = Result.error(102,"热点参数限流了"); } else if (e instanceof SystemBlockException) { r = Result.error(103,"触发系统保护规则了"); } else if (e instanceof AuthorityException) { r = Result.error(104,"授权规则不通过"); } return ServerResponse.status(HttpStatus.OK) .contentType(MediaType.APPLICATION_JSON) .body(BodyInserters.fromValue(r)); } }; GatewayCallbackManager.setBlockHandler(blockRequestHandler); } }
public class Result<T> { private Integer code; private String msg; private T data; public Integer getCode() { return code; } public void setCode(Integer code) { this.code = code; } public String getMsg() { return msg; } public void setMsg(String msg) { this.msg = msg; } public T getData() { return data; } public void setData(T data) { this.data = data; } public Result(Integer code, String msg, T data) { this.code = code; this.msg = msg; this.data = data; } public Result(Integer code, String msg) { this.code = code; this.msg = msg; } public static Result error(Integer code,String msg){ return new Result(code,msg); } }
方法二(yml配置)
#配置sentinel sentinel: transport: dashboard: 127.0.0.1:8080 scg: fallback: #流控降级规则 mode: response response-body: "{code:'1002',message:'limit limit'}"
十一.网关高可用
为了保证 Gateway 的高可用性,可以同时启动多个 Gateway 实例进行负载,在 Gateway 的上游使用 Nginx 或者 F5 进行负载转发以达到高可用。
Gateway依赖和yml配置总合
server: port: 8088 spring: application: name: api-gateway cloud: nacos: discovery: server-addr: 127.0.0.1:8848 username: nacos password: nacos sentinel: transport: dashboard: 127.0.0.1:8858 scg: fallback: #流控降级规则 使用配置类更好 mode: response response-body: "{code:'1002',message:'limit limit'}" #gateway gateway: #路由规则 routes: - id: order_route #路由的唯一标识,例如路由到order uri: lb://order-service #需要转发的地址 lb: 使用nacos中的本地负载均衡策略 #(我们直接写服务地址维护十分麻烦,通过nacos使用服务调用) #断言规则 用于路由规则的匹配 predicates: - Path=/order‐serv/** #当请求路径满足Path指定的规则时,才进行路由转发 #http://localhost:8088/order‐serv/order/add ——>路由到 #http://order-service/order‐serv/order/add ->filter后 #http://order-service/order/add #http://localhost:8088/order/add ——>路由到 #http://order-service/order/add #- After=2027-12-31T23:59:59.789+08:00[Asia/Shanghai] #- Header=X-Request-Id,\d+ #请求头断言,例如这个:key为X-Request-Id,value为数字 #- Method=GET #请求方法断言 #- Query=name,zxy #http://localhost:8088/order/add?name=zxy #- CheckAuth=zxy #自定义的简单断言 filters: - StripPrefix=1 #路由转发之前去掉第一层路径,即/order‐serv/ #- AddRequestHeader=X-Request-color,red #添加请求头 (请求过程) - PrefixPath=/mall‐order #添加前缀http://localhost:8088/order/add——> #http://localhost:8088/mall‐order/order/add #- RedirectTo=302,https://www.baidu.com/ #重定向 #- SetStatus=404 #修改状态码 - CheckAuth=xxx #自定义过滤器 #- id: stock_route #gateway #gateway: # discovery: # locator: # enabled: true #是否启动自动识别nacos服务 globalcors: #跨域配置 cors-configurations: '[/**]': #允许跨域的资源 allowedOrigins: "https://docs.spring.io" #允许跨域的来源url allowedMethods: #允许跨域的请求方法 - GET allowedHeaders: '*' #允许跨域的请求头 # 预检请求的缓存时间(秒),即在这个时间段里对于相同的跨域请求不会再预检 maxAge: 180
依赖
<!‐‐ gateway网关 Spring Cloud开发 ‐‐> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring‐cloud‐starter‐gateway</artifactId> </dependency> <!‐‐ gateway网关整合nacos ‐‐> <dependency> <groupId>com.alibaba.cloud</groupId> <artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId> </dependency> <dependency> <groupId>com.alibaba.cloud</groupId> <artifactId>spring-cloud-starter-alibaba-sentinel</artifactId> </dependency> <!--Sentinel整合gateway alibaba2.1之后一个依赖足够--> <dependency> <groupId>com.alibaba.cloud</groupId> artifactId>spring-cloud-alibaba-sentinel-gateway</artifactId> </dependency>
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 阿里最新开源QwQ-32B,效果媲美deepseek-r1满血版,部署成本又又又降低了!
· SQL Server 2025 AI相关能力初探
· 单线程的Redis速度为什么快?
· AI编程工具终极对决:字节Trae VS Cursor,谁才是开发者新宠?
· 开源Multi-agent AI智能体框架aevatar.ai,欢迎大家贡献代码