feat(player): 接入创蓝短信手机注册与登录页优化

新增 SMS 验证码注册、8 国手机号选择与 Redis 频控;优化登录/注册 UI 及图形验证码样式。

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
2026-06-12 10:25:59 +08:00
parent 168aecfd5c
commit db28390be9
39 changed files with 1521 additions and 107 deletions

View File

@@ -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 } {

View 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';
}

View 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 {}

View 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);
}
}