feat(player): 注册账号、登录双模式与移动端性能优化
注册必填 7-32 位账号,手机号区号/本地号分存;登录默认账号模式并支持切换手机号登录;Player i18n 拆包与赛事接口优化。 Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
@@ -9,7 +9,7 @@ import { PrismaService } from '../../shared/prisma/prisma.service';
|
||||
import { SystemConfigService } from '../../shared/config/system-config.service';
|
||||
import { InvitesService } from './invites.service';
|
||||
import { SmsService } from './sms/sms.service';
|
||||
import { normalizePhone, resolvePlayerLoginUsername } from './sms/phone.util';
|
||||
import { normalizePhone, resolvePlayerLoginCandidates, stripLocalPhoneDigits } from './sms/phone.util';
|
||||
|
||||
const MAX_LOGIN_FAILS = 5;
|
||||
const LOCK_DURATION_MS = 15 * 60 * 1000;
|
||||
@@ -51,15 +51,51 @@ export class AuthService {
|
||||
portal: 'player' | 'admin' | 'agent',
|
||||
countryCode?: string,
|
||||
) {
|
||||
const lookupUsername =
|
||||
portal === 'player'
|
||||
? resolvePlayerLoginUsername(username, countryCode)
|
||||
: username.trim();
|
||||
let user;
|
||||
if (portal === 'player') {
|
||||
const trimmed = username.trim();
|
||||
if (/[a-zA-Z]/.test(trimmed)) {
|
||||
user = await this.prisma.user.findUnique({
|
||||
where: { username: trimmed },
|
||||
include: { auth: true, adminRole: { include: { role: true } } },
|
||||
});
|
||||
} else {
|
||||
const candidates = resolvePlayerLoginCandidates(username, countryCode);
|
||||
let matches = await this.prisma.user.findMany({
|
||||
where: {
|
||||
username: { in: candidates },
|
||||
userType: 'PLAYER',
|
||||
deletedAt: null,
|
||||
},
|
||||
include: { auth: true, adminRole: { include: { role: true } } },
|
||||
});
|
||||
|
||||
const user = await this.prisma.user.findUnique({
|
||||
where: { username: lookupUsername },
|
||||
include: { auth: true, adminRole: { include: { role: true } } },
|
||||
});
|
||||
if (matches.length === 0 && countryCode?.trim()) {
|
||||
const dial = countryCode.replace(/\D/g, '');
|
||||
const local = stripLocalPhoneDigits(username);
|
||||
matches = await this.prisma.user.findMany({
|
||||
where: {
|
||||
userType: 'PLAYER',
|
||||
deletedAt: null,
|
||||
preferences: {
|
||||
phoneCountryDial: dial,
|
||||
phoneLocal: local,
|
||||
},
|
||||
},
|
||||
include: { auth: true, adminRole: { include: { role: true } } },
|
||||
});
|
||||
}
|
||||
|
||||
if (matches.length === 1) {
|
||||
user = matches[0];
|
||||
}
|
||||
}
|
||||
} else {
|
||||
user = await this.prisma.user.findUnique({
|
||||
where: { username: username.trim() },
|
||||
include: { auth: true, adminRole: { include: { role: true } } },
|
||||
});
|
||||
}
|
||||
|
||||
if (!user || !user.auth) {
|
||||
throw appUnauthorized('INVALID_CREDENTIALS');
|
||||
@@ -156,6 +192,7 @@ export class AuthService {
|
||||
}
|
||||
|
||||
async registerPlayer(data: {
|
||||
username: string;
|
||||
phone: string;
|
||||
countryCode: string;
|
||||
password: string;
|
||||
@@ -164,14 +201,20 @@ export class AuthService {
|
||||
inviteCode?: string;
|
||||
locale?: string;
|
||||
}) {
|
||||
const phone = normalizePhone(data.countryCode, data.phone);
|
||||
const username = phone;
|
||||
|
||||
const username = data.username.trim();
|
||||
if (!username) {
|
||||
throw appBadRequest('USERNAME_REQUIRED');
|
||||
}
|
||||
try {
|
||||
assertPlayerUsername(username);
|
||||
} catch {
|
||||
throw appBadRequest('PHONE_INVALID');
|
||||
throw appBadRequest('USERNAME_FORMAT_INVALID');
|
||||
}
|
||||
|
||||
const dial = data.countryCode.replace(/\D/g, '');
|
||||
const phoneLocal = stripLocalPhoneDigits(data.phone);
|
||||
const phone = normalizePhone(dial, data.phone);
|
||||
|
||||
if (!data.password || data.password.length < 8) {
|
||||
throw appBadRequest('PASSWORD_MIN_LENGTH');
|
||||
}
|
||||
@@ -189,11 +232,23 @@ export class AuthService {
|
||||
const { parentId, sponsorId, inviteId } = await this.resolveInviteSponsor(data.inviteCode);
|
||||
const inviteSponsorId = parentId == null && sponsorId != null ? sponsorId : null;
|
||||
|
||||
const existing = await this.prisma.user.findUnique({
|
||||
const existingUser = await this.prisma.user.findUnique({
|
||||
where: { username },
|
||||
select: { id: true },
|
||||
});
|
||||
if (existing) {
|
||||
if (existingUser) {
|
||||
throw appBadRequest('USERNAME_TAKEN');
|
||||
}
|
||||
|
||||
const existingPhone = await this.prisma.userPreference.findFirst({
|
||||
where: {
|
||||
phoneCountryDial: dial,
|
||||
phoneLocal,
|
||||
user: { deletedAt: null, userType: 'PLAYER' },
|
||||
},
|
||||
select: { userId: true },
|
||||
});
|
||||
if (existingPhone) {
|
||||
throw appBadRequest('PHONE_TAKEN');
|
||||
}
|
||||
|
||||
@@ -220,7 +275,13 @@ export class AuthService {
|
||||
});
|
||||
|
||||
await tx.userPreference.create({
|
||||
data: { userId: created.id, locale, phone },
|
||||
data: {
|
||||
userId: created.id,
|
||||
locale,
|
||||
phone,
|
||||
phoneCountryDial: dial,
|
||||
phoneLocal,
|
||||
},
|
||||
});
|
||||
|
||||
if (inviteId) {
|
||||
|
||||
Reference in New Issue
Block a user