SEO技术

SEO技术

Products

当前位置:首页 > SEO技术 >

如何创建黄山地区建设行业的二级网站?

96SEO 2026-02-19 17:42 12


如何创建黄山地区建设行业的二级网站?

最近雷袭又接到一项新的挑战#xff1a;了解SAAS模型#xff0c;考虑怎么将公司的产品转换成多租…

随着公司业务战略的发展相关的软件服务也逐步的向多元化转变之前是单纯的拿项目赚人工钱现在开始向产品化\服务化转变。

最近雷袭又接到一项新的挑战了解SAAS模型考虑怎么将公司的产品转换成多租户架构。

经过一番百度雷袭对多租户架构总算有了一番了解以下是整理的笔记。

多租户架构是一种软件架构用于实现多用户环境下使用相同的系统或程序组件时能保证用户之间数据的隔离性。

简单说就是使用共用的数据中心通过单一系统架构与服务提供多数客户端相同甚至可定制化的服务并且保障客户的数据隔离。

一个支持多租户架构的系统需要在设计上对它的数据和配置进行虚拟分区从而使系统的每个租户或组织都能够使用一个单独的系统实例每个租户都可以根据自己的需求对租用的系统实例进行个性化配置。

多租户技术的实现重点在于不同租户间应用程序环境的隔离以及数据的隔离使得不同租户间应用程序不会相互干扰。

应用程序可通过进程隔离或者多种运维工具实现数据存储上的隔离方案则是有三种

1、独立数据库优点是独立性最高缺点是数据库较多购置和维护成本高。

2、共享数据库隔离数据架构同一个数据库实例内多个用户/schema来对应多个租户优点是单实例可以支持更多租户缺点是数据恢复比较困难。

3、共享数据库共享数据结构物理分表表分区或者在表中通过字段区分优点是成本最低实现难度低缺点是数据隔离程度低。

第三种其实雷袭已经试过了之前的博客里就提到了表分区分表的实现方式这里不多缀述今天雷袭想试试前面两种因此不得不解决的一个问题如何实现同一个项目中数据源的动态切换

代码实践

雷袭在网上查阅了很多资料最终找到了两种合适的方式实现一种是通过AOP来实现另一种是通过Filter实现以下是实现的方式说明。

1、准备工作创建数据库模式添加测试数据

数据库key即保存Map中的key(保证唯一并且和DataSourceType中的枚举项保持一致包括大小写);

COMMENT

SAAS_MASTER.sys_db_info.db_name

数据库名称;

SAAS_MASTER.sys_db_info.driver_class_name

数据库驱动;

SAAS_MASTER.sys_db_info.password

密码;

SAAS_MASTER.sys_db_info.username

用户名;--添加数据源信息

(id,url,username,password,driver_class_name,db_name,db_key,status,remark)

values

jdbc:dm://127.0.0.1:5236/SAAS_DEV,

SAAS_DEV,

(id,url,username,password,driver_class_name,db_name,db_key,status,remark)

values

jdbc:dm://127.0.0.1:5236/SAAS_UAT,

SAAS_UAT,

2、创建一个springboot项目项目环境为JDK17以下是相关配置和代码

pom.xml

xmlnshttp://maven.apache.org/POM/4.0.0

xmlns:xsihttp://www.w3.org/2001/XMLSchema-instance

xsi:schemaLocationhttp://maven.apache.org/POM/4.0.0

https://maven.apache.org/xsd/maven-4.0.0.xsdmodelVersion4.0.0/modelVersionpackagingjar/packagingparentgroupIdorg.springframework.boot/groupIdartifactIdspring-boot-starter-parent/artifactIdversion3.3.2/version

!--

--/parentgroupIdcom.leixi.hub.saasdb/groupIdartifactIdleixi-saas-db/artifactIdversion1.0-SNAPSHOT/versionnameleixi-saas-db/namedescription用于动态切换数据源/descriptionpropertiesmaven.compiler.source17/maven.compiler.sourcemaven.compiler.target17/maven.compiler.targetproject.build.sourceEncodingUTF-8/project.build.sourceEncodinghutool.version5.8.15/hutool.versionmysql.version8.0.28/mysql.versiondruid.version1.2.16/druid.versionmybatis-plus.version3.5.3.1/mybatis-plus.versionlombok-mapstruct-binding.version0.2.0/lombok-mapstruct-binding.version/propertiesdependencies!--

