Version: 4.3 Update

Signed-off-by: gh0stkey <24655118+gh0stkey@users.noreply.github.com>
This commit is contained in:
gh0stkey
2025-07-29 12:04:21 +08:00
parent fca804cb7c
commit b597a9e6d9
8 changed files with 659 additions and 292 deletions

View File

@@ -35,7 +35,7 @@ public class Databoard extends JPanel {
private JSplitPane splitPane;
private MessageTable messageTable;
private JProgressBar progressBar;
private SwingWorker<Map<String, List<String>>, Void> handleComboBoxWorker;
private SwingWorker<Map<String, List<String>>, Integer> handleComboBoxWorker;
private SwingWorker<Void, Void> applyHostFilterWorker;
public Databoard(MontoyaApi api, ConfigLoader configLoader, MessageTableModel messageTableModel) {
@@ -125,16 +125,16 @@ public class Databoard extends JPanel {
columnModel.getColumn(5).setPreferredWidth((int) (totalWidth * 0.1));
}
private void setProgressBar(boolean status) {
progressBar.setIndeterminate(status);
if (!status) {
progressBar.setMaximum(100);
progressBar.setString("OK");
progressBar.setStringPainted(true);
private void setProgressBar(boolean status, String message, int progress) {
progressBar.setIndeterminate(status && progress <= 0);
progressBar.setString(message);
progressBar.setStringPainted(true);
progressBar.setMaximum(100);
if (progress > 0) {
progressBar.setValue(progress);
} else if (!status) {
progressBar.setValue(progressBar.getMaximum());
} else {
progressBar.setString("Loading...");
progressBar.setStringPainted(true);
}
}
@@ -173,54 +173,15 @@ public class Databoard extends JPanel {
String selectedHost = hostComboBox.getSelectedItem().toString();
if (getHostByList().contains(selectedHost)) {
progressBar.setVisible(true);
setProgressBar(true);
hostTextField.setText(selectedHost);
hostComboBox.setPopupVisible(false);
if (handleComboBoxWorker != null && !handleComboBoxWorker.isDone()) {
progressBar.setVisible(false);
handleComboBoxWorker.cancel(true);
}
handleComboBoxWorker = new SwingWorker<>() {
@Override
protected Map<String, List<String>> doInBackground() {
return getSelectedMapByHost(selectedHost);
}
@Override
protected void done() {
if (!isCancelled()) {
try {
Map<String, List<String>> selectedDataMap = get();
if (!selectedDataMap.isEmpty()) {
dataTabbedPane.removeAll();
for (Map.Entry<String, List<String>> entry : selectedDataMap.entrySet()) {
String tabTitle = String.format("%s (%s)", entry.getKey(), entry.getValue().size());
Datatable datatablePanel = new Datatable(api, configLoader, entry.getKey(), entry.getValue());
datatablePanel.setTableListener(messageTableModel);
dataTabbedPane.addTab(tabTitle, datatablePanel);
}
JSplitPane messageSplitPane = messageTableModel.getSplitPane();
splitPane.setLeftComponent(dataTabbedPane);
splitPane.setRightComponent(messageSplitPane);
messageTable = messageTableModel.getMessageTable();
resizePanel();
splitPane.setVisible(true);
hostTextField.setText(selectedHost);
hostComboBox.setPopupVisible(false);
applyHostFilter(selectedHost);
setProgressBar(false);
}
} catch (Exception ignored) {
}
}
}
};
handleComboBoxWorker = new DataLoadingWorker(selectedHost);
handleComboBoxWorker.execute();
}
@@ -251,33 +212,57 @@ public class Databoard extends JPanel {
isMatchHost = false;
}
private Map<String, List<String>> getSelectedMapByHost(String selectedHost) {
private Map<String, List<String>> getSelectedMapByHost(String selectedHost, DataLoadingWorker worker) {
ConcurrentHashMap<String, Map<String, List<String>>> dataMap = Config.globalDataMap;
Map<String, List<String>> selectedDataMap;
if (selectedHost.contains("*")) {
selectedDataMap = new HashMap<>();
dataMap.keySet().forEach(key -> {
List<String> matchingKeys = new ArrayList<>();
// 第一步:找出所有匹配的键(预处理)
for (String key : dataMap.keySet()) {
if ((StringProcessor.matchesHostPattern(key, selectedHost) || selectedHost.equals("*")) && !key.contains("*")) {
Map<String, List<String>> ruleMap = dataMap.get(key);
matchingKeys.add(key);
}
}
// 第二步:分批处理数据
int totalKeys = matchingKeys.size();
for (int i = 0; i < totalKeys; i++) {
String key = matchingKeys.get(i);
Map<String, List<String>> ruleMap = dataMap.get(key);
if (ruleMap != null) {
for (String ruleKey : ruleMap.keySet()) {
List<String> dataList = ruleMap.get(ruleKey);
if (selectedDataMap.containsKey(ruleKey)) {
List<String> mergedList = new ArrayList<>(selectedDataMap.get(ruleKey));
mergedList.addAll(dataList);
// 使用HashSet去重
HashSet<String> uniqueSet = new HashSet<>(mergedList);
selectedDataMap.put(ruleKey, new ArrayList<>(uniqueSet));
} else {
selectedDataMap.put(ruleKey, dataList);
selectedDataMap.put(ruleKey, new ArrayList<>(dataList));
}
}
}
});
// 报告进度
if (worker != null && i % 5 == 0) {
int progress = (int) ((i + 1) * 90.0 / totalKeys);
worker.publishProgress(progress);
}
}
} else {
selectedDataMap = dataMap.get(selectedHost);
// 对于非通配符匹配,直接返回结果
if (worker != null) {
worker.publishProgress(90);
}
}
return selectedDataMap;
return selectedDataMap != null ? selectedDataMap : new HashMap<>();
}
private void filterComboBoxList() {
@@ -395,4 +380,67 @@ public class Databoard extends JPanel {
hostTextField.setText("");
}
}
// 定义为内部类
private class DataLoadingWorker extends SwingWorker<Map<String, List<String>>, Integer> {
private final String selectedHost;
public DataLoadingWorker(String selectedHost) {
this.selectedHost = selectedHost;
progressBar.setVisible(true);
}
@Override
protected Map<String, List<String>> doInBackground() throws Exception {
return getSelectedMapByHost(selectedHost, this);
}
@Override
protected void process(List<Integer> chunks) {
if (!chunks.isEmpty()) {
int progress = chunks.get(chunks.size() - 1);
setProgressBar(true, "Loading... " + progress + "%", progress);
}
}
@Override
protected void done() {
if (!isCancelled()) {
try {
Map<String, List<String>> selectedDataMap = get();
if (selectedDataMap != null && !selectedDataMap.isEmpty()) {
dataTabbedPane.removeAll();
for (Map.Entry<String, List<String>> entry : selectedDataMap.entrySet()) {
String tabTitle = String.format("%s (%s)", entry.getKey(), entry.getValue().size());
Datatable datatablePanel = new Datatable(api, configLoader, entry.getKey(), entry.getValue());
datatablePanel.setTableListener(messageTableModel);
dataTabbedPane.addTab(tabTitle, datatablePanel);
}
JSplitPane messageSplitPane = messageTableModel.getSplitPane();
splitPane.setLeftComponent(dataTabbedPane);
splitPane.setRightComponent(messageSplitPane);
messageTable = messageTableModel.getMessageTable();
resizePanel();
splitPane.setVisible(true);
applyHostFilter(selectedHost);
setProgressBar(false, "OK", 100);
} else {
setProgressBar(false, "Error", 0);
}
} catch (Exception e) {
api.logging().logToOutput("DataLoadingWorker: " + e.getMessage());
setProgressBar(false, "Error", 0);
}
}
}
// 提供一个公共方法来发布进度
public void publishProgress(int progress) {
publish(progress);
}
}
}

View File

@@ -33,15 +33,32 @@ public class MessageRenderer extends DefaultTableCellRenderer {
boolean hasFocus, int row, int column) {
Component component = super.getTableCellRendererComponent(table, value, isSelected, hasFocus, row, column);
MessageEntry messageEntry = log.get(table.convertRowIndexToModel(row)); // 使用convertRowIndexToModel方法转换行索引
// 添加边界检查以防止IndexOutOfBoundsException
int modelRow = table.convertRowIndexToModel(row);
if (modelRow < 0 || modelRow >= log.size()) {
// 如果索引无效,返回默认渲染组件(使用默认背景色)
component.setBackground(Color.WHITE);
component.setForeground(Color.BLACK);
return component;
}
MessageEntry messageEntry = log.get(modelRow);
// 设置颜色
String colorByLog = messageEntry.getColor();
Color color = colorMap.get(colorByLog);
// 如果颜色映射中没有找到对应颜色,使用默认白色
if (color == null) {
color = Color.WHITE;
}
if (isSelected) {
// 通过更改RGB颜色来达成阴影效果
component.setBackground(new Color(color.getRed() - 0x20, color.getGreen() - 0x20, color.getBlue() - 0x20));
int red = Math.max(0, color.getRed() - 0x20);
int green = Math.max(0, color.getGreen() - 0x20);
int blue = Math.max(0, color.getBlue() - 0x20);
component.setBackground(new Color(red, green, blue));
} else {
// 否则使用原始颜色
component.setBackground(color);

View File

@@ -55,7 +55,6 @@ public class MessageTableModel extends AbstractTableModel {
messageTable.setDefaultRenderer(Object.class, new MessageRenderer(filteredLog, messageTable));
messageTable.setAutoCreateRowSorter(true);
// Length字段根据大小进行排序
TableRowSorter<DefaultTableModel> sorter = getDefaultTableModelTableRowSorter();
messageTable.setRowSorter(sorter);
messageTable.setAutoResizeMode(JTable.AUTO_RESIZE_OFF);
@@ -71,6 +70,8 @@ public class MessageTableModel extends AbstractTableModel {
private TableRowSorter<DefaultTableModel> getDefaultTableModelTableRowSorter() {
TableRowSorter<DefaultTableModel> sorter = (TableRowSorter<DefaultTableModel>) messageTable.getRowSorter();
// Length字段根据大小进行排序
sorter.setComparator(4, (Comparator<String>) (s1, s2) -> {
Integer age1 = Integer.parseInt(s1);
Integer age2 = Integer.parseInt(s2);
@@ -104,6 +105,14 @@ public class MessageTableModel extends AbstractTableModel {
return;
}
if (comment == null || comment.trim().isEmpty()) {
return;
}
if (color == null || color.trim().isEmpty()) {
return;
}
boolean isDuplicate = false;
try {
if (!log.isEmpty() && flag) {
@@ -241,131 +250,163 @@ public class MessageTableModel extends AbstractTableModel {
}
public void applyHostFilter(String filterText) {
filteredLog.clear();
fireTableDataChanged();
// 预分配合适的容量,避免频繁扩容
final List<MessageEntry> newFilteredLog = new ArrayList<>(log.size() / 2);
// 预处理过滤条件,优化性能
final boolean isWildcardFilter = "*".equals(filterText) || filterText.contains("*");
final String normalizedFilter = filterText.toLowerCase().trim();
// 创建log的安全副本
final List<MessageEntry> logSnapshot;
synchronized (log) {
logSnapshot = new ArrayList<>(log);
}
int batchSize = 500;
// 分批处理数据
List<MessageEntry> batch = new ArrayList<>(batchSize);
int count = 0;
for (MessageEntry entry : log) {
String host = StringProcessor.getHostByUrl(entry.getUrl());
if (!host.isEmpty() && (StringProcessor.matchesHostPattern(host, filterText) || filterText.contains("*"))) {
batch.add(entry);
count++;
// 当批次达到指定大小时更新UI
if (count % batchSize == 0) {
final List<MessageEntry> currentBatch = new ArrayList<>(batch);
SwingUtilities.invokeLater(() -> {
filteredLog.addAll(currentBatch);
fireTableDataChanged();
});
batch.clear();
// 使用并行流高效过滤,但保持有序
logSnapshot.parallelStream()
.filter(entry -> {
// 快速通配符检查
if (isWildcardFilter && "*".equals(filterText)) {
return true;
}
}
}
try {
String host = StringProcessor.getHostByUrl(entry.getUrl());
if (host.isEmpty()) {
return false;
}
// 优化后的匹配逻辑
return StringProcessor.matchesHostPattern(host, filterText) ||
(isWildcardFilter && host.toLowerCase().contains(normalizedFilter.replace("*", "")));
} catch (Exception e) {
return false;
}
})
.forEachOrdered(newFilteredLog::add);
// 处理最后一批
if (!batch.isEmpty()) {
final List<MessageEntry> finalBatch = new ArrayList<>(batch);
SwingUtilities.invokeLater(() -> {
filteredLog.addAll(finalBatch);
fireTableDataChanged();
});
}
// 一次性更新UI避免频繁刷新
SwingUtilities.invokeLater(() -> {
synchronized (filteredLog) {
filteredLog.clear();
filteredLog.addAll(newFilteredLog);
}
fireTableDataChanged();
});
}
public void applyMessageFilter(String tableName, String filterText) {
filteredLog.clear();
for (MessageEntry entry : log) {
List<MessageEntry> newFilteredLog = new ArrayList<>();
// 创建log的安全副本以避免ConcurrentModificationException
List<MessageEntry> logSnapshot;
synchronized (log) {
logSnapshot = new ArrayList<>(log);
}
for (MessageEntry entry : logSnapshot) {
// 标志变量,表示是否满足过滤条件
AtomicBoolean isMatched = new AtomicBoolean(false);
HttpRequestResponse requestResponse = entry.getRequestResponse();
HttpRequest httpRequest = requestResponse.request();
HttpResponse httpResponse = requestResponse.response();
try {
HttpRequestResponse requestResponse = entry.getRequestResponse();
HttpRequest httpRequest = requestResponse.request();
HttpResponse httpResponse = requestResponse.response();
String requestString = new String(httpRequest.toByteArray().getBytes(), StandardCharsets.UTF_8);
String requestBody = new String(httpRequest.body().getBytes(), StandardCharsets.UTF_8);
String requestHeaders = httpRequest.headers().stream()
.map(HttpHeader::toString)
.collect(Collectors.joining("\r\n"));
String requestString = new String(httpRequest.toByteArray().getBytes(), StandardCharsets.UTF_8);
String requestBody = new String(httpRequest.body().getBytes(), StandardCharsets.UTF_8);
String requestHeaders = httpRequest.headers().stream()
.map(HttpHeader::toString)
.collect(Collectors.joining("\r\n"));
String responseString = new String(httpResponse.toByteArray().getBytes(), StandardCharsets.UTF_8);
String responseBody = new String(httpResponse.body().getBytes(), StandardCharsets.UTF_8);
String responseHeaders = httpResponse.headers().stream()
.map(HttpHeader::toString)
.collect(Collectors.joining("\r\n"));
String responseString = new String(httpResponse.toByteArray().getBytes(), StandardCharsets.UTF_8);
String responseBody = new String(httpResponse.body().getBytes(), StandardCharsets.UTF_8);
String responseHeaders = httpResponse.headers().stream()
.map(HttpHeader::toString)
.collect(Collectors.joining("\r\n"));
Config.globalRules.keySet().forEach(i -> {
for (Object[] objects : Config.globalRules.get(i)) {
String name = objects[1].toString();
String format = objects[4].toString();
String scope = objects[6].toString();
Config.globalRules.keySet().forEach(i -> {
for (Object[] objects : Config.globalRules.get(i)) {
String name = objects[1].toString();
String format = objects[4].toString();
String scope = objects[6].toString();
// 从注释中查看是否包含当前规则名,包含的再进行查询,有效减少无意义的检索时间
if (entry.getComment().contains(name)) {
if (name.equals(tableName)) {
// 标志变量,表示当前规则是否匹配
boolean isMatch = false;
// 从注释中查看是否包含当前规则名,包含的再进行查询,有效减少无意义的检索时间
if (entry.getComment().contains(name)) {
if (name.equals(tableName)) {
// 标志变量,表示当前规则是否匹配
boolean isMatch = false;
switch (scope) {
case "any":
isMatch = matchingString(format, filterText, requestString) || matchingString(format, filterText, responseString);
break;
case "request":
isMatch = matchingString(format, filterText, requestString);
break;
case "response":
isMatch = matchingString(format, filterText, responseString);
break;
case "any header":
isMatch = matchingString(format, filterText, requestHeaders) || matchingString(format, filterText, responseHeaders);
break;
case "request header":
isMatch = matchingString(format, filterText, requestHeaders);
break;
case "response header":
isMatch = matchingString(format, filterText, responseHeaders);
break;
case "any body":
isMatch = matchingString(format, filterText, requestBody) || matchingString(format, filterText, responseBody);
break;
case "request body":
isMatch = matchingString(format, filterText, requestBody);
break;
case "response body":
isMatch = matchingString(format, filterText, responseBody);
break;
case "request line":
String requestLine = requestString.split("\\r?\\n", 2)[0];
isMatch = matchingString(format, filterText, requestLine);
break;
case "response line":
String responseLine = responseString.split("\\r?\\n", 2)[0];
isMatch = matchingString(format, filterText, responseLine);
break;
default:
break;
switch (scope) {
case "any":
isMatch = matchingString(format, filterText, requestString) || matchingString(format, filterText, responseString);
break;
case "request":
isMatch = matchingString(format, filterText, requestString);
break;
case "response":
isMatch = matchingString(format, filterText, responseString);
break;
case "any header":
isMatch = matchingString(format, filterText, requestHeaders) || matchingString(format, filterText, responseHeaders);
break;
case "request header":
isMatch = matchingString(format, filterText, requestHeaders);
break;
case "response header":
isMatch = matchingString(format, filterText, responseHeaders);
break;
case "any body":
isMatch = matchingString(format, filterText, requestBody) || matchingString(format, filterText, responseBody);
break;
case "request body":
isMatch = matchingString(format, filterText, requestBody);
break;
case "response body":
isMatch = matchingString(format, filterText, responseBody);
break;
case "request line":
String requestLine = requestString.split("\\r?\\n", 2)[0];
isMatch = matchingString(format, filterText, requestLine);
break;
case "response line":
String responseLine = responseString.split("\\r?\\n", 2)[0];
isMatch = matchingString(format, filterText, responseLine);
break;
default:
break;
}
isMatched.set(isMatch);
break;
}
isMatched.set(isMatch);
break;
}
}
}
});
});
// 由于每个用户规则不同,如果进行项目文件共享则需要考虑全部匹配一下
if (!isMatched.get()) {
isMatched.set(matchingString("{0}", filterText, requestString) || matchingString("{0}", filterText, responseString));
}
if (isMatched.get()) {
newFilteredLog.add(entry);
}
} catch (Exception ignored) {
if (isMatched.get()) {
filteredLog.add(entry);
}
}
fireTableDataChanged();
messageTable.lastSelectedIndex = -1;
// 在EDT线程中更新UI
SwingUtilities.invokeLater(() -> {
synchronized (filteredLog) {
filteredLog.clear();
filteredLog.addAll(newFilteredLog);
}
fireTableDataChanged();
messageTable.lastSelectedIndex = -1;
});
}
private boolean matchingString(String format, String filterText, String target) {
@@ -398,7 +439,9 @@ public class MessageTableModel extends AbstractTableModel {
@Override
public int getRowCount() {
return filteredLog.size();
synchronized (filteredLog) {
return filteredLog.size();
}
}
@Override
@@ -408,27 +451,31 @@ public class MessageTableModel extends AbstractTableModel {
@Override
public Object getValueAt(int rowIndex, int columnIndex) {
if (!filteredLog.isEmpty()) {
synchronized (filteredLog) {
if (rowIndex < 0 || rowIndex >= filteredLog.size()) {
return "";
}
try {
MessageEntry messageEntry = filteredLog.get(rowIndex);
if (messageEntry != null) {
return switch (columnIndex) {
case 0 -> messageEntry.getMethod();
case 1 -> messageEntry.getUrl();
case 2 -> messageEntry.getComment();
case 3 -> messageEntry.getStatus();
case 4 -> messageEntry.getLength();
case 5 -> messageEntry.getColor();
default -> "";
};
if (messageEntry == null) {
return "";
}
return switch (columnIndex) {
case 0 -> messageEntry.getMethod();
case 1 -> messageEntry.getUrl();
case 2 -> messageEntry.getComment();
case 3 -> messageEntry.getStatus();
case 4 -> messageEntry.getLength();
case 5 -> messageEntry.getColor();
default -> "";
};
} catch (Exception e) {
api.logging().logToError("getValueAt: " + e.getMessage());
return "";
}
}
return "";
}
@Override