当前位置: 首页 > wzjs >正文

网站首页模块建设什么叫优化关键词

网站首页模块建设,什么叫优化关键词,珠海横琴天聚建设工程有限公司网站,wordpress关闭搜索功能摘要 本文将详细介绍自定义 Spring Boot Starter 的完整过程。要构建自定义 Starter,首先需掌握 Spring Boot 中 Auto-configuration 以及相关注解的工作原理,同时了解 Spring Boot 提供的一系列条件注解。在具备这些知识基础后,再按照特定步…

摘要

       本文将详细介绍自定义 Spring Boot Starter 的完整过程。要构建自定义 Starter,首先需掌握 Spring Boot 中 @Auto-configuration 以及相关注解的工作原理,同时了解 Spring Boot 提供的一系列条件注解。在具备这些知识基础后,再按照特定步骤完成自定义 Starter 的开发,最后对其进行测试。接下来,让我们基于官网文档,深入学习具体内容。

@AutoConfiguration

   @AutoConfiguration 是 Spring Boot 3.0 引入的一个注解,用于标记自动配置类。自动配置类的主要作用是根据类路径中的依赖、配置属性等条件,自动为应用程序配置所需的 Bean。它替代了早期版本中的 @Configuration 和 @EnableAutoConfiguration 组合,让自动配置类的定义更加清晰和简洁。

代码示例

import org.springframework.boot.autoconfigure.AutoConfiguration;
import org.springframework.context.annotation.Bean;@AutoConfiguration
public class MyAutoConfiguration {@Beanpublic MyService myService() {return new MyService();}
}

        在这个示例中,MyAutoConfiguration 类被标记为自动配置类,其中定义了一个 MyService 的 Bean。当 Spring Boot 应用启动时,会自动扫描并加载这个自动配置类,创建 MyService 的实例。

Spring Boot 自动配置类的配置

加载

  • 我们需要在 META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports 配置文件中,加入我们的自动配置类,每个配置类占一行,从而让Spring Boot 加载自动配置类。

  • 可以使用 # 字符在该文件中添加注释。

  • 若自动配置类不是顶级类,其类名应使用 $ 分隔其与包含类,如 com.example.Outer$NestedAutoConfiguration

  • 自动配置类必须通过在 imports 文件中列出类名来加载。要确保它们定义在特定的包空间中,且永远不会成为组件扫描的目标。此外,自动配置类不应使用组件扫描来查找其他组件,而应使用特定的 @Import 注解。

顺序设置

  • @AutoConfiguration 注解属性:若配置需要按特定顺序应用,可使用 @AutoConfiguration 注解的 beforebeforeNameafter 和 afterName 属性,或使用专门的 @AutoConfigureBefore 和 @AutoConfigureAfter 注解。例如,若提供特定的 Web 配置,可能需要在 WebMvcAutoConfiguration 之后应用该配置类。

  • @AutoConfigureOrder 注解:若要对彼此无直接关联的某些自动配置类进行排序,可使用 @AutoConfigureOrder 注解。该注解与常规的 @Order 注解语义相同,但专门用于自动配置类。

  • 与标准的 @Configuration 类一样,自动配置类的应用顺序仅影响其定义 Bean 的顺序,而后续创建这些 Bean 的顺序不受影响,创建顺序由每个 Bean 的依赖关系和任何 @DependsOn 关系决定。

  • 带顺序的自动配置代码示例

import org.springframework.boot.autoconfigure.AutoConfiguration;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
import org.springframework.context.annotation.Bean;// 第一个自动配置类,创建 ServiceOne
@AutoConfiguration
public class ServiceOneAutoConfiguration {@Bean@ConditionalOnMissingBeanpublic ServiceOne serviceOne() {return new ServiceOne();}
}// 第二个自动配置类,使用 @AutoConfiguration 的 after 属性,在 ServiceOneAutoConfiguration 之后配置
@AutoConfiguration(after = ServiceOneAutoConfiguration.class)
public class ServiceTwoAutoConfiguration {@Bean@ConditionalOnMissingBeanpublic ServiceTwo serviceTwo() {return new ServiceTwo();}
}// 第三个自动配置类,使用 @AutoConfigureOrder 注解进行排序
import org.springframework.boot.autoconfigure.AutoConfigureOrder;
import org.springframework.core.Ordered;@AutoConfigureOrder(Ordered.LOWEST_PRECEDENCE)
@AutoConfiguration
public class ServiceThreeAutoConfiguration {@Bean@ConditionalOnMissingBeanpublic ServiceThree serviceThree() {return new ServiceThree();}
}

