SyncTaskAdd.jsx 13 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495
  1. import { Steps, Button, Space, Form, Drawer, message } from 'antd'
  2. import StepOne from './StepOne'
  3. import StepTwo from './StepTwo'
  4. import StepThree from './StepThree'
  5. import StepFour from './StepFour'
  6. import React, { useState, useEffect, useRef } from 'react'
  7. import DataTableStruct from './DataTableStruct'
  8. import styled from 'styled-components'
  9. import { MenuFoldOutlined, MenuUnfoldOutlined } from '@ant-design/icons'
  10. import {
  11. getDataSourceList,
  12. getTableSchema,
  13. buildJsonData,
  14. createJob,
  15. } from '../services'
  16. import { useNavigate } from 'react-router'
  17. import moment from 'moment'
  18. const { Step } = Steps
  19. const SyncTask = styled.div`
  20. padding: 20px;
  21. .check__btn {
  22. position: absolute;
  23. right: 0;
  24. top: 20%;
  25. border-radius: 0;
  26. }
  27. `
  28. export default function SyncTaskAdd() {
  29. // 路由导航
  30. const navigate = useNavigate()
  31. // 步骤三ref
  32. const stepThreeRef = React.createRef()
  33. // jsonValue
  34. const jsonValue = React.createRef()
  35. const cronRef = useRef()
  36. // 步骤数
  37. const [currentStep, setCurrentStep] = useState(0)
  38. // 初始化数据源列表数据 constructor
  39. const [dataSourceList, setDataSourceList] = useState([])
  40. // 构建loading状态
  41. const [building, setBuilding] = useState(false)
  42. // 提交loading状态
  43. const [submiting, setSubmiting] = useState(false)
  44. // 完成状态
  45. const [isfinishBuild, setIsFinishBuild] = useState(false)
  46. const [dataxJson, setDataxJson] = useState(null)
  47. const [partitionNum, setPartitionNum] = useState(0)
  48. const [cron_data, setCronData] = useState(null)
  49. // 提取源表单表结构
  50. const [drawDataStruct, setDrawDataStruct] = useState([])
  51. const updateDrawDataStruct = async (ds_id, table_name) => {
  52. await updateTableStruct(ds_id, table_name, setDrawDataStruct)
  53. }
  54. // 加载源表单表结构
  55. const [loadDataStruct, setLoadDataStruct] = useState([])
  56. const updateLoadDataStruct = async (id, table_name) => {
  57. await updateTableStruct(id, table_name, setLoadDataStruct)
  58. }
  59. // 更新表结构
  60. const updateTableStruct = async (id, table_name, setFunc) => {
  61. const { data } = await getTableSchema({ id, table_name })
  62. if (data.code === 200) {
  63. const tableList = data.data.map(item => {
  64. const splitData = item.split(':')
  65. return {
  66. key: splitData[0],
  67. id: splitData[0],
  68. field: splitData[1],
  69. type: splitData[2],
  70. }
  71. })
  72. setFunc(tableList)
  73. } else {
  74. message.error('表结构数据加载失败')
  75. }
  76. }
  77. // 配置提取源表单
  78. const [drawDataForm] = Form.useForm()
  79. // 配置加载源表单
  80. const [loadDataForm] = Form.useForm()
  81. // 同步参数表单
  82. const [syncDataForm] = Form.useForm()
  83. // 当前表单
  84. const [currentForm, setCurrentForm] = useState(drawDataForm)
  85. // 上一步/下一步
  86. const changeStep = num => {
  87. if (num === -1) {
  88. setIsFinishBuild(false)
  89. }
  90. setCurrentStep(currentStep + num)
  91. }
  92. // 暂存cron表达式
  93. const saveCron = () => {
  94. if (Object.keys(cronRef.current.getCronData).length !== 0) {
  95. setCronData(cronRef.current.getCronData)
  96. }
  97. }
  98. // 监听步骤设置当前表单
  99. useEffect(() => {
  100. switch (currentStep) {
  101. case 0:
  102. setCurrentForm(drawDataForm)
  103. break
  104. case 1:
  105. setCurrentForm(loadDataForm)
  106. break
  107. case 3:
  108. setCurrentForm(syncDataForm)
  109. break
  110. default:
  111. break
  112. }
  113. }, [currentStep])
  114. // 右侧表结构可视
  115. const [visible, setVisible] = useState(false)
  116. // 展示
  117. const showDrawer = () => {
  118. setVisible(true)
  119. }
  120. // 关闭
  121. const onClose = () => {
  122. if (currentStep === 3) {
  123. saveCron()
  124. if (jsonValue.current) {
  125. setDataxJson(JSON.parse(jsonValue.current.jsonValue.editor.getValue()))
  126. }
  127. }
  128. setVisible(false)
  129. }
  130. // 获取列表数据
  131. const fetchDataSourceList = async () => {
  132. const { data } = await getDataSourceList()
  133. if (data.code === 200) {
  134. const list = data.data.items.map(item => {
  135. return {
  136. key: item.id,
  137. datasource_name: item.datasource_name,
  138. datasource: item.datasource,
  139. }
  140. })
  141. setDataSourceList(list)
  142. }
  143. }
  144. // 挂载的时候获取SourceList
  145. useEffect(() => {
  146. fetchDataSourceList()
  147. }, [])
  148. const [syncMapSaving, setSyncMapSaving] = useState([])
  149. // reader_columns
  150. const [reader_columns, setReaderColumns] = useState([])
  151. // writer_columns
  152. const [writer_columns, setWriterColumns] = useState([])
  153. // 下一步
  154. const nextStep = () => {
  155. if (currentStep === 2) {
  156. setSyncMapSaving(stepThreeRef.current.syncMappings)
  157. setReaderColumns(
  158. stepThreeRef.current.syncMappings.map(item => {
  159. return item.key.drawData
  160. })
  161. )
  162. setWriterColumns(
  163. stepThreeRef.current.syncMappings.map(item => {
  164. return item.key.loadData
  165. })
  166. )
  167. }
  168. currentForm
  169. .validateFields()
  170. .then(() => {
  171. changeStep(1)
  172. })
  173. .catch(err => {
  174. message.error('请检查表单数据是否完整')
  175. })
  176. }
  177. // 格式化构建请求参数数据
  178. const formatBuildData = () => {
  179. // 获取表单数据
  180. const reader_form = drawDataForm.getFieldValue()
  181. const writer_form = loadDataForm.getFieldValue()
  182. // 获取id tables
  183. const reader_datasource_id = reader_form['datasource_name']
  184. const reader_tables = [reader_form['datasource_table']]
  185. const writer_datasource_id = writer_form['datasource_name']
  186. const writer_tables = [writer_form['datasource_table']]
  187. const writer_filename = writer_form['writer_filename']
  188. // 基础固定参数
  189. const base_params = {
  190. reader_datasource_id,
  191. reader_tables,
  192. reader_columns,
  193. writer_datasource_id,
  194. writer_tables,
  195. writer_columns,
  196. }
  197. // 获取表类型
  198. const reader_type = dataSourceList.find(
  199. item => item.key === reader_datasource_id
  200. ).datasource
  201. const writer_type = dataSourceList.find(
  202. item => item.key === writer_datasource_id
  203. ).datasource
  204. // 结构表单参数
  205. const {
  206. reader_split_pk,
  207. where_param,
  208. query_sql,
  209. reader_default_fs,
  210. reader_file_type,
  211. reader_path,
  212. reader_field_delimiter,
  213. } = reader_form
  214. const {
  215. pre_sql,
  216. post_sql,
  217. writer_default_fs,
  218. writer_file_type,
  219. writer_path,
  220. writer_mode,
  221. writer_field_delimiter,
  222. } = writer_form
  223. // 根据类型区分参数
  224. switch (`${reader_type}2${writer_type}`) {
  225. case 'mysql2mysql':
  226. return {
  227. ...base_params,
  228. rdbms_reader: { reader_split_pk, where_param, query_sql },
  229. rdbms_writer: { pre_sql, post_sql },
  230. }
  231. case 'mysql2hive':
  232. return {
  233. ...base_params,
  234. rdbms_reader: { reader_split_pk, where_param, query_sql },
  235. hive_writer: {
  236. writer_default_fs,
  237. writer_file_type,
  238. writer_path,
  239. writer_mode,
  240. writer_field_delimiter,
  241. writer_filename,
  242. },
  243. }
  244. case 'hive2mysql':
  245. return {
  246. ...base_params,
  247. hive_reader: {
  248. reader_default_fs,
  249. reader_file_type,
  250. reader_path,
  251. reader_field_delimiter,
  252. },
  253. rdbms_writer: { pre_sql, post_sql },
  254. }
  255. case 'hive2hive':
  256. return {
  257. ...base_params,
  258. hive_reader: {
  259. reader_default_fs,
  260. reader_file_type,
  261. reader_path,
  262. reader_field_delimiter,
  263. },
  264. hive_writer: {
  265. writer_default_fs,
  266. writer_file_type,
  267. writer_path,
  268. writer_mode,
  269. writer_field_delimiter,
  270. writer_filename,
  271. },
  272. }
  273. default:
  274. break
  275. }
  276. return null
  277. }
  278. // 构建表单
  279. const build = async () => {
  280. saveCron()
  281. const buildParams = formatBuildData()
  282. if (!buildParams) {
  283. message.error('获取表单参数失败')
  284. return
  285. }
  286. setBuilding(true)
  287. const { data } = await buildJsonData(buildParams)
  288. if (data.code === 200) {
  289. message.success('构建成功')
  290. const json = data.data.json
  291. setDataxJson(json)
  292. showDrawer()
  293. setIsFinishBuild(true)
  294. } else {
  295. message.error('构建失败,请检查表单数据')
  296. }
  297. setBuilding(false)
  298. }
  299. // 完成提交
  300. const finishSubmit = async job_json => {
  301. // setSubmiting(true)
  302. const fields = currentForm.getFieldValue()
  303. const params = {
  304. ...fields,
  305. job_json,
  306. inc_start_time: moment(fields.inc_start_time).unix(),
  307. user_id: 'test',
  308. }
  309. params['partition_num'] = partitionNum
  310. const { data } = await createJob(params)
  311. if (data.code === 200) {
  312. message.success('提交成功')
  313. navigate(-1)
  314. } else {
  315. message.error('提交失败')
  316. }
  317. setSubmiting(false)
  318. }
  319. // 提交表单
  320. const submit = () => {
  321. if (jsonValue.current) {
  322. setDataxJson(JSON.parse(jsonValue.current.jsonValue.editor.getValue()))
  323. }
  324. try {
  325. let job_json = JSON.stringify(dataxJson)
  326. if (jsonValue.current) {
  327. job_json = JSON.stringify(
  328. JSON.parse(jsonValue.current.jsonValue.editor.getValue())
  329. )
  330. }
  331. if (Object.keys(cronRef.current.getCronData).length !== 0) {
  332. currentForm.setFieldValue(
  333. 'cron_expression',
  334. cronRef.current.getCronData
  335. )
  336. }
  337. currentForm
  338. .validateFields()
  339. .then(() => {
  340. finishSubmit(job_json)
  341. })
  342. .catch(err => {
  343. message.error('请检查表单数据是否完整')
  344. })
  345. } catch (error) {
  346. message.error('转换JSON字符串失败,请检查json数据', error)
  347. }
  348. }
  349. return (
  350. <SyncTask>
  351. {/* 步骤条 */}
  352. <Steps current={currentStep} labelPlacement="vertical" size="small">
  353. <Step title="步骤1: 配置提取源" />
  354. <Step title="步骤2: 配置加载源" />
  355. <Step title="步骤3: 配置转换规则" />
  356. <Step title="步骤4: 设置同步参数" />
  357. </Steps>
  358. {/* 表单项 */}
  359. {/* 配置提取源 */}
  360. {currentStep === 0 && (
  361. <StepOne
  362. drawDataForm={drawDataForm}
  363. dataSourceList={dataSourceList}
  364. updateTableStruct={updateDrawDataStruct}
  365. />
  366. )}
  367. {/* 配置加载源 */}
  368. {currentStep === 1 && (
  369. <StepTwo
  370. loadDataForm={loadDataForm}
  371. dataSourceList={dataSourceList}
  372. updateTableStruct={updateLoadDataStruct}
  373. />
  374. )}
  375. {/* 配置转换规则 */}
  376. {currentStep === 2 && (
  377. <StepThree
  378. onRef={stepThreeRef}
  379. drawDataForm={drawDataForm}
  380. loadDataForm={loadDataForm}
  381. syncMapSaving={syncMapSaving}
  382. />
  383. )}
  384. {/* 设置同步参数 */}
  385. {currentStep === 3 && (
  386. <StepFour
  387. onRef={cronRef}
  388. syncDataForm={syncDataForm}
  389. cron_data={cron_data}
  390. partitionNumHandle={{
  391. num: partitionNum,
  392. func: val => setPartitionNum(val),
  393. }}
  394. />
  395. )}
  396. {/* 按扭操作 */}
  397. {/* 表结构预览 */}
  398. {(currentStep === 0 || currentStep === 1 || currentStep === 3) && (
  399. <>
  400. {dataxJson !== null && (
  401. <Button
  402. type="primary"
  403. onClick={showDrawer}
  404. className="check__btn"
  405. size="large"
  406. icon={<MenuFoldOutlined />}>
  407. {currentStep !== 3 ? '表结构预览' : '构建'}
  408. </Button>
  409. )}
  410. <Drawer
  411. title={currentStep === 3 ? '构建' : '表结构预览'}
  412. placement="right"
  413. onClose={onClose}
  414. visible={visible}
  415. width={600}
  416. mask={false}
  417. destroyOnClose={true}
  418. closeIcon={<MenuUnfoldOutlined style={{ color: '#1881DA' }} />}>
  419. <DataTableStruct
  420. currentStep={currentStep}
  421. tableData={currentStep === 0 ? drawDataStruct : loadDataStruct}
  422. datax={dataxJson}
  423. onRef={jsonValue}
  424. />
  425. </Drawer>
  426. </>
  427. )}
  428. {/* 按扭操作 */}
  429. <Space style={{ margin: '20px' }}>
  430. <Button
  431. onClick={() => {
  432. changeStep(-1)
  433. }}
  434. disabled={currentStep === 0 || building}>
  435. 上一步
  436. </Button>
  437. {currentStep === 3 ? (
  438. <Button
  439. type="primary"
  440. onClick={isfinishBuild ? submit : build}
  441. loading={isfinishBuild ? submiting : building}>
  442. {isfinishBuild ? '提交' : '构建'}
  443. </Button>
  444. ) : (
  445. <Button type="primary" onClick={nextStep}>
  446. 下一步
  447. </Button>
  448. )}
  449. </Space>
  450. </SyncTask>
  451. )
  452. }