dolphinscheduler2.0.5 HTTP任务类型改造

  • 背景
  • 改造思路
  • 方案一
  • 方案二
  • 改造方案二
  • 前端页面
  • 涉及代码
  • 前端
  • http.vue
  • zh_CN.js
  • 后端
  • HttpStatusTask
  • HttpParameters
  • HttpTask
  • pom.xml
  • 测试
  • 总结
  • 动态获取查询参数
  • 签名栏位
  • 其它


背景

在使用HTTP任务的时候,发现一个问题。当HTTP执行的任务是异步处理的时候,海豚调度获取到的信息是任务已成功执行,便判断为任务结束,然后开始执行下游任务。但实际第三方还在后台处理中,这个时候下游任务如果依赖该任务的处理结果,那么就产生问题了。因此就需要HTTP任务后台处理完成才可以继续执行下游任务。那就需要再提供一个查询状态的API,从而获取结果

改造思路

方案一

配置两个HTTP节点,startAPI–>statusAPI。
痛点在于查询API往往需要启动API的返回值作为查询条件。但是目前HTTP不支持参数传递,而且即便支持,参数值如何获取?比如从启动API的响应报文中解析获取。

方案二

在HTTP页面添加状态配置开关,输入状态查询URL。

改造方案二

前端页面

如果需要等待任务最终处理完再执行下游,则打开状态设置开关

hive sql 获取月末时间 hive获取上月末_hive sql 获取月末时间


最开始计划增加一个状态URL栏位,后来一边开发一边想,发现不够,结果就有了下面一堆栏位(就是重写了一遍上面的)

hive sql 获取月末时间 hive获取上月末_HttpTask_02

涉及代码

前端

http.vue
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements.  See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You under the Apache License, Version 2.0
* (the "License"); you may not use this file except in compliance with
* the License.  You may obtain a copy of the License at
*
*    http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
<template>
  <div class="http-model">
    <m-list-box>
      <div slot="text">{{$t('Http Url')}}</div>
      <div slot="content">
        <el-input
          :autosize="{minRows:2}"
          :disabled="isDetails"
          type="textarea"
          size="small"
          v-model="url"
          :placeholder="$t('Please Enter Http Url')"
          autocomplete="off">
        </el-input>
      </div>
    </m-list-box>
    <m-list-box>
      <div slot="text">{{$t('Http Method')}}</div>
      <div slot="content">
        <el-select
          style="width: 150px;"
          size="small"
          v-model="httpMethod"
          :disabled="isDetails">
          <el-option
            v-for="city in httpMethodList"
            :key="city.code"
            :value="city.code"
            :label="city.code">
          </el-option>
        </el-select>
      </div>
    </m-list-box>
    <m-list-box>
      <div slot="text">{{$t('Http Parameters')}}</div>
      <div slot="content">
        <m-http-params
          ref="refHttpParams"
          @on-http-params="_onHttpParams"
          :udp-list="httpParams"
          :hide="false">
        </m-http-params>
      </div>
    </m-list-box>
    <m-list-box>
      <div slot="text">{{$t('Http Check Condition')}}</div>
      <div slot="content">
        <el-select
          style="width: 230px;"
          size="small"
          v-model="httpCheckCondition"
          :disabled="isDetails">
          <el-option
            v-for="city in httpCheckConditionList"
            :key="city.code"
            :value="city.code"
            :label="city.value">
          </el-option>
        </el-select>
      </div>
    </m-list-box>
    <m-list-box>
      <div slot="text">{{$t('Http Condition')}}</div>
      <div slot="content">
        <el-input
          :autosize="{minRows:2}"
          :disabled="isDetails"
          type="textarea"
          size="small"
          v-model="condition"
          :placeholder="$t('Please Enter Http Condition')">
        </el-input>
      </div>
    </m-list-box>
    <m-list-box>
      <div slot="text">{{$t('Timeout Settings')}}</div>
      <div slot="content">
        <label class="label-box">
          <div style="padding-top: 5px;">
            <el-switch size="small" v-model="timeoutSettings" :disabled="isDetails"></el-switch>
          </div>
        </label>
      </div>
    </m-list-box>
    <m-list-box v-if="timeoutSettings">
      <div slot="text">{{$t('Connect Timeout')}}</div>
      <div slot="content">
        <span  class="label-box" style="width: 193px;display: inline-block;">
          <el-input size="small" v-model='connectTimeout' maxlength="7"></el-input>
        </span>
        <span>{{$t('ms')}}</span>
        <span class="text-b">{{$t('Socket Timeout')}}</span>
        <span  class="label-box" style="width: 193px;display: inline-block;">
          <el-input size="small" v-model='socketTimeout' maxlength="7"></el-input>
        </span>
        <span>{{$t('ms')}}</span>
      </div>
    </m-list-box>
    <m-list-box>
      <div slot="text">{{$t('Custom Parameters')}}</div>
      <div slot="content">
        <m-local-params
          ref="refLocalParams"
          @on-local-params="_onLocalParams"
          :udp-list="localParams"
          :hide="false">
        </m-local-params>
      </div>
    </m-list-box>
    <!-- lw 20220415 start =========================================== -->
     <m-list-box>
      <div slot="text">{{$t('Status Settings')}}</div>
      <div slot="content">
        <label class="label-box">
          <div style="padding-top: 5px;">
            <el-switch size="small" v-model="statusSettings" :disabled="isDetails"></el-switch>
          </div>
        </label>
      </div>
    </m-list-box>
    <hr style="margin-left: 80px;">
    <m-list-box v-if="statusSettings">
      <div slot="text">{{$t('Status url')}}</div>
      <div slot="content">
        <el-input
          :autosize="{minRows:2}"
          :disabled="isDetails"
          type="textarea"
          size="small"
          v-model="statusUrl"
          :placeholder="$t('Please Enter Status Http Url')"
          autocomplete="off">
        </el-input>
      </div>
    </m-list-box>
    <m-list-box  v-if="statusSettings">
      <div slot="text">{{$t('Status Request Settings')}}</div>
      <div slot="content">
        <label class="label-box">
          <div style="padding-top: 5px;">
            <el-switch size="small" v-model="statusRequestSettings" :disabled="isDetails"></el-switch>
          </div>
        </label>
      </div>
    </m-list-box>
    <m-list-box v-if="statusRequestSettings">
      <div slot="text">{{$t('Query Interval')}}</div>
      <div slot="content">
        <span  class="label-box" style="width: 193px;display: inline-block;">
          <el-input size="small" v-model='queryInterval' maxlength="7"></el-input>
        </span>
        <span>{{$t('ms')}}</span>
        <span class="text-b">{{$t('Query Times')}}</span>
        <span  class="label-box" style="width: 193px;display: inline-block;">
          <el-input size="small" v-model='queryTimes' maxlength="7"></el-input>
        </span>
        <span>{{$t('Times')}}</span>
      </div>
    </m-list-box>
    <m-list-box v-if="statusSettings">
      <div slot="text">{{$t('Status Http Method')}}</div>
      <div slot="content">
        <el-select
          style="width: 150px;"
          size="small"
          v-model="statusHttpMethod"
          :disabled="isDetails">
          <el-option
            v-for="city in httpMethodList"
            :key="city.code"
            :value="city.code"
            :label="city.code">
          </el-option>
        </el-select>
      </div>
    </m-list-box>
    <m-list-box v-if="statusSettings">
      <div slot="text">{{$t('Status Http Parameters')}}</div>
      <div slot="content">
        <m-http-params
          ref="refHttpParams"
          @on-http-params="_onStatusHttpParams"
          :udp-list="statusHttpParams"
          :hide="false">
        </m-http-params>
      </div>
    </m-list-box>
    <m-list-box v-if="statusSettings">
      <div slot="text">{{$t('Status Http Check Condition')}}</div>
      <div slot="content">
        <el-select
          style="width: 230px;"
          size="small"
          v-model="statusHttpCheckCondition"
          :disabled="isDetails">
          <el-option
            v-for="city in httpCheckConditionList"
            :key="city.code"
            :value="city.code"
            :label="city.value">
          </el-option>
        </el-select>
      </div>
    </m-list-box>
    <m-list-box v-if="statusSettings">
      <div slot="text">{{$t('Status Http Condition')}}</div>
      <div slot="content">
        <el-input
          :autosize="{minRows:2}"
          :disabled="isDetails"
          type="textarea"
          size="small"
          v-model="statusCondition"
          :placeholder="$t('Please Enter Status Http Condition')">
        </el-input>
      </div>
    </m-list-box>
    <m-list-box v-if="statusSettings">
      <div slot="text">{{$t('Sign Check')}}</div>
      <div slot="content">
        <label class="label-box">
          <div style="padding-top: 5px;">
            <el-switch size="small" v-model="signcheck" :disabled="isDetails"></el-switch>
          </div>
        </label>
      </div>
    </m-list-box>
    <m-list-box v-if="statusSettings">
      <div slot="text">{{$t('Status Custom Parameters')}}</div>
      <div slot="content">
        <m-local-params
          ref="refStatusLocalParams"
          @on-local-params="_onStatusLocalParams"
          :udp-list="statusLocalParams"
          :hide="false">
        </m-local-params>
      </div>
    </m-list-box>
     <!-- lw 20220415 end =========================================== -->
  </div>