弃用与替换

  • 在开发过程中,有时需要对自动配置类进行弃用并提供替代方案,例如更改自动配置类所在的包名。

  • 由于自动配置类可能会在 before/after 排序和排除项中被引用,因此需要额外创建一个文件来告知 Spring Boot 如何处理替换操作。具体做法是创建一个名为 META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.replacements 的文件,在其中指明旧类和新类之间的映射关系。替换示例:

person.wend.springbootlearnexample.config.MyAutoConfiguration=person.wend.springbootlearnexample.config.auto.MyAutoConfiguration

  • 同时,AutoConfiguration.imports 文件也需要更新,使其仅引用替换后的新类,以确保 Spring Boot 在进行自动配置时能正确加载最新的配置类。

条件注解

        在进行自动配置时,SpringBoot 包含六类@Conditional注解。可通过对 @Configuration 类或单个 @Bean 方法添加这些注解,在自定义代码中复用。这些注解包括:

  1. 类条件注解(Class Conditions)

  2. Bean 条件注解(Bean Conditions)

  3. 属性条件注解(Property Conditions)

  4. 资源条件注解(Resource Conditions)

  5. Web 应用条件注解(Web Application Conditions)

  6. SpEL 表达式条件注解(SpEL Expression Conditions)

类条件注解

  • @ConditionalOnClass:@ConditionalOnClass 注解用于指定只有当类路径中存在指定的类时,被注解的配置类或 @Bean 方法才会生效。这在需要根据特定依赖是否存在来决定是否进行配置时非常有用。例如,当项目引入了某个库时,才加载与之相关的配置。

  • @ConditionalOnMissingClass:@ConditionalOnMissingClass 注解与 @ConditionalOnClass 注解相反,它用于指定只有当类路径中不存在指定的类时,被注解的配置类或 @Bean 方法才会生效。这在需要避免某些配置在特定类存在时生效的场景中非常有用。

  • 示例代码

import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;// 模拟某个库中的类
class SomeLibraryClass {}@Configuration
@ConditionalOnClass(SomeLibraryClass.class)
public class MyConfig {@Beanpublic MyService myService() {return new MyService();}
}class MyService {}
  • @ConditionalOnClass 和 @ConditionalOnMissingClass 注解通过检查类路径中特定类的存在与否,为 Spring Boot 应用的配置提供了灵活的条件加载机制,有助于根据不同的依赖环境进行动态配置。

Bean 条件注解

  • @ConditionalOnBean 和 @ConditionalOnMissingBean 注解可根据特定 Bean 是否存在来决定是否包含某个 Bean。

  • 使用 value 属性按类型指定 Bean

  • 使用 name 属性按名称指定 Bean

  • search 属性可用于限制在搜索 Bean 时应考虑的 ApplicationContext 层次结构

  • 示例代码

import org.springframework.boot.autoconfigure.AutoConfiguration;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
import org.springframework.context.annotation.Bean;@AutoConfiguration
public class MyAutoConfiguration {@Bean@ConditionalOnMissingBeanpublic SomeService someService() {return new SomeService();}
}
  • 在上述示例中,如果 ApplicationContext 中尚未包含 SomeService 类型的 Bean,那么 someService Bean 将会被创建。

属性条件注解

  • @ConditionalOnProperty 注解可根据 Spring 环境属性来决定是否包含配置。使用 prefix 和 name 属性指定要检查的属性。默认情况下,任何存在且不等于 false 的属性都会匹配。还可以使用 havingValue 和 matchIfMissing 属性进行更高级的检查。若 name 属性中指定了多个名称,则所有属性都必须通过测试条件才会匹配。

  • 代码示例

import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;@Configuration
public class PropertyConfig {@Bean@ConditionalOnProperty(prefix = "myapp", name = "enabled", havingValue = "true")public MyService myService() {return new MyService();}
}class MyService {}
  • 在 application.properties 中配置 myapp.enabled=true 时,myService Bean 才会被创建。

