短信验证调试成功

This commit is contained in:
2026-07-06 13:18:56 +08:00
parent 5ba69eb935
commit 1c978b8adc
62 changed files with 2491 additions and 354 deletions
@@ -15,8 +15,11 @@ import { PrismaService } from '../../common/prisma/prisma.module';
import { RedisService } from '../../common/redis/redis.service';
import { SMS_PROVIDER, WECHAT_PROVIDER } from '../../integrations/integrations.constants';
import { ISmsProvider } from '../../integrations/sms/sms.interface';
import { SmsCodeStore } from '../../integrations/sms/sms-code.store';
import type { IWechatProvider } from '../../integrations/wechat/wechat.interface';
import { serializeBigInt } from '../../common/decorators/current-user.decorator';
import { AnalyticsService } from '../analytics/analytics.service';
import { UserAddressService } from './user-address.service';
import type { User } from '@prisma/client';
@@ -53,10 +56,33 @@ export class AuthService {
private readonly redis: RedisService,
@Inject(SMS_PROVIDER) private readonly smsProvider: ISmsProvider,
@Inject(WECHAT_PROVIDER) private readonly wechatProvider: IWechatProvider,
private readonly analyticsService: AnalyticsService,
private readonly smsCodeStore: SmsCodeStore,
private readonly userAddressService: UserAddressService,
) {}
private assertMobilePhone(phone: string) {
const trimmed = phone.trim();
if (!/^1[3-9]\d{9}$/.test(trimmed)) {
throw new BadRequestException('请输入正确的手机号码');
}
return trimmed;
}
async sendSms(phone: string, scene: string) {
await this.smsProvider.send(phone, scene);
const normalizedPhone = this.assertMobilePhone(phone);
if (!Object.values(SmsScene).includes(scene as SmsScene)) {
throw new BadRequestException('无效的验证码场景');
}
await this.smsCodeStore.assertSendCooldown(normalizedPhone);
try {
await this.smsProvider.send(normalizedPhone, scene);
await this.smsCodeStore.setSendCooldown(normalizedPhone);
} catch (err) {
if (err instanceof BadRequestException) throw err;
const message = err instanceof Error ? err.message : '短信发送失败';
throw new BadRequestException(message);
}
return { sent: true };
}
@@ -111,9 +137,10 @@ export class AuthService {
}
async loginUser(phone: string, code: string, clientApp: ClientApp, guestId?: bigint) {
await this.smsProvider.verify(phone, code, SmsScene.USER_LOGIN);
const normalizedPhone = this.assertMobilePhone(phone);
await this.smsProvider.verify(normalizedPhone, code, SmsScene.USER_LOGIN);
let user: UserRow | null = await this.prisma.user.findUnique({
where: { phone },
where: { phone: normalizedPhone },
include: { avatar: true },
});
@@ -125,9 +152,9 @@ export class AuthService {
user = await this.prisma.user.update({
where: { id: guestId },
data: {
phone,
phone: normalizedPhone,
phoneVerifiedAt: new Date(),
nickname: guest.nickname === '访客' ? `用户${phone.slice(-4)}` : guest.nickname,
nickname: guest.nickname === '访客' ? `用户${normalizedPhone.slice(-4)}` : guest.nickname,
},
include: { avatar: true },
});
@@ -139,10 +166,10 @@ export class AuthService {
if (!user) {
user = await this.prisma.user.create({
data: {
phone,
phone: normalizedPhone,
phoneVerifiedAt: new Date(),
userNo: generateUserNo(),
nickname: `用户${phone.slice(-4)}`,
nickname: `用户${normalizedPhone.slice(-4)}`,
cityPreference: {
create: {
selectedCityCode: '410100',
@@ -170,30 +197,40 @@ export class AuthService {
if (!user) throw new BadRequestException('登录失败');
this.analyticsService.trackOneSafe(user.id, clientApp, {
eventName: 'sms_login',
extraJson: { method: 'sms' },
});
this.analyticsService.trackOneSafe(user.id, clientApp, {
eventName: 'login_success',
extraJson: { method: 'sms' },
});
return this.buildSessionResponse(user, clientApp, user.deviceKey);
}
async bindPhone(actorId: bigint, phone: string, code: string, clientApp: ClientApp) {
await this.smsProvider.verify(phone, code, SmsScene.BIND_PHONE);
const normalizedPhone = this.assertMobilePhone(phone);
await this.smsProvider.verify(normalizedPhone, code, SmsScene.BIND_PHONE);
const guest = await this.assertActiveUser(actorId);
if (guest.phone && guest.phoneVerifiedAt) {
if (guest.phone === phone) {
if (guest.phone === normalizedPhone) {
return this.buildSessionResponse(guest, clientApp, guest.deviceKey);
}
throw new BadRequestException('当前账号已绑定其他手机号');
}
const existing = await this.prisma.user.findUnique({ where: { phone } });
const existing = await this.prisma.user.findUnique({ where: { phone: normalizedPhone } });
let targetUser: UserRow;
if (!existing) {
targetUser = await this.prisma.user.update({
where: { id: guest.id },
data: {
phone,
phone: normalizedPhone,
phoneVerifiedAt: new Date(),
nickname: guest.nickname === '访客' ? `用户${phone.slice(-4)}` : guest.nickname,
nickname: guest.nickname === '访客' ? `用户${normalizedPhone.slice(-4)}` : guest.nickname,
},
include: { avatar: true },
});
@@ -210,9 +247,10 @@ export class AuthService {
}
async loginStore(phone: string, code: string, clientApp: ClientApp) {
await this.smsProvider.verify(phone, code, SmsScene.STORE_LOGIN);
const normalizedPhone = this.assertMobilePhone(phone);
await this.smsProvider.verify(normalizedPhone, code, SmsScene.STORE_LOGIN);
const account = await this.prisma.storeAccount.findUnique({
where: { phone },
where: { phone: normalizedPhone },
include: { store: true },
});
if (!account) throw new BadRequestException('门店账号不存在');
@@ -230,9 +268,10 @@ export class AuthService {
}
async loginPartner(phone: string, code: string, clientApp: ClientApp) {
await this.smsProvider.verify(phone, code, SmsScene.PARTNER_LOGIN);
const normalizedPhone = this.assertMobilePhone(phone);
await this.smsProvider.verify(normalizedPhone, code, SmsScene.PARTNER_LOGIN);
const account = await this.prisma.partnerAccount.findUnique({
where: { phone },
where: { phone: normalizedPhone },
include: { partner: true },
});
if (!account) throw new BadRequestException('合伙人账号不存在');
@@ -251,8 +290,9 @@ export class AuthService {
}
async loginHq(phone: string, code: string, clientApp: ClientApp) {
await this.smsProvider.verify(phone, code, SmsScene.HQ_LOGIN);
const account = await this.prisma.hqAccount.findUnique({ where: { phone } });
const normalizedPhone = this.assertMobilePhone(phone);
await this.smsProvider.verify(normalizedPhone, code, SmsScene.HQ_LOGIN);
const account = await this.prisma.hqAccount.findUnique({ where: { phone: normalizedPhone } });
if (!account) throw new BadRequestException('HQ账号不存在');
if (account.status !== 'ACTIVE') throw new BadRequestException('账号已停用');
await this.prisma.hqAccount.update({
@@ -331,6 +371,14 @@ export class AuthService {
},
include: { avatar: true },
});
this.analyticsService.trackOneSafe(activeUser.id, clientApp, {
eventName: 'wechat_login',
extraJson: { platform },
});
this.analyticsService.trackOneSafe(activeUser.id, clientApp, {
eventName: 'login_success',
extraJson: { method: 'wechat', platform },
});
return this.buildSessionResponse(activeUser, clientApp, activeUser.deviceKey);
}
@@ -460,6 +508,14 @@ export class AuthService {
if (!targetUserId) throw new BadRequestException('绑定失败');
const user = await this.assertActiveUser(targetUserId);
await this.redis.del(`wx:session:${wxSessionKey}`);
this.analyticsService.trackOneSafe(user.id, clientApp, {
eventName: 'wechat_phone',
extraJson: { method: 'bind_phone' },
});
this.analyticsService.trackOneSafe(user.id, clientApp, {
eventName: 'login_success',
extraJson: { method: 'wechat_bind' },
});
return this.buildSessionResponse(user, clientApp, user.deviceKey);
}
@@ -646,6 +702,7 @@ export class AuthService {
});
});
await this.userAddressService.normalizeDefaultAddress(primaryId);
return this.assertActiveUser(primaryId);
}