feat(bpm): 适配达梦数据库(DM8) for master-jdk17 (Flowable 7.2.0)

This commit is contained in:
puhui999
2026-03-09 12:29:44 +08:00
parent 82037d0271
commit 8d58ab0067
10 changed files with 858 additions and 22 deletions

View File

@@ -16,7 +16,7 @@
<module>yudao-module-system</module> <module>yudao-module-system</module>
<module>yudao-module-infra</module> <module>yudao-module-infra</module>
<!-- <module>yudao-module-member</module>--> <!-- <module>yudao-module-member</module>-->
<!-- <module>yudao-module-bpm</module>--> <module>yudao-module-bpm</module>
<!-- <module>yudao-module-report</module>--> <!-- <module>yudao-module-report</module>-->
<!-- <module>yudao-module-mp</module>--> <!-- <module>yudao-module-mp</module>-->
<!-- <module>yudao-module-pay</module>--> <!-- <module>yudao-module-pay</module>-->

View File

@@ -51,7 +51,6 @@
<dependency> <dependency>
<groupId>com.dameng</groupId> <groupId>com.dameng</groupId>
<artifactId>DmJdbcDriver18</artifactId> <artifactId>DmJdbcDriver18</artifactId>
<optional>true</optional>
</dependency> </dependency>
<dependency> <dependency>
<groupId>cn.com.kingbase</groupId> <groupId>cn.com.kingbase</groupId>

View File

@@ -75,5 +75,11 @@
<groupId>org.flowable</groupId> <groupId>org.flowable</groupId>
<artifactId>flowable-spring-boot-starter-actuator</artifactId> <artifactId>flowable-spring-boot-starter-actuator</artifactId>
</dependency> </dependency>
<!-- Liquibase达梦数据库适配补丁DmDatabase、BooleanType编译依赖 -->
<dependency>
<groupId>org.liquibase</groupId>
<artifactId>liquibase-core</artifactId>
<scope>provided</scope> <!-- 设置为 provided只有 bpm 使用到 -->
</dependency>
</dependencies> </dependencies>
</project> </project>

View File

@@ -6,12 +6,15 @@ import cn.iocoder.yudao.module.bpm.framework.flowable.core.candidate.BpmTaskCand
import cn.iocoder.yudao.module.bpm.framework.flowable.core.candidate.BpmTaskCandidateStrategy; import cn.iocoder.yudao.module.bpm.framework.flowable.core.candidate.BpmTaskCandidateStrategy;
import cn.iocoder.yudao.module.bpm.framework.flowable.core.event.BpmProcessInstanceEventPublisher; import cn.iocoder.yudao.module.bpm.framework.flowable.core.event.BpmProcessInstanceEventPublisher;
import cn.iocoder.yudao.module.system.api.user.AdminUserApi; import cn.iocoder.yudao.module.system.api.user.AdminUserApi;
import org.apache.ibatis.mapping.DatabaseIdProvider;
import org.apache.ibatis.mapping.VendorDatabaseIdProvider;
import org.flowable.common.engine.api.delegate.FlowableFunctionDelegate; import org.flowable.common.engine.api.delegate.FlowableFunctionDelegate;
import org.flowable.common.engine.api.delegate.event.FlowableEventListener; import org.flowable.common.engine.api.delegate.event.FlowableEventListener;
import org.flowable.spring.SpringProcessEngineConfiguration; import org.flowable.spring.SpringProcessEngineConfiguration;
import org.flowable.spring.boot.EngineConfigurationConfigurer; import org.flowable.spring.boot.EngineConfigurationConfigurer;
import org.springframework.beans.factory.ObjectProvider; import org.springframework.beans.factory.ObjectProvider;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean; import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.context.ApplicationEventPublisher; import org.springframework.context.ApplicationEventPublisher;
import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration; import org.springframework.context.annotation.Configuration;
@@ -19,6 +22,7 @@ import org.springframework.core.task.AsyncTaskExecutor;
import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor; import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor;
import java.util.List; import java.util.List;
import java.util.Properties;
/** /**
* BPM 模块的 Flowable 配置类 * BPM 模块的 Flowable 配置类
@@ -29,7 +33,8 @@ import java.util.List;
public class BpmFlowableConfiguration { public class BpmFlowableConfiguration {
/** /**
* 参考 {@link org.flowable.spring.boot.FlowableJobConfiguration} 类,创建对应的 AsyncListenableTaskExecutor Bean * 参考 {@link org.flowable.spring.boot.FlowableJobConfiguration} 类,创建对应的
* AsyncListenableTaskExecutor Bean
* <p> * <p>
* 如果不创建会导致项目启动时Flowable 报错的问题 * 如果不创建会导致项目启动时Flowable 报错的问题
*/ */
@@ -65,7 +70,8 @@ public class BpmFlowableConfiguration {
// 设置 ActivityBehaviorFactory 实现类,用于流程任务的审核人的自定义 // 设置 ActivityBehaviorFactory 实现类,用于流程任务的审核人的自定义
configuration.setActivityBehaviorFactory(bpmActivityBehaviorFactory); configuration.setActivityBehaviorFactory(bpmActivityBehaviorFactory);
// 设置自定义的函数 // 设置自定义的函数
configuration.setCustomFlowableFunctionDelegates(ListUtil.toList(customFlowableFunctionDelegates.stream().iterator())); configuration.setCustomFlowableFunctionDelegates(
ListUtil.toList(customFlowableFunctionDelegates.stream().iterator()));
}; };
} }
@@ -81,7 +87,7 @@ public class BpmFlowableConfiguration {
@Bean @Bean
@SuppressWarnings("SpringJavaInjectionPointsAutowiringInspection") // adminUserApi 可以注入成功 @SuppressWarnings("SpringJavaInjectionPointsAutowiringInspection") // adminUserApi 可以注入成功
public BpmTaskCandidateInvoker bpmTaskCandidateInvoker(List<BpmTaskCandidateStrategy> strategyList, public BpmTaskCandidateInvoker bpmTaskCandidateInvoker(List<BpmTaskCandidateStrategy> strategyList,
AdminUserApi adminUserApi) { AdminUserApi adminUserApi) {
return new BpmTaskCandidateInvoker(strategyList, adminUserApi); return new BpmTaskCandidateInvoker(strategyList, adminUserApi);
} }
@@ -92,4 +98,42 @@ public class BpmFlowableConfiguration {
return new BpmProcessInstanceEventPublisher(publisher); return new BpmProcessInstanceEventPublisher(publisher);
} }
// =========== 达梦数据库适配(通过 yudao.dameng.enabled=true 开启)==========
/**
* 达梦数据库适配的内部配置类。
* <p>
* 仅当配置 yudao.dameng.enabled=true 时才生效,避免影响 MySQL、PostgreSQL 等其他数据库。
* 适配内容:
* 1. 强制 Flowable 引擎 databaseType 为 oracle复用 Oracle 方言
* 2. 自定义 MyBatis DatabaseIdProvider将 "DM DBMS" 映射为 "oracle"
*/
@Configuration(proxyBeanMethods = false)
@ConditionalOnProperty(prefix = "yudao.dameng", name = "enabled", havingValue = "true")
static class DamengAdaptConfiguration {
/**
* 达梦数据库适配:预设 Flowable 的 databaseType 为 oracle
* initDataSource() 中检测到 databaseType 已有值时会跳过自动探测,
* 从而避免因找不到 "DM DBMS" 映射而报错。
*/
@Bean
public EngineConfigurationConfigurer<SpringProcessEngineConfiguration> damengEngineConfigurationConfigurer() {
return configuration -> configuration.setDatabaseType("oracle");
}
/**
* 自定义 DatabaseIdProvider将达梦数据库驱动返回的 "DM DBMS" 映射为 "oracle"
* 让 MyBatis 层自动复用 Flowable 的 Oracle SQL 映射文件。
*/
@Bean
public DatabaseIdProvider databaseIdProvider() {
DatabaseIdProvider databaseIdProvider = new VendorDatabaseIdProvider();
Properties properties = new Properties();
properties.setProperty("DM DBMS", "oracle");
databaseIdProvider.setProperties(properties);
return databaseIdProvider;
}
}
} }

