diff --git a/modules/server/pom.xml b/modules/server/pom.xml
index 42ba07f02..a18ffb9f7 100644
--- a/modules/server/pom.xml
+++ b/modules/server/pom.xml
@@ -122,6 +122,12 @@
${project.version}
+
+ org.dromara.jpom.storage-module
+ storage-module-postgresql
+ ${project.version}
+
+
me.zhyd.oauth
JustAuth
diff --git a/modules/server/src/main/java/org/dromara/jpom/system/db/InitDb.java b/modules/server/src/main/java/org/dromara/jpom/system/db/InitDb.java
index 0fa07303a..84dcb0a77 100644
--- a/modules/server/src/main/java/org/dromara/jpom/system/db/InitDb.java
+++ b/modules/server/src/main/java/org/dromara/jpom/system/db/InitDb.java
@@ -26,6 +26,7 @@ import org.dromara.jpom.JpomApplication;
import org.dromara.jpom.common.ILoadEvent;
import org.dromara.jpom.common.JpomApplicationEvent;
import org.dromara.jpom.db.*;
+import org.dromara.jpom.dialect.DialectUtil;
import org.dromara.jpom.model.data.BackupInfoModel;
import org.dromara.jpom.service.dblog.BackupInfoService;
import org.dromara.jpom.system.JpomRuntimeException;
@@ -203,7 +204,7 @@ public class InitDb implements DisposableBean, ILoadEvent {
eachSql.accept(name);
try {
IStorageSqlBuilderService sqlBuilderService = StorageTableFactory.get();
- Db.use(dataSource).tx((CheckedUtil.VoidFunc1Rt) parameter -> {
+ Db.use(dataSource, DialectUtil.getDialectByMode(mode)).tx((CheckedUtil.VoidFunc1Rt) parameter -> {
// 分隔后执行,mysql 不能执行多条 sql 语句
List list = StrUtil.isEmpty(sqlBuilderService.delimiter()) ?
CollUtil.newArrayList(sql) : StrUtil.splitTrim(sql, sqlBuilderService.delimiter());
@@ -279,11 +280,12 @@ public class InitDb implements DisposableBean, ILoadEvent {
// 导入数据
importH2Sql(environment, s);
});
+
// 迁移数据
- Opt.ofNullable(environment.getProperty("h2-migrate-mysql")).ifPresent(s -> {
+ Consumer migrateOpr = targetMode->{
DbExtConfig.Mode mode = dbExtConfig.getMode();
- if (mode != DbExtConfig.Mode.MYSQL) {
- throw new JpomRuntimeException(StrUtil.format("当前模式不正确,不能直接迁移到 {}", mode));
+ if (mode != targetMode) {
+ throw new JpomRuntimeException(StrUtil.format("当前模式不正确,不能直接迁移到 {}", targetMode));
}
// 都提前清理
StorageServiceFactory.clearExecuteSqlLog();
@@ -293,10 +295,16 @@ public class InitDb implements DisposableBean, ILoadEvent {
String pass = environment.getProperty("h2-pass");
this.addCallback("迁移数据", () -> {
//
- StorageServiceFactory.migrateH2ToNow(dbExtConfig, url, user, pass);
+ StorageServiceFactory.migrateH2ToNow(dbExtConfig, url, user, pass,targetMode);
return false;
});
log.info("开始等待数据迁移");
+ };
+ Opt.ofNullable(environment.getProperty("h2-migrate-mysql")).ifPresent(s -> {
+ migrateOpr.accept(DbExtConfig.Mode.MYSQL);
+ });
+ Opt.ofNullable(environment.getProperty("h2-migrate-postgresql")).ifPresent(s -> {
+ migrateOpr.accept(DbExtConfig.Mode.POSTGRESQL);
});
}
diff --git a/modules/server/src/main/resources/application-postgresql.yml b/modules/server/src/main/resources/application-postgresql.yml
new file mode 100644
index 000000000..e7d53da43
--- /dev/null
+++ b/modules/server/src/main/resources/application-postgresql.yml
@@ -0,0 +1,11 @@
+jpom:
+ db:
+ # 数据库默认 支持 :H2、MYSQL
+ mode: POSTGRESQL
+ url: jdbc:postgresql://localhost:5432/jpom
+ # 数据库账号 默认 jpom
+ #user-name: jpom
+ user-name: jpom
+ # 数据库密码 默认 jpom 如果自行配置请保证密码强度
+ # user-pwd: jpom
+ user-pwd: 123456
diff --git a/modules/server/src/main/resources/sql-view/alter.all.v1.0.csv b/modules/server/src/main/resources/sql-view/alter.all.v1.0.csv
index 2fe0c33e7..a11b00097 100644
--- a/modules/server/src/main/resources/sql-view/alter.all.v1.0.csv
+++ b/modules/server/src/main/resources/sql-view/alter.all.v1.0.csv
@@ -2,7 +2,7 @@ alterType,tableName,name,type,len,defaultValue,comment,notNull
DROP,NODE_INFO,cycle
ADD,PROJECT_INFO,triggerToken,String,100,,触发器token
ADD,OUT_GIVING,secondaryDirectory,String,200,,二级目录
-ADD,OUT_GIVING,uploadCloseFirst,TINYINT,,0,是否清空旧包发布
+ADD,OUT_GIVING,uploadCloseFirst,Boolean,,0,是否清空旧包发布
ADD,USEROPERATELOGV1,dataName,String,200,,数据名称
DROP,SSHTERMINALEXECUTELOG,optTime
DROP,USEROPERATELOGV1,optType
diff --git a/modules/server/src/main/resources/sql-view/alter.all.v1.1.csv b/modules/server/src/main/resources/sql-view/alter.all.v1.1.csv
index 7fd2c5d6b..d534e1d12 100644
--- a/modules/server/src/main/resources/sql-view/alter.all.v1.1.csv
+++ b/modules/server/src/main/resources/sql-view/alter.all.v1.1.csv
@@ -6,7 +6,7 @@ ADD,BUILDHISTORYLOG,buildEnvCache,TEXT,,,构建环境变量
ADD,OUT_GIVING,webhook,String,255,,webhook
DROP,NODE_INFO,unLockType
ADD,NODE_INFO,machineId,String,50,,机器id
-ADD,MACHINE_NODE_INFO,templateNode,TINYINT,,,模板节点 ,1 模板节点 0 非模板节点
+ADD,MACHINE_NODE_INFO,templateNode,Boolean,,,模板节点 ,1 模板节点 0 非模板节点
ADD,REPOSITORY,timeout,Integer,,,仓库超时连接
ADD,SSH_INFO,group,String,50,,分组
ADD,SSH_INFO,machineSshId,String,50,,机器sshid
@@ -28,4 +28,4 @@ DROP,DOCKER_SWARM_INFO,status,
DROP,DOCKER_SWARM_INFO,failureMsg,
DROP,DOCKER_SWARM_INFO,dockerId,
DROP,DOCKER_SWARM_INFO,dockerId,
-ADD,MACHINE_DOCKER_INFO,swarmControlAvailable,TINYINT,,,集群管理员
+ADD,MACHINE_DOCKER_INFO,swarmControlAvailable,Boolean,,,集群管理员
diff --git a/modules/server/src/main/resources/sql-view/alter.all.v1.3.csv b/modules/server/src/main/resources/sql-view/alter.all.v1.3.csv
index 98777189d..2175d2de4 100644
--- a/modules/server/src/main/resources/sql-view/alter.all.v1.3.csv
+++ b/modules/server/src/main/resources/sql-view/alter.all.v1.3.csv
@@ -40,10 +40,10 @@ ADD,BUILD_INFO,aliasCode,String,50,,别名码,false
ADD,FILE_STORAGE,aliasCode,String,50,,别名码,false
ADD,PROJECT_INFO,dslContent,TEXT,,,dslContent,false
DROP,PROJECT_INFO,jdkId,
-ADD,PROJECT_INFO,autoStart,TINYINT,,,在启动,false
+ADD,PROJECT_INFO,autoStart,Boolean,,,在启动,false
ADD,MACHINE_DOCKER_INFO,certInfo,String,100,,证书信息,false
ADD,CERTIFICATE_INFO,fingerprint,String,50,,证书指纹,false
-ADD,MACHINE_DOCKER_INFO,certExist,TINYINT,,,证书是否存在,false
+ADD,MACHINE_DOCKER_INFO,certExist,Boolean,,,证书是否存在,false
ADD,REPOSITORY,createUser,String,50,,创建人,false
ADD,SERVER_SCRIPT_INFO,createUser,String,50,,创建人,false
ADD,BUILD_INFO,statusMsg,TEXT,,,状态信息
@@ -58,7 +58,7 @@ ADD,SCRIPT_EXECUTE_LOG,workspaceName,String,50,,工作空间名称,false
ALTER,REPOSITORY,password,String,255,,登录密码,false
ADD,WORKSPACE,group,String,50,,分组,false
ADD,MACHINE_SSH_INFO,dockerInfo,String,255,,服务器中的 docker 信息,false
-ADD,MACHINE_DOCKER_INFO,enableSsh,tinyint,0,,是否开启SSH连接,false
+ADD,MACHINE_DOCKER_INFO,enableSsh,Boolean,0,,是否开启SSH连接,false
ADD,MACHINE_DOCKER_INFO,machineSshId,String,255,,SSH连接信息,false
ADD,BUILD_INFO,resultKeepDay,Integer,,,产物保留天数,false
ADD,REPOSITORY,group,String,50,,分组,false
diff --git a/modules/server/src/main/resources/sql-view/execute.postgresql.v1.0.sql b/modules/server/src/main/resources/sql-view/execute.postgresql.v1.0.sql
new file mode 100644
index 000000000..4b627598e
--- /dev/null
+++ b/modules/server/src/main/resources/sql-view/execute.postgresql.v1.0.sql
@@ -0,0 +1,126 @@
+--
+-- Copyright (c) 2019 Of Him Code Technology Studio
+-- Jpom is licensed under Mulan PSL v2.
+-- You can use this software according to the terms and conditions of the Mulan PSL v2.
+-- You may obtain a copy of Mulan PSL v2 at:
+-- http://license.coscl.org.cn/MulanPSL2
+-- THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE.
+-- See the Mulan PSL v2 for more details.
+--
+
+DROP FUNCTION IF EXISTS column_exists;
+CREATE FUNCTION column_exists(tname varchar, cname varchar)
+RETURNS boolean
+AS
+$$
+BEGIN
+RETURN EXISTS (
+ SELECT 1
+ FROM information_schema.columns
+ WHERE table_name = tname
+ AND column_name = cname
+ );
+END;
+$$
+LANGUAGE plpgsql;
+
+-- postgresql $delimiter$
+
+DROP PROCEDURE IF EXISTS drop_column_if_exists;
+CREATE PROCEDURE drop_column_if_exists(
+ tname varchar,
+ cname varchar
+)
+LANGUAGE plpgsql
+AS $$
+DECLARE
+ drop_query varchar;
+BEGIN
+ -- 检查列是否存在
+ IF (select column_exists(tname,cname)) THEN
+ -- 构造ALTER TABLE语句
+ drop_query := format('ALTER TABLE %s DROP COLUMN %s', tname, cname);
+ -- 执行ALTER TABLE语句
+ EXECUTE drop_query;
+ END IF;
+END;
+$$;
+
+-- postgresql $delimiter$
+
+DROP PROCEDURE IF EXISTS add_column_if_not_exists;
+CREATE PROCEDURE add_column_if_not_exists(
+ tname varchar,
+ cname varchar,
+ columninfo varchar
+)
+LANGUAGE plpgsql
+AS $$
+BEGIN
+ IF NOT (
+ select column_exists(tname,cname)
+ ) THEN
+ -- 构造并执行ALTER TABLE语句来添加新列
+ EXECUTE format('ALTER TABLE %s ADD COLUMN %s ', tname, columninfo);
+ END IF;
+END;
+$$;
+
+-- postgresql $delimiter$
+
+DROP PROCEDURE IF EXISTS drop_index_if_exists;
+CREATE PROCEDURE drop_index_if_exists(
+ p_tablename varchar,
+ p_idxname varchar
+)
+LANGUAGE plpgsql
+AS $$
+DECLARE
+ idx_exists boolean;
+ drop_idx_sql text;
+BEGIN
+ -- 检查索引是否存在
+ SELECT EXISTS (
+ SELECT 1
+ FROM pg_indexes
+ WHERE tablename = p_tablename
+ AND indexname = p_idxname
+ ) INTO idx_exists;
+
+ -- 如果索引存在,则构建DROP INDEX语句并执行
+ IF idx_exists THEN
+ drop_idx_sql := format('DROP INDEX IF EXISTS %s', p_idxname);
+ EXECUTE drop_idx_sql;
+ END IF;
+END;
+$$;
+
+-- postgresql $delimiter$
+
+DROP PROCEDURE IF EXISTS exec_if_column_exists;
+CREATE PROCEDURE exec_if_column_exists(
+ tname varchar,
+ cname varchar,
+ statemetStr varchar
+)
+LANGUAGE plpgsql
+AS $$
+BEGIN
+ IF (
+ select column_exists(tname,cname)
+ ) THEN
+ EXECUTE statemetStr;
+END IF;
+END;
+$$;
+
+-- postgresql $delimiter$
+
+-- 实现 instr函数,这个是postgresql上没有的
+DROP FUNCTION IF EXISTS instr;
+CREATE FUNCTION instr(str1 text, str2 text)
+RETURNS boolean AS
+$$
+ SELECT POSITION(str2 IN str1) > 0;
+$$
+LANGUAGE sql;
diff --git a/modules/server/src/main/resources/sql-view/table.all.v1.0.csv b/modules/server/src/main/resources/sql-view/table.all.v1.0.csv
index 6091d091e..f3a107be5 100644
--- a/modules/server/src/main/resources/sql-view/table.all.v1.0.csv
+++ b/modules/server/src/main/resources/sql-view/table.all.v1.0.csv
@@ -76,9 +76,9 @@ MONITOR_INFO,modifyTimeMillis,Long,,,false,false,数据修改时间,
MONITOR_INFO,modifyUser,String,50,,false,false,修改人,
MONITOR_INFO,workspaceId,String,50,,true,false,所属工作空间,
MONITOR_INFO,name,String,50,,true,false,名称,
-MONITOR_INFO,autoRestart,TINYINT,,0,false,false,是否自动重启{1,是,0 否},
-MONITOR_INFO,status,TINYINT,,0,false,false,启用状态{1,启用,0 未启用},
-MONITOR_INFO,alarm,TINYINT,,0,false,false,报警状态{1,报警中,0 未报警},
+MONITOR_INFO,autoRestart,Boolean,,0,false,false,是否自动重启{1,是,0 否},
+MONITOR_INFO,status,Boolean,,0,false,false,启用状态{1,启用,0 未启用},
+MONITOR_INFO,alarm,Boolean,,0,false,false,报警状态{1,报警中,0 未报警},
MONITOR_INFO,cycle,Integer,,0,false,false,监控周期,
MONITOR_INFO,notifyUser,TEXT,,,false,false,报警联系人,
MONITOR_INFO,projects,TEXT,,,false,false,监控的项目,
@@ -91,7 +91,7 @@ DOCKER_INFO,modifyUser,String,50,,false,false,修改人,
DOCKER_INFO,workspaceId,String,50,,true,false,所属工作空间,
DOCKER_INFO,name,String,255,,true,false,名称,
DOCKER_INFO,host,String,255,,false,false,docker host,
-DOCKER_INFO,tlsVerify,TINYINT,,0,false,false,tls 认证{1,启用,0 未启用},
+DOCKER_INFO,tlsVerify,Boolean,,0,false,false,tls 认证{1,启用,0 未启用},
DOCKER_INFO,status,TINYINT,,0,false,false,状态{1,启用,0 未启用},
DOCKER_INFO,failureMsg,String,255,,false,false,错误消息,
DOCKER_INFO,heartbeatTimeout,Integer,,,false,false,心跳超时时间,
@@ -149,9 +149,9 @@ MONITORNOTIFYLOG,projectId,String,30,,false,false,项目id,
MONITORNOTIFYLOG,createTime,Long,,,false,false,异常时间,
MONITORNOTIFYLOG,title,String,100,,false,false,异常描述,
MONITORNOTIFYLOG,content,TEXT,,,false,false,异常内容,
-MONITORNOTIFYLOG,status,TINYINT,,,false,false,当前状态,
+MONITORNOTIFYLOG,status,Boolean,,,false,false,当前状态,
MONITORNOTIFYLOG,notifyStyle,TINYINT,,,false,false,通知方式,
-MONITORNOTIFYLOG,notifyStatus,TINYINT,,,false,false,通知状态,
+MONITORNOTIFYLOG,notifyStatus,Boolean,,,false,false,通知状态,
MONITORNOTIFYLOG,notifyObject,TEXT,,,false,false,通知对象,
MONITORNOTIFYLOG,notifyError,TEXT,,,false,false,通知异常内容,
MONITORNOTIFYLOG,workspaceId,String,50,,false,false,所属工作空间,
@@ -187,7 +187,7 @@ MONITOR_USER_OPT,modifyUser,String,50,,false,false,修改人,
MONITOR_USER_OPT,workspaceId,String,50,,true,false,所属工作空间,
MONITOR_USER_OPT,name,String,50,,true,false,名称,
MONITOR_USER_OPT,monitorUser,TEXT,,,false,false,监控的人,
-MONITOR_USER_OPT,status,TINYINT,,0,false,false,启用状态{1,启用,0 未启用},
+MONITOR_USER_OPT,status,Boolean,,0,false,false,启用状态{1,启用,0 未启用},
MONITOR_USER_OPT,notifyUser,TEXT,,,false,false,报警联系人,
MONITOR_USER_OPT,monitorFeature,TEXT,,,false,false,监控的项目,
MONITOR_USER_OPT,monitorOpt,TEXT,,,false,false,监控的项目,
@@ -227,7 +227,7 @@ PROJECT_INFO,args,TEXT,,,false,false,args,
PROJECT_INFO,javaCopyItemList,TEXT,,,false,false,javaCopyItemList,
PROJECT_INFO,token,String,255,,false,false,token,
PROJECT_INFO,runMode,String,20,,false,false,连接方式,
-PROJECT_INFO,outGivingProject,TINYINT,,0,false,false,分发项目{1,分发,0 独立项目},
+PROJECT_INFO,outGivingProject,Boolean,,0,false,false,分发项目{1,分发,0 独立项目},
PROJECT_INFO,javaExtDirsCp,TEXT,,,false,false,javaExtDirsCp,
PROJECT_INFO,sortValue,Float,,,false,false,排序值,
PROJECT_INFO,triggerToken,String,100,,false,false,触发器token,
@@ -297,13 +297,13 @@ OUT_GIVING,modifyUser,String,50,,false,false,修改人,
OUT_GIVING,workspaceId,String,50,,true,false,所属工作空间,
OUT_GIVING,name,String,50,,true,false,名称,
OUT_GIVING,afterOpt,Integer,,0,false,false,分发后的操作,
-OUT_GIVING,clearOld,TINYINT,,0,false,false,是否清空旧包发布,
-OUT_GIVING,outGivingProject,TINYINT,,0,false,false,是否为单独创建的分发项目,
+OUT_GIVING,clearOld,Boolean,,0,false,false,是否清空旧包发布,
+OUT_GIVING,outGivingProject,Boolean,,0,false,false,是否为单独创建的分发项目,
OUT_GIVING,outGivingNodeProjectList,TEXT,,,false,false,分发项目信息,
OUT_GIVING,intervalTime,Integer,,10,false,false,分发间隔时间,
OUT_GIVING,status,Integer,,0,false,false,状态{0: 未分发; 1: 分发中; 2: 分发结束},
OUT_GIVING,secondaryDirectory,String,200,,false,false,二级目录,
-OUT_GIVING,uploadCloseFirst,TINYINT,,0,false,false,是否清空旧包发布,
+OUT_GIVING,uploadCloseFirst,Boolean,,0,false,false,是否清空旧包发布,
USEROPERATELOGV1,id,String,50,,true,true,id,操作日志
USEROPERATELOGV1,ip,String,80,,false,false,客户端IP地址,
USEROPERATELOGV1,userId,String,30,,false,false,操作的用户ID,
diff --git a/modules/server/src/main/resources/sql-view/table.all.v1.1.csv b/modules/server/src/main/resources/sql-view/table.all.v1.1.csv
index 9b1e4e716..5d3305457 100644
--- a/modules/server/src/main/resources/sql-view/table.all.v1.1.csv
+++ b/modules/server/src/main/resources/sql-view/table.all.v1.1.csv
@@ -72,7 +72,7 @@ MACHINE_DOCKER_INFO,modifyUser,String,50,,false,false,修改人,
MACHINE_DOCKER_INFO,groupName,String,50,,true,false,分组名称,
MACHINE_DOCKER_INFO,name,String,255,,true,false,名称,
MACHINE_DOCKER_INFO,host,String,255,,false,false,docker host,
-MACHINE_DOCKER_INFO,tlsVerify,TINYINT,,0,false,false,tls 认证{1,启用,0 未启用},
+MACHINE_DOCKER_INFO,tlsVerify,Boolean,,0,false,false,tls 认证{1,启用,0 未启用},
MACHINE_DOCKER_INFO,status,TINYINT,,0,false,false,状态{1,启用,0 未启用},
MACHINE_DOCKER_INFO,failureMsg,String,255,,false,false,错误消息,
MACHINE_DOCKER_INFO,heartbeatTimeout,Integer,,,false,false,心跳超时时间,
@@ -90,8 +90,8 @@ USER_LOGIN_LOG,modifyTimeMillis,Long,,,false,false,数据修改时间,
USER_LOGIN_LOG,modifyUser,String,50,,false,false,修改人,
USER_LOGIN_LOG,ip,String,80,,false,false,客户端IP地址,
USER_LOGIN_LOG,userAgent,TEXT,,,false,false,浏览器标识,
-USER_LOGIN_LOG,useMfa,TINYINT,,,false,false,是否使用 mfa,
-USER_LOGIN_LOG,success,TINYINT,,,false,false,是否登录成功,
+USER_LOGIN_LOG,useMfa,Boolean,,,false,false,是否使用 mfa,
+USER_LOGIN_LOG,success,Boolean,,,false,false,是否登录成功,
USER_LOGIN_LOG,operateCode,TINYINT,,,false,false,操作状态码(备注码),
USER_LOGIN_LOG,username,String,50,,false,false,昵称,
FILE_STORAGE,id,String,50,,true,true,id,文件管理中心
diff --git a/modules/storage-module/pom.xml b/modules/storage-module/pom.xml
index ba6f674db..eccd4a43e 100644
--- a/modules/storage-module/pom.xml
+++ b/modules/storage-module/pom.xml
@@ -24,6 +24,7 @@
storage-module-common
storage-module-h2
storage-module-mysql
+ storage-module-postgresql
4.0.0
2.11.3
diff --git a/modules/storage-module/storage-module-common/src/main/java/org/dromara/jpom/db/DbExtConfig.java b/modules/storage-module/storage-module-common/src/main/java/org/dromara/jpom/db/DbExtConfig.java
index 49158473d..e92619056 100644
--- a/modules/storage-module/storage-module-common/src/main/java/org/dromara/jpom/db/DbExtConfig.java
+++ b/modules/storage-module/storage-module-common/src/main/java/org/dromara/jpom/db/DbExtConfig.java
@@ -143,6 +143,10 @@ public class DbExtConfig implements InitializingBean {
/**
* mysql
*/
- MYSQL
+ MYSQL,
+ /**
+ * postgresql
+ */
+ POSTGRESQL
}
}
diff --git a/modules/storage-module/storage-module-common/src/main/java/org/dromara/jpom/db/StorageServiceFactory.java b/modules/storage-module/storage-module-common/src/main/java/org/dromara/jpom/db/StorageServiceFactory.java
index b5a2f18d2..e231cb041 100644
--- a/modules/storage-module/storage-module-common/src/main/java/org/dromara/jpom/db/StorageServiceFactory.java
+++ b/modules/storage-module/storage-module-common/src/main/java/org/dromara/jpom/db/StorageServiceFactory.java
@@ -22,19 +22,24 @@ import cn.hutool.db.Entity;
import cn.hutool.db.Page;
import cn.hutool.db.PageResult;
import cn.hutool.db.ds.DSFactory;
+import cn.hutool.db.sql.Wrapper;
import cn.hutool.setting.Setting;
import lombok.Lombok;
import lombok.extern.slf4j.Slf4j;
+import org.dromara.jpom.dialect.DialectUtil;
import org.dromara.jpom.system.ExtConfigBean;
import org.dromara.jpom.system.JpomRuntimeException;
import org.springframework.util.Assert;
import java.io.File;
+import java.lang.reflect.Field;
import java.sql.SQLException;
+import java.util.Arrays;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Set;
import java.util.stream.Collectors;
+import java.util.stream.Stream;
/**
* 数据存储服务
@@ -67,8 +72,9 @@ public class StorageServiceFactory {
/**
* 将数据迁移到当前环境
*/
- public static void migrateH2ToNow(DbExtConfig dbExtConfig, String h2Url, String h2User, String h2Pass) {
+ public static void migrateH2ToNow(DbExtConfig dbExtConfig, String h2Url, String h2User, String h2Pass,DbExtConfig.Mode targetNode) {
log.info("开始迁移 h2 数据到 {}", dbExtConfig.getMode());
+ Assert.notNull(mode,"未指定目标数据库信息");
try {
IStorageService h2StorageService = doCreateStorageService(DbExtConfig.Mode.H2);
boolean hasDbData = h2StorageService.hasDbData();
@@ -110,7 +116,7 @@ public class StorageServiceFactory {
log.info("准备迁移数据");
int total = 0;
for (Class> aClass : classes) {
- total += migrateH2ToNowItem(aClass, h2DsFactory, nowDsFactory);
+ total += migrateH2ToNowItem(aClass, h2DsFactory, nowDsFactory,targetNode);
}
long endTime = SystemClock.now();
log.info("迁移完成,累计迁移 {} 条数据,耗时:{}", total, DateUtil.formatBetween(endTime - time));
@@ -124,14 +130,20 @@ public class StorageServiceFactory {
}
}
- private static int migrateH2ToNowItem(Class> aClass, DSFactory h2DsFactory, DSFactory mysqlDsFactory) throws SQLException {
+ private static int migrateH2ToNowItem(Class> aClass, DSFactory h2DsFactory, DSFactory targetDsFactory,DbExtConfig.Mode targetNode) throws SQLException {
TableName tableName = aClass.getAnnotation(TableName.class);
+ Wrapper h2FieldWrapper = DialectUtil.getH2Dialect().getWrapper();
+ Set boolFieldSet = Arrays.stream(ReflectUtil.getFields(aClass, field -> Boolean.class.equals(field.getType()) || boolean.class.equals(field.getType())))
+ .map(Field::getName)
+ .flatMap(name-> Stream.of(name,h2FieldWrapper.wrap(name)))
+ .collect(Collectors.toSet());
+
log.info("开始迁移 {} {}", tableName.name(), tableName.value());
int total = 0;
while (true) {
Entity where = Entity.create(tableName.value());
PageResult pageResult;
- Db db = Db.use(h2DsFactory.getDataSource());
+ Db db = Db.use(h2DsFactory.getDataSource(),DialectUtil.getH2Dialect());
Page page = new Page(1, 200);
pageResult = db.page(where, page);
if (pageResult.isEmpty()) {
@@ -139,12 +151,22 @@ public class StorageServiceFactory {
}
// 过滤需要忽略迁移的数据
List newResult = pageResult.stream()
- .map(entity -> entity.toBeanIgnoreCase(aClass))
- .map(o -> {
- // 兼容大小写
- Entity entity = Entity.create(tableName.value());
- return entity.parseBean(o, false, true);
- }).collect(Collectors.toList());
+ .map(entity -> entity.toBeanIgnoreCase(aClass))
+ .map(o -> {
+ // 兼容大小写
+ Entity entity = Entity.create(tableName.value());
+ return entity.parseBean(o, false, true);
+ }).peek(entity -> {
+ if( DbExtConfig.Mode.POSTGRESQL.equals(targetNode) ) {
+ // tinyint类型查出来是数字,需转为bool
+ boolFieldSet.forEach(fieldName->{
+ Object field = entity.get(fieldName);
+ if( field instanceof Number ) {
+ entity.set(fieldName,BooleanUtil.toBoolean(field.toString()));
+ }
+ });
+ }
+ }).collect(Collectors.toList());
if (newResult.isEmpty()) {
if (pageResult.isLast()) {
// 最后一页
@@ -155,7 +177,7 @@ public class StorageServiceFactory {
}
total += newResult.size();
// 插入信息数据
- Db db2 = Db.use(mysqlDsFactory.getDataSource());
+ Db db2 = Db.use(targetDsFactory.getDataSource(),DialectUtil.getDialectByMode(targetNode));
db2.insert(newResult);
// 删除数据
Entity deleteWhere = Entity.create(tableName.value());
diff --git a/modules/storage-module/storage-module-common/src/main/java/org/dromara/jpom/dialect/DialectUtil.java b/modules/storage-module/storage-module-common/src/main/java/org/dromara/jpom/dialect/DialectUtil.java
new file mode 100644
index 000000000..4680d2995
--- /dev/null
+++ b/modules/storage-module/storage-module-common/src/main/java/org/dromara/jpom/dialect/DialectUtil.java
@@ -0,0 +1,102 @@
+package org.dromara.jpom.dialect;
+
+import cn.hutool.core.bean.BeanUtil;
+import cn.hutool.db.dialect.Dialect;
+import cn.hutool.db.dialect.impl.H2Dialect;
+import cn.hutool.db.dialect.impl.MysqlDialect;
+import cn.hutool.db.dialect.impl.PostgresqlDialect;
+import cn.hutool.db.sql.Wrapper;
+import cn.hutool.extra.spring.SpringUtil;
+import org.dromara.jpom.db.DbExtConfig;
+
+import java.util.Set;
+import java.util.concurrent.ConcurrentHashMap;
+import java.util.stream.Collectors;
+import java.util.stream.Stream;
+
+/**
+ * 数据库方言工具类
+ *
+ * @author whz
+ * @since 2024/3/10
+ */
+public class DialectUtil {
+
+ private final static ConcurrentHashMap DIALECT_CACHE = new ConcurrentHashMap<>();
+ private static volatile Wrapper currentDbFieldWrapper;
+ private static final ConcurrentHashMap WRAP_FIELD_MAP = new ConcurrentHashMap<>();
+
+ public static Dialect getH2Dialect() {
+ return DIALECT_CACHE.computeIfAbsent(DbExtConfig.Mode.H2, key-> {
+ H2Dialect h2Dialect = new H2Dialect();
+ h2Dialect.setWrapper(new Wrapper('`'));
+ return h2Dialect;
+ });
+ }
+
+ public static Dialect getMySqlDialect() {
+ return DIALECT_CACHE.computeIfAbsent(DbExtConfig.Mode.MYSQL, key->new MysqlDialect());
+ }
+
+ /**
+ * 获取自定义postgresql数据库的方言
+ * @return 自定义postgresql数据库的方言
+ */
+ public static Dialect getPostgresqlDialect() {
+ return DIALECT_CACHE.computeIfAbsent(DbExtConfig.Mode.POSTGRESQL, key->{
+ // 需要特殊处理的列名或表名,需要时在这里添加即可
+ Set names = Stream.of("group","desc","user","content").map(String::toLowerCase).collect(Collectors.toSet());
+ Wrapper wrapper = new Wrapper(){
+ @Override
+ public String wrap(String field) {
+ field = field.toLowerCase();
+ if( field.charAt(0) == '`' && field.charAt(field.length()-1) == '`' ) {
+ field = field.substring(1,field.length()-1);
+ }
+ if( names.contains(field) ) {
+ return super.wrap(field);
+ }
+ return field; // 不属于columns的直接返回
+ }
+ };
+
+ PostgresqlDialect dialect = new PostgresqlDialect();
+ Wrapper innerWrapper = (Wrapper) BeanUtil.getFieldValue(dialect, "wrapper");
+
+ wrapper.setPreWrapQuote(innerWrapper.getPreWrapQuote());
+ wrapper.setSufWrapQuote(innerWrapper.getSufWrapQuote());
+ BeanUtil.setFieldValue(dialect,"wrapper",wrapper);
+ return dialect;
+ });
+ }
+
+ public static Dialect getDialectByMode(DbExtConfig.Mode mode) {
+ switch ( mode ) {
+ case H2: return getH2Dialect();
+ case MYSQL: return getMySqlDialect();
+ case POSTGRESQL: return getPostgresqlDialect();
+ }
+ throw new IllegalArgumentException("未知的数据库方言类型");
+ }
+
+ /**
+ * 获取表名或列名在当前配置的数据库上的方言,例如 postgresql会使用 " ,mysql使用 `
+ * @param field 表名或者列名
+ * @return 处理后的表名或者列名
+ */
+ public static String wrapField(String field) {
+ if( currentDbFieldWrapper == null ) {
+ synchronized (DialectUtil.class) {
+ if( currentDbFieldWrapper == null ) {
+ DbExtConfig dbExtConfig = SpringUtil.getBean(DbExtConfig.class);
+ if( dbExtConfig == null || dbExtConfig.getMode() == null ) {
+ throw new IllegalStateException("数据库Mode配置缺失");
+ }
+ Dialect dialectByMode = getDialectByMode(dbExtConfig.getMode());
+ currentDbFieldWrapper = dialectByMode.getWrapper();
+ }
+ }
+ }
+ return WRAP_FIELD_MAP.computeIfAbsent(field,key-> currentDbFieldWrapper.wrap(field) );
+ }
+}
diff --git a/modules/storage-module/storage-module-h2/src/main/java/org/dromara/jpom/storage/H2TableBuilderImpl.java b/modules/storage-module/storage-module-h2/src/main/java/org/dromara/jpom/storage/H2TableBuilderImpl.java
index 3a51d1786..9b2b34b1f 100644
--- a/modules/storage-module/storage-module-h2/src/main/java/org/dromara/jpom/storage/H2TableBuilderImpl.java
+++ b/modules/storage-module/storage-module-h2/src/main/java/org/dromara/jpom/storage/H2TableBuilderImpl.java
@@ -147,6 +147,7 @@ public class H2TableBuilderImpl implements IStorageSqlBuilderService {
case "INTEGER":
stringBuilder.append("INTEGER").append(StrUtil.SPACE);
break;
+ case "BOOLEAN":
case "TINYINT":
stringBuilder.append("TINYINT").append(StrUtil.SPACE);
break;
diff --git a/modules/storage-module/storage-module-mysql/src/main/java/org/dromara/jpom/storage/MysqlTableBuilderImpl.java b/modules/storage-module/storage-module-mysql/src/main/java/org/dromara/jpom/storage/MysqlTableBuilderImpl.java
index 1903e1a02..fd2443af6 100644
--- a/modules/storage-module/storage-module-mysql/src/main/java/org/dromara/jpom/storage/MysqlTableBuilderImpl.java
+++ b/modules/storage-module/storage-module-mysql/src/main/java/org/dromara/jpom/storage/MysqlTableBuilderImpl.java
@@ -160,6 +160,7 @@ public class MysqlTableBuilderImpl implements IStorageSqlBuilderService {
case "INTEGER":
stringBuilder.append("int").append(StrUtil.SPACE);
break;
+ case "BOOLEAN":
case "TINYINT":
stringBuilder.append("TINYINT").append(StrUtil.SPACE);
break;
diff --git a/modules/storage-module/storage-module-postgresql/pom.xml b/modules/storage-module/storage-module-postgresql/pom.xml
new file mode 100644
index 000000000..443183fe5
--- /dev/null
+++ b/modules/storage-module/storage-module-postgresql/pom.xml
@@ -0,0 +1,39 @@
+
+
+
+ jpom-storage-module-parent
+ org.dromara.jpom.storage-module
+ 2.11.3
+
+ 4.0.0
+
+ storage-module-postgresql
+
+
+ 8
+ 8
+ UTF-8
+
+
+
+
+ org.dromara.jpom.storage-module
+ storage-module-common
+ ${project.version}
+
+
+
+ org.postgresql
+ postgresql
+
+
+
+ org.dromara.jpom
+ common
+ provided
+ ${project.version}
+
+
+
diff --git a/modules/storage-module/storage-module-postgresql/src/main/java/org/dromara/jpom/storage/PostgresqlStorageServiceImpl.java b/modules/storage-module/storage-module-postgresql/src/main/java/org/dromara/jpom/storage/PostgresqlStorageServiceImpl.java
new file mode 100644
index 000000000..3d36678ee
--- /dev/null
+++ b/modules/storage-module/storage-module-postgresql/src/main/java/org/dromara/jpom/storage/PostgresqlStorageServiceImpl.java
@@ -0,0 +1,108 @@
+/*
+ * The MIT License (MIT)
+ *
+ * Copyright (c) 2019 Code Technology Studio
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy of
+ * this software and associated documentation files (the "Software"), to deal in
+ * the Software without restriction, including without limitation the rights to
+ * use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
+ * the Software, and to permit persons to whom the Software is furnished to do so,
+ * subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in all
+ * copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
+ * FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
+ * COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
+ * IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
+ * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+ */
+package org.dromara.jpom.storage;
+
+import cn.hutool.core.lang.Opt;
+import cn.hutool.db.ds.DSFactory;
+import cn.hutool.setting.Setting;
+import lombok.extern.slf4j.Slf4j;
+import org.dromara.jpom.db.DbExtConfig;
+import org.dromara.jpom.db.IStorageService;
+import org.dromara.jpom.system.JpomRuntimeException;
+import org.springframework.util.Assert;
+
+/**
+ * @author whz
+ * @since 2024/03/06
+ */
+@Slf4j
+public class PostgresqlStorageServiceImpl implements IStorageService {
+
+ private String dbUrl;
+ private DSFactory dsFactory;
+
+ @Override
+ public String dbUrl() {
+ Assert.hasText(this.dbUrl, "还没有初始化数据库");
+ return dbUrl;
+ }
+
+ @Override
+ public int getFetchSize() {
+ return Integer.MIN_VALUE;
+ }
+
+ @Override
+ public DbExtConfig.Mode mode() {
+ return DbExtConfig.Mode.POSTGRESQL;
+ }
+
+ @Override
+ public DSFactory init(DbExtConfig dbExtConfig) {
+ Assert.isNull(this.dsFactory, "不要重复初始化数据库");
+ Assert.hasText(dbExtConfig.getUrl(), "没有配置数据库连接");
+ Setting setting = dbExtConfig.toSetting();
+ this.dsFactory = DSFactory.create(setting);
+ this.dbUrl = dbExtConfig.getUrl();
+ return this.dsFactory;
+ }
+
+ @Override
+ public DSFactory create(DbExtConfig dbExtConfig, String url, String user, String pass) {
+ Setting setting = this.createSetting(dbExtConfig, url, user, pass);
+ return DSFactory.create(setting);
+ }
+
+ @Override
+ public Setting createSetting(DbExtConfig dbExtConfig, String url, String user, String pass) {
+ String url2 = Opt.ofBlankAble(url).orElse(dbExtConfig.getUrl());
+ String user2 = Opt.ofBlankAble(user).orElse(dbExtConfig.getUserName());
+ String pass2 = Opt.ofBlankAble(pass).orElse(dbExtConfig.getUserPwd());
+ Setting setting = dbExtConfig.toSetting();
+ setting.set("user", user2);
+ setting.set("pass", pass2);
+ setting.set("url", url2);
+ return setting;
+ }
+
+ public DSFactory getDsFactory() {
+ Assert.notNull(this.dsFactory, "还没有初始化数据库");
+ return dsFactory;
+ }
+
+
+ @Override
+ public JpomRuntimeException warpException(Exception e) {
+ return new JpomRuntimeException("数据库异常", e);
+ }
+
+
+ @Override
+ public void close() {
+ log.info("postgresql db destroy");
+ if (this.dsFactory != null) {
+ dsFactory.destroy();
+ this.dsFactory = null;
+ }
+ }
+}
diff --git a/modules/storage-module/storage-module-postgresql/src/main/java/org/dromara/jpom/storage/PostgresqlTableBuilderImpl.java b/modules/storage-module/storage-module-postgresql/src/main/java/org/dromara/jpom/storage/PostgresqlTableBuilderImpl.java
new file mode 100644
index 000000000..efd9e645c
--- /dev/null
+++ b/modules/storage-module/storage-module-postgresql/src/main/java/org/dromara/jpom/storage/PostgresqlTableBuilderImpl.java
@@ -0,0 +1,308 @@
+/*
+ * The MIT License (MIT)
+ *
+ * Copyright (c) 2019 Code Technology Studio
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy of
+ * this software and associated documentation files (the "Software"), to deal in
+ * the Software without restriction, including without limitation the rights to
+ * use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
+ * the Software, and to permit persons to whom the Software is furnished to do so,
+ * subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in all
+ * copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
+ * FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
+ * COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
+ * IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
+ * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+ */
+package org.dromara.jpom.storage;
+
+import cn.hutool.core.collection.CollUtil;
+import cn.hutool.core.util.BooleanUtil;
+import cn.hutool.core.util.ObjectUtil;
+import cn.hutool.core.util.StrUtil;
+import cn.hutool.db.sql.Wrapper;
+import org.dromara.jpom.db.*;
+import org.dromara.jpom.dialect.DialectUtil;
+import org.springframework.util.Assert;
+
+import java.util.List;
+import java.util.stream.Collectors;
+
+/**
+ * postgresql的sql语句构建类
+ * 目前不管列名还是表名,索引名,都会转化为小写的形式,而特殊的关键字会通过Dialect的Wrapper处理
+ * @author whz
+ * @since 2024/3/10
+ */
+public class PostgresqlTableBuilderImpl implements IStorageSqlBuilderService {
+
+ @Override
+ public DbExtConfig.Mode mode() {
+ return DbExtConfig.Mode.POSTGRESQL;
+ }
+
+ @Override
+ public String generateIndexSql(List row) {
+ StringBuilder stringBuilder = new StringBuilder();
+ Wrapper fieldWrapper = DialectUtil.getPostgresqlDialect().getWrapper();
+ for (TableViewIndexData viewIndexData : row) {
+ String indexType = viewIndexData.getIndexType();
+ // 存储过程对大小写敏感,因此传入的 表名,索引名,列名都转需要为小写
+ String name = viewIndexData.getName().toLowerCase();
+ String field = viewIndexData.getField();
+ String tableName = viewIndexData.getTableName().toLowerCase();
+
+ List fields = StrUtil.splitTrim(field, "+").stream()
+ .map(fieldWrapper::wrap).collect(Collectors.toList());
+ /**
+ * CALL drop_index_if_exists('表名','索引名');
+ * CREATE UNIQUE INDEX 索引名 ON 表名 (field1,field2,...);
+ * CREATE INDEX 索引名 ON 表名 (field1,field2,...);
+ * field需要通过wrapper处理
+ */
+ switch (indexType) {
+ case "ADD-UNIQUE":{
+ Assert.notEmpty(fields, "索引未配置字段");
+ stringBuilder.append("CALL drop_index_if_exists('").append(tableName).append("','").append(name).append("')").append(";").append(StrUtil.LF);
+ stringBuilder.append(this.delimiter()).append(StrUtil.LF);
+ stringBuilder.append("CREATE UNIQUE INDEX ").append(name)
+ .append(" ON ").append(tableName).append(" (").append(CollUtil.join(fields, StrUtil.COMMA)).append(")")
+ .append(";");
+ break;
+ }
+ case "ADD": {
+ Assert.notEmpty(fields, "索引未配置字段");
+ stringBuilder.append("CALL drop_index_if_exists('").append(tableName).append("','").append(name).append("')").append(";").append(StrUtil.LF);
+ stringBuilder.append(this.delimiter()).append(StrUtil.LF);
+ stringBuilder.append("CREATE INDEX ").append(name)
+ .append(" ON ").append(tableName).append(" (").append(CollUtil.join(fields, StrUtil.COMMA)).append(")")
+ .append(";");
+ break;
+ }
+ default:
+ throw new IllegalArgumentException("不支持的类型:" + indexType);
+ }
+ stringBuilder.append(";").append(StrUtil.LF);
+ stringBuilder.append(this.delimiter()).append(StrUtil.LF);
+ }
+ return stringBuilder.toString();
+ }
+
+ @Override
+ public String generateAlterTableSql(List row) {
+ StringBuilder stringBuilder = new StringBuilder();
+ Wrapper fieldWrapper = DialectUtil.getPostgresqlDialect().getWrapper();
+ for (TableViewAlterData viewAlterData : row) {
+ String alterType = viewAlterData.getAlterType();
+ String tableName = fieldWrapper.wrap(viewAlterData.getTableName());
+ String columnName = viewAlterData.getName().toLowerCase(); //不使用wrapper,存储过程调用时,column不需要包裹
+ switch (alterType) {
+ case "DROP":
+ stringBuilder.append("CALL drop_column_if_exists('").append(tableName).append("', '").append(columnName).append("')");
+ break;
+ case "ADD":
+ stringBuilder.append("CALL add_column_if_not_exists('").append(tableName).append("','")
+ .append(columnName).append("','");
+ stringBuilder.append(generateAddColumnSql(viewAlterData,true));
+ stringBuilder.append("');");
+ /**
+ * 添加列时,因为不能同时指定注释内容,单独加一个语句设置注释
+ * COMMENT ON COLUMN 表名.field IS '注释内容'
+ * field需要通过wrapper处理
+ */
+ if( StrUtil.isNotBlank(viewAlterData.getComment()) ){
+ stringBuilder.append(delimiter()).append(StrUtil.LF);
+ stringBuilder.append("COMMENT ON COLUMN ").append(tableName)
+ .append(StrUtil.DOT).append(fieldWrapper.wrap(viewAlterData.getName())).append(" IS ")
+ .append("'").append(viewAlterData.getComment().trim()).append("';");
+ }
+ break;
+ case "ALTER":
+ stringBuilder.append(generateAlterSql(viewAlterData));
+ break;
+ case "DROP-TABLE":
+ stringBuilder.append("drop table if exists ").append(viewAlterData.getTableName());
+ break;
+ default:
+ throw new IllegalArgumentException("不支持的类型:" + alterType);
+ }
+ stringBuilder.append(";").append(StrUtil.LF);
+ stringBuilder.append(this.delimiter()).append(StrUtil.LF);
+
+ }
+ return stringBuilder.toString();
+ }
+
+ /**
+ * @param name 表名
+ * @param desc 描述
+ * @param row 字段信息
+ * @return sql
+ */
+ @Override
+ public String generateTableSql(String name, String desc, List row) {
+ StringBuilder stringBuilder = new StringBuilder();
+ Wrapper fieldWrapper = DialectUtil.getPostgresqlDialect().getWrapper();
+ String tableName = fieldWrapper.wrap(name);
+
+ stringBuilder.append("CREATE TABLE IF NOT EXISTS ").append(tableName).append(StrUtil.LF);
+ stringBuilder.append("(").append(StrUtil.LF);
+ for (TableViewData tableViewData : row) {
+ stringBuilder.append(StrUtil.TAB).append(this.generateColumnSql(tableViewData)).append(StrUtil.COMMA).append(StrUtil.LF);
+ }
+ // 主键
+ List primaryKeys = row.stream()
+ .filter(tableViewData -> tableViewData.getPrimaryKey() != null && tableViewData.getPrimaryKey())
+ .map(viewData->fieldWrapper.wrap(viewData.getName()))
+ .collect(Collectors.toList());
+ Assert.notEmpty(primaryKeys, "表没有主键");
+ stringBuilder.append(StrUtil.TAB).append("PRIMARY KEY (").append(CollUtil.join(primaryKeys, StrUtil.COMMA)).append(")").append(StrUtil.LF);
+ stringBuilder.append(");").append(StrUtil.LF);
+ // 表注释
+ stringBuilder.append("COMMENT ON TABLE ").append(fieldWrapper.wrap(name)).append(" IS '").append(desc).append("';");
+
+
+ // 建表语句的列注释需要通过单独的sql语句设置
+ for (TableViewData tableViewData : row) {
+ if( StrUtil.isNotBlank(tableViewData.getComment()) ) {
+ stringBuilder.append(delimiter()).append(StrUtil.LF);
+ stringBuilder.append("CALL exec_if_column_exists('")
+ .append(tableName).append("','")
+ .append(tableViewData.getName()).append("','")
+ .append("COMMENT ON COLUMN ").append(tableName)
+ .append(StrUtil.DOT).append(fieldWrapper.wrap(tableViewData.getName())).append(" IS ")
+ .append("''").append(tableViewData.getComment().trim()).append("'' ');");
+ }
+ }
+ return stringBuilder.toString();
+ }
+
+ @Override
+ public String generateColumnSql(TableViewRowData tableViewRowData) {
+ return generateAddColumnSql(tableViewRowData,false);
+ }
+
+
+ private String generateAddColumnSql(TableViewRowData tableViewRowData,boolean encode) {
+
+ StringBuilder strBuilder = new StringBuilder();
+ Wrapper fieldWrapper = DialectUtil.getPostgresqlDialect().getWrapper();
+ String type = getColumnTypeStr(tableViewRowData.getType(),tableViewRowData.getLen());
+ strBuilder.append(StrUtil.SPACE).append(fieldWrapper.wrap(tableViewRowData.getName()))
+ .append(StrUtil.SPACE).append(type);
+
+ String defaultValue = tableViewRowData.getDefaultValue();
+ if (StrUtil.isNotEmpty(defaultValue)) {
+ if( "BOOLEAN".equals(type) ) {
+ defaultValue = Boolean.toString(BooleanUtil.toBoolean(defaultValue.trim()));
+ }
+ strBuilder.append(" DEFAULT '").append(defaultValue).append("'");
+ }
+
+ Boolean notNull = tableViewRowData.getNotNull();
+ if (notNull != null && notNull) {
+ strBuilder.append(" NOT NULL ");
+ }
+ String columnSql = strBuilder.toString();
+ columnSql = encode ? StrUtil.replace(columnSql, "'", "''") : columnSql;
+ int length = StrUtil.length(columnSql);
+ Assert.state(length <= 180, "sql 语句太长啦");
+ return columnSql;
+ }
+
+ /**
+ * 生成postgresql的alter语句
+ * postgresql不像 h2或mysql可以一个alter同时设置 数据类型,默认值,非空,注释,因此需生成多条sql语句才能实现功能
+ * @param viewAlterData
+ * @return
+ */
+ private String generateAlterSql(TableViewAlterData viewAlterData) {
+
+ Wrapper fieldWrapper = DialectUtil.getPostgresqlDialect().getWrapper();
+ StringBuilder strBuilder = new StringBuilder();
+ String tableName = fieldWrapper.wrap(viewAlterData.getTableName());
+ String name = fieldWrapper.wrap(viewAlterData.getName());
+
+ // 先改类型
+ String type = getColumnTypeStr(viewAlterData.getType(),viewAlterData.getLen());
+ strBuilder.append("ALTER TABLE ").append(tableName)
+ .append(" ALTER COLUMN ").append(name)
+ .append(" TYPE ").append(type).append(";");
+
+ // 再设置默认值
+ strBuilder.append(delimiter()).append(StrUtil.LF);
+ String defaultValue = viewAlterData.getDefaultValue();
+ strBuilder.append("ALTER TABLE ").append(tableName)
+ .append(" ALTER COLUMN ").append(name)
+ .append(" SET DEFAULT '");
+ if (StrUtil.isNotEmpty(defaultValue)) {
+ if( "BOOLEAN".equals(type) ) {
+ defaultValue = Boolean.toString(BooleanUtil.toBoolean(defaultValue.trim()));
+ }
+ strBuilder.append(defaultValue).append("';");
+ }else {
+ strBuilder.append("NULL").append("';");
+ }
+
+ // 设置非空
+ strBuilder.append(delimiter()).append(StrUtil.LF);
+ strBuilder.append("ALTER TABLE ").append(tableName)
+ .append(" ALTER COLUMN ").append(name);
+ Boolean notNull = viewAlterData.getNotNull();
+ if (notNull != null && notNull) {
+ strBuilder.append(" SET NOT NULL ").append(";");
+ }else {
+ strBuilder.append(" DROP NOT NULL ").append(";");
+ }
+
+ // 注释
+ strBuilder.append(delimiter()).append(StrUtil.LF);
+ String comment = viewAlterData.getComment();
+ comment = StrUtil.isEmpty(comment) ? StrUtil.EMPTY : comment.trim();
+ strBuilder.append("COMMENT ON COLUMN ").append(tableName)
+ .append(StrUtil.DOT).append(name).append(" IS ")
+ .append("'").append(comment).append("';");
+
+ Assert.state(strBuilder.length() <= 1000, "sql 语句太长啦");
+ return strBuilder.toString();
+ }
+
+
+
+ @Override
+ public String delimiter() {
+ return "-- postgresql $delimiter$";
+ }
+
+
+ private String getColumnTypeStr(String type,Integer dataLen) {
+ Assert.hasText(type, "未正确配置数据类型");
+ type = type.toUpperCase();
+ switch (type) {
+ case "LONG":
+ return "BIGINT";
+ case "STRING":
+ return "VARCHAR(" + ObjectUtil.defaultIfNull(dataLen, 255) + ")";
+ case "TEXT":
+ return "TEXT";
+ case "INTEGER":
+ return "INTEGER";
+ case "BOOLEAN":
+ return "BOOLEAN";
+ case "TINYINT":
+ return "SMALLINT";
+ case "FLOAT":
+ return "REAL";
+ case "DOUBLE":
+ return "DOUBLE PRECISION";
+ default:
+ throw new IllegalArgumentException("不支持的数据类型:" + type);
+ }
+ }
+}
diff --git a/modules/storage-module/storage-module-postgresql/src/main/resources/META-INF/services/org.dromara.jpom.db.IStorageService b/modules/storage-module/storage-module-postgresql/src/main/resources/META-INF/services/org.dromara.jpom.db.IStorageService
new file mode 100644
index 000000000..735b67e3a
--- /dev/null
+++ b/modules/storage-module/storage-module-postgresql/src/main/resources/META-INF/services/org.dromara.jpom.db.IStorageService
@@ -0,0 +1 @@
+org.dromara.jpom.storage.PostgresqlStorageServiceImpl
diff --git a/modules/storage-module/storage-module-postgresql/src/main/resources/META-INF/services/org.dromara.jpom.db.IStorageSqlBuilderService b/modules/storage-module/storage-module-postgresql/src/main/resources/META-INF/services/org.dromara.jpom.db.IStorageSqlBuilderService
new file mode 100644
index 000000000..6173f7ff3
--- /dev/null
+++ b/modules/storage-module/storage-module-postgresql/src/main/resources/META-INF/services/org.dromara.jpom.db.IStorageSqlBuilderService
@@ -0,0 +1 @@
+org.dromara.jpom.storage.PostgresqlTableBuilderImpl