feat: 新增 Anyrouter/Cubence/YesCode 架构及余额监控增强

- 新增三个 provider 架构:Anyrouter、Cubence、YesCode
- 前端新增对应的认证配置模板
- 余额监控增强:支持窗口限额显示(进度条+倒计时)、签到状态、错误信息展示
- 架构基类新增 prepare_verify_config 异步预处理方法
- API 返回 ops_architecture_id 字段用于前端展示
This commit is contained in:
fawney19
2026-01-19 17:18:03 +08:00
parent 3d88dfd98a
commit 6bc9cdc69d
25 changed files with 2057 additions and 20 deletions

View File

@@ -0,0 +1,84 @@
/**
* Anyrouter 认证模板
*
* 适用于 Anyrouter 中转站:
* - 使用 Cookie 认证session
* - 自动处理 acw_sc__v2 反爬 Cookie后端处理
* - quota 单位是 1/500000 美元
*/
import type { AuthTemplate, AuthTemplateFieldGroup } from './types'
import type { SaveConfigRequest } from '@/api/providerOps'
export const anyrouterTemplate: AuthTemplate = {
id: 'anyrouter',
name: 'Anyrouter',
description: '适用于 Anyrouter 中转站,使用 Cookie 认证',
getFields(providerWebsite?: string): AuthTemplateFieldGroup[] {
return [
{
fields: [
{
key: 'base_url',
label: '站点地址',
type: 'text',
placeholder: providerWebsite || 'https://anyrouter.top',
required: !providerWebsite,
helpText: '通常为 https://anyrouter.top',
},
{
key: 'session_cookie',
label: 'Cookie',
type: 'password',
placeholder: 'session=MTc2ODc4...; acw_sc__v2=...',
required: true,
sensitive: true,
helpText: '从浏览器开发者工具复制完整 Cookie或仅填写 session 值',
},
],
},
]
},
buildRequest(formData: Record<string, any>, providerWebsite?: string): SaveConfigRequest {
const baseUrl = formData.base_url || providerWebsite || ''
return {
architecture_id: 'anyrouter',
base_url: baseUrl,
connector: {
auth_type: 'cookie',
config: {},
credentials: {
session_cookie: formData.session_cookie,
},
},
actions: {},
schedule: {},
}
},
parseConfig(config: any): Record<string, any> {
return {
base_url: config?.base_url || '',
session_cookie: config?.connector?.credentials?.session_cookie || '',
}
},
validate(formData: Record<string, any>): string | null {
if (!formData.session_cookie?.trim()) {
return '请填写 Session Cookie'
}
return null
},
formatQuota(quota: number): string {
// Anyrouter 的 quota 单位是 1/500000 美元
const usd = quota / 500000
if (usd >= 1) {
return `$${usd.toFixed(2)}`
}
return `$${usd.toFixed(4)}`
},
}

View File