Lombok

--dependencygroupIdorg.projectlombok/groupIdartifactIdlombok/artifactId!--编译测试环境不打包在lib--scopeprovided/scope/dependency!--

Bean类中使用MapStruct注解

--dependencygroupIdorg.projectlombok/groupIdartifactIdlombok-mapstruct-binding/artifactIdversion${lombok-mapstruct-binding.version}/versionscopeprovided/scope/dependency!--

hutool工具包

--dependencygroupIdcn.hutool/groupIdartifactIdhutool-all/artifactIdversion${hutool.version}/version/dependency!--

web支持

--dependencygroupIdorg.springframework.boot/groupIdartifactIdspring-boot-starter-web/artifactId/dependency!--

aop

--dependencygroupIdorg.springframework.boot/groupIdartifactIdspring-boot-starter-aop/artifactId/dependency!--

DM驱动

--dependencygroupIdcom.dameng/groupIdartifactIdDmJdbcDriver18/artifactIdversion8.1.1.193/version/dependency!--

阿里druid工具包

--dependencygroupIdcom.alibaba/groupIdartifactIddruid-spring-boot-starter/artifactIdversion${druid.version}/version/dependencydependencygroupIdcom.alibaba/groupIdartifactIdfastjson/artifactIdversion2.0.40/version/dependency!--

mybatis-plus

--dependencygroupIdcom.baomidou/groupIdartifactIdmybatis-plus-spring-boot3-starter/artifactIdversion3.5.5/version/dependency/dependenciesbuildpluginsplugingroupIdorg.springframework.boot/groupIdartifactIdspring-boot-maven-plugin/artifactId/pluginplugingroupIdorg.apache.maven.plugins/groupIdartifactIdmaven-archetype-plugin/artifactIdversion3.0.0/version/plugin/plugins/build/projectapplication.yml

server:port:

com.alibaba.druid.pool.DruidDataSourcedriver-class-name:

jdbc:dm://127.0.0.1:5236/SAAS_MASTERusername:

SAAS_MASTERpassword:

60000time-between-eviction-runs-millis:

60000min-evictable-idle-time-millis:

test-while-idle:

falsetask:execution:thread-pool:core-size:

10max-size:

classpath:/mapper/**/*Mapper.xmlglobal-config:db-config:#主键类型

2:用户输入ID,3:全局唯一ID

返回类型为Map,显示null对应的字段call-setters-on-nulls:

truemap-underscore-to-camel-case:

true

这个配置会将执行的sql打印出来在开发或测试的时候可以用log-impl:

org.apache.ibatis.logging.stdout.StdOutImpl

data_source_keyload_source_form_db:

true以下是设置数据源的核心代码其原理为在项目启动时先通过LoadDataSourceRunner从数据库中查询相关的数据连接存储在内存中对Controller中的方法添加DataSource注解执行方法时通过注解中的静态枚举切换对应的数据源对指定的数据库进行操作。

package

com.leixi.hub.saasdb.config;import

com.alibaba.druid.pool.DruidDataSource;

import

com.baomidou.mybatisplus.core.toolkit.CollectionUtils;

import

com.leixi.hub.saasdb.entity.SysDbInfo;

import

org.springframework.beans.BeanUtils;

import

org.springframework.jdbc.datasource.lookup.AbstractRoutingDataSource;import

import

实现动态数据源根据AbstractRoutingDataSource路由到不同数据源中**

author

数据源列表多数据源情况下具体使用哪一个数据源由此获取private

final

