SpringCloud源码剖析-Eureka Server的自动配置

简介: 这里和EureakClientAutoConfiguration差不多,都是由主启动类上的@SpringBootApplication标签中的@EnableAutoConfiguration启动自动配置,通过AutoConfigurationImportSelector来扫描classpath下的starter包中的自动配置类

前言

前面的章节我们针对于Eureak Client的初始化 ,服务注册 ,服务发现,服务续约,取消注册功能进行了分析,接下来我们围绕Eureka的核心功能对Server端进行分析,本章将会分析Eureka Server的启动过程。


1.EureakServerAutoConfiguration的注册

这里和EureakClientAutoConfiguration差不多,都是由主启动类上的@SpringBootApplication标签中的@EnableAutoConfiguration启动自动配置,通过AutoConfigurationImportSelector来扫描classpath下的starter包中的自动配置类

@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented@Inherited@SpringBootConfiguration@EnableAutoConfiguration//开启自动配置@ComponentScan(
excludeFilters= {@Filter(
type=FilterType.CUSTOM,
classes= {TypeExcludeFilter.class}
), @Filter(
type=FilterType.CUSTOM,
classes= {AutoConfigurationExcludeFilter.class}
)}
)
public@interfaceSpringBootApplication {
}

@EnableAutoConfiguration标签的源码

@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented@Inherited@AutoConfigurationPackage@Import({AutoConfigurationImportSelector.class})    //自动配置选择器public@interfaceEnableAutoConfiguration {
StringENABLED_OVERRIDE_PROPERTY="spring.boot.enableautoconfiguration";
Class<?>[] exclude() default {};
String[] excludeName() default {};
}

AutoConfigurationImportSelector导入选择器源码

publicclassAutoConfigurationImportSelectorimplementsDeferredImportSelector, BeanClassLoaderAware, ResourceLoaderAware,
BeanFactoryAware, EnvironmentAware, Ordered {
    ...省略部分代码...
@OverridepublicString[] selectImports(AnnotationMetadataannotationMetadata) {
if (!isEnabled(annotationMetadata)) {
returnNO_IMPORTS;
        }
AutoConfigurationMetadataautoConfigurationMetadata=AutoConfigurationMetadataLoader                .loadMetadata(this.beanClassLoader);
AnnotationAttributesattributes=getAttributes(annotationMetadata);
//1.这里会扫描spring.factories文件中的配置List<String>configurations=getCandidateConfigurations(annotationMetadata,
attributes);
configurations=removeDuplicates(configurations);
Set<String>exclusions=getExclusions(annotationMetadata, attributes);
checkExcludedClasses(configurations, exclusions);
configurations.removeAll(exclusions);
configurations=filter(configurations, autoConfigurationMetadata);
fireAutoConfigurationImportEvents(configurations, exclusions);
//2.加载一堆自动配置,其中就有 EureakServerAutoConfiguration returnStringUtils.toStringArray(configurations);
    }
//获取候选的自动配置protectedList<String>getCandidateConfigurations(AnnotationMetadatametadata,
AnnotationAttributesattributes) {
//通过SpringFactoriesLoader加载spring.spring.factories中的配置类List<String>configurations=SpringFactoriesLoader.loadFactoryNames(
getSpringFactoriesLoaderFactoryClass(), getBeanClassLoader());
Assert.notEmpty(configurations,
//从错误日志就可以看得出在扫描 META-INF/spring.factories"No auto configuration classes found in META-INF/spring.factories. If you "+"are using a custom packaging, make sure that file is correct.");
returnconfigurations;
    }
}

这里的 selectImports方法会通过getCandidateConfigurations方法使用SpringFactoriesLoader去加载classpath下的jar中的META-INF/spring.factories文件,该文件中有很多的自动配置,其中 EureakServerAutoConfiguration 就在spring-cloud-netflix-eureka-server-2.0.1.RELEASE.jar 中如下

2.EureakServerAutoConfiguration被加载的条件

EureakServerAutoConfiguration自动配置类上有一个注册条件