</template>
<script>
  import _ from 'lodash'
  import i18n from '@/module/i18n'
  import cookies from 'js-cookie'
  import mLocalParams from './_source/localParams'
  import mHttpParams from './_source/httpParams'
  import mListBox from './_source/listBox'
  import disabledState from '@/module/mixin/disabledState'
  export default {
    name: 'http',
    data () {
      return {
        timeoutSettings: false,
        connectTimeout: 60000,
        socketTimeout: 60000,

        url: '',
        condition: '',
        localParams: [],
        httpParams: [],
        httpMethod: 'GET',
        statusLocalParams: [],
        statusHttpParams: [],
        statusHttpMethod: 'GET',
        httpMethodList: [{ code: 'GET' }, { code: 'POST' }, { code: 'HEAD' }, { code: 'PUT' }, { code: 'DELETE' }],
        httpCheckCondition: 'STATUS_CODE_DEFAULT',
        httpCheckConditionList: cookies.get('language') === 'en_US' ? [{ code: 'STATUS_CODE_DEFAULT', value: 'Default response code 200' }, { code: 'STATUS_CODE_CUSTOM', value: 'Custom response code' }, { code: 'BODY_CONTAINS', value: 'Content includes' }, { code: 'BODY_NOT_CONTAINS', value: 'Content does not contain' }] : [{ code: 'STATUS_CODE_DEFAULT', value: '默认响应码200' }, { code: 'STATUS_CODE_CUSTOM', value: '自定义响应码' }, { code: 'BODY_CONTAINS', value: '内容包含' }, { code: 'BODY_NOT_CONTAINS', value: '内容不包含' }],
        statusSettings: false, // lw 20220415 start
        statusUrl: '',
        signcheck: false,
        sign: 0,
        statusRequestSettings: false,
        queryInterval: 600000,
        statusCondition: '',
        statusHttpCheckCondition: 'STATUS_CODE_DEFAULT',
        statusHttpCheckConditionList: cookies.get('language') === 'en_US' ? [{ code: 'STATUS_CODE_DEFAULT', value: 'Default response code 200' }, { code: 'STATUS_CODE_CUSTOM', value: 'Custom response code' }, { code: 'BODY_CONTAINS', value: 'Content includes' }, { code: 'BODY_NOT_CONTAINS', value: 'Content does not contain' }] : [{ code: 'STATUS_CODE_DEFAULT', value: '默认响应码200' }, { code: 'STATUS_CODE_CUSTOM', value: '自定义响应码' }, { code: 'BODY_CONTAINS', value: '内容包含' }, { code: 'BODY_NOT_CONTAINS', value: '内容不包含' }],
        queryTimes: 10 // lw 20220418 end
      }
    },
    props: {
      backfillItem: {}
    },
    mixins: [disabledState],
    methods: {
      /**
       * return localParams
       */
      _onLocalParams (a) {
        this.localParams = a
      },
      _onHttpParams (a) {
        this.httpParams = a
      },
      // lw 20220415 start
      _onStatusLocalParams (a) {
        this.statusLocalParams = a
      },
      _onStatusHttpParams (a) {
        this.statusHttpParams = a
      },
      // lw 20220415 end
      /**
       * verification
       */
      _verification () {
        if (!this.url) {
          this.$message.warning(`${i18n.$t('Please Enter Http Url')}`)
          return false
        }
        if (this.statusSettings) { // lw 20220415 comb-update add
          if (!this.statusUrl) {
            this.$message.warning(`${i18n.$t('Please Enter Status Http Url')}`)
            return false
          }
        } else {
          this.statusUrl = ''
        }
        if (!_.isNumber(parseInt(this.queryInterval))) {
          this.$message.warning(`${i18n.$t('Query Interval be a positive integer')}`)
          return false
        }
        if (!_.isNumber(parseInt(this.queryTimes))) {
          this.$message.warning(`${i18n.$t('Query Times be a positive integer')}`)
          return false
        }
        // localParams Subcomponent verification
        if (!this.$refs.refLocalParams._verifProp()) {
          return false
        }
        if (!this.$refs.refHttpParams._verifProp()) {
          return false
        }
        if (!this.$refs.refHttpParams._verifValue()) {
          return false
        }
        if (!_.isNumber(parseInt(this.socketTimeout))) {
          this.$message.warning(`${i18n.$t('Socket Timeout be a positive integer')}`)
          return false
        }
        if (!_.isNumber(parseInt(this.connectTimeout))) {
          this.$message.warning(`${i18n.$t('Connect timeout be a positive integer')}`)
          return false
        }
        // storage
        this.$emit('on-params', {
          localParams: this.localParams,
          httpParams: this.httpParams,
          url: this.url,
          httpMethod: this.httpMethod,
          httpCheckCondition: this.httpCheckCondition,
          condition: this.condition,
          connectTimeout: this.connectTimeout,
          socketTimeout: this.socketTimeout,
          // statusSettings: this.statusSettings,// lw 20220415
          statusUrl: this.statusUrl, // lw 20220415
          sign: this.signcheck ? 1 : 0, // lw 20220415
          statusLocalParams: this.statusLocalParams,
          statusHttpParams: this.statusHttpParams,
          queryInterval: this.queryInterval,
          queryTimes: this.queryTimes,
          statusHttpCheckCondition: this.statusHttpCheckCondition,
          statusCondition: this.statusCondition,
          statusHttpMethod: this.statusHttpMethod
        })
        return true
      }
    },
    computed: {
      cacheParams () {
        return {
          localParams: this.localParams,
          httpParams: this.httpParams,
          url: this.url,
          httpMethod: this.httpMethod,
          httpCheckCondition: this.httpCheckCondition,
          condition: this.condition,
          connectTimeout: this.connectTimeout,
          socketTimeout: this.socketTimeout,
          // statusSettings: this.statusSettings,// lw 20220415
          statusUrl: this.statusUrl, // lw 20220415
          sign: this.sign, // lw 20220415
          statusLocalParams: this.statusLocalParams,
          statusHttpParams: this.statusHttpParams,
          queryInterval: this.queryInterval,
          queryTimes: this.queryTimes,
          statusHttpCheckCondition: this.statusHttpCheckCondition,
          statusCondition: this.statusCondition,
          statusHttpMethod: this.statusHttpMethod
        }
      }
    },
    watch: {
      /**
       * Watch the cacheParams
       * @param val
       */
      cacheParams (val) {
        this.$emit('on-cache-params', val)
      }
    },
    created () {
      let o = this.backfillItem
      // Non-null objects represent backfill
      if (!_.isEmpty(o)) {
        this.url = o.params.url || ''
        this.httpMethod = o.params.httpMethod || 'GET'
        this.statusHttpMethod = o.params.statusHttpMethod || 'GET'
        this.httpCheckCondition = o.params.httpCheckCondition || 'DEFAULT'
        this.condition = o.params.condition || ''
        this.statusHttpCheckCondition = o.params.statusHttpCheckCondition
        this.statusCondition = o.params.statusCondition || ''
        this.connectTimeout = o.params.connectTimeout
        this.socketTimeout = o.params.socketTimeout
        if (this.connectTimeout !== 60000 || this.socketTimeout !== 60000) {
          this.timeoutSettings = true
        }
        this.queryInterval = o.params.queryInterval
        this.queryTimes = o.params.queryTimes
        if (this.queryInterval !== 600000 || this.queryTimes !== 10) {
          this.statusRequestSettings = true
        }
        // backfill localParams
        let localParams = o.params.localParams || []
        if (localParams.length) {
          this.localParams = localParams
        }
        let httpParams = o.params.httpParams || []
        if (httpParams.length) {
          this.httpParams = httpParams
        }
        // lw 20220415 comb-update start
        this.statusUrl = o.params.statusUrl
        this.sign = o.params.sign
        if (this.statusUrl) {
          this.statusSettings = true
        }
        if (this.sign === 1) {
          this.signcheck = true
        } else {
          this.signcheck = false
        }
        // backfill localParams
        let statusLocalParams = o.params.statusLocalParams || []
        if (statusLocalParams.length) {
          this.statusLocalParams = statusLocalParams
        }
        let statusHttpParams = o.params.statusHttpParams || []
        if (statusHttpParams.length) {
          this.statusHttpParams = statusHttpParams
        }
        // lw 20220415 comb-update end
      }
    },
    mounted () {
    },
    components: { mLocalParams, mHttpParams, mListBox }
  }
