feat: 为 Provider 认证添加代理配置支持

- 前端新增可折叠的代理配置字段组(URL/用户名/密码)
- 更新 anyrouter/cubence/new-api/yescode 模板支持代理
- 后端 provider_ops 服务验证和数据获取时支持代理
- 移除 Dockerfile 中的 ca-certificates(回退之前改动)
This commit is contained in:
fawney19
2026-01-19 18:30:52 +08:00
parent b6a9969aee
commit ffdc9a9585
13 changed files with 336 additions and 101 deletions

View File

@@ -2,43 +2,32 @@
# 构建命令: docker build -f Dockerfile.app -t aether-app:latest . # 构建命令: docker build -f Dockerfile.app -t aether-app:latest .
# 用于 GitHub Actions CI官方源 # 用于 GitHub Actions CI官方源
FROM aether-base:latest AS builder FROM aether-base:latest AS builder
WORKDIR /app WORKDIR /app
# 复制前端源码并构建 # 复制前端源码并构建
COPY frontend/ ./frontend/ COPY frontend/ ./frontend/
RUN cd frontend && npm run build RUN cd frontend && npm run build
# ==================== 运行时镜像 ==================== # ==================== 运行时镜像 ====================
FROM python:3.12-slim FROM python:3.12-slim
WORKDIR /app WORKDIR /app
# 运行时依赖(无 gcc/nodejs/npm # 运行时依赖(无 gcc/nodejs/npm
RUN apt-get update && apt-get install -y \ RUN apt-get update && apt-get install -y \
nginx \ nginx \
supervisor \ supervisor \
libpq5 \ libpq5 \
curl \ curl \
ca-certificates \
&& rm -rf /var/lib/apt/lists/* && rm -rf /var/lib/apt/lists/*
# 从 base 镜像复制 Python 包 # 从 base 镜像复制 Python 包
COPY --from=builder /usr/local/lib/python3.12/site-packages /usr/local/lib/python3.12/site-packages COPY --from=builder /usr/local/lib/python3.12/site-packages /usr/local/lib/python3.12/site-packages
# 只复制需要的 Python 可执行文件 # 只复制需要的 Python 可执行文件
COPY --from=builder /usr/local/bin/gunicorn /usr/local/bin/ COPY --from=builder /usr/local/bin/gunicorn /usr/local/bin/
COPY --from=builder /usr/local/bin/uvicorn /usr/local/bin/ COPY --from=builder /usr/local/bin/uvicorn /usr/local/bin/
COPY --from=builder /usr/local/bin/alembic /usr/local/bin/ COPY --from=builder /usr/local/bin/alembic /usr/local/bin/
# 从 builder 阶段复制前端构建产物 # 从 builder 阶段复制前端构建产物
COPY --from=builder /app/frontend/dist /usr/share/nginx/html COPY --from=builder /app/frontend/dist /usr/share/nginx/html
# 复制后端代码 # 复制后端代码
COPY src/ ./src/ COPY src/ ./src/
COPY alembic.ini ./ COPY alembic.ini ./
COPY alembic/ ./alembic/ COPY alembic/ ./alembic/
# Nginx 配置模板 # Nginx 配置模板
# 智能处理 IP有外层代理头就透传没有就用直连 IP # 智能处理 IP有外层代理头就透传没有就用直连 IP
RUN printf '%s\n' \ RUN printf '%s\n' \
@@ -119,7 +108,6 @@ RUN printf '%s\n' \
' proxy_read_timeout 600s;' \ ' proxy_read_timeout 600s;' \
' }' \ ' }' \
'}' > /etc/nginx/sites-available/default.template '}' > /etc/nginx/sites-available/default.template
# Supervisor 配置 # Supervisor 配置
RUN printf '%s\n' \ RUN printf '%s\n' \
'[supervisord]' \ '[supervisord]' \
@@ -144,14 +132,11 @@ RUN printf '%s\n' \
'stderr_logfile=/dev/stderr' \ 'stderr_logfile=/dev/stderr' \
'stderr_logfile_maxbytes=0' \ 'stderr_logfile_maxbytes=0' \
'environment=PYTHONUNBUFFERED=1,PYTHONIOENCODING=utf-8,LANG=C.UTF-8,LC_ALL=C.UTF-8,DOCKER_CONTAINER=true' > /etc/supervisor/conf.d/supervisord.conf 'environment=PYTHONUNBUFFERED=1,PYTHONIOENCODING=utf-8,LANG=C.UTF-8,LC_ALL=C.UTF-8,DOCKER_CONTAINER=true' > /etc/supervisor/conf.d/supervisord.conf
# 创建目录 # 创建目录
RUN mkdir -p /var/log/supervisor /app/logs /app/data RUN mkdir -p /var/log/supervisor /app/logs /app/data
# 入口脚本(启动前执行迁移) # 入口脚本(启动前执行迁移)
COPY entrypoint.sh /entrypoint.sh COPY entrypoint.sh /entrypoint.sh
RUN chmod +x /entrypoint.sh RUN chmod +x /entrypoint.sh
# 环境变量 # 环境变量
ENV PYTHONUNBUFFERED=1 \ ENV PYTHONUNBUFFERED=1 \
PYTHONDONTWRITEBYTECODE=1 \ PYTHONDONTWRITEBYTECODE=1 \
@@ -160,11 +145,8 @@ ENV PYTHONUNBUFFERED=1 \
LC_ALL=C.UTF-8 \ LC_ALL=C.UTF-8 \
PORT=8084 \ PORT=8084 \
GUNICORN_WORKERS=4 GUNICORN_WORKERS=4
EXPOSE 80 EXPOSE 80
HEALTHCHECK --interval=30s --timeout=10s --start-period=5s --retries=3 \ HEALTHCHECK --interval=30s --timeout=10s --start-period=5s --retries=3 \
CMD curl -f http://localhost/health || exit 1 CMD curl -f http://localhost/health || exit 1
ENTRYPOINT ["/entrypoint.sh"] ENTRYPOINT ["/entrypoint.sh"]
CMD ["/usr/bin/supervisord", "-c", "/etc/supervisor/conf.d/supervisord.conf"] CMD ["/usr/bin/supervisord", "-c", "/etc/supervisor/conf.d/supervisord.conf"]

View File

@@ -21,7 +21,6 @@ RUN sed -i 's/deb.debian.org/mirrors.tuna.tsinghua.edu.cn/g' /etc/apt/sources.li
supervisor \ supervisor \
libpq5 \ libpq5 \
curl \ curl \
ca-certificates \
&& rm -rf /var/lib/apt/lists/* && rm -rf /var/lib/apt/lists/*
# 从 base 镜像复制 Python 包 # 从 base 镜像复制 Python 包

View File

@@ -12,7 +12,6 @@ RUN apt-get update && apt-get install -y \
gcc \ gcc \
nodejs \ nodejs \
npm \ npm \
ca-certificates \
&& rm -rf /var/lib/apt/lists/* && rm -rf /var/lib/apt/lists/*
# Python 依赖 # Python 依赖

View File

@@ -12,7 +12,6 @@ RUN sed -i 's/deb.debian.org/mirrors.tuna.tsinghua.edu.cn/g' /etc/apt/sources.li
gcc \ gcc \
nodejs \ nodejs \
npm \ npm \
ca-certificates \
&& rm -rf /var/lib/apt/lists/* && rm -rf /var/lib/apt/lists/*
# pip 镜像源 # pip 镜像源

View File

@@ -9,6 +9,7 @@
import type { AuthTemplate, AuthTemplateFieldGroup } from './types' import type { AuthTemplate, AuthTemplateFieldGroup } from './types'
import type { SaveConfigRequest } from '@/api/providerOps' import type { SaveConfigRequest } from '@/api/providerOps'
import { PROXY_FIELD_GROUP, buildProxyUrl, parseProxyUrl } from './types'
export const anyrouterTemplate: AuthTemplate = { export const anyrouterTemplate: AuthTemplate = {
id: 'anyrouter', id: 'anyrouter',
@@ -38,6 +39,7 @@ export const anyrouterTemplate: AuthTemplate = {
}, },
], ],
}, },
PROXY_FIELD_GROUP,
] ]
}, },
@@ -49,7 +51,9 @@ export const anyrouterTemplate: AuthTemplate = {
base_url: baseUrl, base_url: baseUrl,
connector: { connector: {
auth_type: 'cookie', auth_type: 'cookie',
config: {}, config: {
proxy: buildProxyUrl(formData),
},
credentials: { credentials: {
session_cookie: formData.session_cookie, session_cookie: formData.session_cookie,
}, },
@@ -60,9 +64,11 @@ export const anyrouterTemplate: AuthTemplate = {
}, },
parseConfig(config: any): Record<string, any> { parseConfig(config: any): Record<string, any> {
const proxyData = parseProxyUrl(config?.connector?.config?.proxy)
return { return {
base_url: config?.base_url || '', base_url: config?.base_url || '',
session_cookie: config?.connector?.credentials?.session_cookie || '', session_cookie: config?.connector?.credentials?.session_cookie || '',
...proxyData,
} }
}, },

View File

@@ -9,6 +9,7 @@
import type { AuthTemplate, AuthTemplateFieldGroup, BalanceExtraItem } from './types' import type { AuthTemplate, AuthTemplateFieldGroup, BalanceExtraItem } from './types'
import type { SaveConfigRequest } from '@/api/providerOps' import type { SaveConfigRequest } from '@/api/providerOps'
import { PROXY_FIELD_GROUP, buildProxyUrl, parseProxyUrl } from './types'
/** /**
* 格式化窗口限额显示(百分比格式) * 格式化窗口限额显示(百分比格式)
@@ -78,6 +79,7 @@ export const cubenceTemplate: AuthTemplate = {
}, },
], ],
}, },
PROXY_FIELD_GROUP,
] ]
}, },
@@ -89,7 +91,9 @@ export const cubenceTemplate: AuthTemplate = {
base_url: baseUrl, base_url: baseUrl,
connector: { connector: {
auth_type: 'cookie', auth_type: 'cookie',
config: {}, config: {
proxy: buildProxyUrl(formData),
},
credentials: { credentials: {
token_cookie: formData.token_cookie, token_cookie: formData.token_cookie,
}, },
@@ -100,9 +104,11 @@ export const cubenceTemplate: AuthTemplate = {
}, },
parseConfig(config: any): Record<string, any> { parseConfig(config: any): Record<string, any> {
const proxyData = parseProxyUrl(config?.connector?.config?.proxy)
return { return {
base_url: config?.base_url || '', base_url: config?.base_url || '',
token_cookie: config?.connector?.credentials?.token_cookie || '', token_cookie: config?.connector?.credentials?.token_cookie || '',
...proxyData,
} }
}, },

View File

@@ -9,6 +9,7 @@
import type { AuthTemplate, AuthTemplateFieldGroup } from './types' import type { AuthTemplate, AuthTemplateFieldGroup } from './types'
import type { SaveConfigRequest } from '@/api/providerOps' import type { SaveConfigRequest } from '@/api/providerOps'
import { PROXY_FIELD_GROUP, buildProxyUrl, parseProxyUrl } from './types'
export const newApiTemplate: AuthTemplate = { export const newApiTemplate: AuthTemplate = {
id: 'new_api', id: 'new_api',
@@ -45,6 +46,7 @@ export const newApiTemplate: AuthTemplate = {
}, },
], ],
}, },
PROXY_FIELD_GROUP,
] ]
}, },
@@ -58,6 +60,7 @@ export const newApiTemplate: AuthTemplate = {
auth_type: 'api_key', auth_type: 'api_key',
config: { config: {
auth_method: 'bearer', auth_method: 'bearer',
proxy: buildProxyUrl(formData),
}, },
credentials: { credentials: {
api_key: formData.api_key, api_key: formData.api_key,
@@ -70,10 +73,12 @@ export const newApiTemplate: AuthTemplate = {
}, },
parseConfig(config: any): Record<string, any> { parseConfig(config: any): Record<string, any> {
const proxyData = parseProxyUrl(config?.connector?.config?.proxy)
return { return {
base_url: config?.base_url || '', base_url: config?.base_url || '',
api_key: config?.connector?.credentials?.api_key || '', api_key: config?.connector?.credentials?.api_key || '',
user_id: config?.connector?.credentials?.user_id || '', user_id: config?.connector?.credentials?.user_id || '',
...proxyData,
} }
}, },

View File

@@ -46,6 +46,14 @@ export interface AuthTemplateFieldGroup {
title?: string title?: string
/** 分组内的字段 */ /** 分组内的字段 */
fields: AuthTemplateField[] fields: AuthTemplateField[]
/** 是否可折叠(默认展开) */
collapsible?: boolean
/** 折叠时的默认状态true=展开false=收起 */
defaultExpanded?: boolean
/** 是否有启用开关(与 collapsible 配合使用) */
hasToggle?: boolean
/** 启用开关对应的表单字段 key */
toggleKey?: string
} }
/** /**
@@ -141,3 +149,134 @@ export interface AuthTemplateRegistry {
/** 注册模板 */ /** 注册模板 */
register(template: AuthTemplate): void register(template: AuthTemplate): void
} }
// ==================== 通用字段定义 ====================
/**
* 代理地址字段
*/
export const PROXY_URL_FIELD: AuthTemplateField = {
key: 'proxy_url',
label: '代理地址',
type: 'text',
placeholder: 'http://proxy:port 或 socks5://',
required: false,
}
/**
* 代理用户名字段
*/
export const PROXY_USERNAME_FIELD: AuthTemplateField = {
key: 'proxy_username',
label: '用户名',
type: 'text',
placeholder: '可选',
required: false,
}
/**
* 代理密码字段
*/
export const PROXY_PASSWORD_FIELD: AuthTemplateField = {
key: 'proxy_password',
label: '密码',
type: 'password',
placeholder: '可选',
required: false,
sensitive: true,
}
/**
* 通用代理配置字段组(可折叠,带启用开关)
*/
export const PROXY_FIELD_GROUP: AuthTemplateFieldGroup = {
title: '代理配置',
fields: [PROXY_URL_FIELD, PROXY_USERNAME_FIELD, PROXY_PASSWORD_FIELD],
collapsible: true,
defaultExpanded: false,
hasToggle: true,
toggleKey: 'proxy_enabled',
}
/**
* 构建代理 URL包含认证信息
*
* @param formData 表单数据
* @returns 完整的代理 URL或 undefined
*/
export function buildProxyUrl(formData: Record<string, any>): string | undefined {
if (!formData.proxy_enabled || !formData.proxy_url) {
return undefined
}
const proxyUrl = formData.proxy_url.trim()
const username = formData.proxy_username?.trim()
const password = formData.proxy_password?.trim()
// 如果没有认证信息,直接返回 URL
if (!username) {
return proxyUrl
}
// 解析 URL 并添加认证信息
try {
const url = new URL(proxyUrl)
url.username = username
if (password) {
url.password = password
}
return url.toString()
} catch {
// URL 解析失败,尝试简单拼接
const protocol = proxyUrl.includes('://') ? proxyUrl.split('://')[0] : 'http'
const host = proxyUrl.includes('://') ? proxyUrl.split('://')[1] : proxyUrl
const auth = password ? `${username}:${password}` : username
return `${protocol}://${auth}@${host}`
}
}
/**
* 从代理 URL 解析表单数据
*
* @param proxyUrl 代理 URL
* @returns 表单数据
*/
export function parseProxyUrl(proxyUrl: string | undefined): Record<string, any> {
if (!proxyUrl) {
return {
proxy_enabled: false,
proxy_url: '',
proxy_username: '',
proxy_password: '',
}
}
try {
const url = new URL(proxyUrl)
const username = url.username || ''
const password = url.password || ''
// 移除认证信息后的 URL
url.username = ''
url.password = ''
const cleanUrl = url.toString()
return {
proxy_enabled: true,
proxy_url: cleanUrl,
proxy_username: username,
proxy_password: password,
}
} catch {
// 解析失败,直接使用原始 URL
return {
proxy_enabled: true,
proxy_url: proxyUrl,
proxy_username: '',
proxy_password: '',
}
}
}
// 兼容旧的导出(已废弃,请使用 PROXY_FIELD_GROUP
export const PROXY_FIELD: AuthTemplateField = PROXY_URL_FIELD