/*** @author Gunnar Hillert* @author Biju Kunjummen* @author Fahim Farook*/@Configuration//导入 EurekaServerInitializerConfiguration 对Eureak进行初始化@Import(EurekaServerInitializerConfiguration.class)
//条件:EurekaServerMarkerConfiguration.Marker是通过@EnableEureakServer激活@ConditionalOnBean(EurekaServerMarkerConfiguration.Marker.class)
//Eureka仪表盘界面配置,以及实例注册的配置@EnableConfigurationProperties({ EurekaDashboardProperties.class,
InstanceRegistryProperties.class })
//加载server.properties 服务端配置文件@PropertySource("classpath:/eureka/server.properties")
publicclassEurekaServerAutoConfigurationextendsWebMvcConfigurerAdapter {
}

@ConditionalOnBean(EurekaServerMarkerConfiguration.Marker.class) 这个条件是通过@EnableEureakServer开启,

/**激活 EurekaServerAutoConfiguration* Annotation to activate Eureka Server related configuration {@link EurekaServerAutoConfiguration}** @author Dave Syer* @author Biju Kunjummen**/@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented@Import(EurekaServerMarkerConfiguration.class)
public@interfaceEnableEurekaServer {
}

EnableEurekaServer的注释写的很清楚,该注解用来激活EureakServerAutoConfiguration配置,通过@Import(EurekaServerMarkerConfiguration.class)来激活的

/*** Responsible for adding in a marker bean to activate* {@link EurekaServerAutoConfiguration}** @author Biju Kunjummen*/@ConfigurationpublicclassEurekaServerMarkerConfiguration {
@BeanpublicMarkereurekaServerMarkerBean() {
returnnewMarker();
    }
classMarker {
    }
}

看到这里就清楚了,EnableEurekaServer通过@Import(EurekaServerMarkerConfiguration.class)导入EurekaServerMarkerConfiguration.Marker,而在EureakServerAutoConfiguration配置类上正好有一个条件

@ConditionalOnBean(EurekaServerMarkerConfiguration.Marker.class),满足这个条件,自动配置被加载,他们的加载关系如下:

3.EureakServerAutoConfiguration配置了什么?

EureakServerAutoConfiguration配置了那些东西?我们来看一下源码,哦~~~,特别说明一下,服务端的pom我用的是spring-cloud-starter-netflix-eureka-server,该依赖会把客户端的包spring-cloud-starter-netflix-eureka-client也导入进来(Eureka集群需要互相注册),所以你会看到下面的配置类中也会有eureka client的配置

/*** @author Gunnar Hillert* @author Biju Kunjummen* @author Fahim Farook*/@Configuration//导入 EurekaServerInitializerConfiguration 对Eureak进行初始化@Import(EurekaServerInitializerConfiguration.class)
//条件:EurekaServerMarkerConfiguration.Marker是通过@EnableEureakServer激活@ConditionalOnBean(EurekaServerMarkerConfiguration.Marker.class)
//Eureka仪表盘界面配置,以及实例注册的配置@EnableConfigurationProperties({ EurekaDashboardProperties.class,
InstanceRegistryProperties.class })
//加载server.properties 服务端配置文件@PropertySource("classpath:/eureka/server.properties")
publicclassEurekaServerAutoConfigurationextendsWebMvcConfigurerAdapter {
/*** List of packages containing Jersey resources required by the Eureka server*/privatestaticfinalString[] EUREKA_PACKAGES=newString[] { "com.netflix.discovery",
"com.netflix.eureka" };
@AutowiredprivateApplicationInfoManagerapplicationInfoManager;
//eureka server服务端配置对象加载 eureka.server 开头的配置项//里面记录了了EurekaServer所需要的配置@AutowiredprivateEurekaServerConfigeurekaServerConfig;
//eureka client 客户端配置对象 加载eureka.client开头的配置项@AutowiredprivateEurekaClientConfigeurekaClientConfig;
//Eureka客户端@AutowiredprivateEurekaClienteurekaClient;
//实例注册表属性配置,加载eureka.instance.registry开头的配置@AutowiredprivateInstanceRegistryPropertiesinstanceRegistryProperties;
publicstaticfinalCloudJacksonJsonJACKSON_JSON=newCloudJacksonJson();
@BeanpublicHasFeatureseurekaServerFeature() {
returnHasFeatures.namedFeature("Eureka Server",
EurekaServerAutoConfiguration.class);
    }
//配置EurekaServerConfig ,Eureka服务器配置,加载eureka.server开头配置@ConfigurationprotectedstaticclassEurekaServerConfigBeanConfiguration {
@Bean@ConditionalOnMissingBeanpublicEurekaServerConfigeurekaServerConfig(EurekaClientConfigclientConfig) {
EurekaServerConfigBeanserver=newEurekaServerConfigBean();
//如果开启了eureka.client.registerWithEureka=true 注册到Eurekaif (clientConfig.shouldRegisterWithEureka()) {
// Set a sensible default if we are supposed to replicate//当eureka服务器启动时尝试去获取集群里其他服务器上的注册信息的次数,默认为5server.setRegistrySyncRetries(5);
            }
returnserver;
        }
    }
