完成两个登录接口和逻辑

master
expressgy 2 years ago
parent da7442c3e1
commit 14f5711e87
  1. 1
      .gitignore
  2. 11
      config/development.ts
  3. 7
      config/production.ts
  4. 86
      src/Gservice/GEMAIL/gemail.service.ts
  5. 238
      src/Gservice/GREDIS/gredis.service.ts
  6. 4
      src/Gservice/GTOOLS/gtools.service.ts
  7. 16
      src/starlight/dto/signIn.dto.ts
  8. 26
      src/starlight/starlight.controller.ts
  9. 288
      src/starlight/starlight.service.ts

1
.gitignore vendored

@ -11,6 +11,7 @@ pnpm-debug.log*
yarn-debug.log*
yarn-error.log*
lerna-debug.log*
pnpm-lock.yaml
# OS
.DS_Store

@ -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,
},
};

@ -47,4 +47,11 @@ export default {
timeout: 1000 * 60 * 60 * 24 * 7,
secretKey: 'Missing You!(John waite)',
},
// 登录
signIn: {
// @ 密码验证次数超限后的冷却时长
signInErrorTimeout: 60 * 10,
// @ 允许密码输错几次
signInErrorNumber: 5,
},
};

@ -228,4 +228,90 @@ export class GemailService {
}
});
}
// ? ?
// ? 函数名称: sendSignInCodeMail(邮箱, 验证码)
// ? 函数描述: 给制定邮箱发送注册验证码
// ? ?
public sendSignInCodeMail(email, code) {
return new Promise(async (res, rej) => {
const test = `
<style>
.emailBody{
position: relative;
min-height: 400px;
min-width: 300px;
height: 100%;
width:100%;
display: flex;
align-items: center;
justify-content: center;
margin: 0 auto;
padding: 0;
overflow: overlay;
}
.emailMain{
position:relative;
}
.emailTitle{
line-height: 3em;
font-size: 24px;
font-weight: bold;
text-align: center;
}
.emailTitleLetter{
position:relative;
font-size: 16px;
line-height: 3em;
}
.emailCode{
position:relative;
height: 100px;
width: 100%;
display: flex;
align-items: center;
justify-content: center;
}
.emailCode>div{
position:relative;
box-sizing: border-box;
padding: 1em;
font-size: 30px;
text-align: center;
color: #fefefe;
background-color: #222;
}
.emailDescription{
position:relative;
text-align: center;
padding: 20px 0 ;
line-height: 1.8em;
}
</style>
<div class="emailBody">
<div class="emailMain">
<div class="emailTitle">使${this.sysName}</div>
<div class="emailTitleLetter"></div>
<div class="emailCode">
<div>${code}</div>
</div>
<div class="emailDescription"><br/>5使</div>
</div>
</div>
`;
try {
console.time('EMAIL');
const resd = await this.senMail(test, email);
console.timeEnd('EMAIL');
res(resd);
} catch (e) {
rej({
data: e,
message: '发送邮件失败!',
});
}
});
}
}

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

@ -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',
}

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

@ -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<object> {
return this.starlightService.signInEmailSendCode(params);
}
// ? ?
// ? 函数名称: signInEmailEntry
// ? 函数描述: 邮箱验证登录
// ? ?
@Post('signIn/email/entry')
signInEmailEntry(@Body() body: SignInEmailEntryDto): Promise<object> {
return this.starlightService.signInEmailEntry(body);
}
// ? ?
// ? 函数名称:
// ? 函数描述: 测试Token
@ -129,6 +152,7 @@ export class StarlightController {
this.logger.info(user);
return {};
}
//#endredion
//#region 测试

