2026/4/14 20:19:51
网站建设
项目流程
内网门户网站建设要求,免费发布信息大全,旅游网站建设风格,卓成建设集团有限公司网站概述这是一个基于Spring Boot的多数据源动态切换方案#xff0c;通过解析请求的域名自动选择对应的数据源。核心组件实现1. 会话上下文管理 (SessionContext)使用 TransmittableThreadLocal 实现线程间数据传递提供统一的键值对存储接口在请求开始时清理旧数据#xff0c;在结…概述这是一个基于Spring Boot的多数据源动态切换方案通过解析请求的域名自动选择对应的数据源。核心组件实现1. 会话上下文管理 (SessionContext)使用 TransmittableThreadLocal 实现线程间数据传递提供统一的键值对存储接口在请求开始时清理旧数据在结束时移除数据避免内存泄漏2. 请求拦截器 (HeaderInterceptor)在 preHandle 阶段解析请求头中的 Origin提取域名并存储到 SessionContext 中调用 SessionContext.remove() 清理线程数据3.动态数据源实现 (DynamicDataSource)继承 AbstractRoutingDataSource重写 determineCurrentLookupKey() 方法从 SessionContext 获取域名作为数据源标识4.数据源配置 (DataSourceConfig)配置多个实际数据源如 bjDataSource 和 cdDataSource构建 targetDataSources 映射表以域名为键关联具体数据源设置默认数据源工作流程请求进入 → HeaderInterceptor 解析 Origin 头部域名提取 → 将域名存入 SessionContext数据源路由 → DynamicDataSource 根据域名选择对应数据源执行操作 → 使用选定的数据源执行数据库操作会话上下文管理package com.park.context; import cn.hutool.core.convert.Convert; import com.alibaba.ttl.TransmittableThreadLocal; import com.park.constants.SecurityConstants; import org.apache.commons.lang3.StringUtils; import java.util.Map; import java.util.Set; import java.util.concurrent.ConcurrentHashMap; public class SessionContext { private static final TransmittableThreadLocalMapString, Object THREAD_LOCAL new TransmittableThreadLocal(); public static void set(String key, Object value) { MapString, Object map getLocalMap(); map.put(key, value null ? StringUtils.EMPTY : value); } public static String get(String key) { MapString, Object map getLocalMap(); return Convert.toStr(map.getOrDefault(key, StringUtils.EMPTY)); } public static MapString, Object getLocalMap() { MapString, Object map THREAD_LOCAL.get(); if (map null) { map new ConcurrentHashMap(); THREAD_LOCAL.set(map); } return map; } public static void setCompanyId(String companyId) { set(SecurityConstants.COMPANY_ID, companyId); } public static String getCompanyId() { return get(SecurityConstants.COMPANY_ID); } public static void setParkingLotId(SetString parkingLotIds) { set(SecurityConstants.PARKINGLOT_ID, parkingLotIds); } public static SetString getParkingLotId() { return get(SecurityConstants.PARKINGLOT_ID, Set.class); } public static T T get(String key, ClassT clazz) { MapString, Object map getLocalMap(); return cast(map.getOrDefault(key, null)); } public static T T cast(Object obj) { if (obj null) { return null; } return (T) obj; } public static void remove() { THREAD_LOCAL.remove(); } }核心动态数据源类package com.park.source; import com.park.constants.SecurityConstants; import com.park.context.SessionContext; import org.springframework.jdbc.datasource.lookup.AbstractRoutingDataSource; public class DynamicDataSource extends AbstractRoutingDataSource { Override protected Object determineCurrentLookupKey() { System.out.println(当前数据源 SessionContext.get(SecurityConstants.DOMAIN)); return SessionContext.get(SecurityConstants.DOMAIN); } }请求拦截器package com.park.constants; public class SecurityConstants { public static final String AUTHORIZATION Authorization; public static final String COMPANY_ID companyId; public static final String PARKINGLOT_ID parkingLotIds; public static final String DOMAIN domain; } package com.park.interceptor; import com.park.constants.SecurityConstants; import com.park.context.SessionContext; import jakarta.servlet.http.HttpServletRequest; import jakarta.servlet.http.HttpServletResponse; import org.springframework.stereotype.Component; import org.springframework.web.servlet.AsyncHandlerInterceptor; import java.net.URI; import java.net.URISyntaxException; Component public class DomainInterceptor implements AsyncHandlerInterceptor { Override public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception { // 清除之前的会话数据 SessionContext.remove(); String origin request.getHeader(Origin); if (origin ! null) { try { URI uri new URI(origin); String domain uri.getHost(); SessionContext.setDomain(domain); } catch (URISyntaxException e) { // 记录错误日志 } } return true; } Override public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception { // 请求结束后清除线程本地变量 SessionContext.remove(); } } package com.park.config; import com.park.interceptor.DomainInterceptor; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.context.annotation.Configuration; import org.springframework.web.servlet.config.annotation.InterceptorRegistry; import org.springframework.web.servlet.config.annotation.WebMvcConfigurer; Configuration public class WebConfig implements WebMvcConfigurer { Autowired private DomainInterceptor domainInterceptor; Override public void addInterceptors(InterceptorRegistry registry) { registry.addInterceptor(domainInterceptor) .addPathPatterns(/**) .excludePathPatterns(/login, /public/**); } }数据源配置spring: datasource: bj: jdbc-url: jdbc:postgresql://localhost:5432/bj_db username: your_username password: your_password driver-class-name: org.postgresql.Driver cd: jdbc-url: jdbc:postgresql://localhost:5432/cd_db username: your_username password: your_password driver-class-name: org.postgresql.Driver package com.park.config; import com.park.source.DynamicDataSource; import org.springframework.boot.context.properties.ConfigurationProperties; import org.springframework.boot.jdbc.DataSourceBuilder; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.context.annotation.Primary; import javax.sql.DataSource; import java.util.HashMap; import java.util.Map; Configuration public class DataSourceConfig { Bean ConfigurationProperties(prefix spring.datasource.bj) public DataSource bjDataSource() { return DataSourceBuilder.create().build(); } Bean ConfigurationProperties(prefix spring.datasource.cd) public DataSource cdDataSource() { return DataSourceBuilder.create().build(); } Bean Primary public DataSource dynamicDataSource() { MapObject, Object targetDataSources new HashMap(); targetDataSources.put(bj.park.com, bjDataSource()); targetDataSources.put(cd.park.com, cdDataSource()); DynamicDataSource dynamicDataSource new DynamicDataSource(); dynamicDataSource.setTargetDataSources(targetDataSources); dynamicDataSource.setDefaultTargetDataSource(primaryDataSource()); return dynamicDataSource; } }