From 977b8bfd4b21d65f4bd6c18fecae63948f2d4bcf Mon Sep 17 00:00:00 2001 From: tjq Date: Sat, 19 Dec 2020 21:49:41 +0800 Subject: [PATCH] fix: fetch instance log failed when server use different http port --- .../powerjob/common/InstanceStatus.java | 4 +-- .../server/service/DispatchService.java | 1 + .../server/service/InstanceLogService.java | 25 +++++++++++++-- .../web/controller/InstanceController.java | 32 +++---------------- .../src/main/resources/static/js/9.js | 2 +- 5 files changed, 32 insertions(+), 32 deletions(-) diff --git a/powerjob-common/src/main/java/com/github/kfcfans/powerjob/common/InstanceStatus.java b/powerjob-common/src/main/java/com/github/kfcfans/powerjob/common/InstanceStatus.java index 536ef621..996ce997 100644 --- a/powerjob-common/src/main/java/com/github/kfcfans/powerjob/common/InstanceStatus.java +++ b/powerjob-common/src/main/java/com/github/kfcfans/powerjob/common/InstanceStatus.java @@ -24,8 +24,8 @@ public enum InstanceStatus { CANCELED(9, "取消"), STOPPED(10, "手动停止"); - private int v; - private String des; + private final int v; + private final String des; // 广义的运行状态 public static final List generalizedRunningStatus = Lists.newArrayList(WAITING_DISPATCH.v, WAITING_WORKER_RECEIVE.v, RUNNING.v); diff --git a/powerjob-server/src/main/java/com/github/kfcfans/powerjob/server/service/DispatchService.java b/powerjob-server/src/main/java/com/github/kfcfans/powerjob/server/service/DispatchService.java index 8f98c7a0..3e5c41f4 100644 --- a/powerjob-server/src/main/java/com/github/kfcfans/powerjob/server/service/DispatchService.java +++ b/powerjob-server/src/main/java/com/github/kfcfans/powerjob/server/service/DispatchService.java @@ -81,6 +81,7 @@ public class DispatchService { if (maxInstanceNum > 0) { // 这个 runningInstanceCount 已经包含了本 instance + // 不统计 WAITING_DISPATCH 的状态:使用 OpenAPI 触发的延迟任务显然不应该统计进去(比如 delay 是 1 天) long runningInstanceCount = instanceInfoRepository.countByJobIdAndStatusIn(jobId, Lists.newArrayList(WAITING_WORKER_RECEIVE.getV(), RUNNING.getV())); // 超出最大同时运行限制,不执行调度 if (runningInstanceCount > maxInstanceNum) { diff --git a/powerjob-server/src/main/java/com/github/kfcfans/powerjob/server/service/InstanceLogService.java b/powerjob-server/src/main/java/com/github/kfcfans/powerjob/server/service/InstanceLogService.java index 90a9fd8c..b197d31e 100644 --- a/powerjob-server/src/main/java/com/github/kfcfans/powerjob/server/service/InstanceLogService.java +++ b/powerjob-server/src/main/java/com/github/kfcfans/powerjob/server/service/InstanceLogService.java @@ -4,7 +4,9 @@ import com.github.kfcfans.powerjob.common.OmsConstant; import com.github.kfcfans.powerjob.common.TimeExpressionType; import com.github.kfcfans.powerjob.common.model.InstanceLogContent; import com.github.kfcfans.powerjob.common.utils.CommonUtils; +import com.github.kfcfans.powerjob.common.utils.NetUtils; import com.github.kfcfans.powerjob.common.utils.SegmentLock; +import com.github.kfcfans.powerjob.server.common.redirect.DesignateServer; import com.github.kfcfans.powerjob.server.common.utils.OmsFileUtils; import com.github.kfcfans.powerjob.server.persistence.StringPage; import com.github.kfcfans.powerjob.server.persistence.core.model.JobInfoDO; @@ -21,6 +23,7 @@ import org.apache.commons.io.FileUtils; import org.apache.commons.lang3.exception.ExceptionUtils; import org.apache.commons.lang3.time.FastDateFormat; import org.springframework.beans.BeanUtils; +import org.springframework.beans.factory.annotation.Value; import org.springframework.scheduling.annotation.Async; import org.springframework.scheduling.annotation.Scheduled; import org.springframework.stereotype.Service; @@ -45,6 +48,9 @@ import java.util.stream.Stream; @Service public class InstanceLogService { + @Value("${server.port}") + private int port; + @Resource private InstanceMetadataService instanceMetadataService; @Resource @@ -99,11 +105,13 @@ public class InstanceLogService { /** * 获取任务实例运行日志(默认存在本地数据,需要由生成完成请求的路由与转发) + * @param appId appId,AOP 专用 * @param instanceId 任务实例ID * @param index 页码,从0开始 * @return 文本字符串 */ - public StringPage fetchInstanceLog(Long instanceId, long index) { + @DesignateServer(appIdParameterName = "appId") + public StringPage fetchInstanceLog(Long appId, Long instanceId, Long index) { try { Future fileFuture = prepareLogFile(instanceId); // 超时并不会打断正在执行的任务 @@ -125,7 +133,7 @@ public class InstanceLogService { ++lines; } }catch (Exception e) { - log.warn("[InstanceLog-{}] read logFile from disk failed.", instanceId, e); + log.warn("[InstanceLog-{}] read logFile from disk failed for app: {}.", instanceId, appId, e); return StringPage.simple("oms-server execution exception, caused by " + ExceptionUtils.getRootCauseMessage(e)); } @@ -140,6 +148,19 @@ public class InstanceLogService { } } + /** + * 获取日志的下载链接 + * @param appId AOP 专用 + * @param instanceId 任务实例 ID + * @return 下载链接 + */ + @DesignateServer(appIdParameterName = "appId") + public String fetchDownloadUrl(Long appId, Long instanceId) { + String url = "http://" + NetUtils.getLocalHost() + ":" + port + "/instance/downloadLog?instanceId=" + instanceId; + log.info("[InstanceLog-{}] downloadURL for appId[{}]: {}", instanceId, appId, url); + return url; + } + /** * 下载全部的任务日志文件 * @param instanceId 任务实例ID diff --git a/powerjob-server/src/main/java/com/github/kfcfans/powerjob/server/web/controller/InstanceController.java b/powerjob-server/src/main/java/com/github/kfcfans/powerjob/server/web/controller/InstanceController.java index d3495717..ae36a78a 100644 --- a/powerjob-server/src/main/java/com/github/kfcfans/powerjob/server/web/controller/InstanceController.java +++ b/powerjob-server/src/main/java/com/github/kfcfans/powerjob/server/web/controller/InstanceController.java @@ -3,7 +3,6 @@ package com.github.kfcfans.powerjob.server.web.controller; import com.github.kfcfans.powerjob.common.InstanceStatus; import com.github.kfcfans.powerjob.common.PowerJobException; import com.github.kfcfans.powerjob.common.response.ResultDTO; -import com.github.kfcfans.powerjob.server.akka.OhMyServer; import com.github.kfcfans.powerjob.server.common.utils.OmsFileUtils; import com.github.kfcfans.powerjob.server.persistence.PageResult; import com.github.kfcfans.powerjob.server.persistence.StringPage; @@ -19,7 +18,6 @@ import com.github.kfcfans.powerjob.server.web.response.InstanceDetailVO; import com.github.kfcfans.powerjob.server.web.response.InstanceInfoVO; import lombok.extern.slf4j.Slf4j; import org.springframework.beans.BeanUtils; -import org.springframework.beans.factory.annotation.Value; import org.springframework.data.domain.Example; import org.springframework.data.domain.Page; import org.springframework.data.domain.PageRequest; @@ -46,8 +44,7 @@ import java.util.stream.Collectors; @RequestMapping("/instance") public class InstanceController { - @Value("${server.port}") - private int port; + @Resource private InstanceService instanceService; @@ -79,32 +76,13 @@ public class InstanceController { } @GetMapping("/log") - public ResultDTO getInstanceLog(Long instanceId, Long index, HttpServletResponse response) { - - String targetServer = getTargetServer(instanceId); - - // 转发HTTP请求(如果使用Akka,则需要传输两次,而转发HTTP请求只需要传输一次"大"数据包) - if (!OhMyServer.getActorSystemAddress().equals(targetServer)) { - String ip = targetServer.split(":")[0]; - String url = String.format("http://%s:%s/instance/log?instanceId=%d&index=%d", ip, port, instanceId, index); - try { - response.sendRedirect(url); - return ResultDTO.success(StringPage.simple("redirecting...")); - }catch (Exception e) { - log.warn("[Instance-{}] redirect request to url[{}] failed, please ensure all server has the same http port!", instanceId, url, e); - return ResultDTO.failed(e); - } - } - - return ResultDTO.success(instanceLogService.fetchInstanceLog(instanceId, index)); + public ResultDTO getInstanceLog(Long appId, Long instanceId, Long index) { + return ResultDTO.success(instanceLogService.fetchInstanceLog(appId, instanceId, index)); } @GetMapping("/downloadLogUrl") - public ResultDTO getDownloadUrl(Long instanceId) { - String targetServer = getTargetServer(instanceId); - String ip = targetServer.split(":")[0]; - String url = "http://" + ip + ":" + port + "/instance/downloadLog?instanceId=" + instanceId; - return ResultDTO.success(url); + public ResultDTO getDownloadUrl(Long appId, Long instanceId) { + return ResultDTO.success(instanceLogService.fetchDownloadUrl(appId, instanceId)); } @GetMapping("/downloadLog") diff --git a/powerjob-server/src/main/resources/static/js/9.js b/powerjob-server/src/main/resources/static/js/9.js index 34cb3277..9955e5b3 100644 --- a/powerjob-server/src/main/resources/static/js/9.js +++ b/powerjob-server/src/main/resources/static/js/9.js @@ -8,7 +8,7 @@ /***/ (function(module, __webpack_exports__, __webpack_require__) { "use strict"; -eval("__webpack_require__.r(__webpack_exports__);\n/* harmony import */ var _common_InstanceDetail__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! ../common/InstanceDetail */ \"./src/components/common/InstanceDetail.vue\");\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n\n/* harmony default export */ __webpack_exports__[\"default\"] = ({\n name: \"InstanceManager\",\n components: {\n InstanceDetail: _common_InstanceDetail__WEBPACK_IMPORTED_MODULE_0__[\"default\"]\n },\n data: function data() {\n return {\n // 实例查询对象\n instanceQueryContent: {\n appId: this.$store.state.appInfo.id,\n index: 0,\n pageSize: 10,\n instanceId: undefined,\n wfInstanceId: undefined,\n status: \"\",\n jobId: undefined,\n type: \"NORMAL\"\n },\n // 实例查询结果\n instancePageResult: {\n pageSize: 10,\n totalItems: 0,\n data: []\n },\n // 详细信息弹出框是否可见\n instanceDetailVisible: false,\n // 日志查询对象\n logQueryContent: {\n instanceId: undefined,\n index: 0\n },\n // 日志对象\n paginableInstanceLog: {\n index: 0,\n totalPages: 0,\n data: \"\"\n },\n // 日志弹出框是否可见\n instanceLogVisible: false,\n currentInstanceId: undefined,\n // 任务实例状态选择\n instanceStatusOptions: [{\n key: \"\",\n label: this.$t('message.all')\n }, {\n key: \"WAITING_DISPATCH\",\n label: this.$t('message.waitingDispatch')\n }, {\n key: \"WAITING_WORKER_RECEIVE\",\n label: this.$t('message.waitingWorkerReceive')\n }, {\n key: \"RUNNING\",\n label: this.$t('message.running')\n }, {\n key: \"FAILED\",\n label: this.$t('message.failed')\n }, {\n key: \"SUCCEED\",\n label: this.$t('message.success')\n }, {\n key: \"CANCELED\",\n label: this.$t('message.canceled')\n }, {\n key: \"STOPPED\",\n label: this.$t('message.stopped')\n }]\n };\n },\n methods: {\n // 查询任务实例信息\n listInstanceInfos: function listInstanceInfos() {\n var that = this;\n that.axios.post(\"/instance/list\", that.instanceQueryContent).then(function (res) {\n that.instancePageResult = res;\n });\n },\n // 点击重置按钮\n onClickRest: function onClickRest() {\n this.instanceQueryContent.jobId = undefined;\n this.instanceQueryContent.instanceId = undefined;\n this.instanceQueryContent.wfInstanceId = undefined;\n this.instanceQueryContent.status = \"\";\n this.listInstanceInfos();\n },\n // 点击查询详情\n onClickShowDetail: function onClickShowDetail(data) {\n this.instanceDetailVisible = true;\n this.currentInstanceId = data.instanceId;\n },\n // 点击重跑\n onClickRetryJob: function onClickRetryJob(data) {\n var _this = this;\n\n var that = this;\n var url = \"/instance/retry?instanceId=\" + data.instanceId + \"&appId=\" + that.$store.state.appInfo.id;\n this.axios.get(url).then(function () {\n that.$message.success(_this.$t('message.success'));\n that.listInstanceInfos();\n });\n },\n // 点击停止实例\n onClickStop: function onClickStop(data) {\n var _this2 = this;\n\n var that = this;\n var url = \"/instance/stop?instanceId=\" + data.instanceId;\n this.axios.get(url).then(function () {\n that.$message.success(_this2.$t('message.success')); // 重新加载列表\n\n that.listInstanceInfos();\n });\n },\n // 换页\n onClickChangeInstancePage: function onClickChangeInstancePage(index) {\n // 后端从0开始,前端从1开始\n this.instanceQueryContent.index = index - 1;\n this.listInstanceInfos();\n },\n instanceTableRowClassName: function instanceTableRowClassName(_ref) {\n var row = _ref.row;\n\n switch (row.status) {\n // 失败\n case 4:\n return 'error-row';\n // 成功\n\n case 5:\n return 'success-row';\n\n case 9:\n case 10:\n return 'warning-row';\n }\n },\n // 查看日志\n queryLog: function queryLog() {\n var that = this;\n var url = \"/instance/log?instanceId=\" + this.logQueryContent.instanceId + \"&index=\" + this.logQueryContent.index;\n this.axios.get(url).then(function (res) {\n that.paginableInstanceLog = res;\n that.instanceLogVisible = true;\n });\n },\n // 查看在线日志\n onClickShowLog: function onClickShowLog(data) {\n this.logQueryContent.instanceId = data.instanceId;\n this.logQueryContent.index = 0;\n this.queryLog();\n },\n // 查看其它页的在线日志\n onClickChangeLogPage: function onClickChangeLogPage(index) {\n this.logQueryContent.index = index - 1;\n this.queryLog();\n },\n // 下载日志\n onclickDownloadLog: function onclickDownloadLog() {\n var url = \"/instance/downloadLogUrl?instanceId=\" + this.logQueryContent.instanceId;\n this.axios.get(url).then(function (res) {\n return window.open(res);\n });\n },\n // 获取状态\n fetchStatus: function fetchStatus(s) {\n return this.common.translateInstanceStatus(s);\n }\n },\n mounted: function mounted() {\n // 读取传递的参数\n var jobId = this.$route.params.jobId;\n\n if (jobId !== undefined) {\n this.instanceQueryContent.jobId = jobId;\n }\n\n this.listInstanceInfos();\n }\n});\n\n//# sourceURL=webpack:///./src/components/views/InstanceManager.vue?./node_modules/cache-loader/dist/cjs.js??ref--12-0!./node_modules/babel-loader/lib!./node_modules/cache-loader/dist/cjs.js??ref--0-0!./node_modules/vue-loader/lib??vue-loader-options"); +eval("__webpack_require__.r(__webpack_exports__);\n/* harmony import */ var _common_InstanceDetail__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! ../common/InstanceDetail */ \"./src/components/common/InstanceDetail.vue\");\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n//\n\n/* harmony default export */ __webpack_exports__[\"default\"] = ({\n name: \"InstanceManager\",\n components: {\n InstanceDetail: _common_InstanceDetail__WEBPACK_IMPORTED_MODULE_0__[\"default\"]\n },\n data: function data() {\n return {\n // 实例查询对象\n instanceQueryContent: {\n appId: this.$store.state.appInfo.id,\n index: 0,\n pageSize: 10,\n instanceId: undefined,\n wfInstanceId: undefined,\n status: \"\",\n jobId: undefined,\n type: \"NORMAL\"\n },\n // 实例查询结果\n instancePageResult: {\n pageSize: 10,\n totalItems: 0,\n data: []\n },\n // 详细信息弹出框是否可见\n instanceDetailVisible: false,\n // 日志查询对象\n logQueryContent: {\n instanceId: undefined,\n index: 0\n },\n // 日志对象\n paginableInstanceLog: {\n index: 0,\n totalPages: 0,\n data: \"\"\n },\n // 日志弹出框是否可见\n instanceLogVisible: false,\n currentInstanceId: undefined,\n // 任务实例状态选择\n instanceStatusOptions: [{\n key: \"\",\n label: this.$t('message.all')\n }, {\n key: \"WAITING_DISPATCH\",\n label: this.$t('message.waitingDispatch')\n }, {\n key: \"WAITING_WORKER_RECEIVE\",\n label: this.$t('message.waitingWorkerReceive')\n }, {\n key: \"RUNNING\",\n label: this.$t('message.running')\n }, {\n key: \"FAILED\",\n label: this.$t('message.failed')\n }, {\n key: \"SUCCEED\",\n label: this.$t('message.success')\n }, {\n key: \"CANCELED\",\n label: this.$t('message.canceled')\n }, {\n key: \"STOPPED\",\n label: this.$t('message.stopped')\n }]\n };\n },\n methods: {\n // 查询任务实例信息\n listInstanceInfos: function listInstanceInfos() {\n var that = this;\n that.axios.post(\"/instance/list\", that.instanceQueryContent).then(function (res) {\n that.instancePageResult = res;\n });\n },\n // 点击重置按钮\n onClickRest: function onClickRest() {\n this.instanceQueryContent.jobId = undefined;\n this.instanceQueryContent.instanceId = undefined;\n this.instanceQueryContent.wfInstanceId = undefined;\n this.instanceQueryContent.status = \"\";\n this.listInstanceInfos();\n },\n // 点击查询详情\n onClickShowDetail: function onClickShowDetail(data) {\n this.instanceDetailVisible = true;\n this.currentInstanceId = data.instanceId;\n },\n // 点击重跑\n onClickRetryJob: function onClickRetryJob(data) {\n var _this = this;\n\n var that = this;\n var url = \"/instance/retry?instanceId=\" + data.instanceId + \"&appId=\" + that.$store.state.appInfo.id;\n this.axios.get(url).then(function () {\n that.$message.success(_this.$t('message.success'));\n that.listInstanceInfos();\n });\n },\n // 点击停止实例\n onClickStop: function onClickStop(data) {\n var _this2 = this;\n\n var that = this;\n var url = \"/instance/stop?instanceId=\" + data.instanceId;\n this.axios.get(url).then(function () {\n that.$message.success(_this2.$t('message.success')); // 重新加载列表\n\n that.listInstanceInfos();\n });\n },\n // 换页\n onClickChangeInstancePage: function onClickChangeInstancePage(index) {\n // 后端从0开始,前端从1开始\n this.instanceQueryContent.index = index - 1;\n this.listInstanceInfos();\n },\n instanceTableRowClassName: function instanceTableRowClassName(_ref) {\n var row = _ref.row;\n\n switch (row.status) {\n // 失败\n case 4:\n return 'error-row';\n // 成功\n\n case 5:\n return 'success-row';\n\n case 9:\n case 10:\n return 'warning-row';\n }\n },\n // 查看日志\n queryLog: function queryLog() {\n var that = this;\n var url = \"/instance/log?instanceId=\" + this.logQueryContent.instanceId + \"&index=\" + this.logQueryContent.index + \"&appId=\" + that.$store.state.appInfo.id;\n this.axios.get(url).then(function (res) {\n that.paginableInstanceLog = res;\n that.instanceLogVisible = true;\n });\n },\n // 查看在线日志\n onClickShowLog: function onClickShowLog(data) {\n this.logQueryContent.instanceId = data.instanceId;\n this.logQueryContent.index = 0;\n this.queryLog();\n },\n // 查看其它页的在线日志\n onClickChangeLogPage: function onClickChangeLogPage(index) {\n this.logQueryContent.index = index - 1;\n this.queryLog();\n },\n // 下载日志\n onclickDownloadLog: function onclickDownloadLog() {\n var url = \"/instance/downloadLogUrl?instanceId=\" + this.logQueryContent.instanceId + \"&appId=\" + this.$store.state.appInfo.id;\n this.axios.get(url).then(function (res) {\n return window.open(res);\n });\n },\n // 获取状态\n fetchStatus: function fetchStatus(s) {\n return this.common.translateInstanceStatus(s);\n }\n },\n mounted: function mounted() {\n // 读取传递的参数\n var jobId = this.$route.params.jobId;\n\n if (jobId !== undefined) {\n this.instanceQueryContent.jobId = jobId;\n }\n\n this.listInstanceInfos();\n }\n});\n\n//# sourceURL=webpack:///./src/components/views/InstanceManager.vue?./node_modules/cache-loader/dist/cjs.js??ref--12-0!./node_modules/babel-loader/lib!./node_modules/cache-loader/dist/cjs.js??ref--0-0!./node_modules/vue-loader/lib??vue-loader-options"); /***/ }),