之前简单介绍了一下 Dataway 使用,本文继续介绍一下它的多数据源配置和使用。
# springboot多环境配置
#端口,项目上下文
server:
port: 8080
servlet:
context-path: /springboot-dataway
# 全局服务编码设置
encoding:
charset: utf-8
enabled: true
force: true
# mysql 连接信息配置
spring:
# mysql 数据库连接信息,本地使用 mysql 服务版本为:8.0.28
datasource:
username: root
password: 6tojyh*A3eQ6
url: jdbc:mysql://localhost:3306/dataway?useSSL=false&useUnicode=true&characterEncoding=utf8&serverTimezone=Asia/Shanghai&rewriteBatchedStatements=true&allowPublicKeyRetrieval=true
driver-class-name: com.mysql.cj.jdbc.Driver
# druid 数据连接池配置
druid:
initial-size: 3
min-idle: 3
max-active: 10
max-wait: 6000
# 配置druid监控页
aop-patterns: com.demo.* #监控springBean
stat-view-servlet: # 配置监控页功能
enabled: true # 默认开启,这里显示说明
login-username: admin # 登录名
login-password: 6tojyh*A3eQ6 # 登录密码
reset-enable: false # 禁用重置按钮
web-stat-filter: # 监控 web
enabled: true
url-pattern: /* # 监控所有
exclusions: '*.js,*.gif,*.jpg,*.png,*.css,*.ico,/druid/*' #放行
filter:
stat: # 对上面 filters 里的 stat 的详细配置
slow-sql-millis: 1000 # 慢 sql 时间是毫秒单位的,执行时间 1 秒以上的为慢 SQL
log-slow-sql: true # 日志记录
enabled: true
wall:
enabled: true
config:
drop-table-allow: false # 禁用删除表的 sql
# 除主数据源外,第1个数据源
db1:
username: root
password: 6tojyh*A3eQ6
url: jdbc:mysql://localhost:3306/console?useSSL=false&useUnicode=true&characterEncoding=utf8&serverTimezone=Asia/Shanghai&rewriteBatchedStatements=true&allowPublicKeyRetrieval=true
driver-class-name: com.mysql.cj.jdbc.Driver
# 除主数据源外,第 2 个数据源
db2:
username: postgres
password: UFWOd75qD7
url: jdbc:postgresql://localhost:32770/postgres?binaryTransfer=false&forceBinary=false&reWriteBatchedInserts=true
driver-class-name: org.postgresql.Driver
# mdataway 配置
# 是否启用 Dataway 功能(必选:默认false)
HASOR_DATAQL_DATAWAY: true
# 开启 ui 管理功能(注意生产环境必须要设置为 false,否则会造成严重的生产安全事故)
HASOR_DATAQL_DATAWAY_ADMIN: true
# dataway API工作路径(可选,默认:/api/)
HASOR_DATAQL_DATAWAY_API_URL: /api/
# dataway-ui 的工作路径(可选,默认:/interface-ui/)
HASOR_DATAQL_DATAWAY_UI_URL: /interface-ui/
# SQL执行器方言设置(可选,建议设置)
HASOR_DATAQL_FX_PAGE_DIALECT: mysql
# 登陆认证方式在 basic 模式下的时候,配置的登陆账号
HASOR_DATAQL_DATAWAY_AUTHORIZATION_USERNAME: admin
# 登陆认证方式在 basic 模式下的时候,配置的登陆密码,默认密码admin
HASOR_DATAQL_DATAWAY_AUTHORIZATION_PASSWORD: 6tojyh*A3eQ6
# 日志输出配置
logging:
level:
root: debug
org:
springframework:
security: WARN
web: ERROR
# 设置自己的 com.demo.mapper 目录 输出sql日志
com.demo.mapper: debug
file:
path: ./logs
name: './logs/springboot-dataway.log'
pattern:
file: '%d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level %logger{50}:%L - %msg%n'
console: '%d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level %logger{50}:%L - %msg%n'
package com.demo.config;
import com.alibaba.druid.pool.DruidDataSource;
import com.demo.chain.*;
import net.hasor.core.ApiBinder;
import net.hasor.core.DimModule;
import net.hasor.core.TypeSupplier;
import net.hasor.dataql.Finder;
import net.hasor.dataql.QueryApiBinder;
import net.hasor.dataql.fx.db.FxSqlCheckChainSpi;
import net.hasor.dataway.spi.LoginPerformChainSpi;
import net.hasor.dataway.spi.LoginTokenChainSpi;
import net.hasor.dataway.spi.PreExecuteChainSpi;
import net.hasor.dataway.spi.ResultProcessChainSpi;
import net.hasor.db.JdbcModule;
import net.hasor.db.Level;
import net.hasor.spring.SpringModule;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.ApplicationContext;
import org.springframework.stereotype.Component;
import javax.annotation.Resource;
import javax.sql.DataSource;
/**
* @Classname DatawayModule
* @Description 将Hasor模块注入spring,并注入数据源
* @Date 2023/7/28 11:43
* @Created by Leo825
*/
@DimModule // Hasor 中的标签,表明是一个Hasor的model
@Component // Spring 中的标签,表明是一个组件
public class DatawayModule implements SpringModule,TypeSupplier {
// 默认数据源
@Autowired
private DataSource dataSource;
// 数据源1的配置信息
@Value("${spring.datasource.db1.url}")
private String jdbcUrl1;
@Value("${spring.datasource.db1.driver-class-name}")
private String driver1;
@Value("${spring.datasource.db1.username}")
private String username1;
@Value("${spring.datasource.db1.password}")
private String password1;
// 数据源2的配置信息
@Value("${spring.datasource.db2.url}")
private String jdbcUrl2;
@Value("${spring.datasource.db2.driver-class-name}")
private String driver2;
@Value("${spring.datasource.db2.username}")
private String username2;
@Value("${spring.datasource.db2.password}")
private String password2;
@Resource
private ApplicationContext applicationContext;
@Override
public <T> T get(Class<? extends T> targetType) {
return applicationContext.getBean(targetType);
}
@Override
public <T> boolean test(Class<? extends T> targetType) {
return applicationContext.getBeanNamesForType(targetType).length > 0;
}
@Override
public void loadModule(ApiBinder apiBinder) throws Throwable {
// .DataSource form Spring boot into Hasor
apiBinder.installModule(new JdbcModule(Level.Full, this.dataSource));
// 注入数据源
apiBinder.installModule(new JdbcModule(Level.Full, this.dataSource));
apiBinder.installModule(new JdbcModule(Level.Full, "dataSource1", getDataSource(jdbcUrl1, driver1, username1, password1)));
apiBinder.installModule(new JdbcModule(Level.Full, "dataSource2", getDataSource(jdbcUrl2, driver2, username2, password2)));
// 打印sql日志
apiBinder.bindSpiListener(FxSqlCheckChainSpi.class, FxSqlCheckChain.getInstance());
// 数据权限参数
apiBinder.bindSpiListener(PreExecuteChainSpi.class, PreExecuteChain.getInstance());
// 返回结果
apiBinder.bindSpiListener(ResultProcessChainSpi.class, ResultProcessChain.getInstance());
// // 登录过滤
// apiBinder.bindSpiListener(LoginPerformChainSpi.class, LoginPerformChain.getInstance());
// 登录 token 过滤
// apiBinder.bindSpiListener(LoginTokenChainSpi.class, LoginTokenChain.getInstance());
// udf/udfSource/import 指令 的类型创建委托给 spring
QueryApiBinder queryBinder = apiBinder.tryCast(QueryApiBinder.class);
queryBinder.bindFinder(Finder.TYPE_SUPPLIER.apply(this));
}
/**
* 注入数据源
*
* @param
* @param driver
* @param username
* @param password
* @return
*/
private DruidDataSource getDataSource(String jdbcUrl, String driver, String username, String password) {
DruidDataSource dataSource = new DruidDataSource();
dataSource.setUrl(jdbcUrl);
dataSource.setUsername(username);
dataSource.setPassword(password);
dataSource.setDriverClassName(driver);
// 用来检测连接是否有效
dataSource.setValidationQuery("SELECT 1");
// 借用连接时执行validationQuery检测连接是否有效,做了这个配置会降低性能
dataSource.setTestOnBorrow(false);
// 归还连接时执行validationQuery检测连接是否有效,做了这个配置会降低性能
dataSource.setTestOnReturn(false);
// 连接空闲时检测,如果连接空闲时间大于timeBetweenEvictionRunsMillis指定的毫秒,
// 执行validationQuery指定的SQL来检测连接是否有效
// 如果检测失败,则连接将被从池中去除
dataSource.setTestWhileIdle(true);
dataSource.setTimeBetweenEvictionRunsMillis(60000);//1分钟
dataSource.setMaxActive(20);
dataSource.setInitialSize(5);
return dataSource;
}
}
FxSqlCheckChainSpi,这里主要用来打印 sql
package com.demo.chain;
import lombok.extern.slf4j.Slf4j;
import net.hasor.dataql.fx.db.FxSqlCheckChainSpi;
import net.hasor.utils.StringUtils;
/**
* @Classname FxSqlCheckChain
* @Description 打印sql
* @Date 2023/8/3 20:35
* @Created by Leo825
*/
@Slf4j
public class FxSqlCheckChain implements FxSqlCheckChainSpi {
public static FxSqlCheckChain getInstance() {
return new FxSqlCheckChain();
}
@Override
public int doCheck(FxSqlInfo fxSqlInfo) throws Throwable {
String sourceName = fxSqlInfo.getSourceName();
if (StringUtils.isNotEmpty(sourceName)) {
log.info("【dataway】dataSource ==>:{}", sourceName);
}
log.info("【dataway】sql ==>:{}", fxSqlInfo.getQueryString().trim());
log.info("【dataway】params ==>: {}", fxSqlInfo.getQueryParams());
// 如果存在后续,那么继续执行检查,否则使用 EXIT 常量控制退出后续的检查。
return FxSqlCheckChainSpi.NEXT;
}
}
PreExecuteChainSpi,这里主要是在接口执行前进行一些权限参数控制
package com.demo.chain;
import lombok.extern.slf4j.Slf4j;
import net.hasor.dataway.spi.ApiInfo;
import net.hasor.dataway.spi.PreExecuteChainSpi;
import net.hasor.utils.future.BasicFuture;
import org.apache.commons.collections4.MapUtils;
import java.util.HashMap;
import java.util.Map;
/**
* @Classname PreExecuteChain
* @Description 接口执行前
* @Date 2023/8/3 20:37
* @Created by Leo825
*/
@Slf4j
public class PreExecuteChain implements PreExecuteChainSpi {
public static PreExecuteChain getInstance() {
return new PreExecuteChain();
}
/**
* sql 直接前
* @param apiInfo
* @param basicFuture
*/
@Override
public void preExecute(ApiInfo apiInfo, BasicFuture<Object> basicFuture) {
Map<String, Object> parameter = apiInfo.getParameterMap();
// 注入用户权限参数
parameter.putAll(MapUtils.emptyIfNull(loginUserAuthParams()));
}
/**
* 获取登录用户权限参数
* @return
*/
private Map<String, Object> loginUserAuthParams () {
Map<String, Object> authParams = new HashMap<>();
// todo 注入登录用户 权限参数
return authParams;
}
}
ResultProcessChainSpi,主要是返回值进行一些封装
package com.demo.chain;
import com.demo.common.AjaxResult;
import lombok.extern.slf4j.Slf4j;
import net.hasor.dataway.spi.ApiInfo;
import net.hasor.dataway.spi.ResultProcessChainSpi;
/**
* @Classname ReturnProcessChain
* @Description 兼
* 容 4.1.5之前 dataway版本 dataway 执行结果处理, 让 structure 配置仅使用于 dataway页面调试使用
* @Date 2023/8/3 20:47
* @Created by Leo825
*/
@Slf4j
public class ResultProcessChain implements ResultProcessChainSpi {
public static ResultProcessChain getInstance() {
return new ResultProcessChain();
}
/**
* 对返回结果进行处理
* @param formPre
* @param apiInfo
* @param result
* @return
*/
@Override
public Object callAfter(boolean formPre, ApiInfo apiInfo, Object result) {
// apiInfo.isPerform() 为 true 表示,API 调用是从 UI 界面发起的。
if (apiInfo.isPerform() || apiInfo.getParameterMap().containsKey("SELF_CALL")) {
return result;
}
apiInfo.getOptionMap().put("resultStructure", false);
return AjaxResult.success(result);
}
/**
* 异常
* @param formPre
* @param apiInfo
* @param e
* @return
*/
@Override
public Object callError(boolean formPre, ApiInfo apiInfo, Throwable e) {
if (apiInfo.isPerform()) {
return ResultProcessChainSpi.super.callError(formPre, apiInfo, e);
}
apiInfo.getOptionMap().put("resultStructure", false);
return AjaxResult.error( "系统繁忙");
}
}
package com.demo.config;
import org.springframework.context.annotation.Primary;
import org.springframework.stereotype.Component;
import springfox.documentation.swagger.web.SwaggerResource;
import springfox.documentation.swagger.web.SwaggerResourcesProvider;
import java.util.ArrayList;
import java.util.List;
/**
* swagger配置
*/
@Component
@Primary
public class SwaggerProvider implements SwaggerResourcesProvider {
@Override
public List<SwaggerResource> get() {
List<SwaggerResource> resources = new ArrayList<>();
resources.add(swaggerResource("应用接口", "/v2/api-docs", "1.0"));
resources.add(swaggerResource("Dataway接口", "/interface-ui/api/docs/swagger2.json", "1.0"));
return resources;
}
private SwaggerResource swaggerResource(String name, String location, String version) {
SwaggerResource swaggerResource = new SwaggerResource();
swaggerResource.setName(name);
swaggerResource.setLocation(location);
swaggerResource.setSwaggerVersion(version);
return swaggerResource;
}
}
将 Dataway 里面的接口发布到 swagger里面
package com.demo.config;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import springfox.documentation.builders.ApiInfoBuilder;
import springfox.documentation.builders.PathSelectors;
import springfox.documentation.builders.RequestHandlerSelectors;
import springfox.documentation.service.ApiInfo;
import springfox.documentation.spi.DocumentationType;
import springfox.documentation.spring.web.plugins.Docket;
import springfox.documentation.swagger2.annotations.EnableSwagger2;
/**
* swagger配置
*/
@EnableSwagger2
@Configuration()
public class SwaggerConfig {
@Bean
public Docket createRestApi() {
return new Docket(DocumentationType.SWAGGER_2)//
.apiInfo(apiInfo())//
.select()//
.apis(RequestHandlerSelectors.basePackage("com.demo"))//
.paths(PathSelectors.any())//
.build();
}
private ApiInfo apiInfo() {
return new ApiInfoBuilder()//
.title("Spring Boot中使用Swagger2构建RESTful APIs")//
.description("参考手册:https://www.hasor.net/doc/display/dataql/")//
.termsOfServiceUrl("https://www.hasor.net/doc/display/dataql/")//
.contact("[email protected]").version("1.0")//
.build();
}
}
可以使用自定义udf,并且在管理页面可以直接访问
package com.demo.udf;
import com.demo.config.SpringContextUtil;
import com.demo.service.MyUdfService;
import lombok.extern.slf4j.Slf4j;
import net.hasor.dataql.DimUdf;
import net.hasor.dataql.Hints;
import net.hasor.dataql.Udf;
import org.springframework.stereotype.Service;
import javax.annotation.Resource;
/**
* 自定义 udf,需要在 DatawayModule 添加以下配置代码,否则会出现注入为 null 问题
* // udf/udfSource/import 指令 的类型创建委托给 spring
* QueryApiBinder queryBinder = apiBinder.tryCast(QueryApiBinder.class);
* queryBinder.bindFinder(Finder.TYPE_SUPPLIER.apply(this));
*/
@Slf4j
@DimUdf("myNameUdf")
@Service
public class MyNameUdf implements Udf {
@Resource
private MyUdfService myUdfService;
@Override
public Object call(Hints readOnly, Object... params) {
log.info("获取当前服务信息: " + myUdfService.myName());
return "张三";
}
}
web管理页面调用方式如下:
import 'com.demo.udf.MyNameUdf' as myNameUdf;
return myNameUdf();
据官方文档说可以通过 import ‘bean’ 方式引入,但是尝试了没有成功。
主要通过 FRAGMENT_SQL_DATA_SOURCE 来指定数据源:
// springboot 整合Dataway 多数据源配置。使用示例:
hint FRAGMENT_SQL_DATA_SOURCE = "dataSource2";
var query = @@sql()<%
select * from subjects
%>
return query()