/** * 懂车帝热搜API模块 * 负责数据获取、验证和格式化 */ class CarHotTopicsAPI { constructor() { this.baseURL = 'https://60s.api.shumengya.top/v2/dongchedi'; this.timeout = 10000; // 10秒超时 this.retryCount = 3; this.retryDelay = 1000; // 1秒重试延迟 } /** * 获取热搜数据 * @param {string} encoding - 编码格式(可选) * @returns {Promise} 热搜数据 */ async fetchHotTopics(encoding = '') { const url = encoding ? `${this.baseURL}?encoding=${encodeURIComponent(encoding)}` : this.baseURL; for (let attempt = 1; attempt <= this.retryCount; attempt++) { try { console.log(`[API] 尝试获取数据 (${attempt}/${this.retryCount}): ${url}`); const controller = new AbortController(); const timeoutId = setTimeout(() => controller.abort(), this.timeout); const response = await fetch(url, { method: 'GET', headers: { 'Accept': 'application/json', 'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36' }, signal: controller.signal }); clearTimeout(timeoutId); if (!response.ok) { throw new Error(`HTTP ${response.status}: ${response.statusText}`); } const data = await response.json(); console.log('[API] 数据获取成功:', data); // 验证数据格式 const validatedData = this.validateData(data); return this.formatData(validatedData); } catch (error) { console.error(`[API] 第${attempt}次请求失败:`, error.message); if (attempt === this.retryCount) { throw new Error(`获取数据失败: ${error.message}`); } // 等待后重试 await this.delay(this.retryDelay * attempt); } } } /** * 验证API返回数据格式 * @param {Object} data - API返回的原始数据 * @returns {Object} 验证后的数据 */ validateData(data) { if (!data || typeof data !== 'object') { throw new Error('数据格式错误:响应不是有效的JSON对象'); } if (data.code !== 200) { throw new Error(`API错误:${data.message || '未知错误'}`); } if (!Array.isArray(data.data)) { throw new Error('数据格式错误:data字段不是数组'); } // 验证每个热搜项目的必需字段 data.data.forEach((item, index) => { const requiredFields = ['rank', 'title', 'score', 'score_desc']; const missingFields = requiredFields.filter(field => !(field in item)); if (missingFields.length > 0) { console.warn(`[API] 第${index + 1}项数据缺少字段:`, missingFields); } }); return data; } /** * 格式化数据 * @param {Object} data - 验证后的数据 * @returns {Object} 格式化后的数据 */ formatData(data) { const formattedTopics = data.data.map(item => ({ rank: parseInt(item.rank) || 0, title: String(item.title || '').trim(), score: parseInt(item.score) || 0, scoreDesc: String(item.score_desc || '').trim(), // 添加一些计算字段 isTop3: parseInt(item.rank) <= 3, formattedScore: this.formatScore(item.score), searchUrl: this.generateSearchUrl(item.title) })); return { code: data.code, message: data.message, data: formattedTopics, updateTime: new Date().toLocaleString('zh-CN'), total: formattedTopics.length }; } /** * 格式化分数显示 * @param {number} score - 原始分数 * @returns {string} 格式化后的分数 */ formatScore(score) { if (!score || isNaN(score)) return '0'; if (score >= 10000) { return (score / 10000).toFixed(1) + 'w'; } return score.toLocaleString(); } /** * 生成搜索URL * @param {string} title - 热搜标题 * @returns {string} 搜索URL */ generateSearchUrl(title) { const encodedTitle = encodeURIComponent(title); return `https://www.dongchedi.com/search?query=${encodedTitle}`; } /** * 延迟函数 * @param {number} ms - 延迟毫秒数 * @returns {Promise} Promise对象 */ delay(ms) { return new Promise(resolve => setTimeout(resolve, ms)); } /** * 获取API状态 * @returns {Promise} API状态信息 */ async getAPIStatus() { try { const startTime = Date.now(); await this.fetchHotTopics(); const responseTime = Date.now() - startTime; return { status: 'online', responseTime: responseTime, message: 'API服务正常' }; } catch (error) { return { status: 'offline', responseTime: null, message: error.message }; } } } // 导出API实例 window.CarHotTopicsAPI = CarHotTopicsAPI;