From e03e0fb26063397a4bb217997c6b520762f5e462 Mon Sep 17 00:00:00 2001 From: tone <3341154833@qq.com> Date: Wed, 7 May 2025 16:01:37 +0800 Subject: [PATCH] =?UTF-8?q?=E5=AE=8C=E6=88=90=E9=AA=8C=E8=AF=81=E7=A0=81?= =?UTF-8?q?=E5=8F=91=E9=80=81=E5=92=8C=E9=AA=8C=E8=AF=81?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../dto/send-verification-code.dto.ts | 19 ++++ .../verification.controller.spec.ts | 18 ++++ .../verification/verification.controller.ts | 28 ++++++ .../src/verification/verification.module.ts | 7 +- .../verification/verification.service.spec.ts | 18 ++++ .../src/verification/verification.service.ts | 88 +++++++++++++++++++ 6 files changed, 177 insertions(+), 1 deletion(-) create mode 100644 tone-page-server/src/verification/dto/send-verification-code.dto.ts create mode 100644 tone-page-server/src/verification/verification.controller.spec.ts create mode 100644 tone-page-server/src/verification/verification.controller.ts create mode 100644 tone-page-server/src/verification/verification.service.spec.ts create mode 100644 tone-page-server/src/verification/verification.service.ts diff --git a/tone-page-server/src/verification/dto/send-verification-code.dto.ts b/tone-page-server/src/verification/dto/send-verification-code.dto.ts new file mode 100644 index 0000000..d73e221 --- /dev/null +++ b/tone-page-server/src/verification/dto/send-verification-code.dto.ts @@ -0,0 +1,19 @@ +import { IsEnum, IsString, Length, ValidateIf } from "class-validator"; + +export class SendVerificationCodeDto { + @IsEnum(['phone', 'email'], { message: '请求类型错误' }) + targetType: 'phone' | 'email'; + + @IsEnum(['login'], { message: '请求类型错误' }) + type: 'login' + + @ValidateIf(o => o.targetType === 'phone') + @IsString({ message: '手机号必须输入' }) + @Length(11, 11, { message: '手机号异常' })// 中国大陆,11位数字 + phone?: string; + + @ValidateIf(o => o.targetType === 'email') + @IsString({ message: '邮箱必须输入' }) + @Length(6, 254, { message: '邮箱异常' })// RFC 5321 + email?: string; +} \ No newline at end of file diff --git a/tone-page-server/src/verification/verification.controller.spec.ts b/tone-page-server/src/verification/verification.controller.spec.ts new file mode 100644 index 0000000..3b53dcc --- /dev/null +++ b/tone-page-server/src/verification/verification.controller.spec.ts @@ -0,0 +1,18 @@ +import { Test, TestingModule } from '@nestjs/testing'; +import { VerificationController } from './verification.controller'; + +describe('VerificationController', () => { + let controller: VerificationController; + + beforeEach(async () => { + const module: TestingModule = await Test.createTestingModule({ + controllers: [VerificationController], + }).compile(); + + controller = module.get(VerificationController); + }); + + it('should be defined', () => { + expect(controller).toBeDefined(); + }); +}); diff --git a/tone-page-server/src/verification/verification.controller.ts b/tone-page-server/src/verification/verification.controller.ts new file mode 100644 index 0000000..d085765 --- /dev/null +++ b/tone-page-server/src/verification/verification.controller.ts @@ -0,0 +1,28 @@ +import { BadRequestException, Body, Controller, Post } from '@nestjs/common'; +import { SendVerificationCodeDto } from './dto/send-verification-code.dto'; +import { VerificationService } from './verification.service'; + +@Controller('verification') +export class VerificationController { + + constructor( + private readonly verificationService: VerificationService, + ) { } + + @Post('send') + 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/tone-page-server/src/verification/verification.module.ts b/tone-page-server/src/verification/verification.module.ts index a7f050e..c8fd9d3 100644 --- a/tone-page-server/src/verification/verification.module.ts +++ b/tone-page-server/src/verification/verification.module.ts @@ -1,4 +1,9 @@ import { Module } from '@nestjs/common'; +import { VerificationController } from './verification.controller'; +import { VerificationService } from './verification.service'; -@Module({}) +@Module({ + controllers: [VerificationController], + providers: [VerificationService] +}) export class VerificationModule {} diff --git a/tone-page-server/src/verification/verification.service.spec.ts b/tone-page-server/src/verification/verification.service.spec.ts new file mode 100644 index 0000000..caa2f6d --- /dev/null +++ b/tone-page-server/src/verification/verification.service.spec.ts @@ -0,0 +1,18 @@ +import { Test, TestingModule } from '@nestjs/testing'; +import { VerificationService } from './verification.service'; + +describe('VerificationService', () => { + let service: VerificationService; + + beforeEach(async () => { + const module: TestingModule = await Test.createTestingModule({ + providers: [VerificationService], + }).compile(); + + service = module.get(VerificationService); + }); + + it('should be defined', () => { + expect(service).toBeDefined(); + }); +}); diff --git a/tone-page-server/src/verification/verification.service.ts b/tone-page-server/src/verification/verification.service.ts new file mode 100644 index 0000000..7556c20 --- /dev/null +++ b/tone-page-server/src/verification/verification.service.ts @@ -0,0 +1,88 @@ +import { Injectable } from '@nestjs/common'; + +@Injectable() +export class VerificationService { + + private pool: Map = new Map(); + + async sendPhoneCode(phone: string, type: 'login') { + const key = `phone:${phone}:${type}`; + // 检测是否在冷却时间内 + // TODO + + // 生成验证码 + const code = this.generateCode(); + // 发送验证码 + // TODO + + // 存储验证码 + this.saveCode(key, code); + } + + async sendEmailCode(email: string, type: 'login') { + const key = `email:${email}:${type}`; + // 检测是否在冷却时间内 + // TODO + + // 生成验证码 + const code = this.generateCode(); + // 发送验证码 + // TODO + + // 存储验证码 + this.saveCode(key, code); + } + + private saveCode(key: string, code: string) { + this.pool.set(key, { + code: code, + createdAt: Date.now(), + expiredAt: Date.now() + 10 * 60 * 1000, // 10分钟过期 + tryCount: 0, + maxTryCount: 5, + }); + } + + verifyPhoneCode(phone: string, code: string, type: 'login') { + const key = `phone:${phone}:${type}`; + return this.verifyCode(key, code); + } + + verifyEmailCode(email: string, code: string, type: 'login') { + const key = `email:${email}:${type}`; + return this.verifyCode(key, code); + } + + /** + * @returns 0: 验证码正确, -1: 验证码不存在或已过期, -2: 验证码错误, -3: 超过最大尝试次数 + */ + private verifyCode(key: string, code: string) { + const data = this.pool.get(key); + if (!data) { + return -1; + } + if (data.tryCount >= data.maxTryCount) { + return -3; + } + if (data.expiredAt < Date.now()) { + return -1; + } + if (data.code !== code) { + data.tryCount++; + return -2; + } + this.pool.delete(key); + return 0; + } + + private generateCode() { + return Math.floor(100000 + Math.random() * 900000).toString(); + } +} +