</script>
zh_CN.js
/*
 * Licensed to the Apache Software Foundation (ASF) under one or more
 * contributor license agreements.  See the NOTICE file distributed with
 * this work for additional information regarding copyright ownership.
 * The ASF licenses this file to You under the Apache License, Version 2.0
 * (the "License"); you may not use this file except in compliance with
 * the License.  You may obtain a copy of the License at
 *
 *    http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

export default {
  'User Name': '用户名',
  'Please enter user name': '请输入用户名',
  Password: '密码',
  'Please enter your password': '请输入密码',
  'Password consists of at least two combinations of numbers, letters, and characters, and the length is between 6-22': '密码至少包含数字,字母和字符的两种组合,长度在6-22之间',
  Login: '登录',
  Home: '首页',
  'Failed to create node to save': '未创建节点保存失败',
  'Global parameters': '全局参数',
  'Local parameters': '局部参数',
  'Copy success': '复制成功',
  'The browser does not support automatic copying': '该浏览器不支持自动复制',
  'Whether to save the DAG graph': '是否保存DAG图',
  'Current node settings': '当前节点设置',
  'View history': '查看历史',
  'View log': '查看日志',
  'Force success': '强制成功',
  'Enter this child node': '进入该子节点',
  'Node name': '节点名称',
  'Please enter name (required)': '请输入名称(必填)',
  'Run flag': '运行标志',
  Normal: '正常',
  'Prohibition execution': '禁止执行',
  'Please enter description': '请输入描述',
  'Number of failed retries': '失败重试次数',
  Times: '次',
  'Failed retry interval': '失败重试间隔',
  Minute: '分',
  'Delay execution time': '延时执行时间',
  'Delay execution': '延时执行',
  'Forced success': '强制成功',
  Cancel: '取消',
  'Confirm add': '确认添加',
  'The newly created sub-Process has not yet been executed and cannot enter the sub-Process': '新创建子工作流还未执行,不能进入子工作流',
  'The task has not been executed and cannot enter the sub-Process': '该任务还未执行,不能进入子工作流',
  'Name already exists': '名称已存在请重新输入',
  'Download Log': '下载日志',
  'Refresh Log': '刷新日志',
  'Enter full screen': '进入全屏',
  'Cancel full screen': '取消全屏',
  Close: '关闭',
  'Update log success': '更新日志成功',
  'No more logs': '暂无更多日志',
  'No log': '暂无日志',
  'Loading Log...': '正在努力请求日志中...',
  'Set the DAG diagram name': '设置DAG图名称',
  'Please enter description(optional)': '请输入描述(选填)',
  'Set global': '设置全局',
  'Whether to go online the process definition': '是否上线流程定义',
  'Whether to update the process definition': '是否更新流程定义',
  Add: '添加',
  'DAG graph name cannot be empty': 'DAG图名称不能为空',
  'Create Datasource': '创建数据源',
  'Project Home': '工作流监控',
  'Project Manage': '项目管理',
  'Create Project': '创建项目',
  'Cron Manage': '定时管理',
  'Copy Workflow': '复制工作流',
  'Tenant Manage': '租户管理',
  'Create Tenant': '创建租户',
  'User Manage': '用户管理',
  'Create User': '创建用户',
  'User Information': '用户信息',
  'Edit Password': '密码修改',
  Success: '成功',
  Failed: '失败',
  Delete: '删除',
  'Please choose': '请选择',
  'Please enter a positive integer': '请输入正整数',
  'Program Type': '程序类型',
  'Main Class': '主函数的Class',
  'Main Package': '主程序包',
  'Please enter main package': '请选择主程序包',
  'Please enter main class': '请填写主函数的Class',
  'Main Arguments': '主程序参数',
  'Please enter main arguments': '请输入主程序参数',
  'Option Parameters': '选项参数',
  'Please enter option parameters': '请输入选项参数',
  Resources: '资源',
  'Custom Parameters': '自定义参数',
  'Custom template': '自定义模版',
  Datasource: '数据源',
  methods: '方法',
  'Please enter the procedure method': '请输入存储脚本 \n\n调用存储过程:{call <procedure-name>[(<arg1>,<arg2>, ...)]}\n\n调用存储函数:{?= call <procedure-name>[(<arg1>,<arg2>, ...)]} ',
  'The procedure method script example': '示例:{call <procedure-name>[(?,?, ...)]} 或 {?= call <procedure-name>[(?,?, ...)]}',
  Script: '脚本',
  'Please enter script(required)': '请输入脚本(必填)',
  'Deploy Mode': '部署方式',
  'Driver Cores': 'Driver核心数',
  'Please enter Driver cores': '请输入Driver核心数',
  'Driver Memory': 'Driver内存数',
  'Please enter Driver memory': '请输入Driver内存数',
  'Executor Number': 'Executor数量',
  'Please enter Executor number': '请输入Executor数量',
  'The Executor number should be a positive integer': 'Executor数量为正整数',
  'Executor Memory': 'Executor内存数',
  'Please enter Executor memory': '请输入Executor内存数',
  'Executor Cores': 'Executor核心数',
  'Please enter Executor cores': '请输入Executor核心数',
  'Memory should be a positive integer': '内存数为数字',
  'Core number should be positive integer': '核心数为正整数',
  'Flink Version': 'Flink版本',
  'JobManager Memory': 'JobManager内存数',
  'Please enter JobManager memory': '请输入JobManager内存数',
  'TaskManager Memory': 'TaskManager内存数',
  'Please enter TaskManager memory': '请输入TaskManager内存数',
  'Slot Number': 'Slot数量',
  'Please enter Slot number': '请输入Slot数量',
  Parallelism: '并行度',
  'Custom Parallelism': '自定义并行度',
  'Please enter Parallelism': '请输入并行度',
  'Parallelism number should be positive integer': '并行度必须为正整数',
  'Parallelism tip': '如果存在大量任务需要补数时,可以利用自定义并行度将补数的任务线程设置成合理的数值,避免对服务器造成过大的影响',
  'TaskManager Number': 'TaskManager数量',
  'Please enter TaskManager number': '请输入TaskManager数量',
  'App Name': '任务名称',
  'Please enter app name(optional)': '请输入任务名称(选填)',
  'SQL Type': 'sql类型',
  'Send Email': '发送邮件',
  'Log display': '日志显示',
  'rows of result': '行查询结果',
  Title: '主题',
  'Please enter the title of email': '请输入邮件主题',
  Table: '表名',
  TableMode: '表格',
  Attachment: '附件',
  'SQL Parameter': 'sql参数',
  'SQL Statement': 'sql语句',
  'UDF Function': 'UDF函数',
  'Please enter a SQL Statement(required)': '请输入sql语句(必填)',
  'Please enter a JSON Statement(required)': '请输入json语句(必填)',
  'One form or attachment must be selected': '表格、附件必须勾选一个',
  'Mail subject required': '邮件主题必填',
  'Child Node': '子节点',
  'Please select a sub-Process': '请选择子工作流',
  Edit: '编辑',
  'Switch To This Version': '切换到该版本',
  'Datasource Name': '数据源名称',
  'Please enter datasource name': '请输入数据源名称',
  IP: 'IP主机名',
  'Please enter IP': '请输入IP主机名',
  Port: '端口',
  'Please enter port': '请输入端口',
  'Database Name': '数据库名',
  'Please enter database name': '请输入数据库名',
  'Oracle Connect Type': '服务名或SID',
  'Oracle Service Name': '服务名',
  'Oracle SID': 'SID',
  'jdbc connect parameters': 'jdbc连接参数',
  'Test Connect': '测试连接',
  'Please enter resource name': '请输入数据源名称',
  'Please enter resource folder name': '请输入资源文件夹名称',
  'Please enter a non-query SQL statement': '请输入非查询sql语句',
  'Please enter IP/hostname': '请输入IP/主机名',
  'jdbc connection parameters is not a correct JSON format': 'jdbc连接参数不是一个正确的JSON格式',
  '#': '编号',
  'Datasource Type': '数据源类型',
  'Datasource Parameter': '数据源参数',
  'Create Time': '创建时间',
  'Update Time': '更新时间',
  Operation: '操作',
  'Current Version': '当前版本',
  'Click to view': '点击查看',
  'Delete?': '确定删除吗?',
  'Switch Version Successfully': '切换版本成功',
  'Confirm Switch To This Version?': '确定切换到该版本吗?',
  Confirm: '确定',
  'Task status statistics': '任务状态统计',
  Number: '数量',
  State: '状态',
  'Dry-run flag': '空跑标识',
  'Process Status Statistics': '流程状态统计',
  'Process Definition Statistics': '流程定义统计',
  'Project Name': '项目名称',
  'Please enter name': '请输入名称',
  'Owned Users': '所属用户',
  'Process Pid': '进程Pid',
  'Zk registration directory': 'zk注册目录',
  cpuUsage: 'cpuUsage',
  memoryUsage: 'memoryUsage',
  'Last heartbeat time': '最后心跳时间',
  'Edit Tenant': '编辑租户',
  'OS Tenant Code': '操作系统租户',
  'Tenant Name': '租户名称',
  Queue: '队列',
  'Please select a queue': '默认为租户关联队列',
  'Please enter the os tenant code in English': '请输入操作系统租户只允许英文',
  'Please enter os tenant code in English': '请输入英文操作系统租户',
  'Please enter os tenant code': '请输入操作系统租户',
  'Please enter tenant Name': '请输入租户名称',
  'The os tenant code. Only letters or a combination of letters and numbers are allowed': '操作系统租户只允许字母或字母与数字组合',
  'Edit User': '编辑用户',
  Tenant: '租户',
  Email: '邮件',
  Phone: '手机',
  'User Type': '用户类型',
  'Please enter phone number': '请输入手机',
  'Please enter email': '请输入邮箱',
  'Please enter the correct email format': '请输入正确的邮箱格式',
  'Please enter the correct mobile phone format': '请输入正确的手机格式',
  Project: '项目',
  Authorize: '授权',
  'File resources': '文件资源',
  'UDF resources': 'UDF资源',
  'UDF resources directory': 'UDF资源目录',
  'Please select UDF resources directory': '请选择UDF资源目录',
  'Alarm group': '告警组',
  'Alarm group required': '告警组必填',
  'Edit alarm group': '编辑告警组',
  'Create alarm group': '创建告警组',
  'Create Alarm Instance': '创建告警实例',
  'Edit Alarm Instance': '编辑告警实例',
  'Group Name': '组名称',
  'Alarm instance name': '告警实例名称',
  'Alarm plugin name': '告警插件名称',
  'Select plugin': '选择插件',
  'Select Alarm plugin': '请选择告警插件',
  'Please enter group name': '请输入组名称',
  'Instance parameter exception': '实例参数异常',
  'Group Type': '组类型',
  'Alarm plugin instance': '告警插件实例',
  'Please enter alarm plugin instance name': '请输入告警实例名称',
  'Select Alarm plugin instance': '请选择告警插件实例',
  Remarks: '备注',
  SMS: '短信',
  'Managing Users': '管理用户',
  Permission: '权限',
  Administrator: '管理员',
  'Confirm Password': '确认密码',
  'Please enter confirm password': '请输入确认密码',
  'Password cannot be in Chinese': '密码不能为中文',
  'Please enter a password (6-22) character password': '请输入密码(6-22)字符密码',
  'Confirmation password cannot be in Chinese': '确认密码不能为中文',
  'Please enter a confirmation password (6-22) character password': '请输入确认密码(6-22)字符密码',
  'The password is inconsistent with the confirmation password': '密码与确认密码不一致,请重新确认',
  'Please select the datasource': '请选择数据源',
  'Please select resources': '请选择资源',
  Query: '查询',
  'Non Query': '非查询',
  'prop(required)': 'prop(必填)',
  'value(optional)': 'value(选填)',
  'value(required)': 'value(必填)',
  'prop is empty': 'prop不能为空',
  'value is empty': 'value不能为空',
  'prop is repeat': 'prop中有重复',
  'Start Time': '开始时间',
  'End Time': '结束时间',
  crontab: 'crontab',
  'Failure Strategy': '失败策略',
  online: '上线',
  offline: '下线',
  'Task Status': '任务状态',
  'Process Instance': '工作流实例',
  'Task Instance': '任务实例',
  'Select date range': '选择日期区间',
  startDate: '开始日期',
  endDate: '结束日期',
  Date: '日期',
  Waiting: '等待',
  Execution: '执行中',
  Finish: '完成',
  'Create File': '创建文件',
  'Create folder': '创建文件夹',
  'File Name': '文件名称',
  'Folder Name': '文件夹名称',
  'File Format': '文件格式',
  'Folder Format': '文件夹格式',
  'File Content': '文件内容',
  'Upload File Size': '文件大小不能超过1G',
  Create: '创建',
  'Please enter the resource content': '请输入资源内容',
  'Resource content cannot exceed 3000 lines': '资源内容不能超过3000行',
  'File Details': '文件详情',
  'Download Details': '下载详情',
  Return: '返回',
  Save: '保存',
  'File Manage': '文件管理',
  'Upload Files': '上传文件',
  'Create UDF Function': '创建UDF函数',
  'Upload UDF Resources': '上传UDF资源',
  'Service-Master': '服务管理-Master',
  'Service-Worker': '服务管理-Worker',
  'Process Name': '工作流名称',
  Executor: '执行用户',
  'Run Type': '运行类型',
  'Scheduling Time': '调度时间',
  'Run Times': '运行次数',
  host: 'host',
  'fault-tolerant sign': '容错标识',
  Rerun: '重跑',
  'Recovery Failed': '恢复失败',
  Stop: '停止',
  Pause: '暂停',
  'Recovery Suspend': '恢复运行',
  Gantt: '甘特图',
  'Node Type': '节点类型',
  'Submit Time': '提交时间',
  Duration: '运行时长',
  'Retry Count': '重试次数',
  'Task Name': '任务名称',
  'Task Date': '任务日期',
  'Source Table': '源表',
  'Record Number': '记录数',
  'Target Table': '目标表',
  'Online viewing type is not supported': '不支持在线查看类型',
  Size: '大小',
  Rename: '重命名',
  Download: '下载',
  Export: '导出',
  'Version Info': '版本信息',
  Submit: '提交',
  'Edit UDF Function': '编辑UDF函数',
  type: '类型',
  'UDF Function Name': 'UDF函数名称',
  FILE: '文件',
  UDF: 'UDF',
  'File Subdirectory': '文件子目录',
  'Please enter a function name': '请输入函数名',
  'Package Name': '包名类名',
  'Please enter a Package name': '请输入包名类名',
  Parameter: '参数',
  'Please enter a parameter': '请输入参数',
  'UDF Resources': 'UDF资源',
  'Upload Resources': '上传资源',
  Instructions: '使用说明',
  'Please enter a instructions': '请输入使用说明',
  'Please enter a UDF function name': '请输入UDF函数名称',
  'Select UDF Resources': '请选择UDF资源',
  'Class Name': '类名',
  'Jar Package': 'jar包',
  'Library Name': '库名',
  'UDF Resource Name': 'UDF资源名称',
  'File Size': '文件大小',
  Description: '描述',
  'Drag Nodes and Selected Items': '拖动节点和选中项',
  'Select Line Connection': '选择线条连接',
  'Delete selected lines or nodes': '删除选中的线或节点',
  'Full Screen': '全屏',
  Unpublished: '未发布',
  'Start Process': '启动工作流',
  'Execute from the current node': '从当前节点开始执行',
  'Recover tolerance fault process': '恢复被容错的工作流',
  'Resume the suspension process': '恢复运行流程',
  'Execute from the failed nodes': '从失败节点开始执行',
  'Complement Data': '补数',
  'Scheduling execution': '调度执行',
  'Recovery waiting thread': '恢复等待线程',
  'Submitted successfully': '提交成功',
  Executing: '正在执行',
  'Ready to pause': '准备暂停',
  'Ready to stop': '准备停止',
  'Need fault tolerance': '需要容错',
  Kill: 'Kill',
  'Waiting for thread': '等待线程',
  'Waiting for dependence': '等待依赖',
  Start: '运行',
  Copy: '复制节点',
  'Copy name': '复制名称',
  'Copy path': '复制路径',
  'Please enter keyword': '请输入关键词',
  'File Upload': '文件上传',
  'Drag the file into the current upload window': '请将文件拖拽到当前上传窗口内!',
  'Drag area upload': '拖动区域上传',
  Upload: '上传',
  'ReUpload File': '重新上传文件',
  'Please enter file name': '请输入文件名',
  'Please select the file to upload': '请选择要上传的文件',
  'Resources manage': '资源中心',
  Security: '安全中心',
  Logout: '退出',
  'No data': '查询无数据',
  'Uploading...': '文件上传中',
  'Loading...': '正在努力加载中...',
  List: '列表',
  'Unable to download without proper url': '无下载url无法下载',
  Process: '工作流',
  'Process definition': '工作流定义',
  'Task record': '任务记录',
  'Warning group manage': '告警组管理',
  'Warning instance manage': '告警实例管理',
  'Servers manage': '服务管理',
  'UDF manage': 'UDF管理',
  'Resource manage': '资源管理',
  'Function manage': '函数管理',
  'Edit password': '修改密码',
  'Ordinary users': '普通用户',
  'Create process': '创建工作流',
  'Import process': '导入工作流',
  'Timing state': '定时状态',
  Timing: '定时',
  Timezone: '时区',
  TreeView: '树形图',
  'Mailbox already exists! Recipients and copyers cannot repeat': '邮箱已存在!收件人和抄送人不能重复',
  'Mailbox input is illegal': '邮箱输入不合法',
  'Please set the parameters before starting': '启动前请先设置参数',
  Continue: '继续',
  End: '结束',
  'Node execution': '节点执行',
  'Backward execution': '向后执行',
  'Forward execution': '向前执行',
  'Execute only the current node': '仅执行当前节点',
  'Notification strategy': '通知策略',
  'Notification group': '通知组',
  'Please select a notification group': '请选择通知组',
  'Whether it is a complement process?': '是否补数',
  'Schedule date': '调度日期',
  'Mode of execution': '执行方式',
  'Serial execution': '串行执行',
  'Parallel execution': '并行执行',
  'Set parameters before timing': '定时前请先设置参数',
  'Start and stop time': '起止时间',
  'Please select time': '请选择时间',
  'Please enter crontab': '请输入crontab',
  none_1: '都不发',
  success_1: '成功发',
  failure_1: '失败发',
  All_1: '成功或失败都发',
  Toolbar: '工具栏',
  'View variables': '查看变量',
  'Format DAG': '格式化DAG',
  'Refresh DAG status': '刷新DAG状态',
  Return_1: '返回上一节点',
  'Please enter format': '请输入格式为',
  'connection parameter': '连接参数',
  'Process definition details': '流程定义详情',
  'Create process definition': '创建流程定义',
  'Scheduled task list': '定时任务列表',
  'Process instance details': '流程实例详情',
  'Create Resource': '创建资源',
  'User Center': '用户中心',
  AllStatus: '全部状态',
  None: '无',
  Name: '名称',
  'Process priority': '流程优先级',
  'Task priority': '任务优先级',
  'Task timeout alarm': '任务超时告警',
  'Timeout strategy': '超时策略',
  'Timeout alarm': '超时告警',
  'Timeout failure': '超时失败',
  'Timeout period': '超时时长',
  'Waiting Dependent complete': '等待依赖完成',
  'Waiting Dependent start': '等待依赖启动',
  'Check interval': '检查间隔',
  'Timeout must be longer than check interval': '超时时间必须比检查间隔长',
  'Timeout strategy must be selected': '超时策略必须选一个',
  'Timeout must be a positive integer': '超时时长必须为正整数',
  'Add dependency': '添加依赖',
  'Add driven': '添加驱动',
  'Whether dry-run': '是否空跑',
  and: '且',
  or: '或',
  month: '月',
  week: '周',
  day: '日',
  hour: '时',
  Running: '正在运行',
  'Waiting for dependency to complete': '等待依赖完成',
  Selected: '已选',
  CurrentHour: '当前小时',
  Last1Hour: '前1小时',
  Last2Hours: '前2小时',
  Last3Hours: '前3小时',
  Last24Hours: '前24小时',
  today: '今天',
  Last1Days: '昨天',
  Last2Days: '前两天',
  Last3Days: '前三天',
  Last7Days: '前七天',
  ThisWeek: '本周',
  LastWeek: '上周',
  LastMonday: '上周一',
  LastTuesday: '上周二',
  LastWednesday: '上周三',
  LastThursday: '上周四',
  LastFriday: '上周五',
  LastSaturday: '上周六',
  LastSunday: '上周日',
  ThisMonth: '本月',
  LastMonth: '上月',
  LastMonthBegin: '上月初',
  LastMonthEnd: '上月末',
  'Refresh status succeeded': '刷新状态成功',
  'Queue manage': 'Yarn 队列管理',
  'Create queue': '创建队列',
  'Edit queue': '编辑队列',
  'Datasource manage': '数据源中心',
  'History task record': '历史任务记录',
  'Please go online': '不要忘记上线',
  'Queue value': '队列值',
  'Please enter queue value': '请输入队列值',
  'Worker group manage': 'Worker分组管理',
  'Create worker group': '创建Worker分组',
  'Edit worker group': '编辑Worker分组',
  'Token manage': '令牌管理',
  'Create token': '创建令牌',
  'Edit token': '编辑令牌',
  Addresses: '地址',
  'Worker Addresses': 'Worker地址',
  'Please select the worker addresses': '请选择Worker地址',
  'Failure time': '失效时间',
  'Expiration time': '失效时间',
  User: '用户',
  'Please enter token': '请输入令牌',
  'Generate token': '生成令牌',
  Monitor: '监控中心',
  Group: '分组',
  'Queue statistics': '队列统计',
  'Command status statistics': '命令状态统计',
  'Task kill': '等待kill任务',
  'Task queue': '等待执行任务',
  'Error command count': '错误指令数',
  'Normal command count': '正确指令数',
  Manage: '管理',
  'Number of connections': '连接数',
  Sent: '发送量',
  Received: '接收量',
  'Min latency': '最低延时',
  'Avg latency': '平均延时',
  'Max latency': '最大延时',
  'Node count': '节点数',
  'Query time': '当前查询时间',
  'Node self-test status': '节点自检状态',
  'Health status': '健康状态',
  'Max connections': '最大连接数',
  'Threads connections': '当前连接数',
  'Max used connections': '同时使用连接最大数',
  'Threads running connections': '数据库当前活跃连接数',
  'Worker group': 'Worker分组',
  'Please enter a positive integer greater than 0': '请输入大于 0 的正整数',
  'Pre Statement': '前置sql',
  'Post Statement': '后置sql',
  'Statement cannot be empty': '语句不能为空',
  'Process Define Count': '工作流定义数',
  'Process Instance Running Count': '正在运行的流程数',
  'command number of waiting for running': '待执行的命令数',
  'failure command number': '执行失败的命令数',
  'tasks number of waiting running': '待运行任务数',
  'task number of ready to kill': '待杀死任务数',
  'Statistics manage': '统计管理',
  statistics: '统计',
  'select tenant': '选择租户',
  'Please enter Principal': '请输入Principal',
  'Please enter the kerberos authentication parameter java.security.krb5.conf': '请输入kerberos认证参数 java.security.krb5.conf',
  'Please enter the kerberos authentication parameter login.user.keytab.username': '请输入kerberos认证参数 login.user.keytab.username',
  'Please enter the kerberos authentication parameter login.user.keytab.path': '请输入kerberos认证参数 login.user.keytab.path',
  'The start time must not be the same as the end': '开始时间和结束时间不能相同',
  'Startup parameter': '启动参数',
  'Startup type': '启动类型',
  'warning of timeout': '超时告警',
  'Next five execution times': '接下来五次执行时间',
  'Execute time': '执行时间',
  'Complement range': '补数范围',
  'Http Url': '请求地址',
  'Http Method': '请求类型',
  'Http Parameters': '请求参数',
  'Http Parameters Key': '参数名',
  'Http Parameters Position': '参数位置',
  'Http Parameters Value': '参数值',
  'Http Check Condition': '校验条件',
  'Http Condition': '校验内容',
  'Please Enter Http Url': '请填写请求地址(必填)',
  'Please Enter Http Condition': '请填写校验内容',
  'There is no data for this period of time': '该时间段无数据',
  'Worker addresses cannot be empty': 'Worker地址不能为空',
  'Please generate token': '请生成Token',
  'Please Select token': '请选择Token失效时间',
  'Spark Version': 'Spark版本',
  TargetDataBase: '目标库',
  TargetTable: '目标表',
  TargetJobName: '目标任务名',
  'Please enter Pigeon job name': '请输入Pigeon任务名',
  'Please enter the table of target': '请输入目标表名',
  'Please enter a Target Table(required)': '请输入目标表(必填)',
  SpeedByte: '限流(字节数)',
  SpeedRecord: '限流(记录数)',
  '0 means unlimited by byte': 'KB,0代表不限制',
  '0 means unlimited by count': '0代表不限制',
  'Modify User': '修改用户',
  'Whether directory': '是否文件夹',
  Yes: '是',
  No: '否',
  'Hadoop Custom Params': 'Hadoop参数',
  'Sqoop Advanced Parameters': 'Sqoop参数',
  'Sqoop Job Name': '任务名称',
  'Please enter Mysql Database(required)': '请输入Mysql数据库(必填)',
  'Please enter Mysql Table(required)': '请输入Mysql表名(必填)',
  'Please enter Columns (Comma separated)': '请输入列名,用 , 隔开',
  'Please enter Target Dir(required)': '请输入目标路径(必填)',
  'Please enter Export Dir(required)': '请输入数据源路径(必填)',
  'Please enter Hive Database(required)': '请输入Hive数据库(必填)',
  'Please enter Hive Table(required)': '请输入Hive表名(必填)',
  'Please enter hive target dir': '请输入Hive临时目录',
  'Please enter Hive Partition Keys': '请输入分区键',
  'Please enter Hive Partition Values': '请输入分区值',
  'Please enter Replace Delimiter': '请输入替换分隔符',
  'Please enter Fields Terminated': '请输入列分隔符',
  'Please enter Lines Terminated': '请输入行分隔符',
  'Please enter Concurrency': '请输入并发度',
  'Please enter Update Key': '请输入更新列',
  'Please enter Job Name(required)': '请输入任务名称(必填)',
  'Please enter Custom Shell(required)': '请输入自定义脚本',
  Direct: '流向',
  Type: '类型',
  ModelType: '模式',
  ColumnType: '列类型',
  Database: '数据库',
  Column: '列',
  'Map Column Hive': 'Hive类型映射',
  'Map Column Java': 'Java类型映射',
  'Export Dir': '数据源路径',
  'Hive partition Keys': 'Hive 分区键',
  'Hive partition Values': 'Hive 分区值',
  FieldsTerminated: '列分隔符',
  LinesTerminated: '行分隔符',
  IsUpdate: '是否更新',
  UpdateKey: '更新列',
  UpdateMode: '更新类型',
  'Target Dir': '目标路径',
  DeleteTargetDir: '是否删除目录',
  FileType: '保存格式',
  CompressionCodec: '压缩类型',
  CreateHiveTable: '是否创建新表',
  DropDelimiter: '是否删除分隔符',
  OverWriteSrc: '是否覆盖数据源',
  ReplaceDelimiter: '替换分隔符',
  Concurrency: '并发度',
  Form: '表单',
  OnlyUpdate: '只更新',
  AllowInsert: '无更新便插入',
  'Data Source': '数据来源',
  'Data Target': '数据目的',
  'All Columns': '全表导入',
  'Some Columns': '选择列',
  'Branch flow': '分支流转',
  'Custom Job': '自定义任务',
  'Custom Script': '自定义脚本',
  'Cannot select the same node for successful branch flow and failed branch flow': '成功分支流转和失败分支流转不能选择同一个节点',
  'Successful branch flow and failed branch flow are required': 'conditions节点成功和失败分支流转必填',
  'No resources exist': '不存在资源',
  'Please delete all non-existing resources': '请删除所有不存在资源',
  'Unauthorized or deleted resources': '未授权或已删除资源',
  'Please delete all non-existent resources': '请删除所有未授权或已删除资源',
  Kinship: '工作流关系',
  Reset: '重置',
  KinshipStateActive: '当前选择',
  KinshipState1: '已上线',
  KinshipState0: '工作流未上线',
  KinshipState10: '调度未上线',
  'Dag label display control': 'Dag节点名称显隐',
  Enable: '启用',
  Disable: '停用',
  'The Worker group no longer exists, please select the correct Worker group!': '该Worker分组已经不存在,请选择正确的Worker分组!',
  'Please confirm whether the workflow has been saved before downloading': '下载前请确定工作流是否已保存',
  'User name length is between 3 and 39': '用户名长度在3~39之间',
  'Timeout Settings': '超时设置',
  'Connect Timeout': '连接超时',
  'Socket Timeout': 'Socket超时',
  'Connect timeout be a positive integer': '连接超时必须为数字',
  'Socket Timeout be a positive integer': 'Socket超时必须为数字',
  ms: '毫秒',
  'Please Enter Url': '请直接填写地址,例如:127.0.0.1:7077',
  Master: 'Master',
  'Please select the waterdrop resources': '请选择waterdrop配置文件',
  zkDirectory: 'zk注册目录',
  'Directory detail': '查看目录详情',
  'Connection name': '连线名',
  'Current connection settings': '当前连线设置',
  'Please save the DAG before formatting': '格式化前请先保存DAG',
  'Batch copy': '批量复制',
  'Related items': '关联项目',
  'Project name is required': '项目名称必填',
  'Batch move': '批量移动',
  Version: '版本',
  'Pre tasks': '前置任务',
  'Running Memory': '运行内存',
  'Max Memory': '最大内存',
  'Min Memory': '最小内存',
  'The workflow canvas is abnormal and cannot be saved, please recreate': '该工作流画布异常,无法保存,请重新创建',
  Info: '提示',
  'Datasource userName': '所属用户',
  'Resource userName': '所属用户',
  'Environment manage': '环境管理',
  'Create environment': '创建环境',
  'Edit environment': '编辑',
  'Environment value': 'Environment value',
  'Environment Name': '环境名称',
  'Environment Code': '环境编码',
  'Environment Config': '环境配置',
  'Environment Desc': '详细描述',
  'Environment Worker Group': 'Worker组',
  'Please enter environment config': '请输入环境配置信息',
  'Please enter environment desc': '请输入详细描述',
  'Please select worker groups': '请选择Worker分组',
  condition: '条件',
  'The condition content cannot be empty': '条件内容不能为空',
  'Reference from': '使用已有任务',
  'No more...': '没有更多了...',
  'Task Definition': '任务定义',
  'Create task': '创建任务',
  'Task Type': '任务类型',
  'Process execute type': '执行策略',
  parallel: '并行',
  'Serial wait': '串行等待',
  'Serial discard': '串行抛弃',
  'Serial priority': '串行优先',
  'Recover serial wait': '串行恢复',
  // lw 20220325 comb-update add
  'enable parallel': '并行',
  IsEnableProxy: '启用代理',
  WebHook: 'Web钩子',
  webHook: 'Web钩子',
  Keyword: '关键词',
  Secret: '密钥',
  MsgType: '消息类型',
  AtMobiles: '@手机号',
  AtUserIds: '@用户ID',
  IsAtAll: '@所有人',
  Proxy: '代理',
  receivers: '收件人',
  receiverCcs: '抄送人',
  transportProtocol: '邮件协议',
  serverHost: 'SMTP服务器',
  serverPort: 'SMTP端口',
  sender: '发件人',
  enableSmtpAuth: '请求认证',
  starttlsEnable: 'STARTTLS连接',
  sslEnable: 'SSL连接',
  smtpSslTrust: 'SSL证书信任',
  url: 'URL',
  requestType: '请求方式',
  headerParams: '请求头',
  bodyParams: '请求体',
  contentField: '内容字段',
  path: '脚本路径',
  userParams: '自定义参数',
  corpId: '企业ID',
  secret: '密钥',
  teamSendMsg: '群发信息',
  userSendMsg: '群员信息',
  agentId: '应用ID',
  users: '群员',
  Username: '用户名',
  username: '用户名',
  showType: '内容展示类型',
  'Please select a task type (required)': '请选择任务类型(必选)',
  layoutType: '布局类型',
  gridLayout: '网格布局',
  dagreLayout: '层次布局',
  rows: '行数',
  cols: '列数',
  processOnline: '已上线',
  searchNode: '搜索节点',
  dagScale: '缩放',
  workflowName: '工作流名称',
  scheduleStartTime: '定时开始时间',
  scheduleEndTime: '定时结束时间',
  crontabExpression: 'Crontab',
  workflowPublishStatus: '工作流上线状态',
  schedulePublishStatus: '定时状态',
  CombTools: 'Comb系列工具',
  'Please select the combtool': '请选择Comb工具',
  'Please enter JobName (required)': '请输入作业名(必填)',
  JobName: '作业名称',
  'Status Settings': '状态设置',
  'Status url': '状态请求地址',
  'Sign Check': '签名校验',
  'Please Enter Status Http Url': '请填写获取状态请求地址(必填)',
  'Status Http Method': '状态请求类型',
  'Status Http Parameters': '状态请求参数',
  'Status Custom Parameters': '状态自定义参数',
  'Status Request Settings': '状态请求设置',
  'Query Interval': '查询间隔',
  'Query Times': '查询次数',
  'Query Interval be a positive integer': '查询间隔为数字',
  'Query Times be a positive integer': '查询次数为数字',
  'Status Http Check Condition': '状态校验条件',
  'Status Http Condition': '状态校验内容',
  'Please Enter Status Http Condition': '请填写状态校验内容'
}

后端

HttpStatusTask

该类是新增的,直接拷贝的HttpTask,然后主要在此基础上改造,HttpTask负责调用该类,减少对原代码的改动

/*
 * Licensed to the Apache Software Foundation (ASF) under one or more
 * contributor license agreements.  See the NOTICE file distributed with
 * this work for additional information regarding copyright ownership.
 * The ASF licenses this file to You under the Apache License, Version 2.0
 * (the "License"); you may not use this file except in compliance with
 * the License.  You may obtain a copy of the License at
 *
 *    http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package org.apache.dolphinscheduler.plugin.task.http;

import static org.apache.dolphinscheduler.plugin.task.http.HttpTaskConstants.APPLICATION_JSON;

import java.io.IOException;
import java.nio.charset.StandardCharsets;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

import org.apache.commons.collections4.CollectionUtils;
import org.apache.commons.io.Charsets;
import org.apache.dolphinscheduler.plugin.task.util.MapUtils;
import org.apache.dolphinscheduler.spi.task.Property;
import org.apache.dolphinscheduler.spi.task.paramparser.ParamUtils;
import org.apache.dolphinscheduler.spi.task.paramparser.ParameterUtils;
import org.apache.dolphinscheduler.spi.task.request.TaskRequest;
import org.apache.dolphinscheduler.spi.utils.DateUtils;
import org.apache.dolphinscheduler.spi.utils.JSONUtils;
import org.apache.dolphinscheduler.spi.utils.StringUtils;
import org.apache.http.HttpEntity;
import org.apache.http.ParseException;
import org.apache.http.client.config.RequestConfig;
import org.apache.http.client.methods.CloseableHttpResponse;
import org.apache.http.client.methods.HttpUriRequest;
import org.apache.http.client.methods.RequestBuilder;
import org.apache.http.entity.StringEntity;
import org.apache.http.impl.client.CloseableHttpClient;
import org.apache.http.impl.client.HttpClientBuilder;
import org.apache.http.impl.client.HttpClients;
import org.apache.http.util.EntityUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import com.alibaba.fastjson.JSON;
import com.fasterxml.jackson.databind.node.ObjectNode;

/**
 * 
 * @author  lw 20220415  调用status url
 *
 */