View File

@@ -0,0 +1,605 @@
package liquibase.database.core;
import liquibase.CatalogAndSchema;
import liquibase.Scope;
import liquibase.database.AbstractJdbcDatabase;
import liquibase.database.DatabaseConnection;
import liquibase.database.OfflineConnection;
import liquibase.database.jvm.JdbcConnection;
import liquibase.exception.DatabaseException;
import liquibase.exception.UnexpectedLiquibaseException;
import liquibase.exception.ValidationErrors;
import liquibase.executor.ExecutorService;
import liquibase.statement.DatabaseFunction;
import liquibase.statement.SequenceCurrentValueFunction;
import liquibase.statement.SequenceNextValueFunction;
import liquibase.statement.core.RawCallStatement;
import liquibase.statement.core.RawSqlStatement;
import liquibase.structure.DatabaseObject;
import liquibase.structure.core.Catalog;
import liquibase.structure.core.Index;
import liquibase.structure.core.PrimaryKey;
import liquibase.structure.core.Schema;
import liquibase.util.JdbcUtils;
import liquibase.util.StringUtil;
import java.lang.reflect.Method;
import java.sql.Connection;
import java.sql.ResultSet;
import java.sql.Statement;
import java.util.*;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
/**
* 达梦数据库DM DBMS的 Liquibase 适配实现。
* <p>
* 参考 {@link OracleDatabase} 实现,达梦 DM8 的 SQL 语法与 Oracle 高度兼容。
* 主要修改点:
* <ul>
* <li>PRODUCT_NAME 常量设为 "DM DBMS"</li>
* <li>默认端口设为 5236</li>
* <li>shortName 设为 "dm"</li>
* <li>默认驱动设为 dm.jdbc.driver.DmDriver</li>
* <li>getDatabaseMinorVersion() 增加异常保护,防止部分 DM8 驱动返回非数字版本号</li>
* </ul>
*/
public class DmDatabase extends AbstractJdbcDatabase {
private static final String PRODUCT_NAME = "DM DBMS";
@Override
protected String getDefaultDatabaseProductName() {
return PRODUCT_NAME;
}
/**
* 判断当前连接是否为达梦数据库
*/
@Override
public boolean isCorrectDatabaseImplementation(DatabaseConnection conn) throws DatabaseException {
return PRODUCT_NAME.equalsIgnoreCase(conn.getDatabaseProductName());
}
/**
* 根据 JDBC URL 返回达梦数据库的默认驱动类名
*/
@Override
public String getDefaultDriver(String url) {
if (url.startsWith("jdbc:dm")) {
return "dm.jdbc.driver.DmDriver";
}
return null;
}
/**
* 返回数据库类型的短名称 "dm",用于 Liquibase 的 dbms 条件判断
*/
@Override
public String getShortName() {
return "dm";
}
@Override
public Integer getDefaultPort() {
return 5236;
}
/**
* 达梦数据库支持延迟约束
*/
@Override
public boolean supportsInitiallyDeferrableColumns() {
return true;
}
@Override
public boolean supportsTablespaces() {
return true;
}
@Override
public int getPriority() {
return PRIORITY_DEFAULT;
}
private static final Pattern PROXY_USER = Pattern.compile(".*(?:thin|oci)\\:(.+)/@.*");
protected final int SHORT_IDENTIFIERS_LENGTH = 30;
protected final int LONG_IDENTIFIERS_LEGNTH = 128;
public static final int ORACLE_12C_MAJOR_VERSION = 12;
private Set<String> reservedWords = new HashSet<>();
private Set<String> userDefinedTypes;
private Map<String, String> savedSessionNlsSettings;
private Boolean canAccessDbaRecycleBin;
private Integer databaseMajorVersion;
private Integer databaseMinorVersion;
/**
* 达梦数据库默认构造函数,设置日期函数和序列语法(兼容 Oracle 语法)
*/
public DmDatabase() {
super.unquotedObjectsAreUppercased = true;
//noinspection HardCodedStringLiteral
super.setCurrentDateTimeFunction("SYSTIMESTAMP");
// 设置兼容 Oracle 的日期函数
//noinspection HardCodedStringLiteral
dateFunctions.add(new DatabaseFunction("SYSDATE"));
//noinspection HardCodedStringLiteral
dateFunctions.add(new DatabaseFunction("SYSTIMESTAMP"));
//noinspection HardCodedStringLiteral
dateFunctions.add(new DatabaseFunction("CURRENT_TIMESTAMP"));
//noinspection HardCodedStringLiteral
super.sequenceNextValueFunction = "%s.nextval";
//noinspection HardCodedStringLiteral
super.sequenceCurrentValueFunction = "%s.currval";
}
private void tryProxySession(final String url, final Connection con) {
Matcher m = PROXY_USER.matcher(url);
if (m.matches()) {
Properties props = new Properties();
props.put("PROXY_USER_NAME", m.group(1));
try {
Method method = con.getClass().getMethod("openProxySession", int.class, Properties.class);
method.setAccessible(true);
method.invoke(con, 1, props);
} catch (Exception e) {
Scope.getCurrentScope().getLog(getClass()).info("Could not open proxy session on DmDatabase: " + e.getCause().getMessage());
}
}
}
@Override
public int getDatabaseMajorVersion() throws DatabaseException {
if (databaseMajorVersion == null) {
return super.getDatabaseMajorVersion();
} else {
return databaseMajorVersion;
}
}
/**
* 获取数据库次版本号。
* <p>
* 注意:部分 DM8 驱动版本返回的微版本号可能包含非数字字符(如 "8.1.2.xxx"
* 导致 {@link NumberFormatException}。此处增加异常保护,返回默认值 0。
*/
@Override
public int getDatabaseMinorVersion() throws DatabaseException {
if (databaseMinorVersion == null) {
try {
return super.getDatabaseMinorVersion();
} catch (Exception e) {
// 达梦驱动某些版本返回的版本号可能导致解析异常,返回默认值 0
Scope.getCurrentScope().getLog(getClass()).info(
"无法解析达梦数据库次版本号,使用默认值 0", e);
return 0;
}
} else {
return databaseMinorVersion;
}
}
@Override
public String getJdbcCatalogName(CatalogAndSchema schema) {
return null;
}
@Override
public String getJdbcSchemaName(CatalogAndSchema schema) {
return correctObjectName((schema.getCatalogName() == null) ? schema.getSchemaName() : schema.getCatalogName(), Schema.class);
}
@Override
protected String getAutoIncrementClause(final String generationType, final Boolean defaultOnNull) {
if (StringUtil.isEmpty(generationType)) {
return super.getAutoIncrementClause();
}
String autoIncrementClause = "GENERATED %s AS IDENTITY"; // %s -- [ ALWAYS | BY DEFAULT [ ON NULL ] ]
String generationStrategy = generationType;
if (Boolean.TRUE.equals(defaultOnNull) && generationType.toUpperCase().equals("BY DEFAULT")) {
generationStrategy += " ON NULL";
}
return String.format(autoIncrementClause, generationStrategy);
}
@Override
public String generatePrimaryKeyName(String tableName) {
if (tableName.length() > 27) {
//noinspection HardCodedStringLiteral
return "PK_" + tableName.toUpperCase(Locale.US).substring(0, 27);
} else {
//noinspection HardCodedStringLiteral
return "PK_" + tableName.toUpperCase(Locale.US);
}
}
@Override
public boolean isReservedWord(String objectName) {
return reservedWords.contains(objectName.toUpperCase());
}
@Override
public boolean supportsSequences() {
return true;
}
/**
* 达梦数据库在 Liquibase 层面不使用 schema 概念(与 Oracle 行为一致)
*/
@Override
public boolean supportsSchemas() {
return false;
}
@Override
protected String getConnectionCatalogName() throws DatabaseException {
if (getConnection() instanceof OfflineConnection) {
return getConnection().getCatalog();
}
try {
//noinspection HardCodedStringLiteral
return Scope.getCurrentScope().getSingleton(ExecutorService.class).getExecutor("jdbc", this).queryForObject(new RawCallStatement("select sys_context( 'userenv', 'current_schema' ) from dual"), String.class);
} catch (Exception e) {
//noinspection HardCodedStringLiteral
Scope.getCurrentScope().getLog(getClass()).info("Error getting default schema", e);
}
return null;
}
@Override
public String getDefaultCatalogName() {//NOPMD
return (super.getDefaultCatalogName() == null) ? null : super.getDefaultCatalogName().toUpperCase(Locale.US);
}
/**
* <p>Returns an Oracle date literal with the same value as a string formatted using ISO 8601.</p>
*
* <p>Convert an ISO8601 date string to one of the following results:
* to_date('1995-05-23', 'YYYY-MM-DD')
* to_date('1995-05-23 09:23:59', 'YYYY-MM-DD HH24:MI:SS')</p>
* <p>
* Implementation restriction:<br>
* Currently, only the following subsets of ISO8601 are supported:<br>
* <ul>
* <li>YYYY-MM-DD</li>
* <li>YYYY-MM-DDThh:mm:ss</li>
* </ul>
*/
@Override
public String getDateLiteral(String isoDate) {
String normalLiteral = super.getDateLiteral(isoDate);
if (isDateOnly(isoDate)) {
return "TO_DATE(" + normalLiteral + ", 'YYYY-MM-DD')";
} else if (isTimeOnly(isoDate)) {
return "TO_DATE(" + normalLiteral + ", 'HH24:MI:SS')";
} else if (isTimestamp(isoDate)) {
return "TO_TIMESTAMP(" + normalLiteral + ", 'YYYY-MM-DD HH24:MI:SS.FF')";
} else if (isDateTime(isoDate)) {
int seppos = normalLiteral.lastIndexOf('.');
if (seppos != -1) {
normalLiteral = normalLiteral.substring(0, seppos) + "'";
}
return "TO_DATE(" + normalLiteral + ", 'YYYY-MM-DD HH24:MI:SS')";
}
return "UNSUPPORTED:" + isoDate;
}
@Override
public boolean isSystemObject(DatabaseObject example) {
if (example == null) {
return false;
}
if (this.isLiquibaseObject(example)) {
return false;
}
if (example instanceof Schema) {
//noinspection HardCodedStringLiteral,HardCodedStringLiteral,HardCodedStringLiteral,HardCodedStringLiteral
if ("SYSTEM".equals(example.getName()) || "SYS".equals(example.getName()) || "CTXSYS".equals(example.getName()) || "XDB".equals(example.getName())) {
return true;
}
//noinspection HardCodedStringLiteral,HardCodedStringLiteral,HardCodedStringLiteral,HardCodedStringLiteral
if ("SYSTEM".equals(example.getSchema().getCatalogName()) || "SYS".equals(example.getSchema().getCatalogName()) || "CTXSYS".equals(example.getSchema().getCatalogName()) || "XDB".equals(example.getSchema().getCatalogName())) {
return true;
}
} else if (isSystemObject(example.getSchema())) {
return true;
}
if (example instanceof Catalog) {
//noinspection HardCodedStringLiteral,HardCodedStringLiteral,HardCodedStringLiteral,HardCodedStringLiteral
if (("SYSTEM".equals(example.getName()) || "SYS".equals(example.getName()) || "CTXSYS".equals(example.getName()) || "XDB".equals(example.getName()))) {
return true;
}
} else if (example.getName() != null) {
//noinspection HardCodedStringLiteral
if (example.getName().startsWith("BIN$")) { //oracle deleted table
boolean filteredInOriginalQuery = this.canAccessDbaRecycleBin();
if (!filteredInOriginalQuery) {
filteredInOriginalQuery = StringUtil.trimToEmpty(example.getSchema().getName()).equalsIgnoreCase(this.getConnection().getConnectionUserName());
}
if (filteredInOriginalQuery) {
return !((example instanceof PrimaryKey) || (example instanceof Index) || (example instanceof
liquibase.statement.UniqueConstraint));
} else {
return true;
}
} else //noinspection HardCodedStringLiteral
if (example.getName().startsWith("AQ$")) { //oracle AQ tables
return true;
} else //noinspection HardCodedStringLiteral
if (example.getName().startsWith("DR$")) { //oracle index tables
return true;
} else //noinspection HardCodedStringLiteral
if (example.getName().startsWith("SYS_IOT_OVER")) { //oracle system table
return true;
} else //noinspection HardCodedStringLiteral,HardCodedStringLiteral
if ((example.getName().startsWith("MDRT_") || example.getName().startsWith("MDRS_")) && example.getName().endsWith("$")) {
// CORE-1768 - Oracle creates these for spatial indices and will remove them when the index is removed.
return true;
} else //noinspection HardCodedStringLiteral
if (example.getName().startsWith("MLOG$_")) { //Created by materliaized view logs for every table that is part of a materialized view. Not available for DDL operations.
return true;
} else //noinspection HardCodedStringLiteral
if (example.getName().startsWith("RUPD$_")) { //Created by materialized view log tables using primary keys. Not available for DDL operations.
return true;
} else //noinspection HardCodedStringLiteral
if (example.getName().startsWith("WM$_")) { //Workspace Manager backup tables.
return true;
} else //noinspection HardCodedStringLiteral
if ("CREATE$JAVA$LOB$TABLE".equals(example.getName())) { //This table contains the name of the Java object, the date it was loaded, and has a BLOB column to store the Java object.
return true;
} else //noinspection HardCodedStringLiteral
if ("JAVA$CLASS$MD5$TABLE".equals(example.getName())) { //This is a hash table that tracks the loading of Java objects into a schema.
return true;
} else //noinspection HardCodedStringLiteral
if (example.getName().startsWith("ISEQ$$_")) { //System-generated sequence
return true;
} else //noinspection HardCodedStringLiteral
if (example.getName().startsWith("USLOG$")) { //for update materialized view
return true;
} else if (example.getName().startsWith("SYS_FBA")) { //for Flashback tables
return true;
}
}
return super.isSystemObject(example);
}
@Override
public boolean supportsAutoIncrement() {
// Oracle supports Identity beginning with version 12c
boolean isAutoIncrementSupported = false;
try {
if (getDatabaseMajorVersion() >= 12) {
isAutoIncrementSupported = true;
}
// Returning true will generate create table command with 'IDENTITY' clause, example:
// CREATE TABLE AutoIncTest (IDPrimaryKey NUMBER(19) GENERATED BY DEFAULT AS IDENTITY NOT NULL, TypeID NUMBER(3) NOT NULL, Description NVARCHAR2(50), CONSTRAINT PK_AutoIncTest PRIMARY KEY (IDPrimaryKey));
// While returning false will continue to generate create table command without 'IDENTITY' clause, example:
// CREATE TABLE AutoIncTest (IDPrimaryKey NUMBER(19) NOT NULL, TypeID NUMBER(3) NOT NULL, Description NVARCHAR2(50), CONSTRAINT PK_AutoIncTest PRIMARY KEY (IDPrimaryKey));
} catch (DatabaseException ex) {
isAutoIncrementSupported = false;
}
return isAutoIncrementSupported;
}
// public Set<UniqueConstraint> findUniqueConstraints(String schema) throws DatabaseException {
// Set<UniqueConstraint> returnSet = new HashSet<UniqueConstraint>();
//
// List<Map> maps = new Executor(this).queryForList(new RawSqlStatement("SELECT UC.CONSTRAINT_NAME, UCC.TABLE_NAME, UCC.COLUMN_NAME FROM USER_CONSTRAINTS UC, USER_CONS_COLUMNS UCC WHERE UC.CONSTRAINT_NAME=UCC.CONSTRAINT_NAME AND CONSTRAINT_TYPE='U' ORDER BY UC.CONSTRAINT_NAME"));
//
// UniqueConstraint constraint = null;
// for (Map map : maps) {
// if (constraint == null || !constraint.getName().equals(constraint.getName())) {
// returnSet.add(constraint);
// Table table = new Table((String) map.get("TABLE_NAME"));
// constraint = new UniqueConstraint(map.get("CONSTRAINT_NAME").toString(), table);
// }
// }
// if (constraint != null) {
// returnSet.add(constraint);
// }
//
// return returnSet;
// }
@Override
public boolean supportsRestrictForeignKeys() {
return false;
}
@Override
public int getDataTypeMaxParameters(String dataTypeName) {
//noinspection HardCodedStringLiteral
if ("BINARY_FLOAT".equals(dataTypeName.toUpperCase())) {
return 0;
}
//noinspection HardCodedStringLiteral
if ("BINARY_DOUBLE".equals(dataTypeName.toUpperCase())) {
return 0;
}
return super.getDataTypeMaxParameters(dataTypeName);
}
public String getSystemTableWhereClause(String tableNameColumn) {
List<String> clauses = new ArrayList<String>(Arrays.asList("BIN$",
"AQ$",
"DR$",
"SYS_IOT_OVER",
"MLOG$_",
"RUPD$_",
"WM$_",
"ISEQ$$_",
"USLOG$",
"SYS_FBA"));
for (int i = 0; i < clauses.size(); i++) {
clauses.set(i, tableNameColumn + " NOT LIKE '" + clauses.get(i) + "%'");
}
return "(" + StringUtil.join(clauses, " AND ") + ")";
}
@Override
public boolean jdbcCallsCatalogsSchemas() {
return true;
}
public Set<String> getUserDefinedTypes() {
if (userDefinedTypes == null) {
userDefinedTypes = new HashSet<>();
if ((getConnection() != null) && !(getConnection() instanceof OfflineConnection)) {
try {
try {
//noinspection HardCodedStringLiteral
userDefinedTypes.addAll(Scope.getCurrentScope().getSingleton(ExecutorService.class).getExecutor("jdbc", this).queryForList(new RawSqlStatement("SELECT DISTINCT TYPE_NAME FROM ALL_TYPES"), String.class));
} catch (DatabaseException e) { //fall back to USER_TYPES if the user cannot see ALL_TYPES
//noinspection HardCodedStringLiteral
userDefinedTypes.addAll(Scope.getCurrentScope().getSingleton(ExecutorService.class).getExecutor("jdbc", this).queryForList(new RawSqlStatement("SELECT TYPE_NAME FROM USER_TYPES"), String.class));
}
} catch (DatabaseException e) {
//ignore error
}
}
}
return userDefinedTypes;
}
@Override
public String generateDatabaseFunctionValue(DatabaseFunction databaseFunction) {
//noinspection HardCodedStringLiteral
if ((databaseFunction != null) && "current_timestamp".equalsIgnoreCase(databaseFunction.toString())) {
return databaseFunction.toString();
}
if ((databaseFunction instanceof SequenceNextValueFunction) || (databaseFunction instanceof
SequenceCurrentValueFunction)) {
String quotedSeq = super.generateDatabaseFunctionValue(databaseFunction);
// replace "myschema.my_seq".nextval with "myschema"."my_seq".nextval
return quotedSeq.replaceFirst("\"([^\\.\"]+)\\.([^\\.\"]+)\"", "\"$1\".\"$2\"");
}
return super.generateDatabaseFunctionValue(databaseFunction);
}
@Override
public ValidationErrors validate() {
ValidationErrors errors = super.validate();
DatabaseConnection connection = getConnection();
if ((connection == null) || (connection instanceof OfflineConnection)) {
//noinspection HardCodedStringLiteral
Scope.getCurrentScope().getLog(getClass()).info("Cannot validate offline database");
return errors;
}
if (!canAccessDbaRecycleBin()) {
errors.addWarning(getDbaRecycleBinWarning());
}
return errors;
}
public String getDbaRecycleBinWarning() {
//noinspection HardCodedStringLiteral,HardCodedStringLiteral,HardCodedStringLiteral,HardCodedStringLiteral,
// HardCodedStringLiteral
//noinspection HardCodedStringLiteral,HardCodedStringLiteral,HardCodedStringLiteral
return "Liquibase needs to access the DBA_RECYCLEBIN table so we can automatically handle the case where " +
"constraints are deleted and restored. Since Oracle doesn't properly restore the original table names " +
"referenced in the constraint, we use the information from the DBA_RECYCLEBIN to automatically correct this" +
" issue.\n" +
"\n" +
"The user you used to connect to the database (" + getConnection().getConnectionUserName() +
") needs to have \"SELECT ON SYS.DBA_RECYCLEBIN\" permissions set before we can perform this operation. " +
"Please run the following SQL to set the appropriate permissions, and try running the command again.\n" +
"\n" +
" GRANT SELECT ON SYS.DBA_RECYCLEBIN TO " + getConnection().getConnectionUserName() + ";";
}
public boolean canAccessDbaRecycleBin() {
if (canAccessDbaRecycleBin == null) {
DatabaseConnection connection = getConnection();
if ((connection == null) || (connection instanceof OfflineConnection)) {
return false;
}
Statement statement = null;
try {
statement = ((JdbcConnection) connection).createStatement();
@SuppressWarnings("HardCodedStringLiteral") ResultSet resultSet = statement.executeQuery("select 1 from dba_recyclebin where 0=1");
resultSet.close(); //don't need to do anything with the result set, just make sure statement ran.
this.canAccessDbaRecycleBin = true;
} catch (Exception e) {
// 达梦错误码与 Oracle 不同,统一处理为无法访问 dba_recyclebin
Scope.getCurrentScope().getLog(getClass()).warning("无法检查 dba_recyclebin 访问权限", e);
this.canAccessDbaRecycleBin = false;
} finally {
JdbcUtils.close(null, statement);
}
}
return canAccessDbaRecycleBin;
}
@Override
public boolean supportsNotNullConstraintNames() {
return true;
}
/**
* Tests if the given String would be a valid identifier in Oracle DBMS. In Oracle, a valid identifier has
* the following form (case-insensitive comparison):
* 1st character: A-Z
* 2..n characters: A-Z0-9$_#
* The maximum length of an identifier differs by Oracle version and object type.
*/
public boolean isValidOracleIdentifier(String identifier, Class<? extends DatabaseObject> type) {
if ((identifier == null) || (identifier.length() < 1))
return false;
if (!identifier.matches("^(i?)[A-Z][A-Z0-9\\$\\_\\#]*$"))
return false;
/*
* @todo It seems we currently do not have a class for tablespace identifiers, and all other classes
* we do know seem to be supported as 12cR2 long identifiers, so:
*/
return (identifier.length() <= LONG_IDENTIFIERS_LEGNTH);
}
/**
* Returns the maximum number of bytes (NOT: characters) for an identifier. For Oracle <=12c Release 20, this
* is 30 bytes, and starting from 12cR2, up to 128 (except for tablespaces, PDB names and some other rather rare
* object types).
*
* @return the maximum length of an object identifier, in bytes
*/
public int getIdentifierMaximumLength() {
try {
if (getDatabaseMajorVersion() < ORACLE_12C_MAJOR_VERSION) {
return SHORT_IDENTIFIERS_LENGTH;
} else if ((getDatabaseMajorVersion() == ORACLE_12C_MAJOR_VERSION) && (getDatabaseMinorVersion() <= 1)) {
return SHORT_IDENTIFIERS_LENGTH;
} else {
return LONG_IDENTIFIERS_LEGNTH;
}
} catch (DatabaseException ex) {
throw new UnexpectedLiquibaseException("Cannot determine the Oracle database version number", ex);
}
}
}

