中文亚洲精品无码_熟女乱子伦免费_人人超碰人人爱国产_亚洲熟妇女综合网

當(dāng)前位置: 首頁 > news >正文

重慶所有做網(wǎng)站的公司排名海外seo

重慶所有做網(wǎng)站的公司排名,海外seo,網(wǎng)頁設(shè)計(jì)素材文字,1688精品貨源免費(fèi)入口本文來自 Apache Seata官方文檔,歡迎訪問官網(wǎng),查看更多深度文章。 本文來自 Apache Seata官方文檔,歡迎訪問官網(wǎng),查看更多深度文章。 Apache Seata透過源碼解決SeataAT模式整合Mybatis-Plus失去MP特性的問題 透過源碼解決SeataAT…

本文來自 Apache Seata官方文檔,歡迎訪問官網(wǎng),查看更多深度文章。
本文來自 Apache Seata官方文檔,歡迎訪問官網(wǎng),查看更多深度文章。
Apache Seata透過源碼解決SeataAT模式整合Mybatis-Plus失去MP特性的問題

透過源碼解決SeataAT模式整合Mybatis-Plus失去MP特性的問題

項(xiàng)目地址:https://gitee.com/itCjb/springboot-dubbo-mybatisplus-seata

本文作者:FUNKYE(陳健斌),杭州某互聯(lián)網(wǎng)公司主程。

介紹

Mybatis-Plus:MyBatis-Plus(簡稱 MP)是一個(gè) MyBatis 的增強(qiáng)工具,在 MyBatis 的基礎(chǔ)上只做增強(qiáng)不做改變,為簡化開發(fā)、提高效率而生。

MP配置:

<bean id="sqlSessionFactory" class="com.baomidou.mybatisplus.extension.spring.MybatisSqlSessionFactoryBean"><property name="dataSource" ref="dataSource"/>
</bean>

Seata:Seata 是一款開源的分布式事務(wù)解決方案,致力于提供高性能和簡單易用的分布式事務(wù)服務(wù)。Seata 將為用戶提供了 AT、TCC、SAGA 和 XA 事務(wù)模式,為用戶打造一站式的分布式解決方案。

AT模式機(jī)制:

  • 一階段:業(yè)務(wù)數(shù)據(jù)和回滾日志記錄在同一個(gè)本地事務(wù)中提交,釋放本地鎖和連接資源。
  • 二階段:
    • 提交異步化,非常快速地完成。
    • 回滾通過一階段的回滾日志進(jìn)行反向補(bǔ)償。

分析原因

? 1.首先我們通過介紹,可以看到,mp是需要注冊sqlSessionFactory,注入數(shù)據(jù)源,而Seata是通過代理數(shù)據(jù)源來保證事務(wù)的正?;貪L跟提交。

? 2.我們來看基于seata的官方demo提供的SeataAutoConfig的代碼