public class HttpStatusTask {
    private static final String FROM_PRE_RESP = "FrmPreRsp";
    protected volatile int exitStatusCode = -1;
    protected static final int MAX_CONNECTION_MILLISECONDS = 120000;
    protected static final int MAX_SOCKETTIMEOUT_MILLISECONDS = 172800000;//两天
    
    /**
     * output
     */
    protected String output;
    /**
     * http parameters
     */
    private HttpParameters httpParameters;
    /**
     * taskExecutionContext
     */
    private TaskRequest taskExecutionContext;
    private String startBbody;
    protected final Logger logger = LoggerFactory.getLogger(HttpStatusTask.class);

    /**
     * constructor
     *
     * @param taskExecutionContext taskExecutionContext
     * @param startBbody 
     */
    public HttpStatusTask(TaskRequest taskExecutionContext,HttpParameters httpParameters, String startBbody) {
        this.taskExecutionContext = taskExecutionContext;
        this.httpParameters = httpParameters;
        this.startBbody = startBbody;
    }

    public String  handleStatus() throws Exception {
        long startStatusTime = System.currentTimeMillis();
        String formatStatusTimeStamp = DateUtils.formatTimeStamp(startStatusTime);
        String statusStatusCode = null;
        String statusBody = null;
        try (CloseableHttpClient client = createHttpClient();
            CloseableHttpResponse statusResponse = sendRequest(client)) {
            statusStatusCode = String.valueOf(getStatusCode(statusResponse));
            statusBody = getResponseBody(statusResponse);
            exitStatusCode = validResponse(statusBody, statusStatusCode);
            long costStatusTime = System.currentTimeMillis() - startStatusTime;
            logger.info("statusStartTime: {}, statusHttpUrl: {}, statushttpMethod: {}, statuscostTime : {} milliseconds, statusStatusCode : {}, statusbody : {}, log : {}",
                    formatStatusTimeStamp, httpParameters.getStatusUrl(),
                    httpParameters.getHttpMethod(), costStatusTime, statusStatusCode, statusBody, output);
            
            return "exitStatusCode"+exitStatusCode+",statuscostTime : {" + costStatusTime + "} milliseconds, statusStatusCode : {"+statusStatusCode+"}, statusbody : {"+statusBody +"}";
        } catch (Exception e) {
            appendMessage(e.toString());
            exitStatusCode = -1;
            logger.error("statusHttpUrl[" + httpParameters.getStatusUrl() + "] connection failed:" + output, e);
            throw e;
        }
       
    }
    /**
     * 将startAPI的返回值替换前端页面值为的变量
     */
    private String getValueFromStartBody(String key,String value) {
        if(value.equals(FROM_PRE_RESP)) {
            try {
                com.alibaba.fastjson.JSONObject startJob = JSON.parseObject(startBbody);
                com.alibaba.fastjson.JSONObject startData = JSON.parseObject(String.valueOf(startJob.get("data")));
                if(startData!=null) {
                    if(startData.get(key)!=null) {
                        return startData.get(key).toString();
                    } else {
                        logger.info("无法获取"+key+"的值,startBody为"+startBbody);
                        return "";
                    }
                }else {
                    if(startJob.get(key)!=null) {
                        return startJob.get(key).toString();
                    } else {
                        logger.info("无法获取"+key+"的值,startBody为"+startBbody);
                        return "";
                    }
                }
            } catch (Exception e) {
                logger.error("startBbody[" + startBbody + "] 解析失败,必须是json格式:{}", e);
                return "";
            }
        }else {
            return value;
        }
    }
    /**
     * send request
     *
     * @param client client
     * @return CloseableHttpResponse
     * @throws IOException io exception
     */
    protected CloseableHttpResponse sendRequest(CloseableHttpClient client) throws IOException {
        RequestBuilder builder = createRequestBuilder();

        // replace placeholder,and combine local and global parameters
        Map<String, Property> paramsMap = ParamUtils.convert(taskExecutionContext,this.httpParameters);
        if (MapUtils.isEmpty(paramsMap)) {
            paramsMap = new HashMap<>();
        }
        if (MapUtils.isNotEmpty(taskExecutionContext.getParamsMap())) {
            paramsMap.putAll(taskExecutionContext.getParamsMap());
        }
        //替换FROM_PRE_RESP
        for(Map.Entry<String, Property> entry:paramsMap.entrySet()) {
            if(entry.getValue().getValue().equals(FROM_PRE_RESP)) {
                entry.getValue().setValue(getValueFromStartBody(entry.getKey(), FROM_PRE_RESP));
                paramsMap.put(entry.getKey(), entry.getValue());
            }
        }
        List<StatusHttpProperty> httpPropertyList = new ArrayList<>();
        if (CollectionUtils.isNotEmpty(httpParameters.getStatusHttpParams())) {
            for (StatusHttpProperty httpProperty : httpParameters.getStatusHttpParams()) {
                String jsonObject = JSONUtils.toJsonString(httpProperty);
                String params = ParameterUtils.convertParameterPlaceholders(jsonObject, ParamUtils.convert(paramsMap));
                logger.info("http request params:{}", params);
                httpPropertyList.add(JSONUtils.parseObject(params, StatusHttpProperty.class));
            }
        }
        addRequestParams(builder, httpPropertyList);
        int signFlag = httpParameters.getSign();
        if(signFlag != 0) {
            SHA1Utils.createSignAndSet2(builder);
        }
        String requestUrl = ParameterUtils.convertParameterPlaceholders(httpParameters.getStatusUrl(), ParamUtils.convert(paramsMap));
        HttpUriRequest request = builder.setUri(requestUrl).build();
        setHeaders(request, httpPropertyList);
        logger.info("requestUrl:"+request.getURI());
        return client.execute(request);
    }