//定义Eureka Server dashboard仪表盘监控界面的端点,注册中心的监控界面就是在这里定义的默认是“/”@Bean@ConditionalOnProperty(prefix="eureka.dashboard", name="enabled", matchIfMissing=true)
publicEurekaControllereurekaController() {
returnnewEurekaController(this.applicationInfoManager);
    }
static {
CodecWrappers.registerWrapper(JACKSON_JSON);
EurekaJacksonCodec.setInstance(JACKSON_JSON.getCodec());
    }
@BeanpublicServerCodecsserverCodecs() {
returnnewCloudServerCodecs(this.eurekaServerConfig);
    }
privatestaticCodecWrappergetFullJson(EurekaServerConfigserverConfig) {
CodecWrappercodec=CodecWrappers.getCodec(serverConfig.getJsonCodecName());
returncodec==null?CodecWrappers.getCodec(JACKSON_JSON.codecName()) : codec;
    }
privatestaticCodecWrappergetFullXml(EurekaServerConfigserverConfig) {
CodecWrappercodec=CodecWrappers.getCodec(serverConfig.getXmlCodecName());
returncodec==null?CodecWrappers.getCodec(CodecWrappers.XStreamXml.class)
                : codec;
    }
classCloudServerCodecsextendsDefaultServerCodecs {
publicCloudServerCodecs(EurekaServerConfigserverConfig) {
super(getFullJson(serverConfig),
CodecWrappers.getCodec(CodecWrappers.JacksonJsonMini.class),
getFullXml(serverConfig),
CodecWrappers.getCodec(CodecWrappers.JacksonXmlMini.class));
        }
    }
// 注册PeerAwareInstanceRegistry,应用对象注册表接口,提供了Eureka群内注册信息的同步功能。@BeanpublicPeerAwareInstanceRegistrypeerAwareInstanceRegistry(
ServerCodecsserverCodecs) {
//客户端获取注册列表,强制初始化EurekaClientthis.eurekaClient.getApplications(); // force initialization//创建实例注册器InstanceRegistry,继承了PeerAwareInstanceRegistry,PeerAwareInstanceRegistryimpl的实现returnnewInstanceRegistry(this.eurekaServerConfig, this.eurekaClientConfig,
serverCodecs, this.eurekaClient,
//期望最大每分钟续租次数this.instanceRegistryProperties.getExpectedNumberOfRenewsPerMin(),
this.instanceRegistryProperties.getDefaultOpenForTrafficCount());
    }
//PeerEurekaNode的工具类,用来管理PeerEurekaNode的生命周期,PeerEurekaNode表示集群中的一个节点@Bean@ConditionalOnMissingBeanpublicPeerEurekaNodespeerEurekaNodes(PeerAwareInstanceRegistryregistry,
ServerCodecsserverCodecs) {
returnnewRefreshablePeerEurekaNodes(registry, this.eurekaServerConfig,
this.eurekaClientConfig, serverCodecs, this.applicationInfoManager);
    }
/**//可刷新的集群节点,通过监听EnvironmentChangeEvent环境改变事件,如果特点的配置改变如://eureka.client.region,eureka.client.service-url配置改变就刷新集群中的节点信息* {@link PeerEurekaNodes} which updates peers when /refresh is invoked.* Peers are updated only if* <code>eureka.client.use-dns-for-fetching-service-urls</code> is* <code>false</code> and one of following properties have changed.* </p>* <ul>* <li><code>eureka.client.availability-zones</code></li>* <li><code>eureka.client.region</code></li>* <li><code>eureka.client.service-url.&lt;zone&gt;</code></li>* </ul>*/staticclassRefreshablePeerEurekaNodesextendsPeerEurekaNodesimplementsApplicationListener<EnvironmentChangeEvent> {
publicRefreshablePeerEurekaNodes(
finalPeerAwareInstanceRegistryregistry,
finalEurekaServerConfigserverConfig,
finalEurekaClientConfigclientConfig, 
finalServerCodecsserverCodecs,
finalApplicationInfoManagerapplicationInfoManager) {
super(registry, serverConfig, clientConfig, serverCodecs, applicationInfoManager);
        }
