gateway路由过程

gateway是在DispatcherHandler.java类的handle()方法中对请求进行处理。这个方法其实就是对handlerMappingsprivate List<HandlerMapping> handlerMappings;集合进行迭代,调用每个mapping的getHandler()方法获取handler。

	public Mono<Void> handle(ServerWebExchange exchange) {
		if (logger.isDebugEnabled()) {
			ServerHttpRequest request = exchange.getRequest();
			logger.debug("Processing " + request.getMethodValue() + " request for [" + request.getURI() + "]");
		}
		if (this.handlerMappings == null) {
			return Mono.error(HANDLER_NOT_FOUND_EXCEPTION);
		}
		return Flux.fromIterable(this.handlerMappings)
				.concatMap(mapping -> mapping.getHandler(exchange))
				.next()
				.switchIfEmpty(Mono.error(HANDLER_NOT_FOUND_EXCEPTION))
				.flatMap(handler -> invokeHandler(exchange, handler))
				.flatMap(result -> handleResult(exchange, result));
	}

gateway中会用到的HandlerMapping为RoutePredicateHandlerMapping,因此这里就是调用RoutePredicateHandlerMapping中的getHandlerInternal()方法获取FilteringWebHandler。

	protected Mono<?> getHandlerInternal(ServerWebExchange exchange) {
		exchange.getAttributes().put(GATEWAY_HANDLER_MAPPER_ATTR, getClass().getSimpleName());

		return lookupRoute(exchange)
				// .log("route-predicate-handler-mapping", Level.FINER) //name this
				.flatMap((Function<Route, Mono<?>>) r -> {
					exchange.getAttributes().remove(GATEWAY_PREDICATE_ROUTE_ATTR);
					if (logger.isDebugEnabled()) {
						logger.debug("Mapping [" + getExchangeDesc(exchange) + "] to " + r);
					}

					exchange.getAttributes().put(GATEWAY_ROUTE_ATTR, r);
					return Mono.just(webHandler);
				}).switchIfEmpty(Mono.empty().then(Mono.fromRunnable(() -> {
					exchange.getAttributes().remove(GATEWAY_PREDICATE_ROUTE_ATTR);
					if (logger.isTraceEnabled()) {
						logger.trace("No RouteDefinition found for [" + getExchangeDesc(exchange) + "]");
					}
				})));
	}

而在getHandlerInternal()会通过lookupRoute()会获取所有路由,再使用每个路由的断言判断是否可以应用到当前的请求上,选择一个可以使用的路由。

	protected Mono<Route> lookupRoute(ServerWebExchange exchange) {
		return this.routeLocator.getRoutes()
				.filterWhen(route ->  {
					// add the current route we are testing
					exchange.getAttributes().put(GATEWAY_PREDICATE_ROUTE_ATTR, route.getId());
					return route.getPredicate().apply(exchange);
				})
				// .defaultIfEmpty() put a static Route not found
				// or .switchIfEmpty()
				// .switchIfEmpty(Mono.<Route>empty().log("noroute"))
				.next()
				//TODO: error handling
				.map(route -> {
					if (logger.isDebugEnabled()) {
						logger.debug("Route matched: " + route.getId());
					}
					validateRoute(route, exchange);
					return route;
				});

		/* TODO: trace logging
			if (logger.isTraceEnabled()) {
				logger.trace("RouteDefinition did not match: " + routeDefinition.getId());
			}*/
	}

路由的生成是在PropertiesRouteDefinitionLocator和InMemoryRouteDefinitionRepository对象中创建的。
PropertiesRouteDefinitionLocator获取的是GatewayProperties属性源中的routes集合中所有的RouteDefinition。RouteDefinition类中的属性跟配置文件中的配置项是对应的,也就是我们在yaml文件配置的路由的各项的值,这样的话我们每添加一个路由的配置就会生成一个RouteDefinition,被保存在GatewayProperties属性源的routes集合中private List<RouteDefinition> routes = new ArrayList<>();;

public class RouteDefinition {
	@NotEmpty
	private String id = UUID.randomUUID().toString();

	@NotEmpty
	@Valid
	private List<PredicateDefinition> predicates = new ArrayList<>();

	@Valid
	private List<FilterDefinition> filters = new ArrayList<>();

	@NotNull
	private URI uri;