View File

@@ -10,6 +10,7 @@
import type { AuthTemplate, AuthTemplateFieldGroup, BalanceExtraItem } from './types' import type { AuthTemplate, AuthTemplateFieldGroup, BalanceExtraItem } from './types'
import type { SaveConfigRequest } from '@/api/providerOps' import type { SaveConfigRequest } from '@/api/providerOps'
import { PROXY_FIELD_GROUP, buildProxyUrl, parseProxyUrl } from './types'
/** /**
* 格式化限额显示(百分比格式) * 格式化限额显示(百分比格式)
@@ -51,6 +52,7 @@ export const yescodeTemplate: AuthTemplate = {
}, },
], ],
}, },
PROXY_FIELD_GROUP,
] ]
}, },
@@ -62,7 +64,9 @@ export const yescodeTemplate: AuthTemplate = {
base_url: baseUrl, base_url: baseUrl,
connector: { connector: {
auth_type: 'cookie', auth_type: 'cookie',
config: {}, config: {
proxy: buildProxyUrl(formData),
},
credentials: { credentials: {
auth_cookie: formData.auth_cookie, auth_cookie: formData.auth_cookie,
}, },
@@ -73,9 +77,11 @@ export const yescodeTemplate: AuthTemplate = {
}, },
parseConfig(config: any): Record<string, any> { parseConfig(config: any): Record<string, any> {
const proxyData = parseProxyUrl(config?.connector?.config?.proxy)
return { return {
base_url: config?.base_url || '', base_url: config?.base_url || '',
auth_cookie: config?.connector?.credentials?.auth_cookie || '', auth_cookie: config?.connector?.credentials?.auth_cookie || '',
...proxyData,
} }
}, },

View File

@@ -48,80 +48,139 @@
v-for="(group, groupIndex) in fieldGroups" v-for="(group, groupIndex) in fieldGroups"
:key="groupIndex" :key="groupIndex"
> >
<!-- 分组标题 --> <!-- 可折叠的分组如代理配置 -->
<div <div
v-if="group.title" v-if="group.collapsible && group.hasToggle && group.toggleKey"
class="pt-2 text-sm font-medium text-muted-foreground"
>
{{ group.title }}
</div>
<!-- 字段列表 -->
<div
v-for="field in group.fields"
:key="field.key"
class="space-y-2" class="space-y-2"
> >
<Label> <!-- 标题栏标题在左开关在右在卡片外 -->
{{ field.label }} <div class="flex items-center justify-between">
<span <span class="text-sm font-medium text-foreground">{{ group.title }}</span>
v-if="field.required" <div class="flex items-center gap-2">
class="text-muted-foreground/70" <span class="text-xs text-muted-foreground">启用代理</span>
>*</span> <Switch
</Label> :model-value="formData[group.toggleKey] || false"
@update:model-value="formData[group.toggleKey] = $event"
/>
</div>
</div>
<!-- 文本输入 --> <!-- 展开内容卡片 -->
<Input <div
v-if="field.type === 'text'" v-if="formData[group.toggleKey]"
v-model="formData[field.key]" class="rounded-lg border border-border bg-muted/30 px-4 py-3"
:placeholder="field.placeholder"
disable-autofill
/>
<!-- 密码/敏感输入 -->
<Input
v-else-if="field.type === 'password'"
v-model="formData[field.key]"
:placeholder="sensitivePlaceholders[field.key] || field.placeholder"
masked
/>
<!-- 下拉选择 -->
<Select
v-else-if="field.type === 'select'"
v-model="formData[field.key]"
@update:model-value="handleFieldChange(field.key, $event)"
> >
<SelectTrigger> <!-- 横向排列的字段代理地址占更多空间 -->
<SelectValue :placeholder="field.placeholder || '请选择'" /> <div class="flex gap-3">
</SelectTrigger> <div
<SelectContent> v-for="(field, fieldIndex) in group.fields"
<SelectItem :key="field.key"
v-for="option in field.options" class="space-y-1"
:key="option.value" :class="fieldIndex === 0 ? 'flex-[2]' : 'flex-1'"
:value="option.value"
> >
{{ option.label }} <Label class="text-xs text-muted-foreground">
</SelectItem> {{ field.label }}
</SelectContent> </Label>
</Select>
<!-- 多行文本 --> <!-- 文本输入 -->
<Textarea <Input
v-else-if="field.type === 'textarea'" v-if="field.type === 'text'"
v-model="formData[field.key]" v-model="formData[field.key]"
:placeholder="field.placeholder" :placeholder="field.placeholder"
rows="3" disable-autofill
/> class="h-8 text-sm"
/>
<!-- 帮助文本 --> <!-- 密码/敏感输入 -->
<p <Input
v-if="field.helpText" v-else-if="field.type === 'password'"
class="text-xs text-muted-foreground" v-model="formData[field.key]"
> :placeholder="sensitivePlaceholders[field.key] || field.placeholder"
{{ field.helpText }} masked
</p> class="h-8 text-sm"
/>
</div>
</div>
</div>
</div> </div>
<!-- 普通分组非折叠 -->
<template v-else>
<!-- 分组标题 -->
<div
v-if="group.title"
class="pt-2 text-sm font-medium text-muted-foreground"
>
{{ group.title }}
</div>
<!-- 字段列表 -->
<div
v-for="field in group.fields"
:key="field.key"
class="space-y-2"
>
<Label>
{{ field.label }}
<span
v-if="field.required"
class="text-muted-foreground/70"
>*</span>
</Label>
<!-- 文本输入 -->
<Input
v-if="field.type === 'text'"
v-model="formData[field.key]"
:placeholder="field.placeholder"
disable-autofill
/>
<!-- 密码/敏感输入 -->
<Input
v-else-if="field.type === 'password'"
v-model="formData[field.key]"
:placeholder="sensitivePlaceholders[field.key] || field.placeholder"
masked
/>
<!-- 下拉选择 -->
<Select
v-else-if="field.type === 'select'"
v-model="formData[field.key]"
@update:model-value="handleFieldChange(field.key, $event)"
>
<SelectTrigger>
<SelectValue :placeholder="field.placeholder || '请选择'" />
</SelectTrigger>
<SelectContent>
<SelectItem
v-for="option in field.options"
:key="option.value"
:value="option.value"
>
{{ option.label }}
</SelectItem>
</SelectContent>
</Select>
<!-- 多行文本 -->
<Textarea
v-else-if="field.type === 'textarea'"
v-model="formData[field.key]"
:placeholder="field.placeholder"
rows="3"
/>
<!-- 帮助文本 -->
<p
v-if="field.helpText"
class="text-xs text-muted-foreground"
>
{{ field.helpText }}
</p>
</div>
</template>
</template> </template>
</template> </template>
</div> </div>
@@ -165,6 +224,7 @@ import {
SelectItem, SelectItem,
SelectTrigger, SelectTrigger,
SelectValue, SelectValue,
Switch,
} from '@/components/ui' } from '@/components/ui'
import { saveProviderOpsConfig, verifyProviderAuth, getProviderOpsConfig } from '@/api/providerOps' import { saveProviderOpsConfig, verifyProviderAuth, getProviderOpsConfig } from '@/api/providerOps'
import { useToast } from '@/composables/useToast' import { useToast } from '@/composables/useToast'
@@ -175,7 +235,7 @@ import {
} from '../auth-templates' } from '../auth-templates'
// 敏感字段列表(用于验证和加载配置时的特殊处理) // 敏感字段列表(用于验证和加载配置时的特殊处理)
const SENSITIVE_FIELDS = ['api_key', 'password', 'session_token', 'session_cookie', 'token_cookie', 'auth_cookie', 'cookie_string', 'cookies'] as const const SENSITIVE_FIELDS = ['api_key', 'password', 'session_token', 'session_cookie', 'token_cookie', 'auth_cookie', 'cookie_string', 'cookies', 'proxy_password'] as const
const props = defineProps<{ const props = defineProps<{
open: boolean open: boolean

View File

@@ -180,7 +180,9 @@ def _parse_session_user_id(cookie_input: str) -> Tuple[Optional[str], Optional[s
return None, None return None, None
async def _get_acw_cookie(base_url: str, timeout: float = 10) -> Optional[str]: async def _get_acw_cookie(
base_url: str, timeout: float = 10, proxy: Optional[str] = None
) -> Optional[str]:
""" """
获取 acw_sc__v2 Cookie 获取 acw_sc__v2 Cookie
@@ -189,12 +191,22 @@ async def _get_acw_cookie(base_url: str, timeout: float = 10) -> Optional[str]:
Args: Args:
base_url: 目标站点 URL base_url: 目标站点 URL
timeout: 请求超时时间 timeout: 请求超时时间
proxy: 代理地址
Returns: Returns:
Cookie 字符串 (acw_sc__v2=xxx),如果不需要或获取失败则返回 None Cookie 字符串 (acw_sc__v2=xxx),如果不需要或获取失败则返回 None
""" """
try: try:
async with httpx.AsyncClient(timeout=timeout, verify=get_ssl_context()) as client: # 构建 client 参数
client_kwargs: Dict[str, Any] = {
"timeout": timeout,
"verify": get_ssl_context(),
}
if proxy:
client_kwargs["proxy"] = proxy
logger.debug(f"获取 acw_sc__v2 Cookie 使用代理: {proxy}")
async with httpx.AsyncClient(**client_kwargs) as client:
resp = await client.get( resp = await client.get(
base_url, base_url,
headers={ headers={
@@ -253,8 +265,8 @@ class AnyrouterConnector(ProviderConnector):
# 解析 user_id # 解析 user_id
self._user_id, _ = _parse_session_user_id(session_cookie) self._user_id, _ = _parse_session_user_id(session_cookie)
# 尝试获取反爬 Cookie # 尝试获取反爬 Cookie(使用配置中的代理)
self._acw_cookie = await _get_acw_cookie(self.base_url) self._acw_cookie = await _get_acw_cookie(self.base_url, proxy=self._proxy)
self._set_connected() self._set_connected()
return True return True
@@ -371,7 +383,9 @@ class AnyrouterArchitecture(ProviderArchitecture):
Returns: Returns:
包含 acw_cookie 的配置 包含 acw_cookie 的配置
""" """
acw_cookie = await _get_acw_cookie(base_url) # 从 config 获取代理配置
proxy = config.get("proxy")
acw_cookie = await _get_acw_cookie(base_url, proxy=proxy)
if acw_cookie: if acw_cookie:
return {"acw_cookie": acw_cookie} return {"acw_cookie": acw_cookie}
return {} return {}

View File

@@ -193,13 +193,21 @@ class YesCodeArchitecture(ProviderArchitecture):
cookie_header = _build_cookie_header(cookie_input) cookie_header = _build_cookie_header(cookie_input)
# 获取代理配置
proxy = config.get("proxy")
try: try:
# 构建 client 参数
client_kwargs: Dict[str, Any] = {
"headers": {"Cookie": cookie_header},
"timeout": 10.0,
"verify": get_ssl_context(),
}
if proxy:
client_kwargs["proxy"] = proxy
# 创建临时 client 获取合并数据 # 创建临时 client 获取合并数据
async with httpx.AsyncClient( async with httpx.AsyncClient(**client_kwargs) as client:
headers={"Cookie": cookie_header},
timeout=10.0,
verify=get_ssl_context(),
) as client:
combined_data = await fetch_yescode_combined_data(client, base_url) combined_data = await fetch_yescode_combined_data(client, base_url)
extra_config["_combined_data"] = combined_data extra_config["_combined_data"] = combined_data
except Exception: except Exception:

View File

@@ -727,8 +727,20 @@ class ProviderOpsService:
f"endpoint={verify_endpoint}, headers={list(headers.keys())}" f"endpoint={verify_endpoint}, headers={list(headers.keys())}"
) )
# 获取代理配置
proxy = config.get("proxy")
try: try:
async with httpx.AsyncClient(timeout=30.0, verify=get_ssl_context()) as client: # 构建 httpx client 参数
client_kwargs: Dict[str, Any] = {
"timeout": 30.0,
"verify": get_ssl_context(),
}
if proxy:
client_kwargs["proxy"] = proxy
logger.debug(f"使用代理: {proxy}")
async with httpx.AsyncClient(**client_kwargs) as client:
response = await client.get(verify_endpoint, headers=headers) response = await client.get(verify_endpoint, headers=headers)
logger.debug( logger.debug(