增加postgresql数据库支持和h2迁移postgresql功能

This commit is contained in:
whz 2024-03-16 22:17:58 +08:00
parent 5d3b783468
commit a1c513e058
20 changed files with 776 additions and 37 deletions

View File

@ -122,6 +122,12 @@
<version>${project.version}</version>
</dependency>
<dependency>
<groupId>org.dromara.jpom.storage-module</groupId>
<artifactId>storage-module-postgresql</artifactId>
<version>${project.version}</version>
</dependency>
<dependency>
<groupId>me.zhyd.oauth</groupId>
<artifactId>JustAuth</artifactId>

View File

@ -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<Db>) parameter -> {
Db.use(dataSource, DialectUtil.getDialectByMode(mode)).tx((CheckedUtil.VoidFunc1Rt<Db>) parameter -> {
// 分隔后执行mysql 不能执行多条 sql 语句
List<String> 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<DbExtConfig.Mode> 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);
});
}

View File

@ -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

View File

@ -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

1 alterType,tableName,name,type,len,defaultValue,comment,notNull
2 DROP,NODE_INFO,cycle
3 ADD,PROJECT_INFO,triggerToken,String,100,,触发器token
4 ADD,OUT_GIVING,secondaryDirectory,String,200,,二级目录
5 ADD,OUT_GIVING,uploadCloseFirst,TINYINT,,0,是否清空旧包发布 ADD,OUT_GIVING,uploadCloseFirst,Boolean,,0,是否清空旧包发布
6 ADD,USEROPERATELOGV1,dataName,String,200,,数据名称
7 DROP,SSHTERMINALEXECUTELOG,optTime
8 DROP,USEROPERATELOGV1,optType

View File

@ -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,,,集群管理员

1 alterType,tableName,name,type,len,defaultValue,comment,notNull
6 ADD,OUT_GIVING,webhook,String,255,,webhook
7 DROP,NODE_INFO,unLockType
8 ADD,NODE_INFO,machineId,String,50,,机器id
9 ADD,MACHINE_NODE_INFO,templateNode,TINYINT,,,模板节点 ,1 模板节点 0 非模板节点 ADD,MACHINE_NODE_INFO,templateNode,Boolean,,,模板节点 ,1 模板节点 0 非模板节点
10 ADD,REPOSITORY,timeout,Integer,,,仓库超时连接
11 ADD,SSH_INFO,group,String,50,,分组
12 ADD,SSH_INFO,machineSshId,String,50,,机器sshid
28 DROP,DOCKER_SWARM_INFO,failureMsg,
29 DROP,DOCKER_SWARM_INFO,dockerId,
30 DROP,DOCKER_SWARM_INFO,dockerId,
31 ADD,MACHINE_DOCKER_INFO,swarmControlAvailable,TINYINT,,,集群管理员 ADD,MACHINE_DOCKER_INFO,swarmControlAvailable,Boolean,,,集群管理员

View File

@ -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

1 alterType,tableName,name,type,len,defaultValue,comment,notNull
40 ADD,FILE_STORAGE,aliasCode,String,50,,别名码,false
41 ADD,PROJECT_INFO,dslContent,TEXT,,,dslContent,false
42 DROP,PROJECT_INFO,jdkId,
43 ADD,PROJECT_INFO,autoStart,TINYINT,,,在启动,false ADD,PROJECT_INFO,autoStart,Boolean,,,在启动,false
44 ADD,MACHINE_DOCKER_INFO,certInfo,String,100,,证书信息,false
45 ADD,CERTIFICATE_INFO,fingerprint,String,50,,证书指纹,false
46 ADD,MACHINE_DOCKER_INFO,certExist,TINYINT,,,证书是否存在,false ADD,MACHINE_DOCKER_INFO,certExist,Boolean,,,证书是否存在,false
47 ADD,REPOSITORY,createUser,String,50,,创建人,false
48 ADD,SERVER_SCRIPT_INFO,createUser,String,50,,创建人,false
49 ADD,BUILD_INFO,statusMsg,TEXT,,,状态信息
58 ALTER,REPOSITORY,password,String,255,,登录密码,false
59 ADD,WORKSPACE,group,String,50,,分组,false
60 ADD,MACHINE_SSH_INFO,dockerInfo,String,255,,服务器中的 docker 信息,false
61 ADD,MACHINE_DOCKER_INFO,enableSsh,tinyint,0,,是否开启SSH连接,false ADD,MACHINE_DOCKER_INFO,enableSsh,Boolean,0,,是否开启SSH连接,false
62 ADD,MACHINE_DOCKER_INFO,machineSshId,String,255,,SSH连接信息,false
63 ADD,BUILD_INFO,resultKeepDay,Integer,,,产物保留天数,false
64 ADD,REPOSITORY,group,String,50,,分组,false

