diff --git a/.gitignore b/.gitignore
index 07f2afc..e74865e 100644
--- a/.gitignore
+++ b/.gitignore
@@ -11,6 +11,7 @@ pnpm-debug.log*
yarn-debug.log*
yarn-error.log*
lerna-debug.log*
+pnpm-lock.yaml
# OS
.DS_Store
diff --git a/config/development.ts b/config/development.ts
index 4ff06ad..9187605 100644
--- a/config/development.ts
+++ b/config/development.ts
@@ -20,7 +20,7 @@ export default {
host: 'localhost',
port: 3306,
username: 'root',
- password: 'Hxl1314521',
+ password: 'root',
database: 'Starlight',
},
},
@@ -47,4 +47,13 @@ export default {
timeout: 1000 * 60 * 60 * 24 * 7,
secretKey: 'Missing You!(John waite)',
},
+ // 登录
+ signIn: {
+ // @ 密码验证次数超限后的冷却时长
+ signInErrorTimeout: 60 * 10,
+ // @ 允许密码输错几次
+ signInErrorNumber: 5,
+ // @ 允许同时在线数
+ onLineNumber: 6,
+ },
};
diff --git a/config/production.ts b/config/production.ts
index 622db12..63b74e3 100644
--- a/config/production.ts
+++ b/config/production.ts
@@ -47,4 +47,11 @@ export default {
timeout: 1000 * 60 * 60 * 24 * 7,
secretKey: 'Missing You!(John waite)',
},
+ // 登录
+ signIn: {
+ // @ 密码验证次数超限后的冷却时长
+ signInErrorTimeout: 60 * 10,
+ // @ 允许密码输错几次
+ signInErrorNumber: 5,
+ },
};
diff --git a/src/Gservice/GEMAIL/gemail.service.ts b/src/Gservice/GEMAIL/gemail.service.ts
index 2084043..5171a8e 100644
--- a/src/Gservice/GEMAIL/gemail.service.ts
+++ b/src/Gservice/GEMAIL/gemail.service.ts
@@ -228,4 +228,90 @@ export class GemailService {
}
});
}
+
+ // ? ?
+ // ? 函数名称: sendSignInCodeMail(邮箱, 验证码)
+ // ? 函数描述: 给制定邮箱发送注册验证码
+ // ? ?
+ public sendSignInCodeMail(email, code) {
+ return new Promise(async (res, rej) => {
+ const test = `
+
+
+
+
我们欢迎您使用${this.sysName}
+
您在某些地方请求了邮箱的验证码,如果不是自己操作请修改账户的密码。
+
+
此邮件作用于账户登录
截止邮件发送时间5分钟内使用有效。
+
+
+ `;
+ try {
+ console.time('EMAIL');
+ const resd = await this.senMail(test, email);
+ console.timeEnd('EMAIL');
+ res(resd);
+ } catch (e) {
+ rej({
+ data: e,
+ message: '发送邮件失败!',
+ });
+ }
+ });
+ }
}
diff --git a/src/Gservice/GREDIS/gredis.service.ts b/src/Gservice/GREDIS/gredis.service.ts
index b0b6d70..09d4fc2 100644
--- a/src/Gservice/GREDIS/gredis.service.ts
+++ b/src/Gservice/GREDIS/gredis.service.ts
@@ -1,13 +1,18 @@
import { Injectable } from '@nestjs/common';
import { createClient } from 'redis';
import config from '../../../config';
-import { GtoolsService } from '@/Gservice/GTOOLS/gtools.service';
+import { GtoolsService, HASHT } from '@/Gservice/GTOOLS/gtools.service';
export declare interface ISetRegisterEmailCode {
data: any;
message: string;
registerCode?: string;
}
+export declare interface ISetSignInEmailCode {
+ data: any;
+ message: string;
+ signInCode?: string;
+}
@Injectable()
export class GredisService {
@@ -15,11 +20,18 @@ export class GredisService {
private config;
// @ 专注于注册的数据库
private readonly UserRegisterPoll = 1;
+ // @ 专注于登录的数据库
+ private readonly UserSignInPoll = 2;
+ // @ 登录的相关配置文件
+ private readonly SignInCFG = config().signIn;
+ // @ Token相关的配置文件
+ private readonly TokenCFG = config().token;
constructor(private readonly gtools: GtoolsService) {
this.config = config().redis.starLight;
this.start();
}
+ //#region 注册
// ? ?
// ? 函数名称: start
// ? 函数描述: 连接redis
@@ -31,6 +43,7 @@ export class GredisService {
await client.connect();
this.DB = client;
}
+
// ? ?
// ? 函数名称: setRegisterEmailCode(邮箱)
// ? 函数描述: 设置注册邮箱验证
@@ -87,12 +100,12 @@ export class GredisService {
const result = await this.DB.get(key);
if (result === null) {
rej({
- data: {},
+ data: null,
message: '未找到相匹配的验证码!',
});
} else {
res({
- data: {},
+ data: null,
message: '获取成功',
registerCode: result as string,
});
@@ -100,11 +113,12 @@ export class GredisService {
} catch (e) {
rej({
data: e,
- message: '从Redis获取注册吗出现错误!',
+ message: '从Redis获取注册码出现错误!',
});
}
});
}
+
// ? ?
// ? 函数名称: removeRegisterEmailCode
// ? 函数描述:
@@ -127,4 +141,220 @@ export class GredisService {
}
});
}
+ //#endregion
+
+ //#region 密码登录
+ // ? ?
+ // ? 函数名称: getSignInErrorNumber
+ // ? 函数描述: 获取异常登录信息
+ // ? ?
+ public async getSignInErrorNumber(uuid) {
+ const key = 'SIN' + uuid;
+ this.DB.select(this.UserSignInPoll);
+ try {
+ const result = await this.DB.get(key);
+ if (
+ result === null ||
+ Number(result) < this.SignInCFG.signInErrorNumber
+ ) {
+ return {
+ state: true,
+ message: '获取成功',
+ error: null,
+ };
+ } else {
+ const ttl = await this.DB.ttl(key);
+ return {
+ state: false,
+ message: '登陆异常已达上限,请稍后重试!',
+ ttl,
+ error: null,
+ };
+ }
+ } catch (e) {
+ return {
+ state: false,
+ message: '查找登录异常数据出错!',
+ error: e,
+ };
+ }
+ }
+
+ // ? ?
+ // ? 函数名称: setSignInErrorNumber
+ // ? 函数描述: 设置用户登陆异常数字
+ // ? ?
+ public async setSignInErrorNumber(uuid) {
+ const key = 'SIN' + uuid;
+ this.DB.select(this.UserSignInPoll);
+ try {
+ const exist = await this.DB.exists(key);
+ if (exist == 0) {
+ console.log(key);
+ await this.DB.set(key, 1);
+ await this.DB.expire(key, this.SignInCFG.signInErrorTimeout);
+ return {
+ error: null,
+ number: 1,
+ state: true,
+ };
+ } else {
+ const number = await this.DB.get(key);
+ await this.DB.set(key, Number(number) + 1);
+ await this.DB.expire(key, this.SignInCFG.signInErrorTimeout);
+ return {
+ error: null,
+ number: Number(number) + 1,
+ state: true,
+ };
+ }
+ } catch (e) {
+ return {
+ error: e,
+ message: '设置登陆异常数量出现错误!',
+ };
+ }
+ }
+
+ // ? ?
+ // ? 函数名称: setToken
+ // ? 函数描述: 设置Token到Redis
+ // ? ?
+ public async setToken(uuid, token) {
+ this.DB.select(this.UserSignInPoll);
+ const tokenKey =
+ 'TK' + this.gtools.makeHASH(token, HASHT.MD5).slice(0, 32);
+ const uuidKey = 'UK' + uuid;
+ await this.DB.setEx(tokenKey, this.TokenCFG.timeout / 1000, uuid);
+
+ // 获取此用户的登陆数量
+ let signInNumber: number;
+ try {
+ signInNumber = await this.DB.zCard(uuidKey);
+ } catch (e) {
+ return {
+ state: false,
+ message: '获取登陆数量出错!',
+ error: e,
+ };
+ }
+ // 写入Token有序集合
+ try {
+ const score = new Date().getTime();
+ await this.DB.zAdd(uuidKey, [{ score, value: tokenKey }]);
+ } catch (e) {
+ return {
+ state: false,
+ message: '写入Token有序集合出错!',
+ error: e,
+ };
+ }
+ // 清除最早的TokenKey和uuidKey
+ try {
+ if (signInNumber >= this.SignInCFG.onLineNumber) {
+ // 超出范围内
+ // 删除最先的
+ const uuidKeyZList = await this.DB.zRange(
+ uuidKey,
+ 0,
+ signInNumber,
+ );
+ // 删除第一个
+ const popTokenKey = await this.DB.zRemRangeByRank(
+ uuidKey,
+ 0,
+ 0,
+ );
+ // 删除token
+ const delUuidKey = await this.DB.del(uuidKeyZList[0]);
+ // 加入新的
+ }
+ } catch (e) {
+ return {
+ state: false,
+ message: '清除最早的TokenKey和uuidKey出错!',
+ error: e,
+ };
+ }
+ return {
+ data: {
+ tokenKey,
+ uuidKey,
+ },
+ state: true,
+ message: '设置成功!',
+ };
+ }
+
+ // ? ?
+ // ? 函数名称: setSignInEmailCode(邮箱)
+ // ? 函数描述: 设置邮箱登录验证
+ // ? ?
+ public setSignInEmailCode(email): Promise {
+ return new Promise(async (res, rej) => {
+ this.DB.select(this.UserSignInPoll);
+ try {
+ const key = 'SignIn-' + email;
+ const result = await this.DB.get(key);
+ if (result === null) {
+ const signInCode = this.gtools.getRandomString(6);
+ try {
+ const data = await this.DB.setEx(key, 300, signInCode);
+ res({
+ data,
+ signInCode,
+ message: '生成验证码成功。',
+ });
+ } catch (e) {
+ rej({
+ data: e,
+ message: '生成验证码失败。',
+ });
+ }
+ } else {
+ rej({
+ data: null,
+ message: '已发送过邮件,请稍后再重试。',
+ });
+ }
+ } catch (e) {
+ rej({
+ data: e,
+ message: '查找登录码失败。',
+ });
+ }
+ });
+ }
+
+ // ? ?
+ // ? 函数名称: getSignInEmailEntryCode(邮箱)
+ // ? 函数描述: 读取登录验证码
+ // ? ?
+ public getSignInEmailEntryCode(email): Promise {
+ return new Promise(async (res, rej) => {
+ this.DB.select(this.UserSignInPoll);
+ const key = 'SignIn-' + email;
+ try {
+ const result = await this.DB.get(key);
+ if (result === null) {
+ rej({
+ data: null,
+ message: '未找到相匹配的验证码!',
+ });
+ } else {
+ res({
+ data: null,
+ message: '获取成功',
+ signInCode: result as string,
+ });
+ }
+ } catch (e) {
+ rej({
+ data: e,
+ message: '从Redis获取登录码出现错误!',
+ });
+ }
+ });
+ }
+ //#endregion
}
diff --git a/src/Gservice/GTOOLS/gtools.service.ts b/src/Gservice/GTOOLS/gtools.service.ts
index 1d9847d..9a12df5 100644
--- a/src/Gservice/GTOOLS/gtools.service.ts
+++ b/src/Gservice/GTOOLS/gtools.service.ts
@@ -4,13 +4,13 @@ import config from '@CFG/index';
import * as jwt from 'jsonwebtoken';
// @ 不可逆加密
-enum HASHT {
+export enum HASHT {
MD5 = 'md5', // 32位
SHA256 = 'sha256', // 64位
SHA512 = 'sha512', // 128位
}
// @ 可逆加密
-enum AEST {
+export enum AEST {
AES128 = 'aes-128-cbc',
AES256 = 'aes-256-gcm',
}
diff --git a/src/starlight/dto/signIn.dto.ts b/src/starlight/dto/signIn.dto.ts
index 8f1c574..157ccd8 100644
--- a/src/starlight/dto/signIn.dto.ts
+++ b/src/starlight/dto/signIn.dto.ts
@@ -5,8 +5,9 @@
* * @date: 2023/3/26 22:41
* * */
-import { Length } from 'class-validator';
+import { IsEmail, IsString, Length } from 'class-validator';
+// @ 密码登录
export class SignInPasswdEntryDto {
// @ 用户名
@Length(8, 128, { message: '请将用户名长度控制在8到128位之间!' })
@@ -16,3 +17,16 @@ export class SignInPasswdEntryDto {
@Length(8, 128, { message: '请将密码长度控制在8到128位之间!' })
password: string;
}
+
+// 邮箱登录
+export class SignInEmailEntryDto {
+ // @ 邮箱
+ @Length(8, 128, { message: '请将邮箱长度控制在8到128位之间!' })
+ @IsEmail({}, { message: '邮箱格式错误!' })
+ @IsString({ message: '邮箱应为字符串格式!' })
+ email: string;
+
+ // @ 验证码
+ @Length(6, 6, { message: '验证码是6位字符!' })
+ code: string;
+}
diff --git a/src/starlight/starlight.controller.ts b/src/starlight/starlight.controller.ts
index f502866..0742c72 100644
--- a/src/starlight/starlight.controller.ts
+++ b/src/starlight/starlight.controller.ts
@@ -34,7 +34,10 @@ import {
RegisterEmailCheckoutUsernameDto,
RegisterEmailSignUpDto,
} from '@/starlight/dto/register.dto';
-import { SignInPasswdEntryDto } from '@/starlight/dto/signIn.dto';
+import {
+ SignInEmailEntryDto,
+ SignInPasswdEntryDto,
+} from '@/starlight/dto/signIn.dto';
import { VerifyGuard } from '@/starlight/verify.guard';
// nest g d [name]
@@ -119,6 +122,26 @@ export class StarlightController {
return this.starlightService.signInPasswdEntry(body);
}
+ // ? ?
+ // ? 函数名称: signInEmailSendCode
+ // ? 函数描述: 获取邮箱登录验证码
+ // ? ?
+ @Get('signIn/email/sendCode/:email')
+ signInEmailSendCode(
+ @Param() params: RegisterEmailCheckoutEmailDto,
+ ): Promise