{super.setDefaultTargetDataSource(defaultDataSource);super.setTargetDataSources(targetDataSources);this.targetDataSourceMap

决定使用哪个数据源**

DynamicDataSourceContextHolder.getDataSource();}/***

添加数据源信息**

(CollectionUtils.isNotEmpty(dataSources))

{for

{//校验数据库是否可以连接Class.forName(ds.getDriverClassName());DriverManager.getConnection(ds.getUrl(),

ds.getUsername(),

ds.getPassword());//定义数据源DruidDataSource

dataSource

DruidDataSource();BeanUtils.copyProperties(ds,

dataSource);//申请连接时执行validationQuery检测连接是否有效这里建议配置为TRUE防止取到的连接不可用dataSource.setTestOnBorrow(true);//建议配置为true不影响性能并且保证安全性。

//申请连接的时候检测如果空闲时间大于timeBetweenEvictionRunsMillis执行validationQuery检测连接是否有效。

dataSource.setTestWhileIdle(true);//用来检测连接是否有效的sql要求是一个查询语句。

dataSource.setValidationQuery(select

将数据源放入Map中key为数据源名称要和DataSourceType中的枚举项对应包括大小写并且保证唯一this.targetDataSourceMap.put(ds.getDbKey(),

dataSource);}//

更新数据源配置列表这里主要是从数据源super.setTargetDataSources(this.targetDataSourceMap);//

将TargetDataSources中的连接信息放入resolvedDataSources管理super.afterPropertiesSet();}}

catch

Objects.nonNull(this.targetDataSourceMap)

Objects.nonNull(this.targetDataSourceMap.get(key));}

}package

com.alibaba.druid.spring.boot.autoconfigure.DruidDataSourceBuilder;

import

org.springframework.boot.context.properties.ConfigurationProperties;

import

org.springframework.context.annotation.Bean;

import

org.springframework.context.annotation.Configuration;

import

org.springframework.context.annotation.Primary;import

import

该数据源是在application配置文件master中所配置的*/BeanConfigurationProperties(spring.datasource)public

DataSource

DruidDataSourceBuilder.create().build();}/***

return

DynamicDataSource*/PrimaryBean(name

DynamicDataSource

配置主数据源默认使用该数据源并且主数据源只能配置一个dataSourceMap.put(MASTER_SOURCE_KEY,

配置动态数据源默认使用主数据源如果有从数据源配则使用从数据库中读取源并加载到dataSourceMap中return

new

DynamicDataSource(defaultDataSource,

dataSourceMap);}

createDynamicDataSource()方法dataSourceMap的key保持一致/***

主库*/MASTER,/***

com.leixi.hub.saasdb.config;import

创建一个类用于实现ThreadLocal主要是通过getsetremove方法来获取、设置、删除当前线程对应的数据源。

**

author

{//此类提供线程局部变量。

这些变量不同于它们的正常对应关系是每个线程访问一个线程(通过get、set方法),有自己的独立初始化变量的副本。

private

static

dataSourceName);DATASOURCE_HOLDER.set(dataSourceName);}/***

return

getDataSource());DATASOURCE_HOLDER.remove();}

}package

com.leixi.hub.saasdb.config;import

import

com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;

import

com.leixi.hub.saasdb.dao.SysDbInfoMapper;

import

com.leixi.hub.saasdb.entity.SysDbInfo;

import

org.springframework.beans.factory.annotation.Value;

import

org.springframework.boot.CommandLineRunner;

import

org.springframework.stereotype.Component;

import

org.springframework.util.CollectionUtils;import

java.util.ArrayList;

是否启用从库多数据源配置*/Value(${leixi.saas.load_source_form_db:false})private

boolean

dynamicDataSource;Resourceprivate

SysDbInfoMapper

return;refreshDataSource();}/***

刷新数据源*/public

LambdaQueryWrapperSysDbInfo().eq(SysDbInfo::getStatus,

0));if

(CollectionUtils.isEmpty(dbInfos))

return;ListSysDbInfo

ArrayList();log.info(开始加载数据源);for

(SysDbInfo

(StrUtil.isAllNotBlank(info.getUrl(),

数据库连接地址info.getDriverClassName(),

数据库密码info.getDbKey()

info.getRemark());}}dynamicDataSource.createDataSource(ds);log.info(数据源加载完成);}

}package

com.leixi.hub.saasdb.config;import

自定义多数据源切换注解*

优先级先方法后类如果方法覆盖了类上的数据源类型以方法的为准否则以类上的为准*

author

