!226 优化及添加tdengine

* 修改默认端口
* 更新dist
* 添加tdengine
* 优化任务详情
* 优化任务详情
* 添加tdengine
* 添加tdengine
* 优化任务详情
* Merge remote-tracking branch 'remote/master'
* 模式名超出部分截断问题
* 优化任务详情
* 优化任务详情
* Merge remote-tracking branch 'remote/master'
* 任务详情优化
* 回滚配置信息
* 回滚配置信息
* 任务列表优化
* 代码优化
* 代码优化
* Merge remote-tracking branch 'upstream/master'
* Merge remote-tracking branch 'upstream/master'
* 任务管理 同步数据,增加指定SQL
* 任务管理 同步数据,增加指定SQL
* Merge remote-tracking branch 'upstream/master'
* 任务管理优化 10%
* Merge remote-tracking branch 'upstream/master'
* 优化任务管理详情
* 优化
* 优化
* Merge remote-tracking branch 'remote/master'
* 左侧列表优化
* 优化数据导航
* 优化样式
* Merge remote-tracking branch 'remote/master'
* 任务管理优化 10%
* 任务管理优化 10%
* Merge remote-tracking branch 'origin/master'
* Merge remote-tracking branch 'origin/master'
* Merge remote-tracking branch 'remote/master'
* Merge remote-tracking branch 'upstream/master'
* 数据导航优化
* 数据导航优化
* 删除数据源ID
* 驱动配置类型展示优化
* sync remote
* 删除任务详情展示限制
* 调度时间 添加默认值
* Merge remote-tracking branch 'remote/master'
* 更新dbswitch-admin/resources静态资源文件
* Merge remote-tracking branch 'origin/master'
* 解决 【npm run build】 打包报错问题
This commit is contained in:
叶子.
2024-12-19 11:32:48 +00:00
committed by inrgihc
parent 774426b74e
commit 52a955d9c8
22 changed files with 1690 additions and 261 deletions

Binary file not shown.

Before

Width:  |  Height:  |  Size: 55 KiB

After

Width:  |  Height:  |  Size: 2.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.5 KiB

View File

