From 70517058ae0519ae1ad0d982a516e7885edf532f Mon Sep 17 00:00:00 2001 From: tone Date: Tue, 16 Dec 2025 22:48:51 +0800 Subject: [PATCH] =?UTF-8?q?feat:=20=E5=90=8E=E7=AB=AF=E8=B0=83=E6=95=B4?= =?UTF-8?q?=E7=99=BB=E9=99=86=E9=80=BB=E8=BE=91?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- apps/backend/src/auth/auth.controller.ts | 81 +++++++++++++---- apps/backend/src/auth/auth.service.ts | 89 ++++++------------- apps/backend/src/auth/dto/login.dto.ts | 50 ++++++----- .../src/auth/strategies/jwt.strategy.ts | 11 ++- apps/backend/src/blog/blog.controller.ts | 4 +- .../src/common/constants/error-codes.ts | 46 ++++++++++ .../src/notification/notification.service.ts | 59 ++++++------ .../src/user/entities/user-session.entity.ts | 10 +-- apps/backend/src/user/entities/user.entity.ts | 10 +++ .../src/user/services/user-session.service.ts | 46 +++++----- apps/backend/src/user/user.service.ts | 48 ++++++++-- .../verification/verification.controller.ts | 38 ++++---- .../src/verification/verification.service.ts | 7 +- 13 files changed, 305 insertions(+), 194 deletions(-) create mode 100644 apps/backend/src/common/constants/error-codes.ts diff --git a/apps/backend/src/auth/auth.controller.ts b/apps/backend/src/auth/auth.controller.ts index 05cf354..5a84b27 100644 --- a/apps/backend/src/auth/auth.controller.ts +++ b/apps/backend/src/auth/auth.controller.ts @@ -2,37 +2,86 @@ import { BadRequestException, Body, Controller, + NotImplementedException, Post, Request, + Res, UseGuards, } from '@nestjs/common'; -import { LoginDto } from './dto/login.dto'; +import { LoginByPasswordDto } from './dto/login.dto'; import { AuthService } from './auth.service'; import { AuthGuard } from '@nestjs/passport'; import { UserSessionService } from 'src/user/services/user-session.service'; import { Throttle, ThrottlerGuard } from '@nestjs/throttler'; +import { Response } from 'express'; +import { UserService } from 'src/user/user.service'; @Controller('auth') export class AuthController { constructor( private readonly authService: AuthService, + private readonly userService: UserService, private readonly userSessionService: UserSessionService, - ) {} + ) { } - @Post('login') - @UseGuards(ThrottlerGuard) - @Throttle({ default: { limit: 20, ttl: 60000 } }) - async login(@Body() loginDto: LoginDto) { - switch (loginDto.type) { - case 'password': - return this.authService.loginWithPassword(loginDto); - case 'phone': - return this.authService.loginWithPhone(loginDto); - case 'email': - return this.authService.loginWithEmail(loginDto); - default: - throw new BadRequestException('服务器错误'); - } + // @Post('login') + // @UseGuards(ThrottlerGuard) + // @Throttle({ default: { limit: 20, ttl: 60000 } }) + // async login(@Body() loginDto: LoginDto) { + // switch (loginDto.type) { + // case 'password': + // return this.authService.loginWithPassword(loginDto); + // case 'phone': + // return this.authService.loginWithPhone(loginDto); + // case 'email': + // return this.authService.loginWithEmail(loginDto); + // default: + // throw new BadRequestException('服务器错误'); + // } + // } + + private setUserToken(res: Response, token: string) { + res.cookie('token', token, { + httpOnly: true, + secure: process.env.NODE_ENV === 'production', + sameSite: 'lax', + // 永不过期,不用设置maxAge + path: '/', + }) + } + + @Post('login/password') + async loginByPassword( + @Body() loginDto: LoginByPasswordDto, + @Res({ passthrough: true }) res: Response, + ) { + const { identifier, password } = loginDto; + const loginRes = await this.authService.loginWithPassword(identifier, password); + const { userId, token } = loginRes; + this.setUserToken(res, token); + return { + user: await this.userService.findById(userId), + }; + } + + @Post('sms/send') + async sendSms() { + throw new NotImplementedException(); + } + + @Post('login/sms') + async loginBySms() { + throw new NotImplementedException(); + } + + @Post('passkey/login/options') + async loginByPasskeyOptions() { + throw new NotImplementedException(); + } + + @Post('passkey/login') + async loginByPasskey() { + throw new NotImplementedException(); } @UseGuards(AuthGuard('jwt')) diff --git a/apps/backend/src/auth/auth.service.ts b/apps/backend/src/auth/auth.service.ts index 0d6ca0b..71cf3fc 100644 --- a/apps/backend/src/auth/auth.service.ts +++ b/apps/backend/src/auth/auth.service.ts @@ -1,12 +1,12 @@ import { createHash } from 'crypto'; import { BadRequestException, Injectable } from '@nestjs/common'; -import { LoginDto } from './dto/login.dto'; import { UserService } from 'src/user/user.service'; import { User } from 'src/user/entities/user.entity'; import { JwtService } from '@nestjs/jwt'; import { UserSessionService } from 'src/user/services/user-session.service'; -import { v4 as uuidv4 } from 'uuid'; import { VerificationService } from 'src/verification/verification.service'; +import { BusinessException } from 'src/common/exceptions/business.exception'; +import { ErrorCode } from 'src/common/constants/error-codes'; @Injectable() export class AuthService { @@ -15,40 +15,49 @@ export class AuthService { private readonly jwtService: JwtService, private readonly userSessionService: UserSessionService, private readonly verificationService: VerificationService, - ) {} + ) { } - async loginWithPassword(loginDto: LoginDto) { - const { account, password } = loginDto; - // 依次使用邮箱登录、手机号、账号 + async loginWithPassword(identifier: string, password: string) { + // 依次使用邮箱、手机号、账号登陆(防止有大聪明给账号改成别人的邮箱或手机号) const user = await this.userService.findOne( - [{ email: account }, { phone: account }, { username: account }], + [{ email: identifier }, { phone: identifier }, { username: identifier }], { withDeleted: true, }, ); if (user && user.deletedAt !== null) { - throw new BadRequestException('该账号注销中'); + throw new BusinessException({ + message: '该账号注销中', + code: ErrorCode.USER_ACCOUNT_DEACTIVATED, + }); } if (user === null || !user.password_hash || !user.salt) { - throw new BadRequestException('账户或密码错误'); + throw new BusinessException({ + message: '账户或密码错误', + code: ErrorCode.AUTH_INVALID_CREDENTIALS + }); } // 判断密码是否正确 const hashedPassword = this.hashPassword(password, user.salt); if (hashedPassword !== user.password_hash) { - throw new BadRequestException('账户或密码错误'); + throw new BusinessException({ + message: '账户或密码错误', + code: ErrorCode.AUTH_INVALID_CREDENTIALS + }); } // 登录成功,颁发token return { token: await this.generateToken(user), + userId: user.userId, }; } - async loginWithPhone(loginDto: LoginDto) { - const { phone, code } = loginDto; + async loginWithPhone(data: { phone: string; code: string; }) { + const { phone, code } = data; // 先判断验证码是否正确 const isValid = this.verificationService.verifyPhoneCode( phone, @@ -90,65 +99,21 @@ export class AuthService { }; } - async loginWithEmail(loginDto: LoginDto) { - const { email, code } = loginDto; - // 先判断验证码是否正确 - const isValid = this.verificationService.verifyEmailCode( - email, - code, - 'login', - ); - switch (isValid) { - case 0: - break; - case -1: - throw new BadRequestException('验证码已过期,请重新获取'); - case -2: - throw new BadRequestException('验证码错误'); - case -3: - throw new BadRequestException('验证码已失效,请重新获取'); - default: - throw new BadRequestException('验证码错误,请稍后再试'); - } - - // 判断用户是否存在,若不存在则进行注册 - let user = await this.userService.findOne({ email }, { withDeleted: true }); - if (user && user.deletedAt !== null) { - throw new BadRequestException('该账号注销中,请使用其他邮箱'); - } - - if (!user) { - // 执行注册操作 - user = await this.userService.create({ email: email }); - } - - if (!user || !user.userId) { - // 注册失败或用户信息错误 - throw new BadRequestException('请求失败,请稍后再试'); - } - - // 登录,颁发token - return { - token: await this.generateToken(user), - }; - } - private hashPassword(password: string, salt: string): string { return createHash('sha256').update(`${password}${salt}`).digest('hex'); } private async generateToken(user: User) { + // 存储 + const sessionRes = await this.userSessionService.createSession( + user.userId, + ); + const payload = { userId: user.userId, - sessionId: uuidv4(), + sessionId: sessionRes.sessionId, }; - // 存储 - await this.userSessionService.createSession( - payload.userId, - payload.sessionId, - ); - // 颁发token return this.jwtService.sign(payload); } diff --git a/apps/backend/src/auth/dto/login.dto.ts b/apps/backend/src/auth/dto/login.dto.ts index a97b4b6..d8c5efb 100644 --- a/apps/backend/src/auth/dto/login.dto.ts +++ b/apps/backend/src/auth/dto/login.dto.ts @@ -1,31 +1,37 @@ import { IsEnum, IsString, Length, ValidateIf } from 'class-validator'; -export class LoginDto { - @IsEnum(['password', 'phone', 'email'], { message: '请求类型错误' }) - type: 'password' | 'phone' | 'email'; +// export class LoginDto { +// @IsEnum(['password', 'phone', 'email'], { message: '请求类型错误' }) +// type: 'password' | 'phone' | 'email'; - @ValidateIf((o) => o.type === 'password') +// @ValidateIf((o) => o.type === 'password') + +// account?: string; + + + +// @ValidateIf((o) => o.type === 'phone') +// @IsString({ message: '手机号必须输入' }) +// @Length(11, 11, { message: '手机号异常' }) // 中国大陆,11位数字 +// phone?: string; + +// @ValidateIf((o) => o.type === 'email') +// @IsString({ message: '邮箱必须输入' }) +// @Length(6, 254, { message: '邮箱异常' }) // RFC 5321 +// email?: string; + +// @ValidateIf((o) => o.type === 'phone' || o.type === 'email') +// @IsString({ message: '验证码必须输入' }) +// @Length(6, 6, { message: '验证码异常' }) // 6位数字 +// code?: string; +// } + +export class LoginByPasswordDto { @IsString({ message: '账户必须输入' }) @Length(1, 254, { message: '账户异常' }) // 用户名、邮箱、手机号 - account?: string; + identifier: string; - @ValidateIf((o) => o.type === 'password') @IsString({ message: '密码必须输入' }) @Length(6, 32, { message: '密码异常' }) // 6-32位 - password?: string; - - @ValidateIf((o) => o.type === 'phone') - @IsString({ message: '手机号必须输入' }) - @Length(11, 11, { message: '手机号异常' }) // 中国大陆,11位数字 - phone?: string; - - @ValidateIf((o) => o.type === 'email') - @IsString({ message: '邮箱必须输入' }) - @Length(6, 254, { message: '邮箱异常' }) // RFC 5321 - email?: string; - - @ValidateIf((o) => o.type === 'phone' || o.type === 'email') - @IsString({ message: '验证码必须输入' }) - @Length(6, 6, { message: '验证码异常' }) // 6位数字 - code?: string; + password: string; } diff --git a/apps/backend/src/auth/strategies/jwt.strategy.ts b/apps/backend/src/auth/strategies/jwt.strategy.ts index 99c4845..6ea59eb 100644 --- a/apps/backend/src/auth/strategies/jwt.strategy.ts +++ b/apps/backend/src/auth/strategies/jwt.strategy.ts @@ -26,15 +26,14 @@ export class JwtStrategy extends PassportStrategy(Strategy, 'jwt') { async validate(payload: any) { const { userId, sessionId } = payload ?? {}; - const isValidSession = await this.userSessionService.isSessionValid( + await this.userSessionService.isSessionValid( userId, sessionId, - ); - if (!isValidSession) { - throw new UnauthorizedException('登录凭证已过期,请重新登录'); - } + ).catch((e) => { + throw new UnauthorizedException(`${e}`); + }); - const user = await this.userService.findById(userId); + const user = await this.userService.findOne({ userId }); if (!user) { throw new BadRequestException('用户不存在'); } diff --git a/apps/backend/src/blog/blog.controller.ts b/apps/backend/src/blog/blog.controller.ts index b94ad09..32e4b51 100644 --- a/apps/backend/src/blog/blog.controller.ts +++ b/apps/backend/src/blog/blog.controller.ts @@ -22,7 +22,7 @@ export class BlogController { constructor( private readonly blogService: BlogService, private readonly userService: UserService, - ) {} + ) { } @Get() getBlogs() { @@ -100,7 +100,7 @@ export class BlogController { throw new BadRequestException('作者关闭了该文章的评论功能'); } - const user = userId ? await this.userService.findById(userId) : null; + const user = userId ? await this.userService.findOne({ userId }) : null; const ip = req.headers['x-forwarded-for'] || req.ip; // 获取IP归属地 diff --git a/apps/backend/src/common/constants/error-codes.ts b/apps/backend/src/common/constants/error-codes.ts new file mode 100644 index 0000000..c307109 --- /dev/null +++ b/apps/backend/src/common/constants/error-codes.ts @@ -0,0 +1,46 @@ +/** + * 全局业务错误码规范: + * - 每个模块分配一个 1000 起始的段(如 USER: -1000~1999, AUTH: -2000~2999) + * - 代码结构:{ 模块名大写 }_{ 错误语义 } + */ + +export const ErrorCode = { + // 通用错误(0 ~ 999) + COMMON_INTERNAL_ERROR: -1, + COMMON_INVALID_PARAM: -2, + COMMON_NOT_FOUND: -3, + + // 用户模块(1000 ~ 1999) + USER_NOT_FOUND: -1001, + USER_ALREADY_EXISTS: -1002, + USER_ACCOUNT_DISABLED: -1003, + USER_FIND_OPTIONS_EMPTY: -1004, + USER_ACCOUNT_DEACTIVATED: -1005, + + // 认证模块(2000 ~ 2999) + AUTH_INVALID_CREDENTIALS: -2001, + AUTH_SMS_CODE_EXPIRED: -2002, + AUTH_SMS_CODE_INCORRECT: -2003, + AUTH_PASSKEY_NOT_REGISTERED: -2004, + AUTH_SESSION_EXPIRED: -2005, + + // 博客模块(3000 ~ 3999) + BLOG_NOT_FOUND: -3001, + BLOG_PERMISSION_DENIED: -3002, + + // 验证模块(4000 ~ 4999) + VERIFICATION_CODE_EXPIRED: -4001, + VERIFICATION_CODE_INCORRECT: -4002, + + // 通知模块(5000 ~ 5999) + NOTIFICATION_SEND_FAILED: -5001, + + // 资源模块(6000 ~ 6999) + RESOURCE_UPLOAD_FAILED: -6001, + RESOURCE_NOT_FOUND: -6002, + + // 管理员模块(7000 ~ 7999) + ADMIN_FORBIDDEN: -7001, +} as const; + +export type ErrorCodeType = typeof ErrorCode[keyof typeof ErrorCode]; \ No newline at end of file diff --git a/apps/backend/src/notification/notification.service.ts b/apps/backend/src/notification/notification.service.ts index c177643..d2c465c 100644 --- a/apps/backend/src/notification/notification.service.ts +++ b/apps/backend/src/notification/notification.service.ts @@ -8,18 +8,18 @@ import Credential, { Config } from '@alicloud/credentials'; @Injectable() export class NotificationService { - private dm: Dm20151123; + // private dm: Dm20151123; constructor() { - const credentialsConfig = new Config({ - type: 'access_key', - accessKeyId: process.env.ALIYUN_ACCESS_KEY_ID, - accessKeySecret: process.env.ALIYUN_ACCESS_KEY_SECRET, - }); - const credential = new Credential(credentialsConfig); - const config = new $OpenApi.Config({ credential }); - config.endpoint = 'dm.aliyuncs.com'; - this.dm = new Dm20151123(config); + // const credentialsConfig = new Config({ + // type: 'access_key', + // accessKeyId: process.env.ALIYUN_ACCESS_KEY_ID, + // accessKeySecret: process.env.ALIYUN_ACCESS_KEY_SECRET, + // }); + // const credential = new Credential(credentialsConfig); + // const config = new $OpenApi.Config({ credential }); + // config.endpoint = 'dm.aliyuncs.com'; + // this.dm = new Dm20151123(config); } private getMailHtmlBody(option: { type: 'login-verify'; code: string }) { @@ -86,27 +86,28 @@ export class NotificationService { targetMail: string; code: string; }) { - const runtime = new $Util.RuntimeOptions({}); + // const runtime = new $Util.RuntimeOptions({}); - const singleSendMailRequest = new $Dm20151123.SingleSendMailRequest({ - accountName: 'security@tonesc.cn', - addressType: 1, - replyToAddress: false, - toAddress: `${option.targetMail}`, - subject: '【特恩的日志】登陆验证码', - htmlBody: this.getMailHtmlBody({ - type: 'login-verify', - code: option.code, - }), - textBody: '', - }); + // const singleSendMailRequest = new $Dm20151123.SingleSendMailRequest({ + // accountName: 'security@tonesc.cn', + // addressType: 1, + // replyToAddress: false, + // toAddress: `${option.targetMail}`, + // subject: '【特恩的日志】登陆验证码', + // htmlBody: this.getMailHtmlBody({ + // type: 'login-verify', + // code: option.code, + // }), + // textBody: '', + // }); - try { - await this.dm.singleSendMailWithOptions(singleSendMailRequest, runtime); - } catch (error) { - console.error(error); - throw new BadRequestException('邮件发送失败'); - } + // try { + // await this.dm.singleSendMailWithOptions(singleSendMailRequest, runtime); + // } catch (error) { + // console.error(error); + // throw new BadRequestException('邮件发送失败'); + // } + throw new Error('not implement') } /** diff --git a/apps/backend/src/user/entities/user-session.entity.ts b/apps/backend/src/user/entities/user-session.entity.ts index 121c31f..8f263a6 100644 --- a/apps/backend/src/user/entities/user-session.entity.ts +++ b/apps/backend/src/user/entities/user-session.entity.ts @@ -11,21 +11,17 @@ import { @Index(['sessionId', 'userId']) export class UserSession { @PrimaryGeneratedColumn('uuid') - id: string; - - @Column({ length: 36 }) sessionId: string; @Column({ length: 36 }) userId: string; + @Column({ nullable: true }) + disabledReason?: string; + @CreateDateColumn({ precision: 3 }) createdAt: Date; @DeleteDateColumn({ nullable: true, precision: 3 }) deletedAt: Date; } - -/** - * 考虑是否使用sessionId代替id,以节省存储空间 - */ diff --git a/apps/backend/src/user/entities/user.entity.ts b/apps/backend/src/user/entities/user.entity.ts index 1b5e6d7..3a9df52 100644 --- a/apps/backend/src/user/entities/user.entity.ts +++ b/apps/backend/src/user/entities/user.entity.ts @@ -95,3 +95,13 @@ export class User { @Column({ type: 'jsonb', default: [] }) roles: RoleItem[]; } + +export class UserPublicProfile { + userId: string; + nickname: string; + avatar: string | null; + email: string | null; + phone: string | null; + roles: RoleItem[]; + createdAt: Date; +} diff --git a/apps/backend/src/user/services/user-session.service.ts b/apps/backend/src/user/services/user-session.service.ts index e48cb61..b61d855 100644 --- a/apps/backend/src/user/services/user-session.service.ts +++ b/apps/backend/src/user/services/user-session.service.ts @@ -8,39 +8,45 @@ export class UserSessionService { constructor( @InjectRepository(UserSession) private readonly userSessionRepository: Repository, - ) {} + ) { } - async createSession(userId: string, sessionId: string): Promise { + async createSession(userId: string): Promise { const session = this.userSessionRepository.create({ userId, - sessionId, }); - return await this.userSessionRepository.save(session); + return this.userSessionRepository.save(session); } - async isSessionValid(userId: string, sessionId: string): Promise { + /** + * @throws string 无效原因 + */ + async isSessionValid(userId: string, sessionId: string): Promise { const session = await this.userSessionRepository.findOne({ where: { userId, sessionId, - deletedAt: null, }, + withDeleted: true, }); - return !!session; - } - - async invalidateSession(userId: string, sessionId: string): Promise { - const session = await this.userSessionRepository.findOne({ - where: { - userId, - sessionId, - deletedAt: null, - }, - }); - - if (session) { - await this.userSessionRepository.softDelete(session.id); + if (session === null) { + throw '登陆凭证无效'; } + + if (session.deletedAt !== null) { + throw session.disabledReason || '登陆凭证无效'; + } + + return null; + } + + async invalidateSession(userId: string, sessionId: string, reason?: string): Promise { + await this.userSessionRepository.update( + { userId, sessionId, deletedAt: null }, + { + deletedAt: new Date(), + disabledReason: reason || null, + } + ) } } diff --git a/apps/backend/src/user/user.service.ts b/apps/backend/src/user/user.service.ts index 7d500ef..0136f23 100644 --- a/apps/backend/src/user/user.service.ts +++ b/apps/backend/src/user/user.service.ts @@ -2,12 +2,14 @@ import { BadRequestException, ConflictException, Injectable, + InternalServerErrorException, } from '@nestjs/common'; import { InjectRepository } from '@nestjs/typeorm'; -import { User } from './entities/user.entity'; +import { User, UserPublicProfile } from './entities/user.entity'; import { QueryFailedError, Repository } from 'typeorm'; import { createHash } from 'crypto'; import { v4 as uuid } from 'uuid'; +import { BusinessException } from 'src/common/exceptions/business.exception'; type UserFindOptions = Partial< Pick @@ -18,26 +20,54 @@ export class UserService { constructor( @InjectRepository(User) private readonly userRepository: Repository, - ) {} + ) { } - /** - * @deprecated 尽量不使用该方法 - */ async findOne( options: UserFindOptions | UserFindOptions[], additionalOptions?: { withDeleted?: boolean }, ): Promise { - if (Object.keys(options).length === 0) { - throw new BadRequestException('查询条件不能为空'); + if (Array.isArray(options)) { + if (options.length === 0) { + throw new BusinessException({ + message: '查询条件不能为空', + }); + } + + const users = await this.userRepository.find({ + where: options, + withDeleted: additionalOptions?.withDeleted ?? false, + take: 1, + }); + return users[0] || null; } + + if (!options || typeof options !== 'object' || Object.keys(options).length === 0) { + throw new BusinessException({ + message: '查询条件不能为空', + }); + } + return this.userRepository.findOne({ where: options, - withDeleted: additionalOptions?.withDeleted || false, + withDeleted: additionalOptions?.withDeleted ?? false, }); } - async findById(userId: string): Promise { + /** + * 仅包含用户可见字段 + */ + async findById(userId: string): Promise { return this.userRepository.findOne({ + select: { + avatar: true, + createdAt: true, + email: true, + nickname: true, + username: true, + phone: true, + roles: true, + userId: true, + }, where: { userId, }, diff --git a/apps/backend/src/verification/verification.controller.ts b/apps/backend/src/verification/verification.controller.ts index e54e421..0619232 100644 --- a/apps/backend/src/verification/verification.controller.ts +++ b/apps/backend/src/verification/verification.controller.ts @@ -11,24 +11,24 @@ import { Throttle, ThrottlerGuard } from '@nestjs/throttler'; @Controller('verification') export class VerificationController { - constructor(private readonly verificationService: VerificationService) {} + constructor(private readonly verificationService: VerificationService) { } - @Post('send') - @UseGuards(ThrottlerGuard) - @Throttle({ default: { limit: 20, ttl: 60000 } }) - async sendVerificationCode(@Body() dto: SendVerificationCodeDto) { - switch (dto.type) { - case 'login': - switch (dto.targetType) { - case 'phone': - return this.verificationService.sendPhoneCode(dto.phone, dto.type); - case 'email': - return this.verificationService.sendEmailCode(dto.email, dto.type); - default: - throw new BadRequestException('不支持的目标类型'); - } - default: - throw new BadRequestException('不支持的验证码类型'); - } - } + // @Post('send') + // @UseGuards(ThrottlerGuard) + // @Throttle({ default: { limit: 20, ttl: 60000 } }) + // async sendVerificationCode(@Body() dto: SendVerificationCodeDto) { + // switch (dto.type) { + // case 'login': + // switch (dto.targetType) { + // case 'phone': + // return this.verificationService.sendPhoneCode(dto.phone, dto.type); + // case 'email': + // return this.verificationService.sendEmailCode(dto.email, dto.type); + // default: + // throw new BadRequestException('不支持的目标类型'); + // } + // default: + // throw new BadRequestException('不支持的验证码类型'); + // } + // } } diff --git a/apps/backend/src/verification/verification.service.ts b/apps/backend/src/verification/verification.service.ts index ba925dd..0c5b642 100644 --- a/apps/backend/src/verification/verification.service.ts +++ b/apps/backend/src/verification/verification.service.ts @@ -5,7 +5,7 @@ import { NotificationService } from 'src/notification/notification.service'; export class VerificationService { private readonly logger = new Logger(VerificationService.name); - constructor(private readonly notificationService: NotificationService) {} + constructor(private readonly notificationService: NotificationService) { } private pool: Map< string, @@ -121,7 +121,10 @@ export class VerificationService { return 0; } - private generateCode() { + /** + * 生成100000~999999的随机纯数字验证码 + */ + private generateCode(): string { return Math.floor(100000 + Math.random() * 900000).toString(); } }