Retention(RetentionPolicy.RUNTIME)

ElementType.TYPE})

com.leixi.hub.saasdb.config;import

io.micrometer.common.util.StringUtils;

import

org.aspectj.lang.ProceedingJoinPoint;

import

org.aspectj.lang.annotation.Around;

import

org.aspectj.lang.annotation.Aspect;

import

org.aspectj.lang.annotation.Pointcut;

import

org.springframework.core.annotation.AnnotationUtils;

import

org.springframework.core.annotation.Order;

import

org.springframework.stereotype.Component;

import

org.aspectj.lang.reflect.MethodSignature;

import

注解Pointcut(annotation(com.leixi.hub.saasdb.config.DataSource)

within(com.leixi.hub.saasdb.config.DataSource))public

void

StringUtils.isNotEmpty(dataSource.value().name()))

{//

将用户自定义配置的数据源添加到线程局部变量中DynamicDataSourceContextHolder.setDataSource(dataSource.value().name());}try

{return

在执行完方法之后销毁数据源DynamicDataSourceContextHolder.removeDataSource();}}/***

注意当类上配置后方法上没有该注解那么当前类中的所有方法都将使用类上配置的数据源*/public

DataSource

getDataSource(ProceedingJoinPoint

point)

AnnotationUtils.findAnnotation(signature.getMethod(),

DataSource.class);//

AnnotationUtils.findAnnotation(signature.getDeclaringType(),

DataSource.class);}

com.leixi.hub.saasdb.entity;import

com.baomidou.mybatisplus.annotation.TableField;

import

com.baomidou.mybatisplus.annotation.TableName;

import

lombok.experimental.Accessors;import

java.io.Serial;

定义一个key用于作为DynamicDataSource中Map中的key。

*

这里的key需要和DataSourceType中的枚举项保持一致*/private

String

com.leixi.hub.saasdb.dao;import

com.baomidou.mybatisplus.core.mapper.BaseMapper;

import

com.leixi.hub.saasdb.entity.SysDbInfo;

import

org.apache.ibatis.annotations.Mapper;Mapper

public

com.leixi.hub.saasdb.dao;import

com.baomidou.mybatisplus.core.mapper.BaseMapper;

import

org.apache.ibatis.annotations.Mapper;

import

org.apache.ibatis.annotations.Param;import

java.util.List;

http://mybatis.org/dtd/mybatis-3-mapper.dtd

mapper

namespacecom.leixi.hub.saasdb.dao.CommonMapperselect

idgetDataBySql

resultTypejava.util.Map${sql}/selectupdate

/mapperpackage

com.leixi.hub.saasdb.controller;import

com.leixi.hub.saasdb.config.DataSource;

import

com.leixi.hub.saasdb.config.DataSourceType;

import

com.leixi.hub.saasdb.dao.CommonMapper;

import

org.springframework.beans.factory.annotation.Autowired;

import

org.springframework.web.bind.annotation.GetMapping;

import

org.springframework.web.bind.annotation.RequestParam;

import

