微信授权逻辑修改
This commit is contained in:
@@ -28,6 +28,7 @@ type WxSessionPayload = {
|
||||
openId: string;
|
||||
unionId?: string;
|
||||
sessionKey?: string;
|
||||
accessToken?: string;
|
||||
clientApp: ClientApp;
|
||||
guestId?: string;
|
||||
};
|
||||
@@ -512,6 +513,10 @@ export class AuthService {
|
||||
},
|
||||
include: { avatar: true },
|
||||
});
|
||||
if (session.accessToken) {
|
||||
const synced = await this.syncWechatUserProfile(activeUser.id, session.accessToken, session.openId);
|
||||
if (synced) activeUser = synced as UserRow;
|
||||
}
|
||||
this.analyticsService.trackOneSafe(activeUser.id, clientApp, {
|
||||
eventName: 'wechat_login',
|
||||
extraJson: { platform },
|
||||
@@ -523,6 +528,18 @@ export class AuthService {
|
||||
return this.buildSessionResponse(activeUser, clientApp, activeUser.deviceKey);
|
||||
}
|
||||
|
||||
if (guestId) {
|
||||
try {
|
||||
const guest = await this.assertActiveUser(guestId);
|
||||
if (guest.phoneVerifiedAt && !guest.wxOpenId) {
|
||||
const activeUser = await this.attachWechatToUser(guest.id, session, clientApp, platform);
|
||||
return this.buildSessionResponse(activeUser, clientApp, activeUser.deviceKey);
|
||||
}
|
||||
} catch (e) {
|
||||
if (e instanceof BadRequestException) throw e;
|
||||
}
|
||||
}
|
||||
|
||||
const wxSessionKey = randomUUID();
|
||||
await this.redis.setJson(
|
||||
`wx:session:${wxSessionKey}`,
|
||||
@@ -530,6 +547,7 @@ export class AuthService {
|
||||
openId: session.openId,
|
||||
unionId: session.unionId,
|
||||
sessionKey: 'sessionKey' in session ? session.sessionKey : undefined,
|
||||
accessToken: session.accessToken,
|
||||
clientApp,
|
||||
guestId: guestId?.toString(),
|
||||
} satisfies WxSessionPayload,
|
||||
@@ -647,6 +665,9 @@ export class AuthService {
|
||||
}
|
||||
|
||||
if (!targetUserId) throw new BadRequestException('绑定失败');
|
||||
if (wxSession.accessToken) {
|
||||
await this.syncWechatUserProfile(targetUserId, wxSession.accessToken, wxSession.openId);
|
||||
}
|
||||
const user = await this.assertActiveUser(targetUserId);
|
||||
await this.redis.del(`wx:session:${wxSessionKey}`);
|
||||
this.trackSmsUserEvent(user.id, clientApp, 'bind_phone', {
|
||||
@@ -664,6 +685,53 @@ export class AuthService {
|
||||
return this.buildSessionResponse(user, clientApp, user.deviceKey);
|
||||
}
|
||||
|
||||
async bindUserWechat(
|
||||
userId: bigint,
|
||||
input: { code?: string; wxSessionKey?: string },
|
||||
clientApp: ClientApp,
|
||||
platform: 'h5' | 'mini' = 'h5',
|
||||
) {
|
||||
this.assertWechatEnabled();
|
||||
if (!input.code && !input.wxSessionKey) {
|
||||
throw new BadRequestException('请提供微信授权 code 或会话');
|
||||
}
|
||||
|
||||
let openId: string;
|
||||
let unionId: string | undefined;
|
||||
let accessToken: string | undefined;
|
||||
const actorRef = { refType: 'USER', refId: userId };
|
||||
|
||||
if (input.code) {
|
||||
const session =
|
||||
platform === 'mini'
|
||||
? await this.wechatProvider.code2Session(input.code, actorRef)
|
||||
: await this.wechatProvider.oauth2AccessToken(input.code, actorRef);
|
||||
openId = session.openId;
|
||||
unionId = session.unionId;
|
||||
accessToken = session.accessToken;
|
||||
} else {
|
||||
const wxSession = await this.redis.getJson<WxSessionPayload>(`wx:session:${input.wxSessionKey}`);
|
||||
if (!wxSession) throw new BadRequestException('微信会话已过期,请重新授权');
|
||||
openId = wxSession.openId;
|
||||
unionId = wxSession.unionId;
|
||||
accessToken = wxSession.accessToken;
|
||||
await this.redis.del(`wx:session:${input.wxSessionKey}`);
|
||||
}
|
||||
|
||||
const user = await this.attachWechatToUser(
|
||||
userId,
|
||||
{ openId, unionId, accessToken },
|
||||
clientApp,
|
||||
platform,
|
||||
{ skipLoginEvents: true },
|
||||
);
|
||||
this.analyticsService.trackOneSafe(userId, clientApp, {
|
||||
eventName: 'wechat_bind',
|
||||
extraJson: { platform },
|
||||
});
|
||||
return this.buildSessionResponse(user, clientApp, user.deviceKey);
|
||||
}
|
||||
|
||||
async loginStoreWechat(code: string, clientApp: ClientApp, platform: 'h5' | 'mini' = 'h5') {
|
||||
this.assertWechatEnabled();
|
||||
const session =
|
||||
@@ -871,6 +939,113 @@ export class AuthService {
|
||||
return this.issueToken('USER', user.id, clientApp, phoneVerified, this.formatUserProfile(user), undefined, undefined, deviceKey);
|
||||
}
|
||||
|
||||
private isDefaultNickname(nickname: string | null | undefined) {
|
||||
if (!nickname || nickname === '访客') return true;
|
||||
return /^用户\d{4}$/.test(nickname);
|
||||
}
|
||||
|
||||
private async syncWechatUserProfile(
|
||||
userId: bigint,
|
||||
accessToken: string,
|
||||
openId: string,
|
||||
): Promise<UserRow | null> {
|
||||
try {
|
||||
const info = await this.wechatProvider.fetchOAuthUserInfo(accessToken, openId, {
|
||||
refType: 'USER',
|
||||
refId: userId,
|
||||
});
|
||||
const current = await this.prisma.user.findUnique({
|
||||
where: { id: userId },
|
||||
include: { avatar: true },
|
||||
});
|
||||
if (!current) return null;
|
||||
|
||||
const data: {
|
||||
nickname?: string;
|
||||
avatarResourceId?: bigint;
|
||||
} = {};
|
||||
|
||||
if (info.nickname && this.isDefaultNickname(current.nickname)) {
|
||||
data.nickname = info.nickname;
|
||||
}
|
||||
|
||||
if (info.headImgUrl && !current.avatarResourceId) {
|
||||
const avatar = await this.prisma.commonResource.create({
|
||||
data: {
|
||||
ownerType: 'USER',
|
||||
ownerId: userId,
|
||||
bizType: 'AVATAR',
|
||||
mediaType: 'IMAGE',
|
||||
ossBucket: 'wechat',
|
||||
ossKey: `wx-avatar/${openId}`,
|
||||
url: info.headImgUrl,
|
||||
status: 'ACTIVE',
|
||||
},
|
||||
});
|
||||
data.avatarResourceId = avatar.id;
|
||||
}
|
||||
|
||||
if (!data.nickname && !data.avatarResourceId) {
|
||||
return current as UserRow;
|
||||
}
|
||||
|
||||
return this.prisma.user.update({
|
||||
where: { id: userId },
|
||||
data,
|
||||
include: { avatar: true },
|
||||
});
|
||||
} catch {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
private async attachWechatToUser(
|
||||
userId: bigint,
|
||||
session: { openId: string; unionId?: string; accessToken?: string },
|
||||
clientApp: ClientApp,
|
||||
platform: string,
|
||||
options?: { skipLoginEvents?: boolean },
|
||||
): Promise<UserRow> {
|
||||
const conflict = await this.prisma.user.findFirst({
|
||||
where: {
|
||||
wxOpenId: session.openId,
|
||||
id: { not: userId },
|
||||
status: 1,
|
||||
mergedIntoUserId: null,
|
||||
},
|
||||
});
|
||||
if (conflict) {
|
||||
throw new BadRequestException('该微信已绑定其他账号');
|
||||
}
|
||||
|
||||
let user = (await this.prisma.user.update({
|
||||
where: { id: userId },
|
||||
data: {
|
||||
wxOpenId: session.openId,
|
||||
wxUnionId: session.unionId ?? undefined,
|
||||
},
|
||||
include: { avatar: true },
|
||||
})) as UserRow;
|
||||
|
||||
if (session.accessToken) {
|
||||
const synced = await this.syncWechatUserProfile(userId, session.accessToken, session.openId);
|
||||
if (synced) user = synced as UserRow;
|
||||
}
|
||||
|
||||
if (!options?.skipLoginEvents) {
|
||||
this.analyticsService.trackOneSafe(userId, clientApp, {
|
||||
eventName: 'wechat_login',
|
||||
extraJson: { platform },
|
||||
});
|
||||
this.analyticsService.trackOneSafe(userId, clientApp, {
|
||||
eventName: 'login_success',
|
||||
extraJson: { method: 'wechat', platform },
|
||||
});
|
||||
}
|
||||
|
||||
return user;
|
||||
}
|
||||
|
||||
private formatUserProfile(user: UserRow) {
|
||||
return {
|
||||
id: user.id.toString(),
|
||||
|
||||
Reference in New Issue
Block a user