    /**
     * get response body
     *
     * @param httpResponse http response
     * @return response body
     * @throws ParseException parse exception
     * @throws IOException io exception
     */
    protected String getResponseBody(CloseableHttpResponse httpResponse) throws ParseException, IOException {
        if (httpResponse == null) {
            return null;
        }
        HttpEntity entity = httpResponse.getEntity();
        if (entity == null) {
            return null;
        }
        return EntityUtils.toString(entity, StandardCharsets.UTF_8.name());
    }

    /**
     * get status code
     *
     * @param httpResponse http response
     * @return status code
     */
    protected int getStatusCode(CloseableHttpResponse httpResponse) {
        return httpResponse.getStatusLine().getStatusCode();
    }

    /**
     * valid response
     *
     * @param body body
     * @param statusCode status code
     * @return exit status code
     */
    protected int validResponse(String body, String statusCode) {
        int exitStatusCode = 0;
        switch (httpParameters.getStatusHttpCheckCondition()) {
            case BODY_CONTAINS:
                if (StringUtils.isEmpty(body) || !body.contains(httpParameters.getStatusCondition())) {
                    appendMessage(httpParameters.getStatusUrl() + " doesn contain "
                            + httpParameters.getStatusCondition());
                    exitStatusCode = -1;
                }
                break;
            case BODY_NOT_CONTAINS:
                if (StringUtils.isEmpty(body) || body.contains(httpParameters.getStatusCondition())) {
                    appendMessage(httpParameters.getStatusUrl() + " contains "
                            + httpParameters.getStatusCondition());
                    exitStatusCode = -1;
                }
                break;
            case STATUS_CODE_CUSTOM:
                if (!statusCode.equals(httpParameters.getStatusCondition())) {
                    appendMessage(httpParameters.getStatusUrl() + " statuscode: " + statusCode + ", Must be: " + httpParameters.getStatusCondition());
                    exitStatusCode = -1;
                }
                break;
            default:
                if (!"200".equals(statusCode)) {
                    appendMessage(httpParameters.getStatusUrl() + " statuscode: " + statusCode + ", Must be: 200");
                    exitStatusCode = -1;
                }
                break;
        }
        return exitStatusCode;
    }