org.springframework.web.bind.annotation.RestController;/****

author

commonMapper;GetMapping(/getDataBySqlFromMaster)DataSource(DataSourceType.MASTER)public

Object

getDataBySqlFromMaster(RequestParam(value

sql)

commonMapper.getDataBySql(sql);}GetMapping(/getDataBySqlFromUat)DataSource(DataSourceType.UAT)public

Object

getDataBySqlFromSlave(RequestParam(value

sql)

commonMapper.getDataBySql(sql);}GetMapping(/getDataBySql)public

Object

getDataBySql(RequestParam(value

sql)

commonMapper.getDataBySql(sql);}}3、启动项目通过Postman测试结果和预期一致

上述的方法虽然有效但多少有些固化了为何一只有添加了注解的类或方法才能动态切换数据源需要对已有代码进行修改那就多少会有漏改少改的位置二来可选的数据源在枚举或代码中写死了假设在数据库里新增了一个数据源则程序中必须要做相应的调整可扩展性不高综合考虑后我决定再用过滤器的方式试试。

过滤器的原理其实和AOP相似只是在Header中添加一个数据库的Key在过滤器中根据这个Key来指定数据源实现代码如下

package

com.leixi.hub.saasdb.filter;import

com.leixi.hub.saasdb.config.DynamicDataSourceContextHolder;

import

io.micrometer.common.util.StringUtils;

import

jakarta.servlet.ServletException;

import

jakarta.servlet.ServletRequest;

import

jakarta.servlet.ServletResponse;

import

jakarta.servlet.http.HttpServletRequest;

import

org.springframework.core.annotation.Order;import

author

httpRequest.getHeader(dataSourceKey);if

(StringUtils.isNotEmpty(dataSource))

{DynamicDataSourceContextHolder.setDataSource(dataSource);chain.doFilter(request,

else

{DynamicDataSourceContextHolder.removeDataSource();}}package

import

org.springframework.beans.factory.annotation.Value;

import

org.springframework.boot.web.servlet.FilterRegistrationBean;

import

org.springframework.context.annotation.Bean;

import

org.springframework.context.annotation.Configuration;/****

author

{Value(${leixi.saas.data_source_key:data_source_key})private

String

FilterRegistrationBeanDataSourceChangeFilter

licenseValidationFilterRegistration()

{FilterRegistrationBeanDataSourceChangeFilter

registration

FilterRegistrationBean();registration.setFilter(new

DataSourceChangeFilter(dataSourceKey));registration.addUrlPatterns(/*);

应用于所有URL

当前这个项目是已经实现了多数据源的动态切换那么如果想让其他项目也支持应该怎么办呢咱可以把这个项目打成一个jar包然后让其他项目引入依赖即可改动如下

!--可以打成供其他包依赖的包--buildpluginsplugingroupIdorg.apache.maven.plugins/groupIdartifactIdmaven-archetype-plugin/artifactIdversion3.0.0/version/pluginplugingroupIdorg.apache.maven.plugins/groupIdartifactIdmaven-compiler-plugin/artifactIdversion3.11.0/versionconfigurationsource17/sourcetarget17/targetencodingUTF-8/encoding/configuration/plugin/pluginsresourcesresourcedirectorysrc/main/resources/config/directoryfilteringtrue/filteringexcludesexclude*/exclude/excludes/resource/resources/build

3、打包完成后可以在target中看到对应的jar文件也可以在其他项目中引用该文件如下

后记与致谢

Demo比较简单手法也相对稚嫩希望不会贻笑大方也希望新手看到这个Demo能有所启发。

这次实践也并非一蹴而就的离不开大佬们的支持和点拨雷袭在网上找了很多资料以下这篇博客是最有价值的可以说雷袭完全是照抄了他的成果这里附上原文链接拜谢大佬



SEO优化服务概述

作为专业的SEO优化服务提供商,我们致力于通过科学、系统的搜索引擎优化策略,帮助企业在百度、Google等搜索引擎中获得更高的排名和流量。我们的服务涵盖网站结构优化、内容优化、技术SEO和链接建设等多个维度。

百度官方合作伙伴 白帽SEO技术 数据驱动优化 效果长期稳定

SEO优化核心服务

网站技术SEO

  • 网站结构优化 - 提升网站爬虫可访问性
  • 页面速度优化 - 缩短加载时间,提高用户体验
  • 移动端适配 - 确保移动设备友好性
  • HTTPS安全协议 - 提升网站安全性与信任度
  • 结构化数据标记 - 增强搜索结果显示效果

内容优化服务

  • 关键词研究与布局 - 精准定位目标关键词
  • 高质量内容创作 - 原创、专业、有价值的内容
  • Meta标签优化 - 提升点击率和相关性
  • 内容更新策略 - 保持网站内容新鲜度
  • 多媒体内容优化 - 图片、视频SEO优化

外链建设策略

  • 高质量外链获取 - 权威网站链接建设
  • 品牌提及监控 - 追踪品牌在线曝光
  • 行业目录提交 - 提升网站基础权威
  • 社交媒体整合 - 增强内容传播力
  • 链接质量分析 - 避免低质量链接风险

SEO服务方案对比

