Skip to content

Commit cbf9cd1

Browse files
committed
fix: harden sqlite concurrency and media cache delivery
1 parent dd09e54 commit cbf9cd1

8 files changed

Lines changed: 541 additions & 284 deletions

File tree

src/core/account_tiers.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -33,7 +33,7 @@ def get_paygate_tier_label(user_paygate_tier: Optional[str]) -> str:
3333
return "Ult"
3434
if normalized == PAYGATE_TIER_ONE:
3535
return "Pro"
36-
return "Normal"
36+
return "Free"
3737

3838

3939
def get_required_paygate_tier_for_model(model_name: Optional[str]) -> str:

src/core/database.py

Lines changed: 75 additions & 47 deletions
Large diffs are not rendered by default.

src/services/file_cache.py

Lines changed: 260 additions & 148 deletions
Large diffs are not rendered by default.

src/services/flow_client.py

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -126,6 +126,13 @@ def _set_request_fingerprint(self, fingerprint: Optional[Dict[str, Any]]):
126126
"""设置当前请求链路的浏览器指纹上下文。"""
127127
self._request_fingerprint_ctx.set(dict(fingerprint) if fingerprint else None)
128128

129+
def get_request_fingerprint(self) -> Optional[Dict[str, Any]]:
130+
"""获取当前请求链路绑定的浏览器指纹快照。"""
131+
fingerprint = self._request_fingerprint_ctx.get()
132+
if not isinstance(fingerprint, dict) or not fingerprint:
133+
return None
134+
return dict(fingerprint)
135+
129136
def clear_request_fingerprint(self):
130137
"""清理请求链路绑定的浏览器指纹。"""
131138
self._set_request_fingerprint(None)

src/services/generation_handler.py

Lines changed: 149 additions & 85 deletions
Large diffs are not rendered by default.

