feat: 短信调试日志、Tawk 客服、手机号校验放宽与 Docker 文档

API
- 短信发码/验码/创蓝全链路结构化日志(手机号脱敏)
- 新增 SMS_DEBUG_LOG_CODE,联调时可输出验证码与 sessionId(对应创蓝批次号)
- 注册成功、短信找回密码成功写入审计相关日志
- 放宽手机号归一化:移除区号白名单与 10~15 位长度限制

Player
- 公告走马灯滚动周期调整为 35 秒
- 在线客服接入 Tawk.to(tawk.html),登录用户透传昵称/头像/ID
- 三语补充 support.connecting 文案

部署与文档
- docker-compose 与 .env.docker.example 增加 SMS_DEBUG_LOG_CODE
- 新增 docs/短信调试与日志说明.md、docs/docker 镜像构建导出脚本与说明
- Docker 部署指南补充镜像构建文档链接
- .gitignore 忽略 thebet365-images.tar 与 docker-build.log

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
2026-06-12 14:08:00 +08:00
parent ff89c31b51
commit cb9a1e8708
21 changed files with 870 additions and 34 deletions

View File

@@ -1,4 +1,4 @@
import { Injectable } from '@nestjs/common';
import { Injectable, Logger } from '@nestjs/common';
import { Decimal } from '@prisma/client/runtime/library';
import { appForbidden, appUnauthorized, appBadRequest, appNotFound } from '../../shared/common/app-error';
import { JwtService } from '@nestjs/jwt';
@@ -11,6 +11,7 @@ import { InvitesService } from './invites.service';
import { SmsService } from './sms/sms.service';
import { AuditService } from '../operations/audit/audit.service';
import { normalizePhone, resolvePlayerLoginCandidates, stripLocalPhoneDigits } from './sms/phone.util';
import { maskPhoneForLog } from './sms/sms-log.util';
const MAX_LOGIN_FAILS = 5;
const LOCK_DURATION_MS = 15 * 60 * 1000;
@@ -24,6 +25,8 @@ export interface JwtPayload {
@Injectable()
export class AuthService {
private readonly logger = new Logger(AuthService.name);
constructor(
private prisma: PrismaService,
private jwt: JwtService,
@@ -313,6 +316,8 @@ export class AuthService {
return created;
});
this.logger.log(`Player registered username=${username} phone=${maskPhoneForLog(phone)}`);
return this.login(username, data.password, 'player');
}
@@ -425,6 +430,10 @@ export class AuthService {
ipAddress: data.ipAddress,
});
this.logger.log(
`Password reset by phone userId=${player.id.toString()} phone=${maskPhoneForLog(normalizePhone(data.countryCode, data.phone))} ip=${data.ipAddress ?? 'n/a'}`,
);
return { success: true };
}