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:
Horux Dev
2026-08-03 04:07:58 +00:00
parent 3f31e25ae7
commit 24d35333df
2 changed files with 120 additions and 0 deletions

View 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}`);
}
}

View 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();