src/services/load_balancer.py

Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -275,3 +275,43 @@ async def select_token(
275275

276276
debug_logger.log_info(f"[LOAD_BALANCER] ❌ 候选Token均不可用 (图片生成={for_image_generation}, 视频生成={for_video_generation})")
277277
return None
278+
279+
async def get_unavailable_reason(
280+
self,
281+
*,
282+
for_image_generation: bool = False,
283+
for_video_generation: bool = False,
284+
model: Optional[str] = None,
285+
) -> Optional[str]:
286+
"""给出更明确的“无可用账号”原因,优先用于分辨率/tier 档位提示。"""
287+
active_tokens = await self.token_manager.get_active_tokens()
288+
if not active_tokens:
289+
return None
290+
291+
required_tier = get_required_paygate_tier_for_model(model)
292+
supported_tokens = []
293+
for token in active_tokens:
294+
normalized_tier = normalize_user_paygate_tier(token.user_paygate_tier)
295+
if model and not supports_model_for_tier(model, normalized_tier):
296+
continue
297+
supported_tokens.append(token)
298+
299+
if model and not supported_tokens:
300+
tier_label = get_paygate_tier_label(required_tier)
301+
return f"当前模型需要 {tier_label} 账号,但没有可用的 {tier_label} 账号: {model}"
302+
303+
capability_tokens = []
304+
for token in supported_tokens:
305+
if for_image_generation and not token.image_enabled:
306+
continue
307+
if for_video_generation and not token.video_enabled:
308+
continue
309+
capability_tokens.append(token)
310+
311+
if supported_tokens and not capability_tokens:
312+
if for_image_generation:
313+
return "当前有符合档位的账号,但图片生成功能已全部禁用。"
314+
if for_video_generation:
315+
return "当前有符合档位的账号,但视频生成功能已全部禁用。"
316+
317+
return None

src/services/token_manager.py

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -403,7 +403,8 @@ async def _do_refresh_at(self, token_id: int, st: str) -> bool:
403403
credits_result = await self.flow_client.get_credits(new_at)
404404
await self.db.update_token(
405405
token_id,
406-
credits=credits_result.get("credits", 0)
406+
credits=credits_result.get("credits", 0),
407+
user_paygate_tier=credits_result.get("userPaygateTier"),
407408
)
408409
debug_logger.log_info(f"[AT_REFRESH] Token {token_id}: AT 验证成功(余额: {credits_result.get('credits', 0)})")
409410
return True
@@ -619,9 +620,14 @@ async def refresh_credits(self, token_id: int) -> int:
619620
try:
620621
result = await self.flow_client.get_credits(token.at)
621622
credits = result.get("credits", 0)
623+
user_paygate_tier = result.get("userPaygateTier")
622624

623625
# 更新数据库
624-
await self.db.update_token(token_id, credits=credits)
626+
await self.db.update_token(
627+
token_id,
628+
credits=credits,
629+
user_paygate_tier=user_paygate_tier,
630+
)
625631

626632
return credits
627633
except Exception as e:

static/manage.html

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -785,7 +785,7 @@ <h3 class="text-lg font-semibold">导入 Token</h3>
785785
formatSora2=(t)=>{if(t.sora2_supported===true){const remaining=t.sora2_total_count-t.sora2_redeemed_count;const tooltipText=`邀请码: ${t.sora2_invite_code||'无'}\n可用次数: ${remaining}/${t.sora2_total_count}\n已用次数: ${t.sora2_redeemed_count}`;return`<div class="inline-flex items-center gap-1"><span class="inline-flex items-center rounded px-2 py-0.5 text-xs bg-green-50 text-green-700 cursor-pointer" title="${tooltipText}" onclick="copySora2Code('${t.sora2_invite_code||''}')">支持</span><span class="text-xs text-muted-foreground" title="${tooltipText}">${remaining}/${t.sora2_total_count}</span></div>`}else if(t.sora2_supported===false){return`<span class="inline-flex items-center rounded px-2 py-0.5 text-xs bg-gray-100 text-gray-700 cursor-pointer" title="点击使用邀请码激活" onclick="openSora2Modal(${t.id})">不支持</span>`}else{return'-'}},
786786
formatPlanTypeWithTooltip=(t)=>{const tooltipText=t.subscription_end?`套餐到期: ${new Date(t.subscription_end).toLocaleDateString('zh-CN',{year:'numeric',month:'2-digit',day:'2-digit'}).replace(/\//g,'-')} ${new Date(t.subscription_end).toLocaleTimeString('zh-CN',{hour:'2-digit',minute:'2-digit',hour12:false})}`:'';return`<span class="inline-flex items-center rounded px-2 py-0.5 text-xs bg-blue-50 text-blue-700 cursor-pointer" title="${tooltipText||t.plan_title||'-'}">${formatPlanType(t.plan_type)}</span>`},
787787
formatSora2Remaining=(t)=>{if(t.sora2_supported===true){const remaining=t.sora2_remaining_count||0;return`<span class="text-xs">${remaining}</span>`}else{return'-'}},
788-
formatAccountType=(tier)=>{if(tier==='PAYGATE_TIER_NOT_PAID'||!tier){return`<span class="inline-flex items-center rounded px-2 py-0.5 text-xs bg-gray-100 text-gray-700">普通</span>`}if(tier==='PAYGATE_TIER_ONE'){return`<span class="inline-flex items-center rounded px-2 py-0.5 text-xs bg-blue-50 text-blue-700">Pro</span>`}if(tier==='PAYGATE_TIER_TWO'){return`<span class="inline-flex items-center rounded px-2 py-0.5 text-xs bg-purple-50 text-purple-700">Ult</span>`}return`<span class="inline-flex items-center rounded px-2 py-0.5 text-xs bg-amber-50 text-amber-700" title="${tier}">${tier}</span>`},
788+
formatAccountType=(tier)=>{if(tier==='PAYGATE_TIER_NOT_PAID'||!tier){return`<span class="inline-flex items-center rounded px-2 py-0.5 text-xs bg-gray-100 text-gray-700">Free</span>`}if(tier==='PAYGATE_TIER_ONE'){return`<span class="inline-flex items-center rounded px-2 py-0.5 text-xs bg-blue-50 text-blue-700">Pro</span>`}if(tier==='PAYGATE_TIER_TWO'){return`<span class="inline-flex items-center rounded px-2 py-0.5 text-xs bg-purple-50 text-purple-700">Ult</span>`}return`<span class="inline-flex items-center rounded px-2 py-0.5 text-xs bg-amber-50 text-amber-700" title="${tier}">${tier}</span>`},
789789
formatProjectNameDisplay=name=>{if(!name)return'<span class="text-muted-foreground">-</span>';const text=String(name);const match=text.match(/^(.*?)(?:\s+(P\d+))$/);if(match){return`<div class="flex flex-col items-center justify-center gap-1 leading-none"><span class="text-xs font-medium text-foreground whitespace-nowrap">${escapeLogHtml(match[1])}</span><span class="inline-flex items-center rounded-full border border-slate-200 bg-slate-100 px-2 py-0.5 text-[11px] font-medium text-slate-700">${escapeLogHtml(match[2])}</span></div>`}return`<div class="text-center text-xs font-medium text-foreground whitespace-nowrap">${escapeLogHtml(text)}</div>`},
790790
formatProjectIdDisplay=projectId=>{if(!projectId)return'<span class="text-muted-foreground">-</span>';const text=String(projectId);const short=text.length>8?`${text.substring(0,8)}...`:text;return`<button onclick="copyProjectId('${projectId}')" class="inline-flex items-center justify-center rounded-full border border-blue-200 bg-blue-50 px-2 py-0.5 text-[10px] font-mono font-medium text-blue-700 transition-colors hover:bg-blue-100 hover:text-blue-800" title="点击复制项目ID&#10;${escapeLogHtml(text)}">${escapeLogHtml(short)}</button>`},
791791
renderTokenActions=t=>{const toggleLabel=t.is_active?'禁用':'启用';return`<div class="inline-flex flex-nowrap items-center justify-end gap-1"><button onclick="refreshTokenAT(${t.id})" class="inline-flex h-7 items-center justify-center rounded-md px-2 text-xs font-medium hover:bg-blue-50 hover:text-blue-700">刷新AT</button><button onclick="openEditModal(${t.id})" class="inline-flex h-7 items-center justify-center rounded-md px-2 text-xs font-medium hover:bg-green-50 hover:text-green-700">详情</button><button onclick="toggleToken(${t.id},${t.is_active})" class="inline-flex h-7 items-center justify-center rounded-md px-2 text-xs font-medium hover:bg-accent">${toggleLabel}</button><button onclick="deleteToken(${t.id})" class="inline-flex h-7 items-center justify-center rounded-md px-2 text-xs font-medium hover:bg-destructive/10 hover:text-destructive">删除</button></div>`},

0 commit comments

Comments
 (0)