//监听事件@OverridepublicvoidonApplicationEvent(finalEnvironmentChangeEventevent) {
//如果配置发生改变if (shouldUpdate(event.getKeys())) {
//更新集群中的节点,通过删除旧的不可用的PeerEurekaNode,创建新的副本updatePeerEurekaNodes(resolvePeerUrls());
            }
        }
/*检查是否需要更新节点,检查特点的配置是否有改变* Check whether specific properties have changed.*/protectedbooleanshouldUpdate(finalSet<String>changedKeys) {
assertchangedKeys!=null;
// if eureka.client.use-dns-for-fetching-service-urls is true, then// service-url will not be fetched from environment.if (clientConfig.shouldUseDnsForFetchingServiceUrls()) {
returnfalse;
            }
if (changedKeys.contains("eureka.client.region")) {
returntrue;
            }
for (finalStringkey : changedKeys) {
// property keys are not expected to be null.if (key.startsWith("eureka.client.service-url.") ||key.startsWith("eureka.client.availability-zones.")) {
returntrue;
                }
            }
returnfalse;
        }
    }
//EurekaServer的上下文对象@BeanpublicEurekaServerContexteurekaServerContext(ServerCodecsserverCodecs,
PeerAwareInstanceRegistryregistry, PeerEurekaNodespeerEurekaNodes) {
returnnewDefaultEurekaServerContext(this.eurekaServerConfig, serverCodecs,
registry, peerEurekaNodes, this.applicationInfoManager);
    }
//EurekaServerBootstrap  :EurekaServer的启动引导@BeanpublicEurekaServerBootstrapeurekaServerBootstrap(PeerAwareInstanceRegistryregistry,
EurekaServerContextserverContext) {
returnnewEurekaServerBootstrap(this.applicationInfoManager,
this.eurekaClientConfig, this.eurekaServerConfig, registry,
serverContext);
    }
/**注册 Jersey filter ,通过ServletContainer处理/eureka/*请求* Register the Jersey filter*/@BeanpublicFilterRegistrationBeanjerseyFilterRegistration(
javax.ws.rs.core.ApplicationeurekaJerseyApp) {
FilterRegistrationBeanbean=newFilterRegistrationBean();
//使用ServletContainer处理bean.setFilter(newServletContainer(eurekaJerseyApp));
bean.setOrder(Ordered.LOWEST_PRECEDENCE);
//filter的拦截url   /eureka/*bean.setUrlPatterns(
Collections.singletonList(EurekaConstants.DEFAULT_PREFIX+"/*"));
returnbean;
    }
/**创建用Eureka服务器所需的所有资源,构建Jersey {@link javax.ws.rs.core.Application}。* Construct a Jersey {@link javax.ws.rs.core.Application} with all the resources* required by the Eureka server.*/@Beanpublicjavax.ws.rs.core.ApplicationjerseyApplication(Environmentenvironment,
ResourceLoaderresourceLoader) {
ClassPathScanningCandidateComponentProviderprovider=newClassPathScanningCandidateComponentProvider(
false, environment);
// Filter to include only classes that have a particular annotation.//provider.addIncludeFilter(newAnnotationTypeFilter(Path.class));
provider.addIncludeFilter(newAnnotationTypeFilter(Provider.class));
// Find classes in Eureka packages (or subpackages)//Set<Class<?>>classes=newHashSet<>();
for (StringbasePackage : EUREKA_PACKAGES) {
Set<BeanDefinition>beans=provider.findCandidateComponents(basePackage);
for (BeanDefinitionbd : beans) {
Class<?>cls=ClassUtils.resolveClassName(bd.getBeanClassName(),
resourceLoader.getClassLoader());
classes.add(cls);
            }
        }
// Construct the Jersey ResourceConfig//Map<String, Object>propsAndFeatures=newHashMap<>();
propsAndFeatures.put(
// Skip static content used by the webappServletContainer.PROPERTY_WEB_PAGE_CONTENT_REGEX,
EurekaConstants.DEFAULT_PREFIX+"/(fonts|images|css|js)/.*");
DefaultResourceConfigrc=newDefaultResourceConfig(classes);
rc.setPropertiesAndFeatures(propsAndFeatures);
returnrc;
    }