服务项目 基础套餐 标准套餐 高级定制
关键词优化数量 10-20个核心词 30-50个核心词+长尾词 80-150个全方位覆盖
内容优化 基础页面优化 全站内容优化+每月5篇原创 个性化内容策略+每月15篇原创
技术SEO 基本技术检查 全面技术优化+移动适配 深度技术重构+性能优化
外链建设 每月5-10条 每月20-30条高质量外链 每月50+条多渠道外链
数据报告 月度基础报告 双周详细报告+分析 每周深度报告+策略调整
效果保障 3-6个月见效 2-4个月见效 1-3个月快速见效

SEO优化实施流程

我们的SEO优化服务遵循科学严谨的流程,确保每一步都基于数据分析和行业最佳实践:

1

网站诊断分析

全面检测网站技术问题、内容质量、竞争对手情况,制定个性化优化方案。

2

关键词策略制定

基于用户搜索意图和商业目标,制定全面的关键词矩阵和布局策略。

3

技术优化实施

解决网站技术问题,优化网站结构,提升页面速度和移动端体验。

4

内容优化建设

创作高质量原创内容,优化现有页面,建立内容更新机制。

5

外链建设推广

获取高质量外部链接,建立品牌在线影响力,提升网站权威度。

6

数据监控调整

持续监控排名、流量和转化数据,根据效果调整优化策略。

SEO优化常见问题

SEO优化一般需要多长时间才能看到效果?
SEO是一个渐进的过程,通常需要3-6个月才能看到明显效果。具体时间取决于网站现状、竞争程度和优化强度。我们的标准套餐一般在2-4个月内开始显现效果,高级定制方案可能在1-3个月内就能看到初步成果。
你们使用白帽SEO技术还是黑帽技术?
我们始终坚持使用白帽SEO技术,遵循搜索引擎的官方指南。我们的优化策略注重长期效果和可持续性,绝不使用任何可能导致网站被惩罚的违规手段。作为百度官方合作伙伴,我们承诺提供安全、合规的SEO服务。
SEO优化后效果能持续多久?
通过我们的白帽SEO策略获得的排名和流量具有长期稳定性。一旦网站达到理想排名,只需适当的维护和更新,效果可以持续数年。我们提供优化后维护服务,确保您的网站长期保持竞争优势。
你们提供SEO优化效果保障吗?
我们提供基于数据的SEO效果承诺。根据服务套餐不同,我们承诺在约定时间内将核心关键词优化到指定排名位置,或实现约定的自然流量增长目标。所有承诺都会在服务合同中明确约定,并提供详细的KPI衡量标准。

SEO优化效果数据

基于我们服务的客户数据统计,平均优化效果如下:

+85%
自然搜索流量提升
+120%
关键词排名数量
+60%
网站转化率提升
3-6月
平均见效周期

行业案例 - 制造业

  • 优化前:日均自然流量120,核心词无排名
  • 优化6个月后:日均自然流量950,15个核心词首页排名
  • 效果提升:流量增长692%,询盘量增加320%

行业案例 - 电商

  • 优化前:月均自然订单50单,转化率1.2%
  • 优化4个月后:月均自然订单210单,转化率2.8%
  • 效果提升:订单增长320%,转化率提升133%

行业案例 - 教育

  • 优化前:月均咨询量35个,主要依赖付费广告
  • 优化5个月后:月均咨询量180个,自然流量占比65%
  • 效果提升:咨询量增长414%,营销成本降低57%

为什么选择我们的SEO服务

专业团队

  • 10年以上SEO经验专家带队
  • 百度、Google认证工程师
  • 内容创作、技术开发、数据分析多领域团队
  • 持续培训保持技术领先

数据驱动

  • 自主研发SEO分析工具
  • 实时排名监控系统
  • 竞争对手深度分析
  • 效果可视化报告

透明合作

  • 清晰的服务内容和价格
  • 定期进展汇报和沟通
  • 效果数据实时可查
  • 灵活的合同条款

我们的SEO服务理念

我们坚信,真正的SEO优化不仅仅是追求排名,而是通过提供优质内容、优化用户体验、建立网站权威,最终实现可持续的业务增长。我们的目标是与客户建立长期合作关系,共同成长。

提交需求或反馈

Demand feedback