View File

@@ -0,0 +1,158 @@
package liquibase.datatype.core;
import liquibase.change.core.LoadDataChange;
import liquibase.database.Database;
import liquibase.database.core.*;
import liquibase.datatype.DataTypeInfo;
import liquibase.datatype.DatabaseDataType;
import liquibase.datatype.LiquibaseDataType;
import liquibase.exception.UnexpectedLiquibaseException;
import liquibase.statement.DatabaseFunction;
import liquibase.util.StringUtil;
import java.util.Locale;
import java.util.regex.Pattern;
@DataTypeInfo(name = "boolean", aliases = {"java.sql.Types.BOOLEAN", "java.lang.Boolean", "bit", "bool"}, minParameters = 0, maxParameters = 0, priority = LiquibaseDataType.PRIORITY_DEFAULT)
public class BooleanType extends LiquibaseDataType {
@Override
public DatabaseDataType toDatabaseDataType(Database database) {
String originalDefinition = StringUtil.trimToEmpty(getRawDefinition());
if ((database instanceof Db2zDatabase) || (database instanceof FirebirdDatabase)) {
return new DatabaseDataType("SMALLINT");
} else if (database instanceof MSSQLDatabase) {
return new DatabaseDataType(database.escapeDataTypeName("bit"));
} else if (database instanceof MySQLDatabase) {
if (originalDefinition.toLowerCase(Locale.US).startsWith("bit")) {
return new DatabaseDataType("BIT", getParameters());
}
return new DatabaseDataType("BIT", 1);
} else if (database instanceof OracleDatabase) {
return new DatabaseDataType("NUMBER", 1);
} else if ((database instanceof SybaseASADatabase) || (database instanceof SybaseDatabase)) {
return new DatabaseDataType("BIT");
} else if (database instanceof DerbyDatabase) {
if (((DerbyDatabase) database).supportsBooleanDataType()) {
return new DatabaseDataType("BOOLEAN");
} else {
return new DatabaseDataType("SMALLINT");
}
} else if (database instanceof DB2Database) {
if (((DB2Database) database).supportsBooleanDataType())
return new DatabaseDataType("BOOLEAN");
else
return new DatabaseDataType("SMALLINT");
} else if (database instanceof HsqlDatabase) {
return new DatabaseDataType("BOOLEAN");
} else if (database instanceof PostgresDatabase) {
if (originalDefinition.toLowerCase(Locale.US).startsWith("bit")) {
return new DatabaseDataType("BIT", getParameters());
}
} else if (database instanceof DmDatabase) { // 达梦数据库:布尔类型映射为 bit
return new DatabaseDataType("bit");
}
return super.toDatabaseDataType(database);
}
@Override
public String objectToSql(Object value, Database database) {
if ((value == null) || "null".equals(value.toString().toLowerCase(Locale.US))) {
return null;
}
String returnValue;
if (value instanceof String) {
value = ((String) value).replaceAll("'", "");
if ("true".equals(((String) value).toLowerCase(Locale.US)) || "1".equals(value) || "b'1'".equals(((String) value).toLowerCase(Locale.US)) || "t".equals(((String) value).toLowerCase(Locale.US)) || ((String) value).toLowerCase(Locale.US).equals(this.getTrueBooleanValue(database).toLowerCase(Locale.US))) {
returnValue = this.getTrueBooleanValue(database);
} else if ("false".equals(((String) value).toLowerCase(Locale.US)) || "0".equals(value) || "b'0'".equals(
((String) value).toLowerCase(Locale.US)) || "f".equals(((String) value).toLowerCase(Locale.US)) || ((String) value).toLowerCase(Locale.US).equals(this.getFalseBooleanValue(database).toLowerCase(Locale.US))) {
returnValue = this.getFalseBooleanValue(database);
} else if (database instanceof PostgresDatabase && Pattern.matches("b?([01])\\1*(::bit|::\"bit\")?", (String) value)) {
returnValue = "b'"
+ value.toString()
.replace("b", "")
.replace("\"", "")
.replace("::it", "")
+ "'::\"bit\"";
} else {
throw new UnexpectedLiquibaseException("Unknown boolean value: " + value);
}
} else if (value instanceof Long) {
if (Long.valueOf(1).equals(value)) {
returnValue = this.getTrueBooleanValue(database);
} else {
returnValue = this.getFalseBooleanValue(database);
}
} else if (value instanceof Number) {
if (value.equals(1) || "1".equals(value.toString()) || "1.0".equals(value.toString())) {
returnValue = this.getTrueBooleanValue(database);
} else {
returnValue = this.getFalseBooleanValue(database);
}
} else if (value instanceof DatabaseFunction) {
return value.toString();
} else if (value instanceof Boolean) {
if (((Boolean) value)) {
returnValue = this.getTrueBooleanValue(database);
} else {
returnValue = this.getFalseBooleanValue(database);
}
} else {
throw new UnexpectedLiquibaseException("Cannot convert type " + value.getClass() + " to a boolean value");
}
return returnValue;
}
protected boolean isNumericBoolean(Database database) {
if (database instanceof DerbyDatabase) {
return !((DerbyDatabase) database).supportsBooleanDataType();
} else if (database instanceof DB2Database) {
return !((DB2Database) database).supportsBooleanDataType();
}
return (database instanceof Db2zDatabase)
|| (database instanceof FirebirdDatabase)
|| (database instanceof MSSQLDatabase)
|| (database instanceof MySQLDatabase)
|| (database instanceof OracleDatabase)
|| (database instanceof SQLiteDatabase)
|| (database instanceof SybaseASADatabase)
|| (database instanceof SybaseDatabase)
|| (database instanceof DmDatabase); // 达梦数据库使用数字型布尔值0/1
}
/**
* The database-specific value to use for "false" "boolean" columns.
*/
public String getFalseBooleanValue(Database database) {
if (isNumericBoolean(database)) {
return "0";
}
if (database instanceof InformixDatabase) {
return "'f'";
}
return "FALSE";
}
/**
* The database-specific value to use for "true" "boolean" columns.
*/
public String getTrueBooleanValue(Database database) {
if (isNumericBoolean(database)) {
return "1";
}
if (database instanceof InformixDatabase) {
return "'t'";
}
return "TRUE";
}
@Override
public LoadDataChange.LOAD_DATA_TYPE getLoadTypeName() {
return LoadDataChange.LOAD_DATA_TYPE.BOOLEAN;
}
}