@BeanpublicFilterRegistrationBeantraceFilterRegistration(
@Qualifier("httpTraceFilter") Filterfilter) {
FilterRegistrationBeanbean=newFilterRegistrationBean();
bean.setFilter(filter);
bean.setOrder(Ordered.LOWEST_PRECEDENCE-10);
returnbean;
    }
}

我们来归纳一下EurekaServerAutoConfiguration 配置了那些东西

  • @Import(EurekaServerInitializerConfiguration.class):导入了EurekaServer初始化配置
  • @ConditionalOnBean(EurekaServerMarkerConfiguration.Marker.class):这个是配置起效的条件,通过@EnableEurekaServer开启该条件
  • EurekaDashboardProperties.class:仪表盘配置
  • InstanceRegistryProperties.class :以 eureka.instance.registry开头的服务注册配置
  • @PropertySource("classpath:/eureka/server.properties"):加载server.properties配置文件
  • 注册了EurekaServerConfig:Eureka服务端配置对象,eureka服务器运行所需的配置信息,实现由两个一个是EurekaServerConfigBean是netflix的实现,一个是DefaultEurekaServerConfig是Eureka的实现
  • 注册了EurekaController:仪表盘端点controller,默认的路径是“/”
  • 注册了PeerAwareInstanceRegistry应用对象注册接口,提供了eureka集群间的服务同步功能及相关操作
  • 注册了PeerEurekaNodes:集群节点PeerEurekaNode的管理类,
  • 注册了EurekaServerContext :EurekaServer的上下文对象默认实现DefaultEurekaServerContext,提供了initialize初始化和shutdown关闭方法。
  • 注册了EurekaServerBootstrap 服务端启动引导,EurekaServer启动的关键
  • 注册了jersey Filter:使用ServletContainer实现功能,用来处理/eureka的请求

总结

  1. SpringBoot自动配置加载EurekaServerAutoConfiguration自动配置
  2. @EnableEurekaServer导入EurekaServerMarkerConfiguration.marker激活EurekaServerAutoConfiguration自动配置
  3. EurekaServerAutoConfiguration注册了一系列组件,比较重要的有EurekaServerInitializerConfiguration初始化EurekaServer配置,PeerAwareInstanceRegistry服务注册器,PeerEurekaNodes服务节PeerEurekaNode点生命周期管理工具,EurekaServerContext服务上下文,EurekaServerBootstrap EurekaServer的启动引导等等

在下一章节我们将来研究一下EurekaServer的初始化的详细过程