@ -4,7 +4,7 @@
* * @author: x7129
* * @date: 2023-03-23 17:44
* * */
import { Injectable } from '@nestjs/common';
import { Injectable, UnauthorizedException } from '@nestjs/common';
import { CreateStarlightDto } from './dto/create-starlight.dto';
import { UpdateStarlightDto } from './dto/update-starlight.dto';
import {
@ -19,7 +19,10 @@ import { GdatabaseService } from '@/Gservice/GDATABASE/gdatabase.service';
import { GredisService } from '@/Gservice/GREDIS/gredis.service';
import { GemailService } from '@/Gservice/GEMAIL/gemail.service';
import { GtoolsService } from '@/Gservice/GTOOLS/gtools.service';
import { SignInPasswdEntryDto } from '@/starlight/dto/signIn.dto';
import {
SignInEmailEntryDto,
SignInPasswdEntryDto,
} from '@/starlight/dto/signIn.dto';
// C C
// C 类名称: StarlightService
@ -53,7 +56,7 @@ export class StarlightService {
// ! 从数据库用户身份表查询有没有已经使用的邮箱
const [rows] = await this.database.DB.execute(
`SELECT * FROM user_info_verify WHERE email = ? AND state = 0`,
[params.email.trim()],
[params.email.trim().toLowerCase()],
);
// ! 判断是否存在此邮箱
const resd = {
@ -79,7 +82,7 @@ export class StarlightService {
// ! 从数据库用户身份表查询有没有已经使用的用户名
const [rows] = await this.database.DB.execute(
`SELECT * FROM user_info_verify WHERE username = ? AND state = 0`,
[params.username.trim()],
[params.username.trim().toLowerCase()],
);
// ! 判断是否存在此用户名
const resd = {
@ -98,7 +101,8 @@ export class StarlightService {
// ? 函数描述: 发送邮箱注册验证码
// ? ?
public async registerEmailSendCode(params: RegisterEmailCheckoutEmailDto) {
const { email } = params;
let { email } = params;
email = email.trim().toLowerCase();
// ! 1. 验证是否存在已经注册的
const checkoutEmail = await this.registerEmailCheckoutEmail({
email,
@ -165,6 +169,9 @@ export class StarlightService {
return resd;
}
} catch (e) {
if (e.data != null) {
this.logger.error(e);
}
resd.message = e.message;
return resd;
}
@ -182,9 +189,9 @@ export class StarlightService {
const uuid = this.tools.makeUUID();
const createTime = new Date();
const s = {
email: email.trim(),
email: email.trim().toLowerCase(),
username: username.trim(),
realname: body.realname?.trim(),
realname: body.realname?.trim().toLowerCase(),
nickname: body.nickname?.trim(),
birthday: new Date(body.birthday),
sex: sex[body.sex],
@ -315,28 +322,275 @@ export class StarlightService {
// ? ?
public async signInPasswdEntry(body: SignInPasswdEntryDto) {
const { username, password } = body;
// ! 加密密码
const passwordHash = this.tools.makeHASH(password);
// ! 验证密码
// 查找最后一条
// ! 生成token
const resd = {
data: {},
message: '未找到该用户信息!',
success: false,
};
// ! 1. 从库中获取uuid
const getUserUUIDSQL = `SELECT uuid from user_info_verify where username = ? AND state = 0 ORDER BY id desc limit 1;`;
let uuid: string = null;
try {
const [rows] = await this.database.DB.execute(getUserUUIDSQL, [
username.trim().toLowerCase(),
]);
if (rows.length == 0) {
return resd;
}
uuid = rows[0].uuid;
} catch (e) {
const message = '查找用户UUID出错!';
resd.message = message;
this.logger.error({
message,
data: e,
});
}
// ! 2. 判断是否存在用户
// ! 2.1 查找登陆异常数量Redis
const errorNumber = await this.redis.getSignInErrorNumber(uuid);
if (!errorNumber.state) {
const message = errorNumber.message;
if (!errorNumber.ttl) {
this.logger.warn({
message,
e: errorNumber.error,
});
}
if (errorNumber.ttl) {
resd.data = {
ttl: errorNumber.ttl,
};
}
resd.message = message;
return resd;
}
// ! 3. 加密密码
const passwordHASH = this.tools.makeHASH(password.trim());
// ! 4. 查找,比对密码
let sqlPassword;
const getPasswordSQL = `SELECT passwd FROM user_info_passwd WHERE uuid = ? ORDER BY id desc limit 1;`;
try {
const [rows] = await this.database.DB.execute(getPasswordSQL, [
uuid,
]);
if (rows.length == 0) {
resd.message = '未找到密码!';
return resd;
} else {
this.logger.info(passwordHASH, rows[0]);
if (passwordHASH != rows[0].passwd) {
// ! 5. 登陆异常累加器
const setErrorNumber =
await this.redis.setSignInErrorNumber(uuid);
if (!setErrorNumber.state) {
const message = setErrorNumber.message;
this.logger.error({
data: setErrorNumber.error,
message,
});
resd.message = message;
return resd;
} else {
resd.message = '账户和密码不匹配,请重试。';
resd.data = {
number: setErrorNumber.number,
};
return resd;
}
}
}
} catch (e) {
const message = '查找用户密码时出错';
this.logger.error({
data: e,
message,
});
resd.message = message;
return resd;
}
// ! 6. 创建Token
const token = this.tools.createToken({
username,
uuid,
signInTime: new Date().getTime(),
});
this.logger.info(token);
// ! Redis 登陆存储策略
// ! 返回数据
// ! 7. Redis存储策略
const setToken = await this.redis.setToken(uuid, token);
if (!setToken.state) {
const message = setToken.message;
this.logger.error({
data: setToken.error,
message,
});
resd.message = message;
return resd;
}
return {
message: '登陆成功',
data: {
token: '',
token,
tokenKey: setToken.data.tokenKey,
},
};
}
//#endredion
// ? ?
// ? 函数名称: signInEmailSendCode
// ? 函数描述: 获取邮箱登录验证码
// ? ?
public async signInEmailSendCode(params: RegisterEmailCheckoutEmailDto) {
const email = params.email.trim().toLowerCase();
// ! 1. 验证是否存在已经注册的
const checkoutEmail = await this.registerEmailCheckoutEmail({
email,
});
const resd = {
data: {},
message: '不存在此邮箱账户!',
success: false,
};
if (checkoutEmail.success) {
return resd;
}
// ! 2. 验证是否存在验证码
let registerCode: any;
try {
const redisResd = await this.redis.setSignInEmailCode(email);
registerCode = redisResd.signInCode as string;
} catch (e) {
this.logger.error(e);
resd.message = e.message;
return resd;
}
// ! 3. 发送验证码
try {
const result = await this.email.sendSignInCodeMail(
email,
registerCode,
);
resd.success = true;
resd.message = '发送验证码成功,请注意查收!';
return resd;
} catch (e) {
this.logger.error(e);
resd.message = e.message;
return resd;
}
return {};
}
// ? ?
// ? 函数名称: signInEmailEntry
// ? 函数描述: 邮箱验证登录
// ? ?
public async signInEmailEntry(body: SignInEmailEntryDto) {
let { email, code } = body;
email = email.trim().toLowerCase();
code = code.trim().toUpperCase();
const resd = {
data: {},
message: '未找到该用户信息!',
success: false,
};
// ! 1.从库中获取UUID
const getUserUUIDSQL = `SELECT uuid from user_info_verify where email = ? AND state = 0 ORDER BY id desc limit 1;`;
let uuid: string = null;
try {
const [rows] = await this.database.DB.execute(getUserUUIDSQL, [
email.trim().toLowerCase(),
]);
if (rows.length == 0) {
return resd;
}
uuid = rows[0].uuid;
} catch (e) {
const message = '查找用户UUID出错!';
resd.message = message;
this.logger.error({
message,
data: e,
});
}
// ! 2. 查找登陆异常数量Redis
const errorNumber = await this.redis.getSignInErrorNumber(uuid);
if (!errorNumber.state) {
const message = errorNumber.message;
if (!errorNumber.ttl) {
this.logger.warn({
message,
e: errorNumber.error,
});
}
if (errorNumber.ttl) {
resd.data = {
ttl: errorNumber.ttl,
};
}
resd.message = message;
return resd;
}
// ! 3. 获取、比对验证码
try {
const { signInCode } = await this.redis.getSignInEmailEntryCode(
email,
);
if (signInCode != code) {
const setErrorNumber = await this.redis.setSignInErrorNumber(
uuid,
);
if (!setErrorNumber.state) {
const message = setErrorNumber.message;
this.logger.error({
data: setErrorNumber.error,
message,
});
resd.message = message;
return resd;
} else {
resd.message = '验证码不匹配!';
resd.data = {
number: setErrorNumber.number,
};
return resd;
}
}
} catch (e) {
if (e.data != null) {
this.logger.error(e);
}
resd.message = e.message;
return resd;
}
// ! 6. 创建Token
const token = this.tools.createToken({
email,
uuid,
signInTime: new Date().getTime(),
});
// ! 7. Redis存储策略
const setToken = await this.redis.setToken(uuid, token);
if (!setToken.state) {
const message = setToken.message;
this.logger.error({
data: setToken.error,
message,
});
resd.message = message;
return resd;
}
return {
message: '登陆成功',
data: {
token,
tokenKey: setToken.data.tokenKey,
},
};
}
//#endregion
//#region 测试啊

Loading…
Cancel
Save