	private int order = 0;
    ......
} 

获取到RouteDefinition后会在RouteDefinitionRouteLocator中的getRoutes()方法中将每个路由定义RouteDefinition转化成Route,这样我们就得到Route路由了。

	public Flux<Route> getRoutes() {
		return this.routeDefinitionLocator.getRouteDefinitions()
				.map(this::convertToRoute)
				//TODO: error handling
				.map(route -> {
					if (logger.isDebugEnabled()) {
						logger.debug("RouteDefinition matched: " + route.getId());
					}
					return route;
				});


		/* TODO: trace logging
			if (logger.isTraceEnabled()) {
				logger.trace("RouteDefinition did not match: " + routeDefinition.getId());
			}*/
	}

    private Route convertToRoute(RouteDefinition routeDefinition) {
		AsyncPredicate<ServerWebExchange> predicate = combinePredicates(routeDefinition);
		List<GatewayFilter> gatewayFilters = getFilters(routeDefinition);

		return Route.async(routeDefinition)
				.asyncPredicate(predicate)
				.replaceFilters(gatewayFilters)
				.build();
	}

拿到所有的Route路由之后,我们就可以获取断言判断能否应用到当前的请求route.getPredicate().apply(exchange);,从而找出一个可用的路由,再将获取到的路由保存到请求的属性GATEWAY_ROUTE_ATTR(“gatewayRoute”)中。

接下来就是FilteringWebHandler.java类中的handle()方法,先获取当前路由定义的过滤器,再获取系统中的全局过滤器,然后组合排序,再依次调用每个过滤器的filter()方法对请求进行过滤处理。

	public Mono<Void> handle(ServerWebExchange exchange) {
		Route route = exchange.getRequiredAttribute(GATEWAY_ROUTE_ATTR);
		List<GatewayFilter> gatewayFilters = route.getFilters();

		List<GatewayFilter> combined = new ArrayList<>(this.globalFilters);
		combined.addAll(gatewayFilters);
		//TODO: needed or cached?
		AnnotationAwareOrderComparator.sort(combined);

		logger.debug("Sorted gatewayFilterFactories: "+ combined);

		return new DefaultGatewayFilterChain(combined).filter(exchange);
	}

我们看下LoadBalancerClientFilter过滤器,这个过滤器可以实现微服务的调用,当发现调用的服务是以"lb"开头时,就会通过Ribbon负载均衡器获取相应的微服务地址进行接口调用。再往下就是http的请求调用了。

	public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
		URI url = exchange.getAttribute(GATEWAY_REQUEST_URL_ATTR);
		String schemePrefix = exchange.getAttribute(GATEWAY_SCHEME_PREFIX_ATTR);
		if (url == null || (!"lb".equals(url.getScheme()) && !"lb".equals(schemePrefix))) {
			return chain.filter(exchange);
		}
		//preserve the original url
		addOriginalRequestUrl(exchange, url);

		log.trace("LoadBalancerClientFilter url before: " + url);

		final ServiceInstance instance = loadBalancer.choose(url.getHost());

		if (instance == null) {
			throw new NotFoundException("Unable to find instance for " + url.getHost());
		}

		URI uri = exchange.getRequest().getURI();

		// if the `lb:<scheme>` mechanism was used, use `<scheme>` as the default,
		// if the loadbalancer doesn't provide one.
		String overrideScheme = null;
		if (schemePrefix != null) {
			overrideScheme = url.getScheme();
		}

		URI requestUrl = loadBalancer.reconstructURI(new DelegatingServiceInstance(instance, overrideScheme), uri);

		log.trace("LoadBalancerClientFilter url chosen: " + requestUrl);
		exchange.getAttributes().put(GATEWAY_REQUEST_URL_ATTR, requestUrl);
		return chain.filter(exchange);
	}

