diff --git a/.env.example b/.env.example index a571c13..5db0d79 100644 --- a/.env.example +++ b/.env.example @@ -33,6 +33,8 @@ TIME_OUT=300 # 代理服务器配置 (支持 http 和 socks5) # 示例: PROXIES=["http://user:pass@host:port", "socks5://host:port"] PROXIES=[] +# 对同一个API_KEY使用代理列表中固定的IP策略 +PROXIES_USE_CONSISTENCY_HASH_BY_API_KEY=true #########################image_generate 相关配置########################### PAID_KEY=AIzaSyxxxxxxxxxxxxxxxxxxx CREATE_IMAGE_MODEL=imagen-3.0-generate-002 diff --git a/app/config/config.py b/app/config/config.py index 82a938b..e6709e0 100644 --- a/app/config/config.py +++ b/app/config/config.py @@ -60,6 +60,7 @@ class Settings(BaseSettings): TIME_OUT: int = DEFAULT_TIMEOUT MAX_RETRIES: int = MAX_RETRIES PROXIES: List[str] = [] + PROXIES_USE_CONSISTENCY_HASH_BY_API_KEY: bool = True # 是否使用一致性哈希来选择代理 VERTEX_API_KEYS: List[str] = [] VERTEX_EXPRESS_BASE_URL: str = "https://aiplatform.googleapis.com/v1beta1/publishers/google" diff --git a/app/service/client/api_client.py b/app/service/client/api_client.py index 3e7205e..33ce488 100644 --- a/app/service/client/api_client.py +++ b/app/service/client/api_client.py @@ -46,7 +46,10 @@ class GeminiApiClient(ApiClient): proxy_to_use = None if settings.PROXIES: - proxy_to_use = random.choice(settings.PROXIES) + if settings.PROXIES_USE_CONSISTENCY_HASH_BY_API_KEY: + proxy_to_use = settings.PROXIES[hash(api_key) % len(settings.PROXIES)] + else: + proxy_to_use = random.choice(settings.PROXIES) logger.info(f"Using proxy for getting models: {proxy_to_use}") async with httpx.AsyncClient(timeout=timeout, proxy=proxy_to_use) as client: @@ -69,8 +72,11 @@ class GeminiApiClient(ApiClient): proxy_to_use = None if settings.PROXIES: - proxy_to_use = random.choice(settings.PROXIES) - logger.info(f"Using proxy: {proxy_to_use}") + if settings.PROXIES_USE_CONSISTENCY_HASH_BY_API_KEY: + proxy_to_use = settings.PROXIES[hash(api_key) % len(settings.PROXIES)] + else: + proxy_to_use = random.choice(settings.PROXIES) + logger.info(f"Using proxy for getting models: {proxy_to_use}") async with httpx.AsyncClient(timeout=timeout, proxy=proxy_to_use) as client: url = f"{self.base_url}/models/{model}:generateContent?key={api_key}" @@ -86,8 +92,11 @@ class GeminiApiClient(ApiClient): proxy_to_use = None if settings.PROXIES: - proxy_to_use = random.choice(settings.PROXIES) - logger.info(f"Using proxy: {proxy_to_use}") + if settings.PROXIES_USE_CONSISTENCY_HASH_BY_API_KEY: + proxy_to_use = settings.PROXIES[hash(api_key) % len(settings.PROXIES)] + else: + proxy_to_use = random.choice(settings.PROXIES) + logger.info(f"Using proxy for getting models: {proxy_to_use}") async with httpx.AsyncClient(timeout=timeout, proxy=proxy_to_use) as client: url = f"{self.base_url}/models/{model}:streamGenerateContent?alt=sse&key={api_key}" @@ -109,7 +118,16 @@ class OpenaiApiClient(ApiClient): async def get_models(self, api_key: str) -> Dict[str, Any]: timeout = httpx.Timeout(self.timeout, read=self.timeout) - async with httpx.AsyncClient(timeout=timeout) as client: + + proxy_to_use = None + if settings.PROXIES: + if settings.PROXIES_USE_CONSISTENCY_HASH_BY_API_KEY: + proxy_to_use = settings.PROXIES[hash(api_key) % len(settings.PROXIES)] + else: + proxy_to_use = random.choice(settings.PROXIES) + logger.info(f"Using proxy for getting models: {proxy_to_use}") + + async with httpx.AsyncClient(timeout=timeout, proxy=proxy_to_use) as client: url = f"{self.base_url}/openai/models" headers = {"Authorization": f"Bearer {api_key}"} response = await client.get(url, headers=headers) @@ -120,11 +138,14 @@ class OpenaiApiClient(ApiClient): async def generate_content(self, payload: Dict[str, Any], api_key: str) -> Dict[str, Any]: timeout = httpx.Timeout(self.timeout, read=self.timeout) - + logger.info(f"settings.PROXIES_USE_CONSISTENCY_HASH_BY_API_KEY: {settings.PROXIES_USE_CONSISTENCY_HASH_BY_API_KEY}") proxy_to_use = None if settings.PROXIES: - proxy_to_use = random.choice(settings.PROXIES) - logger.info(f"Using proxy: {proxy_to_use}") + if settings.PROXIES_USE_CONSISTENCY_HASH_BY_API_KEY: + proxy_to_use = settings.PROXIES[hash(api_key) % len(settings.PROXIES)] + else: + proxy_to_use = random.choice(settings.PROXIES) + logger.info(f"Using proxy for getting models: {proxy_to_use}") async with httpx.AsyncClient(timeout=timeout, proxy=proxy_to_use) as client: url = f"{self.base_url}/openai/chat/completions" @@ -137,11 +158,13 @@ class OpenaiApiClient(ApiClient): async def stream_generate_content(self, payload: Dict[str, Any], api_key: str) -> AsyncGenerator[str, None]: timeout = httpx.Timeout(self.timeout, read=self.timeout) - proxy_to_use = None if settings.PROXIES: - proxy_to_use = random.choice(settings.PROXIES) - logger.info(f"Using proxy: {proxy_to_use}") + if settings.PROXIES_USE_CONSISTENCY_HASH_BY_API_KEY: + proxy_to_use = settings.PROXIES[hash(api_key) % len(settings.PROXIES)] + else: + proxy_to_use = random.choice(settings.PROXIES) + logger.info(f"Using proxy for getting models: {proxy_to_use}") async with httpx.AsyncClient(timeout=timeout, proxy=proxy_to_use) as client: url = f"{self.base_url}/openai/chat/completions" @@ -159,8 +182,11 @@ class OpenaiApiClient(ApiClient): proxy_to_use = None if settings.PROXIES: - proxy_to_use = random.choice(settings.PROXIES) - logger.info(f"Using proxy: {proxy_to_use}") + if settings.PROXIES_USE_CONSISTENCY_HASH_BY_API_KEY: + proxy_to_use = settings.PROXIES[hash(api_key) % len(settings.PROXIES)] + else: + proxy_to_use = random.choice(settings.PROXIES) + logger.info(f"Using proxy for getting models: {proxy_to_use}") async with httpx.AsyncClient(timeout=timeout, proxy=proxy_to_use) as client: url = f"{self.base_url}/openai/embeddings" @@ -177,11 +203,14 @@ class OpenaiApiClient(ApiClient): async def generate_images(self, payload: Dict[str, Any], api_key: str) -> Dict[str, Any]: timeout = httpx.Timeout(self.timeout, read=self.timeout) - + proxy_to_use = None if settings.PROXIES: - proxy_to_use = random.choice(settings.PROXIES) - logger.info(f"Using proxy: {proxy_to_use}") + if settings.PROXIES_USE_CONSISTENCY_HASH_BY_API_KEY: + proxy_to_use = settings.PROXIES[hash(api_key) % len(settings.PROXIES)] + else: + proxy_to_use = random.choice(settings.PROXIES) + logger.info(f"Using proxy for getting models: {proxy_to_use}") async with httpx.AsyncClient(timeout=timeout, proxy=proxy_to_use) as client: url = f"{self.base_url}/openai/images/generations" diff --git a/app/templates/config_editor.html b/app/templates/config_editor.html index 8c48b52..524ad31 100644 --- a/app/templates/config_editor.html +++ b/app/templates/config_editor.html @@ -1021,6 +1021,33 @@ endblock %} {% block head_extra_styles %} socks5://host:port。点击按钮可批量添加或删除。 + +
+
+ +
+ + +
+
+ 开启后,对于每一个API_KEY将根据算法从代理列表中选取同一个代理IP,防止一个API_KEY同时被多个IP访问,也同时防止了一个IP访问了过多的API_KEY。 +