@@ -0,0 +1,157 @@
/**
* Cubence 认证模板
*
* 适用于 Cubence 中转站:
* - 使用 Cookie 认证token JWT
* - 余额单位直接是美元
* - 支持窗口限额查询5小时/每周)
*/
import type { AuthTemplate, AuthTemplateFieldGroup, BalanceExtraItem } from './types'
import type { SaveConfigRequest } from '@/api/providerOps'
/**
* 格式化窗口限额显示(百分比格式)
* 单位是美元(原始值除以 1000000
*/
function formatWindowLimit(limit: {
limit?: number
used?: number
remaining?: number
resets_at?: number
}): { text: string; percent: number } {
if (!limit || limit.remaining === undefined || limit.limit === undefined || limit.limit === 0) {
return { text: '-', percent: 0 }
}
// 计算剩余百分比
const percent = Math.round((limit.remaining / limit.limit) * 100)
return { text: `${percent}%`, percent }
}
/**
* 格式化窗口限额详细信息(用于 tooltip
*/
function formatWindowLimitDetail(limit: {
limit?: number
used?: number
remaining?: number
resets_at?: number
}): string {
if (!limit || limit.remaining === undefined || limit.limit === undefined) {
return ''
}
// 转换为美元(原始值除以 1000000
const remaining = (limit.remaining / 1000000).toFixed(2)
const total = (limit.limit / 1000000).toFixed(2)
return `$${remaining} / $${total}`
}
export const cubenceTemplate: AuthTemplate = {
id: 'cubence',
name: 'Cubence',
description: '适用于 Cubence 中转站,使用 Cookie 认证',
getFields(providerWebsite?: string): AuthTemplateFieldGroup[] {
return [
{
fields: [
{
key: 'base_url',
label: '站点地址',
type: 'text',
placeholder: providerWebsite || 'https://cubence.com',
required: !providerWebsite,
helpText: '通常为 https://cubence.com',
},
{
key: 'token_cookie',
label: 'Token Cookie',
type: 'password',
placeholder: 'token=eyJhbGciOiJI...',
required: true,
sensitive: true,
helpText: '从浏览器开发者工具复制 Cookie 中的 token 值JWT 格式)',
},
],
},
]
},
buildRequest(formData: Record<string, any>, providerWebsite?: string): SaveConfigRequest {
const baseUrl = formData.base_url || providerWebsite || ''
return {
architecture_id: 'cubence',
base_url: baseUrl,
connector: {
auth_type: 'cookie',
config: {},
credentials: {
token_cookie: formData.token_cookie,
},
},
actions: {},
schedule: {},
}
},
parseConfig(config: any): Record<string, any> {
return {
base_url: config?.base_url || '',
token_cookie: config?.connector?.credentials?.token_cookie || '',
}
},
validate(formData: Record<string, any>): string | null {
if (!formData.token_cookie?.trim()) {
return '请填写 Token Cookie'
}
return null
},
formatQuota(quota: number): string {
// Cubence 的余额单位直接是美元
if (quota >= 1) {
return `$${quota.toFixed(2)}`
}
return `$${quota.toFixed(4)}`
},
formatBalanceExtra(extra: Record<string, any>): BalanceExtraItem[] {
const items: BalanceExtraItem[] = []
// 5小时窗口限额
if (extra.five_hour_limit) {
const limit = extra.five_hour_limit
const detail = formatWindowLimitDetail(limit)
const { text, percent } = formatWindowLimit(limit)
items.push({
label: '5h',
value: text,
percent,
resetsAt: limit.resets_at,
tooltip: detail || undefined,
})
}
// 每周窗口限额
if (extra.weekly_limit) {
const limit = extra.weekly_limit
const detail = formatWindowLimitDetail(limit)
const { text, percent } = formatWindowLimit(limit)
items.push({
label: '周',
value: text,
percent,
resetsAt: limit.resets_at,
tooltip: detail || undefined,
})
}
return items
},
}

View File

