Browse Source

(*)采集任务 增加空表判断 字段索引外键改为增量更新 数据源配置选择树结构

master
peng.peng 2 years ago
parent
commit
1439998f0c
  1. 8
      api/app/lib/controllers/metadataAcquisition/dataSource.js
  2. 136
      api/app/lib/controllers/metadataAcquisition/taskHandle.js
  3. 1
      web/client/src/layout/components/sider/index.js
  4. 36
      web/client/src/sections/metadataAcquisition/components/steps/postgre/stepOne.js
  5. 4
      web/client/src/sections/metadataAcquisition/components/steps/postgre/stepThree.js
  6. 20
      web/client/src/sections/metadataAcquisition/containers/dataSourceManagement.js

8
api/app/lib/controllers/metadataAcquisition/dataSource.js

@ -6,11 +6,15 @@ function addDataSource(opts) {
const models = ctx.fs.dc.models; const models = ctx.fs.dc.models;
try { try {
const { name } = ctx.request.body const { name, mountPath } = ctx.request.body
const checkName = await models.DataSource.findOne({ where: { name, name } }); const checkName = await models.DataSource.findOne({ where: { name: name } });
const checkMountPath = await models.DataSource.findOne({ where: { mountPath: mountPath } });
if (checkName) { if (checkName) {
ctx.status = 400; ctx.status = 400;
ctx.body = { message: '该数据源名称已存在' } ctx.body = { message: '该数据源名称已存在' }
} else if (checkMountPath) {
ctx.status = 400;
ctx.body = { message: '该资源路径已被使用' }
} else { } else {
let rslt = ctx.request.body; let rslt = ctx.request.body;
await models.DataSource.create(Object.assign({}, rslt)) await models.DataSource.create(Object.assign({}, rslt))

136
api/app/lib/controllers/metadataAcquisition/taskHandle.js

@ -46,8 +46,9 @@ async function handleTask(app, task) {
}//目录树下库只会存在一个 判断是否有库类型元数据 没有则新增库和表元数据 有库数据则比较更新表类型元数据 全量更新字段索引数据 }//目录树下库只会存在一个 判断是否有库类型元数据 没有则新增库和表元数据 有库数据则比较更新表类型元数据 全量更新字段索引数据
}) })
let databaseId = databaseFind ? databaseFind.id : null; let databaseId = databaseFind ? databaseFind.id : null;
if (databaseFind) { //更新表类型元数据
const newTableNames = [] const newTableNames = []
if (databaseFind) { //更新表类型元数据
Object.keys(tables).forEach(key => { newTableNames.push(key) }); Object.keys(tables).forEach(key => { newTableNames.push(key) });
//删除不存在表元数据 //删除不存在表元数据
@ -108,45 +109,74 @@ async function handleTask(app, task) {
} }
}) })
//字段/索引/外键全量更新 先删除之前的字段 再录入新的数据 //字段索引外键上一次存储集合
await models.MetadataDatabase.destroy({ const tableChildrens = await models.MetadataDatabase.findAll({
where: { where: {
type: { $in: ['字段', '索引', '外键'] }, type: { $in: ['字段', '索引', '外键'] },
catalog: dataSource.mountPath, catalog: dataSource.mountPath,
} }
}) })
let fieldBodys = []
const fieldBodys = [] let fieldRslt = null;
if (tableChildrens.length == 0) { //首次执行定时任务新增
for (let table of metaDatabaseTables) { for (let table of metaDatabaseTables) {
Object.keys(tables[table.name].structures).forEach(key => { const children = handleAddTableChildren(dataToSave, tables, table);
dataToSave.parent = table.id; fieldBodys = fieldBodys.concat(children)
dataToSave.name = tables[table.name].structures[key].comment || key; }
dataToSave.code = key; fieldRslt = await models.MetadataDatabase.bulkCreate(fieldBodys);
dataToSave.type = '字段'; } else {//更新数据
const tableObj = { ...dataToSave } const deleteIds = [];//字段索引外键删除id集合
fieldBodys.push(tableObj) const deleteParentIds = [];//删除表集合
tableChildrens.forEach(s => {
let table = metaDatabaseTables.find(x => x.id == s.parent)
if (table) {
if (s.type == '字段' && !tables[table.name].structures[s.code]) {
deleteIds.push(s.id)
}
if (s.type == '外键' && !tables[table.name].foreignKeys.find(x => x.columnName == s.code)) {
deleteIds.push(s.id)
}
if (s.type == '索引' && !tables[table.name].indexes.find(x => x.name == s.code)) {
deleteIds.push(s.id)
}
} else {
if (!deleteParentIds.find(n => n == s.parent)) deleteParentIds.push(s.parent)
}
}) })
tables[table.name].foreignKeys.forEach(v => { //判断新增- 新增表 新增表字段
dataToSave.parent = table.id; Object.keys(tables).forEach(key => {
dataToSave.name = v.columnName; const table = metaDatabaseTables.find(s => s.code == key)
dataToSave.code = v.columnName; if (tableChildrens.find(s => s.parent == table.id)) {
dataToSave.type = '外键'; const children = handleAddTableChildren(dataToSave, tables, table, tableChildrens);
const tableObj = { ...dataToSave } fieldBodys = fieldBodys.concat(children)
fieldBodys.push(tableObj) } else {
const children = handleAddTableChildren(dataToSave, tables, table);
fieldBodys = fieldBodys.concat(children)
}
}) })
tables[table.name].indexes.forEach(v => { //删除不存在的字段索引 外键
dataToSave.parent = table.id; await models.MetadataDatabase.destroy({
dataToSave.name = v.name; where: {
dataToSave.code = v.name; type: { $in: ['字段', '索引', '外键'] },
dataToSave.type = '索引'; catalog: dataSource.mountPath,
const tableObj = { ...dataToSave } $or: [
fieldBodys.push(tableObj) { parent: { $in: deleteParentIds } },
{ id: { $in: deleteIds } }
]
}
}) })
//增加新增的字段索引外键
if (fieldBodys.length > 0) {
fieldRslt = await models.MetadataDatabase.bulkCreate(fieldBodys);
} else {
fieldRslt = true;
}
} }
const fieldRslt = await models.MetadataDatabase.bulkCreate(fieldBodys);
if (fieldRslt) { if (fieldRslt) {
const endTime = moment() const endTime = moment()
const logBody = { const logBody = {
@ -168,10 +198,18 @@ async function handleTask(app, task) {
} catch (error) { } catch (error) {
// await transaction.rollback(); // await transaction.rollback();
const endTime = moment() const endTime = moment()
let message = error.message ? error.message : error
//空表auto-sequelize会抛出异常 提示信息处理
if (error.message && error.message.indexOf('No description found for') > -1) {
if (error.message.split('.') && error.message.split('.').length > 0) {
message = error.message.split('.')[0].split('"')[1] + '未定义任何字段'
}
}
const logBody = { const logBody = {
task: task.id, task: task.id,
success: false, success: false,
details: '采集失败' + JSON.stringify(error).substring(0, 248), details: '采集失败:' + JSON.stringify(message).substring(0, 247),
startTime: startTime, startTime: startTime,
endTime: endTime endTime: endTime
} }
@ -181,12 +219,52 @@ async function handleTask(app, task) {
if (task.retried && task.retryCount && task.retryTime && taskRetryIndex[task.id] < task.retryCount) { if (task.retried && task.retryCount && task.retryTime && taskRetryIndex[task.id] < task.retryCount) {
setTimeout(() => { setTimeout(() => {
handleTask(app, task) handleTask(app, task)
}, 1000 * 60 * task.retryCount); }, 1000 * 60 * task.retryTime);
} }
app.fs.logger.error(`sechedule: handleTask, error: ${error}`); app.fs.logger.error(`sechedule: handleTask, error: ${error}`);
} }
} }
//处理字段 索引 外键 新增body
function handleAddTableChildren(dataToSave, tables, table, tableChildrens) {
const fieldBodys = [];
Object.keys(tables[table.name].structures).forEach(key => {
if (!tableChildrens || !tableChildrens.find(s => s.code == key)) {
dataToSave.parent = table.id;
dataToSave.name = tables[table.name].structures[key].comment || key;
dataToSave.code = key;
dataToSave.type = '字段';
const tableObj = { ...dataToSave }
fieldBodys.push(tableObj)
}
})
tables[table.name].foreignKeys.forEach(v => {
if (!tableChildrens || !tableChildrens.find(s => s.code == v.columnName)) {
dataToSave.parent = table.id;
dataToSave.name = v.columnName;
dataToSave.code = v.columnName;
dataToSave.type = '外键';
const tableObj = { ...dataToSave }
fieldBodys.push(tableObj)
}
})
tables[table.name].indexes.forEach(v => {
if (!tableChildrens || !tableChildrens.find(s => s.code == v.name)) {
dataToSave.parent = table.id;
dataToSave.name = v.name;
dataToSave.code = v.name;
dataToSave.type = '索引';
const tableObj = { ...dataToSave }
fieldBodys.push(tableObj)
}
})
return fieldBodys;
}
function createDbOptions(params) { function createDbOptions(params) {
const dbOptions = { const dbOptions = {
database: params.database, database: params.database,

1
web/client/src/layout/components/sider/index.js

@ -3,6 +3,7 @@ import { Menu } from 'antd';
const JumpUrls = [ const JumpUrls = [
{ url: '/risk/hiddenrectification_approval', keys: 'riskHiddenrectification_approval' }, { url: '/risk/hiddenrectification_approval', keys: 'riskHiddenrectification_approval' },
{ url: '/safetymanage/hiddenrectification_approval', keys: 'hiddenrectification_approval' }, { url: '/safetymanage/hiddenrectification_approval', keys: 'hiddenrectification_approval' },
{ url: '/metadataManagement/latestMetadata', keys: 'latestMetadata' },
] ]
const Sider = (props) => { const Sider = (props) => {
const [items, setItems] = useState([]) const [items, setItems] = useState([])

36
web/client/src/sections/metadataAcquisition/components/steps/postgre/stepOne.js

@ -1,5 +1,5 @@
import React, { useEffect, useState } from 'react' import React, { useEffect, useState } from 'react'
import { Button, Form, Input, Row, Col } from 'antd'; import { Button, Spin, Input, Row, Col } from 'antd';
import { import {
ProForm, ProForm,
ProFormSelect, ProFormSelect,
@ -7,10 +7,11 @@ import {
ProFormText, ProFormText,
ProFormTreeSelect ProFormTreeSelect
} from '@ant-design/pro-form'; } from '@ant-design/pro-form';
import { push } from 'react-router-redux';
import '../../style.less'; import '../../style.less';
function StepOne(props) { function StepOne(props) {
const { next, stepOneValues, stepOneValuesFinish, readOnly, treeData, dataSources } = props; const { next, stepOneValues, stepOneValuesFinish, readOnly, treeData, dataSources, dispatch, refresh, loading, editData } = props;
const formRef = React.createRef(); const formRef = React.createRef();
const initialValues = stepOneValues ? stepOneValues : { const initialValues = stepOneValues ? stepOneValues : {
adapterName: 'PostgreSQL采集适配器', adapterName: 'PostgreSQL采集适配器',
@ -19,7 +20,7 @@ function StepOne(props) {
// mountPath: 1, // mountPath: 1,
} }
const formItemLayout = { labelCol: { span: 3 }, wrapperCol: { span: 10 } }; const formItemLayout = { labelCol: { span: 3 }, wrapperCol: { span: 12 } };
const getTreeNodeData = (dataSource, parent, key) => { const getTreeNodeData = (dataSource, parent, key) => {
let treeData = []; let treeData = [];
@ -40,8 +41,8 @@ function StepOne(props) {
} }
return treeData return treeData
} }
const treeDataFilter = treeData ? getTreeNodeData(treeData, null, 'rc') : [] const treeDataFilter = treeData && dataSources?.rows?.length > 0 ? getTreeNodeData(treeData, null, 'rc') : []
return <> return <Spin spinning={loading || treeDataFilter.length == 0}>
<ProForm <ProForm
title={''} title={''}
initialValues={initialValues} initialValues={initialValues}
@ -112,13 +113,14 @@ function StepOne(props) {
label="数据源挂载路径" label="数据源挂载路径"
// disabled={true} // disabled={true}
/> */} /> */}
<ProFormTreeSelect {treeDataFilter.length > 0 ? <ProFormTreeSelect
// width={'md'}
name="catalogKey" name="catalogKey"
label="数据源挂载路径" label="数据源挂载路径"
placeholder="请选择数据源挂载路径" placeholder="请选择数据源挂载路径"
rules={[{ required: true, message: '请选择数据源挂载路径' }]} rules={[{ required: true, message: '请选择数据源挂载路径' }]}
allowClear allowClear
// width={330} width={480}
secondary secondary
request={async () => { request={async () => {
return treeDataFilter || []; return treeDataFilter || [];
@ -137,7 +139,23 @@ function StepOne(props) {
label: 'title', label: 'title',
}, },
}} }}
/> addonAfter={!editData && <>
<a onClick={() => {
dispatch(push(`/metadataManagement/latestMetadata`));
}} style={{ marginRight: 8 }}>新建</a>
<a onClick={() => {
refresh()
}}>刷新</a>
</>}
disabled={editData}
/> : <ProFormSelect
rules={[{ required: true, message: '请选择' }]}
options={[]}
name="catalogKey"
label="数据源挂载路径"
placeholder="请选择数据源挂载路径"
disabled={editData}
/>}
<ProFormTextArea <ProFormTextArea
name="description" name="description"
@ -151,7 +169,7 @@ function StepOne(props) {
</Button> </Button>
</div> </div>
</ProForm> </ProForm>
</> </Spin>
} }
export default StepOne export default StepOne

4
web/client/src/sections/metadataAcquisition/components/steps/postgre/stepThree.js

@ -114,7 +114,7 @@ function StepThree(props) {
<ProFormDigit <ProFormDigit
width={'md'} width={'md'}
rules={[ rules={[
// { required: true, message: '请输入重试次数' }, { required: true, message: '请输入重试次数' },
// { max: 10, message: '重试次数不能大于10个字符' }, // { max: 10, message: '重试次数不能大于10个字符' },
]} ]}
name="retryCount" name="retryCount"
@ -126,7 +126,7 @@ function StepThree(props) {
<ProFormDigit <ProFormDigit
width={'md'} width={'md'}
rules={[ rules={[
// { required: true, message: '请输入时间间隔' }, { required: true, message: '请输入时间间隔' },
// { max: 255, message: '时间间隔长度不能大于255个字符' }, // { max: 255, message: '时间间隔长度不能大于255个字符' },
]} ]}
name="retryTime" name="retryTime"

20
web/client/src/sections/metadataAcquisition/containers/dataSourceManagement.js

@ -13,16 +13,20 @@ function DataSourceManagement(props) {
const [searchValue, setSearchValue] = useState('') const [searchValue, setSearchValue] = useState('')
const [visible, setVisible] = useState(false);//是否展示新增编辑模态框 const [visible, setVisible] = useState(false);//是否展示新增编辑模态框
const [editData, setEditData] = useState(null);//模态框编辑数据 const [editData, setEditData] = useState(null);//模态框编辑数据
const [refreshTree, setRefreshTree] = useState(1);
const queryData = (search) => { const queryData = (search) => {
const query = { const query = {
limit: search ? 10 : pageSize || 10, // limit: search ? 10 : pageSize || 10,
page: search ? 1 : currentPage || 1, // page: search ? 1 : currentPage || 1,
name: searchValue name: searchValue
} }
dispatch(actions.metadataAcquisition.getDataSources(query)); dispatch(actions.metadataAcquisition.getDataSources(query));
} }
const { data: treeData = [] } = useFsRequest({ url: ApiTable.getResourceCatalog }); const { data: treeData = [] } = useFsRequest({
url: ApiTable.getResourceCatalog,
refreshDeps: [refreshTree]
});
useEffect(() => { useEffect(() => {
dispatch(actions.metadataAcquisition.getAdapters()) dispatch(actions.metadataAcquisition.getAdapters())
@ -141,6 +145,10 @@ function DataSourceManagement(props) {
} }
} }
} }
const refresh = () => {
queryData();
setRefreshTree(refreshTree + 1)
}
return <Spin spinning={loading}> return <Spin spinning={loading}>
<Row className='protable-title'> <Row className='protable-title'>
@ -168,8 +176,9 @@ function DataSourceManagement(props) {
total: dataSources?.count, total: dataSources?.count,
showSizeChanger: true, showSizeChanger: true,
// showQuickJumper: true, // showQuickJumper: true,
current: currentPage, // current: currentPage,
pageSize: pageSize || 10, // pageSize: pageSize || 10,
defaultPageSize: 10,
pageSizeOptions: [10, 20, 50], pageSizeOptions: [10, 20, 50],
showTotal: (total) => { showTotal: (total) => {
return <span style={{ fontSize: 15 }}>{`${Math.ceil(total / pageSize)}页,${total}`}</span> return <span style={{ fontSize: 15 }}>{`${Math.ceil(total / pageSize)}页,${total}`}</span>
@ -201,6 +210,7 @@ function DataSourceManagement(props) {
visible={visible} visible={visible}
onFinish={onFinish} onFinish={onFinish}
treeData={treeData} treeData={treeData}
refresh={refresh}
{...props} {...props}
/> />
} }

Loading…
Cancel
Save