资源条件注解

  • @ConditionalOnResource 注解只有在特定资源存在时才会包含配置。可以使用 Spring 常用约定指定资源,如 file:/home/user/test.dat

import org.springframework.boot.autoconfigure.condition.ConditionalOnResource;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;@Configuration
public class ResourceConfig {@Bean@ConditionalOnResource(resources = "classpath:test.properties")public ResourceService resourceService() {return new ResourceService();}
}class ResourceService {}
  • 当类路径下存在 test.properties 文件时,resourceService Bean 才会被创建。

Web 应用条件注解

  • @ConditionalOnWebApplication 和 @ConditionalOnNotWebApplication 注解根据应用是否为 Web 应用来决定是否包含配置。基于 Servlet 的 Web 应用是指使用 Spring WebApplicationContext、定义了会话作用域或具有 ConfigurableWebEnvironment 的应用;响应式 Web 应用是指使用 ReactiveWebApplicationContext 或具有 ConfigurableReactiveWebEnvironment 的应用。

  • @ConditionalOnWarDeployment 和 @ConditionalOnNotWarDeployment 注解根据应用是否为部署到 Servlet 容器的传统 WAR 应用来决定是否包含配置。对于使用嵌入式 Web 服务器运行的应用,此条件不匹配。

import org.springframework.boot.autoconfigure.condition.ConditionalOnWebApplication;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;@Configuration
public class WebAppConfig {@Bean@ConditionalOnWebApplicationpublic WebService webService() {return new WebService();}
}class WebService {}
  • 当应用是 Web 应用时,webService Bean 才会被创建。

SpEL 表达式条件注解

  • @ConditionalOnExpression 注解根据 SpEL 表达式的结果来决定是否包含配置。但在表达式中引用 Bean 会导致该 Bean 在上下文刷新处理的早期初始化,从而使该 Bean 无法进行后处理(如配置属性绑定),其状态可能不完整。

import org.springframework.boot.autoconfigure.condition.ConditionalOnExpression;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;@Configuration
public class SpELConfig {@Bean@ConditionalOnExpression("#{systemProperties['java.version'].startsWith('17')}")public Java17Service java17Service() {return new Java17Service();}
}class Java17Service {}
  • 当 Java 版本以 17 开头时,java17Service Bean 才会被创建。

测试自动配置

        自动配置受用户配置(如@Bean定义和Environment自定义)、条件评估(特定库是否存在)等因素影响。每个测试都应创建一个定义良好的ApplicationContextApplicationContextRunner是实现此目标的好方法。

ApplicationContextRunner

  • ApplicationContextRunner通常被定义为测试类的一个字段,用于收集基本的通用配置。以下示例确保MyServiceAutoConfiguration始终调用:

	private final ApplicationContextRunner contextRunner = new ApplicationContextRunner().withConfiguration(AutoConfigurations.of(MyServiceAutoConfiguration.class));
  • 若有多个自动配置,无需排序,调用顺序与运行应用时相同。

  • 每个测试可用运行器表示特定用例,如调用用户配置(UserConfiguration)并检查自动配置是否正确退出,@Test方法中使用withUserConfigurationrun回调上下文结合AssertJ进行断言。

@Testvoid defaultServiceBacksOff() {this.contextRunner.withUserConfiguration(UserConfiguration.class).run((context) -> {assertThat(context).hasSingleBean(MyService.class);assertThat(context).getBean("myCustomService").isSameAs(context.getBean(MyService.class));});}@Configuration(proxyBeanMethods = false)static class UserConfiguration {@BeanMyService myCustomService() {return new MyService("mine");}}

  • 可轻松自定义Environment,如withPropertyValues方法设置属性值并进行断言。

@Testvoid serviceNameCanBeConfigured() {this.contextRunner.withPropertyValues("user.name=test123").run((context) -> {assertThat(context).hasSingleBean(MyService.class);assertThat(context.getBean(MyService.class).getName()).isEqualTo("test123");});}

  • 运行器可用于显示ConditionEvaluationReport,通过ConditionEvaluationReportLoggingListener设置日志级别打印报告。