@@ -22,12 +22,15 @@
*/
import type { AuthTemplate, AuthTemplateRegistry } from './types'
import { anyrouterTemplate } from './anyrouter'
import { cubenceTemplate } from './cubence'
import { newApiTemplate } from './new-api'
import { yescodeTemplate } from './yescode'
// ==================== 模板注册 ====================
// 在这里添加新模板
const templates: AuthTemplate[] = [newApiTemplate]
const templates: AuthTemplate[] = [newApiTemplate, anyrouterTemplate, cubenceTemplate, yescodeTemplate]
// ==================== 注册表实现 ====================
@@ -63,4 +66,7 @@ export const authTemplateRegistry: AuthTemplateRegistry = {
// ==================== 导出 ====================
export * from './types'
export { anyrouterTemplate } from './anyrouter'
export { cubenceTemplate } from './cubence'
export { newApiTemplate } from './new-api'
export { yescodeTemplate } from './yescode'

View File

@@ -103,6 +103,29 @@ export interface AuthTemplate {
* @param quota quota 值
*/
formatQuota?(quota: number): string
/**
* 格式化余额 extra 信息(如窗口限额等)
* 返回一个数组,每个元素包含 label 和 value
* @param extra 余额 extra 字段
*/
formatBalanceExtra?(extra: Record<string, any>): BalanceExtraItem[]
}
/**
* 余额附加信息项
*/
export interface BalanceExtraItem {
/** 显示标签 */
label: string
/** 显示值 */
value: string
/** 百分比数值 (0-100),用于进度条显示 */
percent?: number
/** 重置时间戳Unix 秒),用于倒计时显示 */
resetsAt?: number
/** 可选的提示文本 */
tooltip?: string
}
/**

View File

@@ -0,0 +1,135 @@
/**
* YesCode 认证模板
*
* 适用于 YesCode 中转站:
* - 使用 Cookie 认证yescode_auth JWT + yescode_csrf
* - 余额单位直接是美元
* - subscription_balance: 套餐每日额度
* - weekly_limit: 周限额(用完后套餐额度也无法使用)
*/
import type { AuthTemplate, AuthTemplateFieldGroup, BalanceExtraItem } from './types'
import type { SaveConfigRequest } from '@/api/providerOps'
/**
* 格式化限额显示(百分比格式)
*/
function formatLimitPercent(remaining: number, limit: number): { text: string; percent: number } {
if (!limit || limit === 0) {
return { text: '-', percent: 0 }
}
const percent = Math.round((remaining / limit) * 100)
return { text: `${percent}%`, percent }
}
export const yescodeTemplate: AuthTemplate = {
id: 'yescode',
name: 'YesCode',
description: '适用于 YesCode 中转站,使用 Cookie 认证',
getFields(providerWebsite?: string): AuthTemplateFieldGroup[] {
return [
{
fields: [
{
key: 'base_url',
label: '站点地址',
type: 'text',
placeholder: providerWebsite || 'https://co.yes.vg',
required: !providerWebsite,
helpText: '通常为 https://co.yes.vg',
},
{
key: 'auth_cookie',
label: 'Auth Cookie',
type: 'password',
placeholder: 'yescode_auth=eyJhbGciOiJI...; yescode_csrf=...',
required: true,
sensitive: true,
helpText: '从浏览器开发者工具复制 Cookie包含 yescode_auth 和 yescode_csrf',
},
],
},
]
},
buildRequest(formData: Record<string, any>, providerWebsite?: string): SaveConfigRequest {
const baseUrl = formData.base_url || providerWebsite || ''
return {
architecture_id: 'yescode',
base_url: baseUrl,
connector: {
auth_type: 'cookie',
config: {},
credentials: {
auth_cookie: formData.auth_cookie,
},
},
actions: {},
schedule: {},
}
},
parseConfig(config: any): Record<string, any> {
return {
base_url: config?.base_url || '',
auth_cookie: config?.connector?.credentials?.auth_cookie || '',
}
},
validate(formData: Record<string, any>): string | null {
if (!formData.auth_cookie?.trim()) {
return '请填写 Auth Cookie'
}
return null
},
formatQuota(quota: number): string {
// YesCode 的余额单位直接是美元
if (quota >= 1) {
return `$${quota.toFixed(2)}`
}
return `$${quota.toFixed(4)}`
},
formatBalanceExtra(extra: Record<string, any>): BalanceExtraItem[] {
const items: BalanceExtraItem[] = []
const weeklyLimit = extra.weekly_limit
const weeklySpent = extra.weekly_spent
const dailyLimit = extra.daily_limit
const dailySpent = extra.daily_spent
const dailyResetsAt = extra.daily_resets_at
const weeklyResetsAt = extra.weekly_resets_at
// 天限
if (dailyLimit !== undefined && dailyLimit > 0 && dailySpent !== undefined) {
const dailyRemaining = Math.max(0, dailyLimit - dailySpent)
const { text, percent } = formatLimitPercent(dailyRemaining, dailyLimit)
items.push({
label: '天',
value: text,
percent,
resetsAt: dailyResetsAt,
tooltip: `$${dailyRemaining.toFixed(2)} / $${(dailyLimit as number).toFixed(2)}`,
})
}
// 周限
if (weeklyLimit !== undefined && weeklyLimit > 0 && weeklySpent !== undefined) {
const weeklyRemaining = Math.max(0, weeklyLimit - weeklySpent)
const { text, percent } = formatLimitPercent(weeklyRemaining, weeklyLimit)
items.push({
label: '周',
value: text,
percent,
resetsAt: weeklyResetsAt,
tooltip: `$${weeklyRemaining.toFixed(2)} / $${(weeklyLimit as number).toFixed(2)}`,
})
}
return items
},
}

View File

@@ -175,7 +175,7 @@ import {
} from '../auth-templates'
// 敏感字段列表(用于验证和加载配置时的特殊处理)
const SENSITIVE_FIELDS = ['api_key', 'password', 'session_token', 'cookie_string', 'cookies'] as const
const SENSITIVE_FIELDS = ['api_key', 'password', 'session_token', 'session_cookie', 'token_cookie', 'auth_cookie', 'cookie_string', 'cookies'] as const
const props = defineProps<{
open: boolean
@@ -427,11 +427,11 @@ function loadFromConfig(config: any) {
hasExistingConfig.value = true
// 目前只支持 new_api 模板
selectedTemplateId.value = 'new_api'
// 使用模板解析配置
// 根据已保存的 architecture_id 选择对应模板,不存在则回退到 new_api
const architectureId = config.architecture_id || 'new_api'
selectedTemplateId.value = authTemplateRegistry.get(architectureId) ? architectureId : 'new_api'
const template = authTemplateRegistry.get(selectedTemplateId.value)
if (template) {
const parsedData = template.parseConfig(config)