新增调试工具目录 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>
254 lines
7.6 KiB
JavaScript
254 lines
7.6 KiB
JavaScript
// 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;
|