mirror of
https://github.com/apache/rocketmq-dashboard.git
synced 2026-05-29 05:49:25 +08:00
* [ISSUE #30]Added DLQ message management * remove the specific namesrvAddr in application.properties. Co-authored-by: zhangjidi <zhangjidi@cmss.chinamobile.com>
This commit is contained in:
13
pom.xml
13
pom.xml
@@ -100,6 +100,8 @@
|
|||||||
<mockito-inline.version>3.3.3</mockito-inline.version>
|
<mockito-inline.version>3.3.3</mockito-inline.version>
|
||||||
<jaxb-api.version>2.3.1</jaxb-api.version>
|
<jaxb-api.version>2.3.1</jaxb-api.version>
|
||||||
<commons-pool2.version>2.4.3</commons-pool2.version>
|
<commons-pool2.version>2.4.3</commons-pool2.version>
|
||||||
|
<easyexcel.version>2.2.10</easyexcel.version>
|
||||||
|
<asm.version>4.2</asm.version>
|
||||||
</properties>
|
</properties>
|
||||||
|
|
||||||
<dependencies>
|
<dependencies>
|
||||||
@@ -234,6 +236,17 @@
|
|||||||
<artifactId>commons-pool2</artifactId>
|
<artifactId>commons-pool2</artifactId>
|
||||||
<version>${commons-pool2.version}</version>
|
<version>${commons-pool2.version}</version>
|
||||||
</dependency>
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>com.alibaba</groupId>
|
||||||
|
<artifactId>easyexcel</artifactId>
|
||||||
|
<version>${easyexcel.version}</version>
|
||||||
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.ow2.asm</groupId>
|
||||||
|
<artifactId>asm</artifactId>
|
||||||
|
<version>${asm.version}</version>
|
||||||
|
</dependency>
|
||||||
|
|
||||||
</dependencies>
|
</dependencies>
|
||||||
<build>
|
<build>
|
||||||
<plugins>
|
<plugins>
|
||||||
|
|||||||
@@ -0,0 +1,73 @@
|
|||||||
|
/*
|
||||||
|
* 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.rocketmq.dashboard.controller;
|
||||||
|
|
||||||
|
import com.google.common.collect.Lists;
|
||||||
|
import javax.annotation.Resource;
|
||||||
|
import javax.servlet.http.HttpServletResponse;
|
||||||
|
import org.apache.rocketmq.common.MixAll;
|
||||||
|
import org.apache.rocketmq.common.message.MessageExt;
|
||||||
|
import org.apache.rocketmq.dashboard.exception.ServiceException;
|
||||||
|
import org.apache.rocketmq.dashboard.model.DlqMessageExcelModel;
|
||||||
|
import org.apache.rocketmq.dashboard.model.request.MessageQuery;
|
||||||
|
import org.apache.rocketmq.dashboard.permisssion.Permission;
|
||||||
|
import org.apache.rocketmq.dashboard.service.DlqMessageService;
|
||||||
|
import org.apache.rocketmq.dashboard.util.ExcelUtil;
|
||||||
|
import org.apache.rocketmq.tools.admin.MQAdminExt;
|
||||||
|
import org.springframework.stereotype.Controller;
|
||||||
|
import org.springframework.web.bind.annotation.GetMapping;
|
||||||
|
import org.springframework.web.bind.annotation.RequestBody;
|
||||||
|
import org.springframework.web.bind.annotation.RequestMapping;
|
||||||
|
import org.springframework.web.bind.annotation.RequestMethod;
|
||||||
|
import org.springframework.web.bind.annotation.RequestParam;
|
||||||
|
import org.springframework.web.bind.annotation.ResponseBody;
|
||||||
|
|
||||||
|
@Controller
|
||||||
|
@RequestMapping("/dlqMessage")
|
||||||
|
@Permission
|
||||||
|
public class DlqMessageController {
|
||||||
|
|
||||||
|
@Resource
|
||||||
|
private DlqMessageService dlqMessageService;
|
||||||
|
|
||||||
|
@Resource
|
||||||
|
private MQAdminExt mqAdminExt;
|
||||||
|
|
||||||
|
@RequestMapping(value = "/queryDlqMessageByConsumerGroup.query", method = RequestMethod.POST)
|
||||||
|
@ResponseBody
|
||||||
|
public Object queryDlqMessageByConsumerGroup(@RequestBody MessageQuery query) {
|
||||||
|
return dlqMessageService.queryDlqMessageByPage(query);
|
||||||
|
}
|
||||||
|
|
||||||
|
@GetMapping(value = "/exportDlqMessage.do")
|
||||||
|
public void exportDlqMessage(HttpServletResponse response, @RequestParam String consumerGroup,
|
||||||
|
@RequestParam String msgId) {
|
||||||
|
MessageExt messageExt = null;
|
||||||
|
try {
|
||||||
|
String topic = MixAll.DLQ_GROUP_TOPIC_PREFIX + consumerGroup;
|
||||||
|
messageExt = mqAdminExt.viewMessage(topic, msgId);
|
||||||
|
} catch (Exception e) {
|
||||||
|
throw new ServiceException(-1, String.format("Failed to query message by Id: %s", msgId));
|
||||||
|
}
|
||||||
|
DlqMessageExcelModel excelModel = new DlqMessageExcelModel(messageExt);
|
||||||
|
try {
|
||||||
|
ExcelUtil.writeExcel(response, Lists.newArrayList(excelModel), "dlq", "dlq", DlqMessageExcelModel.class);
|
||||||
|
} catch (Exception e) {
|
||||||
|
throw new ServiceException(-1, String.format("export dlq message failed!"));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,81 @@
|
|||||||
|
/*
|
||||||
|
* 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.rocketmq.dashboard.model;
|
||||||
|
|
||||||
|
import com.alibaba.excel.annotation.ExcelProperty;
|
||||||
|
import com.alibaba.excel.annotation.write.style.ColumnWidth;
|
||||||
|
import com.alibaba.excel.metadata.BaseRowModel;
|
||||||
|
import com.alibaba.excel.util.DateUtils;
|
||||||
|
import com.google.common.base.Charsets;
|
||||||
|
import java.io.Serializable;
|
||||||
|
import java.util.Date;
|
||||||
|
import lombok.Data;
|
||||||
|
import org.apache.rocketmq.common.message.MessageExt;
|
||||||
|
|
||||||
|
@Data
|
||||||
|
public class DlqMessageExcelModel extends BaseRowModel implements Serializable {
|
||||||
|
|
||||||
|
@ExcelProperty(value = "topic", index = 0)
|
||||||
|
@ColumnWidth(value = 15)
|
||||||
|
private String topic;
|
||||||
|
|
||||||
|
@ExcelProperty(value = "msgId", index = 1)
|
||||||
|
@ColumnWidth(value = 15)
|
||||||
|
private String msgId;
|
||||||
|
|
||||||
|
@ExcelProperty(value = "bornHost", index = 2)
|
||||||
|
@ColumnWidth(value = 15)
|
||||||
|
private String bornHost;
|
||||||
|
|
||||||
|
@ExcelProperty(value = "bornTimestamp", index = 3)
|
||||||
|
@ColumnWidth(value = 25)
|
||||||
|
private String bornTimestamp;
|
||||||
|
|
||||||
|
@ExcelProperty(value = "storeTimestamp", index = 4)
|
||||||
|
@ColumnWidth(value = 25)
|
||||||
|
private String storeTimestamp;
|
||||||
|
|
||||||
|
@ExcelProperty(value = "reconsumeTimes", index = 5)
|
||||||
|
@ColumnWidth(value = 25)
|
||||||
|
private int reconsumeTimes;
|
||||||
|
|
||||||
|
@ExcelProperty(value = "properties", index = 6)
|
||||||
|
@ColumnWidth(value = 20)
|
||||||
|
private String properties;
|
||||||
|
|
||||||
|
@ExcelProperty(value = "messageBody", index = 7)
|
||||||
|
@ColumnWidth(value = 20)
|
||||||
|
private String messageBody;
|
||||||
|
|
||||||
|
@ExcelProperty(value = "bodyCRC", index = 8)
|
||||||
|
@ColumnWidth(value = 15)
|
||||||
|
private int bodyCRC;
|
||||||
|
|
||||||
|
public DlqMessageExcelModel(MessageExt messageExt) {
|
||||||
|
this.topic = messageExt.getTopic();
|
||||||
|
this.msgId = messageExt.getMsgId();
|
||||||
|
this.bornHost = messageExt.getBornHostString();
|
||||||
|
this.bornTimestamp = DateUtils.format(new Date(messageExt.getBornTimestamp()), DateUtils.DATE_FORMAT_19);
|
||||||
|
this.storeTimestamp = DateUtils.format(new Date(messageExt.getStoreTimestamp()), DateUtils.DATE_FORMAT_19);
|
||||||
|
this.reconsumeTimes = messageExt.getReconsumeTimes();
|
||||||
|
this.properties = messageExt.getProperties().toString();
|
||||||
|
this.messageBody = new String(messageExt.getBody(), Charsets.UTF_8);
|
||||||
|
this.bodyCRC = messageExt.getBodyCRC();
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -0,0 +1,26 @@
|
|||||||
|
/*
|
||||||
|
* 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.rocketmq.dashboard.service;
|
||||||
|
|
||||||
|
import org.apache.rocketmq.dashboard.model.MessagePage;
|
||||||
|
import org.apache.rocketmq.dashboard.model.request.MessageQuery;
|
||||||
|
|
||||||
|
public interface DlqMessageService {
|
||||||
|
|
||||||
|
MessagePage queryDlqMessageByPage(MessageQuery query);
|
||||||
|
}
|
||||||
@@ -0,0 +1,68 @@
|
|||||||
|
/*
|
||||||
|
* 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.rocketmq.dashboard.service.impl;
|
||||||
|
|
||||||
|
import com.google.common.base.Throwables;
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.List;
|
||||||
|
import javax.annotation.Resource;
|
||||||
|
import lombok.extern.slf4j.Slf4j;
|
||||||
|
import org.apache.rocketmq.client.exception.MQClientException;
|
||||||
|
import org.apache.rocketmq.common.MixAll;
|
||||||
|
import org.apache.rocketmq.common.protocol.ResponseCode;
|
||||||
|
import org.apache.rocketmq.dashboard.model.MessagePage;
|
||||||
|
import org.apache.rocketmq.dashboard.model.MessageView;
|
||||||
|
import org.apache.rocketmq.dashboard.model.request.MessageQuery;
|
||||||
|
import org.apache.rocketmq.dashboard.service.DlqMessageService;
|
||||||
|
import org.apache.rocketmq.dashboard.service.MessageService;
|
||||||
|
import org.apache.rocketmq.tools.admin.MQAdminExt;
|
||||||
|
import org.springframework.data.domain.PageImpl;
|
||||||
|
import org.springframework.data.domain.PageRequest;
|
||||||
|
import org.springframework.stereotype.Service;
|
||||||
|
|
||||||
|
@Service
|
||||||
|
@Slf4j
|
||||||
|
public class DlqMessageServiceImpl implements DlqMessageService {
|
||||||
|
|
||||||
|
@Resource
|
||||||
|
private MQAdminExt mqAdminExt;
|
||||||
|
|
||||||
|
@Resource
|
||||||
|
private MessageService messageService;
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public MessagePage queryDlqMessageByPage(MessageQuery query) {
|
||||||
|
List<MessageView> messageViews = new ArrayList<>();
|
||||||
|
PageRequest page = PageRequest.of(query.getPageNum(), query.getPageSize());
|
||||||
|
String topic = query.getTopic();
|
||||||
|
try {
|
||||||
|
mqAdminExt.examineTopicRouteInfo(topic);
|
||||||
|
} catch (MQClientException e) {
|
||||||
|
// If the %DLQ%Group does not exist, the message returns null
|
||||||
|
if (topic.startsWith(MixAll.DLQ_GROUP_TOPIC_PREFIX)
|
||||||
|
&& e.getResponseCode() == ResponseCode.TOPIC_NOT_EXIST) {
|
||||||
|
return new MessagePage(new PageImpl<>(messageViews, page, 0), query.getTaskId());
|
||||||
|
} else {
|
||||||
|
throw Throwables.propagate(e);
|
||||||
|
}
|
||||||
|
} catch (Exception e) {
|
||||||
|
throw Throwables.propagate(e);
|
||||||
|
}
|
||||||
|
return messageService.queryMessageByPage(query);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,56 @@
|
|||||||
|
/*
|
||||||
|
* 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.rocketmq.dashboard.util;
|
||||||
|
|
||||||
|
import com.alibaba.excel.EasyExcel;
|
||||||
|
import com.alibaba.excel.support.ExcelTypeEnum;
|
||||||
|
import com.alibaba.excel.write.metadata.style.WriteCellStyle;
|
||||||
|
import com.alibaba.excel.write.metadata.style.WriteFont;
|
||||||
|
import com.alibaba.excel.write.style.HorizontalCellStyleStrategy;
|
||||||
|
import java.io.OutputStream;
|
||||||
|
import java.net.URLEncoder;
|
||||||
|
import java.util.List;
|
||||||
|
import javax.servlet.http.HttpServletResponse;
|
||||||
|
import org.apache.poi.ss.usermodel.HorizontalAlignment;
|
||||||
|
|
||||||
|
public class ExcelUtil {
|
||||||
|
|
||||||
|
public static void writeExcel(HttpServletResponse response, List<? extends Object> data, String fileName,
|
||||||
|
String sheetName, Class clazz) throws Exception {
|
||||||
|
WriteCellStyle headWriteCellStyle = new WriteCellStyle();
|
||||||
|
WriteFont writeFont = new WriteFont();
|
||||||
|
writeFont.setFontHeightInPoints((short)12);
|
||||||
|
writeFont.setFontName("Microsoft YaHei UI");
|
||||||
|
headWriteCellStyle.setWriteFont(writeFont);
|
||||||
|
headWriteCellStyle.setHorizontalAlignment(HorizontalAlignment.CENTER);
|
||||||
|
|
||||||
|
WriteCellStyle contentWriteCellStyle = new WriteCellStyle();
|
||||||
|
contentWriteCellStyle.setWriteFont(writeFont);
|
||||||
|
contentWriteCellStyle.setHorizontalAlignment(HorizontalAlignment.CENTER);
|
||||||
|
HorizontalCellStyleStrategy horizontalCellStyleStrategy = new HorizontalCellStyleStrategy(headWriteCellStyle, contentWriteCellStyle);
|
||||||
|
EasyExcel.write(getOutputStream(fileName, response), clazz)
|
||||||
|
.excelType(ExcelTypeEnum.XLSX).sheet(sheetName).registerWriteHandler(horizontalCellStyleStrategy).doWrite(data);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static OutputStream getOutputStream(String fileName, HttpServletResponse response) throws Exception {
|
||||||
|
fileName = URLEncoder.encode(fileName, "UTF-8");
|
||||||
|
response.setContentType("application/vnd.ms-excel");
|
||||||
|
response.setCharacterEncoding("utf8");
|
||||||
|
response.setHeader("Content-Disposition", "attachment;filename=" + fileName + ".xlsx");
|
||||||
|
return response.getOutputStream();
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -107,6 +107,7 @@
|
|||||||
<script type="text/javascript" src="src/consumer.js?timestamp=6"></script>
|
<script type="text/javascript" src="src/consumer.js?timestamp=6"></script>
|
||||||
<script type="text/javascript" src="src/producer.js"></script>
|
<script type="text/javascript" src="src/producer.js"></script>
|
||||||
<script type="text/javascript" src="src/message.js"></script>
|
<script type="text/javascript" src="src/message.js"></script>
|
||||||
|
<script type="text/javascript" src="src/dlqMessage.js"></script>
|
||||||
<script type="text/javascript" src="src/messageTrace.js"></script>
|
<script type="text/javascript" src="src/messageTrace.js"></script>
|
||||||
<script type="text/javascript" src="src/ops.js?timestamp=7"></script>
|
<script type="text/javascript" src="src/ops.js?timestamp=7"></script>
|
||||||
<script type="text/javascript" src="src/remoteApi/remoteApi.js"></script>
|
<script type="text/javascript" src="src/remoteApi/remoteApi.js"></script>
|
||||||
|
|||||||
@@ -195,6 +195,9 @@ app.config(['$routeProvider', '$httpProvider','$cookiesProvider','getDictNamePro
|
|||||||
}).when('/message', {
|
}).when('/message', {
|
||||||
templateUrl: 'view/pages/message.html',
|
templateUrl: 'view/pages/message.html',
|
||||||
controller:'messageController'
|
controller:'messageController'
|
||||||
|
}).when('/dlqMessage', {
|
||||||
|
templateUrl: 'view/pages/dlqMessage.html',
|
||||||
|
controller:'dlqMessageController'
|
||||||
}).when('/messageTrace', {
|
}).when('/messageTrace', {
|
||||||
templateUrl: 'view/pages/messageTrace.html',
|
templateUrl: 'view/pages/messageTrace.html',
|
||||||
controller:'messageTraceController'
|
controller:'messageTraceController'
|
||||||
|
|||||||
184
src/main/resources/static/src/dlqMessage.js
Normal file
184
src/main/resources/static/src/dlqMessage.js
Normal file
@@ -0,0 +1,184 @@
|
|||||||
|
/*
|
||||||
|
* 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.
|
||||||
|
*/
|
||||||
|
|
||||||
|
var module = app;
|
||||||
|
const SYS_GROUP_TOPIC_PREFIX = "%SYS%";
|
||||||
|
module.controller('dlqMessageController', ['$scope', 'ngDialog', '$http', 'Notification', function ($scope, ngDialog, $http, Notification) {
|
||||||
|
$scope.allConsumerGroupList = [];
|
||||||
|
$scope.selectedConsumerGroup = [];
|
||||||
|
$scope.messageId = "";
|
||||||
|
$scope.queryDlqMessageByConsumerGroupResult = [];
|
||||||
|
$scope.queryDlqMessageByMessageIdResult = {};
|
||||||
|
$http({
|
||||||
|
method: "GET",
|
||||||
|
url: "consumer/groupList.query"
|
||||||
|
}).success(function (resp) {
|
||||||
|
if (resp.status == 0) {
|
||||||
|
for (const consumerGroup of resp.data) {
|
||||||
|
if (!consumerGroup.group.startsWith(SYS_GROUP_TOPIC_PREFIX)) {
|
||||||
|
$scope.allConsumerGroupList.push(consumerGroup.group);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
$scope.allConsumerGroupList.sort();
|
||||||
|
} else {
|
||||||
|
Notification.error({message: resp.errMsg, delay: 2000});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
$scope.timepickerBegin = moment().subtract(3, 'hour').format('YYYY-MM-DD HH:mm');
|
||||||
|
$scope.timepickerEnd = moment().format('YYYY-MM-DD HH:mm');
|
||||||
|
$scope.timepickerOptions = {format: 'YYYY-MM-DD HH:mm', showClear: true};
|
||||||
|
|
||||||
|
$scope.taskId = "";
|
||||||
|
|
||||||
|
$scope.paginationConf = {
|
||||||
|
currentPage: 1,
|
||||||
|
totalItems: 0,
|
||||||
|
itemsPerPage: 20,
|
||||||
|
pagesLength: 15,
|
||||||
|
perPageOptions: [10],
|
||||||
|
rememberPerPage: 'perPageItems',
|
||||||
|
onChange: function () {
|
||||||
|
$scope.queryDlqMessageByConsumerGroup()
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
$scope.queryDlqMessageByConsumerGroup = function () {
|
||||||
|
$("#noMsgTip").css("display", "none");
|
||||||
|
if ($scope.timepickerEnd < $scope.timepickerBegin) {
|
||||||
|
Notification.error({message: "endTime is later than beginTime!", delay: 2000});
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if ($scope.selectedConsumerGroup === [] || (typeof $scope.selectedConsumerGroup) == "object") {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
$http({
|
||||||
|
method: "POST",
|
||||||
|
url: "dlqMessage/queryDlqMessageByConsumerGroup.query",
|
||||||
|
data: {
|
||||||
|
topic: DLQ_GROUP_TOPIC_PREFIX + $scope.selectedConsumerGroup,
|
||||||
|
begin: $scope.timepickerBegin.valueOf(),
|
||||||
|
end: $scope.timepickerEnd.valueOf(),
|
||||||
|
pageNum: $scope.paginationConf.currentPage,
|
||||||
|
pageSize: $scope.paginationConf.itemsPerPage,
|
||||||
|
taskId: $scope.taskId
|
||||||
|
}
|
||||||
|
}).success(function (resp) {
|
||||||
|
if (resp.status === 0) {
|
||||||
|
$scope.messageShowList = resp.data.page.content;
|
||||||
|
if ($scope.messageShowList.length == 0){
|
||||||
|
$("#noMsgTip").removeAttr("style");
|
||||||
|
}
|
||||||
|
console.log($scope.messageShowList);
|
||||||
|
if (resp.data.page.first) {
|
||||||
|
$scope.paginationConf.currentPage = 1;
|
||||||
|
}
|
||||||
|
$scope.paginationConf.currentPage = resp.data.page.number + 1;
|
||||||
|
$scope.paginationConf.totalItems = resp.data.page.totalElements;
|
||||||
|
$scope.taskId = resp.data.taskId
|
||||||
|
} else {
|
||||||
|
Notification.error({message: resp.errMsg, delay: 2000});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
$scope.queryDlqMessageDetail = function (messageId, consumerGroup) {
|
||||||
|
$http({
|
||||||
|
method: "GET",
|
||||||
|
url: "messageTrace/viewMessage.query",
|
||||||
|
params: {
|
||||||
|
msgId: messageId,
|
||||||
|
topic: DLQ_GROUP_TOPIC_PREFIX + consumerGroup
|
||||||
|
}
|
||||||
|
}).success(function (resp) {
|
||||||
|
if (resp.status == 0) {
|
||||||
|
console.log(resp);
|
||||||
|
ngDialog.open({
|
||||||
|
template: 'dlqMessageDetailViewDialog',
|
||||||
|
data: resp.data
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
Notification.error({message: resp.errMsg, delay: 2000});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
$scope.queryDlqMessageByMessageId = function (messageId, consumerGroup) {
|
||||||
|
$http({
|
||||||
|
method: "GET",
|
||||||
|
url: "messageTrace/viewMessage.query",
|
||||||
|
params: {
|
||||||
|
msgId: messageId,
|
||||||
|
topic: DLQ_GROUP_TOPIC_PREFIX + consumerGroup
|
||||||
|
}
|
||||||
|
}).success(function (resp) {
|
||||||
|
if (resp.status == 0) {
|
||||||
|
$scope.queryDlqMessageByMessageIdResult = resp.data;
|
||||||
|
} else {
|
||||||
|
Notification.error({message: resp.errMsg, delay: 2000});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
$scope.changeShowMessageList = function (currentPage, totalItem) {
|
||||||
|
var perPage = $scope.paginationConf.itemsPerPage;
|
||||||
|
var from = (currentPage - 1) * perPage;
|
||||||
|
var to = (from + perPage) > totalItem ? totalItem : from + perPage;
|
||||||
|
$scope.messageShowList = $scope.queryMessageByTopicResult.slice(from, to);
|
||||||
|
$scope.paginationConf.totalItems = totalItem;
|
||||||
|
};
|
||||||
|
|
||||||
|
$scope.onChangeQueryCondition = function () {
|
||||||
|
console.log("change")
|
||||||
|
$scope.taskId = "";
|
||||||
|
$scope.paginationConf.currentPage = 1;
|
||||||
|
$scope.paginationConf.totalItems = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
$scope.resendDlqMessage = function (messageView, consumerGroup) {
|
||||||
|
const topic = messageView.properties.RETRY_TOPIC;
|
||||||
|
const msgId = messageView.properties.ORIGIN_MESSAGE_ID;
|
||||||
|
$http({
|
||||||
|
method: "POST",
|
||||||
|
url: "message/consumeMessageDirectly.do",
|
||||||
|
params: {
|
||||||
|
msgId: msgId,
|
||||||
|
consumerGroup: consumerGroup,
|
||||||
|
topic: topic
|
||||||
|
}
|
||||||
|
}).success(function (resp) {
|
||||||
|
if (resp.status == 0) {
|
||||||
|
ngDialog.open({
|
||||||
|
template: 'operationResultDialog',
|
||||||
|
data: {
|
||||||
|
result: resp.data
|
||||||
|
}
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
ngDialog.open({
|
||||||
|
template: 'operationResultDialog',
|
||||||
|
data: {
|
||||||
|
result: resp.errMsg
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
$scope.exportDlqMessage = function (msgId, consumerGroup) {
|
||||||
|
window.location.href = "dlqMessage/exportDlqMessage.do?msgId=" + msgId + "&consumerGroup=" + consumerGroup;
|
||||||
|
}
|
||||||
|
}]);
|
||||||
@@ -41,6 +41,7 @@ var en = {
|
|||||||
"RESEND_MESSAGE":"Resend Message",
|
"RESEND_MESSAGE":"Resend Message",
|
||||||
"VIEW_EXCEPTION":"View Exception",
|
"VIEW_EXCEPTION":"View Exception",
|
||||||
"MESSAGETRACE":"MessageTrace",
|
"MESSAGETRACE":"MessageTrace",
|
||||||
|
"DLQ_MESSAGE":"DLQMessage",
|
||||||
"COMMIT": "Commit",
|
"COMMIT": "Commit",
|
||||||
"OPERATION": "Operation",
|
"OPERATION": "Operation",
|
||||||
"ADD": "Add",
|
"ADD": "Add",
|
||||||
@@ -108,5 +109,7 @@ var en = {
|
|||||||
"ENABLE_MESSAGE_TRACE":"Enable Message Trace",
|
"ENABLE_MESSAGE_TRACE":"Enable Message Trace",
|
||||||
"MESSAGE_TRACE_DETAIL":"Message Trace Detail",
|
"MESSAGE_TRACE_DETAIL":"Message Trace Detail",
|
||||||
"TRACE_TOPIC":"TraceTopic",
|
"TRACE_TOPIC":"TraceTopic",
|
||||||
"SELECT_TRACE_TOPIC":"selectTraceTopic"
|
"SELECT_TRACE_TOPIC":"selectTraceTopic",
|
||||||
|
"EXPORT": "export",
|
||||||
|
"NO_MATCH_RESULT": "no match result"
|
||||||
}
|
}
|
||||||
@@ -39,8 +39,9 @@ var zh = {
|
|||||||
"PRODUCER":"生产者",
|
"PRODUCER":"生产者",
|
||||||
"MESSAGE":"消息",
|
"MESSAGE":"消息",
|
||||||
"MESSAGE_DETAIL":"消息详情",
|
"MESSAGE_DETAIL":"消息详情",
|
||||||
"RESEND_MESSAGE":"重新消费",
|
"RESEND_MESSAGE":"重新发送",
|
||||||
"VIEW_EXCEPTION":"查看异常",
|
"VIEW_EXCEPTION":"查看异常",
|
||||||
|
"DLQ_MESSAGE":"死信消息",
|
||||||
"MESSAGETRACE":"消息轨迹",
|
"MESSAGETRACE":"消息轨迹",
|
||||||
"OPERATION": "操作",
|
"OPERATION": "操作",
|
||||||
"ADD": "新增",
|
"ADD": "新增",
|
||||||
@@ -109,5 +110,7 @@ var zh = {
|
|||||||
"ENABLE_MESSAGE_TRACE":"开启消息轨迹",
|
"ENABLE_MESSAGE_TRACE":"开启消息轨迹",
|
||||||
"MESSAGE_TRACE_DETAIL":"消息轨迹详情",
|
"MESSAGE_TRACE_DETAIL":"消息轨迹详情",
|
||||||
"TRACE_TOPIC":"消息轨迹主题",
|
"TRACE_TOPIC":"消息轨迹主题",
|
||||||
"SELECT_TRACE_TOPIC":"选择消息轨迹主题"
|
"SELECT_TRACE_TOPIC":"选择消息轨迹主题",
|
||||||
|
"EXPORT": "导出",
|
||||||
|
"NO_MATCH_RESULT": "没有查到符合条件的结果"
|
||||||
}
|
}
|
||||||
@@ -34,6 +34,7 @@
|
|||||||
<li ng-class="path =='consumer' ? 'active':''"><a ng-href="#/consumer">{{'CONSUMER' | translate}}</a></li>
|
<li ng-class="path =='consumer' ? 'active':''"><a ng-href="#/consumer">{{'CONSUMER' | translate}}</a></li>
|
||||||
<li ng-class="path =='producer' ? 'active':''"><a ng-href="#/producer">{{'PRODUCER' | translate}}</a></li>
|
<li ng-class="path =='producer' ? 'active':''"><a ng-href="#/producer">{{'PRODUCER' | translate}}</a></li>
|
||||||
<li ng-class="path =='message' ? 'active':''"><a ng-href="#/message">{{'MESSAGE' | translate}}</a></li>
|
<li ng-class="path =='message' ? 'active':''"><a ng-href="#/message">{{'MESSAGE' | translate}}</a></li>
|
||||||
|
<li ng-class="path =='dlqMessage' ? 'active':''"><a ng-href="#/dlqMessage">{{'DLQ_MESSAGE' | translate}}</a></li>
|
||||||
<li ng-class="path =='messageTrace' ? 'active':''"><a ng-href="#/messageTrace">{{'MESSAGETRACE' | translate}}</a></li>
|
<li ng-class="path =='messageTrace' ? 'active':''"><a ng-href="#/messageTrace">{{'MESSAGETRACE' | translate}}</a></li>
|
||||||
</ul>
|
</ul>
|
||||||
<ul class="nav navbar-nav navbar-right">
|
<ul class="nav navbar-nav navbar-right">
|
||||||
|
|||||||
@@ -209,8 +209,8 @@
|
|||||||
<div class="modal-body " ng-repeat="item in ngDialogData.consumerRequestList">
|
<div class="modal-body " ng-repeat="item in ngDialogData.consumerRequestList">
|
||||||
<form id="addAppForm" name="addAppForm" class="form-horizontal" novalidate>
|
<form id="addAppForm" name="addAppForm" class="form-horizontal" novalidate>
|
||||||
<div class="form-group" ng-hide="ngDialogData.bIsUpdate">
|
<div class="form-group" ng-hide="ngDialogData.bIsUpdate">
|
||||||
<label class="control-label col-sm-4">clusterName:</label>
|
<label class="control-label col-sm-3">clusterName:</label>
|
||||||
<div class="col-sm-8">
|
<div class="col-sm-9">
|
||||||
<select name="mySelectClusterNameList" multiple chosen
|
<select name="mySelectClusterNameList" multiple chosen
|
||||||
ng-model="item.clusterNameList"
|
ng-model="item.clusterNameList"
|
||||||
ng-options="clusterNameItem for clusterNameItem in ngDialogData.allClusterNameList">
|
ng-options="clusterNameItem for clusterNameItem in ngDialogData.allClusterNameList">
|
||||||
@@ -219,8 +219,8 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="form-group">
|
<div class="form-group">
|
||||||
<label class="control-label col-sm-4">brokerName:</label>
|
<label class="control-label col-sm-3">brokerName:</label>
|
||||||
<div class="col-sm-8">
|
<div class="col-sm-9">
|
||||||
<select name="mySelectBrokerNameList" multiple chosen
|
<select name="mySelectBrokerNameList" multiple chosen
|
||||||
ng-disabled="ngDialogData.bIsUpdate"
|
ng-disabled="ngDialogData.bIsUpdate"
|
||||||
ng-model="item.brokerNameList"
|
ng-model="item.brokerNameList"
|
||||||
@@ -230,16 +230,16 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="form-group">
|
<div class="form-group">
|
||||||
<label class="control-label col-sm-4">groupName:</label>
|
<label class="control-label col-sm-3">groupName:</label>
|
||||||
<div class="col-sm-8">
|
<div class="col-sm-9">
|
||||||
<input class="form-control" ng-model="item.subscriptionGroupConfig.groupName" type="text"
|
<input class="form-control" ng-model="item.subscriptionGroupConfig.groupName" type="text"
|
||||||
ng-disabled="ngDialogData.bIsUpdate" required/>
|
ng-disabled="ngDialogData.bIsUpdate" required/>
|
||||||
<span class="text-danger" ng-show="addAppForm.name.$error.required">编号不能为空.</span>
|
<span class="text-danger" ng-show="addAppForm.name.$error.required">编号不能为空.</span>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="form-group">
|
<div class="form-group">
|
||||||
<label class="control-label col-sm-4">consumeEnable:</label>
|
<label class="control-label col-sm-3">consumeEnable:</label>
|
||||||
<div class="col-sm-8">
|
<div class="col-sm-9">
|
||||||
<md-switch class="md-primary" ng-disabled="{{!ngDialogData.writeOperationEnabled}}" md-no-ink
|
<md-switch class="md-primary" ng-disabled="{{!ngDialogData.writeOperationEnabled}}" md-no-ink
|
||||||
aria-label="Switch No Ink" ng-model="item.subscriptionGroupConfig.consumeEnable">
|
aria-label="Switch No Ink" ng-model="item.subscriptionGroupConfig.consumeEnable">
|
||||||
</md-switch>
|
</md-switch>
|
||||||
@@ -257,8 +257,8 @@
|
|||||||
<!--</div>-->
|
<!--</div>-->
|
||||||
<!--</div>-->
|
<!--</div>-->
|
||||||
<div class="form-group">
|
<div class="form-group">
|
||||||
<label class="control-label col-sm-4">consumeBroadcastEnable:</label>
|
<label class="control-label col-sm-3">consumeBroadcastEnable:</label>
|
||||||
<div class="col-sm-8">
|
<div class="col-sm-9">
|
||||||
<md-switch class="md-primary" ng-disabled="{{!ngDialogData.writeOperationEnabled}}" md-no-ink
|
<md-switch class="md-primary" ng-disabled="{{!ngDialogData.writeOperationEnabled}}" md-no-ink
|
||||||
aria-label="Switch No Ink"
|
aria-label="Switch No Ink"
|
||||||
ng-model="item.subscriptionGroupConfig.consumeBroadcastEnable">
|
ng-model="item.subscriptionGroupConfig.consumeBroadcastEnable">
|
||||||
@@ -266,8 +266,8 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="form-group">
|
<div class="form-group">
|
||||||
<label class="control-label col-sm-4">retryQueueNums:</label>
|
<label class="control-label col-sm-3">retryQueueNums:</label>
|
||||||
<div class="col-sm-8">
|
<div class="col-sm-9">
|
||||||
<input class="form-control" ng-model="item.subscriptionGroupConfig.retryQueueNums"
|
<input class="form-control" ng-model="item.subscriptionGroupConfig.retryQueueNums"
|
||||||
type="text" ng-disabled="{{!ngDialogData.writeOperationEnabled}}"
|
type="text" ng-disabled="{{!ngDialogData.writeOperationEnabled}}"
|
||||||
required/>
|
required/>
|
||||||
@@ -284,16 +284,16 @@
|
|||||||
<!--</div>-->
|
<!--</div>-->
|
||||||
<!--</div>-->
|
<!--</div>-->
|
||||||
<div class="form-group">
|
<div class="form-group">
|
||||||
<label class="control-label col-sm-2">brokerId:</label>
|
<label class="control-label col-sm-3">brokerId:</label>
|
||||||
<div class="col-sm-8">
|
<div class="col-sm-9">
|
||||||
<input class="form-control" ng-model="item.subscriptionGroupConfig.brokerId" type="text"
|
<input class="form-control" ng-model="item.subscriptionGroupConfig.brokerId" type="text"
|
||||||
ng-disabled="{{!ngDialogData.writeOperationEnabled}}" required/>
|
ng-disabled="{{!ngDialogData.writeOperationEnabled}}" required/>
|
||||||
<span class="text-danger" ng-show="addAppForm.name.$error.required">编号不能为空.</span>
|
<span class="text-danger" ng-show="addAppForm.name.$error.required">编号不能为空.</span>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="form-group">
|
<div class="form-group">
|
||||||
<label class="control-label col-sm-2">whichBrokerWhenConsumeSlowly:</label>
|
<label class="control-label col-sm-3">whichBrokerWhenConsumeSlowly:</label>
|
||||||
<div class="col-sm-8">
|
<div class="col-sm-9">
|
||||||
<input class="form-control"
|
<input class="form-control"
|
||||||
ng-model="item.subscriptionGroupConfig.whichBrokerWhenConsumeSlowly" type="text"
|
ng-model="item.subscriptionGroupConfig.whichBrokerWhenConsumeSlowly" type="text"
|
||||||
ng-disabled="{{!ngDialogData.writeOperationEnabled}}" required/>
|
ng-disabled="{{!ngDialogData.writeOperationEnabled}}" required/>
|
||||||
|
|||||||
266
src/main/resources/static/view/pages/dlqMessage.html
Normal file
266
src/main/resources/static/view/pages/dlqMessage.html
Normal file
@@ -0,0 +1,266 @@
|
|||||||
|
<!--
|
||||||
|
~ 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.
|
||||||
|
-->
|
||||||
|
<div class="container-fluid" id="deployHistoryList">
|
||||||
|
<div class="modal-body">
|
||||||
|
<div ng-cloak="" class="tabsdemoDynamicHeight">
|
||||||
|
<md-content>
|
||||||
|
<md-tabs md-dynamic-height="" md-border-bottom="">
|
||||||
|
<md-tab label="Consumer">
|
||||||
|
<h5 class="md-display-5">Total {{paginationConf.totalItems}} Messages</h5>
|
||||||
|
<md-content class="md-padding" style="min-height:600px">
|
||||||
|
<div class="row">
|
||||||
|
<form class="form-inline pull-left">
|
||||||
|
<div class="form-group">
|
||||||
|
<label>{{'CONSUMER' | translate}}:</label>
|
||||||
|
</div>
|
||||||
|
<div class="form-group ">
|
||||||
|
<div style="width: 300px">
|
||||||
|
<select name="mySelectGroup" chosen
|
||||||
|
ng-model="selectedConsumerGroup"
|
||||||
|
ng-options="item for item in allConsumerGroupList"
|
||||||
|
ng-change="onChangeQueryCondition()"
|
||||||
|
required>
|
||||||
|
<option value=""></option>
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="form-group ">
|
||||||
|
<label>{{'BEGIN' | translate}}:</label>
|
||||||
|
<div class="input-group">
|
||||||
|
<input class="form-control" datetimepicker
|
||||||
|
ng-change="onChangeQueryCondition()" ng-model="timepickerBegin"
|
||||||
|
options="timepickerOptions"/>
|
||||||
|
<span class="input-group-addon"><span
|
||||||
|
class="glyphicon glyphicon-calendar"></span></span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="form-group">
|
||||||
|
<label>{{'END' | translate}}:</label>
|
||||||
|
<div class="input-group">
|
||||||
|
<input class="form-control" datetimepicker
|
||||||
|
ng-change="onChangeQueryCondition()" ng-model="timepickerEnd"
|
||||||
|
options="timepickerOptions"/>
|
||||||
|
<span class="input-group-addon"><span
|
||||||
|
class="glyphicon glyphicon-calendar"></span></span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<button id="searchAppsButton" type="button"
|
||||||
|
class="btn btn-raised btn-sm btn-primary"
|
||||||
|
data-toggle="modal"
|
||||||
|
ng-click="queryDlqMessageByConsumerGroup()">
|
||||||
|
<span class="glyphicon glyphicon-search"></span> {{ 'SEARCH' | translate}}
|
||||||
|
</button>
|
||||||
|
</form>
|
||||||
|
<table class="table table-bordered text-middle">
|
||||||
|
<tr>
|
||||||
|
<th class="text-center">Message ID</th>
|
||||||
|
<th class="text-center">Tag</th>
|
||||||
|
<th class="text-center">Key</th>
|
||||||
|
<th class="text-center">StoreTime</th>
|
||||||
|
<th class="text-center">Operation</th>
|
||||||
|
</tr>
|
||||||
|
<tr style="display: none" id="noMsgTip">
|
||||||
|
<td colspan="6" style="text-align: center">{{'NO_MATCH_RESULT' | translate}}</td>
|
||||||
|
</tr>
|
||||||
|
<tr ng-repeat="item in messageShowList">
|
||||||
|
<td class="text-center">{{item.msgId}}</td>
|
||||||
|
<td class="text-center">{{item.properties.TAGS}}</td>
|
||||||
|
<td class="text-center">{{item.properties.KEYS}}</td>
|
||||||
|
<td class="text-center">{{item.storeTimestamp | date:'yyyy-MM-dd HH:mm:ss'}}
|
||||||
|
</td>
|
||||||
|
<td class="text-center">
|
||||||
|
<button class="btn btn-raised btn-sm btn-primary" type="button"
|
||||||
|
ng-click="queryDlqMessageDetail(item.msgId, selectedConsumerGroup)">
|
||||||
|
{{'MESSAGE_DETAIL' | translate}}
|
||||||
|
</button>
|
||||||
|
<button class="btn btn-raised btn-sm btn-primary" type="button"
|
||||||
|
ng-click="resendDlqMessage(item, selectedConsumerGroup)">
|
||||||
|
{{'RESEND_MESSAGE' | translate}}
|
||||||
|
</button>
|
||||||
|
<button class="btn btn-raised btn-sm btn-primary" type="button"
|
||||||
|
ng-click="exportDlqMessage(item.msgId, selectedConsumerGroup)">
|
||||||
|
{{'EXPORT' | translate}}
|
||||||
|
</button>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
</table>
|
||||||
|
<tm-pagination conf="paginationConf"></tm-pagination>
|
||||||
|
</div>
|
||||||
|
</md-content>
|
||||||
|
</md-tab>
|
||||||
|
<md-tab label="Message ID">
|
||||||
|
<h5 class="md-display-5">topic can't be empty if you producer client version>=v3.5.8</h5>
|
||||||
|
<md-content class="md-padding" style="min-height:600px">
|
||||||
|
<div class="row">
|
||||||
|
<form class="form-inline pull-left">
|
||||||
|
<div class="form-group">
|
||||||
|
<label>{{'CONSUMER' | translate}}:</label>
|
||||||
|
</div>
|
||||||
|
<div class="form-group ">
|
||||||
|
<div style="width: 300px">
|
||||||
|
<select name="mySelectGroup" chosen
|
||||||
|
ng-model="selectedConsumerGroup"
|
||||||
|
ng-options="item for item in allConsumerGroupList"
|
||||||
|
ng-change="onChangeQueryCondition()"
|
||||||
|
required>
|
||||||
|
<option value=""></option>
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="form-group">
|
||||||
|
<label>MessageId:</label>
|
||||||
|
<input class="form-control" style="width: 450px" type="text" ng-model="messageId"
|
||||||
|
required/>
|
||||||
|
</div>
|
||||||
|
<button type="button" class="btn btn-raised btn-sm btn-primary" data-toggle="modal"
|
||||||
|
ng-click="queryDlqMessageByMessageId(messageId, selectedConsumerGroup)">
|
||||||
|
<span class="glyphicon glyphicon-search"></span> {{ 'SEARCH' | translate}}
|
||||||
|
</button>
|
||||||
|
</form>
|
||||||
|
<table class="table table-bordered text-middle">
|
||||||
|
<tr>
|
||||||
|
<th class="text-center">Message ID</th>
|
||||||
|
<th class="text-center">Tag</th>
|
||||||
|
<th class="text-center">Key</th>
|
||||||
|
<th class="text-center">StoreTime</th>
|
||||||
|
<th class="text-center">Operation</th>
|
||||||
|
</tr>
|
||||||
|
<tr ng-repeat="item in queryDlqMessageByMessageIdResult">
|
||||||
|
<td class="text-center">{{item.msgId}}</td>
|
||||||
|
<td class="text-center">{{item.properties.TAGS}}</td>
|
||||||
|
<td class="text-center">{{item.properties.KEYS}}</td>
|
||||||
|
<td class="text-center">{{item.storeTimestamp | date:'yyyy-MM-dd HH:mm:ss'}}
|
||||||
|
</td>
|
||||||
|
<td class="text-center">
|
||||||
|
<button class="btn btn-raised btn-sm btn-primary" type="button"
|
||||||
|
ng-click="queryDlqMessageDetail(item.msgId, selectedConsumerGroup)">
|
||||||
|
{{'MESSAGE_DETAIL' | translate}}
|
||||||
|
</button>
|
||||||
|
<button class="btn btn-raised btn-sm btn-primary" type="button"
|
||||||
|
ng-click="resendDlqMessage(item, selectedConsumerGroup)">
|
||||||
|
{{'RESEND_MESSAGE' | translate}}
|
||||||
|
</button>
|
||||||
|
<button class="btn btn-raised btn-sm btn-primary" type="button"
|
||||||
|
ng-click="exportDlqMessage(item.msgId, selectedConsumerGroup)">
|
||||||
|
{{'EXPORT' | translate}}
|
||||||
|
</button>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
</table>
|
||||||
|
</div>
|
||||||
|
</md-content>
|
||||||
|
</md-tab>
|
||||||
|
</md-tabs>
|
||||||
|
</md-content>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
|
||||||
|
<script type="text/ng-template" id="dlqMessageDetailViewDialog">
|
||||||
|
<md-content class="md-padding">
|
||||||
|
<div>
|
||||||
|
<form id="addAppForm" name="addAppForm" class="form-horizontal" novalidate>
|
||||||
|
<div class="form-group">
|
||||||
|
<label class="control-label col-sm-2">Message ID:</label>
|
||||||
|
<div class="col-sm-10">
|
||||||
|
<label class="form-control">{{ngDialogData.messageView.msgId}}</label>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="form-group">
|
||||||
|
<label class="control-label col-sm-2">Topic:</label>
|
||||||
|
<div class="col-sm-10">
|
||||||
|
<label class="form-control">{{ngDialogData.messageView.topic}}</label>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="form-group">
|
||||||
|
<label class="control-label col-sm-2">Properties:</label>
|
||||||
|
<div class="col-sm-10">
|
||||||
|
<textarea class="form-control"
|
||||||
|
style="min-height:60px; resize: none"
|
||||||
|
readonly>{{ngDialogData.messageView.properties}}</textarea>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="form-group">
|
||||||
|
<label class="control-label col-sm-2">ReconsumeTimes:</label>
|
||||||
|
<div class="col-sm-10">
|
||||||
|
<label class="form-control">{{ngDialogData.messageView.reconsumeTimes}}</label>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="form-group">
|
||||||
|
<label class="control-label col-sm-2">Tag:</label>
|
||||||
|
<div class="col-sm-10">
|
||||||
|
<label class="form-control">{{ngDialogData.messageView.properties.TAGS}}</label>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="form-group">
|
||||||
|
<label class="control-label col-sm-2">Key:</label>
|
||||||
|
<div class="col-sm-10">
|
||||||
|
<label class="form-control">{{ngDialogData.messageView.properties.KEYS}}</label>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="form-group">
|
||||||
|
<label class="control-label col-sm-2">Storetime:</label>
|
||||||
|
<div class="col-sm-10">
|
||||||
|
<label class="form-control">{{ngDialogData.messageView.storeTimestamp | date:'yyyy-MM-dd HH:mm:ss'}}</label>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="form-group">
|
||||||
|
<label class="control-label col-sm-2">StoreHost:</label>
|
||||||
|
<div class="col-sm-10">
|
||||||
|
<label class="form-control">{{ngDialogData.messageView.storeHost}}</label>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="form-group">
|
||||||
|
<label class="control-label col-sm-2">Message body:</label>
|
||||||
|
<div class="col-sm-10">
|
||||||
|
<textarea class="form-control"
|
||||||
|
ng-model="ngDialogData.messageView.messageBody"
|
||||||
|
style="min-height:100px; resize: none"
|
||||||
|
readonly></textarea>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
</md-content>
|
||||||
|
<div class="modal-footer">
|
||||||
|
<div class="ngdialog-buttons">
|
||||||
|
<button type="button" class="ngdialog-button ngdialog-button-secondary"
|
||||||
|
ng-click="closeThisDialog('Cancel')">{{ 'CLOSE' | translate }}
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<script type="text/ng-template" id="operationResultDialog">
|
||||||
|
<div class="modal-header">
|
||||||
|
<h4 class="modal-title">Result</h4>
|
||||||
|
</div>
|
||||||
|
<div class="modal-body ">
|
||||||
|
<form class="form-horizontal" novalidate>
|
||||||
|
{{ngDialogData.result}}
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
<div class="modal-footer">
|
||||||
|
<div class="ngdialog-buttons">
|
||||||
|
<button type="button" class="ngdialog-button ngdialog-button-secondary"
|
||||||
|
ng-click="closeThisDialog('Cancel')">{{ 'CLOSE' | translate }}
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</script>
|
||||||
@@ -228,7 +228,7 @@
|
|||||||
</form>
|
</form>
|
||||||
</div>
|
</div>
|
||||||
<p>messageTrackList:</p>
|
<p>messageTrackList:</p>
|
||||||
<table class="table-bordered table">
|
<table class="table-bordered table text-middle">
|
||||||
<tr>
|
<tr>
|
||||||
<th class="text-center">consumerGroup</th>
|
<th class="text-center">consumerGroup</th>
|
||||||
<th class="text-center">trackType</th>
|
<th class="text-center">trackType</th>
|
||||||
|
|||||||
@@ -0,0 +1,138 @@
|
|||||||
|
/*
|
||||||
|
* 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.rocketmq.dashboard.controller;
|
||||||
|
|
||||||
|
import com.alibaba.fastjson.JSON;
|
||||||
|
import com.google.common.collect.Lists;
|
||||||
|
import org.apache.rocketmq.client.exception.MQClientException;
|
||||||
|
import org.apache.rocketmq.common.MixAll;
|
||||||
|
import org.apache.rocketmq.common.protocol.ResponseCode;
|
||||||
|
import org.apache.rocketmq.common.protocol.route.TopicRouteData;
|
||||||
|
import org.apache.rocketmq.dashboard.model.MessagePage;
|
||||||
|
import org.apache.rocketmq.dashboard.model.MessageView;
|
||||||
|
import org.apache.rocketmq.dashboard.model.request.MessageQuery;
|
||||||
|
import org.apache.rocketmq.dashboard.service.impl.DlqMessageServiceImpl;
|
||||||
|
import org.apache.rocketmq.dashboard.service.impl.MessageServiceImpl;
|
||||||
|
import org.apache.rocketmq.dashboard.util.MockObjectUtil;
|
||||||
|
import org.junit.Test;
|
||||||
|
import org.mockito.InjectMocks;
|
||||||
|
import org.mockito.Mock;
|
||||||
|
import org.mockito.Spy;
|
||||||
|
import org.springframework.data.domain.PageImpl;
|
||||||
|
import org.springframework.data.domain.PageRequest;
|
||||||
|
import org.springframework.http.MediaType;
|
||||||
|
import org.springframework.test.web.servlet.request.MockMvcRequestBuilders;
|
||||||
|
|
||||||
|
import static org.hamcrest.Matchers.hasSize;
|
||||||
|
import static org.mockito.ArgumentMatchers.any;
|
||||||
|
import static org.mockito.Mockito.when;
|
||||||
|
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.content;
|
||||||
|
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.jsonPath;
|
||||||
|
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;
|
||||||
|
|
||||||
|
public class DlqMessageControllerTest extends BaseControllerTest {
|
||||||
|
|
||||||
|
@InjectMocks
|
||||||
|
private DlqMessageController dlqMessageController;
|
||||||
|
|
||||||
|
@Spy
|
||||||
|
private DlqMessageServiceImpl dlqMessageService;
|
||||||
|
|
||||||
|
@Mock
|
||||||
|
private MessageServiceImpl messageService;
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testQueryDlqMessageByConsumerGroup() throws Exception {
|
||||||
|
final String url = "/dlqMessage/queryDlqMessageByConsumerGroup.query";
|
||||||
|
MessageQuery query = new MessageQuery();
|
||||||
|
query.setPageNum(1);
|
||||||
|
query.setPageSize(10);
|
||||||
|
query.setTopic(MixAll.DLQ_GROUP_TOPIC_PREFIX + "group_test");
|
||||||
|
query.setTaskId("");
|
||||||
|
query.setBegin(System.currentTimeMillis() - 3 * 24 * 60 * 60 * 1000);
|
||||||
|
query.setEnd(System.currentTimeMillis());
|
||||||
|
{
|
||||||
|
TopicRouteData topicRouteData = MockObjectUtil.createTopicRouteData();
|
||||||
|
when(mqAdminExt.examineTopicRouteInfo(any()))
|
||||||
|
.thenThrow(new MQClientException(ResponseCode.TOPIC_NOT_EXIST, "topic not exist"))
|
||||||
|
.thenThrow(new MQClientException(ResponseCode.NO_MESSAGE, "query no message"))
|
||||||
|
.thenThrow(new RuntimeException())
|
||||||
|
.thenReturn(topicRouteData);
|
||||||
|
MessageView messageView = MessageView.fromMessageExt(MockObjectUtil.createMessageExt());
|
||||||
|
PageRequest page = PageRequest.of(query.getPageNum(), query.getPageSize());
|
||||||
|
MessagePage messagePage = new MessagePage
|
||||||
|
(new PageImpl<>(Lists.newArrayList(messageView), page, 0), query.getTaskId());
|
||||||
|
when(messageService.queryMessageByPage(any())).thenReturn(messagePage);
|
||||||
|
}
|
||||||
|
requestBuilder = MockMvcRequestBuilders.post(url);
|
||||||
|
requestBuilder.contentType(MediaType.APPLICATION_JSON_UTF8);
|
||||||
|
requestBuilder.content(JSON.toJSONString(query));
|
||||||
|
// 1、%DLQ%group_test is not exist
|
||||||
|
perform = mockMvc.perform(requestBuilder);
|
||||||
|
perform.andExpect(status().isOk())
|
||||||
|
.andExpect(jsonPath("$.data.page.content", hasSize(0)));
|
||||||
|
|
||||||
|
// 2、Other MQClientException occur
|
||||||
|
perform = mockMvc.perform(requestBuilder);
|
||||||
|
perform.andExpect(status().isOk())
|
||||||
|
.andExpect(jsonPath("$.data").doesNotExist())
|
||||||
|
.andExpect(jsonPath("$.status").value(-1))
|
||||||
|
.andExpect(jsonPath("$.errMsg").isNotEmpty());
|
||||||
|
|
||||||
|
// 3、Other Exception occur
|
||||||
|
perform = mockMvc.perform(requestBuilder);
|
||||||
|
perform.andExpect(status().isOk())
|
||||||
|
.andExpect(jsonPath("$.data").doesNotExist())
|
||||||
|
.andExpect(jsonPath("$.status").value(-1))
|
||||||
|
.andExpect(jsonPath("$.errMsg").isNotEmpty());
|
||||||
|
|
||||||
|
// 4、query dlq message success
|
||||||
|
perform = mockMvc.perform(requestBuilder);
|
||||||
|
perform.andExpect(status().isOk())
|
||||||
|
.andExpect(jsonPath("$.data.page.content", hasSize(1)))
|
||||||
|
.andExpect(jsonPath("$.data.page.content[0].msgId").value("0A9A003F00002A9F0000000000000319"));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testExportDlqMessage() throws Exception {
|
||||||
|
final String url = "/dlqMessage/exportDlqMessage.do";
|
||||||
|
{
|
||||||
|
when(mqAdminExt.viewMessage(any(), any()))
|
||||||
|
.thenThrow(new RuntimeException())
|
||||||
|
.thenReturn(MockObjectUtil.createMessageExt());
|
||||||
|
}
|
||||||
|
requestBuilder = MockMvcRequestBuilders.get(url);
|
||||||
|
requestBuilder.param("consumerGroup", "group_test");
|
||||||
|
requestBuilder.param("msgId", "0A9A003F00002A9F0000000000000319");
|
||||||
|
// 1、viewMessage exception
|
||||||
|
perform = mockMvc.perform(requestBuilder);
|
||||||
|
perform.andExpect(status().isOk())
|
||||||
|
.andExpect(jsonPath("$.data").doesNotExist())
|
||||||
|
.andExpect(jsonPath("$.status").value(-1))
|
||||||
|
.andExpect(jsonPath("$.errMsg").isNotEmpty());
|
||||||
|
|
||||||
|
// 2、export dlqMessage success
|
||||||
|
perform = mockMvc.perform(requestBuilder);
|
||||||
|
perform.andExpect(status().is(200))
|
||||||
|
.andExpect(content().contentType("application/vnd.ms-excel"));
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override protected Object getTestController() {
|
||||||
|
return dlqMessageController;
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user