class MyConditionEvaluationReportingTests {@Testvoid autoConfigTest() {new ApplicationContextRunner().withInitializer(ConditionEvaluationReportLoggingListener.forLogLevel(LogLevel.INFO)).run((context) -> {// Test something...});}}

模拟 Web 环境

        若测试仅在 servlet 或反应式 Web 应用程序上下文中运行的自动配置,可分别使用WebApplicationContextRunnerReactiveWebApplicationContextRunner

  • WebApplicationContextRunner代码示例:

import org.junit.jupiter.api.Test;
import org.springframework.boot.test.context.runner.ReactiveWebApplicationContextRunner;
import org.springframework.context.ApplicationContext;
import static org.assertj.core.api.Assertions.assertThat;
import reactor.test.StepVerifier;public class ReactiveAutoConfigurationTest {private final ReactiveWebApplicationContextRunner reactiveWebContextRunner = new ReactiveWebApplicationContextRunner().withConfiguration(AutoConfigurations.of(MyReactiveAutoConfiguration.class));@Testvoid testReactiveAutoConfiguration() {reactiveWebContextRunner.run((ApplicationContext context) -> {// 断言上下文中存在 MyReactiveService 的 BeanassertThat(context).hasSingleBean(MyReactiveService.class);MyReactiveService myReactiveService = context.getBean(MyReactiveService.class);// 调用反应式服务类的方法并使用 StepVerifier 进行断言StepVerifier.create(myReactiveService.sayHello()).expectNext("Hello from MyReactiveService in Reactive context").verifyComplete();});}
}
  • ReactiveWebApplicationContextRunner代码示例:

import org.junit.jupiter.api.Test;
import org.springframework.boot.test.context.runner.ReactiveWebApplicationContextRunner;
import org.springframework.context.ApplicationContext;
import static org.assertj.core.api.Assertions.assertThat;
import reactor.test.StepVerifier;public class ReactiveAutoConfigurationTest {private final ReactiveWebApplicationContextRunner reactiveWebContextRunner = new ReactiveWebApplicationContextRunner().withConfiguration(AutoConfigurations.of(MyReactiveAutoConfiguration.class));@Testvoid testReactiveAutoConfiguration() {reactiveWebContextRunner.run((ApplicationContext context) -> {// 断言上下文中存在 MyReactiveService 的 BeanassertThat(context).hasSingleBean(MyReactiveService.class);MyReactiveService myReactiveService = context.getBean(MyReactiveService.class);// 调用反应式服务类的方法并使用 StepVerifier 进行断言StepVerifier.create(myReactiveService.sayHello()).expectNext("Hello from MyReactiveService in Reactive context").verifyComplete();});}
}

覆盖类路径

        使用FilteredClassLoader可测试运行时特定类和 / 或包不存在时的情况,如通过withClassLoader方法设置类加载器并断言自动配置是否正确禁用。

    @Testvoid serviceIsIgnoredIfLibraryIsNotPresent() {this.contextRunner.withClassLoader(new FilteredClassLoader(MyService.class)).run((context) -> assertThat(context).doesNotHaveBean("myService"));}

创建自定义Starter的步骤

        典型的 Spring Boot Starter 包含用于自动配置和定制特定技术(以 “acme” 为例)基础设施的代码。为实现易于扩展,会在专用命名空间中向环境暴露多个配置键,并且提供单一的 “starter” 依赖,方便用户快速上手。

  • acme:“acme” 是一个占位符,用来代表任意一种技术、框架、库或者服务。“acme” 代表开发者想要自动配置和定制其基础设施的目标技术。例如,这个技术可能是 Redis、MongoDB、RabbitMQ 等,“acme” 在这里可以理解成一个泛指的、可以被替换为具体技术的抽象概念。

自定义 Starter 的组成

  • 自动配置模块(autoconfigure module):包含针对 “acme” 的自动配置代码。

  • Starter 模块:依赖于自动配置模块,同时包含 “acme” 以及其他常用的额外依赖。简而言之,添加该 Starter 应能提供使用相应库所需的一切。

模块分离的考量

  • 分离的优势:将自动配置和 Starter 分成两个模块并非必要,但当 “acme” 存在多种变体、选项或可选功能时,进行分离是更好的选择。这样可以明确表明某些功能是可选的,并且能够定制一个对这些可选依赖有特定选择的 Starter。同时,其他人可以仅依赖自动配置模块,按照自己的需求定制 Starter。

  • 合并的情况:如果自动配置相对简单,且没有可选功能,将两个模块合并到 Starter 中也是可行的。

Starter 的命名规则

  • 官方Starter:Spring-Boot 官方的Starter 都统一以 spring-boot-starter-* 格式命名,其中*是特定类型的应用程序。此命名结构旨在帮助您在需要查找启动器时使用。许多 IDE 中的 Maven 集成允许您按名称搜索依赖项。例如,安装了适当的 Eclipse 或 Spring Tools 插件后,您可以按ctrl-spacePOM 编辑器并键入“spring-boot-starter”以获取完整列表。

  • 三方库或自建Starter:第三方启动器不应以 spring-boot开头,因为它是为官方 Spring Boot 工件保留的。通常正确的写法是,名为thirdpartyproject的第三方启动器项目,通常其定义的Starter应命名为thirdpartyproject-spring-boot-starter

配置键

命名空间使用规范

        如果自定义的 Starter 提供配置键,要使用独特的命名空间,切勿将配置键置于 Spring Boot 已使用的命名空间(如 servermanagementspring 等)中。因为后续 Spring Boot 可能会修改这些命名空间,从而导致模块出现问题。一般来说,应使用自己拥有的命名空间作为配置键的前缀,例如 acme

  • 配置示例

acme.my-project.person.first-name=John
acme.my-project.person.last-name=Smith
acme.my-project.person.version=1.0.0
配置键文档化
  • 使用 @ConfigurationProperties 注解类:为保证配置键有文档记录,需为 @ConfigurationProperties 注解类中的每个属性添加字段 Javadoc。

@ConfigurationProperties("acme")
public class AcmeProperties {/*** Whether to check the location of acme resources.*/private boolean checkLocation = true;/*** Timeout for establishing a connection to the acme server.*/private Duration loginTimeout = Duration.ofSeconds(3);// getters/setters ...public boolean isCheckLocation() {return this.checkLocation;}public void setCheckLocation(boolean checkLocation) {this.checkLocation = checkLocation;}public Duration getLoginTimeout() {return this.loginTimeout;}public void setLoginTimeout(Duration loginTimeout) {this.loginTimeout = loginTimeout;}}
  • 配置示例展示了 AcmeProperties 类,包含 checkLocation 和 loginTimeout 等属性,并为每个属性添加了说明性的 Javadoc。

  • 记录类的处理方式:若使用 @ConfigurationProperties 注解记录类(record class),需通过类级别的 Javadoc 标签 @param 来提供记录组件的描述,因为记录类没有显式的实例字段来放置常规的字段级 Javadoc。

描述规范

        为确保描述的一致性,需遵循以下规则:

  • 描述开头不要使用 “The” 或 “A”。

  • 对于布尔类型,描述以 “Whether” 或 “Enable” 开头。

  • 对于基于集合的类型,描述以 “Comma - separated list” 开头。

  • 使用 Duration 类型而非 long 类型,若默认单位不是毫秒,需描述默认单位,如 “If a duration suffix is not specified, seconds will be used”。

  • 除非默认值需在运行时确定,否则描述中不要提供默认值。

元数据生成

        要触发元数据生成,以便 IDE 能为配置键提供辅助功能。可以查看生成的元数据文件 META - INF/spring-configuration-metadata.json,确保配置键有恰当的文档记录。在兼容的 IDE 中使用自定义的 Starter 也是验证元数据质量的好方法。

“autoconfigure” 模块

        “autoconfigure” 模块包含了使用某个库所需的一切必要内容。它可能包含配置键定义(例如使用 @ConfigurationProperties 注解),以及可用于进一步自定义组件初始化方式的回调接口。

  • 依赖设置:应该将对该库的依赖标记为可选的。这样做可以更轻松地将 “autoconfigure” 模块包含到项目中。因为当依赖被标记为可选时,库本身不会被提供,默认情况下 Spring Boot 会跳过对相关自动配置的加载。

    • 在 Maven 项目中,通过在 pom.xml 文件里的 <dependency> 标签中添加 <optional>true</optional> 来将依赖标记为可选。

<dependencies><!-- 其他依赖 --><dependency><groupId>com.example</groupId><artifactId>your-library</artifactId><version>1.0.0</version><optional>true</optional></dependency>
</dependencies>
  • 自动配置元数据文件:Spring Boot 使用注解处理器将自动配置的条件收集到一个元数据文件(META - INF/spring-autoconfigure-metadata.properties)中。如果该文件存在,Spring Boot 会使用它来提前过滤掉不匹配的自动配置,从而提高应用的启动时间。

  • Maven 构建配置:在使用 Maven 进行项目构建时,需要将编译器插件(版本 3.12.0 或更高)配置为把 spring-boot-autoconfigure-processor 添加到注解处理器路径中。

<project><build><plugins><plugin><groupId>org.apache.maven.plugins</groupId><artifactId>maven-compiler-plugin</artifactId><configuration><annotationProcessorPaths><path><groupId>org.springframework.boot</groupId><artifactId>spring-boot-autoconfigure-processor</artifactId></path></annotationProcessorPaths></configuration></plugin></plugins></build>
</project>

Starter 模块的本质与作用

        Starter 本质上是一个空的 JAR 文件,其主要作用是提供使用相关库所需的必要依赖,可被视为关于启动使用该库所需依赖的一种推荐组合。

  • spring-boot-starter 示例:

  • Starter 依赖添加原则:

    • 避免主观臆断:当你开发一个 Spring Boot Starter 时,不要想当然地认为引入这个 Starter 的项目是什么样的情况。比如说,不能觉得所有用你这个 Starter 的项目都有某些特定的配置或者已经引入了某些其他库。如果这个 Starter 所自动配置的库在正常使用的时候还需要其他的 Starter 配合,那你就得把这些额外需要的 Starter 清楚地告诉别人。

    • 合理选择默认依赖:要是你要自动配置的库有很多可以选择的依赖,这时候选一套合适的默认依赖就挺难办的。你得保证 Starter 里只放那些在大家平时用这个库的时候一定会用到的依赖,那些可有可无的依赖就别放进去了。

  • Starter 依赖要求

    • Starter 模块必须直接或间接引用 Spring Boot 核心 Starter(spring-boot- starter)。若项目仅使用自定义 Starter 创建,由于核心 Starter 的存在,Spring Boot 的核心功能仍能正常使用。如果自定义 Starter 依赖于其他已包含核心 Starter 的 Starter,则无需再额外添加核心 Starter 依赖。

参考文献

Creating Your Own Auto-configuration :: Spring Boot

http://www.dtcms.com/wzjs/328626.html

相关文章:

  • 中山做网站的公司哪家好昆明seo关键词排名
  • 淄博市建设工程质量协会网站怎么做百度网页
  • 美女做恐怖手术视频网站成都seo学徒
  • 北京 网站建设600今日重点新闻
  • 网站转微信小程序网站推广优化排名教程
  • php开发一个企业网站价格aso关键词排名优化是什么
  • 电商网站设计公司力荐亿企邦燃灯seo
  • wordpress 企业网站制作河南网站建设报价
  • ios网页游戏网络推广优化网站
  • 公司网站建设完成通知怎样搭建自己的网站
  • 如何根据仿站做网站广告营销推广方案
  • 凡科网做的网站提高网站排名
  • 杭州专业seo服务公司优化网站搜索
  • 如何给网站做外部优化长沙网站托管seo优化公司
  • win10搭建服务器做网站黄页推广2021
  • 关于h5的网站广州网络seo公司
  • 代备案网站空间爱链接
  • 静安做网站的公司唐山seo
  • 微信编辑器做网站营销技巧有哪些
  • 为什么做的网站别的浏览器打不开怎么办班级优化大师下载
  • 湖北网址大全太原seo外包平台
  • 织梦做的网站快照被攻击wordpress免费建站
  • 400电话实名制认证网站网络营销师主要做什么
  • 番禺哪里有做网站的公司电话营销技巧和营销方法
  • seo做多个网站微信指数
  • 怎么做联盟网站网站友链查询接口
  • 手机版网站做一下多少钱站优云seo优化
  • 网店运营计划书范文关键词seo优化软件
  • app网站建设介绍开户推广竞价开户
  • 品牌网站设计制作公司优化设计答案六年级上册语文