feat: 通知调试能力
This commit is contained in:
253
src/devtools/apiDebugger.js
Normal file
253
src/devtools/apiDebugger.js
Normal file
@@ -0,0 +1,253 @@
|
||||
// src/debug/apiDebugger.js
|
||||
/**
|
||||
* API 调试工具
|
||||
* 生产环境临时调试使用,后期可整体删除 src/debug/ 目录
|
||||
*/
|
||||
|
||||
import axios from 'axios';
|
||||
import { getApiBase } from '@utils/apiConfig';
|
||||
|
||||
class ApiDebugger {
|
||||
constructor() {
|
||||
this.requestLog = [];
|
||||
this.maxLogSize = 100;
|
||||
this.isLogging = true;
|
||||
}
|
||||
|
||||
/**
|
||||
* 初始化 Axios 拦截器
|
||||
*/
|
||||
init() {
|
||||
// 请求拦截器
|
||||
axios.interceptors.request.use(
|
||||
(config) => {
|
||||
if (this.isLogging) {
|
||||
const logEntry = {
|
||||
type: 'request',
|
||||
timestamp: new Date().toISOString(),
|
||||
method: config.method.toUpperCase(),
|
||||
url: config.url,
|
||||
baseURL: config.baseURL,
|
||||
fullURL: this._getFullURL(config),
|
||||
headers: config.headers,
|
||||
data: config.data,
|
||||
params: config.params,
|
||||
};
|
||||
|
||||
this._addLog(logEntry);
|
||||
|
||||
console.log(
|
||||
`%c[API Request] ${logEntry.method} ${logEntry.fullURL}`,
|
||||
'color: #2196F3; font-weight: bold;',
|
||||
{
|
||||
headers: config.headers,
|
||||
data: config.data,
|
||||
params: config.params,
|
||||
}
|
||||
);
|
||||
}
|
||||
return config;
|
||||
},
|
||||
(error) => {
|
||||
console.error('[API Request Error]', error);
|
||||
return Promise.reject(error);
|
||||
}
|
||||
);
|
||||
|
||||
// 响应拦截器
|
||||
axios.interceptors.response.use(
|
||||
(response) => {
|
||||
if (this.isLogging) {
|
||||
const logEntry = {
|
||||
type: 'response',
|
||||
timestamp: new Date().toISOString(),
|
||||
method: response.config.method.toUpperCase(),
|
||||
url: response.config.url,
|
||||
fullURL: this._getFullURL(response.config),
|
||||
status: response.status,
|
||||
statusText: response.statusText,
|
||||
headers: response.headers,
|
||||
data: response.data,
|
||||
};
|
||||
|
||||
this._addLog(logEntry);
|
||||
|
||||
console.log(
|
||||
`%c[API Response] ${logEntry.method} ${logEntry.fullURL} - ${logEntry.status}`,
|
||||
'color: #4CAF50; font-weight: bold;',
|
||||
{
|
||||
status: response.status,
|
||||
data: response.data,
|
||||
headers: response.headers,
|
||||
}
|
||||
);
|
||||
}
|
||||
return response;
|
||||
},
|
||||
(error) => {
|
||||
if (this.isLogging) {
|
||||
const logEntry = {
|
||||
type: 'error',
|
||||
timestamp: new Date().toISOString(),
|
||||
method: error.config?.method?.toUpperCase() || 'UNKNOWN',
|
||||
url: error.config?.url || 'UNKNOWN',
|
||||
fullURL: error.config ? this._getFullURL(error.config) : 'UNKNOWN',
|
||||
status: error.response?.status,
|
||||
statusText: error.response?.statusText,
|
||||
message: error.message,
|
||||
data: error.response?.data,
|
||||
};
|
||||
|
||||
this._addLog(logEntry);
|
||||
|
||||
console.error(
|
||||
`%c[API Error] ${logEntry.method} ${logEntry.fullURL}`,
|
||||
'color: #F44336; font-weight: bold;',
|
||||
{
|
||||
status: error.response?.status,
|
||||
message: error.message,
|
||||
data: error.response?.data,
|
||||
}
|
||||
);
|
||||
}
|
||||
return Promise.reject(error);
|
||||
}
|
||||
);
|
||||
|
||||
console.log('%c[API Debugger] Initialized', 'color: #FF9800; font-weight: bold;');
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取完整 URL
|
||||
*/
|
||||
_getFullURL(config) {
|
||||
const baseURL = config.baseURL || '';
|
||||
const url = config.url || '';
|
||||
const fullURL = baseURL + url;
|
||||
|
||||
// 添加查询参数
|
||||
if (config.params) {
|
||||
const params = new URLSearchParams(config.params).toString();
|
||||
return params ? `${fullURL}?${params}` : fullURL;
|
||||
}
|
||||
|
||||
return fullURL;
|
||||
}
|
||||
|
||||
/**
|
||||
* 添加日志
|
||||
*/
|
||||
_addLog(entry) {
|
||||
this.requestLog.unshift(entry);
|
||||
if (this.requestLog.length > this.maxLogSize) {
|
||||
this.requestLog = this.requestLog.slice(0, this.maxLogSize);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取所有日志
|
||||
*/
|
||||
getLogs(type = 'all') {
|
||||
if (type === 'all') {
|
||||
return this.requestLog;
|
||||
}
|
||||
return this.requestLog.filter((log) => log.type === type);
|
||||
}
|
||||
|
||||
/**
|
||||
* 清空日志
|
||||
*/
|
||||
clearLogs() {
|
||||
this.requestLog = [];
|
||||
console.log('[API Debugger] Logs cleared');
|
||||
}
|
||||
|
||||
/**
|
||||
* 导出日志为 JSON
|
||||
*/
|
||||
exportLogs() {
|
||||
const blob = new Blob([JSON.stringify(this.requestLog, null, 2)], {
|
||||
type: 'application/json',
|
||||
});
|
||||
const url = URL.createObjectURL(blob);
|
||||
const a = document.createElement('a');
|
||||
a.href = url;
|
||||
a.download = `api-logs-${Date.now()}.json`;
|
||||
a.click();
|
||||
URL.revokeObjectURL(url);
|
||||
console.log('[API Debugger] Logs exported');
|
||||
}
|
||||
|
||||
/**
|
||||
* 打印日志统计
|
||||
*/
|
||||
printStats() {
|
||||
const stats = {
|
||||
total: this.requestLog.length,
|
||||
requests: this.requestLog.filter((log) => log.type === 'request').length,
|
||||
responses: this.requestLog.filter((log) => log.type === 'response').length,
|
||||
errors: this.requestLog.filter((log) => log.type === 'error').length,
|
||||
};
|
||||
|
||||
console.table(stats);
|
||||
return stats;
|
||||
}
|
||||
|
||||
/**
|
||||
* 手动发送 API 请求(测试用)
|
||||
*/
|
||||
async testRequest(method, endpoint, data = null, config = {}) {
|
||||
const apiBase = getApiBase();
|
||||
const url = `${apiBase}${endpoint}`;
|
||||
|
||||
console.log(`[API Debugger] Testing ${method.toUpperCase()} ${url}`);
|
||||
|
||||
try {
|
||||
const response = await axios({
|
||||
method,
|
||||
url,
|
||||
data,
|
||||
...config,
|
||||
});
|
||||
|
||||
console.log('[API Debugger] Test succeeded:', response.data);
|
||||
return response.data;
|
||||
} catch (error) {
|
||||
console.error('[API Debugger] Test failed:', error);
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 开启/关闭日志记录
|
||||
*/
|
||||
toggleLogging(enabled) {
|
||||
this.isLogging = enabled;
|
||||
console.log(`[API Debugger] Logging ${enabled ? 'enabled' : 'disabled'}`);
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取最近的错误
|
||||
*/
|
||||
getRecentErrors(count = 10) {
|
||||
return this.requestLog.filter((log) => log.type === 'error').slice(0, count);
|
||||
}
|
||||
|
||||
/**
|
||||
* 按 URL 过滤日志
|
||||
*/
|
||||
getLogsByURL(urlPattern) {
|
||||
return this.requestLog.filter((log) => log.url && log.url.includes(urlPattern));
|
||||
}
|
||||
|
||||
/**
|
||||
* 按状态码过滤日志
|
||||
*/
|
||||
getLogsByStatus(status) {
|
||||
return this.requestLog.filter((log) => log.status === status);
|
||||
}
|
||||
}
|
||||
|
||||
// 导出单例
|
||||
export const apiDebugger = new ApiDebugger();
|
||||
export default apiDebugger;
|
||||
268
src/devtools/index.js
Normal file
268
src/devtools/index.js
Normal file
@@ -0,0 +1,268 @@
|
||||
// src/debug/index.js
|
||||
/**
|
||||
* 调试工具统一入口
|
||||
*
|
||||
* 使用方法:
|
||||
* 1. 开启调试: 在 .env.production 中设置 REACT_APP_ENABLE_DEBUG=true
|
||||
* 2. 使用控制台命令: window.__DEBUG__.api.getLogs()
|
||||
* 3. 后期移除: 删除整个 src/debug/ 目录 + 从 src/index.js 移除导入
|
||||
*
|
||||
* 全局 API:
|
||||
* - window.__DEBUG__ - 调试 API 主对象
|
||||
* - window.__DEBUG__.api - API 调试工具
|
||||
* - window.__DEBUG__.notification - 通知调试工具
|
||||
* - window.__DEBUG__.socket - Socket 调试工具
|
||||
* - window.__DEBUG__.help() - 显示帮助信息
|
||||
* - window.__DEBUG__.exportAll() - 导出所有日志
|
||||
*/
|
||||
|
||||
import { apiDebugger } from './apiDebugger';
|
||||
import { notificationDebugger } from './notificationDebugger';
|
||||
import { socketDebugger } from './socketDebugger';
|
||||
|
||||
class DebugToolkit {
|
||||
constructor() {
|
||||
this.api = apiDebugger;
|
||||
this.notification = notificationDebugger;
|
||||
this.socket = socketDebugger;
|
||||
}
|
||||
|
||||
/**
|
||||
* 初始化所有调试工具
|
||||
*/
|
||||
init() {
|
||||
console.log(
|
||||
'%c╔════════════════════════════════════════════════════════════════╗',
|
||||
'color: #FF9800; font-weight: bold;'
|
||||
);
|
||||
console.log(
|
||||
'%c║ 🔧 调试模式已启用 (Debug Mode Enabled) ║',
|
||||
'color: #FF9800; font-weight: bold;'
|
||||
);
|
||||
console.log(
|
||||
'%c╚════════════════════════════════════════════════════════════════╝',
|
||||
'color: #FF9800; font-weight: bold;'
|
||||
);
|
||||
console.log('');
|
||||
|
||||
// 初始化各个调试工具
|
||||
this.api.init();
|
||||
this.notification.init();
|
||||
this.socket.init();
|
||||
|
||||
// 暴露到全局
|
||||
window.__DEBUG__ = this;
|
||||
|
||||
// 打印帮助信息
|
||||
this._printWelcome();
|
||||
}
|
||||
|
||||
/**
|
||||
* 打印欢迎信息
|
||||
*/
|
||||
_printWelcome() {
|
||||
console.log('%c📚 调试工具使用指南:', 'color: #2196F3; font-weight: bold; font-size: 14px;');
|
||||
console.log('');
|
||||
console.log('%c1️⃣ API 调试:', 'color: #2196F3; font-weight: bold;');
|
||||
console.log(' __DEBUG__.api.getLogs() - 获取所有 API 日志');
|
||||
console.log(' __DEBUG__.api.getRecentErrors() - 获取最近的错误');
|
||||
console.log(' __DEBUG__.api.exportLogs() - 导出 API 日志');
|
||||
console.log(' __DEBUG__.api.testRequest(method, endpoint, data) - 测试 API 请求');
|
||||
console.log('');
|
||||
console.log('%c2️⃣ 通知调试:', 'color: #9C27B0; font-weight: bold;');
|
||||
console.log(' __DEBUG__.notification.getLogs() - 获取所有通知日志');
|
||||
console.log(' __DEBUG__.notification.forceNotification() - 发送测试通知');
|
||||
console.log(' __DEBUG__.notification.checkPermission() - 检查通知权限');
|
||||
console.log(' __DEBUG__.notification.exportLogs() - 导出通知日志');
|
||||
console.log('');
|
||||
console.log('%c3️⃣ Socket 调试:', 'color: #00BCD4; font-weight: bold;');
|
||||
console.log(' __DEBUG__.socket.getLogs() - 获取所有 Socket 日志');
|
||||
console.log(' __DEBUG__.socket.getStatus() - 获取连接状态');
|
||||
console.log(' __DEBUG__.socket.reconnect() - 手动重连');
|
||||
console.log(' __DEBUG__.socket.exportLogs() - 导出 Socket 日志');
|
||||
console.log('');
|
||||
console.log('%c4️⃣ 通用命令:', 'color: #4CAF50; font-weight: bold;');
|
||||
console.log(' __DEBUG__.help() - 显示帮助信息');
|
||||
console.log(' __DEBUG__.exportAll() - 导出所有日志');
|
||||
console.log(' __DEBUG__.printStats() - 打印所有统计信息');
|
||||
console.log(' __DEBUG__.clearAll() - 清空所有日志');
|
||||
console.log('');
|
||||
console.log(
|
||||
'%c⚠️ 警告: 调试模式会记录所有 API 请求和响应,请勿在生产环境长期开启!',
|
||||
'color: #F44336; font-weight: bold;'
|
||||
);
|
||||
console.log('');
|
||||
}
|
||||
|
||||
/**
|
||||
* 显示帮助信息
|
||||
*/
|
||||
help() {
|
||||
this._printWelcome();
|
||||
}
|
||||
|
||||
/**
|
||||
* 导出所有日志
|
||||
*/
|
||||
exportAll() {
|
||||
console.log('[Debug Toolkit] Exporting all logs...');
|
||||
|
||||
const allLogs = {
|
||||
timestamp: new Date().toISOString(),
|
||||
api: this.api.getLogs(),
|
||||
notification: this.notification.getLogs(),
|
||||
socket: this.socket.getLogs(),
|
||||
};
|
||||
|
||||
const blob = new Blob([JSON.stringify(allLogs, null, 2)], {
|
||||
type: 'application/json',
|
||||
});
|
||||
const url = URL.createObjectURL(blob);
|
||||
const a = document.createElement('a');
|
||||
a.href = url;
|
||||
a.download = `debug-all-logs-${Date.now()}.json`;
|
||||
a.click();
|
||||
URL.revokeObjectURL(url);
|
||||
|
||||
console.log('[Debug Toolkit] ✅ All logs exported');
|
||||
}
|
||||
|
||||
/**
|
||||
* 打印所有统计信息
|
||||
*/
|
||||
printStats() {
|
||||
console.log('\n%c=== 📊 调试统计信息 ===', 'color: #FF9800; font-weight: bold; font-size: 16px;');
|
||||
console.log('\n%c[API 统计]', 'color: #2196F3; font-weight: bold;');
|
||||
const apiStats = this.api.printStats();
|
||||
|
||||
console.log('\n%c[通知统计]', 'color: #9C27B0; font-weight: bold;');
|
||||
const notificationStats = this.notification.printStats();
|
||||
|
||||
console.log('\n%c[Socket 统计]', 'color: #00BCD4; font-weight: bold;');
|
||||
const socketStats = this.socket.printStats();
|
||||
|
||||
return {
|
||||
api: apiStats,
|
||||
notification: notificationStats,
|
||||
socket: socketStats,
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* 清空所有日志
|
||||
*/
|
||||
clearAll() {
|
||||
console.log('[Debug Toolkit] Clearing all logs...');
|
||||
this.api.clearLogs();
|
||||
this.notification.clearLogs();
|
||||
this.socket.clearLogs();
|
||||
console.log('[Debug Toolkit] ✅ All logs cleared');
|
||||
}
|
||||
|
||||
/**
|
||||
* 快速诊断(检查所有系统状态)
|
||||
*/
|
||||
diagnose() {
|
||||
console.log('\n%c=== 🔍 系统诊断 ===', 'color: #FF9800; font-weight: bold; font-size: 16px;');
|
||||
|
||||
// 1. Socket 状态
|
||||
console.log('\n%c[1/3] Socket 状态', 'color: #00BCD4; font-weight: bold;');
|
||||
const socketStatus = this.socket.getStatus();
|
||||
|
||||
// 2. 通知权限
|
||||
console.log('\n%c[2/3] 通知权限', 'color: #9C27B0; font-weight: bold;');
|
||||
const notificationStatus = this.notification.checkPermission();
|
||||
|
||||
// 3. API 错误
|
||||
console.log('\n%c[3/3] 最近的 API 错误', 'color: #F44336; font-weight: bold;');
|
||||
const recentErrors = this.api.getRecentErrors(5);
|
||||
if (recentErrors.length > 0) {
|
||||
console.table(
|
||||
recentErrors.map((err) => ({
|
||||
时间: err.timestamp,
|
||||
方法: err.method,
|
||||
URL: err.url,
|
||||
状态码: err.status,
|
||||
错误信息: err.message,
|
||||
}))
|
||||
);
|
||||
} else {
|
||||
console.log('✅ 没有 API 错误');
|
||||
}
|
||||
|
||||
// 4. 汇总报告
|
||||
const report = {
|
||||
timestamp: new Date().toISOString(),
|
||||
socket: socketStatus,
|
||||
notification: notificationStatus,
|
||||
apiErrors: recentErrors.length,
|
||||
};
|
||||
|
||||
console.log('\n%c=== 诊断报告 ===', 'color: #4CAF50; font-weight: bold;');
|
||||
console.table(report);
|
||||
|
||||
return report;
|
||||
}
|
||||
|
||||
/**
|
||||
* 性能监控
|
||||
*/
|
||||
performance() {
|
||||
console.log('\n%c=== ⚡ 性能监控 ===', 'color: #FF9800; font-weight: bold; font-size: 16px;');
|
||||
|
||||
// 计算 API 平均响应时间
|
||||
const apiLogs = this.api.getLogs();
|
||||
const responseTimes = [];
|
||||
|
||||
for (let i = 0; i < apiLogs.length - 1; i++) {
|
||||
const log = apiLogs[i];
|
||||
const prevLog = apiLogs[i + 1];
|
||||
|
||||
if (
|
||||
log.type === 'response' &&
|
||||
prevLog.type === 'request' &&
|
||||
log.url === prevLog.url
|
||||
) {
|
||||
const responseTime =
|
||||
new Date(log.timestamp).getTime() - new Date(prevLog.timestamp).getTime();
|
||||
responseTimes.push({
|
||||
url: log.url,
|
||||
method: log.method,
|
||||
time: responseTime,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
if (responseTimes.length > 0) {
|
||||
const avgTime =
|
||||
responseTimes.reduce((sum, item) => sum + item.time, 0) / responseTimes.length;
|
||||
const maxTime = Math.max(...responseTimes.map((item) => item.time));
|
||||
const minTime = Math.min(...responseTimes.map((item) => item.time));
|
||||
|
||||
console.log('API 响应时间统计:');
|
||||
console.table({
|
||||
平均响应时间: `${avgTime.toFixed(2)}ms`,
|
||||
最快响应: `${minTime}ms`,
|
||||
最慢响应: `${maxTime}ms`,
|
||||
请求总数: responseTimes.length,
|
||||
});
|
||||
|
||||
// 显示最慢的 5 个请求
|
||||
console.log('\n最慢的 5 个请求:');
|
||||
const slowest = responseTimes.sort((a, b) => b.time - a.time).slice(0, 5);
|
||||
console.table(
|
||||
slowest.map((item) => ({
|
||||
方法: item.method,
|
||||
URL: item.url,
|
||||
响应时间: `${item.time}ms`,
|
||||
}))
|
||||
);
|
||||
} else {
|
||||
console.log('暂无性能数据');
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 导出单例
|
||||
export const debugToolkit = new DebugToolkit();
|
||||
export default debugToolkit;
|
||||
166
src/devtools/notificationDebugger.js
Normal file
166
src/devtools/notificationDebugger.js
Normal file
@@ -0,0 +1,166 @@
|
||||
// src/debug/notificationDebugger.js
|
||||
/**
|
||||
* 通知系统调试工具
|
||||
* 扩展现有的 window.__NOTIFY_DEBUG__,添加更多生产环境调试能力
|
||||
*/
|
||||
|
||||
import { browserNotificationService } from '@services/browserNotificationService';
|
||||
|
||||
class NotificationDebugger {
|
||||
constructor() {
|
||||
this.eventLog = [];
|
||||
this.maxLogSize = 100;
|
||||
}
|
||||
|
||||
/**
|
||||
* 初始化调试工具
|
||||
*/
|
||||
init() {
|
||||
console.log('%c[Notification Debugger] Initialized', 'color: #FF9800; font-weight: bold;');
|
||||
}
|
||||
|
||||
/**
|
||||
* 记录通知事件
|
||||
*/
|
||||
logEvent(eventType, data) {
|
||||
const logEntry = {
|
||||
type: eventType,
|
||||
timestamp: new Date().toISOString(),
|
||||
data,
|
||||
};
|
||||
|
||||
this.eventLog.unshift(logEntry);
|
||||
if (this.eventLog.length > this.maxLogSize) {
|
||||
this.eventLog = this.eventLog.slice(0, this.maxLogSize);
|
||||
}
|
||||
|
||||
console.log(
|
||||
`%c[Notification Event] ${eventType}`,
|
||||
'color: #9C27B0; font-weight: bold;',
|
||||
data
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取所有事件日志
|
||||
*/
|
||||
getLogs() {
|
||||
return this.eventLog;
|
||||
}
|
||||
|
||||
/**
|
||||
* 清空日志
|
||||
*/
|
||||
clearLogs() {
|
||||
this.eventLog = [];
|
||||
console.log('[Notification Debugger] Logs cleared');
|
||||
}
|
||||
|
||||
/**
|
||||
* 导出日志
|
||||
*/
|
||||
exportLogs() {
|
||||
const blob = new Blob([JSON.stringify(this.eventLog, null, 2)], {
|
||||
type: 'application/json',
|
||||
});
|
||||
const url = URL.createObjectURL(blob);
|
||||
const a = document.createElement('a');
|
||||
a.href = url;
|
||||
a.download = `notification-logs-${Date.now()}.json`;
|
||||
a.click();
|
||||
URL.revokeObjectURL(url);
|
||||
console.log('[Notification Debugger] Logs exported');
|
||||
}
|
||||
|
||||
/**
|
||||
* 强制发送浏览器通知(测试用)
|
||||
*/
|
||||
forceNotification(options = {}) {
|
||||
const defaultOptions = {
|
||||
title: '🧪 测试通知',
|
||||
body: `测试时间: ${new Date().toLocaleString()}`,
|
||||
tag: `test_${Date.now()}`,
|
||||
requireInteraction: false,
|
||||
autoClose: 5000,
|
||||
};
|
||||
|
||||
const finalOptions = { ...defaultOptions, ...options };
|
||||
|
||||
console.log('[Notification Debugger] Sending test notification:', finalOptions);
|
||||
|
||||
const notification = browserNotificationService.sendNotification(finalOptions);
|
||||
|
||||
if (notification) {
|
||||
console.log('[Notification Debugger] ✅ Notification sent successfully');
|
||||
} else {
|
||||
console.error('[Notification Debugger] ❌ Failed to send notification');
|
||||
}
|
||||
|
||||
return notification;
|
||||
}
|
||||
|
||||
/**
|
||||
* 检查通知权限状态
|
||||
*/
|
||||
checkPermission() {
|
||||
const permission = browserNotificationService.getPermissionStatus();
|
||||
const isSupported = browserNotificationService.isSupported();
|
||||
|
||||
const status = {
|
||||
supported: isSupported,
|
||||
permission,
|
||||
canSend: isSupported && permission === 'granted',
|
||||
};
|
||||
|
||||
console.table(status);
|
||||
return status;
|
||||
}
|
||||
|
||||
/**
|
||||
* 请求通知权限
|
||||
*/
|
||||
async requestPermission() {
|
||||
console.log('[Notification Debugger] Requesting notification permission...');
|
||||
const result = await browserNotificationService.requestPermission();
|
||||
console.log(`[Notification Debugger] Permission result: ${result}`);
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* 打印事件统计
|
||||
*/
|
||||
printStats() {
|
||||
const stats = {
|
||||
total: this.eventLog.length,
|
||||
byType: {},
|
||||
};
|
||||
|
||||
this.eventLog.forEach((log) => {
|
||||
stats.byType[log.type] = (stats.byType[log.type] || 0) + 1;
|
||||
});
|
||||
|
||||
console.log('=== Notification Stats ===');
|
||||
console.table(stats.byType);
|
||||
console.log(`Total events: ${stats.total}`);
|
||||
|
||||
return stats;
|
||||
}
|
||||
|
||||
/**
|
||||
* 按类型过滤日志
|
||||
*/
|
||||
getLogsByType(eventType) {
|
||||
return this.eventLog.filter((log) => log.type === eventType);
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取最近的事件
|
||||
*/
|
||||
getRecentEvents(count = 10) {
|
||||
return this.eventLog.slice(0, count);
|
||||
}
|
||||
}
|
||||
|
||||
// 导出单例
|
||||
export const notificationDebugger = new NotificationDebugger();
|
||||
export default notificationDebugger;
|
||||
194
src/devtools/socketDebugger.js
Normal file
194
src/devtools/socketDebugger.js
Normal file
@@ -0,0 +1,194 @@
|
||||
// src/debug/socketDebugger.js
|
||||
/**
|
||||
* Socket 调试工具
|
||||
* 扩展现有的 window.__SOCKET_DEBUG__,添加更多生产环境调试能力
|
||||
*/
|
||||
|
||||
import { socket } from '@services/socket';
|
||||
|
||||
class SocketDebugger {
|
||||
constructor() {
|
||||
this.eventLog = [];
|
||||
this.maxLogSize = 100;
|
||||
}
|
||||
|
||||
/**
|
||||
* 初始化调试工具
|
||||
*/
|
||||
init() {
|
||||
// 监听所有 Socket 事件
|
||||
this._attachEventListeners();
|
||||
console.log('%c[Socket Debugger] Initialized', 'color: #FF9800; font-weight: bold;');
|
||||
}
|
||||
|
||||
/**
|
||||
* 附加事件监听器
|
||||
*/
|
||||
_attachEventListeners() {
|
||||
const events = [
|
||||
'connect',
|
||||
'disconnect',
|
||||
'connect_error',
|
||||
'reconnect',
|
||||
'reconnect_failed',
|
||||
'new_event',
|
||||
'system_notification',
|
||||
];
|
||||
|
||||
events.forEach((event) => {
|
||||
socket.on(event, (data) => {
|
||||
this.logEvent(event, data);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* 记录 Socket 事件
|
||||
*/
|
||||
logEvent(eventType, data) {
|
||||
const logEntry = {
|
||||
type: eventType,
|
||||
timestamp: new Date().toISOString(),
|
||||
data,
|
||||
};
|
||||
|
||||
this.eventLog.unshift(logEntry);
|
||||
if (this.eventLog.length > this.maxLogSize) {
|
||||
this.eventLog = this.eventLog.slice(0, this.maxLogSize);
|
||||
}
|
||||
|
||||
console.log(
|
||||
`%c[Socket Event] ${eventType}`,
|
||||
'color: #00BCD4; font-weight: bold;',
|
||||
data
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取所有事件日志
|
||||
*/
|
||||
getLogs() {
|
||||
return this.eventLog;
|
||||
}
|
||||
|
||||
/**
|
||||
* 清空日志
|
||||
*/
|
||||
clearLogs() {
|
||||
this.eventLog = [];
|
||||
console.log('[Socket Debugger] Logs cleared');
|
||||
}
|
||||
|
||||
/**
|
||||
* 导出日志
|
||||
*/
|
||||
exportLogs() {
|
||||
const blob = new Blob([JSON.stringify(this.eventLog, null, 2)], {
|
||||
type: 'application/json',
|
||||
});
|
||||
const url = URL.createObjectURL(blob);
|
||||
const a = document.createElement('a');
|
||||
a.href = url;
|
||||
a.download = `socket-logs-${Date.now()}.json`;
|
||||
a.click();
|
||||
URL.revokeObjectURL(url);
|
||||
console.log('[Socket Debugger] Logs exported');
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取连接状态
|
||||
*/
|
||||
getStatus() {
|
||||
const status = {
|
||||
connected: socket.connected || false,
|
||||
type: window.SOCKET_TYPE || 'UNKNOWN',
|
||||
reconnectAttempts: socket.getReconnectAttempts?.() || 0,
|
||||
maxReconnectAttempts: socket.getMaxReconnectAttempts?.() || Infinity,
|
||||
};
|
||||
|
||||
console.table(status);
|
||||
return status;
|
||||
}
|
||||
|
||||
/**
|
||||
* 手动触发连接
|
||||
*/
|
||||
connect() {
|
||||
console.log('[Socket Debugger] Manually connecting...');
|
||||
socket.connect();
|
||||
}
|
||||
|
||||
/**
|
||||
* 手动断开连接
|
||||
*/
|
||||
disconnect() {
|
||||
console.log('[Socket Debugger] Manually disconnecting...');
|
||||
socket.disconnect();
|
||||
}
|
||||
|
||||
/**
|
||||
* 手动重连
|
||||
*/
|
||||
reconnect() {
|
||||
console.log('[Socket Debugger] Manually reconnecting...');
|
||||
socket.disconnect();
|
||||
setTimeout(() => {
|
||||
socket.connect();
|
||||
}, 1000);
|
||||
}
|
||||
|
||||
/**
|
||||
* 发送测试事件
|
||||
*/
|
||||
emitTest(eventName, data = {}) {
|
||||
console.log(`[Socket Debugger] Emitting test event: ${eventName}`, data);
|
||||
socket.emit(eventName, data);
|
||||
}
|
||||
|
||||
/**
|
||||
* 打印事件统计
|
||||
*/
|
||||
printStats() {
|
||||
const stats = {
|
||||
total: this.eventLog.length,
|
||||
byType: {},
|
||||
};
|
||||
|
||||
this.eventLog.forEach((log) => {
|
||||
stats.byType[log.type] = (stats.byType[log.type] || 0) + 1;
|
||||
});
|
||||
|
||||
console.log('=== Socket Stats ===');
|
||||
console.table(stats.byType);
|
||||
console.log(`Total events: ${stats.total}`);
|
||||
|
||||
return stats;
|
||||
}
|
||||
|
||||
/**
|
||||
* 按类型过滤日志
|
||||
*/
|
||||
getLogsByType(eventType) {
|
||||
return this.eventLog.filter((log) => log.type === eventType);
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取最近的事件
|
||||
*/
|
||||
getRecentEvents(count = 10) {
|
||||
return this.eventLog.slice(0, count);
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取错误事件
|
||||
*/
|
||||
getErrors() {
|
||||
return this.eventLog.filter(
|
||||
(log) => log.type === 'connect_error' || log.type === 'reconnect_failed'
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
// 导出单例
|
||||
export const socketDebugger = new SocketDebugger();
|
||||
export default socketDebugger;
|
||||
Reference in New Issue
Block a user