package org.test.config;import javax.sql.DataSource; import org.apache.ibatis.session.SqlSessionFactory;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.autoconfigure.jdbc.DataSourceProperties;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Primary;import com.alibaba.druid.pool.DruidDataSource;
import com.baomidou.mybatisplus.extension.spring.MybatisSqlSessionFactoryBean;import io.seata.rm.datasource.DataSourceProxy;
import io.seata.spring.annotation.GlobalTransactionScanner;@Configuration
public class SeataAutoConfig {@Autowired(required = true)private DataSourceProperties dataSourceProperties;private final static Logger logger = LoggerFactory.getLogger(SeataAutoConfig.class);@Bean(name = "dataSource") // 聲明其為Bean實(shí)例@Primary // 在同樣的DataSource中,首先使用被標(biāo)注的DataSourcepublic DataSource druidDataSource() {DruidDataSource druidDataSource = new DruidDataSource();logger.info("dataSourceProperties.getUrl():{}",dataSourceProperties.getUrl());druidDataSource.setUrl(dataSourceProperties.getUrl());druidDataSource.setUsername(dataSourceProperties.getUsername());druidDataSource.setPassword(dataSourceProperties.getPassword());druidDataSource.setDriverClassName(dataSourceProperties.getDriverClassName());druidDataSource.setInitialSize(0);druidDataSource.setMaxActive(180);druidDataSource.setMaxWait(60000);druidDataSource.setMinIdle(0);druidDataSource.setValidationQuery("Select 1 from DUAL");druidDataSource.setTestOnBorrow(false);druidDataSource.setTestOnReturn(false);druidDataSource.setTestWhileIdle(true);druidDataSource.setTimeBetweenEvictionRunsMillis(60000);druidDataSource.setMinEvictableIdleTimeMillis(25200000);druidDataSource.setRemoveAbandoned(true);druidDataSource.setRemoveAbandonedTimeout(1800);druidDataSource.setLogAbandoned(true);logger.info("裝載dataSource........");return druidDataSource;}/*** init datasource proxy* * @Param: druidDataSource datasource bean instance* @Return: DataSourceProxy datasource proxy*/@Beanpublic DataSourceProxy dataSourceProxy(DataSource dataSource) {logger.info("代理dataSource........");return new DataSourceProxy(dataSource);}@Beanpublic SqlSessionFactory sqlSessionFactory(DataSourceProxy dataSourceProxy) throws Exception {MybatisSqlSessionFactoryBean factory = new MybatisSqlSessionFactoryBean();factory.setDataSource(dataSourceProxy);factory.setMapperLocations(new PathMatchingResourcePatternResolver().getResources("classpath*:/mapper/*.xml"));return factory.getObject();}/*** init global transaction scanner** @Return: GlobalTransactionScanner*/@Beanpublic GlobalTransactionScanner globalTransactionScanner() {logger.info("配置seata........");return new GlobalTransactionScanner("test-service", "test-group");}
}

首先看到我們的seata配置數(shù)據(jù)源的類里,我們配置了一個(gè)數(shù)據(jù)源,然后又配置了一個(gè)seata代理datasource的bean,這時(shí)候.

然后我們?nèi)绻苯訂觤p整合seata的項(xiàng)目會發(fā)現(xiàn),分頁之類的插件會直接失效,連掃描mapper都得從代碼上寫,這是為什么呢?

通過閱讀以上代碼,是因?yàn)槲覀兞硗獾呐渲昧艘粋€(gè)sqlSessionFactory,導(dǎo)致mp的sqlSessionFactory失效了,這時(shí)候我們發(fā)現(xiàn)了問題的所在了,即使我們不配置sqlSessionFactoryl,也會因?yàn)閙p所使用的數(shù)據(jù)源不是被seata代理過后的數(shù)據(jù)源,導(dǎo)致分布式事務(wù)失效.但是如何解決這個(gè)問題呢?

這時(shí)候我們需要去閱讀mp的源碼,找到他的啟動類,一看便知

