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: '绑定成功,可以直接去同步歌单了', + } +})