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。

改造方案二

前端页面

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

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

涉及代码

前端

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-selectstyle="width: 150px;"size="small"v-model="httpMethod":disabled="isDetails"><el-optionv-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-paramsref="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-selectstyle="width: 230px;"size="small"v-model="httpCheckCondition":disabled="isDetails"><el-optionv-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-paramsref="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-selectstyle="width: 150px;"size="small"v-model="statusHttpMethod":disabled="isDetails"><el-optionv-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-paramsref="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-selectstyle="width: 230px;"size="small"v-model="statusHttpCheckCondition":disabled="isDetails"><el-optionv-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-paramsref="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 startstatusUrl: '',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 addif (!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 verificationif (!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}// storagethis.$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 20220415statusUrl: this.statusUrl, // lw 20220415sign: this.signcheck ? 1 : 0, // lw 20220415statusLocalParams: 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 20220415statusUrl: this.statusUrl, // lw 20220415sign: this.sign, // lw 20220415statusLocalParams: 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 backfillif (!_.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.statusHttpCheckConditionthis.statusCondition = o.params.statusCondition || ''this.connectTimeout = o.params.connectTimeoutthis.socketTimeout = o.params.socketTimeoutif (this.connectTimeout !== 60000 || this.socketTimeout !== 60000) {this.timeoutSettings = true}this.queryInterval = o.params.queryIntervalthis.queryTimes = o.params.queryTimesif (this.queryInterval !== 600000 || this.queryTimes !== 10) {this.statusRequestSettings = true}// backfill localParamslet 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 startthis.statusUrl = o.params.statusUrlthis.sign = o.params.signif (this.statusUrl) {this.statusSettings = true}if (this.sign === 1) {this.signcheck = true} else {this.signcheck = false}// backfill localParamslet 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 parametersMap<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_RESPfor(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;@Overridepublic boolean checkParameters() {return StringUtils.isNotEmpty(url);}@Overridepublic 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;}@Overridepublic 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");}}@Overridepublic 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 parametersMap<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;}}@Overridepublic AbstractParameters getParameters() {return this.httpParameters;}
}

pom.xml

<dependency><groupId>com.alibaba</groupId><artifactId>fastjson</artifactId><version>1.2.61</version>
</dependency>

测试




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


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

总结

动态获取查询参数

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

签名栏位

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

其它

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

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

dolphinscheduler2.0.5 HTTP任务类型改造相关推荐

  1. 某机器字长8位,试用如下所给芯片设计一个存储器,容量为10KW,其中RAM为高8KW,ROM为低2KW,最低地址为0(RAM芯片类型为:4K×8。ROM芯片为:2K×4)。

    某机器字长8位,试用如下所给芯片设计一个存储器,容量为10KW,其中RAM为高8KW,ROM为低2KW,最低地址为0(RAM芯片类型为:4K×8.ROM芯片为:2K×4). ①地址线.数据线各为多少根 ...

  2. 分析 C# 2.0 新特性 -- 空类型(Nullable Types)

    分析 C# 2.0 新特性  -- 空类型(Nullable Types) 在讨论C# 2.0 空类型前,先回顾一下.NET 1.0和.NET 1.1对于类型有下面这样的定义: ".NET  ...

  3. IHostingEnvironment VS IHostEnvironment - .NET Core 3.0中的废弃类型

    原文:https://andrewlock.net/ihostingenvironment-vs-ihost-environment-obsolete-types-in-net-core-3/ 作者: ...

  4. bootstrapV4.6.0实现标签页(改造v3.3.7)- 代码篇

    文章目录 疑问 · 注意事项: 效果图: 全部代码示下: 疑问 · 注意事项: 本案例中bootstrap.css.js使用的是4.6.0版本: 网上说4.0+版本的没有"标签页" ...

  5. 【转】SqlLite .Net 4.0 System.IO.FileLoadException”类型的未经处理的异常出现在XXX

    [转]SqlLite .Net 4.0 System.IO.FileLoadException"类型的未经处理的异常出现在XXX 参考文章: (1)[转]SqlLite .Net 4.0 S ...

  6. .Net 4.0 之 Dynamic 动态类型

    本文主要旨在与网友分享.Net4.0的Dynamic 对Duck Type 的支持.     一..net4.0主要新特性 .Net4.0在.Net3.5基础上新增的主要特性有:可选参数.命名参数和D ...

  7. WinCE7.0 下 Silverlight(XAML) 类型的应用启动逻辑

    WinCE7.0 下 Silverlight(XAML) 类型的应用启动顺序,如下堆栈所示: SWEClock.exe!MainPage::OnLoaded(IXRDependencyObject* ...

  8. IIS6.0上某些文件类型不能下载

    现象: IIS6上,碰到某些文件类型的文件,访问不了,出现404无法找到的提示. 解决方案: IIS6上,碰到某些文件类型的文件,访问不了,出现404无法找到的提示. 原因分析: IIS6.0取消了对 ...

  9. jms.jar 2.0_JMS 2.0中JMSContext的类型

    jms.jar 2.0 如果您遵循Java EE,那么您将不会知道JMS 2.0(Java EE 7)中的简化API组件. 构成简化API一部分的重要接口之一是javax.jms.JMSContext ...

  10. JMS 2.0中JMSContext的类型

    如果您遵循Java EE,您将不会知道JMS 2.0(Java EE 7)中的简化API组件. 构成简化API一部分的重要接口之一是javax.jms.JMSContext接口. 根据实例的获取和管理 ...

最新文章

  1. 第十六届全国大学智能车竞赛华南赛区成绩汇总
  2. python和c学习-学习 Python与C相互调用
  3. MVC架构接收jsp页面传值
  4. Vijos p1484 ISBN号码
  5. 这篇顶会paper,讲述了疫情期间憋疯的你和我
  6. react封装函数_React 模式-将函数作为 children 传入和 render prop - 极客教程
  7. linux有k歌软件吗,在Linux下可用Wine安装和运行暴风影音16、全民K歌
  8. 定时器Timer和播放器MediaPlayer
  9. I have no name !;sudo: unknown uid 1000: who are you?
  10. Android RadioButton(单选按钮)点击事件的两种方法
  11. 校园网打开IEEE 显示未登录
  12. python文章抄袭检测_中小学生的噩梦:怎样用Python检测抄袭行为?广大中小学生们的美梦就此结束...
  13. python一个简单的一元二次方程求解的过程
  14. modules node 太大了_解决node_modules文件名太长无法删除的两个方法-文件名太长
  15. 使用 closest 和 matches 方法来检测元素是否存在某选择器
  16. js鼠标拖拽移动盒子但只在父框内移动(三种写法)
  17. JVM 运行时内存空间详解——元空间
  18. “影响力之父”西奥迪尼:人类就像录音机,按一下就播放
  19. c++学习六(静态成员和友员函数)
  20. CNN卷积神经网络案例程序源代码合集matlab/Python等

热门文章

  1. DRAM存储系统结构
  2. linux更新后不能进入系统,Ubuntu内核升级后无法进入系统的解决办法
  3. fbx模型压缩成gltf格式
  4. unity3d 坦克大战实战
  5. 苹果开发者申请-创建证书签名请求
  6. python自动化webdriver_轻松自动化---selenium-webdriver(python) (六)
  7. Android 获取当前地理位置信息
  8. python ca模块_[转]常用的python模块及安装方法
  9. css中aspect,CSS属性之aspect-ratio
  10. excel怎么录入身份证号码快速方便?