/** Copyright (c) 2011-2020, baomidou (jobob@qq.com).* <p>* Licensed under the Apache License, Version 2.0 (the "License"); you may not* use this file except in compliance with the License. You may obtain a copy of* the License at* <p>* https://www.apache.org/licenses/LICENSE-2.0* <p>* Unless required by applicable law or agreed to in writing, software* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the* License for the specific language governing permissions and limitations under* the License.*/
package com.baomidou.mybatisplus.autoconfigure;import com.baomidou.mybatisplus.core.MybatisConfiguration;
import com.baomidou.mybatisplus.core.config.GlobalConfig;
import com.baomidou.mybatisplus.core.handlers.MetaObjectHandler;
import com.baomidou.mybatisplus.core.incrementer.IKeyGenerator;
import com.baomidou.mybatisplus.core.injector.ISqlInjector;
import com.baomidou.mybatisplus.extension.spring.MybatisSqlSessionFactoryBean;
import org.apache.ibatis.annotations.Mapper;
import org.apache.ibatis.mapping.DatabaseIdProvider;
import org.apache.ibatis.plugin.Interceptor;
import org.apache.ibatis.scripting.LanguageDriver;
import org.apache.ibatis.session.ExecutorType;
import org.apache.ibatis.session.SqlSessionFactory;
import org.apache.ibatis.type.TypeHandler;
import org.mybatis.spring.SqlSessionFactoryBean;
import org.mybatis.spring.SqlSessionTemplate;
import org.mybatis.spring.mapper.MapperFactoryBean;
import org.mybatis.spring.mapper.MapperScannerConfigurer;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.BeanWrapper;
import org.springframework.beans.BeanWrapperImpl;
import org.springframework.beans.factory.BeanFactory;
import org.springframework.beans.factory.BeanFactoryAware;
import org.springframework.beans.factory.InitializingBean;
import org.springframework.beans.factory.ObjectProvider;
import org.springframework.beans.factory.support.BeanDefinitionBuilder;
import org.springframework.beans.factory.support.BeanDefinitionRegistry;
import org.springframework.boot.autoconfigure.AutoConfigurationPackages;
import org.springframework.boot.autoconfigure.AutoConfigureAfter;
import org.springframework.boot.autoconfigure.EnableAutoConfiguration;
import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
import org.springframework.boot.autoconfigure.condition.ConditionalOnSingleCandidate;
import org.springframework.boot.autoconfigure.jdbc.DataSourceAutoConfiguration;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.context.ApplicationContext;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Import;
import org.springframework.context.annotation.ImportBeanDefinitionRegistrar;
import org.springframework.core.io.Resource;
import org.springframework.core.io.ResourceLoader;
import org.springframework.core.type.AnnotationMetadata;
import org.springframework.util.Assert;
import org.springframework.util.CollectionUtils;
import org.springframework.util.ObjectUtils;
import org.springframework.util.StringUtils;import javax.sql.DataSource;
import java.util.List;
import java.util.Optional;
import java.util.stream.Stream;/*** {@link EnableAutoConfiguration Auto-Configuration} for Mybatis. Contributes a* {@link SqlSessionFactory} and a {@link SqlSessionTemplate}.* <p>* If {@link org.mybatis.spring.annotation.MapperScan} is used, or a* configuration file is specified as a property, those will be considered,* otherwise this auto-configuration will attempt to register mappers based on* the interface definitions in or under the root auto-configuration package.* </p>* <p> copy from {@link org.mybatis.spring.boot.autoconfigure.MybatisAutoConfiguration}</p>** @author Eddú Meléndez* @author Josh Long* @author Kazuki Shimizu* @author Eduardo Macarrón*/
@Configuration
@ConditionalOnClass({SqlSessionFactory.class, SqlSessionFactoryBean.class})
@ConditionalOnSingleCandidate(DataSource.class)
@EnableConfigurationProperties(MybatisPlusProperties.class)
@AutoConfigureAfter(DataSourceAutoConfiguration.class)
public class MybatisPlusAutoConfiguration implements InitializingBean {private static final Logger logger = LoggerFactory.getLogger(MybatisPlusAutoConfiguration.class);private final MybatisPlusProperties properties;private final Interceptor[] interceptors;private final TypeHandler[] typeHandlers;private final LanguageDriver[] languageDrivers;private final ResourceLoader resourceLoader;private final DatabaseIdProvider databaseIdProvider;private final List<ConfigurationCustomizer> configurationCustomizers;private final List<MybatisPlusPropertiesCustomizer> mybatisPlusPropertiesCustomizers;private final ApplicationContext applicationContext;public MybatisPlusAutoConfiguration(MybatisPlusProperties properties,ObjectProvider<Interceptor[]> interceptorsProvider,ObjectProvider<TypeHandler[]> typeHandlersProvider,ObjectProvider<LanguageDriver[]> languageDriversProvider,ResourceLoader resourceLoader,ObjectProvider<DatabaseIdProvider> databaseIdProvider,ObjectProvider<List<ConfigurationCustomizer>> configurationCustomizersProvider,ObjectProvider<List<MybatisPlusPropertiesCustomizer>> mybatisPlusPropertiesCustomizerProvider,ApplicationContext applicationContext) {this.properties = properties;this.interceptors = interceptorsProvider.getIfAvailable();this.typeHandlers = typeHandlersProvider.getIfAvailable();this.languageDrivers = languageDriversProvider.getIfAvailable();this.resourceLoader = resourceLoader;this.databaseIdProvider = databaseIdProvider.getIfAvailable();this.configurationCustomizers = configurationCustomizersProvider.getIfAvailable();this.mybatisPlusPropertiesCustomizers = mybatisPlusPropertiesCustomizerProvider.getIfAvailable();this.applicationContext = applicationContext;}@Overridepublic void afterPropertiesSet() {if (!CollectionUtils.isEmpty(mybatisPlusPropertiesCustomizers)) {mybatisPlusPropertiesCustomizers.forEach(i -> i.customize(properties));}checkConfigFileExists();}private void checkConfigFileExists() {if (this.properties.isCheckConfigLocation() && StringUtils.hasText(this.properties.getConfigLocation())) {Resource resource = this.resourceLoader.getResource(this.properties.getConfigLocation());Assert.state(resource.exists(),"Cannot find config location: " + resource + " (please add config file or check your Mybatis configuration)");}}@SuppressWarnings("SpringJavaInjectionPointsAutowiringInspection")@Bean@ConditionalOnMissingBeanpublic SqlSessionFactory sqlSessionFactory(DataSource dataSource) throws Exception {// TODO 使用 MybatisSqlSessionFactoryBean 而不是 SqlSessionFactoryBeanMybatisSqlSessionFactoryBean factory = new MybatisSqlSessionFactoryBean();factory.setDataSource(dataSource);factory.setVfs(SpringBootVFS.class);if (StringUtils.hasText(this.properties.getConfigLocation())) {factory.setConfigLocation(this.resourceLoader.getResource(this.properties.getConfigLocation()));}applyConfiguration(factory);if (this.properties.getConfigurationProperties() != null) {factory.setConfigurationProperties(this.properties.getConfigurationProperties());}if (!ObjectUtils.isEmpty(this.interceptors)) {factory.setPlugins(this.interceptors);}if (this.databaseIdProvider != null) {factory.setDatabaseIdProvider(this.databaseIdProvider);}if (StringUtils.hasLength(this.properties.getTypeAliasesPackage())) {factory.setTypeAliasesPackage(this.properties.getTypeAliasesPackage());}if (this.properties.getTypeAliasesSuperType() != null) {factory.setTypeAliasesSuperType(this.properties.getTypeAliasesSuperType());}if (StringUtils.hasLength(this.properties.getTypeHandlersPackage())) {factory.setTypeHandlersPackage(this.properties.getTypeHandlersPackage());}if (!ObjectUtils.isEmpty(this.typeHandlers)) {factory.setTypeHandlers(this.typeHandlers);}if (!ObjectUtils.isEmpty(this.properties.resolveMapperLocations())) {factory.setMapperLocations(this.properties.resolveMapperLocations());}// TODO 對源碼做了一定的修改(因?yàn)樵创a適配了老舊的mybatis版本,但我們不需要適配)Class<? extends LanguageDriver> defaultLanguageDriver = this.properties.getDefaultScriptingLanguageDriver();if (!ObjectUtils.isEmpty(this.languageDrivers)) {factory.setScriptingLanguageDrivers(this.languageDrivers);}Optional.ofNullable(defaultLanguageDriver).ifPresent(factory::setDefaultScriptingLanguageDriver);// TODO 自定義枚舉包if (StringUtils.hasLength(this.properties.getTypeEnumsPackage())) {factory.setTypeEnumsPackage(this.properties.getTypeEnumsPackage());}// TODO 此處必為非 NULLGlobalConfig globalConfig = this.properties.getGlobalConfig();// TODO 注入填充器if (this.applicationContext.getBeanNamesForType(MetaObjectHandler.class,false, false).length > 0) {MetaObjectHandler metaObjectHandler = this.applicationContext.getBean(MetaObjectHandler.class);globalConfig.setMetaObjectHandler(metaObjectHandler);}// TODO 注入主鍵生成器if (this.applicationContext.getBeanNamesForType(IKeyGenerator.class, false,false).length > 0) {IKeyGenerator keyGenerator = this.applicationContext.getBean(IKeyGenerator.class);globalConfig.getDbConfig().setKeyGenerator(keyGenerator);}// TODO 注入sql注入器if (this.applicationContext.getBeanNamesForType(ISqlInjector.class, false,false).length > 0) {ISqlInjector iSqlInjector = this.applicationContext.getBean(ISqlInjector.class);globalConfig.setSqlInjector(iSqlInjector);}// TODO 設(shè)置 GlobalConfig 到 MybatisSqlSessionFactoryBeanfactory.setGlobalConfig(globalConfig);return factory.getObject();}// TODO 入?yún)⑹褂?MybatisSqlSessionFactoryBeanprivate void applyConfiguration(MybatisSqlSessionFactoryBean factory) {// TODO 使用 MybatisConfigurationMybatisConfiguration configuration = this.properties.getConfiguration();if (configuration == null && !StringUtils.hasText(this.properties.getConfigLocation())) {configuration = new MybatisConfiguration();}if (configuration != null && !CollectionUtils.isEmpty(this.configurationCustomizers)) {for (ConfigurationCustomizer customizer : this.configurationCustomizers) {customizer.customize(configuration);}}factory.setConfiguration(configuration);}@Bean@ConditionalOnMissingBeanpublic SqlSessionTemplate sqlSessionTemplate(SqlSessionFactory sqlSessionFactory) {ExecutorType executorType = this.properties.getExecutorType();if (executorType != null) {return new SqlSessionTemplate(sqlSessionFactory, executorType);} else {return new SqlSessionTemplate(sqlSessionFactory);}}/*** This will just scan the same base package as Spring Boot does. If you want more power, you can explicitly use* {@link org.mybatis.spring.annotation.MapperScan} but this will get typed mappers working correctly, out-of-the-box,* similar to using Spring Data JPA repositories.*/public static class AutoConfiguredMapperScannerRegistrar implements BeanFactoryAware, ImportBeanDefinitionRegistrar {private BeanFactory beanFactory;@Overridepublic void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry) {if (!AutoConfigurationPackages.has(this.beanFactory)) {logger.debug("Could not determine auto-configuration package, automatic mapper scanning disabled.");return;}logger.debug("Searching for mappers annotated with @Mapper");List<String> packages = AutoConfigurationPackages.get(this.beanFactory);if (logger.isDebugEnabled()) {packages.forEach(pkg -> logger.debug("Using auto-configuration base package '{}'", pkg));}BeanDefinitionBuilder builder = BeanDefinitionBuilder.genericBeanDefinition(MapperScannerConfigurer.class);builder.addPropertyValue("processPropertyPlaceHolders", true);builder.addPropertyValue("annotationClass", Mapper.class);builder.addPropertyValue("basePackage", StringUtils.collectionToCommaDelimitedString(packages));BeanWrapper beanWrapper = new BeanWrapperImpl(MapperScannerConfigurer.class);Stream.of(beanWrapper.getPropertyDescriptors())// Need to mybatis-spring 2.0.2+.filter(x -> x.getName().equals("lazyInitialization")).findAny().ifPresent(x -> builder.addPropertyValue("lazyInitialization", "${mybatis.lazy-initialization:false}"));registry.registerBeanDefinition(MapperScannerConfigurer.class.getName(), builder.getBeanDefinition());}@Overridepublic void setBeanFactory(BeanFactory beanFactory) {this.beanFactory = beanFactory;}}/*** If mapper registering configuration or mapper scanning configuration not present, this configuration allow to scan* mappers based on the same component-scanning path as Spring Boot itself.*/@Configuration@Import(AutoConfiguredMapperScannerRegistrar.class)@ConditionalOnMissingBean({MapperFactoryBean.class, MapperScannerConfigurer.class})public static class MapperScannerRegistrarNotFoundConfiguration implements InitializingBean {@Overridepublic void afterPropertiesSet() {logger.debug("Not found configuration for registering mapper bean using @MapperScan, MapperFactoryBean and MapperScannerConfigurer.");}}
}

看到mp啟動類里的sqlSessionFactory方法了嗎,他也是一樣的注入一個(gè)數(shù)據(jù)源,這時(shí)候大家應(yīng)該都知道解決方法了吧?

沒錯(cuò),就是把被代理過的數(shù)據(jù)源給放到mp的sqlSessionFactory中.

很簡單,我們需要稍微改動一下我們的seata配置類就行了

package org.test.config;import javax.sql.DataSource;import org.mybatis.spring.annotation.MapperScan;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.autoconfigure.jdbc.DataSourceProperties;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Primary;import com.alibaba.druid.pool.DruidDataSource;import io.seata.rm.datasource.DataSourceProxy;
import io.seata.spring.annotation.GlobalTransactionScanner;@Configuration
@MapperScan("com.baomidou.springboot.mapper*")
public class SeataAutoConfig {@Autowired(required = true)private DataSourceProperties dataSourceProperties;private final static Logger logger = LoggerFactory.getLogger(SeataAutoConfig.class);private DataSourceProxy dataSourceProxy;@Bean(name = "dataSource") // 聲明其為Bean實(shí)例@Primary // 在同樣的DataSource中,首先使用被標(biāo)注的DataSourcepublic DataSource druidDataSource() {DruidDataSource druidDataSource = new DruidDataSource();logger.info("dataSourceProperties.getUrl():{}", dataSourceProperties.getUrl());druidDataSource.setUrl(dataSourceProperties.getUrl());druidDataSource.setUsername(dataSourceProperties.getUsername());druidDataSource.setPassword(dataSourceProperties.getPassword());druidDataSource.setDriverClassName(dataSourceProperties.getDriverClassName());druidDataSource.setInitialSize(0);druidDataSource.setMaxActive(180);druidDataSource.setMaxWait(60000);druidDataSource.setMinIdle(0);druidDataSource.setValidationQuery("Select 1 from DUAL");druidDataSource.setTestOnBorrow(false);druidDataSource.setTestOnReturn(false);druidDataSource.setTestWhileIdle(true);druidDataSource.setTimeBetweenEvictionRunsMillis(60000);druidDataSource.setMinEvictableIdleTimeMillis(25200000);druidDataSource.setRemoveAbandoned(true);druidDataSource.setRemoveAbandonedTimeout(1800);druidDataSource.setLogAbandoned(true);logger.info("裝載dataSource........");dataSourceProxy = new DataSourceProxy(druidDataSource);return dataSourceProxy;}/*** init datasource proxy* * @Param: druidDataSource datasource bean instance* @Return: DataSourceProxy datasource proxy*/@Beanpublic DataSourceProxy dataSourceProxy() {logger.info("代理dataSource........");return dataSourceProxy;}/*** init global transaction scanner** @Return: GlobalTransactionScanner*/@Beanpublic GlobalTransactionScanner globalTransactionScanner() {logger.info("配置seata........");return new GlobalTransactionScanner("test-service", "test-group");}
}

看代碼,我們?nèi)サ袅俗约号渲玫膕qlSessionFactory,直接讓DataSource bean返回的是一個(gè)被代理過的bean,并且我們加入了@Primary,導(dǎo)致mp優(yōu)先使用我們配置的數(shù)據(jù)源,這樣就解決了mp因?yàn)閟eata代理了數(shù)據(jù)源跟創(chuàng)建了新的sqlSessionFactory,導(dǎo)致mp的插件,組件失效的bug了!