View File

@@ -0,0 +1,20 @@
liquibase.database.core.CockroachDatabase
liquibase.database.core.DB2Database
liquibase.database.core.Db2zDatabase
liquibase.database.core.DerbyDatabase
liquibase.database.core.FirebirdDatabase
liquibase.database.core.H2Database
liquibase.database.core.HsqlDatabase
liquibase.database.core.InformixDatabase
liquibase.database.core.Ingres9Database
liquibase.database.core.MSSQLDatabase
liquibase.database.core.MariaDBDatabase
liquibase.database.core.MockDatabase
liquibase.database.core.MySQLDatabase
liquibase.database.core.OracleDatabase
liquibase.database.core.PostgresDatabase
liquibase.database.core.SQLiteDatabase
liquibase.database.core.SybaseASADatabase
liquibase.database.core.SybaseDatabase
liquibase.database.core.DmDatabase
liquibase.database.core.UnsupportedDatabase

View File

@@ -46,11 +46,11 @@
<!-- <version>${revision}</version>--> <!-- <version>${revision}</version>-->
<!-- </dependency>--> <!-- </dependency>-->
<!-- 工作流。默认注释,保证编译速度 --> <!-- 工作流。默认注释,保证编译速度 -->
<!-- <dependency>--> <dependency>
<!-- <groupId>cn.iocoder.boot</groupId>--> <groupId>cn.iocoder.boot</groupId>
<!-- <artifactId>yudao-module-bpm</artifactId>--> <artifactId>yudao-module-bpm</artifactId>
<!-- <version>${revision}</version>--> <version>${revision}</version>
<!-- </dependency>--> </dependency>
<!-- 支付服务。默认注释,保证编译速度 --> <!-- 支付服务。默认注释,保证编译速度 -->
<!-- <dependency>--> <!-- <dependency>-->
<!-- <groupId>cn.iocoder.boot</groupId>--> <!-- <groupId>cn.iocoder.boot</groupId>-->

