From a1c513e0587abbaaf348fc04a020edc44ebd2ada Mon Sep 17 00:00:00 2001 From: whz <1415238952@qq.com> Date: Sat, 16 Mar 2024 22:17:58 +0800 Subject: [PATCH] =?UTF-8?q?=E5=A2=9E=E5=8A=A0postgresql=E6=95=B0=E6=8D=AE?= =?UTF-8?q?=E5=BA=93=E6=94=AF=E6=8C=81=E5=92=8Ch2=E8=BF=81=E7=A7=BBpostgre?= =?UTF-8?q?sql=E5=8A=9F=E8=83=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- modules/server/pom.xml | 6 + .../org/dromara/jpom/system/db/InitDb.java | 18 +- .../main/resources/application-postgresql.yml | 11 + .../resources/sql-view/alter.all.v1.0.csv | 2 +- .../resources/sql-view/alter.all.v1.1.csv | 4 +- .../resources/sql-view/alter.all.v1.3.csv | 6 +- .../sql-view/execute.postgresql.v1.0.sql | 126 +++++++ .../resources/sql-view/table.all.v1.0.csv | 22 +- .../resources/sql-view/table.all.v1.1.csv | 6 +- modules/storage-module/pom.xml | 1 + .../java/org/dromara/jpom/db/DbExtConfig.java | 6 +- .../jpom/db/StorageServiceFactory.java | 44 ++- .../org/dromara/jpom/dialect/DialectUtil.java | 102 ++++++ .../jpom/storage/H2TableBuilderImpl.java | 1 + .../jpom/storage/MysqlTableBuilderImpl.java | 1 + .../storage-module-postgresql/pom.xml | 39 +++ .../storage/PostgresqlStorageServiceImpl.java | 108 ++++++ .../storage/PostgresqlTableBuilderImpl.java | 308 ++++++++++++++++++ .../org.dromara.jpom.db.IStorageService | 1 + ....dromara.jpom.db.IStorageSqlBuilderService | 1 + 20 files changed, 776 insertions(+), 37 deletions(-) create mode 100644 modules/server/src/main/resources/application-postgresql.yml create mode 100644 modules/server/src/main/resources/sql-view/execute.postgresql.v1.0.sql create mode 100644 modules/storage-module/storage-module-common/src/main/java/org/dromara/jpom/dialect/DialectUtil.java create mode 100644 modules/storage-module/storage-module-postgresql/pom.xml create mode 100644 modules/storage-module/storage-module-postgresql/src/main/java/org/dromara/jpom/storage/PostgresqlStorageServiceImpl.java create mode 100644 modules/storage-module/storage-module-postgresql/src/main/java/org/dromara/jpom/storage/PostgresqlTableBuilderImpl.java create mode 100644 modules/storage-module/storage-module-postgresql/src/main/resources/META-INF/services/org.dromara.jpom.db.IStorageService create mode 100644 modules/storage-module/storage-module-postgresql/src/main/resources/META-INF/services/org.dromara.jpom.db.IStorageSqlBuilderService 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