feat(player): 接入创蓝短信手机注册与登录页优化
新增 SMS 验证码注册、8 国手机号选择与 Redis 频控;优化登录/注册 UI 及图形验证码样式。 Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
@@ -1,6 +1,8 @@
|
||||
import {
|
||||
BadRequestException,
|
||||
ForbiddenException,
|
||||
HttpException,
|
||||
HttpStatus,
|
||||
NotFoundException,
|
||||
UnauthorizedException,
|
||||
} from '@nestjs/common';
|
||||
@@ -33,6 +35,10 @@ export function appUnauthorized(code: ApiErrorCode, params?: ApiErrorParams) {
|
||||
return new UnauthorizedException(body(code, params));
|
||||
}
|
||||
|
||||
export function appTooManyRequests(code: ApiErrorCode, params?: ApiErrorParams) {
|
||||
return new HttpException(body(code, params), HttpStatus.TOO_MANY_REQUESTS);
|
||||
}
|
||||
|
||||
export function isCodedExceptionResponse(
|
||||
res: unknown,
|
||||
): res is { code: ApiErrorCode; params?: ApiErrorParams } {
|
||||
|
||||
13
apps/api/src/shared/common/client-ip.util.ts
Normal file
13
apps/api/src/shared/common/client-ip.util.ts
Normal file
@@ -0,0 +1,13 @@
|
||||
import type { Request } from 'express';
|
||||
|
||||
export function getClientIp(req: Request): string {
|
||||
const forwarded = req.headers['x-forwarded-for'];
|
||||
if (typeof forwarded === 'string' && forwarded.trim()) {
|
||||
return forwarded.split(',')[0]?.trim() || '0.0.0.0';
|
||||
}
|
||||
const realIp = req.headers['x-real-ip'];
|
||||
if (typeof realIp === 'string' && realIp.trim()) {
|
||||
return realIp.trim();
|
||||
}
|
||||
return req.ip || req.socket?.remoteAddress || '0.0.0.0';
|
||||
}
|
||||
9
apps/api/src/shared/redis/redis.module.ts
Normal file
9
apps/api/src/shared/redis/redis.module.ts
Normal file
@@ -0,0 +1,9 @@
|
||||
import { Global, Module } from '@nestjs/common';
|
||||
import { RedisService } from './redis.service';
|
||||
|
||||
@Global()
|
||||
@Module({
|
||||
providers: [RedisService],
|
||||
exports: [RedisService],
|
||||
})
|
||||
export class RedisModule {}
|
||||
37
apps/api/src/shared/redis/redis.service.ts
Normal file
37
apps/api/src/shared/redis/redis.service.ts
Normal file
@@ -0,0 +1,37 @@
|
||||
import { Injectable, OnModuleDestroy } from '@nestjs/common';
|
||||
import { ConfigService } from '@nestjs/config';
|
||||
import Redis from 'ioredis';
|
||||
|
||||
@Injectable()
|
||||
export class RedisService implements OnModuleDestroy {
|
||||
private readonly client: Redis;
|
||||
|
||||
constructor(config: ConfigService) {
|
||||
const url = config.get<string>('REDIS_URL', 'redis://127.0.0.1:6379');
|
||||
this.client = new Redis(url, { maxRetriesPerRequest: 3, lazyConnect: true });
|
||||
}
|
||||
|
||||
async onModuleDestroy() {
|
||||
await this.client.quit();
|
||||
}
|
||||
|
||||
get raw(): Redis {
|
||||
return this.client;
|
||||
}
|
||||
|
||||
async exists(key: string): Promise<boolean> {
|
||||
return (await this.client.exists(key)) > 0;
|
||||
}
|
||||
|
||||
async get(key: string): Promise<string | null> {
|
||||
return this.client.get(key);
|
||||
}
|
||||
|
||||
async set(key: string, value: string, ttlSeconds: number): Promise<void> {
|
||||
await this.client.set(key, value, 'EX', ttlSeconds);
|
||||
}
|
||||
|
||||
async del(key: string): Promise<void> {
|
||||
await this.client.del(key);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user