feat(devtools): 添加生产环境调试工具系统
新增调试工具目录 src/devtools/,提供完整的生产环境调试能力: - apiDebugger: 拦截所有 API 请求/响应,记录日志 - notificationDebugger: 测试浏览器通知,检查权限 - socketDebugger: 监听所有 Socket 事件,诊断连接状态 - 全局 API: window.__DEBUG__ 提供便捷的控制台调试命令 功能特性: - 环境变量控制:REACT_APP_ENABLE_DEBUG=true 开启 - 动态导入:不影响生产环境性能 - 完整诊断:diagnose()、performance()、exportAll() - 易于移除:所有代码集中在 src/devtools/ 目录 Webpack 配置: - 添加 'debug' alias 强制解析到 node_modules/debug - 添加 @devtools alias 简化导入路径 - 避免与 npm debug 包的命名冲突 🔧 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
@@ -16,6 +16,15 @@ NODE_ENV=production
|
||||
# Mock 配置(生产环境禁用 Mock)
|
||||
REACT_APP_ENABLE_MOCK=false
|
||||
|
||||
# 🔧 调试模式(生产环境临时调试用)
|
||||
# 开启后会在全局暴露 window.__DEBUG__ 调试 API
|
||||
# ⚠️ 警告: 调试模式会记录所有 API 请求/响应,调试完成后请立即关闭!
|
||||
# 使用方法:
|
||||
# 1. 设置为 true 并重新构建
|
||||
# 2. 在浏览器控制台使用 window.__DEBUG__.help() 查看命令
|
||||
# 3. 调试完成后设置为 false 并重新构建
|
||||
REACT_APP_ENABLE_DEBUG=false
|
||||
|
||||
# 后端 API 地址(生产环境)
|
||||
REACT_APP_API_URL=http://49.232.185.254:5001
|
||||
|
||||
|
||||
@@ -110,6 +110,9 @@ module.exports = {
|
||||
...webpackConfig.resolve,
|
||||
alias: {
|
||||
...webpackConfig.resolve.alias,
|
||||
// 强制 'debug' 模块解析到 node_modules(避免与 src/devtools/ 冲突)
|
||||
'debug': path.resolve(__dirname, 'node_modules/debug'),
|
||||
|
||||
// 根目录别名
|
||||
'@': path.resolve(__dirname, 'src'),
|
||||
|
||||
@@ -119,6 +122,7 @@ module.exports = {
|
||||
'@constants': path.resolve(__dirname, 'src/constants'),
|
||||
'@contexts': path.resolve(__dirname, 'src/contexts'),
|
||||
'@data': path.resolve(__dirname, 'src/data'),
|
||||
'@devtools': path.resolve(__dirname, 'src/devtools'),
|
||||
'@hooks': path.resolve(__dirname, 'src/hooks'),
|
||||
'@layouts': path.resolve(__dirname, 'src/layouts'),
|
||||
'@lib': path.resolve(__dirname, 'src/lib'),
|
||||
@@ -263,6 +267,34 @@ module.exports = {
|
||||
logLevel: 'debug',
|
||||
pathRewrite: { '^/concept-api': '' },
|
||||
},
|
||||
'/bytedesk-api': {
|
||||
target: 'http://43.143.189.195',
|
||||
changeOrigin: true,
|
||||
secure: false,
|
||||
logLevel: 'debug',
|
||||
pathRewrite: { '^/bytedesk-api': '' },
|
||||
},
|
||||
'/chat': {
|
||||
target: 'http://43.143.189.195',
|
||||
changeOrigin: true,
|
||||
secure: false,
|
||||
logLevel: 'debug',
|
||||
// 不需要pathRewrite,保留/chat路径
|
||||
},
|
||||
'/config': {
|
||||
target: 'http://43.143.189.195',
|
||||
changeOrigin: true,
|
||||
secure: false,
|
||||
logLevel: 'debug',
|
||||
// 不需要pathRewrite,保留/config路径
|
||||
},
|
||||
'/visitor': {
|
||||
target: 'http://43.143.189.195',
|
||||
changeOrigin: true,
|
||||
secure: false,
|
||||
logLevel: 'debug',
|
||||
// 不需要pathRewrite,保留/visitor路径
|
||||
},
|
||||
},
|
||||
}),
|
||||
},
|
||||
|
||||
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;
|
||||
13
src/index.js
13
src/index.js
@@ -13,6 +13,19 @@ import App from './App';
|
||||
import { browserNotificationService } from './services/browserNotificationService';
|
||||
window.browserNotificationService = browserNotificationService;
|
||||
|
||||
// 🔧 条件导入调试工具(生产环境可选)
|
||||
// 开启方式: 在 .env 文件中设置 REACT_APP_ENABLE_DEBUG=true
|
||||
// 移除方式: 删除此段代码 + 删除 src/devtools/ 目录
|
||||
if (process.env.REACT_APP_ENABLE_DEBUG === 'true') {
|
||||
import('./devtools').then(({ debugToolkit }) => {
|
||||
debugToolkit.init();
|
||||
console.log(
|
||||
'%c✅ 调试工具已加载!使用 window.__DEBUG__.help() 查看命令',
|
||||
'color: #4CAF50; font-weight: bold; font-size: 14px;'
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
// 注册 Service Worker(用于支持浏览器通知)
|
||||
function registerServiceWorker() {
|
||||
// ⚠️ Mock 模式下跳过 Service Worker 注册(避免与 MSW 冲突)
|
||||
|
||||
Reference in New Issue
Block a user