diff --git a/server/api/auth/wechat/bind-self.post.ts b/server/api/auth/wechat/bind-self.post.ts new file mode 100644 index 0000000..59e6506 --- /dev/null +++ b/server/api/auth/wechat/bind-self.post.ts @@ -0,0 +1,92 @@ +// 与同目录/相邻的认证接口保持一致: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: '微信绑定成功' } +})