// ============================================================ // NetRequest — Electron net.request 的 axios 风格封装 // // 用法: // import { request, get, post } from './net-request'; // const { data } = await get('/api/v1/update/check', { // baseURL: 'https://www.heixiu.com', // params: { platform: 'win32', version: '1.0.0' }, // }); // // 设计: // - 底层使用 Electron net.request(不引入额外依赖) // - Promise 封装,支持 async/await // - JSON 自动解析、查询参数自动拼接 // - 流式下载场景仍直接用 net.request(本模块不封装 stream) // ============================================================ import { net } from 'electron'; // ---------- 类型 ---------- export interface RequestConfig { /** 请求方法(默认 GET) */ method?: 'GET' | 'POST' | 'PUT' | 'DELETE' | 'PATCH'; /** 基础 URL(可选,方便切换环境) */ baseURL?: string; /** 查询参数(自动拼接 ?key=value&...) */ params?: Record; /** 请求头 */ headers?: Record; /** 请求体(对象自动 JSON.stringify,字符串原样发送) */ data?: unknown; /** 响应类型(默认 json,设置 text 跳过解析) */ responseType?: 'json' | 'text'; /** 超时时间(毫秒,默认 30s) */ timeout?: number; } export interface RequestResponse { /** HTTP 状态码 */ status: number; /** 状态文本(如 "OK") */ statusText: string; /** 响应头 */ headers: Record; /** 响应体 */ data: T; } /** 网络请求错误 */ export class RequestError extends Error { status?: number; constructor(message: string, status?: number) { super(message); this.name = 'NetRequestError'; this.status = status; } } // ---------- 工具 ---------- const DEFAULT_TIMEOUT = 30_000; /** 拼接 URL(baseURL + path + query) */ function buildUrl(url: string, config: RequestConfig): string { let fullUrl = url; if (config.baseURL) { // url 以 / 开头时为绝对路径,否则拼接 const base = config.baseURL.replace(/\/+$/, ''); const path = url.startsWith('/') ? url : `/${url}`; fullUrl = `${base}${path}`; } if (config.params) { const search = new URLSearchParams(); for (const [key, value] of Object.entries(config.params)) { if (value !== undefined) { search.append(key, String(value)); } } const qs = search.toString(); if (qs) { fullUrl += (fullUrl.includes('?') ? '&' : '?') + qs; } } return fullUrl; } /** 序列化请求体 */ function serializeBody(data: unknown): { body: string; contentType: string } { if (typeof data === 'string') { return { body: data, contentType: 'text/plain' }; } return { body: JSON.stringify(data), contentType: 'application/json' }; } // ---------- 核心 ---------- /** * 发起请求(axios 风格 API) * * 流式下载场景请直接使用 Electron net.request(本模块不封装) */ export async function request( url: string, config: RequestConfig = {}, ): Promise> { const fullUrl = buildUrl(url, config); const method = (config.method || 'GET').toUpperCase(); const timeout = config.timeout ?? DEFAULT_TIMEOUT; return new Promise((resolve, reject) => { const req = net.request({ method, url: fullUrl, }); // 超时处理 let timedOut = false; const timer = setTimeout(() => { timedOut = true; req.abort(); reject(new RequestError(`请求超时 (${timeout}ms)`)); }, timeout); // 请求头 if (config.headers) { for (const [key, value] of Object.entries(config.headers)) { req.setHeader(key, value); } } // 请求体 if (config.data !== undefined) { const { body, contentType } = serializeBody(config.data); req.setHeader('Content-Type', contentType); req.write(body); } req.on('response', (response) => { clearTimeout(timer); const status = response.statusCode; const statusText = response.statusMessage ?? ''; // 读取响应头 const headers: Record = {}; // Electron IncomingMessage.headers 是 Record const rawHeaders = response.headers; if (rawHeaders) { for (const [key, value] of Object.entries(rawHeaders)) { headers[key] = value.length === 1 ? value[0] : value; } } // 读取响应体 let body = ''; response.on('data', (chunk: Buffer) => (body += chunk.toString())); response.on('end', () => { try { const asJson = config.responseType !== 'text'; const data = asJson ? JSON.parse(body) : body; resolve({ status, statusText, headers, data: data as T }); } catch { // JSON 解析失败 → 返回原始文本 resolve({ status, statusText, headers, data: body as T }); } }); response.on('error', (err) => { clearTimeout(timer); reject(new RequestError(`响应读取失败: ${err.message}`)); }); }); req.on('error', (err) => { clearTimeout(timer); if (timedOut) return; reject(new RequestError(`网络请求失败: ${err.message}`)); }); req.end(); }); } // ---------- 默认导出:类 axios 命名空间 ---------- export const netRequest = { request, get(url: string, config?: Omit) { return request(url, { ...config, method: 'GET' }); }, post(url: string, data?: unknown, config?: Omit) { return request(url, { ...config, method: 'POST', data }); }, put(url: string, data?: unknown, config?: Omit) { return request(url, { ...config, method: 'PUT', data }); }, del(url: string, config?: Omit) { return request(url, { ...config, method: 'DELETE' }); }, };