View File

@ -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;

View File

@ -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,是否自动重启{10 否},
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,是否自动重启{10 否},
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,

1 tableName name type len defaultValue notNull primaryKey comment tableComment
76 MONITOR_INFO modifyUser String 50 false false 修改人
77 MONITOR_INFO workspaceId String 50 true false 所属工作空间
78 MONITOR_INFO name String 50 true false 名称
79 MONITOR_INFO autoRestart TINYINT Boolean 0 false false 是否自动重启{1,是,0 否}
80 MONITOR_INFO status TINYINT Boolean 0 false false 启用状态{1,启用,0 未启用}
81 MONITOR_INFO alarm TINYINT Boolean 0 false false 报警状态{1,报警中,0 未报警}
82 MONITOR_INFO cycle Integer 0 false false 监控周期
83 MONITOR_INFO notifyUser TEXT false false 报警联系人
84 MONITOR_INFO projects TEXT false false 监控的项目
91 DOCKER_INFO workspaceId String 50 true false 所属工作空间
92 DOCKER_INFO name String 255 true false 名称
93 DOCKER_INFO host String 255 false false docker host
94 DOCKER_INFO tlsVerify TINYINT Boolean 0 false false tls 认证{1,启用,0 未启用}
95 DOCKER_INFO status TINYINT 0 false false 状态{1,启用,0 未启用}
96 DOCKER_INFO failureMsg String 255 false false 错误消息
97 DOCKER_INFO heartbeatTimeout Integer false false 心跳超时时间
149 MONITORNOTIFYLOG createTime Long false false 异常时间
150 MONITORNOTIFYLOG title String 100 false false 异常描述
151 MONITORNOTIFYLOG content TEXT false false 异常内容
152 MONITORNOTIFYLOG status TINYINT Boolean false false 当前状态
153 MONITORNOTIFYLOG notifyStyle TINYINT false false 通知方式
154 MONITORNOTIFYLOG notifyStatus TINYINT Boolean false false 通知状态
155 MONITORNOTIFYLOG notifyObject TEXT false false 通知对象
156 MONITORNOTIFYLOG notifyError TEXT false false 通知异常内容
157 MONITORNOTIFYLOG workspaceId String 50 false false 所属工作空间
187 MONITOR_USER_OPT workspaceId String 50 true false 所属工作空间
188 MONITOR_USER_OPT name String 50 true false 名称
189 MONITOR_USER_OPT monitorUser TEXT false false 监控的人
190 MONITOR_USER_OPT status TINYINT Boolean 0 false false 启用状态{1,启用,0 未启用}
191 MONITOR_USER_OPT notifyUser TEXT false false 报警联系人
192 MONITOR_USER_OPT monitorFeature TEXT false false 监控的项目
193 MONITOR_USER_OPT monitorOpt TEXT false false 监控的项目
227 PROJECT_INFO javaCopyItemList TEXT false false javaCopyItemList
228 PROJECT_INFO token String 255 false false token
229 PROJECT_INFO runMode String 20 false false 连接方式
230 PROJECT_INFO outGivingProject TINYINT Boolean 0 false false 分发项目{1,分发,0 独立项目}
231 PROJECT_INFO javaExtDirsCp TEXT false false javaExtDirsCp
232 PROJECT_INFO sortValue Float false false 排序值
233 PROJECT_INFO triggerToken String 100 false false 触发器token
297 OUT_GIVING workspaceId String 50 true false 所属工作空间
298 OUT_GIVING name String 50 true false 名称
299 OUT_GIVING afterOpt Integer 0 false false 分发后的操作
300 OUT_GIVING clearOld TINYINT Boolean 0 false false 是否清空旧包发布
301 OUT_GIVING outGivingProject TINYINT Boolean 0 false false 是否为单独创建的分发项目
302 OUT_GIVING outGivingNodeProjectList TEXT false false 分发项目信息
303 OUT_GIVING intervalTime Integer 10 false false 分发间隔时间
304 OUT_GIVING status Integer 0 false false 状态{0: 未分发; 1: 分发中; 2: 分发结束}
305 OUT_GIVING secondaryDirectory String 200 false false 二级目录
306 OUT_GIVING uploadCloseFirst TINYINT Boolean 0 false false 是否清空旧包发布
307 USEROPERATELOGV1 id String 50 true true id 操作日志
308 USEROPERATELOGV1 ip String 80 false false 客户端IP地址
309 USEROPERATELOGV1 userId String 30 false false 操作的用户ID

