From 954b6df3608695fe074130f8149a33e311d80cc4 Mon Sep 17 00:00:00 2001 From: xiaojunnuo Date: Thu, 28 Nov 2024 11:10:57 +0800 Subject: [PATCH] =?UTF-8?q?perf:=20=E7=99=BB=E5=BD=95=E5=A4=B1=E8=B4=A5?= =?UTF-8?q?=E5=A2=9E=E5=8A=A0=E9=87=8D=E8=AF=95=E6=AC=A1=E6=95=B0=E9=99=90?= =?UTF-8?q?=E5=88=B6=E5=8F=8A=E5=86=B7=E5=8D=B4=E6=97=B6=E9=97=B4?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../basic/exception/login-error-exception.ts | 12 +++ .../src/controller/login/login-controller.ts | 14 ++++ .../modules/login/service/login-service.ts | 79 ++++++++++++++++++- 3 files changed, 103 insertions(+), 2 deletions(-) create mode 100644 packages/libs/lib-server/src/basic/exception/login-error-exception.ts diff --git a/packages/libs/lib-server/src/basic/exception/login-error-exception.ts b/packages/libs/lib-server/src/basic/exception/login-error-exception.ts new file mode 100644 index 00000000..b84892f5 --- /dev/null +++ b/packages/libs/lib-server/src/basic/exception/login-error-exception.ts @@ -0,0 +1,12 @@ +import { Constants } from '../constants.js'; +import { BaseException } from './base-exception.js'; +/** + * 通用异常 + */ +export class LoginErrorException extends BaseException { + leftCount: number; + constructor(message, leftCount: number) { + super('LoginErrorException', Constants.res.loginError.code, message ? message : Constants.res.loginError.message); + this.leftCount = leftCount; + } +} diff --git a/packages/ui/certd-server/src/controller/login/login-controller.ts b/packages/ui/certd-server/src/controller/login/login-controller.ts index 2605c8b0..96274c5a 100644 --- a/packages/ui/certd-server/src/controller/login/login-controller.ts +++ b/packages/ui/certd-server/src/controller/login/login-controller.ts @@ -24,6 +24,20 @@ export class LoginController extends BaseController { return this.ok(token); } + @Post('/loginBySms', { summary: Constants.per.guest }) + public async loginBySms( + @Body(ALL) + body: any + ) { + const token = await this.loginService.loginBySmsCode(body); + + this.ctx.cookies.set('token', token.token, { + maxAge: 1000 * token.expire, + }); + + return this.ok(token); + } + @Post('/logout', { summary: Constants.per.authOnly }) public logout() {} } diff --git a/packages/ui/certd-server/src/modules/login/service/login-service.ts b/packages/ui/certd-server/src/modules/login/service/login-service.ts index acb9c39f..24a9fb81 100644 --- a/packages/ui/certd-server/src/modules/login/service/login-service.ts +++ b/packages/ui/certd-server/src/modules/login/service/login-service.ts @@ -6,6 +6,8 @@ import { RoleService } from '../../sys/authority/service/role-service.js'; import { UserEntity } from '../../sys/authority/entity/user.js'; import { SysSettingsService } from '@certd/lib-server'; import { SysPrivateSettings } from '@certd/lib-server'; +import { cache } from '@certd/basic'; +import { LoginErrorException } from '@certd/lib-server/dist/basic/exception/login-error-exception.js'; /** * 系统用户 @@ -22,6 +24,76 @@ export class LoginService { @Inject() sysSettingsService: SysSettingsService; + checkErrorTimes(username: string, errorMessage: string) { + const cacheKey = `login_error_times:${username}`; + const blockTimesKey = `login_block_times:${username}`; + let blockTimes = cache.get(blockTimesKey); + let maxWaitMin = 2; + const maxRetryTimes = 5; + if (blockTimes == null) { + blockTimes = 1; + } + maxWaitMin = maxWaitMin * blockTimes; + let ttl = maxWaitMin * 60 * 1000; + + let errorTimes = cache.get(cacheKey); + + if (errorTimes == null) { + errorTimes = 0; + } else { + const remainingTTL = cache.getRemainingTTL(cacheKey); + if (remainingTTL > 0) { + ttl = remainingTTL; + } + } + errorTimes += 1; + + cache.set(cacheKey, errorTimes, { + ttl: ttl, + }); + if (errorTimes >= maxRetryTimes) { + if (errorTimes === maxRetryTimes) { + blockTimes += 1; + cache.set(blockTimesKey, blockTimes, { + ttl: 24 * 60 * 60 * 1000, + }); + } + const leftMin = Math.ceil(ttl / 1000 / 60); + throw new LoginErrorException(`登录失败次数过多,请${leftMin}分钟后重试`, 0); + } + const leftTimes = maxRetryTimes - errorTimes; + if (leftTimes < 3) { + throw new LoginErrorException(`登录失败,剩余尝试次数:${leftTimes}`, leftTimes); + } + throw new LoginErrorException(errorMessage, leftTimes); + } + + async loginBySmsCode(req: { mobile: string; phoneCode: string; smsChecked: boolean }) { + const { mobile, phoneCode, smsChecked } = req; + if (!smsChecked) { + this.checkErrorTimes(mobile, '验证码错误'); + } + const info = await this.userService.findOne({ phoneCode, mobile: mobile }); + if (info == null) { + throw new CommonException('手机号或验证码错误'); + } + + return this.onLoginSuccess(info); + } + + async loginByPassword(req: { username: string; password: string; phoneCode: string }) { + const { username, password, phoneCode } = req; + const info = await this.userService.findOne([{ username: username }, { email: username }, { phoneCode, mobile: username }]); + if (info == null) { + throw new CommonException('用户名或密码错误'); + } + const right = await this.userService.checkPassword(password, info.password, info.passwordVersion); + if (!right) { + this.checkErrorTimes(username, '用户名或密码错误'); + } + return this.onLoginSuccess(info); + } + /** * login */ @@ -33,12 +105,15 @@ export class LoginService { } const right = await this.userService.checkPassword(user.password, info.password, info.passwordVersion); if (!right) { - throw new CommonException('用户名或密码错误'); + this.checkErrorTimes(user.username, '用户名或密码错误'); } + return await this.onLoginSuccess(info); + } + + private async onLoginSuccess(info: UserEntity) { if (info.status === 0) { throw new CommonException('用户已被禁用'); } - const roleIds = await this.roleService.getRoleIdsByUserId(info.id); return this.generateToken(info, roleIds); }