總結(jié)

踩到坑不可怕,主要又耐心的順著每個(gè)組件實(shí)現(xiàn)的原理,再去思考,查找對應(yīng)沖突的代碼塊,你一定能找到個(gè)兼容二者的方法。

http://www.risenshineclean.com/news/50357.html

相關(guān)文章:

  • 家庭電腦做網(wǎng)站班級優(yōu)化大師免費(fèi)下載安裝
  • 大連專業(yè)網(wǎng)站設(shè)計(jì)服務(wù)商合肥關(guān)鍵詞排名
  • 做的網(wǎng)站每年需要續(xù)費(fèi)重慶seo全網(wǎng)營銷
  • 一件代發(fā)應(yīng)該在哪個(gè)網(wǎng)站上做典型十大優(yōu)秀網(wǎng)絡(luò)營銷案例
  • 什么軟件可以做dj視頻網(wǎng)站推廣優(yōu)化網(wǎng)站排名教程
  • 鄭州大型網(wǎng)站建設(shè)谷歌seo服務(wù)商
  • wordpress docker中文文檔seo搜索引擎優(yōu)化視頻
  • 網(wǎng)站設(shè)計(jì)與編輯網(wǎng)站推廣的技術(shù)有哪些
  • php中英文企業(yè)網(wǎng)站浙江搜索引擎優(yōu)化
  • 山東免費(fèi)網(wǎng)站制作seo標(biāo)題優(yōu)化是什么意思
  • 有效果的網(wǎng)站排名網(wǎng)絡(luò)營銷學(xué)校
  • 自己網(wǎng)站的登錄api怎么做上海今日頭條新聞
  • 廊坊電商網(wǎng)站建設(shè)推廣關(guān)鍵詞排名
  • 網(wǎng)站建設(shè)對企業(yè)的意義淘寶推廣哪種方式最好
  • wordpress會員查看發(fā)布插件上海網(wǎng)站快速排名優(yōu)化
  • 大學(xué)科研項(xiàng)目做網(wǎng)站常州seo收費(fèi)
  • 在網(wǎng)站里文本鏈接怎么做關(guān)鍵詞com
  • 網(wǎng)站開發(fā)階段怎么做測試網(wǎng)絡(luò)培訓(xùn)
  • 做的好的淘寶客網(wǎng)站營銷策劃思路
  • 北京商城網(wǎng)站建設(shè)國內(nèi)設(shè)計(jì)公司前十名
  • 網(wǎng)站建設(shè)技術(shù)服務(wù)合同營銷網(wǎng)站建設(shè)專家
  • 相親網(wǎng)站做期貨現(xiàn)貨貴金屬的人開車搜索關(guān)鍵詞
  • 寧波網(wǎng)站建設(shè)多少錢一個(gè)搜索排名廣告營銷
  • 哪里有做營銷型網(wǎng)站的公司seo推廣計(jì)劃
  • 無錫做網(wǎng)站建設(shè)手機(jī)軟文廣告300字
  • 建網(wǎng)站的哪家好農(nóng)產(chǎn)品網(wǎng)絡(luò)營銷策劃書
  • 高清世界街景地圖如何退訂夫唯seo教程
  • 網(wǎng)站建設(shè)完成后如何備案杭州網(wǎng)站推廣與優(yōu)化
  • 如何做電商網(wǎng)站 昆明東莞互聯(lián)網(wǎng)推廣
  • 哪個(gè)網(wǎng)站是做旅游B2B的關(guān)鍵詞優(yōu)化排名軟件怎么樣