@@ -119,6 +119,12 @@ const constantRouter = new Router({
hidden: true,
component: () => import('@/views/task/detail')
},
{
path: '/task/detail_old',
name: '查看任务(原)',
hidden: true,
component: () => import('@/views/task/detail_old')
},
{
path: '/connection/list/addDataSource1',
name: '数据源创建步骤1',

View File

@@ -49,33 +49,15 @@
min-width="10%"></el-table-column>
<el-table-column label="操作"
min-width="35%">
<template slot-scope="scope">
<el-button-group>
<el-button size="small"
type="danger"
icon="el-icon-video-play"
@click="handleTest(scope.$index, scope.row)"
round>测试
</el-button>
<el-button size="small"
type="primary"
icon="el-icon-document"
@click="handleMore(scope.$index, scope.row)"
round>详情
</el-button>
<el-button size="small"
type="warning"
icon="el-icon-edit"
@click="handleUpdate(scope.$index, scope.row)"
round>编辑
</el-button>
<el-button size="small"
type="success"
icon="el-icon-delete"
@click="handleDelete(scope.$index, scope.row)"
round>删除
</el-button>
</el-button-group>
<el-link class="btn-text" type="primary" @click="handleTest(scope.$index, scope.row)">测试</el-link>
<label class="btn-style">&nbsp;|&nbsp;</label>
<el-link class="btn-text" type="primary" @click="handleUpdate(scope.$index, scope.row)">编辑</el-link>
<label class="btn-style">&nbsp;|&nbsp;</label>
<el-link class="btn-text" type="primary" @click="handleMore(scope.$index, scope.row)">详情</el-link>
<label class="btn-style">&nbsp;|&nbsp;</label>
<el-link class="btn-text" type="primary" @click="handleDelete(scope.$index, scope.row)">删除</el-link>
</template>
</el-table-column>
</el-table>
@@ -347,4 +329,13 @@ export default {
margin-left: auto;
margin: 10px 5px;
}
.btn-style {
color: #e9e9f3;
}
.btn-text {
font-size: 12px;
color: #6873ce;
}
</style>

View File

@@ -22,7 +22,7 @@
type="primary"
size="mini"
icon="el-icon-document-add"
@click="handleCreate">创建
@click="handleCreate">创建任务
</el-button>
</el-row>
<div class="assignment-list-top">
@@ -73,7 +73,7 @@
<p>源端模式名{{ scope.row.sourceSchema }}</p>
<div slot="reference"
class="name-wrapper">
<el-tag size="medium">{{ scope.row.sourceSchema }}</el-tag>
<el-tag size="medium" class="name-wrapper-tag">{{ scope.row.sourceSchema }}</el-tag>
</div>
</el-popover>
</template>
@@ -87,7 +87,7 @@
<p>目标端模式名{{ scope.row.targetSchema }}</p>
<div slot="reference"
class="name-wrapper">
<el-tag size="medium">{{ scope.row.targetSchema }}</el-tag>
<el-tag size="medium" class="name-wrapper-tag">{{ scope.row.targetSchema }}</el-tag>
</div>
</el-popover>
</template>
@@ -125,62 +125,85 @@
<el-table-column label="操作"
min-width="35%">
<template slot-scope="scope">
<el-button-group>
<el-tooltip content="跳转到调度监控"
placement="top">
<el-button size="small"
type="primary"
icon="el-icon-timer"
v-if="scope.row.isPublished===true"
@click="schedulingLog(scope.$index, scope.row)"
round>日志
</el-button>
</el-tooltip>
<el-button size="small"
type="primary"
icon="el-icon-timer"
v-if="scope.row.isPublished===false"
@click="handlePublish(scope.$index, scope.row)"
round>启动
</el-button>
<el-button size="small"
type="info"
icon="el-icon-delete-location"
v-if="scope.row.isPublished===true"
@click="handleRetireTask(scope.$index, scope.row)"
round>停用
</el-button>
<el-tooltip content="人工触发调度执行"
placement="top">
<el-button size="small"
type="danger"
icon="el-icon-video-play"
v-if="scope.row.isPublished===true"
@click="handleRunTask(scope.$index, scope.row)"
round>执行
</el-button>
</el-tooltip>
<el-button size="small"
type="warning"
icon="el-icon-edit"
v-if="scope.row.isPublished===false"
@click="handleUpdate(scope.$index, scope.row)"
round>修改
</el-button>
<el-button size="small"
type="success"
icon="el-icon-document"
@click="handleDetail(scope.$index, scope.row)"
round>详情
</el-button>
<el-button size="small"
type="danger"
icon="el-icon-delete"
v-if="scope.row.isPublished===false"
@click="handleDelete(scope.$index, scope.row)"
round>删除
</el-button>
</el-button-group>
<el-tooltip content="跳转到监控调度" placement="top">
<el-link class="btn-text" type="primary" @click="schedulingLog(scope.$index, scope.row)">日志</el-link>
</el-tooltip>
<label class="btn-style">&nbsp;|&nbsp;</label>
<el-link class="btn-text" type="primary" v-if="scope.row.isPublished===false"
@click="handlePublish(scope.$index, scope.row)">启动</el-link>
<el-link class="btn-text" type="primary" v-if="scope.row.isPublished===true"
@click="handleRetireTask(scope.$index, scope.row)">停止</el-link>
<label v-if="scope.row.isPublished===true" class="btn-style">&nbsp;|&nbsp;</label>
<el-link class="btn-text" type="primary" v-if="scope.row.isPublished===true"
@click="handleRunTask(scope.$index, scope.row)">执行</el-link>
<label v-if="scope.row.isPublished===false" class="btn-style">&nbsp;|&nbsp;</label>
<el-link class="btn-text" type="primary" v-if="scope.row.isPublished===false"
@click="handleUpdate(scope.$index, scope.row)">修改</el-link>
<label class="btn-style">&nbsp;|&nbsp;</label>
<el-link class="btn-text" type="primary"
@click="handleDetail(scope.$index, scope.row)">详情</el-link>
<label v-if="scope.row.isPublished===false" class="btn-style">&nbsp;|&nbsp;</label>
<el-link class="btn-text" type="primary" v-if="scope.row.isPublished===false"
@click="handleDelete(scope.$index, scope.row)">删除</el-link>
<!-- <label class="btn-style">&nbsp;|&nbsp;</label>-->
<!-- <el-link class="btn-text" type="primary"-->
<!-- @click="handleDetailOld(scope.$index, scope.row)">老详情</el-link>-->
<!-- <el-button-group>-->
<!-- <el-button size="small"-->
<!-- type="primary"-->
<!-- icon="el-icon-timer"-->
<!-- v-if="scope.row.isPublished===false"-->
<!-- @click="handlePublish(scope.$index, scope.row)"-->
<!-- round>启动-->
<!-- </el-button>-->
<!-- <el-button size="small"-->
<!-- type="info"-->
<!-- icon="el-icon-delete-location"-->
<!-- v-if="scope.row.isPublished===true"-->
<!-- @click="handleRetireTask(scope.$index, scope.row)"-->
<!-- round>停用-->
<!-- </el-button>-->
<!-- <el-tooltip content="人工触发调度执行"-->
<!-- placement="top">-->
<!-- <el-button size="small"-->
<!-- type="danger"-->
<!-- icon="el-icon-video-play"-->
<!-- v-if="scope.row.isPublished===true"-->
<!-- @click="handleRunTask(scope.$index, scope.row)"-->
<!-- round>执行-->
<!-- </el-button>-->
<!-- </el-tooltip>-->
<!-- <el-button size="small"-->
<!-- type="warning"-->
<!-- icon="el-icon-edit"-->
<!-- v-if="scope.row.isPublished===false"-->
<!-- @click="handleUpdate(scope.$index, scope.row)"-->
<!-- round>修改-->
<!-- </el-button>-->
<!-- <el-button size="small"-->
<!-- type="success"-->
<!-- icon="el-icon-document"-->
<!-- @click="handleDetail(scope.$index, scope.row)"-->
<!-- round>详情-->
<!-- </el-button>-->
<!-- <el-button size="small"-->
<!-- type="danger"-->
<!-- icon="el-icon-delete"-->
<!-- v-if="scope.row.isPublished===false"-->
<!-- @click="handleDelete(scope.$index, scope.row)"-->
<!-- round>删除-->
<!-- </el-button>-->
<!-- <el-tooltip content="跳转到调度监控"-->
<!-- placement="top">-->
<!-- <el-button size="small"-->
<!-- type="primary"-->
<!-- icon="el-icon-timer"-->
<!-- @click="schedulingLog(scope.$index, scope.row)"-->
<!-- round>日志-->
<!-- </el-button>-->
<!-- </el-tooltip>-->
<!-- </el-button-group>-->
</template>
</el-table-column>
</el-table>
@@ -247,9 +270,9 @@ export default {
},
boolFormatPublish (row, column) {
if (row.isPublished === true) {
return "启动";
return "启动";
} else {
return "停止";
return "停止";
}
},
scheduleTimeFormat (row, column) {
@@ -382,7 +405,10 @@ export default {
this.$router.push('/task/create')
},
handleDetail: function (index, row) {
this.$router.push({ path: '/task/detail', query: { id: row.id } })
this.$router.push({ path: '/task/detail', query: { id: row.id, record: row } })
},
handleDetailOld: function (index, row) {
this.$router.push({ path: '/task/detail_old', query: { id: row.id } })
},
handleUpdate: function (index, row) {
this.$router.push({ path: '/task/update', query: { id: row.id } })
@@ -592,4 +618,19 @@ export default {
text-decoration: underline;
}
.btn-style {
color: #e9e9f3;
}
.btn-text {
font-size: 12px;
color: #6873ce;
}
.name-wrapper-tag {
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
max-width: 100%;
}
</style>

View File

@@ -1,168 +1,386 @@
<template>
<el-card>
<el-form :model="updateform"
status-icon
ref="updateform">
<el-descriptions size="small"
:column="1"
label-class-name="el-descriptions-item-label-class"
border>
<el-descriptions-item label="任务名称">{{ updateform.name }}</el-descriptions-item>
<el-descriptions-item label="任务描述">{{ updateform.description }}</el-descriptions-item>
<el-descriptions-item label="集成模式">
<span v-if="updateform.scheduleMode == 'MANUAL'">
<div class="app-container">
<el-tabs v-model="activeName" @tab-click="handleClick">
<el-tab-pane label="基本信息" name="first">
<el-card class="box-card">
<el-row class="row-gutter">
<el-col :span="2">
<label class="key-text">任务ID</label>
</el-col>
<el-col :span="6">
<label class="value-text">{{ infoform.id }}</label>
</el-col>
<el-col :span="2">
<label class="key-text">任务名称</label>
</el-col>
<el-col :span="6">
<label class="value-text">{{ infoform.name }}</label>
</el-col>
<el-col :span="2">
<label class="key-text">任务类型</label>
</el-col>
<el-col :span="6">
<label class="value-text">
普通任务
</label>
</el-col>
</el-row>
<el-row class="row-gutter">
<el-col :span="2">
<label class="key-text">任务状态</label>
</el-col>
<el-col :span="6">
<label class="value-text">
{{ $route.query.record.runStatus }}
</label>
</el-col>
<el-col :span="2">
<label class="key-text">运行状态</label>
</el-col>
<el-col :span="6">
<label class="value-text">
{{ $route.query.record.isPublished === true ? '启动' : '停止' }}
</label>
</el-col>
<el-col :span="2">
<label class="key-text">集成模式</label>
</el-col>
<el-col :span="6">
<label class="value-text"><span v-if="infoform.scheduleMode == 'MANUAL'">
手动
</span>
<span v-if="updateform.scheduleMode == 'SYSTEM_SCHEDULED'">
<span v-if="infoform.scheduleMode == 'SYSTEM_SCHEDULED'">
定时
</span>
</el-descriptions-item>
<el-descriptions-item v-if="updateform.scheduleMode == 'SYSTEM_SCHEDULED'"
label="CRON表达式">{{ updateform.cronExpression }}
</el-descriptions-item>
<el-descriptions-item label="源端数据源">[{{ updateform.sourceConnectionId }}]
{{ updateform.sourceConnectionName }}
</el-descriptions-item>
<el-descriptions-item label="源端schema">{{ updateform.sourceSchema }}</el-descriptions-item>
<el-descriptions-item label="源端表类型">{{ updateform.tableType }}</el-descriptions-item>
<el-descriptions-item label="源端表选择方式">
<span v-if="updateform.includeOrExclude == 'INCLUDE'">
包含表
</span>
<span v-if="updateform.includeOrExclude == 'EXCLUDE'">
排除表
</span>
</el-descriptions-item>
<el-descriptions-item label="源端表名列表">
<span
v-show="updateform.includeOrExclude == 'INCLUDE' && (!updateform.sourceTables || updateform.sourceTables.length==0)"><b>所有物理表</b></span>
<p v-for="item in updateform.sourceTables"
v-bind:key="item">{{ item }}</p>
</el-descriptions-item>
<el-descriptions-item label="目地端数据源">[{{ updateform.targetConnectionId }}]
{{ updateform.targetConnectionName }}
</el-descriptions-item>
<el-descriptions-item label="目地端schema">{{ updateform.targetSchema }}</el-descriptions-item>
<el-descriptions-item label="自动同步模式">
<span v-if="updateform.autoSyncMode == 2">
目标端建表并同步数据
</span>
<span v-if="updateform.autoSyncMode == 1">
目标端只创建物理表
</span>
<span v-if="updateform.autoSyncMode == 0">
目标端只同步表里数据
</span>
</el-descriptions-item>
<el-descriptions-item label="建表字段自增"
v-if=" updateform.autoSyncMode!==0 ">{{ updateform.targetAutoIncrement }}
</el-descriptions-item>
<el-descriptions-item label="表名转换方法"
v-if=" updateform.autoSyncMode!==0 ">
<span v-if="updateform.tableNameCase == 'NONE'">
无转换
</span>
<span v-if="updateform.tableNameCase == 'UPPER'">
转大写
</span>
<span v-if="updateform.tableNameCase == 'LOWER'">
转小写
</span>
<span v-if="updateform.tableNameCase == 'CAMEL'">
下划线转驼峰
</span>
<span v-if="updateform.tableNameCase == 'SNAKE'">
驼峰转下换线
</span>
</el-descriptions-item>
<el-descriptions-item label="列名转换方法"
v-if=" updateform.autoSyncMode!==0 ">
<span v-if="updateform.columnNameCase == 'NONE'">
无转换
</span>
<span v-if="updateform.columnNameCase == 'UPPER'">
转大写
</span>
<span v-if="updateform.columnNameCase == 'LOWER'">
转小写
</span>
<span v-if="updateform.columnNameCase == 'CAMEL'">
下划线转驼峰
</span>
<span v-if="updateform.columnNameCase == 'SNAKE'">
驼峰转下换线
</span>
</el-descriptions-item>
<el-descriptions-item label="数据批次大小"
v-if=" updateform.autoSyncMode!==1 ">{{ updateform.batchSize }}
</el-descriptions-item>
<el-descriptions-item label="通道队列大小"
v-if=" updateform.autoSyncMode!==1 ">{{ updateform.channelSize }}
</el-descriptions-item>
<el-descriptions-item label="同步操作方法"
v-if=" updateform.autoSyncMode!==1 ">{{ updateform.targetSyncOption }}
</el-descriptions-item>
<el-descriptions-item label="同步前置执行SQL脚本"
v-if=" updateform.autoSyncMode!==1 ">
<span v-show="!updateform.beforeSqlScripts || updateform.beforeSqlScripts.length==0">[SQL脚本内容为空]</span>
<span
v-show="updateform.beforeSqlScripts && updateform.beforeSqlScripts.length>0">{{ updateform.beforeSqlScripts }}</span>
</el-descriptions-item>
<el-descriptions-item label="同步后置执行SQL脚本"
v-if=" updateform.autoSyncMode!==1 ">
<span v-show="!updateform.afterSqlScripts || updateform.afterSqlScripts.length==0">[SQL脚本内容为空]</span>
<span
v-show="updateform.afterSqlScripts && updateform.afterSqlScripts.length>0">{{ updateform.afterSqlScripts }}</span>
</el-descriptions-item>
<el-descriptions-item label="表名映射规则">
<span v-show="!updateform.tableNameMapper || updateform.tableNameMapper.length==0">[映射关系为空]</span>
<table v-if="updateform.tableNameMapper && updateform.tableNameMapper.length>0"
class="name-mapper-table">
<tr>
<th>表名匹配的正则名</th>
<th>替换的目标值</th>
</tr>
<tr v-for='(item,index) in updateform.tableNameMapper'
:key="index">
<td>{{ item['fromPattern'] }}</td>
<td>{{ item['toValue'] }}</td>
</tr>
</table>
</el-descriptions-item>
<el-descriptions-item label="字段名映射规则">
<span v-show="!updateform.columnNameMapper || updateform.columnNameMapper.length==0">[映射关系为空]</span>
<table v-if="updateform.columnNameMapper && updateform.columnNameMapper.length>0"
class="name-mapper-table">
<tr>
<th>字段名匹配的正则名</th>
<th>替换的目标值</th>
</tr>
<tr v-for='(item,index) in updateform.columnNameMapper'
:key="index">
<td>{{ item['fromPattern'] }}</td>
<td>{{ item['toValue'] }}</td>
</tr>
</table>
</el-descriptions-item>
</el-descriptions>
</span></label>
</el-col>
</el-row>
<el-row class="row-gutter">
<el-col :span="2">
<label class="key-text">调度计划</label>
</el-col>
<el-col :span="6">
<label class="value-text">
<span v-if="infoform.scheduleMode == 'MANUAL'">
--
</span>
<span v-if="infoform.scheduleMode == 'SYSTEM_SCHEDULED'">
{{ infoform.cronExpression }}
</span>
</label>
</el-col>
<el-col :span="2">
<label class="key-text">开始调度时间</label>
</el-col>
<el-col :span="6">
<label class="value-text">{{ $route.query.record.scheduleTime }}</label>
</el-col>
<el-col :span="2">
<label class="key-text">创建时间</label>
</el-col>
<el-col :span="6">
<label class="value-text">{{ $route.query.record.createTime }}</label>
</el-col>
</el-row>
<el-row class="row-gutter">
<el-col :span="2">
<label class="key-text">描述</label>
</el-col>
<el-col :span="6">
<label class="value-text">{{ infoform.description }}</label>
</el-col>
<el-col :span="2">
<label class="key-text">任务标签</label>
</el-col>
<el-col :span="6">
<label class="value-text">--</label>
</el-col>
<el-col :span="8">
</el-col>
</el-row>
</el-card>
<div class="common-box">
<div class="datainfo">
<div class="source">
<div class="head">
<div class="head-img">
<el-image
style="width: 60px; height: 60px"
:src="require('@/assets/icons/' + $route.query.record.sourceType +'.png')"
:fit="fit"></el-image>
</div>
<div class="head-text">
<div class="title">{{ infoform.sourceConnectionName }}</div>
<div class="sub-title">源端数据源</div>
</div>
</div>
<div class="body">
<el-row class="row-gutter">
<el-col :span="8">
<label class="key-text">源端schema</label>
</el-col>
<el-col :span="16">
<label class="value-text">{{ infoform.sourceSchema }}</label>
</el-col>
</el-row>
<el-row class="row-gutter">
<el-col :span="8">
<label class="key-text">源端表类型</label>
</el-col>
<el-col :span="16">
<label class="value-text">{{ infoform.tableType }}</label>
</el-col>
</el-row>
<el-row class="row-gutter">
<el-col :span="8">
<label class="key-text">源端表选择方式</label>
</el-col>
<el-col :span="16">
<label class="value-text">{{
infoform.includeOrExclude === 'INCLUDE' ? '包含表' : '排除表'
}}</label>
</el-col>
</el-row>
<el-row class="row-gutter">
<el-col :span="8">
<label class="key-text">源端表名列表</label>
</el-col>
<el-col :span="16">
<label class="value-text" v-for="item in infoform.sourceTables"
v-bind:key="item">{{ item }}</label>
</el-col>
</el-row>
</div>
</div>
<div class="target">
<div class="head">
<div class="head-img">
<el-image
style="width: 60px; height: 60px"
:src="require('@/assets/icons/' + $route.query.record.targetType +'.png')"
:fit="fit"></el-image>
</div>
<div class="head-text">
<div class="title">{{ infoform.targetConnectionName }}</div>
<div class="sub-title">目标端数据源</div>
</div>
</div>
<div class="body">
<el-row class="row-gutter">
<el-col :span="8">
<label class="key-text">目地端schema</label>
</el-col>
<el-col :span="16">
<label class="value-text">{{ infoform.targetSchema }}</label>
</el-col>
</el-row>
<el-row class="row-gutter">
<el-col :span="8">
<label class="key-text">自动同步模式</label>
</el-col>
<el-col :span="16">
<span class="value-text" v-if="infoform.autoSyncMode == 2">
目标端建表并同步数据
</span>
<span class="value-text" v-if="infoform.autoSyncMode == 1">
目标端只创建物理表
</span>
<span class="value-text" v-if="infoform.autoSyncMode == 0">
目标端只同步表里数据
</span>
</el-col>
</el-row>
<el-row class="row-gutter">
<el-col :span="8">
<label class="key-text">建表字段自增</label>
</el-col>
<el-col :span="16">
<label class="value-text"
v-if=" infoform.autoSyncMode!==0 ">{{ infoform.targetAutoIncrement }}
</label>
</el-col>
</el-row>
<el-row class="row-gutter">
<el-col :span="8">
<label class="key-text">表名转换方法</label>
</el-col>
<el-col :span="16">
<label class="value-text" v-if=" infoform.autoSyncMode!==0 ">
<span v-if="infoform.tableNameCase == 'NONE'">
无转换
</span>
<span v-if="infoform.tableNameCase == 'UPPER'">
转大写
</span>
<span v-if="infoform.tableNameCase == 'LOWER'">
转小写
</span>
<span v-if="infoform.tableNameCase == 'CAMEL'">
下划线转驼峰
</span>
<span v-if="infoform.tableNameCase == 'SNAKE'">
驼峰转下换线
</span>
</label>
</el-col>
</el-row>
<el-row class="row-gutter">
<el-col :span="8">
<label class="key-text">列名转换方法</label>
</el-col>
<el-col :span="16">
<label class="value-text" v-if=" infoform.autoSyncMode!==0 ">
<span v-if="infoform.columnNameCase == 'NONE'">
无转换
</span>
<span v-if="infoform.columnNameCase == 'UPPER'">
转大写
</span>
<span v-if="infoform.columnNameCase == 'LOWER'">
转小写
</span>
<span v-if="infoform.columnNameCase == 'CAMEL'">
下划线转驼峰
</span>
<span v-if="infoform.columnNameCase == 'SNAKE'">
驼峰转下换线
</span>
</label>
</el-col>
</el-row>
<el-row class="row-gutter">
<el-col :span="8">
<label class="key-text">数据批次大小</label>
</el-col>
<el-col :span="16">
<label class="value-text"
v-if=" infoform.autoSyncMode!==1 ">{{ infoform.batchSize }}
</label>
</el-col>
</el-row>
<el-row class="row-gutter">
<el-col :span="8">
<label class="key-text">通道队列大小</label>
</el-col>
<el-col :span="16">
<label class="value-text"
v-if=" infoform.autoSyncMode!==1 ">{{ infoform.channelSize }}
</label>
</el-col>
</el-row>
<el-row class="row-gutter">
<el-col :span="8">
<label class="key-text">同步操作方法</label>
</el-col>
<el-col :span="16">
<label class="value-text"
v-if="infoform.autoSyncMode!==1 ">{{ infoform.targetSyncOption }}
</label>
</el-col>
</el-row>
<el-row class="row-gutter">
<el-col :span="8">
<label class="key-text">同步前置执行SQL脚本</label>
</el-col>
<el-col :span="16">
<label class="value-text"
v-if=" infoform.autoSyncMode!==1 ">
<span
v-show="!infoform.beforeSqlScripts || infoform.beforeSqlScripts.length==0">[SQL脚本内容为空]</span>
<span
v-show="infoform.beforeSqlScripts && infoform.beforeSqlScripts.length>0">{{
infoform.beforeSqlScripts
}}</span>
</label>
</el-col>
</el-row>
<el-row class="row-gutter">
<el-col :span="8">
<label class="key-text">同步后置执行SQL脚本</label>
</el-col>
<el-col :span="16">
<label class="value-text"
v-if=" infoform.autoSyncMode!==1 ">
<span
v-show="!infoform.afterSqlScripts || infoform.afterSqlScripts.length==0">[SQL脚本内容为空]</span>
<span
v-show="infoform.afterSqlScripts && infoform.afterSqlScripts.length>0">{{
infoform.afterSqlScripts
}}</span>
</label>
</el-col>
</el-row>
</div>
</div>
</div>
<div class="mapper">
<div class="table-left">
<div>表名映射规则</div>
<div>
<el-table
:data="infoform.tableNameMapper"
style="width: 100%"
:row-class-name="tableRowClassName">
<el-table-column
prop="fromPattern"
label="表名匹配的正则名">
</el-table-column>
<el-table-column
prop="toValue"
label="替换的目标值">
</el-table-column>
</el-table>
</div>
</div>
<div class="table-right">
<div>字段名映射规则</div>
<div>
<el-table
:data="infoform.columnNameMapper"
style="width: 100%"
:row-class-name="tableRowClassName">
<el-table-column
prop="fromPattern"
label="表名匹配的正则名">
</el-table-column>
<el-table-column
prop="toValue"
label="替换的目标值">
</el-table-column>
</el-table>
</div>
</div>
</div>
</div>
</el-tab-pane>
<!-- <el-tab-pane label="调度日志" name="second">调度日志22</el-tab-pane>-->
<!-- <el-tab-pane label="操作日志" name="third">操作日志33</el-tab-pane>-->
</el-tabs>
</div>
</el-form>
<el-button type="primary"
size="mini"
icon="el-icon-arrow-left"
@click="handleGoBack"
style="margin: 12px 0px 20px;float: right">
返回
</el-button>
</el-card>
</template>
<script>
export default {
data() {
return {
updateform: {
activeName: 'first',
infoform: {
id: 0,
name: "",
description: "",
@@ -170,6 +388,7 @@ export default {
cronExpression: "",
sourceConnectionId: '请选择',
sourceSchema: "",
runStatus: "",
tableType: "TABLE",
includeOrExclude: "",
sourceTables: [],
@@ -210,7 +429,8 @@ export default {
} else {
varAutoSyncMode = 2;
}
this.updateform = {
debugger
this.infoform = {
id: detail.id,
name: detail.name,
description: detail.description,
@@ -239,9 +459,9 @@ export default {
beforeSqlScripts: detail.configuration.beforeSqlScripts,
afterSqlScripts: detail.configuration.afterSqlScripts,
}
this.selectChangedSourceConnection(this.updateform.sourceConnectionId)
this.selectUpdateChangedSourceSchema(this.updateform.sourceSchema)
this.selectChangedTargetConnection(this.updateform.targetConnectionId)
this.selectChangedSourceConnection(this.infoform.sourceConnectionId)
this.selectUpdateChangedSourceSchema(this.infoform.sourceSchema)
this.selectChangedTargetConnection(this.infoform.targetConnectionId)
} else {
if (res.data.message) {
alert("查询任务失败," + res.data.message);
@@ -269,9 +489,9 @@ export default {
},
selectUpdateChangedSourceSchema: function (value) {
this.sourceSchemaTables = [];
if ('TABLE' === this.updateform.tableType) {
if ('TABLE' === this.infoform.tableType) {
this.$http.get(
"/dbswitch/admin/api/v1/connection/tables/get/" + this.updateform.sourceConnectionId + "?schema=" + value
"/dbswitch/admin/api/v1/connection/tables/get/" + this.infoform.sourceConnectionId + "?schema=" + value
).then(res => {
if (0 === res.data.code) {
this.sourceSchemaTables = res.data.data;
@@ -282,7 +502,7 @@ export default {
});
} else {
this.$http.get(
"/dbswitch/admin/api/v1/connection/views/get/" + this.updateform.sourceConnectionId + "?schema=" + value
"/dbswitch/admin/api/v1/connection/views/get/" + this.infoform.sourceConnectionId + "?schema=" + value
).then(res => {
if (0 === res.data.code) {
this.sourceSchemaTables = res.data.data;
@@ -314,25 +534,163 @@ export default {
handleGoBack() {
this.$router.go(-1);
},
handleClick(tab, event) {
if (tab.index !== 0) {
this.$message({
message: '功能暂未开放,敬请期待!',
center: true
});
}
},
tableRowClassName({row, rowIndex}) {
if (rowIndex === 1) {
return 'warning-row';
} else if (rowIndex === 3) {
return 'success-row';
}
return '';
}
},
created() {
this.loadAssignmentDetail();
console.log(this.$route.query.record)
},
}
</script>
<style scoped>
.el-card {
width: 100%;
height: 100%;
overflow: auto;
.app-container {
background-color: white;
height: 40px;
}
.el-descriptions__body
.el-descriptions__table
.el-descriptions-row
.el-descriptions-item__label {
min-width: 20px;
max-width: 60px;
/deep/ .el-tabs {
padding-left: 24px;
}
/deep/ .el-tabs__content {
width: 98.5%;
}
/deep/ .el-tabs__nav-wrap:after {
width: 270px;
}
.box-card {
width: 100%;
}
.row-gutter {
margin-bottom: 12px;
}
.common-box {
margin-top: 16px;
width: 100%;
display: flex;
flex-direction: column;
row-gap: 16px;
padding-bottom: 24px;
.datainfo {
display: flex;
justify-content: center;
align-items: center;
width: 100%;
gap: 24px;
.source {
flex: 1;
border-radius: 4px;
}
.target {
flex: 1;
border-radius: 4px;
}
.head {
display: flex;
height: 72px;
align-items: center;
background-color: #F7FBFF;
width: 100%;
justify-content: space-between;
.head-img {
width: 40px;
padding-left: 24px;
}
.head-text {
flex-grow: 1;
padding-left: 48px;
.title {
font-size: 14px;
color: #0051FF;
font-weight: bold;
}
.sub-title {
margin-top: 4px;
font-size: 12px;
color: #7D7D7D;
}
}
}
.body {
background-color: white;
padding: 24px;
min-height: 328px;
}
}
.mapper {
display: flex;
justify-content: center;
align-items: center;
width: 100%;
gap: 24px;
.table-left {
background-color: white;
width: 100%;
padding: 24px;
}
.table-right {
background-color: white;
width: 100%;
padding: 24px;
}
}
}
</style>
<style>
.viewer-container {
background-color: white;
padding: 0px !important;
}
.key-text {
color: #7D7D7D;
font-size: 14px;
}
.value-text {
margin-left: 8px;
font-size: 12px;
color: #000000;
}
.el-table .warning-row {
background: oldlace;
}
.el-table .success-row {
background: #f0f9eb;
}
</style>

View File

@@ -0,0 +1,338 @@
<template>
<el-card>
<el-form :model="updateform"
status-icon
ref="updateform">
<el-descriptions size="small"
:column="1"
label-class-name="el-descriptions-item-label-class"
border>
<el-descriptions-item label="任务名称">{{ updateform.name }}</el-descriptions-item>
<el-descriptions-item label="任务描述">{{ updateform.description }}</el-descriptions-item>
<el-descriptions-item label="集成模式">
<span v-if="updateform.scheduleMode == 'MANUAL'">
手动
</span>
<span v-if="updateform.scheduleMode == 'SYSTEM_SCHEDULED'">
定时
</span>
</el-descriptions-item>
<el-descriptions-item v-if="updateform.scheduleMode == 'SYSTEM_SCHEDULED'"
label="CRON表达式">{{ updateform.cronExpression }}
</el-descriptions-item>
<el-descriptions-item label="源端数据源">[{{ updateform.sourceConnectionId }}]
{{ updateform.sourceConnectionName }}
</el-descriptions-item>
<el-descriptions-item label="源端schema">{{ updateform.sourceSchema }}</el-descriptions-item>
<el-descriptions-item label="源端表类型">{{ updateform.tableType }}</el-descriptions-item>
<el-descriptions-item label="源端表选择方式">
<span v-if="updateform.includeOrExclude == 'INCLUDE'">
包含表
</span>
<span v-if="updateform.includeOrExclude == 'EXCLUDE'">
排除表
</span>
</el-descriptions-item>
<el-descriptions-item label="源端表名列表">
<span
v-show="updateform.includeOrExclude == 'INCLUDE' && (!updateform.sourceTables || updateform.sourceTables.length==0)"><b>所有物理表</b></span>
<p v-for="item in updateform.sourceTables"
v-bind:key="item">{{ item }}</p>
</el-descriptions-item>
<el-descriptions-item label="目地端数据源">[{{ updateform.targetConnectionId }}]
{{ updateform.targetConnectionName }}
</el-descriptions-item>
<el-descriptions-item label="目地端schema">{{ updateform.targetSchema }}</el-descriptions-item>
<el-descriptions-item label="自动同步模式">
<span v-if="updateform.autoSyncMode == 2">
目标端建表并同步数据
</span>
<span v-if="updateform.autoSyncMode == 1">
目标端只创建物理表
</span>
<span v-if="updateform.autoSyncMode == 0">
目标端只同步表里数据
</span>
</el-descriptions-item>
<el-descriptions-item label="建表字段自增"
v-if=" updateform.autoSyncMode!==0 ">{{ updateform.targetAutoIncrement }}
</el-descriptions-item>
<el-descriptions-item label="表名转换方法"
v-if=" updateform.autoSyncMode!==0 ">
<span v-if="updateform.tableNameCase == 'NONE'">
无转换
</span>
<span v-if="updateform.tableNameCase == 'UPPER'">
转大写
</span>
<span v-if="updateform.tableNameCase == 'LOWER'">
转小写
</span>
<span v-if="updateform.tableNameCase == 'CAMEL'">
下划线转驼峰
</span>
<span v-if="updateform.tableNameCase == 'SNAKE'">
驼峰转下换线
</span>
</el-descriptions-item>
<el-descriptions-item label="列名转换方法"
v-if=" updateform.autoSyncMode!==0 ">
<span v-if="updateform.columnNameCase == 'NONE'">
无转换
</span>
<span v-if="updateform.columnNameCase == 'UPPER'">
转大写
</span>
<span v-if="updateform.columnNameCase == 'LOWER'">
转小写
</span>
<span v-if="updateform.columnNameCase == 'CAMEL'">
下划线转驼峰
</span>
<span v-if="updateform.columnNameCase == 'SNAKE'">
驼峰转下换线
</span>
</el-descriptions-item>
<el-descriptions-item label="数据批次大小"
v-if=" updateform.autoSyncMode!==1 ">{{ updateform.batchSize }}
</el-descriptions-item>
<el-descriptions-item label="通道队列大小"
v-if=" updateform.autoSyncMode!==1 ">{{ updateform.channelSize }}
</el-descriptions-item>
<el-descriptions-item label="同步操作方法"
v-if=" updateform.autoSyncMode!==1 ">{{ updateform.targetSyncOption }}
</el-descriptions-item>
<el-descriptions-item label="同步前置执行SQL脚本"
v-if=" updateform.autoSyncMode!==1 ">
<span v-show="!updateform.beforeSqlScripts || updateform.beforeSqlScripts.length==0">[SQL脚本内容为空]</span>
<span
v-show="updateform.beforeSqlScripts && updateform.beforeSqlScripts.length>0">{{ updateform.beforeSqlScripts }}</span>
</el-descriptions-item>
<el-descriptions-item label="同步后置执行SQL脚本"
v-if=" updateform.autoSyncMode!==1 ">
<span v-show="!updateform.afterSqlScripts || updateform.afterSqlScripts.length==0">[SQL脚本内容为空]</span>
<span
v-show="updateform.afterSqlScripts && updateform.afterSqlScripts.length>0">{{ updateform.afterSqlScripts }}</span>
</el-descriptions-item>
<el-descriptions-item label="表名映射规则">
<span v-show="!updateform.tableNameMapper || updateform.tableNameMapper.length==0">[映射关系为空]</span>
<table v-if="updateform.tableNameMapper && updateform.tableNameMapper.length>0"
class="name-mapper-table">
<tr>
<th>表名匹配的正则名</th>
<th>替换的目标值</th>
</tr>
<tr v-for='(item,index) in updateform.tableNameMapper'
:key="index">
<td>{{ item['fromPattern'] }}</td>
<td>{{ item['toValue'] }}</td>
</tr>
</table>
</el-descriptions-item>
<el-descriptions-item label="字段名映射规则">
<span v-show="!updateform.columnNameMapper || updateform.columnNameMapper.length==0">[映射关系为空]</span>
<table v-if="updateform.columnNameMapper && updateform.columnNameMapper.length>0"
class="name-mapper-table">
<tr>
<th>字段名匹配的正则名</th>
<th>替换的目标值</th>
</tr>
<tr v-for='(item,index) in updateform.columnNameMapper'
:key="index">
<td>{{ item['fromPattern'] }}</td>
<td>{{ item['toValue'] }}</td>
</tr>
</table>
</el-descriptions-item>
</el-descriptions>
</el-form>
<el-button type="primary"
size="mini"
icon="el-icon-arrow-left"
@click="handleGoBack"
style="margin: 12px 0px 20px;float: right">
返回
</el-button>
</el-card>
</template>
<script>
export default {
data() {
return {
updateform: {
id: 0,
name: "",
description: "",
scheduleMode: "MANUAL",
cronExpression: "",
sourceConnectionId: '请选择',
sourceSchema: "",
tableType: "TABLE",
includeOrExclude: "",
sourceTables: [],
tableNameMapper: [],
columnNameMapper: [],
tableNameCase: 'NONE',
columnNameCase: 'NONE',
targetConnectionId: '请选择',
targetDropTable: true,
targetOnlyCreate: false,
autoSyncMode: 2,
targetSchema: "",
batchSize: 5000,
channelSize: 100,
targetSyncOption: 'INSERT_UPDATE_DELETE',
beforeSqlScripts: '',
afterSqlScripts: '',
},
sourceConnection: {},
targetConnection: {},
sourceConnectionSchemas: [],
sourceSchemaTables: [],
targetConnectionSchemas: [],
}
},
methods: {
loadAssignmentDetail: function () {
this.$http.get(
"/dbswitch/admin/api/v1/assignment/detail/id/" + this.$route.query.id
).then(res => {
if (0 === res.data.code) {
let detail = res.data.data;
let varAutoSyncMode = 2;
if (detail.configuration.targetDropTable && detail.configuration.targetOnlyCreate) {
varAutoSyncMode = 1;
} else if (!detail.configuration.targetDropTable && !detail.configuration.targetOnlyCreate) {
varAutoSyncMode = 0;
} else {
varAutoSyncMode = 2;
}
this.updateform = {
id: detail.id,
name: detail.name,
description: detail.description,
scheduleMode: detail.scheduleMode,
cronExpression: detail.cronExpression,
sourceConnectionId: detail.configuration.sourceConnectionId,
sourceConnectionName: detail.configuration.sourceConnectionName,
sourceSchema: detail.configuration.sourceSchema,
tableType: detail.configuration.tableType,
includeOrExclude: detail.configuration.includeOrExclude,
sourceTables: detail.configuration.sourceTables,
tableNameMapper: detail.configuration.tableNameMapper,
columnNameMapper: detail.configuration.columnNameMapper,
tableNameCase: detail.configuration.tableNameCase,
columnNameCase: detail.configuration.columnNameCase,
targetConnectionId: detail.configuration.targetConnectionId,
targetConnectionName: detail.configuration.targetConnectionName,
targetDropTable: detail.configuration.targetDropTable,
targetOnlyCreate: detail.configuration.targetOnlyCreate,
targetAutoIncrement: detail.configuration.targetAutoIncrement,
autoSyncMode: varAutoSyncMode,
targetSchema: detail.configuration.targetSchema,
batchSize: detail.configuration.batchSize,
channelSize: detail.configuration.channelSize,
targetSyncOption: detail.configuration.targetSyncOption,
beforeSqlScripts: detail.configuration.beforeSqlScripts,
afterSqlScripts: detail.configuration.afterSqlScripts,
}
this.selectChangedSourceConnection(this.updateform.sourceConnectionId)
this.selectUpdateChangedSourceSchema(this.updateform.sourceSchema)
this.selectChangedTargetConnection(this.updateform.targetConnectionId)
} else {
if (res.data.message) {
alert("查询任务失败," + res.data.message);
}
}
});
},
selectChangedSourceConnection: function (value) {
this.sourceConnection = this.connectionNameList.find(
(item) => {
return item.id === value;
});
this.sourceConnectionSchemas = [];
this.$http.get(
"/dbswitch/admin/api/v1/connection/schemas/get/" + value
).then(res => {
if (0 === res.data.code) {
this.sourceConnectionSchemas = res.data.data;
} else {
this.$message.error("查询来源端数据库的Schema失败," + res.data.message);
this.sourceConnectionSchemas = [];
}
});
},
selectUpdateChangedSourceSchema: function (value) {
this.sourceSchemaTables = [];
if ('TABLE' === this.updateform.tableType) {
this.$http.get(
"/dbswitch/admin/api/v1/connection/tables/get/" + this.updateform.sourceConnectionId + "?schema=" + value
).then(res => {
if (0 === res.data.code) {
this.sourceSchemaTables = res.data.data;
} else {
this.$message.error("查询来源端数据库在指定Schema下的物理表列表失败," + res.data.message);
this.sourceSchemaTables = [];
}
});
} else {
this.$http.get(
"/dbswitch/admin/api/v1/connection/views/get/" + this.updateform.sourceConnectionId + "?schema=" + value
).then(res => {
if (0 === res.data.code) {
this.sourceSchemaTables = res.data.data;
} else {
this.$message.error("查询来源端数据库在指定Schema下的视图表列表失败," + res.data.message);
this.sourceSchemaTables = [];
}
});
}
},
selectChangedTargetConnection: function (value) {
this.targetConnection = this.connectionNameList.find(
(item) => {
return item.id === value;
});
this.targetConnectionSchemas = [];
this.$http.get(
"/dbswitch/admin/api/v1/connection/schemas/get/" + value
).then(res => {
if (0 === res.data.code) {
this.targetConnectionSchemas = res.data.data;
} else {
this.$message.error("查询目的端数据库的Schema失败," + res.data.message);
this.targetConnectionSchemas = [];
}
});
},
handleGoBack() {
this.$router.go(-1);
},
},
created() {
this.loadAssignmentDetail();
},
}
</script>
<style scoped>
.el-card {
width: 100%;
height: 100%;
overflow: auto;
}
.el-descriptions__body
.el-descriptions__table
.el-descriptions-row
.el-descriptions-item__label {
min-width: 20px;
max-width: 60px;
}
</style>

View File

@@ -23,4 +23,4 @@ mybatis:
dbswitch:
configuration:
drivers-base-path: ${APP_DRIVERS_PATH}
drivers-base-path: ${APP_DRIVERS_PATH:D:\lzm\otherproject\dbswitch\drivers}

View File

@@ -1 +1 @@
<!DOCTYPE html><html><head><meta charset=utf-8><meta name=viewport content="width=device-width,initial-scale=1"><title>异构数据迁移工具</title><link href=/static/css/app.b7354ab5f63d44a80b8f75636e1e0abc.css rel=stylesheet></head><body><div id=app></div><script type=text/javascript src=/static/js/manifest.78b2138895a8f35eeff6.js></script><script type=text/javascript src=/static/js/vendor.8200341f98478c8f7552.js></script><script type=text/javascript src=/static/js/app.df5c26834a81c347a509.js></script></body></html>
<!DOCTYPE html><html><head><meta charset=utf-8><meta name=viewport content="width=device-width,initial-scale=1"><title>异构数据迁移工具</title><link href=/static/css/app.41fc9337aa8988f4a077ab190bbfa780.css rel=stylesheet></head><body><div id=app></div><script type=text/javascript src=/static/js/manifest.9eb26c1e5b920ae4f10a.js></script><script type=text/javascript src=/static/js/vendor.f606e4b64a7e8691aabf.js></script><script type=text/javascript src=/static/js/app.5fea471d29a49cd53a96.js></script></body></html>

View File

@@ -223,6 +223,15 @@ public enum ProductTypeEnum {
new String[]{"jdbc:oceanbase://{host}[:{port}]/[{database}][\\?{params}]"},
"jdbc:oceanbase://127.0.0.1:2881/test?pool=false&useUnicode=true&characterEncoding=utf-8&useSSL=false"),
/**
* TDengine 数据库类型
*/
TDENGINE(23, "", "TDengine", "com.taosdata.jdbc.rs.RestfulDriver", 6041,
"/* ping */ SELECT 1",
"jdbc:TAOS-RS://",
new String[]{"jdbc:TAOS-RS://{host}[:{port}]/[{database}][\\?{params}]"},
"jdbc:TAOS-RS://127.0.0.1:6041/test?charset=UTF-8&locale=en_US.UTF-8&timezone=UTC+8"),
;
private int id;

View File

@@ -0,0 +1,28 @@
<?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>dbswitch-product</artifactId>
<groupId>org.dromara.dbswitch</groupId>
<version>2.0.0</version>
</parent>
<modelVersion>4.0.0</modelVersion>
<artifactId>dbswitch-product-tdengine</artifactId>
<dependencies>
<dependency>
<groupId>org.dromara.dbswitch</groupId>
<artifactId>dbswitch-common</artifactId>
<version>${project.version}</version>
</dependency>
<dependency>
<groupId>org.dromara.dbswitch</groupId>
<artifactId>dbswitch-core</artifactId>
<version>${project.version}</version>
</dependency>
</dependencies>
</project>

View File

@@ -0,0 +1,29 @@
// Copyright tang. All rights reserved.
// https://gitee.com/inrgihc/dbswitch
//
// Use of this source code is governed by a BSD-style license
//
// Author: Li ZeMin (2413957313@qq.com)
// Date : 2024/12/16
// Location: nanjing , china
/////////////////////////////////////////////////////////////
package org.dromara.dbswitch.product.tdengine;
import org.dromara.dbswitch.core.provider.ProductFactoryProvider;
import org.dromara.dbswitch.core.provider.write.AutoCastTableDataWriteProvider;
import org.springframework.transaction.TransactionDefinition;
import org.springframework.transaction.support.DefaultTransactionDefinition;
public class TDengineTableDataWriteProvider extends AutoCastTableDataWriteProvider {
public TDengineTableDataWriteProvider(ProductFactoryProvider factoryProvider) {
super(factoryProvider);
}
@Override
protected TransactionDefinition getDefaultTransactionDefinition() {
DefaultTransactionDefinition definition = new DefaultTransactionDefinition();
return definition;
}
}

View File

@@ -0,0 +1,60 @@
// Copyright tang. All rights reserved.
// https://gitee.com/inrgihc/dbswitch
//
// Use of this source code is governed by a BSD-style license
//
// Author: Li ZeMin (2413957313@qq.com)
// Date : 2024/12/16
// Location: nanjing , china
/////////////////////////////////////////////////////////////
package org.dromara.dbswitch.product.tdengine;
import javax.sql.DataSource;
import org.dromara.dbswitch.common.type.ProductTypeEnum;
import org.dromara.dbswitch.core.annotation.Product;
import org.dromara.dbswitch.core.features.ProductFeatures;
import org.dromara.dbswitch.core.provider.AbstractFactoryProvider;
import org.dromara.dbswitch.core.provider.manage.TableManageProvider;
import org.dromara.dbswitch.core.provider.meta.MetadataProvider;
import org.dromara.dbswitch.core.provider.query.TableDataQueryProvider;
import org.dromara.dbswitch.core.provider.sync.TableDataSynchronizeProvider;
import org.dromara.dbswitch.core.provider.write.TableDataWriteProvider;
@Product(ProductTypeEnum.TDENGINE)
public class TdengineFactoryProvider extends AbstractFactoryProvider {
public TdengineFactoryProvider(DataSource dataSource) {
super(dataSource);
}
@Override
public ProductFeatures getProductFeatures() {
return new TdengineFeatures();
}
@Override
public MetadataProvider createMetadataQueryProvider() {
return new TdengineMetadataQueryProvider(this);
}
@Override
public TableDataWriteProvider createTableDataWriteProvider(boolean useInsert) {
return new TDengineTableDataWriteProvider(this);
}
@Override
public TableDataSynchronizeProvider createTableDataSynchronizeProvider() {
return new TdengineTableSynchronizer(this);
}
@Override
public TableDataQueryProvider createTableDataQueryProvider() {
return new TdengineTableDataQueryProvider(this);
}
@Override
public TableManageProvider createTableManageProvider() {
return new TdengineTableManageProvider(this);
}
}

View File

@@ -0,0 +1,21 @@
// Copyright tang. All rights reserved.
// https://gitee.com/inrgihc/dbswitch
//
// Use of this source code is governed by a BSD-style license
//
// Author: Li ZeMin (2413957313@qq.com)
// Date : 2024/12/16
// Location: nanjing , china
/////////////////////////////////////////////////////////////
package org.dromara.dbswitch.product.tdengine;
import org.dromara.dbswitch.core.features.ProductFeatures;
public class TdengineFeatures implements ProductFeatures {
@Override
public int convertFetchSize(int fetchSize) {
return fetchSize;
}
}

View File

@@ -0,0 +1,403 @@
// Copyright tang. All rights reserved.
// https://gitee.com/inrgihc/dbswitch
//
// Use of this source code is governed by a BSD-style license
//
// Author: Li ZeMin (2413957313@qq.com)
// Date : 2024/12/16
// Location: nanjing , china
/////////////////////////////////////////////////////////////
package org.dromara.dbswitch.product.tdengine;
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Statement;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.Optional;
import java.util.stream.Collectors;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.dromara.dbswitch.common.consts.Constants;
import org.dromara.dbswitch.core.provider.ProductFactoryProvider;
import org.dromara.dbswitch.core.provider.meta.AbstractMetadataProvider;
import org.dromara.dbswitch.core.schema.ColumnDescription;
import org.dromara.dbswitch.core.schema.ColumnMetaData;
import org.dromara.dbswitch.core.schema.IndexDescription;
import org.dromara.dbswitch.core.schema.SourceProperties;
import org.dromara.dbswitch.core.schema.TableDescription;
@Slf4j
public class TdengineMetadataQueryProvider extends AbstractMetadataProvider {
private static final String SHOW_CREATE_TABLE_SQL = "SHOW CREATE TABLE `%s`.`%s` ";
private static final String SHOW_CREATE_VIEW_SQL = "SHOW CREATE VIEW `%s`.`%s` ";
private static final String QUERY_STABLE_LIST_SQL =
"SELECT `db_name`,`stable_name` as table_name,'TABLE' as `type`,`table_comment` "
+ "FROM `information_schema`.`ins_stables` WHERE `db_name`= ? ";
private static final String QUERY_TABLE_LIST_SQL =
"SELECT `db_name`,`table_name`,`type`,`table_comment` "
+ "FROM `information_schema`.`ins_tables` WHERE `db_name`= ? ";
private static final String QUERY_TABLE_METADATA_SQL =
"SELECT `table_comment`,`type` FROM `information_schema`.`ins_tables` "
+ "WHERE `db_name` = ? AND `table_name` = ?";
public TdengineMetadataQueryProvider(ProductFactoryProvider factoryProvider) {
super(factoryProvider);
}
@Override
public List<String> querySchemaList(Connection connection) {
List<String> result = new ArrayList<>();
try (ResultSet rs = connection.getMetaData().getCatalogs()) {
while (rs.next()) {
Optional.ofNullable(rs.getString(1)).ifPresent(result::add);
}
return result.stream().distinct().collect(Collectors.toList());
} catch (SQLException e) {
throw new RuntimeException(e);
}
}
@Override
public List<TableDescription> queryTableList(Connection connection, String schemaName) {
List<TableDescription> result = new ArrayList<>();
try (PreparedStatement ps = connection.prepareStatement(QUERY_STABLE_LIST_SQL)) {
ps.setString(1, schemaName);
try (ResultSet rs = ps.executeQuery();) {
while (rs.next()) {
TableDescription td = new TableDescription();
td.setSchemaName(rs.getString("db_name"));
td.setTableName(rs.getString("table_name"));
td.setRemarks(rs.getString("table_comment"));
String tableType = rs.getString("type");
if (tableType.equalsIgnoreCase("VIEW")) {
td.setTableType("VIEW");
} else {
td.setTableType("TABLE");
}
result.add(td);
}
}
} catch (SQLException e) {
throw new RuntimeException(e);
}
try (PreparedStatement ps = connection.prepareStatement(QUERY_TABLE_LIST_SQL)) {
ps.setString(1, schemaName);
try (ResultSet rs = ps.executeQuery();) {
while (rs.next()) {
TableDescription td = new TableDescription();
td.setSchemaName(rs.getString("db_name"));
td.setTableName(rs.getString("table_name"));
td.setRemarks(rs.getString("table_comment"));
String tableType = rs.getString("type");
if (tableType.equalsIgnoreCase("VIEW")) {
td.setTableType("VIEW");
} else {
td.setTableType("TABLE");
}
result.add(td);
}
}
} catch (SQLException e) {
throw new RuntimeException(e);
}
return result;
}
@Override
public TableDescription queryTableMeta(Connection connection, String schemaName, String tableName) {
try (PreparedStatement ps = connection.prepareStatement(QUERY_TABLE_METADATA_SQL)) {
ps.setString(1, schemaName);
ps.setString(2, tableName);
try (ResultSet rs = ps.executeQuery();) {
while (rs.next()) {
TableDescription td = new TableDescription();
td.setSchemaName(schemaName);
td.setTableName(tableName);
td.setRemarks(rs.getString(1));
String tableType = rs.getString(2);
if (tableType.equalsIgnoreCase("VIEW")) {
td.setTableType("VIEW");
} else {
td.setTableType("TABLE");
}
return td;
}
return null;
}
} catch (SQLException e) {
throw new RuntimeException(e);
}
}
@Override
public List<String> queryTableColumnName(Connection connection, String schemaName, String tableName) {
List<String> columns = new ArrayList<>();
try (ResultSet rs = connection.getMetaData()
.getColumns(schemaName, null, tableName, null)) {
while (rs.next()) {
columns.add(rs.getString("COLUMN_NAME"));
}
return columns.stream().distinct().collect(Collectors.toList());
} catch (SQLException e) {
throw new RuntimeException(e);
}
}
@Override
public List<ColumnDescription> queryTableColumnMeta(Connection connection, String schemaName,
String tableName) {
String sql = this.getTableFieldsQuerySQL(schemaName, tableName);
List<ColumnDescription> ret = this.querySelectSqlColumnMeta(connection, sql);
// 补充一下注释信息
try (ResultSet columns = connection.getMetaData()
.getColumns(schemaName, null, tableName, null)) {
while (columns.next()) {
String columnName = columns.getString("COLUMN_NAME");
String remarks = columns.getString("REMARKS");
String columnDefault = columns.getString("COLUMN_DEF");
for (ColumnDescription cd : ret) {
if (columnName.equals(cd.getFieldName())) {
cd.setRemarks(remarks);
// 补充默认值信息
cd.setDefaultValue(columnDefault);
}
}
}
return ret;
} catch (SQLException e) {
throw new RuntimeException(e);
}
}
@Override
public List<String> queryTablePrimaryKeys(Connection connection, String schemaName, String tableName) {
List<String> ret = new ArrayList<>();
try (ResultSet primaryKeys = connection.getMetaData()
.getPrimaryKeys(schemaName, null, tableName)) {
while (primaryKeys.next()) {
ret.add(primaryKeys.getString("COLUMN_NAME"));
}
return ret.stream().distinct().collect(Collectors.toList());
} catch (SQLException e) {
throw new RuntimeException(e);
}
}
@Override
public synchronized List<IndexDescription> queryTableIndexes(Connection connection, String schemaName,
String tableName) {
setCatalogName(schemaName);
return super.queryTableIndexes(connection, schemaName, tableName);
}
@Override
public String getTableDDL(Connection connection, String schemaName, String tableName) {
List<String> result = new ArrayList<>();
try (Statement st = connection.createStatement()) {
if (st.execute(String.format(SHOW_CREATE_TABLE_SQL, schemaName, tableName))) {
try (ResultSet rs = st.getResultSet()) {
if (rs != null) {
while (rs.next()) {
String value = rs.getString(2);
Optional.ofNullable(value).ifPresent(result::add);
}
}
}
}
} catch (SQLException e) {
throw new RuntimeException(e);
}
return result.stream().findAny().orElse(null);
}
@Override
public String getViewDDL(Connection connection, String schemaName, String tableName) {
List<String> result = new ArrayList<>();
try (Statement st = connection.createStatement()) {
if (st.execute(String.format(SHOW_CREATE_VIEW_SQL, schemaName, tableName))) {
try (ResultSet rs = st.getResultSet()) {
if (rs != null) {
while (rs.next()) {
String value = rs.getString(2);
Optional.ofNullable(value).ifPresent(result::add);
}
}
}
}
} catch (SQLException e) {
throw new RuntimeException(e);
}
return result.stream().findAny().orElse(null);
}
@Override
public List<ColumnDescription> querySelectSqlColumnMeta(Connection connection, String sql) {
String querySQL = String.format(" %s LIMIT 0,1", sql.replace(";", ""));
return this.getSelectSqlColumnMeta(connection, querySQL);
}
@Override
public void testQuerySQL(Connection connection, String sql) {
String testQuerySql = String.format("explain %s", sql.replace(";", ""));
if (log.isDebugEnabled()) {
log.debug("Execute sql :{}", testQuerySql);
}
try (Statement st = connection.createStatement()) {
st.execute(testQuerySql);
} catch (SQLException e) {
throw new RuntimeException(e);
}
}
@Override
public String getFieldDefinition(ColumnMetaData v, List<String> pks, boolean useAutoInc,
boolean addCr, boolean withRemarks) {
String fieldname = v.getName();
int length = v.getLength();
int precision = v.getPrecision();
int type = v.getType();
String retval = " `" + fieldname + "` ";
switch (type) {
case ColumnMetaData.TYPE_TIMESTAMP:
retval += "TIMESTAMP";
break;
case ColumnMetaData.TYPE_TIME:
retval += "TIME";
break;
case ColumnMetaData.TYPE_DATE:
retval += "DATE";
break;
case ColumnMetaData.TYPE_BOOLEAN:
retval += "TINYINT";
break;
case ColumnMetaData.TYPE_NUMBER:
case ColumnMetaData.TYPE_INTEGER:
case ColumnMetaData.TYPE_BIGNUMBER:
if (null != pks && !pks.isEmpty() && pks.contains(fieldname)) {
if (useAutoInc) {
retval += "BIGINT AUTO_INCREMENT NOT NULL";
} else {
retval += "BIGINT NOT NULL";
}
} else {
// Integer values...
if (precision == 0) {
if (length > 9) {
if (length < 19) {
// can hold signed values between -9223372036854775808 and 9223372036854775807
// 18 significant digits
retval += "BIGINT";
} else {
retval += "DECIMAL(" + length + ")";
}
} else {
retval += "INT";
}
} else {
// Floating point values...
if (length > 65) {
length = 65;
}
if (length > 15) {
retval += "DECIMAL(" + length;
if (precision > 0) {
retval += ", " + precision;
}
retval += ")";
} else {
// A double-precision floating-point number is accurate to approximately 15
// decimal places.
// http://mysql.mirrors-r-us.net/doc/refman/5.1/en/numeric-type-overview.html
retval += "DOUBLE";
}
}
}
break;
case ColumnMetaData.TYPE_STRING:
if (length > 0) {
if (length == 1) {
retval += "CHAR(1)";
} else if (length < 256) {
retval += "VARCHAR(" + length + ")";
} else if (null != pks && !pks.isEmpty() && pks.contains(fieldname)) {
/*
* MySQL5.6中varchar字段为主键时最大长度为254,例如如下的建表语句在MySQL5.7下能通过但在MySQL5.6下无法通过:
* create table `t_test`(
* `key` varchar(1024) binary,
* `val` varchar(1024) binary,
* primary key(`key`)
* );
*/
retval += "VARCHAR(254) BINARY";
} else if (length < 65536) {
retval += "TEXT";
} else if (length < 16777216) {
retval += "MEDIUMTEXT";
} else {
retval += "LONGTEXT";
}
} else {
retval += "TINYTEXT";
}
break;
case ColumnMetaData.TYPE_BINARY:
retval += "LONGBLOB";
break;
default:
retval += "LONGTEXT";
break;
}
if (withRemarks && StringUtils.isNotBlank(v.getRemarks())) {
retval += String.format(" COMMENT '%s' ", v.getRemarks().replace("'", "\\'"));
}
if (addCr) {
retval += Constants.CR;
}
return retval;
}
@Override
public List<String> getTableColumnCommentDefinition(TableDescription td, List<ColumnDescription> cds) {
return Collections.emptyList();
}
@Override
public void preAppendCreateTableSql(StringBuilder builder) {
// builder.append( Const.IF_NOT_EXISTS );
}
@Override
public void postAppendCreateTableSql(StringBuilder builder, String tblComment, List<String> primaryKeys,
SourceProperties tblProperties) {
if (StringUtils.isNotBlank(tblComment)) {
builder.append(String.format(" COMMENT='%s' ", tblComment.replace("'", "\\'")));
}
}
@Override
public void appendPrimaryKeyForCreateTableSql(StringBuilder builder, List<String> primaryKeys) {
// Tdengine默认第一个字段为主键
}
}

View File

@@ -0,0 +1,74 @@
// Copyright tang. All rights reserved.
// https://gitee.com/inrgihc/dbswitch
//
// Use of this source code is governed by a BSD-style license
//
// Author: Li ZeMin (2413957313@qq.com)
// Date : 2024/12/16
// Location: nanjing , china
/////////////////////////////////////////////////////////////
package org.dromara.dbswitch.product.tdengine;
import cn.hutool.core.util.HexUtil;
import java.sql.Connection;
import java.sql.ResultSet;
import java.sql.ResultSetMetaData;
import java.sql.SQLException;
import java.sql.Statement;
import java.util.ArrayList;
import java.util.List;
import org.dromara.dbswitch.common.util.ObjectCastUtils;
import org.dromara.dbswitch.core.provider.ProductFactoryProvider;
import org.dromara.dbswitch.core.provider.query.DefaultTableDataQueryProvider;
import org.dromara.dbswitch.core.schema.SchemaTableData;
public class TdengineTableDataQueryProvider extends DefaultTableDataQueryProvider {
public TdengineTableDataQueryProvider(ProductFactoryProvider factoryProvider) {
super(factoryProvider);
}
@Override
public SchemaTableData queryTableData(Connection connection, String schemaName, String tableName, int rowCount) {
String fullTableName = quoteSchemaTableName(schemaName, tableName);
String querySQL = String.format("SELECT * FROM %s limit %s", fullTableName, rowCount);
SchemaTableData data = new SchemaTableData();
data.setSchemaName(schemaName);
data.setTableName(tableName);
data.setColumns(new ArrayList<>());
data.setRows(new ArrayList<>());
try (Statement st = connection.createStatement()) {
beforeExecuteQuery(connection, schemaName, tableName);
try (ResultSet rs = st.executeQuery(querySQL)) {
ResultSetMetaData m = rs.getMetaData();
int count = m.getColumnCount();
for (int i = 1; i <= count; i++) {
data.getColumns().add(m.getColumnLabel(i));
}
int counter = 0;
while (rs.next() && counter++ < rowCount) {
List<Object> row = new ArrayList<>(count);
for (int i = 1; i <= count; i++) {
Object value = rs.getObject(i);
if (value instanceof byte[]) {
row.add(HexUtil.encodeHexStr((byte[]) value));
} else if (value instanceof java.sql.Clob) {
row.add(ObjectCastUtils.castToString(value));
} else if (value instanceof java.sql.Blob) {
byte[] bytes = ObjectCastUtils.castToByteArray(value);
row.add(HexUtil.encodeHexStr(bytes));
} else {
row.add(null == value ? null : value.toString());
}
}
data.getRows().add(row);
}
return data;
}
} catch (SQLException e) {
throw new RuntimeException(e);
}
}
}

View File

@@ -0,0 +1,35 @@
// Copyright tang. All rights reserved.
// https://gitee.com/inrgihc/dbswitch
//
// Use of this source code is governed by a BSD-style license
//
// Author: Li ZeMin (2413957313@qq.com)
// Date : 2024/12/16
// Location: nanjing , china
/////////////////////////////////////////////////////////////
package org.dromara.dbswitch.product.tdengine;
import org.dromara.dbswitch.core.provider.ProductFactoryProvider;
import org.dromara.dbswitch.core.provider.manage.DefaultTableManageProvider;
public class TdengineTableManageProvider extends DefaultTableManageProvider {
public TdengineTableManageProvider(ProductFactoryProvider factoryProvider) {
super(factoryProvider);
}
@Override
public void truncateTableData(String schemaName, String tableName) {
String sql = String.format("DELETE FROM %s.%s ",
schemaName, tableName);
this.executeSql(sql);
}
@Override
public void dropTable(String schemaName, String tableName) {
String sql = String.format("DROP TABLE %s.%s ",
schemaName, tableName);
this.executeSql(sql);
}
}

View File

@@ -0,0 +1,29 @@
// Copyright tang. All rights reserved.
// https://gitee.com/inrgihc/dbswitch
//
// Use of this source code is governed by a BSD-style license
//
// Author: Li ZeMin (2413957313@qq.com)
// Date : 2024/12/16
// Location: nanjing , china
/////////////////////////////////////////////////////////////
package org.dromara.dbswitch.product.tdengine;
import org.dromara.dbswitch.core.provider.ProductFactoryProvider;
import org.dromara.dbswitch.core.provider.sync.AutoCastTableDataSynchronizeProvider;
import org.springframework.transaction.TransactionDefinition;
import org.springframework.transaction.support.DefaultTransactionDefinition;
public class TdengineTableSynchronizer extends AutoCastTableDataSynchronizeProvider {
public TdengineTableSynchronizer(ProductFactoryProvider factoryProvider) {
super(factoryProvider);
}
@Override
protected TransactionDefinition getDefaultTransactionDefinition() {
DefaultTransactionDefinition definition = new DefaultTransactionDefinition();
return definition;
}
}

View File

@@ -0,0 +1 @@
org.dromara.dbswitch.product.tdengine.TdengineFactoryProvider

View File

@@ -123,6 +123,11 @@
<artifactId>dbswitch-product-oceanbase</artifactId>
<version>${project.version}</version>
</dependency>
<dependency>
<groupId>org.dromara.dbswitch</groupId>
<artifactId>dbswitch-product-tdengine</artifactId>
<version>${project.version}</version>
</dependency>
</dependencies>
</project>

View File

@@ -35,6 +35,7 @@
<module>dbswitch-product-greenplum</module>
<module>dbswitch-product-doris</module>
<module>dbswitch-product-oceanbase</module>
<module>dbswitch-product-tdengine</module>
</modules>
</project>