View File

@ -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,文件管理中心

1 tableName name type len defaultValue notNull primaryKey comment tableComment
72 MACHINE_DOCKER_INFO groupName String 50 true false 分组名称
73 MACHINE_DOCKER_INFO name String 255 true false 名称
74 MACHINE_DOCKER_INFO host String 255 false false docker host
75 MACHINE_DOCKER_INFO tlsVerify TINYINT Boolean 0 false false tls 认证{1,启用,0 未启用}
76 MACHINE_DOCKER_INFO status TINYINT 0 false false 状态{1,启用,0 未启用}
77 MACHINE_DOCKER_INFO failureMsg String 255 false false 错误消息
78 MACHINE_DOCKER_INFO heartbeatTimeout Integer false false 心跳超时时间
90 USER_LOGIN_LOG modifyUser String 50 false false 修改人
91 USER_LOGIN_LOG ip String 80 false false 客户端IP地址
92 USER_LOGIN_LOG userAgent TEXT false false 浏览器标识
93 USER_LOGIN_LOG useMfa TINYINT Boolean false false 是否使用 mfa
94 USER_LOGIN_LOG success TINYINT Boolean false false 是否登录成功
95 USER_LOGIN_LOG operateCode TINYINT false false 操作状态码(备注码)
96 USER_LOGIN_LOG username String 50 false false 昵称
97 FILE_STORAGE id String 50 true true id 文件管理中心

View File

@ -24,6 +24,7 @@
<module>storage-module-common</module>
<module>storage-module-h2</module>
<module>storage-module-mysql</module>
<module>storage-module-postgresql</module>
</modules>
<modelVersion>4.0.0</modelVersion>
<version>2.11.3</version>

View File

@ -143,6 +143,10 @@ public class DbExtConfig implements InitializingBean {
/**
* mysql
*/
MYSQL
MYSQL,
/**
* postgresql
*/
POSTGRESQL
}
}

View File

@ -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<String> 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<Entity> 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<Entity> 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());

View File

@ -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<DbExtConfig.Mode,Dialect> DIALECT_CACHE = new ConcurrentHashMap<>();
private static volatile Wrapper currentDbFieldWrapper;
private static final ConcurrentHashMap<String,String> 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<String> 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) );
}
}

View File

@ -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;

View File

@ -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;

View File

@ -0,0 +1,39 @@
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<parent>
<artifactId>jpom-storage-module-parent</artifactId>
<groupId>org.dromara.jpom.storage-module</groupId>
<version>2.11.3</version>
</parent>
<modelVersion>4.0.0</modelVersion>
<artifactId>storage-module-postgresql</artifactId>
<properties>
<maven.compiler.source>8</maven.compiler.source>
<maven.compiler.target>8</maven.compiler.target>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
</properties>
<dependencies>
<dependency>
<groupId>org.dromara.jpom.storage-module</groupId>
<artifactId>storage-module-common</artifactId>
<version>${project.version}</version>
</dependency>
<dependency>
<groupId>org.postgresql</groupId>
<artifactId>postgresql</artifactId>
</dependency>
<dependency>
<groupId>org.dromara.jpom</groupId>
<artifactId>common</artifactId>
<scope>provided</scope>
<version>${project.version}</version>
</dependency>
</dependencies>
</project>

View File

@ -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;
}
}
}

View File

@ -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<TableViewIndexData> 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<String> 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<TableViewAlterData> 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<TableViewData> 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<String> 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);
}
}
}

View File

@ -0,0 +1 @@
org.dromara.jpom.storage.PostgresqlStorageServiceImpl

View File

@ -0,0 +1 @@
org.dromara.jpom.storage.PostgresqlTableBuilderImpl