    public String getOutput() {
        return output;
    }

    /**
     * append message
     *
     * @param message message
     */
    protected void appendMessage(String message) {
        if (output == null) {
            output = "";
        }
        if (message != null && !message.trim().isEmpty()) {
            output += message;
        }
    }

    /**
     * add request params
     *
     * @param builder buidler
     * @param httpPropertyList http property list
     */
    protected void addRequestParams(RequestBuilder builder, List<StatusHttpProperty> httpPropertyList) {
        if (CollectionUtils.isNotEmpty(httpPropertyList)) {
            ObjectNode jsonParam = JSONUtils.createObjectNode();
            for (StatusHttpProperty property : httpPropertyList) {
                if (property.getHttpParametersType() != null) {
                    String key = property.getProp();
                    String value = property.getValue();
                    if (property.getHttpParametersType().equals(HttpParametersType.PARAMETER)) {
                        builder.addParameter(key, getValueFromStartBody(key, value));
                    } else if (property.getHttpParametersType().equals(HttpParametersType.BODY)) {
                        jsonParam.put(key, getValueFromStartBody(key, value));
                    }
                }
            }
            StringEntity postingString = new StringEntity(jsonParam.toString(), Charsets.UTF_8);
            postingString.setContentEncoding(StandardCharsets.UTF_8.name());
            postingString.setContentType(APPLICATION_JSON);
            builder.setEntity(postingString);
        }
    }

