feat(sat): agrega ProxyManager para rotación de proxies
- Nuevo servicio apps/api/src/services/sat/proxy.service.ts. - Soporta estrategias round-robin y random. - Parsea SAT_PROXY_LIST y crea agentes HttpsProxyAgent. - Script scripts/test-proxy-rotation.ts para validar configuración.
This commit is contained in:
20
apps/api/scripts/test-proxy-rotation.ts
Normal file
20
apps/api/scripts/test-proxy-rotation.ts
Normal file
@@ -0,0 +1,20 @@
|
|||||||
|
import 'dotenv/config';
|
||||||
|
import { ProxyManager } from '../src/services/sat/proxy.service.js';
|
||||||
|
|
||||||
|
const manager = new ProxyManager(
|
||||||
|
process.env.SAT_PROXY_LIST || '',
|
||||||
|
(process.env.SAT_PROXY_STRATEGY as any) || 'round-robin',
|
||||||
|
);
|
||||||
|
|
||||||
|
console.log(`Total de proxies: ${manager.getProxyCount()}`);
|
||||||
|
console.log(`Estrategia: ${process.env.SAT_PROXY_STRATEGY || 'round-robin'}`);
|
||||||
|
console.log('Próximos 10 proxies seleccionados:');
|
||||||
|
|
||||||
|
for (let i = 0; i < 10; i++) {
|
||||||
|
const proxy = manager.getNextProxy();
|
||||||
|
if (!proxy) {
|
||||||
|
console.log(` ${i + 1}. (sin proxy configurado)`);
|
||||||
|
} else {
|
||||||
|
console.log(` ${i + 1}. ${proxy.host}:${proxy.port}`);
|
||||||
|
}
|
||||||
|
}
|
||||||
100
apps/api/src/services/sat/proxy.service.ts
Normal file
100
apps/api/src/services/sat/proxy.service.ts
Normal file
@@ -0,0 +1,100 @@
|
|||||||
|
import { HttpsProxyAgent } from 'https-proxy-agent';
|
||||||
|
|
||||||
|
export interface ProxyConfig {
|
||||||
|
url: string;
|
||||||
|
host: string;
|
||||||
|
port: number;
|
||||||
|
username?: string;
|
||||||
|
password?: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export type ProxyStrategy = 'round-robin' | 'random';
|
||||||
|
|
||||||
|
function parseProxyList(raw: string): ProxyConfig[] {
|
||||||
|
if (!raw.trim()) return [];
|
||||||
|
|
||||||
|
const configs: ProxyConfig[] = [];
|
||||||
|
const items = raw.split(',').map(s => s.trim()).filter(Boolean);
|
||||||
|
|
||||||
|
for (const item of items) {
|
||||||
|
try {
|
||||||
|
const url = new URL(item);
|
||||||
|
if (url.protocol !== 'http:' && url.protocol !== 'https:') {
|
||||||
|
console.warn(`[ProxyManager] Protocolo no soportado, se omite: ${item}`);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
configs.push({
|
||||||
|
url: item,
|
||||||
|
host: url.hostname,
|
||||||
|
port: Number(url.port) || (url.protocol === 'https:' ? 443 : 80),
|
||||||
|
username: url.username || undefined,
|
||||||
|
password: url.password || undefined,
|
||||||
|
});
|
||||||
|
} catch (err) {
|
||||||
|
console.warn(`[ProxyManager] URL de proxy inválida, se omite: ${item}`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return configs;
|
||||||
|
}
|
||||||
|
|
||||||
|
export class ProxyManager {
|
||||||
|
private proxies: ProxyConfig[];
|
||||||
|
private strategy: ProxyStrategy;
|
||||||
|
private currentIndex = 0;
|
||||||
|
|
||||||
|
constructor(
|
||||||
|
proxyList = process.env.SAT_PROXY_LIST || '',
|
||||||
|
strategy: ProxyStrategy = (process.env.SAT_PROXY_STRATEGY as ProxyStrategy) || 'round-robin',
|
||||||
|
) {
|
||||||
|
this.proxies = parseProxyList(proxyList);
|
||||||
|
this.strategy = ['round-robin', 'random'].includes(strategy) ? strategy : 'round-robin';
|
||||||
|
|
||||||
|
if (this.proxies.length > 0) {
|
||||||
|
console.log(`[ProxyManager] ${this.proxies.length} proxy(s) configurados (estrategia: ${this.strategy})`);
|
||||||
|
} else {
|
||||||
|
console.log('[ProxyManager] No hay proxies configurados; se usará la IP directa del servidor');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
hasProxies(): boolean {
|
||||||
|
return this.proxies.length > 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
getProxyCount(): number {
|
||||||
|
return this.proxies.length;
|
||||||
|
}
|
||||||
|
|
||||||
|
getNextProxy(): ProxyConfig | null {
|
||||||
|
if (this.proxies.length === 0) return null;
|
||||||
|
|
||||||
|
if (this.strategy === 'random') {
|
||||||
|
return this.proxies[Math.floor(Math.random() * this.proxies.length)];
|
||||||
|
}
|
||||||
|
|
||||||
|
const proxy = this.proxies[this.currentIndex];
|
||||||
|
this.currentIndex = (this.currentIndex + 1) % this.proxies.length;
|
||||||
|
return proxy;
|
||||||
|
}
|
||||||
|
|
||||||
|
createAgent(proxy: ProxyConfig): HttpsProxyAgent<string> {
|
||||||
|
return new HttpsProxyAgent(proxy.url);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Crea un agente con el siguiente proxy del pool.
|
||||||
|
* Útil cuando se quiere una nueva conexión por solicitud.
|
||||||
|
*/
|
||||||
|
createNextAgent(): HttpsProxyAgent<string> | null {
|
||||||
|
const proxy = this.getNextProxy();
|
||||||
|
if (!proxy) return null;
|
||||||
|
console.log(`[ProxyManager] Usando proxy: ${proxy.host}:${proxy.port}`);
|
||||||
|
return this.createAgent(proxy);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Instancia global del proxy manager.
|
||||||
|
* Lee SAT_PROXY_LIST y SAT_PROXY_STRATEGY del entorno.
|
||||||
|
*/
|
||||||
|
export const proxyManager = new ProxyManager();
|
||||||
Reference in New Issue
Block a user