SyncTaskAdd.jsx 13 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515
  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. if (currentStep === 2) {
  169. if (stepThreeRef.current?.syncMappings.length) {
  170. changeStep(1)
  171. } else {
  172. message.error('缺少同步字段')
  173. }
  174. } else {
  175. currentForm
  176. .validateFields()
  177. .then(() => {
  178. changeStep(1)
  179. })
  180. .catch(err => {
  181. console.log(err)
  182. message.error('请检查表单数据是否完整')
  183. })
  184. }
  185. }
  186. // 格式化构建请求参数数据
  187. const formatBuildData = () => {
  188. // 获取表单数据
  189. const reader_form = drawDataForm.getFieldValue()
  190. const writer_form = loadDataForm.getFieldValue()
  191. // 获取id tables
  192. const reader_datasource_id = reader_form['datasource_name']
  193. const reader_tables = [reader_form['datasource_table']]
  194. const writer_datasource_id = writer_form['datasource_name']
  195. const writer_tables = [writer_form['datasource_table']]
  196. const writer_filename = writer_form['writer_filename']
  197. // 基础固定参数
  198. const base_params = {
  199. reader_datasource_id,
  200. reader_tables,
  201. reader_columns,
  202. writer_datasource_id,
  203. writer_tables,
  204. writer_columns,
  205. }
  206. // 获取表类型
  207. const reader_type = dataSourceList.find(
  208. item => item.key === reader_datasource_id
  209. ).datasource
  210. const writer_type = dataSourceList.find(
  211. item => item.key === writer_datasource_id
  212. ).datasource
  213. // 结构表单参数
  214. const {
  215. reader_split_pk,
  216. where_param,
  217. query_sql,
  218. reader_default_fs,
  219. reader_file_type,
  220. reader_path,
  221. reader_field_delimiter,
  222. } = reader_form
  223. const {
  224. pre_sql,
  225. post_sql,
  226. writer_default_fs,
  227. writer_file_type,
  228. writer_path,
  229. writer_mode,
  230. writer_field_delimiter,
  231. } = writer_form
  232. // 根据类型区分参数
  233. switch (`${reader_type}2${writer_type}`) {
  234. case 'mysql2mysql':
  235. return {
  236. ...base_params,
  237. rdbms_reader: { reader_split_pk, where_param, query_sql },
  238. rdbms_writer: { pre_sql, post_sql },
  239. }
  240. case 'mysql2hive':
  241. return {
  242. ...base_params,
  243. rdbms_reader: { reader_split_pk, where_param, query_sql },
  244. hive_writer: {
  245. writer_default_fs,
  246. writer_file_type,
  247. writer_path,
  248. writer_mode,
  249. writer_field_delimiter,
  250. writer_filename,
  251. },
  252. }
  253. case 'hive2mysql':
  254. return {
  255. ...base_params,
  256. hive_reader: {
  257. reader_default_fs,
  258. reader_file_type,
  259. reader_path,
  260. reader_field_delimiter,
  261. },
  262. rdbms_writer: { pre_sql, post_sql },
  263. }
  264. case 'hive2hive':
  265. return {
  266. ...base_params,
  267. hive_reader: {
  268. reader_default_fs,
  269. reader_file_type,
  270. reader_path,
  271. reader_field_delimiter,
  272. },
  273. hive_writer: {
  274. writer_default_fs,
  275. writer_file_type,
  276. writer_path,
  277. writer_mode,
  278. writer_field_delimiter,
  279. writer_filename,
  280. },
  281. }
  282. default:
  283. break
  284. }
  285. return null
  286. }
  287. // 构建表单
  288. const build = async () => {
  289. saveCron()
  290. const buildParams = formatBuildData()
  291. if (!buildParams) {
  292. message.error('获取表单参数失败')
  293. return
  294. }
  295. setBuilding(true)
  296. const { data } = await buildJsonData(buildParams)
  297. if (data.code === 200) {
  298. message.success('构建成功')
  299. const json = data.data.json
  300. setDataxJson(json)
  301. showDrawer()
  302. setIsFinishBuild(true)
  303. } else {
  304. message.error('构建失败,请检查表单数据')
  305. }
  306. setBuilding(false)
  307. }
  308. // 完成提交
  309. const finishSubmit = async job_json => {
  310. // setSubmiting(true)
  311. const fields = currentForm.getFieldValue()
  312. const params = {
  313. ...fields,
  314. job_json,
  315. inc_start_time: moment(fields.inc_start_time).unix(),
  316. user_id: 'test',
  317. }
  318. params['partition_num'] = partitionNum
  319. const { data } = await createJob(params)
  320. if (data.code === 200) {
  321. message.success('提交成功')
  322. navigate(-1)
  323. } else {
  324. message.error(data.msg)
  325. }
  326. setSubmiting(false)
  327. }
  328. // 提交表单
  329. const submit = () => {
  330. if (jsonValue.current) {
  331. setDataxJson(JSON.parse(jsonValue.current.jsonValue.editor.getValue()))
  332. }
  333. try {
  334. let job_json = JSON.stringify(dataxJson)
  335. if (jsonValue.current) {
  336. job_json = JSON.stringify(
  337. JSON.parse(jsonValue.current.jsonValue.editor.getValue())
  338. )
  339. }
  340. if (Object.keys(cronRef.current.getCronData).length !== 0) {
  341. currentForm.setFieldValue(
  342. 'cron_expression',
  343. cronRef.current.getCronData
  344. )
  345. }
  346. currentForm
  347. .validateFields()
  348. .then(() => {
  349. finishSubmit(job_json)
  350. })
  351. .catch(err => {
  352. message.error('请检查表单数据是否完整')
  353. })
  354. } catch (error) {
  355. message.error('转换JSON字符串失败,请检查json数据', error)
  356. }
  357. }
  358. return (
  359. <SyncTask>
  360. {/* 步骤条 */}
  361. <Steps current={currentStep} labelPlacement="vertical" size="small">
  362. <Step title="步骤1: 配置提取源" />
  363. <Step title="步骤2: 配置加载源" />
  364. <Step title="步骤3: 配置转换规则" />
  365. <Step title="步骤4: 设置同步参数" />
  366. </Steps>
  367. {/* 表单项 */}
  368. {/* 配置提取源 */}
  369. {currentStep === 0 && (
  370. <StepOne
  371. drawDataForm={drawDataForm}
  372. dataSourceList={dataSourceList}
  373. updateTableStruct={updateDrawDataStruct}
  374. />
  375. )}
  376. {/* 配置加载源 */}
  377. {currentStep === 1 && (
  378. <StepTwo
  379. loadDataForm={loadDataForm}
  380. dataSourceList={dataSourceList}
  381. updateTableStruct={updateLoadDataStruct}
  382. />
  383. )}
  384. {/* 配置转换规则 */}
  385. {currentStep === 2 && (
  386. <StepThree
  387. onRef={stepThreeRef}
  388. drawDataForm={drawDataForm}
  389. loadDataForm={loadDataForm}
  390. syncMapSaving={syncMapSaving}
  391. />
  392. )}
  393. {/* 设置同步参数 */}
  394. {currentStep === 3 && (
  395. <StepFour
  396. onRef={cronRef}
  397. syncDataForm={syncDataForm}
  398. cron_data={cron_data}
  399. partitionNumHandle={{
  400. num: partitionNum,
  401. func: val => setPartitionNum(val),
  402. }}
  403. />
  404. )}
  405. {/* 按扭操作 */}
  406. {/* 表结构预览 */}
  407. {(currentStep === 0 || currentStep === 1 || currentStep === 3) && (
  408. <>
  409. {dataxJson !== null ||
  410. (currentStep !== 3 && (
  411. <Button
  412. type="primary"
  413. onClick={showDrawer}
  414. className="check__btn"
  415. size="large"
  416. icon={<MenuFoldOutlined />}>
  417. {currentStep !== 3 ? '表结构预览' : '构建'}
  418. </Button>
  419. ))}
  420. <Drawer
  421. title={currentStep === 3 ? '构建' : '表结构预览'}
  422. placement="right"
  423. onClose={onClose}
  424. visible={visible}
  425. width={600}
  426. mask={false}
  427. destroyOnClose={true}
  428. closeIcon={<MenuUnfoldOutlined style={{ color: '#1890ff' }} />}>
  429. <DataTableStruct
  430. currentStep={currentStep}
  431. tableData={currentStep === 0 ? drawDataStruct : loadDataStruct}
  432. datax={dataxJson}
  433. onRef={jsonValue}
  434. />
  435. </Drawer>
  436. </>
  437. )}
  438. {/* 按扭操作 */}
  439. <Space style={{ margin: '20px' }}>
  440. {currentStep === 0 && (
  441. <Button
  442. onClick={() => {
  443. navigate(-1)
  444. }}>
  445. 返回上一级
  446. </Button>
  447. )}
  448. {currentStep !== 0 && (
  449. <Button
  450. onClick={() => {
  451. changeStep(-1)
  452. }}
  453. disabled={building}>
  454. 上一步
  455. </Button>
  456. )}
  457. {currentStep === 3 ? (
  458. <Button
  459. type="primary"
  460. onClick={isfinishBuild ? submit : build}
  461. loading={isfinishBuild ? submiting : building}>
  462. {isfinishBuild ? '提交' : '构建'}
  463. </Button>
  464. ) : (
  465. <Button type="primary" onClick={nextStep}>
  466. 下一步
  467. </Button>
  468. )}
  469. </Space>
  470. </SyncTask>
  471. )
  472. }