// 与同目录/相邻的认证接口保持一致:db / eq / and / 表 都从 ~/drizzle/db 取 import { db, eq, and, userIdentities } from '~/drizzle/db' import { verifyBindingToken } from '~~/server/utils/oauth-token' /** * 给**当前已登录的账号**绑定第三方身份(目前是微信)—— 一键绑定,不用再填姓名密码。 * * 为什么需要它:原来的绑定只能走 `POST /api/auth/bind`,那个接口是"用姓名 + 密码 * 验证身份后再绑定"(因为它是**未登录**场景:微信扫出来的是个陌生身份,得先确认绑到谁头上)。 * 但用户**已经登录**时再让他输一遍账号密码就很别扭了 —— 站长也是这么反馈的: * 「登陆进去的账号绑定微信就不用再写账号信息了吧」。 * * 这个接口用 `event.context.user`(登录态)直接落库: * · 必须先登录; * · body.bindToken 是刚才 wx.login 换来的第三方身份令牌(未绑定过的才算); * · 该身份已经绑给别人 → 409(不许抢绑); * · 已经绑给**自己** → 直接当成功(幂等,重复点不报错)。 */ export default defineEventHandler(async (event) => { const user = event.context.user if (!user) { throw createError({ statusCode: 401, message: '未登录' }) } const body = await readBody(event) const bindToken = String(body?.bindToken || '').trim() if (!bindToken) { throw createError({ statusCode: 400, message: '缺少绑定令牌,请重新用微信一键登录后再试' }) } let payload: { provider?: string; providerUserId?: string; providerUsername?: string } try { payload = verifyBindingToken(bindToken) } catch (e) { throw createError({ statusCode: 400, message: '绑定令牌无效或已过期,请重新发起绑定' }) } if (!payload?.provider || !payload?.providerUserId) { throw createError({ statusCode: 400, message: '绑定令牌内容不完整,请重新发起绑定' }) } // 这个第三方身份是不是已经绑给谁了? const existing = await db .select({ id: userIdentities.id, userId: userIdentities.userId }) .from(userIdentities) .where( and( eq(userIdentities.provider, payload.provider), eq(userIdentities.providerUserId, payload.providerUserId) ) ) .limit(1) if (existing.length > 0) { if (existing[0].userId === user.id) { return { success: true, alreadyMine: true, message: '这个微信已经绑定在你的账号上了' } } throw createError({ statusCode: 409, message: '这个微信已经绑定在其它账号上了,请先在那边解绑' }) } try { await db.insert(userIdentities).values({ userId: user.id, provider: payload.provider, providerUserId: payload.providerUserId, providerUsername: payload.providerUsername || null }) } catch (e: any) { if (e?.code === '23505') { // 并发下被抢先插入:再查一次,若属于自己就算成功 const again = await db .select({ userId: userIdentities.userId }) .from(userIdentities) .where( and( eq(userIdentities.provider, payload.provider), eq(userIdentities.providerUserId, payload.providerUserId) ) ) .limit(1) if (again.length > 0 && again[0].userId === user.id) { return { success: true, alreadyMine: true, message: '这个微信已经绑定在你的账号上了' } } throw createError({ statusCode: 409, message: '这个微信已经绑定在其它账号上了,请先在那边解绑' }) } throw e } return { success: true, message: '微信绑定成功' } })