This commit is contained in:
oneao committed 2025-06-10 21:27:20 +08:00
commit c570ec514c
550 files changed
+152597

No files matched your search

@@ -0,0 +1,38 @@
target/
!.mvn/wrapper/maven-wrapper.jar
!**/src/main/**/target/
!**/src/test/**/target/
### IntelliJ IDEA ###
.idea/modules.xml
.idea/jarRepositories.xml
.idea/compiler.xml
.idea/libraries/
*.iws
*.iml
*.ipr
### Eclipse ###
.apt_generated
.classpath
.factorypath
.project
.settings
.springBeans
.sts4-cache
### NetBeans ###
/nbproject/private/
/nbbuild/
/dist/
/nbdist/
/.nb-gradle/
build/
!**/src/main/**/build/
!**/src/test/**/build/
### VS Code ###
.vscode/
### Mac OS ###
.DS_Store
+8
View File
@@ -0,0 +1,8 @@
# Default ignored files
/shelf/
/workspace.xml
# Editor-based HTTP Client requests
/httpRequests/
# Datasource local storage ignored files
/dataSources/
/dataSources.local.xml
+7
View File
@@ -0,0 +1,7 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="Encoding">
<file url="file://$PROJECT_DIR$/src/main/java" charset="UTF-8" />
<file url="file://$PROJECT_DIR$/src/main/resources" charset="UTF-8" />
</component>
</project>
+14
View File
@@ -0,0 +1,14 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="ExternalStorageConfigurationManager" enabled="true" />
<component name="MavenProjectsManager">
<option name="originalFiles">
<list>
<option value="$PROJECT_DIR$/pom.xml" />
</list>
</option>
</component>
<component name="ProjectRootManager" version="2" languageLevel="JDK_1_8" default="true" project-jdk-name="1.8" project-jdk-type="JavaSDK">
<output url="file://$PROJECT_DIR$/out" />
</component>
</project>
+6
View File
@@ -0,0 +1,6 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="VcsDirectoryMappings">
<mapping directory="$PROJECT_DIR$/../../.." vcs="Git" />
</component>
</project>
+196
View File
@@ -0,0 +1,196 @@
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>cn.g3soft</groupId>
<artifactId>base-springboot</artifactId>
<version>1.0-SNAPSHOT</version>
<properties>
<java.version>1.8</java.version>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
<maven-jar-plugin.version>3.1.1</maven-jar-plugin.version>
<spring-boot.version>2.5.15</spring-boot.version>
<druid.version>1.2.23</druid.version>
<jwt.version>3.10.3</jwt.version>
<jtds.version>1.3.1</jtds.version>
<commons.version>3.12.0</commons.version>
<!-- override dependency version -->
<tomcat.version>9.0.96</tomcat.version>
<logback.version>1.2.13</logback.version>
<spring-framework.version>5.3.39</spring-framework.version>
</properties>
<dependencyManagement>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-dependencies</artifactId>
<version>${spring-boot.version}</version>
<type>pom</type>
<scope>import</scope>
</dependency>
<!-- 覆盖SpringFramework的依赖配置-->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-framework-bom</artifactId>
<version>${spring-framework.version}</version>
<type>pom</type>
<scope>import</scope>
</dependency>
</dependencies>
</dependencyManagement>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
<!-- SpringBoot Web容器 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<!-- SpringBoot 拦截器 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-aop</artifactId>
</dependency>
<!-- lombok 注解 -->
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
</dependency>
<!-- validation 验证 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-validation</artifactId>
</dependency>
<!-- 覆盖logback的依赖配置-->
<dependency>
<groupId>ch.qos.logback</groupId>
<artifactId>logback-core</artifactId>
<version>${logback.version}</version>
</dependency>
<dependency>
<groupId>ch.qos.logback</groupId>
<artifactId>logback-classic</artifactId>
<version>${logback.version}</version>
</dependency>
<!-- 覆盖tomcat的依赖配置-->
<dependency>
<groupId>org.apache.tomcat.embed</groupId>
<artifactId>tomcat-embed-core</artifactId>
<version>${tomcat.version}</version>
</dependency>
<dependency>
<groupId>org.apache.tomcat.embed</groupId>
<artifactId>tomcat-embed-el</artifactId>
<version>${tomcat.version}</version>
</dependency>
<dependency>
<groupId>org.apache.tomcat.embed</groupId>
<artifactId>tomcat-embed-websocket</artifactId>
<version>${tomcat.version}</version>
</dependency>
<!-- yml解析器 -->
<dependency>
<groupId>org.yaml</groupId>
<artifactId>snakeyaml</artifactId>
</dependency>
<!-- 阿里数据库连接池 -->
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>druid-spring-boot-starter</artifactId>
<version>${druid.version}</version>
</dependency>
<!-- Token生成与解析-->
<dependency>
<groupId>com.auth0</groupId>
<artifactId>java-jwt</artifactId>
<version>${jwt.version}</version>
</dependency>
<dependency>
<groupId>net.sourceforge.jtds</groupId>
<artifactId>jtds</artifactId>
<version>${jtds.version}</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-jdbc</artifactId>
</dependency>
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-lang3</artifactId>
<version>${commons.version}</version>
</dependency>
</dependencies>
<!-- 打包格式 -->
<packaging>jar</packaging>
<repositories>
<repository>
<id>public</id>
<name>aliyun nexus</name>
<url>https://maven.aliyun.com/repository/public</url>
<releases>
<enabled>true</enabled>
</releases>
</repository>
</repositories>
<pluginRepositories>
<pluginRepository>
<id>public</id>
<name>aliyun nexus</name>
<url>https://maven.aliyun.com/repository/public</url>
<releases>
<enabled>true</enabled>
</releases>
<snapshots>
<enabled>false</enabled>
</snapshots>
</pluginRepository>
</pluginRepositories>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
<version>2.5.15</version>
<configuration>
<fork>true</fork> <!-- 如果没有该配置,devtools不会生效 -->
</configuration>
<executions>
<execution>
<goals>
<goal>repackage</goal>
</goals>
</execution>
</executions>
</plugin>
</plugins>
</build>
</project>
@@ -0,0 +1,16 @@
package cn.g3soft;
import com.alibaba.druid.spring.boot.autoconfigure.DruidDataSourceAutoConfigure;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.boot.autoconfigure.jdbc.DataSourceAutoConfiguration;
@SpringBootApplication(exclude = {
DataSourceAutoConfiguration.class,
DruidDataSourceAutoConfigure.class ,
})
public class BaseApplication {
public static void main(String[] args) {
SpringApplication.run(BaseApplication.class,args);
}
}
@@ -0,0 +1,79 @@
package cn.g3soft.common;
import cn.g3soft.enums.ResponseEnum;
import lombok.Data;
import lombok.NonNull;
@Data
public class Result<T> {
private final Integer code;
private final String message;
@NonNull
private final T data;
private final Boolean showExceptionDetail;
private Result(Integer code, String message, T data, Boolean showExceptionDetail) {
this.code = code;
this.message = message;
this.data = data;
this.showExceptionDetail = showExceptionDetail;
}
// 默认showDetail为false的构造
private Result(Integer code, String message, T data) {
this(code, message, data, false);
}
// ✅ 链式设置 showExceptionDetail:返回一个新对象
public Result<T> withShowExceptionDetail(boolean showExceptionDetail) {
return new Result<>(this.code, this.message, this.data, showExceptionDetail);
}
// 成功响应(无数据)
public static <T> Result<T> success() {
return new Result<>(ResponseEnum.SUCCESS.getCode(), ResponseEnum.SUCCESS.getMessage(), null);
}
public static <T> Result<T> success(String message) {
return new Result<>(ResponseEnum.SUCCESS.getCode(), message, null);
}
public static <T> Result<T> success(@NonNull T data) {
return new Result<>(ResponseEnum.SUCCESS.getCode(), ResponseEnum.SUCCESS.getMessage(), data);
}
public static <T> Result<T> success(@NonNull T data, String message) {
return new Result<>(ResponseEnum.SUCCESS.getCode(), message, data);
}
public static <T> Result<T> success(ResponseEnum responseEnum) {
return new Result<>(responseEnum.getCode(), responseEnum.getMessage(), null);
}
public static <T> Result<T> success(ResponseEnum responseEnum, @NonNull T data) {
return new Result<>(responseEnum.getCode(), responseEnum.getMessage(), data);
}
// 错误响应(默认不显示详细信息)
public static <T> Result<T> error(ResponseEnum responseEnum) {
return new Result<>(responseEnum.getCode(), responseEnum.getMessage(), null);
}
public static <T> Result<T> error(Integer code, String message) {
return new Result<>(code, message, null);
}
public static <T> Result<T> error(String message) {
return new Result<>(ResponseEnum.ERROR.getCode(), message, null);
}
public static <T> Result<T> error(T data) {
return new Result<>(ResponseEnum.ERROR.getCode(), ResponseEnum.ERROR.getMessage(), data);
}
public static <T> Result<T> error(T data,String message) {
return new Result<>(ResponseEnum.ERROR.getCode(),message, data);
}
public static <T> Result<T> error() {
return new Result<>(ResponseEnum.ERROR.getCode(), ResponseEnum.ERROR.getMessage(), null);
}
}
@@ -0,0 +1,33 @@
package cn.g3soft.config;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.cors.CorsConfiguration;
import org.springframework.web.cors.UrlBasedCorsConfigurationSource;
import org.springframework.web.filter.CorsFilter;
@Configuration
public class WebCorsConfig {
@Bean
public CorsFilter corsFilter() {
// 1.创建 CORS 配置对象
CorsConfiguration config = new CorsConfiguration();
// 支持域
config.addAllowedOriginPattern("*");
// 是否发送 Cookie
config.setAllowCredentials(true);
// 支持请求方式
config.addAllowedMethod("*");
// 允许的原始请求头部信息
config.addAllowedHeader("*");
// 暴露的头部信息
config.addExposedHeader("*");
// 有效期 1800秒,设置预检请求(OPTIONS请求)的缓存时间,1800秒意味着客户端可以缓存这个设置1800秒
config.setMaxAge(1800L);
// 2.添加地址映射
UrlBasedCorsConfigurationSource corsConfigurationSource = new UrlBasedCorsConfigurationSource();
corsConfigurationSource.registerCorsConfiguration("/**", config);
// 3.返回 CorsFilter 对象
return new CorsFilter(corsConfigurationSource);
}
}
@@ -0,0 +1,22 @@
package cn.g3soft.config.datasource;
import lombok.*;
import java.util.Map;
@Data
@AllArgsConstructor
@NoArgsConstructor
public class DataSourceConfig {
private Map<String, DbConfig> datasource;
@Setter
@Getter
public static class DbConfig {
// getters & setters
private String url;
private String username;
private String password;
private String driverClassName;
}
}
@@ -0,0 +1,216 @@
package cn.g3soft.config.datasource;
import cn.g3soft.context.DataSourceContextHolder;
import com.alibaba.druid.filter.Filter;
import com.alibaba.druid.pool.DruidDataSource;
import com.alibaba.druid.wall.WallConfig;
import com.alibaba.druid.wall.WallFilter;
import org.springframework.core.io.Resource;
import org.springframework.core.io.ResourceLoader;
import org.springframework.jdbc.datasource.lookup.AbstractRoutingDataSource;
import org.springframework.stereotype.Component;
import org.yaml.snakeyaml.Yaml;
import java.io.InputStream;
import java.sql.DriverManager;
import java.sql.SQLException;
import java.util.*;
@Component
public class DataSourceManager extends AbstractRoutingDataSource {
private static final String EXTERNAL_CONFIG_PATH = "file:config/datasource.yml";
private static final String CLASSPATH_CONFIG_PATH = "classpath:datasource.yml";
@javax.annotation.Resource
private ResourceLoader resourceLoader;
private final Map<Object, Object> targetDataSourceMap = new HashMap<>();
public DataSourceManager() {
super.setTargetDataSources(targetDataSourceMap);
}
@Override
protected Object determineCurrentLookupKey() {
return DataSourceContextHolder.getDataSourceKey();
}
public void loadDatasource() {
try {
// 从配置文件中解析出数据源配置对象
DataSourceConfig config = resolveConfigResource();
Map<String, DataSourceConfig.DbConfig> newDsMap = config.getDatasource();
Set<String> newKeys = newDsMap.keySet();
// 关闭并移除已经被删除(配置文件中不再存在)的旧数据源
Set<Object> existingKeys = new HashSet<>(targetDataSourceMap.keySet());
for (Object key : existingKeys) {
// 如果旧数据源中有但新配置中没有,说明该数据源被删除了
if (!newKeys.contains((String) key)) {
// 移除旧数据源并关闭连接池资源
DruidDataSource ds = (DruidDataSource) targetDataSourceMap.remove(key);
ds.close();
System.out.println("移除并关闭旧数据源 [" + key + "]");
}
}
// 遍历新配置,进行新增或更新处理
for (Map.Entry<String, DataSourceConfig.DbConfig> entry : newDsMap.entrySet()) {
String key = entry.getKey();
DataSourceConfig.DbConfig newConfig = entry.getValue();
// 获取已有的数据源实例(如果存在)
DruidDataSource existingDS = (DruidDataSource) targetDataSourceMap.get(key);
// 如果已有数据源配置和新配置相同,则跳过初始化
if (existingDS != null && isSameConfig(existingDS, newConfig)) {
System.out.println("数据源 [" + key + "] 配置未变更,跳过初始化");
continue;
}
// 如果已有数据源但配置不同,关闭旧数据源
if (existingDS != null) {
existingDS.close();
System.out.println("关闭已变更数据源 [" + key + "]");
}
try {
// 预先验证驱动和连接是否可用,防止错误配置
Class.forName(newConfig.getDriverClassName());
DriverManager.getConnection(newConfig.getUrl(), newConfig.getUsername(), newConfig.getPassword()).close();
// 创建并初始化新的 DruidDataSource 实例
DruidDataSource newDS = buildDruidDataSource(newConfig);
// 放入数据源映射中,替换旧数据源(或新增)
targetDataSourceMap.put(key, newDS);
System.out.println("数据源 [" + key + "] 初始化成功");
} catch (Exception e) {
// 初始化失败时输出错误日志,不影响其他数据源加载
System.err.printf("数据源 [%s] 初始化失败:%s%n", key, e.getMessage());
}
}
// 通知父类更新数据源路由配置
super.setTargetDataSources(targetDataSourceMap);
super.afterPropertiesSet();
} catch (Exception e) {
// 捕获整个过程中的异常,打印错误日志
System.err.println("加载数据源配置异常:" + e.getMessage());
}
}
private boolean isSameConfig(DruidDataSource oldDS, DataSourceConfig.DbConfig newConfig) {
return Objects.equals(oldDS.getUrl(), newConfig.getUrl()) &&
Objects.equals(oldDS.getUsername(), newConfig.getUsername()) &&
Objects.equals(oldDS.getPassword(), newConfig.getPassword()) &&
Objects.equals(oldDS.getDriverClassName(), newConfig.getDriverClassName());
}
private DataSourceConfig resolveConfigResource() throws Exception {
Resource resource = resourceLoader.getResource(EXTERNAL_CONFIG_PATH);
if (!resource.exists()) {
resource = resourceLoader.getResource(CLASSPATH_CONFIG_PATH);
}
if (!resource.exists()) {
throw new Exception("配置文件未找到:" + EXTERNAL_CONFIG_PATH + " 或 " + CLASSPATH_CONFIG_PATH);
}
try (InputStream in = resource.getInputStream()) {
return new Yaml().loadAs(in, DataSourceConfig.class);
}
}
private DruidDataSource buildDruidDataSource(DataSourceConfig.DbConfig config) throws Exception {
DruidDataSource ds = getDefaultDruidDataSource();
ds.setDriverClassName(config.getDriverClassName());
ds.setUrl(config.getUrl());
ds.setUsername(config.getUsername());
ds.setPassword(config.getPassword());
ds.init();
return ds;
}
private DruidDataSource getDefaultDruidDataSource() throws SQLException {
DruidDataSource dataSource = new DruidDataSource();
// 启动程序时,在连接池中初始化多少个连接(10-50已足够)
dataSource.setInitialSize(5);
// 回收空闲连接时,将保证至少有 minIdle 个连接(与 initialSize 相同)
dataSource.setMinIdle(5);
// 连接池中最多支持多少个活动会话
dataSource.setMaxActive(100);
// 程序向连接池中请求连接时,超过 maxWait 的值后,认为本次请求失败,即连接池,没有可用连接,单位毫秒,设置 -1 时表示无限等待(建议值为100)
dataSource.setMaxWait(100);
/*
缓存通过以下两个方法发起的 SQL:
public PreparedStatement prepareStatement(String sql)
public PreparedStatement prepareStatement(String sql, int resultSetType, int resultSetConcurrency)
(建议值为 true)
*/
dataSource.setPoolPreparedStatements(true);
// 每个连接最多缓存多少个 SQL(建议值为 20)
dataSource.setMaxPoolPreparedStatementPerConnectionSize(20);
// 检查空闲连接的频率,单位毫秒,非正整数时表示不进行检查(建议值:2000)
dataSource.setTimeBetweenEvictionRunsMillis(2000);
// 连接池中某个连接的空闲时长达到 N 毫秒后, 连接池在下次检查空闲连接时,将回收该连接,要小于防火墙超时设置 net.netfilter.nf_conntrack_tcp_timeout_established 的设置
dataSource.setMinEvictableIdleTimeMillis(600000);
// 配置一个连接在池中最大生存的时间,单位是毫秒
dataSource.setMaxEvictableIdleTimeMillis(900000);
// 程序没有 close 连接且空闲时长超过 minEvictableIdleTimeMillis,则会执行 validationQuery 指定的 SQL,以保证该程序连接不会池 kill 掉,其范围不超过 minIdle 指定的连接个数(建议值为 true)
dataSource.setKeepAlive(true);
// 检查池中的连接是否仍可用的 SQL 语句,druid 会连接到数据库执行该 SQL,如果正常返回,则表示连接可用,否则表示连接不可用
dataSource.setValidationQuery("SELECT 1");
// 当程序请求连接,池在分配连接时,是否先检查该连接是否有效(高效,并且保证安全性;建议值为 true)
dataSource.setTestWhileIdle(true);
// 程序申请连接时,进行连接有效性检查(低效,影响性能;建议值为 false)
dataSource.setTestOnBorrow(false);
// 程序返还连接时,进行连接有效性检查(低效,影响性能;建议值为 false)
dataSource.setTestOnReturn(false);
// 物理连接初始化的时候执行的 sql
// Collection<String> connectionInitSqls = new ArrayList<>(10);
// connectionInitSqls.add("SELECT 1 FROM DUAL");
// dataSource.setConnectionInitSqls(connectionInitSqls);
/*
这里配置的是插件,常用的插件有:
监控统计:stat
日志监控:log4j2
防御 SQL 注入:wall
*/
dataSource.setFilters("stat");
// 是否合并多个 DruidDataSource 的监控数据
dataSource.setUseGlobalDataSourceStat(true);
// 监控统计
// 是否启用慢 SQL 记录
dataSource.addConnectionProperty("druid.stat.logSlowSql", "true");
// 执行时间超过 slowSqlMillis 的就是慢,单位毫秒(建议值 500)
dataSource.addConnectionProperty("druid.stat.slowSqlMillis", "500");
// 要求程序从池中 get 到连接后,N 秒后必须 close,否则 druid 会强制回收该连接,不管该连接中是活动还是空闲,以防止进程不会进行 close 而霸占连接(建议值为 false,当发现程序有未正常 close 连接时设置为 true)
// dataSource.setRemoveAbandoned(false);
// 设置 druid 强制回收连接的时限,当程序从池中 get 到连接开始算起,超过此值后,druid将强制回收该连接,单位秒(应大于业务运行最长时间)
// dataSource.setRemoveAbandonedTimeout();
// 当 druid 强制回收连接后,是否将 stack trace 记录到日志中(建议值为 true)
// dataSource.setLogAbandoned(true);
// 连接属性。比如设置一些连接池统计方面的配置 druid.stat.mergeSql=true;druid.stat.slowSqlMillis=5000
// dataSource.setConnectProperties();
// 防御 SQL 注入
WallFilter wallFilter = new WallFilter();
WallConfig config = new WallConfig();
// 是否允许执行 DELETE 语句(建议值为 false)
config.setDeleteAllow(false);
// 是否允许删除表(建议值为 false)
config.setDropTableAllow(false);
wallFilter.setConfig(config);
// 插件代理
List<Filter> proxyFilters = new ArrayList<>(10);
proxyFilters.add(wallFilter);
dataSource.setProxyFilters(proxyFilters);
return dataSource;
}
}
@@ -0,0 +1,69 @@
package cn.g3soft.config.datasource;
import com.alibaba.druid.filter.Filter;
import com.alibaba.druid.pool.DruidDataSource;
import com.alibaba.druid.support.http.StatViewServlet;
import com.alibaba.druid.support.http.WebStatFilter;
import org.springframework.boot.web.servlet.FilterRegistrationBean;
import org.springframework.boot.web.servlet.ServletRegistrationBean;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import java.util.*;
@Configuration
public class DruidConfig {
@Bean
public ServletRegistrationBean<StatViewServlet> statViewServlet() {
// 监控信息显示页面
StatViewServlet statViewServlet = new StatViewServlet();
// 访问监控信息显示页面的 url 路径(建议值为 /druid/* )
String urlPattern = "/druid/*";
ServletRegistrationBean<StatViewServlet> bean = new ServletRegistrationBean<>(statViewServlet, urlPattern);
Map<String, String> initParams = new HashMap<>();
// 是否允许清空统计数据
initParams.put("resetEnable", "false");
// 登录监控信息显示页面的用户名
initParams.put("loginUsername", "admin");
// 登录监控信息显示页面的密码
initParams.put("loginPassword", "admin");
// 允许访问控制(格式:ip地址、ip地址/子网掩码位数)逗号分隔多个地址
initParams.put("allow", "127.0.0.1");
// 拒绝访问控制(格式:ip地址、ip地址/子网掩码位数)逗号分隔多个地址
// initParams.put("deny", "");
bean.setInitParameters(initParams);
return bean;
}
@Bean
public FilterRegistrationBean<WebStatFilter> webStatFilter() {
FilterRegistrationBean<WebStatFilter> bean = new FilterRegistrationBean<>();
// 网络监控过滤器(用于采集 web-jdbc 关联监控的数据)
bean.setFilter(new WebStatFilter());
// 过滤所有的 url 路径
Collection<String> urlPatterns = new ArrayList<>();
urlPatterns.add("/*");
bean.setUrlPatterns(urlPatterns);
Map<String, String> initParams = new HashMap<>();
// 排除不必要采集的 url 路径,以逗号“,”分割
initParams.put("exclusions", "*.js,*.gif,*.jpg,*.png,*.css,*.ico,*.map,/druid/*");
// 是否使用 session 监控功能
initParams.put("sessionStatEnable", "true");
// 是否使用 session 监控最大数量(默认是1000)
initParams.put("sessionStatMaxCount", "1000");
// 使得 druid 能够知道当前的 session 的用户是谁,根据需要,把改值修改为你 user 信息保存在 session 中的 sessionName
initParams.put("principalSessionName", "session_user_key");
// 如果你的 user 信息保存在 cookie 中,你可以配置 principalCookieName,使得 druid 知道当前的 user 是谁,根据需要,把该值修改为你 user 信息保存在 cookie 中的 cookieName
initParams.put("principalCookieName", "cookie_user_key");
// 是否监控单个 url 调用的 sql 列表
initParams.put("profileEnable", "true");
bean.setInitParameters(initParams);
return bean;
}
}
@@ -0,0 +1,21 @@
package cn.g3soft.config.datasource;
import lombok.extern.slf4j.Slf4j;
import org.springframework.boot.CommandLineRunner;
import org.springframework.stereotype.Component;
import javax.annotation.Resource;
@Slf4j
@Component
public class LoadDataSourceRunner implements CommandLineRunner {
@Resource
private DataSourceManager dataSourceManager;
@Override
public void run(String... args) throws Exception {
dataSourceManager.loadDatasource();
}
}
@@ -0,0 +1,28 @@
package cn.g3soft.constants;
public class ResponseStatusConstants {
/**
* 操作成功
*/
public static final Integer SUCCESS = 200;
/**
* 系统内部错误
*/
public static final Integer ERROR = 500;
/**
* 缺少参数,参数格式有误,请求路径有误等
*/
public static final Integer BAD_REQUEST = 400;
/**
* 未找到资源,或者该资源已丢失
*/
public static final Integer NOT_FOUND = 404;
/**
* 登录失效或已过期
*/
public static final Integer UNAUTHORIZED = 401;
}
@@ -0,0 +1,17 @@
package cn.g3soft.context;
public class DataSourceContextHolder {
private static final ThreadLocal<String> contextHolder = new ThreadLocal<>();
public static void setDataSourceKey(String dataSourceKey) {
contextHolder.set(dataSourceKey);
}
public static String getDataSourceKey() {
return contextHolder.get();
}
public static void clearDataSourceKey() {
contextHolder.remove();
}
}
@@ -0,0 +1,20 @@
package cn.g3soft.controller;
import cn.g3soft.config.datasource.DataSourceManager;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import javax.annotation.Resource;
@RestController
@RequestMapping("/config")
public class ConfigController {
@Resource
private DataSourceManager dataSourceManager;
@GetMapping("/refreshDb")
public void refreshDb(){
dataSourceManager.loadDatasource();
}
}
@@ -0,0 +1,49 @@
package cn.g3soft.controller;
import cn.g3soft.context.DataSourceContextHolder;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import javax.annotation.Resource;
import java.util.List;
import java.util.Map;
@RestController
@RequestMapping("/test")
public class TestController {
@Resource
JdbcTemplate jdbcTemplate;
@GetMapping("/test1")
public List<Map<String, Object>> test1() {
// 切换数据源为 db1
DataSourceContextHolder.setDataSourceKey("db1");
String sql = "SELECT * FROM user_account";
// 执行查询,返回List<Map>,每个Map代表一行记录,列名-值对
List<Map<String, Object>> result = jdbcTemplate.queryForList(sql);
// 查询完后可以清理上下文,防止影响后续操作
DataSourceContextHolder.clearDataSourceKey();
return result;
}
@GetMapping("/test2")
public List<Map<String, Object>> test2() {
// 切换数据源为 db1
DataSourceContextHolder.setDataSourceKey("db2");
String sql = "SELECT * FROM sys_admin";
// 执行查询,返回List<Map>,每个Map代表一行记录,列名-值对
List<Map<String, Object>> result = jdbcTemplate.queryForList(sql);
// 查询完后可以清理上下文,防止影响后续操作
DataSourceContextHolder.clearDataSourceKey();
return result;
}
}
@@ -0,0 +1,18 @@
package cn.g3soft.enums;
import cn.g3soft.constants.ResponseStatusConstants;
import lombok.AllArgsConstructor;
import lombok.Getter;
@AllArgsConstructor
@Getter
public enum ResponseEnum {
SUCCESS(ResponseStatusConstants.SUCCESS,"请求成功"),
ERROR(ResponseStatusConstants.ERROR,"服务器错误,请联系管理人员"),
AUTH_SUCCESS(ResponseStatusConstants.SUCCESS,"登录成功"),
AUTH_ERROR(ResponseStatusConstants.ERROR,"登录失败"),
AUTH_EXPIRE(ResponseStatusConstants.UNAUTHORIZED,"尚未登录或登录已过期,请重新登录");
private final Integer code; //响应码
private final String message; //相应信息
}
@@ -0,0 +1,59 @@
package cn.g3soft.exception;
import cn.g3soft.enums.ResponseEnum;
import lombok.Data;
import lombok.EqualsAndHashCode;
@EqualsAndHashCode(callSuper = true)
@Data
public class BaseException extends RuntimeException {
private final boolean showDetail;
private final String message;
private final Integer code;
private final ResponseEnum responseEnum;
public BaseException(String message) {
this(message, false, null, null);
}
public BaseException(String message, Integer code) {
this(message, false, code, null);
}
public BaseException(Integer code, String message) {
this(message, false, code, null);
}
// 3. 传 ResponseEnum,默认 showDetail=false
public BaseException(ResponseEnum responseEnum) {
super(responseEnum.getMessage());
this.message = responseEnum.getMessage();
this.showDetail = false;
this.code = responseEnum.getCode();
this.responseEnum = responseEnum;
}
public BaseException(String message, boolean showDetail) {
this(message, showDetail, null, null);
}
public BaseException(String message, Integer code, boolean showDetail) {
this(message, showDetail, code, null);
}
public BaseException(ResponseEnum responseEnum, boolean showDetail) {
super(responseEnum.getMessage());
this.message = responseEnum.getMessage();
this.showDetail = showDetail;
this.code = responseEnum.getCode();
this.responseEnum = responseEnum;
}
private BaseException(String message, boolean showDetail, Integer code, ResponseEnum responseEnum) {
super(message);
this.message = message;
this.showDetail = showDetail;
this.code = code;
this.responseEnum = responseEnum;
}
}
@@ -0,0 +1,46 @@
package cn.g3soft.exception;
import cn.g3soft.common.Result;
import cn.g3soft.exception.auth.AuthException;
import lombok.extern.slf4j.Slf4j;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.RestControllerAdvice;
import javax.servlet.http.HttpServletRequest;
import javax.validation.ConstraintViolation;
import javax.validation.ConstraintViolationException;
@Slf4j
@RestControllerAdvice
public class GlobalExceptionHandler {
@ExceptionHandler(ConstraintViolationException.class)
public Result<Object> handleConstraintViolationException(ConstraintViolationException ex) {
// 取第一个错误消息
String errorMessage = ex.getConstraintViolations().stream()
.map(ConstraintViolation::getMessage)
.findFirst()
.orElse("参数校验失败");
return Result.error(errorMessage);
}
@ExceptionHandler(AuthException.class)
public Result<?> handleAuthException(AuthException ex){
return Result.error(ex.getMessage());
}
// @ExceptionHandler(Exception.class)
// public Result<ExceptionDetailInfo> handleException(Exception ex, HttpServletRequest request) {
// ExceptionDetailInfo info = ExceptionUtils.extractExceptionInfo(ex, request, "com.email");
// log.error("异常发生在 {}#{}({}:{}),ID: {},信息: {}",
// info.getClassName(),
// info.getMethodName(),
// info.getFileName(),
// info.getLineNumber(),
// info.getId(),
// info.getSummary());
// ex.printStackTrace();
// return Result.error(info).withShowDetail(true);
// }
}
@@ -0,0 +1,19 @@
package cn.g3soft.exception.auth;
import cn.g3soft.enums.ResponseEnum;
import cn.g3soft.exception.BaseException;
public class AuthException extends BaseException {
public AuthException() {
super(ResponseEnum.AUTH_EXPIRE); // 调用父类的默认构造函数
}
public AuthException(ResponseEnum responseEnum){
super(responseEnum);
}
public AuthException(Integer code,String message){
super(code,message);
}
}
@@ -0,0 +1,157 @@
package cn.g3soft.utils;
import org.springframework.aop.framework.AopContext;
import org.springframework.beans.BeansException;
import org.springframework.beans.factory.NoSuchBeanDefinitionException;
import org.springframework.beans.factory.config.BeanFactoryPostProcessor;
import org.springframework.beans.factory.config.ConfigurableListableBeanFactory;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware;
import org.springframework.stereotype.Component;
/**
* spring工具类 方便在非spring管理环境中获取bean
*
* @author webserver
*/
@Component
public final class SpringUtils implements BeanFactoryPostProcessor, ApplicationContextAware
{
/** Spring应用上下文环境 */
private static ConfigurableListableBeanFactory beanFactory;
private static ApplicationContext applicationContext;
@Override
public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) throws BeansException
{
SpringUtils.beanFactory = beanFactory;
}
@Override
public void setApplicationContext(ApplicationContext applicationContext) throws BeansException
{
SpringUtils.applicationContext = applicationContext;
}
/**
* 获取对象
*
* @param name
* @return Object 一个以所给名字注册的bean的实例
* @throws org.springframework.beans.BeansException
*
*/
@SuppressWarnings("unchecked")
public static <T> T getBean(String name) throws BeansException
{
return (T) beanFactory.getBean(name);
}
/**
* 获取类型为requiredType的对象
*
* @param clz
* @return
* @throws org.springframework.beans.BeansException
*
*/
public static <T> T getBean(Class<T> clz) throws BeansException
{
T result = (T) beanFactory.getBean(clz);
return result;
}
/**
* 如果BeanFactory包含一个与所给名称匹配的bean定义,则返回true
*
* @param name
* @return boolean
*/
public static boolean containsBean(String name)
{
return beanFactory.containsBean(name);
}
/**
* 判断以给定名字注册的bean定义是一个singleton还是一个prototype。 如果与给定名字相应的bean定义没有被找到,将会抛出一个异常(NoSuchBeanDefinitionException)
*
* @param name
* @return boolean
* @throws org.springframework.beans.factory.NoSuchBeanDefinitionException
*
*/
public static boolean isSingleton(String name) throws NoSuchBeanDefinitionException
{
return beanFactory.isSingleton(name);
}
/**
* @param name
* @return Class 注册对象的类型
* @throws org.springframework.beans.factory.NoSuchBeanDefinitionException
*
*/
public static Class<?> getType(String name) throws NoSuchBeanDefinitionException
{
return beanFactory.getType(name);
}
/**
* 如果给定的bean名字在bean定义中有别名,则返回这些别名
*
* @param name
* @return
* @throws org.springframework.beans.factory.NoSuchBeanDefinitionException
*
*/
public static String[] getAliases(String name) throws NoSuchBeanDefinitionException
{
return beanFactory.getAliases(name);
}
/**
* 获取aop代理对象
*
* @param invoker
* @return
*/
@SuppressWarnings("unchecked")
public static <T> T getAopProxy(T invoker)
{
return (T) AopContext.currentProxy();
}
/**
* 获取当前的环境配置,无配置返回null
*
* @return 当前的环境配置
*/
public static String[] getActiveProfiles()
{
return applicationContext.getEnvironment().getActiveProfiles();
}
/**
* 获取当前的环境配置,当有多个环境配置时,只获取第一个
*
* @return 当前的环境配置
*/
public static String getActiveProfile()
{
final String[] activeProfiles = getActiveProfiles();
return activeProfiles.length > 0 ? activeProfiles[0] : null;
}
/**
* 获取配置文件中的值
*
* @param key 配置文件的key
* @return 当前的配置文件的值
*
*/
public static String getRequiredProperty(String key)
{
return applicationContext.getEnvironment().getRequiredProperty(key);
}
}
@@ -0,0 +1,21 @@
server:
servlet:
context-path: / # 应用的访问路径
tomcat:
uri-encoding: UTF-8 # URI编码
accept-count: 1000 # 连接数满后的排队数
threads:
max: 800 # 最大线程数
min-spare: 100 # 初始化的线程数
spring:
config:
name: application
location: classpath:/,file:./config/
jackson:
date-format: yyyy-MM-dd HH:mm:ss
time-zone: Asia/Shanghai
servlet:
multipart:
max-file-size: -1
max-request-size: -1
@@ -0,0 +1,11 @@
datasource:
db1:
url: jdbc:jtds:sqlserver://192.168.1.176:1433;databaseName=G3-Email
username: sa
password: sa
driverClassName: net.sourceforge.jtds.jdbc.Driver
db2:
url: jdbc:jtds:sqlserver://192.168.1.176:1433;databaseName=Sllog
username: sa
password: sa
driverClassName: net.sourceforge.jtds.jdbc.Driver