    /**
     * set headers
     *
     * @param request request
     * @param httpPropertyList http property list
     */
    protected void setHeaders(HttpUriRequest request, List<StatusHttpProperty> httpPropertyList) {
        if (CollectionUtils.isNotEmpty(httpPropertyList)) {
            for (StatusHttpProperty property : httpPropertyList) {
                String key = property.getProp();
                String value = property.getValue();
                if (HttpParametersType.HEADERS.equals(property.getHttpParametersType())) {
                    request.addHeader(key, getValueFromStartBody(key, value));
                }
            }
        }
    }

    /**
     * create http client
     *
     * @return CloseableHttpClient
     */
    protected CloseableHttpClient createHttpClient() {
        final RequestConfig requestConfig = requestConfig();
        HttpClientBuilder httpClientBuilder;
        httpClientBuilder = HttpClients.custom().setDefaultRequestConfig(requestConfig);
        return httpClientBuilder.build();
    }

    /**
     * request config
     *
     * @return RequestConfig
     */
    private RequestConfig requestConfig() {
        return RequestConfig.custom().setSocketTimeout(MAX_SOCKETTIMEOUT_MILLISECONDS).setConnectTimeout(MAX_CONNECTION_MILLISECONDS).build();
    }

    /**
     * create request builder
     *
     * @return RequestBuilder
     */
    protected RequestBuilder createRequestBuilder() {
        if (httpParameters.getStatusHttpMethod().equals(StatusHttpMethod.GET)) {
            return RequestBuilder.get();
        } else if (httpParameters.getStatusHttpMethod().equals(StatusHttpMethod.POST)) {
            return RequestBuilder.post();
        } else if (httpParameters.getStatusHttpMethod().equals(StatusHttpMethod.HEAD)) {
            return RequestBuilder.head();
        } else if (httpParameters.getStatusHttpMethod().equals(StatusHttpMethod.PUT)) {
            return RequestBuilder.put();
        } else if (httpParameters.getStatusHttpMethod().equals(StatusHttpMethod.DELETE)) {
            return RequestBuilder.delete();
        } else {
            return null;
        }

    }
    
}
HttpParameters
/*
 * Licensed to the Apache Software Foundation (ASF) under one or more
 * contributor license agreements.  See the NOTICE file distributed with
 * this work for additional information regarding copyright ownership.
 * The ASF licenses this file to You under the Apache License, Version 2.0
 * (the "License"); you may not use this file except in compliance with
 * the License.  You may obtain a copy of the License at
 *
 *    http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package org.apache.dolphinscheduler.plugin.task.http;

import org.apache.dolphinscheduler.spi.task.AbstractParameters;
import org.apache.dolphinscheduler.spi.task.ResourceInfo;
import org.apache.dolphinscheduler.spi.utils.StringUtils;

import java.util.ArrayList;
import java.util.List;

/**
 * http parameter
 */
public class HttpParameters extends AbstractParameters {
    /**
     * url
     */
    private String url;
    
    private String statusUrl;//状态请求地址
    private int sign;//0-不需要签名 1-需要签名
    private int queryInterval;//查询间隔
    private int queryTimes;//查询次数
    private StatusHttpMethod statusHttpMethod;
    private List<StatusHttpProperty> statusHttpParams;
    private StatusHttpCheckCondition statusHttpCheckCondition = StatusHttpCheckCondition.STATUS_CODE_DEFAULT;
    private String statusCondition;
    
    /**
     * httpMethod
     */
    private HttpMethod httpMethod;

    /**
     *  http params
     */
    private List<HttpProperty> httpParams;

    /**
     * httpCheckCondition
     */
    private HttpCheckCondition httpCheckCondition = HttpCheckCondition.STATUS_CODE_DEFAULT;

    /**
     * condition
     */
    private String condition;

    /**
     * Connect Timeout
     * Unit: ms
     */
    private int connectTimeout;

    /**
     * Socket Timeout
     * Unit: ms
     */
    private int socketTimeout;

    @Override
    public boolean checkParameters() {
        return StringUtils.isNotEmpty(url);
    }

    @Override
    public List<ResourceInfo> getResourceFilesList() {
        return new ArrayList<>();
    }

    public String getUrl() {
        return url;
    }

    public void setUrl(String url) {
        this.url = url;
    }

    public HttpMethod getHttpMethod() {
        return httpMethod;
    }

    public void setHttpMethod(HttpMethod httpMethod) {
        this.httpMethod = httpMethod;
    }

    public List<HttpProperty> getHttpParams() {
        return httpParams;
    }

    public void setHttpParams(List<HttpProperty> httpParams) {
        this.httpParams = httpParams;
    }

    public HttpCheckCondition getHttpCheckCondition() {
        return httpCheckCondition;
    }

    public void setHttpCheckCondition(HttpCheckCondition httpCheckCondition) {
        this.httpCheckCondition = httpCheckCondition;
    }

    public String getCondition() {
        return condition;
    }

    public void setCondition(String condition) {
        this.condition = condition;
    }

    public int getConnectTimeout() {
        return connectTimeout;
    }

    public void setConnectTimeout(int connectTimeout) {
        this.connectTimeout = connectTimeout;
    }

    public int getSocketTimeout() {
        return socketTimeout;
    }

    public void setSocketTimeout(int socketTimeout) {
        this.socketTimeout = socketTimeout;
    }

	public String getStatusUrl() {
		return statusUrl;
	}

	public void setStatusUrl(String statusUrl) {
		this.statusUrl = statusUrl;
	}

	public int getSign() {
		return sign;
	}

	public void setSign(int sign) {
		this.sign = sign;
	}

	public int getQueryInterval() {
		return queryInterval;
	}

	public void setQueryInterval(int queryInterval) {
		this.queryInterval = queryInterval;
	}

	public int getQueryTimes() {
		return queryTimes;
	}

	public void setQueryTimes(int queryTimes) {
		this.queryTimes = queryTimes;
	}

	public StatusHttpMethod getStatusHttpMethod() {
		return statusHttpMethod;
	}

	public void setStatusHttpMethod(StatusHttpMethod statusHttpMethod) {
		this.statusHttpMethod = statusHttpMethod;
	}

	public List<StatusHttpProperty> getStatusHttpParams() {
		return statusHttpParams;
	}

	public void setStatusHttpParams(List<StatusHttpProperty> statusHttpParams) {
		this.statusHttpParams = statusHttpParams;
	}

	public StatusHttpCheckCondition getStatusHttpCheckCondition() {
		return statusHttpCheckCondition;
	}

	public void setStatusHttpCheckCondition(StatusHttpCheckCondition statusHttpCheckCondition) {
		this.statusHttpCheckCondition = statusHttpCheckCondition;
	}

	public String getStatusCondition() {
		return statusCondition;
	}

