From af50ff1d9516ff666a55d5dc29515f7a1d4ba050 Mon Sep 17 00:00:00 2001 From: HenryDu8133 <813367384@qq.com> Date: Wed, 30 Sep 2026 19:09:26 +0800 Subject: [PATCH] =?UTF-8?q?feat(kugou):=20=E6=94=AF=E6=8C=81=E7=B2=98?= =?UTF-8?q?=E8=B4=B4=E6=B5=8F=E8=A7=88=E5=99=A8=20Cookie=20=E7=BB=91?= =?UTF-8?q?=E5=AE=9A=E9=85=B7=E7=8B=97=EF=BC=88=E4=B8=8D=E5=86=8D=E5=BF=85?= =?UTF-8?q?=E9=A1=BB=E6=89=AB=E7=A0=81=EF=BC=89?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 站长要求去掉扫码:扫码必须拿另一台装了酷狗 App 的手机,很别扭。 技术上完全可行 —— kuGouFetch() 本来就是**无状态**的:它把库里 kugouAdminCookie 放进请求头、kugouAdminToken 放进 query 去调 sidecar。 扫码只是获取 cookie 的一种方式,不是同步的前置条件。 新增 POST /api/admin/kugou/bind-cookie {cookie, token?}: - 写入前先用这份凭证探一次 /login/status,无效/过期的凭证**不落库** (避免出现看起来绑定了但同步全失败的假绑定) - 有效则写入 systemSettings 的 kugouAdmin* 字段(与 qr-check 完全相同的落点)+ 刷新缓存 - 连不上 sidecar 时返回 502 并提示去「服务设置」检查酷狗 API 地址 - 只保存会话 Cookie,**不接收也不保存酷狗账号密码** qr-create / qr-check 保留未删(网页版可能还在用)。 --- server/api/admin/kugou/bind-cookie.post.ts | 91 ++++++++++++++++++++++ 1 file changed, 91 insertions(+) create mode 100644 server/api/admin/kugou/bind-cookie.post.ts diff --git a/server/api/admin/kugou/bind-cookie.post.ts b/server/api/admin/kugou/bind-cookie.post.ts new file mode 100644 index 0000000..0879ab7 --- /dev/null +++ b/server/api/admin/kugou/bind-cookie.post.ts @@ -0,0 +1,91 @@ +import { eq } from 'drizzle-orm' +import { db } from '~/drizzle/db' +import { systemSettings } from '~/drizzle/schema' +import { CacheService } from '~~/server/services/cacheService' +import { kuGouFetch } from '~~/server/services/kugouService' + +/** + * 粘贴 Cookie / Token 绑定酷狗 —— 不再需要扫码。 + * + * 为什么可以这样做:`kuGouFetch()` 是**无状态**的 —— 它把库里存的 + * `kugouAdminCookie` 放进请求头、`kugouAdminToken` 放进 query,去调 sidecar。 + * 也就是说"扫码"只是**获取 cookie 的一种方式**;只要拿到一份有效 cookie, + * 后续同步的行为与扫码绑定完全一致。 + * + * 好处:管理员在**电脑浏览器**上登录酷狗(滑块/短信验证码都能过)→ + * 从 devtools 复制 Cookie 粘贴过来即可,不依赖另一台装了酷狗 App 的手机。 + * + * 安全:这里**不接收也不保存酷狗账号密码**,只保存浏览器会话 Cookie(与扫码同款), + * 且写入前会先用它探一次 `/login/status`,无效的凭证不会落库。 + */ +export default defineEventHandler(async (event) => { + const user = event.context.user + if (!user) throw createError({ statusCode: 401, message: '未登录' }) + if (!['ADMIN', 'SUPER_ADMIN'].includes(user.role)) + throw createError({ statusCode: 403, message: '仅管理员可绑定酷狗账号' }) + + const body = await readBody(event) + const cookie = String(body?.cookie || '').trim() + const token = String(body?.token || '').trim() + if (!cookie && !token) { + throw createError({ statusCode: 400, message: '请粘贴 Cookie(或 token)后再保存' }) + } + if (cookie.length > 8000) { + throw createError({ statusCode: 400, message: 'Cookie 太长(超过 8000 字),可能粘贴错了内容' }) + } + + // 先用这份凭证探一次登录状态:有效才写库,避免把废值存进去导致"看起来绑定了但同步全失败" + let loginStatus: any = null + try { + loginStatus = await kuGouFetch( + '/login/status', + { timestamp: Date.now() }, + { forceCookie: cookie || undefined, forceToken: token || undefined }, + ) + } catch (e: any) { + throw createError({ + statusCode: 502, + message: + '用这份 Cookie 连接酷狗失败:' + + (e?.message || String(e)) + + '(如果提示连不上 sidecar,请先到「服务设置」里确认酷狗 API 地址)', + }) + } + + const statusOk = + Number(loginStatus?.status ?? 0) === 1 || !!(loginStatus?.data?.profile || loginStatus?.data?.account) + if (!statusOk) { + throw createError({ + statusCode: 400, + message: + '这份 Cookie 无效或已过期(酷狗侧显示未登录):' + + (loginStatus?.error || loginStatus?.message || '请在浏览器里重新登录酷狗后重新复制'), + }) + } + + const profile = loginStatus?.data?.profile || loginStatus?.data?.account || null + const rows = await db.select().from(systemSettings).limit(1) + const patch: Record = { + kugouAdminToken: token || null, + kugouAdminCookie: cookie || null, + kugouAdminUser: profile || null, + kugouAdminBoundAt: new Date(), + updatedAt: new Date(), + } + if (!rows[0]) { + await db.insert(systemSettings).values(patch as any) + } else { + await db.update(systemSettings).set(patch).where(eq(systemSettings.id, rows[0].id)) + } + try { + const cs = CacheService.getInstance() + await cs.setSystemSettings?.({ ...(rows[0] || {}), ...patch }) + } catch {} + + return { + ok: true, + bound: true, + user: profile, + hint: '绑定成功,可以直接去同步歌单了', + } +})