目录
相关文章
|
3月前
|
安全 Java API
深入解析 Spring Security 配置中的 CSRF 启用与 requestMatchers 报错问题
本文深入解析了Spring Security配置中CSRF启用与`requestMatchers`报错的常见问题。针对CSRF,指出默认已启用,无需调用`enable()`,只需移除`disable()`即可恢复。对于`requestMatchers`多路径匹配报错,分析了Spring Security 6.x中方法签名的变化,并提供了三种解决方案:分次调用、自定义匹配器及降级使用`antMatchers()`。最后提醒开发者关注版本兼容性,确保升级平稳过渡。
357 2
|
4月前
|
缓存 Java API
微服务——SpringBoot使用归纳——Spring Boot集成 Swagger2 展现在线接口文档——Swagger2 的配置
本文介绍了在Spring Boot中配置Swagger2的方法。通过创建一个配置类,添加`@Configuration`和`@EnableSwagger2`注解,使用Docket对象定义API文档的详细信息,包括标题、描述、版本和包路径等。配置完成后,访问`localhost:8080/swagger-ui.html`即可查看接口文档。文中还提示了可能因浏览器缓存导致的问题及解决方法。
140 0
微服务——SpringBoot使用归纳——Spring Boot集成 Swagger2 展现在线接口文档——Swagger2 的配置
|
5月前
|
XML Java 测试技术
Spring IOC—基于注解配置和管理Bean 万字详解(通俗易懂)
Spring 第三节 IOC——基于注解配置和管理Bean 万字详解!
358 26
|
4月前
|
Java 关系型数据库 数据库
微服务——SpringBoot使用归纳——Spring Boot事务配置管理——Spring Boot 事务配置
本文介绍了 Spring Boot 中的事务配置与使用方法。首先需要导入 MySQL 依赖,Spring Boot 会自动注入 `DataSourceTransactionManager`,无需额外配置即可通过 `@Transactional` 注解实现事务管理。接着通过创建一个用户插入功能的示例,展示了如何在 Service 层手动抛出异常以测试事务回滚机制。测试结果表明,数据库中未新增记录,证明事务已成功回滚。此过程简单高效,适合日常开发需求。
196 0
|
4月前
|
Java 测试技术 微服务
微服务——SpringBoot使用归纳——Spring Boot中的项目属性配置——少量配置信息的情形
本课主要讲解Spring Boot项目中的属性配置方法。在实际开发中,测试与生产环境的配置往往不同,因此不应将配置信息硬编码在代码中,而应使用配置文件管理,如`application.yml`。例如,在微服务架构下,可通过配置文件设置调用其他服务的地址(如订单服务端口8002),并利用`@Value`注解在代码中读取这些配置值。这种方式使项目更灵活,便于后续修改和维护。
68 0
|
4月前
|
SQL Java 数据库连接
微服务——SpringBoot使用归纳——Spring Boot使用slf4j进行日志记录—— application.yml 中对日志的配置
在 Spring Boot 项目中,`application.yml` 文件用于配置日志。通过 `logging.config` 指定日志配置文件(如 `logback.xml`),实现日志详细设置。`logging.level` 可定义包的日志输出级别,例如将 `com.itcodai.course03.dao` 包设为 `trace` 级别,便于开发时查看 SQL 操作。日志级别从高到低为 ERROR、WARN、INFO、DEBUG,生产环境建议调整为较高级别以减少日志量。本课程采用 yml 格式,因其层次清晰,但需注意格式要求。
252 0
|
4月前
|
负载均衡 Dubbo Java
Spring Cloud Alibaba与Spring Cloud区别和联系?
Spring Cloud Alibaba与Spring Cloud区别和联系?
|
7月前
|
Java Spring
【Spring】方法注解@Bean,配置类扫描路径
@Bean方法注解,如何在同一个类下面定义多个Bean对象,配置扫描路径
256 73
|
5月前
|
前端开发 Java Nacos
🛡️Spring Boot 3 整合 Spring Cloud Gateway 工程实践
本文介绍了如何使用Spring Cloud Alibaba 2023.0.0.0技术栈构建微服务网关,以应对微服务架构中流量治理与安全管控的复杂性。通过一个包含鉴权服务、文件服务和主服务的项目,详细讲解了网关的整合与功能开发。首先,通过统一路由配置,将所有请求集中到网关进行管理;其次,实现了限流防刷功能,防止恶意刷接口;最后,添加了登录鉴权机制,确保用户身份验证。整个过程结合Nacos注册中心,确保服务注册与配置管理的高效性。通过这些实践,帮助开发者更好地理解和应用微服务网关。
500 0
🛡️Spring Boot 3 整合 Spring Cloud Gateway 工程实践
|
4月前
|
Java 数据库连接 数据库
微服务——SpringBoot使用归纳——Spring Boot集成MyBatis——MyBatis 介绍和配置
本文介绍了Spring Boot集成MyBatis的方法,重点讲解基于注解的方式。首先简述MyBatis作为持久层框架的特点,接着说明集成时的依赖导入,包括`mybatis-spring-boot-starter`和MySQL连接器。随后详细展示了`properties.yml`配置文件的内容,涵盖数据库连接、驼峰命名规范及Mapper文件路径等关键设置,帮助开发者快速上手Spring Boot与MyBatis的整合开发。
215 0
AI助理

你好,我是AI助理

可以解答问题、推荐解决方案等

登录插画

登录以查看您的控制台资源

管理云资源
状态一览
快捷访问