mirror of
https://github.com/apache/rocketmq-dashboard.git
synced 2025-09-11 12:05:56 +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>
|
||||
<jaxb-api.version>2.3.1</jaxb-api.version>
|
||||
<commons-pool2.version>2.4.3</commons-pool2.version>
|
||||
<easyexcel.version>2.2.10</easyexcel.version>
|
||||
<asm.version>4.2</asm.version>
|
||||
</properties>
|
||||
|
||||
<dependencies>
|
||||
@@ -234,6 +236,17 @@
|
||||
<artifactId>commons-pool2</artifactId>
|
||||
<version>${commons-pool2.version}</version>
|
||||
</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>
|
||||
<build>
|
||||
<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/producer.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/ops.js?timestamp=7"></script>
|
||||
<script type="text/javascript" src="src/remoteApi/remoteApi.js"></script>
|
||||
|
@@ -195,6 +195,9 @@ app.config(['$routeProvider', '$httpProvider','$cookiesProvider','getDictNamePro
|
||||
}).when('/message', {
|
||||
templateUrl: 'view/pages/message.html',
|
||||
controller:'messageController'
|
||||
}).when('/dlqMessage', {
|
||||
templateUrl: 'view/pages/dlqMessage.html',
|
||||
controller:'dlqMessageController'
|
||||
}).when('/messageTrace', {
|
||||
templateUrl: 'view/pages/messageTrace.html',
|
||||
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",
|
||||
"VIEW_EXCEPTION":"View Exception",
|
||||
"MESSAGETRACE":"MessageTrace",
|
||||
"DLQ_MESSAGE":"DLQMessage",
|
||||
"COMMIT": "Commit",
|
||||
"OPERATION": "Operation",
|
||||
"ADD": "Add",
|
||||
@@ -108,5 +109,7 @@ var en = {
|
||||
"ENABLE_MESSAGE_TRACE":"Enable Message Trace",
|
||||
"MESSAGE_TRACE_DETAIL":"Message Trace Detail",
|
||||
"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":"生产者",
|
||||
"MESSAGE":"消息",
|
||||
"MESSAGE_DETAIL":"消息详情",
|
||||
"RESEND_MESSAGE":"重新消费",
|
||||
"RESEND_MESSAGE":"重新发送",
|
||||
"VIEW_EXCEPTION":"查看异常",
|
||||
"DLQ_MESSAGE":"死信消息",
|
||||
"MESSAGETRACE":"消息轨迹",
|
||||
"OPERATION": "操作",
|
||||
"ADD": "新增",
|
||||
@@ -109,5 +110,7 @@ var zh = {
|
||||
"ENABLE_MESSAGE_TRACE":"开启消息轨迹",
|
||||
"MESSAGE_TRACE_DETAIL":"消息轨迹详情",
|
||||
"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 =='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 =='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>
|
||||
</ul>
|
||||
<ul class="nav navbar-nav navbar-right">
|
||||
|
@@ -209,8 +209,8 @@
|
||||
<div class="modal-body " ng-repeat="item in ngDialogData.consumerRequestList">
|
||||
<form id="addAppForm" name="addAppForm" class="form-horizontal" novalidate>
|
||||
<div class="form-group" ng-hide="ngDialogData.bIsUpdate">
|
||||
<label class="control-label col-sm-4">clusterName:</label>
|
||||
<div class="col-sm-8">
|
||||
<label class="control-label col-sm-3">clusterName:</label>
|
||||
<div class="col-sm-9">
|
||||
<select name="mySelectClusterNameList" multiple chosen
|
||||
ng-model="item.clusterNameList"
|
||||
ng-options="clusterNameItem for clusterNameItem in ngDialogData.allClusterNameList">
|
||||
@@ -219,8 +219,8 @@
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label class="control-label col-sm-4">brokerName:</label>
|
||||
<div class="col-sm-8">
|
||||
<label class="control-label col-sm-3">brokerName:</label>
|
||||
<div class="col-sm-9">
|
||||
<select name="mySelectBrokerNameList" multiple chosen
|
||||
ng-disabled="ngDialogData.bIsUpdate"
|
||||
ng-model="item.brokerNameList"
|
||||
@@ -230,16 +230,16 @@
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label class="control-label col-sm-4">groupName:</label>
|
||||
<div class="col-sm-8">
|
||||
<label class="control-label col-sm-3">groupName:</label>
|
||||
<div class="col-sm-9">
|
||||
<input class="form-control" ng-model="item.subscriptionGroupConfig.groupName" type="text"
|
||||
ng-disabled="ngDialogData.bIsUpdate" required/>
|
||||
<span class="text-danger" ng-show="addAppForm.name.$error.required">编号不能为空.</span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label class="control-label col-sm-4">consumeEnable:</label>
|
||||
<div class="col-sm-8">
|
||||
<label class="control-label col-sm-3">consumeEnable:</label>
|
||||
<div class="col-sm-9">
|
||||
<md-switch class="md-primary" ng-disabled="{{!ngDialogData.writeOperationEnabled}}" md-no-ink
|
||||
aria-label="Switch No Ink" ng-model="item.subscriptionGroupConfig.consumeEnable">
|
||||
</md-switch>
|
||||
@@ -257,8 +257,8 @@
|
||||
<!--</div>-->
|
||||
<!--</div>-->
|
||||
<div class="form-group">
|
||||
<label class="control-label col-sm-4">consumeBroadcastEnable:</label>
|
||||
<div class="col-sm-8">
|
||||
<label class="control-label col-sm-3">consumeBroadcastEnable:</label>
|
||||
<div class="col-sm-9">
|
||||
<md-switch class="md-primary" ng-disabled="{{!ngDialogData.writeOperationEnabled}}" md-no-ink
|
||||
aria-label="Switch No Ink"
|
||||
ng-model="item.subscriptionGroupConfig.consumeBroadcastEnable">
|
||||
@@ -266,8 +266,8 @@
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label class="control-label col-sm-4">retryQueueNums:</label>
|
||||
<div class="col-sm-8">
|
||||
<label class="control-label col-sm-3">retryQueueNums:</label>
|
||||
<div class="col-sm-9">
|
||||
<input class="form-control" ng-model="item.subscriptionGroupConfig.retryQueueNums"
|
||||
type="text" ng-disabled="{{!ngDialogData.writeOperationEnabled}}"
|
||||
required/>
|
||||
@@ -284,16 +284,16 @@
|
||||
<!--</div>-->
|
||||
<!--</div>-->
|
||||
<div class="form-group">
|
||||
<label class="control-label col-sm-2">brokerId:</label>
|
||||
<div class="col-sm-8">
|
||||
<label class="control-label col-sm-3">brokerId:</label>
|
||||
<div class="col-sm-9">
|
||||
<input class="form-control" ng-model="item.subscriptionGroupConfig.brokerId" type="text"
|
||||
ng-disabled="{{!ngDialogData.writeOperationEnabled}}" required/>
|
||||
<span class="text-danger" ng-show="addAppForm.name.$error.required">编号不能为空.</span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label class="control-label col-sm-2">whichBrokerWhenConsumeSlowly:</label>
|
||||
<div class="col-sm-8">
|
||||
<label class="control-label col-sm-3">whichBrokerWhenConsumeSlowly:</label>
|
||||
<div class="col-sm-9">
|
||||
<input class="form-control"
|
||||
ng-model="item.subscriptionGroupConfig.whichBrokerWhenConsumeSlowly" type="text"
|
||||
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>
|
||||
</div>
|
||||
<p>messageTrackList:</p>
|
||||
<table class="table-bordered table">
|
||||
<table class="table-bordered table text-middle">
|
||||
<tr>
|
||||
<th class="text-center">consumerGroup</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