	public void setStatusCondition(String statusCondition) {
		this.statusCondition = statusCondition;
	}
    
}
HttpTask
/*
 * Licensed to the Apache Software Foundation (ASF) under one or more
 * contributor license agreements.  See the NOTICE file distributed with
 * this work for additional information regarding copyright ownership.
 * The ASF licenses this file to You under the Apache License, Version 2.0
 * (the "License"); you may not use this file except in compliance with
 * the License.  You may obtain a copy of the License at
 *
 *    http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package org.apache.dolphinscheduler.plugin.task.http;

import static org.apache.dolphinscheduler.plugin.task.http.HttpTaskConstants.APPLICATION_JSON;

import java.io.IOException;
import java.nio.charset.StandardCharsets;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

import org.apache.commons.collections4.CollectionUtils;
import org.apache.commons.io.Charsets;
import org.apache.dolphinscheduler.plugin.task.api.AbstractTaskExecutor;
import org.apache.dolphinscheduler.plugin.task.util.MapUtils;
import org.apache.dolphinscheduler.spi.task.AbstractParameters;
import org.apache.dolphinscheduler.spi.task.Property;
import org.apache.dolphinscheduler.spi.task.paramparser.ParamUtils;
import org.apache.dolphinscheduler.spi.task.paramparser.ParameterUtils;
import org.apache.dolphinscheduler.spi.task.request.TaskRequest;
import org.apache.dolphinscheduler.spi.utils.DateUtils;
import org.apache.dolphinscheduler.spi.utils.JSONUtils;
import org.apache.dolphinscheduler.spi.utils.StringUtils;
import org.apache.http.HttpEntity;
import org.apache.http.ParseException;
import org.apache.http.client.config.RequestConfig;
import org.apache.http.client.methods.CloseableHttpResponse;
import org.apache.http.client.methods.HttpUriRequest;
import org.apache.http.client.methods.RequestBuilder;
import org.apache.http.entity.StringEntity;
import org.apache.http.impl.client.CloseableHttpClient;
import org.apache.http.impl.client.HttpClientBuilder;
import org.apache.http.impl.client.HttpClients;
import org.apache.http.util.EntityUtils;

import com.fasterxml.jackson.databind.node.ObjectNode;

public class HttpTask extends AbstractTaskExecutor {

    /**
     * output
     */
    protected String output;
    /**
     * http parameters
     */
    private HttpParameters httpParameters;
    /**
     * taskExecutionContext
     */
    private TaskRequest taskExecutionContext;

    /**
     * constructor
     *
     * @param taskExecutionContext taskExecutionContext
     */
    public HttpTask(TaskRequest taskExecutionContext) {
        super(taskExecutionContext);
        this.taskExecutionContext = taskExecutionContext;
    }

    @Override
    public void init() {
        logger.info("http task params {}", taskExecutionContext.getTaskParams());
        this.httpParameters = JSONUtils.parseObject(taskExecutionContext.getTaskParams(), HttpParameters.class);

        if (!httpParameters.checkParameters()) {
            throw new RuntimeException("http task params is not valid");
        }
    }

    @Override
    public void handle() throws Exception {

        long startTime = System.currentTimeMillis();
        String formatTimeStamp = DateUtils.formatTimeStamp(startTime);
        String statusCode = null;
        String body = null;

        try (CloseableHttpClient client = createHttpClient();
             CloseableHttpResponse response = sendRequest(client)) {
            statusCode = String.valueOf(getStatusCode(response));
            body = getResponseBody(response);
            exitStatusCode = validResponse(body, statusCode);
            long costTime = System.currentTimeMillis() - startTime;
            logger.info("startTime: {}, httpUrl: {}, httpMethod: {}, costTime : {} milliseconds, statusCode : {}, body : {}, log : {}",
                    formatTimeStamp, httpParameters.getUrl(),
                    httpParameters.getHttpMethod(), costTime, statusCode, body, output);
            
            // lw 20220418 start 
            String statusUrl = httpParameters.getStatusUrl();
            if(StringUtils.isNotEmpty(statusUrl)) {
            	HttpStatusTask httpStatusTask = new HttpStatusTask(taskExecutionContext, httpParameters,body);
            	String statusResponse ="";
            	logger.info("start call status url ["+statusUrl+"],for result...");
            	long startStatusTime = System.currentTimeMillis();
            	int queryInterval = httpParameters.getQueryInterval();
            	int queryTimes = httpParameters.getQueryTimes();
            	int actualQueryTimes = 0;
            	String statusExitCode = "CONTINUE";
            	while(actualQueryTimes < queryTimes && statusExitCode.equals("CONTINUE")) {
            		logger.info("call status url ["+statusUrl+"]for result,"+(actualQueryTimes+1)+"th");
            		Thread.sleep(queryInterval);
            		actualQueryTimes++;
            		statusResponse = httpStatusTask.handleStatus();
            		if(statusResponse.startsWith("exitStatusCode0")) {
            			statusExitCode="SUCC";
            		}
            		logger.info(statusResponse);
            	}
    			logger.info("call status url["+statusUrl+"] for result end,cost "+ (System.currentTimeMillis()-startStatusTime)/1000 +" s");
    			if(actualQueryTimes == queryTimes && statusExitCode.equals("CONTINUE")) {
    				exitStatusCode=-1;
    			}
            }// lw 20220418 end
            
        } catch (Exception e) {
            appendMessage(e.toString());
            exitStatusCode = -1;
            logger.error("httpUrl[" + httpParameters.getUrl() + "] connection failed:" + output, e);
            throw e;
        }

    }
    
    /**
     * send request
     *
     * @param client client
     * @return CloseableHttpResponse
     * @throws IOException io exception
     */
    protected CloseableHttpResponse sendRequest(CloseableHttpClient client) throws IOException {
        RequestBuilder builder = createRequestBuilder();

        // replace placeholder,and combine local and global parameters
        Map<String, Property> paramsMap = ParamUtils.convert(taskExecutionContext,getParameters());
        if (MapUtils.isEmpty(paramsMap)) {
            paramsMap = new HashMap<>();
        }
        if (MapUtils.isNotEmpty(taskExecutionContext.getParamsMap())) {
            paramsMap.putAll(taskExecutionContext.getParamsMap());
        }

        List<HttpProperty> httpPropertyList = new ArrayList<>();
        if (CollectionUtils.isNotEmpty(httpParameters.getHttpParams())) {
            for (HttpProperty httpProperty : httpParameters.getHttpParams()) {
                String jsonObject = JSONUtils.toJsonString(httpProperty);
                String params = ParameterUtils.convertParameterPlaceholders(jsonObject, ParamUtils.convert(paramsMap));
                logger.info("http request params:{}", params);
                httpPropertyList.add(JSONUtils.parseObject(params, HttpProperty.class));
            }
        }
        addRequestParams(builder, httpPropertyList);
        int signFlag = httpParameters.getSign();
        if(signFlag != 0) {
        	SHA1Utils.createSignAndSet2(builder);
        }
        String requestUrl = ParameterUtils.convertParameterPlaceholders(httpParameters.getUrl(), ParamUtils.convert(paramsMap));
        HttpUriRequest request = builder.setUri(requestUrl).build();
        setHeaders(request, httpPropertyList);
        logger.info("requestUrl:"+request.getURI());
        return client.execute(request);
    }

    /**
     * get response body
     *
     * @param httpResponse http response
     * @return response body
     * @throws ParseException parse exception
     * @throws IOException io exception
     */
    protected String getResponseBody(CloseableHttpResponse httpResponse) throws ParseException, IOException {
        if (httpResponse == null) {
            return null;
        }
        HttpEntity entity = httpResponse.getEntity();
        if (entity == null) {
            return null;
        }
        return EntityUtils.toString(entity, StandardCharsets.UTF_8.name());
    }

    /**
     * get status code
     *
     * @param httpResponse http response
     * @return status code
     */
    protected int getStatusCode(CloseableHttpResponse httpResponse) {
        return httpResponse.getStatusLine().getStatusCode();
    }

    /**
     * valid response
     *
     * @param body body
     * @param statusCode status code
     * @return exit status code
     */
    protected int validResponse(String body, String statusCode) {
        int exitStatusCode = 0;
        switch (httpParameters.getHttpCheckCondition()) {
            case BODY_CONTAINS:
                if (StringUtils.isEmpty(body) || !body.contains(httpParameters.getCondition())) {
                    appendMessage(httpParameters.getUrl() + " doesn contain "
                            + httpParameters.getCondition());
                    exitStatusCode = -1;
                }
                break;
            case BODY_NOT_CONTAINS:
                if (StringUtils.isEmpty(body) || body.contains(httpParameters.getCondition())) {
                    appendMessage(httpParameters.getUrl() + " contains "
                            + httpParameters.getCondition());
                    exitStatusCode = -1;
                }
                break;
            case STATUS_CODE_CUSTOM:
                if (!statusCode.equals(httpParameters.getCondition())) {
                    appendMessage(httpParameters.getUrl() + " statuscode: " + statusCode + ", Must be: " + httpParameters.getCondition());
                    exitStatusCode = -1;
                }
                break;
            default:
                if (!"200".equals(statusCode)) {
                    appendMessage(httpParameters.getUrl() + " statuscode: " + statusCode + ", Must be: 200");
                    exitStatusCode = -1;
                }
                break;
        }
        return exitStatusCode;
    }

    public String getOutput() {
        return output;
    }

    /**
     * append message
     *
     * @param message message
     */
    protected void appendMessage(String message) {
        if (output == null) {
            output = "";
        }
        if (message != null && !message.trim().isEmpty()) {
            output += message;
        }
    }

    /**
     * add request params
     *
     * @param builder buidler
     * @param httpPropertyList http property list
     */
    protected void addRequestParams(RequestBuilder builder, List<HttpProperty> httpPropertyList) {
        if (CollectionUtils.isNotEmpty(httpPropertyList)) {
            ObjectNode jsonParam = JSONUtils.createObjectNode();
            for (HttpProperty property : httpPropertyList) {
                if (property.getHttpParametersType() != null) {
                    if (property.getHttpParametersType().equals(HttpParametersType.PARAMETER)) {
                        builder.addParameter(property.getProp(), property.getValue());
                    } else if (property.getHttpParametersType().equals(HttpParametersType.BODY)) {
                        jsonParam.put(property.getProp(), property.getValue());
                    }
                }
            }
            StringEntity postingString = new StringEntity(jsonParam.toString(), Charsets.UTF_8);
            postingString.setContentEncoding(StandardCharsets.UTF_8.name());
            postingString.setContentType(APPLICATION_JSON);
            builder.setEntity(postingString);
        }
    }

    /**
     * set headers
     *
     * @param request request
     * @param httpPropertyList http property list
     */
    protected void setHeaders(HttpUriRequest request, List<HttpProperty> httpPropertyList) {
        if (CollectionUtils.isNotEmpty(httpPropertyList)) {
            for (HttpProperty property : httpPropertyList) {
                if (HttpParametersType.HEADERS.equals(property.getHttpParametersType())) {
                    request.addHeader(property.getProp(), property.getValue());
                }
            }
        }
    }

    /**
     * create http client
     *
     * @return CloseableHttpClient
     */
    protected CloseableHttpClient createHttpClient() {
        final RequestConfig requestConfig = requestConfig();
        HttpClientBuilder httpClientBuilder;
        httpClientBuilder = HttpClients.custom().setDefaultRequestConfig(requestConfig);
        return httpClientBuilder.build();
    }

    /**
     * request config
     *
     * @return RequestConfig
     */
    private RequestConfig requestConfig() {
        return RequestConfig.custom().setSocketTimeout(httpParameters.getSocketTimeout()).setConnectTimeout(httpParameters.getConnectTimeout()).build();
    }

    /**
     * create request builder
     *
     * @return RequestBuilder
     */
    protected RequestBuilder createRequestBuilder() {
        if (httpParameters.getHttpMethod().equals(HttpMethod.GET)) {
            return RequestBuilder.get();
        } else if (httpParameters.getHttpMethod().equals(HttpMethod.POST)) {
            return RequestBuilder.post();
        } else if (httpParameters.getHttpMethod().equals(HttpMethod.HEAD)) {
            return RequestBuilder.head();
        } else if (httpParameters.getHttpMethod().equals(HttpMethod.PUT)) {
            return RequestBuilder.put();
        } else if (httpParameters.getHttpMethod().equals(HttpMethod.DELETE)) {
            return RequestBuilder.delete();
        } else {
            return null;
        }

    }

    @Override
    public AbstractParameters getParameters() {
        return this.httpParameters;
    }
}
pom.xml
<dependency>
	<groupId>com.alibaba</groupId>
	<artifactId>fastjson</artifactId>
	<version>1.2.61</version>
</dependency>

测试

hive sql 获取月末时间 hive获取上月末_海豚2.0_03


hive sql 获取月末时间 hive获取上月末_海豚2.0_04


hive sql 获取月末时间 hive获取上月末_hive sql 获取月末时间_05


实际上是失败的,代码只打印了日志

hive sql 获取月末时间 hive获取上月末_HTTP异步处理_06


hive sql 获取月末时间 hive获取上月末_HttpTask_07


明天继续测,目前也没API可以调用,只是简单的淌水

总结

动态获取查询参数

页面添加了太多栏位,确实不够美观,因此直接使用现有参数栏位,如果该参数的值是送启动API获取的,则写死FrmPreRsp,然后后台去获取替换。

hive sql 获取月末时间 hive获取上月末_HTTP异步处理_08

签名栏位

处于安全考虑,签名校验必不可少,目前的想法是海豚调度定义签名规则,这样海豚调度可以统一处理。如果是第三方定义,如果有100个项目,签名规则都不一样,那海豚调度难道要是适配这100个,写100个处理方法。这个有待商榷,默认签名是关闭状态,不走签名校验的。

hive sql 获取月末时间 hive获取上月末_HttpTask_09

其它

目前代码解析启动API响应报文,要求是JSON格式,明天上班了还要好好测试修改,今天只是在开源版本上做个小实验,把路淌通。本来觉得方法二简单,结果花了一天。现在想想还是方案一可能好一点,对页面的改动不大,只需要解决传参的问题。方案二说白了就是把两条HTTP参数全放一起,统一处理。

启动API和查询(状态)API指的是如下:

hive sql 获取月末时间 hive获取上月末_HTTP异步处理_10