总结下,首先spring在启动时会根据配置文件创建路由的规则,当gateway收到请求,就会通过断言判断路由的规则是否能应用到当前的请求,然后将请求转发给路由中的uri,如果是微服务的话就会通过负载均衡动态的获取ip,之后就是过滤器对请求和响应进行处理。有不对的地方请大神指出,欢迎大家一起讨论交流,共同进步。

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
文名: TCP.IP路由技术[第二卷](CCIE职业发展系列) 原名: Routing TCP.IP,Volume 2 作者: Jeff Doyle译者: 毕立波资源格式: PDF 版本: 扫描版 出版社: 人民邮电出版社书号: 9787115100962发行时间: 2002年 地区: 大陆 语言: 简体文,英文 简介: 掌握BGP-4(事实上的域间路由协议标准)的操作、配置及故障检测与排除:理解NAT的操作、配置及故障检测与排除:   通过一系列案例研究及练习题来理解IP多播路由的部署、配置及故障检测与排除;   熟悉IPv6(下一代IP协议)的设计目标以及当前的发展状态;通过大最经专家验证的方法来管理路由器:   通过大量实用且全面的复习题、配置练习题及故障检测与排除练习题来测试和验证各种所学知识:   在掌握高级TCP/IP路由技术的同时。还可以进一步加强CCIE的认证准备工作。 为了管理日益增大的互连网络,需要全面理解路由器的操作行为,理解外部网关协议的各种复杂运行机制,包括TCP连接、消息状态、路径属性、内部路由协议互操作。以及建立邻居连接等内容。本书为读者全面理解BGP一4(边界网关协议版本4)、多播路由、NAT(网络地址转换)、IPv6,以及有效管理路由器等提供了各种翔实的专业知识。Jeff Doyle丰富的实践经验,易于阅读的写作风格及内容全面的论述。使得本书成为所有网络专家的案头宝典。 本书大大扩展了第一卷的主题内容:网络增长所带来的可扩展性和管理性要求。第二卷从第一卷的内部网关协议扩展到了自治系统间的路由协议,以及包括多播和IPv6在内的许多特殊路由问题,并且沿用了在第一卷所采取的有效的信息组织结构。即在讨论完主题基础知识之后,辅之以一系列能充分展现现实网络世界各种概念的配置案例。并通过各种经过验证的故障检测与排除方法来解决网络可能出现的各种问题。本书不但可以帮助广大读者在自己的名字之后获得极具价值的CCIE。号。而且还能帮助大家掌握现实网络所需的大量专家级网络知识和技巧。无论您是在准备CCIE认证考试。还是在准备CClE再认证考试,或是在寻求有关高级路由问题的专家建议。本书都将帮助您理解各种基本概念,并应用各种最佳实践技巧来应对网络的日益增长和有效管理等问题。内容简介本书在《TCP/IP路由技术(第一卷)》的基础上,深入系统地阐述了TCP/IP相关高级路由技术,包括BGP、多播、IPv6以及网络管理等内容。为便于读者深入掌握各章所学知识,本书提供了大量的案例分析材料,内容涵盖协议配置、故障检测与排除等方方面面。并且,在各章结束时都提供了大量的复习题和练习题,以加强读者对所学知识的记忆与理解。   本书除了面向众多备考的准CCIE以及需要通过再认证的CCIE之外,还非常适合从事大型IP网络规划、设计和实施工作的工程技术人员及网络管理员参考。 作者简介 Jeff Doyle,(CCIE#1919)是科罗拉多州丹佛市JuniperNetworks公司的专业服务咨询师,其主要研究方向是IP路由协议和MPLS流量工程技术。Jeff曾经设计和实现的大规模Intemet服务提供商网络遍及北美、欧洲和亚洲?并且在NANOG(NorthAmerican Network Operators’Group,北美网络运营团体)和APRICOT(Asia Pacific Regional Internet Conference onOperational Technologies,亚太地区互联网运营技术协会)等服务提供商论坛讲授高级网络互联技术。在加入Juniper Neworks公司之前,Jeff曾经是INS(Intemational Network Services,国际网络服务)公司的高级网络系统咨询师。 书摘与插图第1部分 外部网关协议   第1章 外部网关协议   聪明的读者一定会问(也应该问):“为什么要浪费纸张专门花一个章节来描述像EGP(Exterior Gateway Protocol,外部网关协议)这样一种已经被废除了的协议?”毕竟EGP已经被’BGP(Border-Gateway Protocol,边界网关协议)协议广泛替代了。该问题的答案有二。   首先,虽然目前已经很少有人在用EGP了,但某些特殊场合仍然会遇到。例如,在写作本书时仍然可以在某些美国军用互联网络见到EGP的影子。因而作为一名CCIE,仍然应该为这种罕见应用做好知识储备。   其次,本章内容作为一种历史铺垫,其目的是解释外部网关协议的发展驱动力,并说明最初的外部网关协议存在的不足,以便引出后面两章的内容。而且,在了解了BGP的演进根源之后,可以更好地认识到BGP的重要性。   1.1 EGP的起源   在20世纪80年代早期,构成.ARPANET(现代互联网的前身)的路由器(网关)设备上都运行了一种距离向量路由协议——GGP(Gateway-to-Gateway Protocol,网关到网关协议)。但是随着ARPANET的不断发展,与当今许多负责管理日益增长的互联网络的网管员一样,ARPANET的架构师们也预见到了相同的问题:现在运行的路由协议没有很好的扩展性。 目录: 第一部分 外部网关协议(EGP) 第1章 外部网关协议 2 1.1 EGP的起源 2 1.2 EGP的操作 3 1.2.1 EGP拓扑问题 3 1.2.2 EGP的功能 5 1.2.3 EGP消息格式 12 1.3 EGP的不足 18 1.4 配置EGP 19 1.4.1 案例研究:一个EGP末梢网关 19 1.4.2 案例研究:一个EGP核心网关 22 1.4.3 案例研究:间接邻居 25 1.4.4 案例研究:缺省路由 27 1.5 EGP的故障排除 28 1.5.1 解释邻居表 29 1.5.2 案例研究:聚合到Syrup的速度 30 1.6 尾注 31 1.7 展望 32 1.8 复习问题 32 1.9 配置练习 33 1.10 故障排除练习 36 第2章 BGP4简介 38 2.1 无类域间路由 38 2.1.1 归纳摘要 39 2.1.2 无类路由 40 2.1.3 路由总结:优势、劣势以及不对称性 43 2.1.4 Internet:经过多年后还保持着分层结构 45 2.1.5 CIDR:减轻了路由表的爆炸性增长 48 2.1.6 CIDR:降低了B类地址空间的消耗 51 2.1.7 CIDR遇到的问题 51 2.2 谁需要BGP 54 2.2.1 一个单宿主自治系统 55 2.2.2 多宿主到一个单一的AS 57 2.2.3 多宿主到多个自治系统 60 2.2.4 “负载均衡”应当注意的一个问题 62 2.2.5 BGP的危险 63 2.3 BGP基础知识 64 2.3.1 BGP消息类型 66 2.3.2 BGP有限状态机 67 2.3.3 路径属性 70 2.3.4 管理权值 78 2.3.5 AS_SET 79 2.3.6 BGP决策过程 80 2.3.7 路由抑制 82 2.4 IBGP和IGP的同步 83 2.5 管理大型BGP对等关系 88 2.5.1 对等组 88 2.5.2 团体 88 2.5.3 路由反射器 88 2.5.4 联盟 93 2.6 BGP消息格式 94 2.6.1 Open消息 95 2.6.2 Update消息 96 2.6.3 Keepalive消息 97 2.6.4 Notification消息 97 2.7 尾注 99 2.8 展望 99 2.9 推荐的读物 99 2.10 复习题 99 第3章 BGP4的配置以及故障排除 105 3.1 基本的BGP配置 105 3.1.1 案例研究:建立BGP路由器之间的对等 105 3.1.2 案例研究:向BGP注入IGP路由 110 3.1.3 案例研究:向IGP注入BGP路由 115 3.1.4 案例研究:没有IGP的IBGP 120 3.1.5 案例研究:IGP上的IBGP 126 3.1.6 案例研究:EBGP多跳 132 3.1.7 案例研究:聚合路由 135 3.1.8 管理BGP连接 150 3.2 路由策略 153 3.2.1 重置BGP连接 153 3.2.2 案例研究:通过NLRI过滤路由 155 3.2.3 案例研究:通过AS_PATH过滤路由 161 3.2.4 案例研究:通过路由图过滤路由 164 3.2.5 案例研究:管理权值 166 3.2.6 案例研究:管理距离以及后门路由 173 3.2.7 案例研究:使用LOCAL_PREF 属性 178 3.2.8 案例研究:使用MULTI_EXIT_DISC属性 182 3.2.9 案例分析:附加AS_PATH 187 3.2.10 案例分析:路由标记 190 3.2.11 案例分析:路由抑制 194 3.3 大型BGP 197 3.3.1 案例分析:BGP对等组 198 3.3.2 案例分析:BGP 团体 201 3.3.3 案例分析:专用AS号 212 3.3.4 案例分析:BGP 联盟 215 3.3.5 案例分析:路由反射器 225 3.4 展望 230 3.5 推荐的读物 230 3.6 命令归纳 231 3.7 配置练习 235 3.8 故障排除练习 240 第二部分 高级IP路由问题 第4章 网络地址翻译 250 4.1 NAT的操作 250 4.1.1 NAT的基本概念 250 4.1.2 NAT和IP地址的保存 252 4.1.3 NAT和ISP的变更 254 4.1.4 NAT和多宿主AS 255 4.1.5 端口地址翻译 257 4.1.6 NAT和TCP负载分配 258 4.1.7 NAT和虚拟服务器 259 4.2 NAT的问题 260 4.2.1 信头校验和 260 4.2.2 分段 260 4.2.3 加密 260 4.2.4 安全性 261 4.2.5 具体协议涉及到的问题 261 4.3 配置NAT 268 4.3.1 案例研究:静态NAT 268 4.3.2 案例研究:动态NAT 274 4.3.3 案例研究:网络合并 278 4.3.4 案例研究:用NAT实现ISP多宿 281 4.3.5 端口地址翻译 286 4.3.6 案例研究:TCP负载均衡 287 4.3.7 案例研究:服务分配 288 4.4 NAT故障排除 290 4.5 尾注 292 4.6 展望 292 4.7 命令归纳 292 4.8 配置练习 293 4.9 故障排除练习 295 第5章 IP多播路由介绍 297 5.1 对IP多播的要求 299 5.2 组成员概念 303 5.2.1 加入和退出组 304 5.2.2 因特网组管理协议(IGMP) 308 5.2.3 Cisco组员资格协议(CGMP) 313 5.3 多播路由的问题 320 5.3.1 多播的前转 320 5.3.2 多播路由 321 5.3.3 稀疏与密集拓扑的比较 322 5.3.4 隐式加入与显式加入的比较 323 5.3.5 基于源的树与共享树的比较 325 5.3.6 多播的范围 326 5.4 距离向量多播路由协议(DVMRP)的操作 329 5.4.1 对邻居的发现和维护 330 5.4.2 DVMRP路由表 330 5.4.3 DVMRP包的前转 332 5.4.4 DVMRP消息的格式 332 5.5 MOSPF的操作 338 5.5.1 MOSPF基础 339 5.5.2 区域间的MOSPF 340 5.5.3 AS间的MOSPF 342 5.5.4 MOSPF扩展的格式 343 5.6 基于核心的树(CBT)的操作 345 5.6.1 CBT基础 345 5.6.2 寻找核心 346 5.6.3 CBT指定路由器 347 5.6.4 成员与非成员的多播源 348 5.6.5 CBT消息格式 349 5.7 与协议无关的多播(PIM)的介绍 353 5.8 与协议无关多播,密集模式(PIM-DM)的操作 354 5.8.1 PIM-DM基础 354 5.8.2 Prune 消息的覆盖 359 5.8.3 单播路由的改变 361 5.8.4 PIM-DM指定路由器 361 5.8.5 PIM前转器的选举 361 5.9 与协议无关的多播,稀疏模式(PIM-SM)的操作 364 5.9.1 PIM-SM基础 364 5.9.2 查找会聚点 365 5.9.3 PIM-SM和共享树 367 5.9.4 源的注册 369 5.9.5 PIM-SM与最短路径树 375 5.9.6 PIMv2消息格式 379 5.10 尾注 385 5.11 展望 386 5.12 推荐读物 386 5.13 命令归纳 386 5.14 复习问题 388 第6章 IP多播路由的配置和故障排除 394 6.1 配置IP多播路由 394 6.2 案例研究:配置与协议无关多播,密集模式(PIM-DM) 395 6.3 配置与协议无关多播,稀疏模式(PIM-SM) 403 6.3.1 案例研究:静态配置RP 403 6.3.2 案例研究:配置Auto-RP 409 6.3.3 案例研究:配置稀疏——密集模式 416 6.3.4 案例研究:配置自举协议 419 6.4 案例研究:多播负荷分担 423 6.5 IP多播路由的故障排除 429 6.5.1 使用mrinfo 430 6.5.2 mtrace与mstat的使用 432 6.6 展望 436 6.7 配置练习 436 6.8 排错练习 438 第7章 大范围IP多播路由 441 7.1 多播范围控制 441 7.2 案例学习:多播穿过非多播域 443 7.3 连接到DVMRP网络 445 7.4 AS间多播 448 7.4.1 BGP的多协议扩展(MBGP) 450 7.4.2 多播源发现协议(MSDP)运行 451 7.4.3 MSDP消息格式 453 7.5 案例学习:配置MBGP 456 7.6 案例学习:配置MSDP 460 7.7 案例学习:MSDP全连接组 464 7.8 案例学习:泛播 RP 466 7.9 案例学习:MSDP缺省对等实体 470 7.10 命令归纳 473 7.11 尾注 474 7.12 展望 474 7.13 复习问题 474 第8章 IPv6 476 8.1 IPv6的设计目标 476 8.1.1 提高可扩展性 477 8.1.2 易于配置 477 8.1.3 安全性 478 8.2 当前IPv6状态 478 8.2.1 IPv6规范(RFC) 478 8.2.2 厂商支持 479 8.2.3 实现 479 8.3 IPv6包格式 480 8.3.1 IPv6地址 480 8.3.2 地址空间 481 8.3.3 地址的文字表示 481 8.3.4 地址前缀的文字表示 482 8.3.5 地址类型分配 482 8.4 地址结构 484 8.4.1 可聚合全球地址格式 484 8.4.2 IPv6头 493 8.5 IPv6功能 497 8.5.1 在Cisco路由器上使能IPv6能力 497 8.5.2 ICMPv6 498 8.5.3 邻居发现 499 8.5.4 自动配置 506 8.5.5 路由 509 8.5.6 泛播处理过程 521 8.5.7 多播 522 8.5.8 服务质量 526 8.6 从IPv4向IPv6过渡 526 8.6.1 双协议栈 527 8.6.2 DNS 527 8.6.3 IPv4的IPv6隧道 528 8.6.4 网络地址翻译-协议翻译 530 8.7 尾注 530 8.8 展望 530 8.9 推荐书目 531 8.10 复习问题 531 8.11 参考文献 533 第9章 路由器管理 535 9.1 规则和程序定义 536 9.1.1 服务等级协议 536 9.1.2 改变管理 536 9.1.3 扩大提交过程程序 538 9.1.4 更新规则 538 9.2 简单网络管理协议 538 9.2.1 SNMP概述 538 9.2.2 CiscoWorks 540 9.2.3 路由器的SNMP配置 540 9.3 RMON 545 9.3.1 RMON概述 545 9.3.2 路由器的RMON配置 546 9.4 记录日志 548 9.5 系统日志(Syslog) 551 9.5.1 Syslog概述 551 9.5.2 路由器上Syslog的配置 552 9.6 网络时间协议(NTP) 553 9.6.1 NTP概述 553 9.6.2 路由器的NTP配置 554 9.7 记账 557 9.7.1 IP记账 558 9.7.2 NetFlow 559 9.8 配置管理 564 9.9 故障管理 565 9.10 性能管理 567 9.11 安全管理 567 9.11.1 口令类型和加密 568 9.11.2 控制交互式访问 568 9.11.3 减少拒绝服务攻击的危险 569 9.11.4 TACACS+ 570 9.11.5 RADIUS 575 9.11.6 安全的命令解释器 576 9.12 设计支持管理程序的服务器 577 9.13 网络健壮性 577 9.13.1 HSRP 577 9.13.2 多组HSRP 578 9.13.3 配置HSRP 579 9.13.4 配置MHSRP 582 9.14 实验室 583 9.15 推荐书目 584 9.16 尾注 585 9.17 展望 585 9.18 命令归纳 585 9.19 复习问题 589 9.20 配置练习 590 9.21 参考文献 590 第三部分 附录 附录A show ip bgp neighbors的显示 594 附录B 正则表达式指南 599 附录C 保留的多播地址 603 附录D 复习问题的答案 619 附录E 配置练习的答案 631 附录F 故障排除练习答案 664

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值