fix(web): 审核回归 — 5 项修复 (渠道写入丢失/斗鱼WS生命周期/归属校验/恢复订阅/画像key稳定)

B1 routers/huya.py: 批量 Web 登录 login_channel/status 回填在 helper 内部 commit
   之后赋值且未再提交 → 丢失; 补 db.commit() 并统一 status=active
B2 routers/login.py + services/login_service.py: 斗鱼登录 WS 断线即 pop 批次,
   刷新后无法重连订阅 → 对齐虎牙模式 (BatchRegistry.mark_finished, 仅结束后清理)
B3 routers/login.py: WS 增加批次归属校验 (非 view_all 仅可订阅自建批次)
S1 LoginTasksPage: effectiveBatchId 从任务列表推导活跃批次, 刷新后停止按钮/
   日志订阅自动恢复
S4 services/huya_service.py: username 仅在为空时回填, 防止 udb_passport 覆盖
   登录名导致一号一设备绑定 key 漂移

验证: 77 后端单测 OK; tsc + vite build OK
This commit is contained in:
yml2213
2026-08-29 18:20:27 +08:00
parent a369829df3
commit 22aa485536
5 changed files with 82 additions and 12 deletions
+31 -9
View File
@@ -213,6 +213,27 @@ export default function LoginTasksPage() {
[tasks],
);
// 刷新/切页后 batchId 内存态丢失:从任务列表推导仍在运行的批次,
// 保证停止按钮与日志订阅在页面恢复后依然可用。
const activeBatchIdFromTasks = useMemo(
() => tasks.find((task) => ['pending', 'running'].includes(task.status))?.batch_id || null,
[tasks],
);
const effectiveBatchId = batchId || activeBatchIdFromTasks;
// 活跃批次存在但未订阅时自动重连实时日志(配合后端 WS 仅在批次结束后清理注册表)。
const wsSubscribedBatchRef = useRef<string | null>(null);
useEffect(() => {
if (!effectiveBatchId || wsConnected) return;
if (wsSubscribedBatchRef.current === effectiveBatchId) return;
wsSubscribedBatchRef.current = effectiveBatchId;
connectLogs(`/api/login/ws/login/${effectiveBatchId}`, {
onClose: () => { setBatchId(null); setStarting(false); },
onResult: () => { setBatchId(null); setStarting(false); },
onError: () => { setBatchId(null); setStarting(false); },
});
}, [effectiveBatchId, wsConnected, connectLogs]);
useEffect(() => {
void loadTags();
void loadTasks();
@@ -249,6 +270,7 @@ export default function LoginTasksPage() {
mode,
});
setBatchId(result.batch_id);
wsSubscribedBatchRef.current = result.batch_id;
message.success(`已创建${mode === 'check' ? '检测' : '登录'}任务,共 ${result.count} 个账号`);
connectLogs(`/api/login/ws/login/${result.batch_id}`, {
@@ -287,9 +309,9 @@ export default function LoginTasksPage() {
};
const handleStop = async () => {
if (batchId) {
if (effectiveBatchId) {
try {
await loginApi.stop(batchId);
await loginApi.stop(effectiveBatchId);
message.success('已发送停止信号');
} catch (e: unknown) {
message.error(getErrorMessage(e));
@@ -522,11 +544,11 @@ export default function LoginTasksPage() {
>
</Button>
{batchId && (
<Button danger icon={<StopOutlined />} onClick={handleStop} size="small">
</Button>
)}
{effectiveBatchId && (
<Button danger icon={<StopOutlined />} onClick={handleStop} size="small">
</Button>
)}
</div>
)}
</div>
@@ -539,7 +561,7 @@ export default function LoginTasksPage() {
<span> <b style={{ color: token.colorSuccess }}>{successCount}</b></span>
<span> <b style={{ color: token.colorError }}>{failedCount}</b></span>
{checkedCount > 0 && <span> <b>{checkedCount}</b></span>}
{batchId && <span>: <b>{batchId}</b></span>}
{effectiveBatchId && <span>: <b>{effectiveBatchId}</b></span>}
<div style={{ flex: 1 }} />
{selectedRowKeys.length > 0 && (
<Popconfirm title={`确定删除选中的 ${selectedRowKeys.length} 个任务?`} onConfirm={handleDeleteSelected} okText="删除" cancelText="取消">
@@ -658,7 +680,7 @@ export default function LoginTasksPage() {
</Form.Item>
<Form.Item
label="登录整体重试次数"
tooltip="单个账号最多重试多少轮(每轮换新代理)。0 表示无限重试直到成功或超时。"
tooltip="单个账号最多重试多少轮(每轮换新代理)。0 表示使用后端默认上限 20 轮。"
>
<Space.Compact>
<InputNumber