View File

@@ -47,27 +47,27 @@ spring:
primary: master primary: master
datasource: datasource:
master: master:
url: jdbc:mysql://127.0.0.1:3306/ruoyi-vue-pro?useSSL=false&serverTimezone=Asia/Shanghai&allowPublicKeyRetrieval=true&nullCatalogMeansCurrent=true&rewriteBatchedStatements=true # MySQL Connector/J 8.X 连接的示例 # url: jdbc:mysql://127.0.0.1:3306/ruoyi-vue-pro?useSSL=false&serverTimezone=Asia/Shanghai&allowPublicKeyRetrieval=true&nullCatalogMeansCurrent=true&rewriteBatchedStatements=true # MySQL Connector/J 8.X 连接的示例
# url: jdbc:mysql://127.0.0.1:3306/ruoyi-vue-pro?useSSL=true&allowPublicKeyRetrieval=true&useUnicode=true&characterEncoding=UTF-8&serverTimezone=Asia/Shanghai&rewriteBatchedStatements=true # MySQL Connector/J 5.X 连接的示例 # url: jdbc:mysql://127.0.0.1:3306/ruoyi-vue-pro?useSSL=true&allowPublicKeyRetrieval=true&useUnicode=true&characterEncoding=UTF-8&serverTimezone=Asia/Shanghai&rewriteBatchedStatements=true # MySQL Connector/J 5.X 连接的示例
# url: jdbc:postgresql://127.0.0.1:5432/ruoyi-vue-pro # PostgreSQL 连接的示例 # url: jdbc:postgresql://127.0.0.1:5432/ruoyi-vue-pro # PostgreSQL 连接的示例
# url: jdbc:oracle:thin:@127.0.0.1:1521:xe # Oracle 连接的示例 # url: jdbc:oracle:thin:@127.0.0.1:1521:xe # Oracle 连接的示例
# url: jdbc:sqlserver://127.0.0.1:1433;DatabaseName=ruoyi-vue-pro;SelectMethod=cursor;encrypt=false;rewriteBatchedStatements=true;useUnicode=true;characterEncoding=utf-8 # SQLServer 连接的示例 # url: jdbc:sqlserver://127.0.0.1:1433;DatabaseName=ruoyi-vue-pro;SelectMethod=cursor;encrypt=false;rewriteBatchedStatements=true;useUnicode=true;characterEncoding=utf-8 # SQLServer 连接的示例
# url: jdbc:dm://127.0.0.1:5236?schema=RUOYI_VUE_PRO # DM 连接的示例 url: jdbc:dm://127.0.0.1:5236?schema=YUDAO_FLOWABLE&compatibleMode=oracle&loginEncrypt=false&useUnicode=true&characterEncoding=UTF-8&keyword_case=upper&ignoreCase=true # DM 连接的示例compatibleMode=oracle 提高兼容性)
# url: jdbc:kingbase8://127.0.0.1:54321/test # 人大金仓 KingbaseES 连接的示例 # url: jdbc:kingbase8://127.0.0.1:54321/test # 人大金仓 KingbaseES 连接的示例
# url: jdbc:postgresql://127.0.0.1:5432/postgres # OpenGauss 连接的示例 # url: jdbc:postgresql://127.0.0.1:5432/postgres # OpenGauss 连接的示例
username: root # username: root # MySQL 连接的示例
password: 123456 # password: 123456 # MySQL 连接的示例
# username: sa # SQL Server 连接的示例 # username: sa # SQL Server 连接的示例
# password: Yudao@2024 # SQL Server 连接的示例 # password: Yudao@2024 # SQL Server 连接的示例
# username: SYSDBA # DM 连接的示例 username: YUDAO_FLOWABLE # DM 达梦数据库
# password: SYSDBA001 # DM 连接的示例 password: Yudao@2024 # DM 达梦数据库
# username: root # OpenGauss 连接的示例 # username: root # OpenGauss 连接的示例
# password: Yudao@2024 # OpenGauss 连接的示例 # password: Yudao@2024 # OpenGauss 连接的示例
slave: # 模拟从库,可根据自己需要修改 # slave: # 模拟从库,可根据自己需要修改
lazy: true # 开启懒加载,保证启动速度 # lazy: true # 开启懒加载,保证启动速度
url: jdbc:mysql://127.0.0.1:3306/ruoyi-vue-pro?useSSL=false&serverTimezone=Asia/Shanghai&allowPublicKeyRetrieval=true&rewriteBatchedStatements=true&nullCatalogMeansCurrent=true # url: jdbc:mysql://127.0.0.1:3306/ruoyi-vue-pro?useSSL=false&serverTimezone=Asia/Shanghai&allowPublicKeyRetrieval=true&rewriteBatchedStatements=true&nullCatalogMeansCurrent=true
username: root # username: root
password: 123456 # password: 123456
# tdengine: # IoT 数据库(需要 IoT 物联网再开启噢!) # tdengine: # IoT 数据库(需要 IoT 物联网再开启噢!)
# lazy: true # 开启懒加载,保证启动速度 # lazy: true # 开启懒加载,保证启动速度
# url: jdbc:TAOS-WS://127.0.0.1:6041/ruoyi_vue_pro?varcharAsString=true # url: jdbc:TAOS-WS://127.0.0.1:6041/ruoyi_vue_pro?varcharAsString=true
@@ -242,7 +242,9 @@ yudao:
wxa-subscribe-message: wxa-subscribe-message:
miniprogram-state: developer # 跳转小程序类型:开发版为 “developer”体验版为 “trial”为正式版为 “formal” miniprogram-state: developer # 跳转小程序类型:开发版为 “developer”体验版为 “trial”为正式版为 “formal”
tencent-lbs-key: TVDBZ-TDILD-4ON4B-PFDZA-RNLKH-VVF6E # QQ 地图的密钥 https://lbs.qq.com/service/staticV2/staticGuide/staticDoc tencent-lbs-key: TVDBZ-TDILD-4ON4B-PFDZA-RNLKH-VVF6E # QQ 地图的密钥 https://lbs.qq.com/service/staticV2/staticGuide/staticDoc
dameng:
enabled: true # 达梦数据库适配开关(本地环境使用 DM需开启
justauth: justauth:
enabled: true enabled: true
type: type:

View File

@@ -349,5 +349,7 @@ yudao:
iot: iot:
message-bus: message-bus:
type: redis # 消息总线的类型 type: redis # 消息总线的类型
dameng:
enabled: false # 达梦数据库适配开关。使用达梦数据库时设为 true会强制 Flowable 引擎使用 Oracle 方言
debug: false debug: false