feat(kugou): 支持粘贴浏览器 Cookie 绑定酷狗(不再必须扫码)
站长要求去掉扫码:扫码必须拿另一台装了酷狗 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 保留未删(网页版可能还在用)。
This commit is contained in:
@@ -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<string, any> = {
|
||||||
|
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: '绑定成功,可以直接去同步歌单了',
|
||||||
|
}
|
||||||
|
})
|
||||||
Reference in New Issue
Block a user