diff --git a/apps/backend/src/app.module.ts b/apps/backend/src/app.module.ts index 112cfd0..b8c3741 100644 --- a/apps/backend/src/app.module.ts +++ b/apps/backend/src/app.module.ts @@ -12,6 +12,7 @@ import { BlogModule } from './blog/blog.module'; import { AdminModule } from './admin/admin.module'; import { OssModule } from './oss/oss.module'; import { ThrottlerModule } from '@nestjs/throttler'; +import { CaptchaModule } from './captcha/captcha.module'; @Module({ imports: [ @@ -43,6 +44,7 @@ import { ThrottlerModule } from '@nestjs/throttler'; BlogModule, AdminModule, OssModule, + CaptchaModule, ], controllers: [AppController], providers: [AppService], diff --git a/apps/backend/src/captcha/captcha.controller.spec.ts b/apps/backend/src/captcha/captcha.controller.spec.ts new file mode 100644 index 0000000..a257614 --- /dev/null +++ b/apps/backend/src/captcha/captcha.controller.spec.ts @@ -0,0 +1,18 @@ +import { Test, TestingModule } from '@nestjs/testing'; +import { CaptchaController } from './captcha.controller'; + +describe('CaptchaController', () => { + let controller: CaptchaController; + + beforeEach(async () => { + const module: TestingModule = await Test.createTestingModule({ + controllers: [CaptchaController], + }).compile(); + + controller = module.get(CaptchaController); + }); + + it('should be defined', () => { + expect(controller).toBeDefined(); + }); +}); diff --git a/apps/backend/src/captcha/captcha.controller.ts b/apps/backend/src/captcha/captcha.controller.ts new file mode 100644 index 0000000..e5f9259 --- /dev/null +++ b/apps/backend/src/captcha/captcha.controller.ts @@ -0,0 +1,10 @@ +import { Controller, Get } from '@nestjs/common'; +import { GetCaptchaDto } from './dto/get-captcha.dto'; + +@Controller('captcha') +export class CaptchaController { + @Get() + async getCaptcha(dto: GetCaptchaDto) { + + } +} diff --git a/apps/backend/src/captcha/captcha.module.ts b/apps/backend/src/captcha/captcha.module.ts new file mode 100644 index 0000000..4b34c4f --- /dev/null +++ b/apps/backend/src/captcha/captcha.module.ts @@ -0,0 +1,11 @@ +import { Module } from '@nestjs/common'; +import { CaptchaService } from './captcha.service'; +import { CaptchaController } from './captcha.controller'; +import { CaptchaRateLimitService } from './service/rate-limit'; + +@Module({ + providers: [CaptchaService, CaptchaRateLimitService], + controllers: [CaptchaController], + imports: [], +}) +export class CaptchaModule { } diff --git a/apps/backend/src/captcha/captcha.service.spec.ts b/apps/backend/src/captcha/captcha.service.spec.ts new file mode 100644 index 0000000..82393cc --- /dev/null +++ b/apps/backend/src/captcha/captcha.service.spec.ts @@ -0,0 +1,18 @@ +import { Test, TestingModule } from '@nestjs/testing'; +import { CaptchaService } from './captcha.service'; + +describe('CaptchaService', () => { + let service: CaptchaService; + + beforeEach(async () => { + const module: TestingModule = await Test.createTestingModule({ + providers: [CaptchaService], + }).compile(); + + service = module.get(CaptchaService); + }); + + it('should be defined', () => { + expect(service).toBeDefined(); + }); +}); diff --git a/apps/backend/src/captcha/captcha.service.ts b/apps/backend/src/captcha/captcha.service.ts new file mode 100644 index 0000000..d5d7f8b --- /dev/null +++ b/apps/backend/src/captcha/captcha.service.ts @@ -0,0 +1,27 @@ +import { Injectable } from '@nestjs/common'; +import { ErrorCode } from 'src/common/constants/error-codes'; +import { BusinessException } from 'src/common/exceptions/business.exception'; + +export enum CaptchaContext { + SEND_SMS = 'send_sms', + PASSKEY = 'passkey', +} + +@Injectable() +export class CaptchaService { + public async generate(context: CaptchaContext, ip: string, userId?: string) { + await this.checkRateLimit(ip, context) + } + + public async verify(token: string, ip: string, userId?: string) { + + } + + private async checkRateLimit(ip: string, context: CaptchaContext) { + /** @todo */ + throw new BusinessException({ + code: ErrorCode.CAPTCHA_RARE_LIMIT, + message: '服务器处理不过来了,过会儿再试试吧', + }); + } +} diff --git a/apps/backend/src/captcha/dto/get-captcha.dto.ts b/apps/backend/src/captcha/dto/get-captcha.dto.ts new file mode 100644 index 0000000..edb62f6 --- /dev/null +++ b/apps/backend/src/captcha/dto/get-captcha.dto.ts @@ -0,0 +1,16 @@ +import { IsEnum, IsOptional, IsUUID } from "class-validator"; + +export enum CaptchaContext { + SEND_SMS = 'send_sms', + PASSKEY = 'passkey', +} + +export class GetCaptchaDto { + + @IsEnum(CaptchaContext, { message: '无效的context' }) + context: string; + + @IsOptional() + @IsUUID('4', { message: 'userId不合法' }) + userId?: string; +} \ No newline at end of file diff --git a/apps/backend/src/captcha/service/rate-limit.ts b/apps/backend/src/captcha/service/rate-limit.ts new file mode 100644 index 0000000..cab5348 --- /dev/null +++ b/apps/backend/src/captcha/service/rate-limit.ts @@ -0,0 +1,3 @@ +export class CaptchaRateLimitService { + +} \ No newline at end of file diff --git a/apps/backend/src/common/constants/error-codes.ts b/apps/backend/src/common/constants/error-codes.ts index c307109..b7affd8 100644 --- a/apps/backend/src/common/constants/error-codes.ts +++ b/apps/backend/src/common/constants/error-codes.ts @@ -29,8 +29,7 @@ export const ErrorCode = { BLOG_PERMISSION_DENIED: -3002, // 验证模块(4000 ~ 4999) - VERIFICATION_CODE_EXPIRED: -4001, - VERIFICATION_CODE_INCORRECT: -4002, + CAPTCHA_RARE_LIMIT: -4001, // 通知模块(5000 ~ 5999) NOTIFICATION_SEND_FAILED: -5001,