完成邮箱注册,半步权限验证,学习自定义装饰器

master
expressgy 2 years ago
parent 3763a85613
commit da7442c3e1
  1. 17
      config/development.ts
  2. 15
      config/production.ts
  3. 2
      package.json
  4. 44
      pnpm-lock.yaml
  5. 4
      src/Ginterceptor/gdevinterceptor.interceptor.ts
  6. 6
      src/Ginterceptor/gresponseinterceptor.interceptor.ts
  7. 2
      src/Gservice/GDATABASE/gdatabase.service.ts
  8. 8
      src/Gservice/GEMAIL/gemail.module.ts
  9. 231
      src/Gservice/GEMAIL/gemail.service.ts
  10. 111
      src/Gservice/GREDIS/gredis.service.ts
  11. 8
      src/Gservice/GTOOLS/gtools.module.ts
  12. 137
      src/Gservice/GTOOLS/gtools.service.ts
  13. 4
      src/app.module.ts
  14. 8
      src/starlight/dto/register.dto.ts
  15. 18
      src/starlight/dto/signIn.dto.ts
  16. 57
      src/starlight/starlight.controller.ts
  17. 305
      src/starlight/starlight.service.ts
  18. 42
      src/starlight/verify.guard.ts

@ -1,6 +1,7 @@
export default { export default {
// 主服务 // 主服务
master: { master: {
systemName: '心曲Tune',
host: '0.0.0.0', host: '0.0.0.0',
port: '3000', port: '3000',
}, },
@ -19,7 +20,7 @@ export default {
host: 'localhost', host: 'localhost',
port: 3306, port: 3306,
username: 'root', username: 'root',
password: 'root', password: 'Hxl1314521',
database: 'Starlight', database: 'Starlight',
}, },
}, },
@ -32,4 +33,18 @@ export default {
dbNumber: 3, dbNumber: 3,
}, },
}, },
email: {
host: 'smtp.qq.com',
user: 'togy.gc@qq.com',
pass: 'miidbsjeuqcxddbf',
},
// 加密
encryption: {
salt: 'время,вперёд!', // 盐
secretKey: 'быть всегда готовым!', // 密钥
},
token: {
timeout: 1000 * 60 * 60 * 24 * 7,
secretKey: 'Missing You!(John waite)',
},
}; };

@ -1,6 +1,7 @@
export default { export default {
// 主服务 // 主服务
master: { master: {
systemName: '心曲Tune',
host: '127.0.0.1', host: '127.0.0.1',
port: '3000', port: '3000',
}, },
@ -32,4 +33,18 @@ export default {
dbNumber: 3, dbNumber: 3,
}, },
}, },
email: {
host: 'smtp.qq.com',
user: 'togy.gc@qq.com',
pass: 'miidbsjeuqcxddbf',
},
// 加密
encryption: {
salt: 'время,вперёд!', // 盐
secretKey: 'быть всегда готовым!', // 密钥
},
token: {
timeout: 1000 * 60 * 60 * 24 * 7,
secretKey: 'Missing You!(John waite)',
},
}; };

@ -31,8 +31,10 @@
"class-transformer": "^0.5.1", "class-transformer": "^0.5.1",
"class-validator": "^0.14.0", "class-validator": "^0.14.0",
"fastify-swagger": "^5.2.0", "fastify-swagger": "^5.2.0",
"jsonwebtoken": "^9.0.0",
"log4js": "^6.9.1", "log4js": "^6.9.1",
"mysql2": "^3.2.0", "mysql2": "^3.2.0",
"nodemailer": "^6.9.1",
"redis": "^4.6.5", "redis": "^4.6.5",
"reflect-metadata": "^0.1.13", "reflect-metadata": "^0.1.13",
"rxjs": "^7.2.0" "rxjs": "^7.2.0"

@ -26,8 +26,10 @@ specifiers:
eslint-plugin-prettier: ^4.0.0 eslint-plugin-prettier: ^4.0.0
fastify-swagger: ^5.2.0 fastify-swagger: ^5.2.0
jest: 29.3.1 jest: 29.3.1
jsonwebtoken: ^9.0.0
log4js: ^6.9.1 log4js: ^6.9.1
mysql2: ^3.2.0 mysql2: ^3.2.0
nodemailer: ^6.9.1
prettier: ^2.3.2 prettier: ^2.3.2
redis: ^4.6.5 redis: ^4.6.5
reflect-metadata: ^0.1.13 reflect-metadata: ^0.1.13
@ -52,8 +54,10 @@ dependencies:
class-transformer: 0.5.1 class-transformer: 0.5.1
class-validator: 0.14.0 class-validator: 0.14.0
fastify-swagger: 5.2.0 fastify-swagger: 5.2.0
jsonwebtoken: 9.0.0
log4js: 6.9.1 log4js: 6.9.1
mysql2: 3.2.0 mysql2: 3.2.0
nodemailer: 6.9.1
redis: 4.6.5 redis: 4.6.5
reflect-metadata: 0.1.13 reflect-metadata: 0.1.13
rxjs: 7.8.0 rxjs: 7.8.0
@ -2047,6 +2051,10 @@ packages:
node-int64: 0.4.0 node-int64: 0.4.0
dev: true dev: true
/buffer-equal-constant-time/1.0.1:
resolution: {integrity: sha512-zRpUiDwd/xk6ADqPMATG8vc9VPrkck7T07OIx0gnjmJAnHnTVXNQG3vfvWNuiZIkwu9KrKdA1iJKfsfTVxE6NA==}
dev: false
/buffer-from/1.1.2: /buffer-from/1.1.2:
resolution: {integrity: sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ==} resolution: {integrity: sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ==}
dev: true dev: true
@ -2434,6 +2442,12 @@ packages:
engines: {node: '>=12'} engines: {node: '>=12'}
dev: false dev: false
/ecdsa-sig-formatter/1.0.11:
resolution: {integrity: sha512-nagl3RYrbNv6kQkeJIpt6NJZy8twLB/2vtz6yN9Z4vRKHN4/QZJIEbqohALSgwKdnksuY3k5Addp5lg8sVoVcQ==}
dependencies:
safe-buffer: 5.2.1
dev: false
/ee-first/1.1.1: /ee-first/1.1.1:
resolution: {integrity: sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow==} resolution: {integrity: sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow==}
dev: false dev: false
@ -3938,6 +3952,31 @@ packages:
graceful-fs: 4.2.10 graceful-fs: 4.2.10
dev: true dev: true
/jsonwebtoken/9.0.0:
resolution: {integrity: sha512-tuGfYXxkQGDPnLJ7SibiQgVgeDgfbPq2k2ICcbgqW8WxWLBAxKQM/ZCu/IT8SOSwmaYl4dpTFCW5xZv7YbbWUw==}
engines: {node: '>=12', npm: '>=6'}
dependencies:
jws: 3.2.2
lodash: 4.17.21
ms: 2.1.3
semver: 7.3.8
dev: false
/jwa/1.4.1:
resolution: {integrity: sha512-qiLX/xhEEFKUAJ6FiBMbes3w9ATzyk5W7Hvzpa/SLYdxNtng+gcurvrI7TbACjIXlsJyr05/S1oUhZrc63evQA==}
dependencies:
buffer-equal-constant-time: 1.0.1
ecdsa-sig-formatter: 1.0.11
safe-buffer: 5.2.1
dev: false
/jws/3.2.2:
resolution: {integrity: sha512-YHlZCB6lMTllWDtSPHz/ZXTsi8S00usEV6v1tjq8tOUZzw7DpSDWVXjXDre6ed1w/pd495ODpHZYSdkRTsa0HA==}
dependencies:
jwa: 1.4.1
safe-buffer: 5.2.1
dev: false
/kleur/3.0.3: /kleur/3.0.3:
resolution: {integrity: sha512-eTIzlVOSUR+JxdDFepEYcBMtZ9Qqdef+rnzWdRZuMbOywu5tO2w2N7rqjoANZ5k9vywhL6Br1VRjUIgTQx4E8w==} resolution: {integrity: sha512-eTIzlVOSUR+JxdDFepEYcBMtZ9Qqdef+rnzWdRZuMbOywu5tO2w2N7rqjoANZ5k9vywhL6Br1VRjUIgTQx4E8w==}
engines: {node: '>=6'} engines: {node: '>=6'}
@ -4242,6 +4281,11 @@ packages:
resolution: {integrity: sha512-5GFldHPXVG/YZmFzJvKK2zDSzPKhEp0+ZR5SVaoSag9fsL5YgHbUHDfnG5494ISANDcK4KwPXAx2xqVEydmd7w==} resolution: {integrity: sha512-5GFldHPXVG/YZmFzJvKK2zDSzPKhEp0+ZR5SVaoSag9fsL5YgHbUHDfnG5494ISANDcK4KwPXAx2xqVEydmd7w==}
dev: true dev: true
/nodemailer/6.9.1:
resolution: {integrity: sha512-qHw7dOiU5UKNnQpXktdgQ1d3OFgRAekuvbJLcdG5dnEo/GtcTHRYM7+UfJARdOFU9WUQO8OiIamgWPmiSFHYAA==}
engines: {node: '>=6.0.0'}
dev: false
/normalize-path/3.0.0: /normalize-path/3.0.0:
resolution: {integrity: sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==} resolution: {integrity: sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==}
engines: {node: '>=0.10.0'} engines: {node: '>=0.10.0'}

@ -17,12 +17,12 @@ export class GdevinterceptorInterceptor implements NestInterceptor {
} }
intercept(context: ExecutionContext, next: CallHandler): Observable<any> { intercept(context: ExecutionContext, next: CallHandler): Observable<any> {
const request = context.switchToHttp().getRequest(); const request = context.switchToHttp().getRequest();
this.logger.error(request.method, request.url, '<=='); this.logger.warn(request.method, request.url, '<==');
const now = Date.now(); const now = Date.now();
return next.handle().pipe( return next.handle().pipe(
tap((data) => { tap((data) => {
this.logger.error( this.logger.warn(
request.method, request.method,
request.url, request.url,
'==>', '==>',

@ -28,7 +28,11 @@ export class GresponseinterceptorInterceptor implements NestInterceptor {
? data.data ? data.data
: data, : data,
statusCode: response.statusCode, statusCode: response.statusCode,
success: true, success: data
? data.success === false
? false
: true
: true,
message: message:
typeof data == 'string' typeof data == 'string'
? 'ok' ? 'ok'

@ -1,6 +1,6 @@
import { Injectable } from '@nestjs/common'; import { Injectable } from '@nestjs/common';
import * as mysql from 'mysql2/promise'; import * as mysql from 'mysql2/promise';
import config from '../../../config'; import config from '@CFG/index';
@Injectable() @Injectable()
export class GdatabaseService { export class GdatabaseService {

@ -0,0 +1,8 @@
import { Global, Module } from '@nestjs/common';
import { GemailService } from './gemail.service';
@Global()
@Module({
providers: [GemailService],
exports: [GemailService],
})
export class GemailModule {}

@ -0,0 +1,231 @@
import { Injectable } from '@nestjs/common';
import * as nodemailer from 'nodemailer';
import config from '@CFG/index';
@Injectable()
export class GemailService {
// @ 邮件服务
private transporter;
// @ 邮箱配置
private EMAILCONFIG;
// @ 系统名称,作用于邮件主题。
private sysName: string;
constructor() {
this.start();
}
// ? ?
// ? 函数名称: start
// ? 函数描述: 生成邮件服务
// ? ?
private start() {
const EMAILCONFIG = config().email;
const transporter = nodemailer.createTransport({
//node_modules/nodemailer/lib/well-known/services.json 查看相关的配置,如果使用qq邮箱,就查看qq邮箱的相关配置
host: EMAILCONFIG.host,
// secureConnection:true,
service: 'qq', //类型qq邮箱
// port: 465,
secure: true, // true for 465, false for other ports
auth: {
user: EMAILCONFIG.user, // 发送方的邮箱
pass: EMAILCONFIG.pass, // smtp 的授权码
},
//pass 不是邮箱账户的密码而是stmp的授权码(必须是相应邮箱的stmp授权码)
//邮箱---设置--账户--POP3/SMTP服务---开启---获取stmp授权码
});
this.transporter = transporter;
this.EMAILCONFIG = EMAILCONFIG;
this.sysName = config().master.systemName;
}
// ? ?
// ? 函数名称: senMail(邮件内容, 收件人邮箱)
// ? 函数描述: 发送邮件主函数
// ? ?
private senMail(html, mail) {
return new Promise((res, rej) => {
const mailOptions = {
from: '"TOGY | 心曲Tune" <togy.gc@qq.com>', // 发送方
to: mail, //接收者邮箱,多个邮箱用逗号间隔
subject: `${this.sysName}`, // 标题
text: 'Hello world?', // 文本内容
html,
};
this.transporter.sendMail(mailOptions, (error, info) => {
if (error) {
rej([error, info]);
} else {
res(info); //因为是异步 所有需要回调函数通知成功结果
}
});
});
}
// ? ?
// ? 函数名称: sendTestEmail
// ? 函数描述: 给指定邮箱发送测试验证码
// ? ?
public sendTestEmail(email) {
return new Promise(async (res, rej) => {
const test = `
<style>
.emailBody{
position: relative;
min-height: 400px;
min-width: 300px;
height: 100vh;
width:90vw;
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>ABCDEF</div>
</div>
<div class="emailDescription"><br/>5使</div>
</div>
</div>
`;
try {
const resd = await this.senMail(test, email);
res(resd);
} catch (e) {
rej(e);
}
});
}
// ? ?
// ? 函数名称: sendRegisterCodeMail(邮箱, 验证码)
// ? 函数描述: 给制定邮箱发送注册验证码
// ? ?
public sendRegisterCodeMail(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,16 +1,29 @@
import { Injectable } from '@nestjs/common'; import { Injectable } from '@nestjs/common';
import { createClient } from 'redis'; import { createClient } from 'redis';
import config from '../../../config'; import config from '../../../config';
import { GtoolsService } from '@/Gservice/GTOOLS/gtools.service';
export declare interface ISetRegisterEmailCode {
data: any;
message: string;
registerCode?: string;
}
@Injectable() @Injectable()
export class GredisService { export class GredisService {
public DB; public DB;
private config; private config;
// @ 专注于注册的数据库
private readonly UserRegisterPoll = 1;
constructor() { constructor(private readonly gtools: GtoolsService) {
this.config = config().redis.starLight; this.config = config().redis.starLight;
this.start(); this.start();
} }
// ? ?
// ? 函数名称: start
// ? 函数描述: 连接redis
// ? ?
private async start() { private async start() {
const client = createClient({ const client = createClient({
url: `redis://${this.config.username}:${this.config.password}@${this.config.host}:${this.config.port}/${this.config.dbNumber}`, url: `redis://${this.config.username}:${this.config.password}@${this.config.host}:${this.config.port}/${this.config.dbNumber}`,
@ -18,4 +31,100 @@ export class GredisService {
await client.connect(); await client.connect();
this.DB = client; this.DB = client;
} }
// ? ?
// ? 函数名称: setRegisterEmailCode(邮箱)
// ? 函数描述: 设置注册邮箱验证
// ? ?
public setRegisterEmailCode(email): Promise<ISetRegisterEmailCode> {
return new Promise(async (res, rej) => {
this.DB.select(this.UserRegisterPoll);
try {
const key = 'Register-' + email;
const result = await this.DB.get(key);
if (result === null) {
const registerCode = this.gtools.getRandomString(6);
try {
const data = await this.DB.setEx(
key,
300,
registerCode,
);
res({
data,
registerCode,
message: '生成验证码成功。',
});
} catch (e) {
rej({
data: e,
message: '生成验证码失败。',
});
}
} else {
rej({
data: null,
message: '已发送过邮件,请稍后再重试。',
});
}
} catch (e) {
rej({
data: e,
message: '查找注册码失败。',
});
}
});
}
// ? ?
// ? 函数名称: getRegisterEmailCode(邮箱)
// ? 函数描述: 读取注册验证码
// ? ?
public getRegisterEmailCode(email): Promise<ISetRegisterEmailCode> {
return new Promise(async (res, rej) => {
this.DB.select(this.UserRegisterPoll);
const key = 'Register-' + email;
try {
const result = await this.DB.get(key);
if (result === null) {
rej({
data: {},
message: '未找到相匹配的验证码!',
});
} else {
res({
data: {},
message: '获取成功',
registerCode: result as string,
});
}
} catch (e) {
rej({
data: e,
message: '从Redis获取注册吗出现错误!',
});
}
});
}
// ? ?
// ? 函数名称: removeRegisterEmailCode
// ? 函数描述:
// ? ?
public removeRegisterEmailCode(email): Promise<ISetRegisterEmailCode> {
return new Promise(async (res, rej) => {
this.DB.select(this.UserRegisterPoll);
const key = 'Register-' + email;
try {
const result = await this.DB.del(key);
res({
message: '清除注册验证码成功!',
data: {},
});
} catch (e) {
res({
message: '清除注册验证码失败!',
data: e,
});
}
});
}
} }

@ -0,0 +1,8 @@
import { Global, Module } from '@nestjs/common';
import { GtoolsService } from './gtools.service';
@Global()
@Module({
providers: [GtoolsService],
exports: [GtoolsService],
})
export class GtoolsModule {}

@ -0,0 +1,137 @@
import { Injectable } from '@nestjs/common';
import * as crypto from 'crypto';
import config from '@CFG/index';
import * as jwt from 'jsonwebtoken';
// @ 不可逆加密
enum HASHT {
MD5 = 'md5', // 32位
SHA256 = 'sha256', // 64位
SHA512 = 'sha512', // 128位
}
// @ 可逆加密
enum AEST {
AES128 = 'aes-128-cbc',
AES256 = 'aes-256-gcm',
}
@Injectable()
export class GtoolsService {
// @ 加解密配置文件
private readonly encryptionCFG = config().encryption;
private readonly tokenCFG = config().token;
// @ AES-128-cec加解密 iv和key
private AES128CEC_key;
private AES128CEC_iv;
constructor() {
this.AES128CEC_key = Buffer.from(
this.makeHASH(this.encryptionCFG.secretKey, HASHT.MD5).slice(-16),
'utf8',
);
this.AES128CEC_iv = Buffer.from(
this.makeHASH(this.encryptionCFG.salt, HASHT.MD5).slice(-16),
'utf8',
);
}
// ? ?
// ? 函数名称: getRandomString(长度,大小写默认big)
// ? 函数描述: 获取制定长度的随机字符串
// ? ?
public getRandomString(len: number, size = 'big'): string {
if (size == 'big') {
return Math.random()
.toString(36)
.slice(-len)
.toString()
.toUpperCase();
} else {
return Math.random()
.toString(36)
.slice(-len)
.toString()
.toLowerCase();
}
}
// ? ?
// ? 函数名称: makeUUID
// ? 函数描述: 生成UUID
// ? ?
public makeUUID() {
return crypto.randomUUID().split('-').join('').toUpperCase();
}
// ? ?
// ? 函数名称: makeHASH(加密内容, 加密编码默认为SHA512)
// ? 函数描述: =加密字符串
// ? ?
public makeHASH(plaintext, algorithm = HASHT.SHA512) {
const sha512 = crypto.createHash(algorithm);
const sha512Sum = sha512.update(plaintext + this.encryptionCFG.salt);
const ciphertext = sha512Sum.digest('hex');
return ciphertext;
}
//#region 可逆加密
// ? ?
// ? 函数名称: encrypt
// ? 函数描述: 加密字符串
// ? ?
encrypt(plaintext, algorithm = AEST.AES128) {
const cipher = crypto.createCipheriv(
algorithm,
this.AES128CEC_key,
this.AES128CEC_iv,
); // 初始化加密算法
let ciphertext = cipher.update(plaintext, 'utf8', 'hex');
ciphertext += cipher.final('hex');
return ciphertext;
}
// ? ?
// ? 函数名称: decrypt
// ? 函数描述: 解密字符串
// ? ?
decrypt(ciphertext, algorithm = AEST.AES128) {
let plaintext = '';
const cipher = crypto.createDecipheriv(
algorithm,
this.AES128CEC_key,
this.AES128CEC_iv,
);
plaintext += cipher.update(ciphertext, 'hex', 'utf8');
plaintext += cipher.final('utf8');
return plaintext;
}
//#endredion
//#region jwt
createToken(data) {
const plaintextToken = jwt.sign(data, this.tokenCFG.secretKey, {
expiresIn: this.tokenCFG.timeout / 1000,
});
const ciphertextToken = this.encrypt(plaintextToken);
return ciphertextToken;
}
resolveToken(ciphertextToken) {
try {
// 解析密文
const plaintextToken = this.decrypt(ciphertextToken);
const data = jwt.verify(plaintextToken, this.tokenCFG.secretKey);
return {
token: true,
data,
};
} catch (e) {
return {
token: false,
data: e,
};
}
}
//#endredion
}

@ -3,6 +3,8 @@ import { ConfigModule } from '@nestjs/config';
import { GredisModule } from '@/Gservice/GREDIS/gredis.module'; import { GredisModule } from '@/Gservice/GREDIS/gredis.module';
import { GloggerModule } from '@/Gservice/GLOGGER/glogger.module'; import { GloggerModule } from '@/Gservice/GLOGGER/glogger.module';
import { GdatabaseModule } from '@/Gservice/GDATABASE/gdatabase.module'; import { GdatabaseModule } from '@/Gservice/GDATABASE/gdatabase.module';
import { GemailModule } from '@/Gservice/GEMAIL/gemail.module';
import { GtoolsModule } from '@/Gservice/GTOOLS/gtools.module';
import config from '@CFG/index'; import config from '@CFG/index';
import { StarlightModule } from '@/starlight/starlight.module'; import { StarlightModule } from '@/starlight/starlight.module';
@ -16,6 +18,8 @@ import { StarlightModule } from '@/starlight/starlight.module';
StarlightModule, StarlightModule,
GdatabaseModule, GdatabaseModule,
GredisModule, GredisModule,
GemailModule,
GtoolsModule,
], ],
controllers: [], controllers: [],
providers: [], providers: [],

@ -40,6 +40,12 @@ export class RegisterEmailCheckoutEmailDto {
@IsString({ message: '邮箱应为字符串格式!' }) @IsString({ message: '邮箱应为字符串格式!' })
email: string; email: string;
} }
export class RegisterEmailCheckoutUsernameDto {
// @ 用户名
@Length(8, 128, { message: '请将用户名长度控制在8到128位之间!' })
username: string;
}
export class RegisterEmailSignUpDto extends RegisterEmailCheckoutEmailDto { export class RegisterEmailSignUpDto extends RegisterEmailCheckoutEmailDto {
// @ 验证码 // @ 验证码
@Length(6, 6, { message: '验证码是6位字符!' }) @Length(6, 6, { message: '验证码是6位字符!' })
@ -51,7 +57,7 @@ export class RegisterEmailSignUpDto extends RegisterEmailCheckoutEmailDto {
// @ 用户名 // @ 用户名
@Length(8, 128, { message: '请将用户名长度控制在8到128位之间!' }) @Length(8, 128, { message: '请将用户名长度控制在8到128位之间!' })
usernmae: string; username: string;
// @ 真实姓名 // @ 真实姓名
@IsOptional() @IsOptional()

@ -0,0 +1,18 @@
/* *
* * @name: signIn.dto.ts
* * @description:
* * @author: xi
* * @date: 2023/3/26 22:41
* * */
import { Length } from 'class-validator';
export class SignInPasswdEntryDto {
// @ 用户名
@Length(8, 128, { message: '请将用户名长度控制在8到128位之间!' })
username: string;
// @ 密码
@Length(8, 128, { message: '请将密码长度控制在8到128位之间!' })
password: string;
}

@ -15,6 +15,8 @@ import {
UseGuards, UseGuards,
Req, Req,
Inject, Inject,
ExecutionContext,
createParamDecorator,
} from '@nestjs/common'; } from '@nestjs/common';
import { ConfigService } from '@nestjs/config'; import { ConfigService } from '@nestjs/config';
import { ApiTags } from '@nestjs/swagger'; import { ApiTags } from '@nestjs/swagger';
@ -29,8 +31,18 @@ import { GdatabaseService } from '@/Gservice/GDATABASE/gdatabase.service';
import { GredisService } from '@/Gservice/GREDIS/gredis.service'; import { GredisService } from '@/Gservice/GREDIS/gredis.service';
import { import {
RegisterEmailCheckoutEmailDto, RegisterEmailCheckoutEmailDto,
RegisterEmailCheckoutUsernameDto,
RegisterEmailSignUpDto, RegisterEmailSignUpDto,
} from './dto/register.dto'; } from '@/starlight/dto/register.dto';
import { SignInPasswdEntryDto } from '@/starlight/dto/signIn.dto';
import { VerifyGuard } from '@/starlight/verify.guard';
// nest g d [name]
// 自定义装饰器
const getUser = createParamDecorator((data: string, ctx: ExecutionContext) => {
const req = ctx.switchToHttp().getRequest();
return req.user;
});
// C C // C C
// C 类名称: StarlightController // C 类名称: StarlightController
@ -77,16 +89,47 @@ export class StarlightController {
} }
// ? ? // ? ?
// ? 函数名称: registerEmailSignUp // ? 函数名称: registerEmailCheckoutUsername
// ? 函数描述: // ? 函数描述: 查重用户名
// ? ? // ? ?
//#endregion @Get('register/email/checkoutUsername/:username')
@Post('/register/email/signUp') registerEmailCheckoutUsername(
registerEmailSignUpDto( @Param() param: RegisterEmailCheckoutUsernameDto,
@Body() body: RegisterEmailSignUpDto,
): Promise<object> { ): Promise<object> {
return this.starlightService.registerEmailCheckoutUsername(param);
}
// ? ?
// ? 函数名称: registerEmailSignUp
// ? 函数描述: 邮箱注册
// ? ?
@Post('register/email/signUp')
registerEmailSignUp(@Body() body: RegisterEmailSignUpDto): Promise<object> {
return this.starlightService.registerEmailSignUpDto(body); return this.starlightService.registerEmailSignUpDto(body);
} }
//#endregion
//#region 登陆
// ? ?
// ? 函数名称: signInPasswdEntry
// ? 函数描述: 使用密码和用户名登陆
// ? ?
@Post('/signIn/passwd/entry')
signInPasswdEntry(@Body() body: SignInPasswdEntryDto): Promise<object> {
return this.starlightService.signInPasswdEntry(body);
}
// ? ?
// ? 函数名称:
// ? 函数描述: 测试Token
// ? ?
@UseGuards(VerifyGuard)
@Get('/signIn/testToken')
testToken(@getUser() user) {
this.logger.info(user);
return {};
}
//#endredion
//#region 测试 //#region 测试
@Post() @Post()

@ -9,12 +9,17 @@ import { CreateStarlightDto } from './dto/create-starlight.dto';
import { UpdateStarlightDto } from './dto/update-starlight.dto'; import { UpdateStarlightDto } from './dto/update-starlight.dto';
import { import {
RegisterEmailCheckoutEmailDto, RegisterEmailCheckoutEmailDto,
RegisterEmailCheckoutUsernameDto,
RegisterEmailSignUpDto, RegisterEmailSignUpDto,
sex,
} from './dto/register.dto'; } from './dto/register.dto';
import { ConfigService } from '@nestjs/config'; import { ConfigService } from '@nestjs/config';
import { GloggerService } from '@/Gservice/GLOGGER/glogger.service'; import { GloggerService } from '@/Gservice/GLOGGER/glogger.service';
import { GdatabaseService } from '@/Gservice/GDATABASE/gdatabase.service'; import { GdatabaseService } from '@/Gservice/GDATABASE/gdatabase.service';
import { GredisService } from '@/Gservice/GREDIS/gredis.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';
// C C // C C
// C 类名称: StarlightService // C 类名称: StarlightService
@ -31,45 +36,309 @@ export class StarlightService {
private readonly database: GdatabaseService, private readonly database: GdatabaseService,
// @ Redis服务 // @ Redis服务
private readonly redis: GredisService, private readonly redis: GredisService,
// @ 邮件服务
private readonly email: GemailService,
// @ 工具服务
private readonly tools: GtoolsService,
) {} ) {}
//#region 邮箱注册 //#region 邮箱注册
// ? ? // ? ?
// ? 函数名称: registerEmailCheckoutEmail // ? 函数名称: registerEmailCheckoutEmail
// ? 函数描述: 查重邮箱注册 // ? 函数描述: 查重邮箱注册邮箱
// ? ? // ? ?
// ? ? // ? ?
async registerEmailCheckoutEmail(params: RegisterEmailCheckoutEmailDto) { public async registerEmailCheckoutEmail(
return { params: RegisterEmailCheckoutEmailDto,
data: { ok: 'ok' }, ) {
message: '', // ! 从数据库用户身份表查询有没有已经使用的邮箱
success: true, const [rows] = await this.database.DB.execute(
`SELECT * FROM user_info_verify WHERE email = ? AND state = 0`,
[params.email.trim()],
);
// ! 判断是否存在此邮箱
const resd = {
data: {},
message: '此邮箱已被使用,请尝试使用其他邮箱注册!',
success: false,
};
if (rows.length == 0) {
resd.message = '此邮箱未被使用,可以作为注册邮箱!';
resd.success = true;
}
return resd;
}
// ? ?
// ? 函数名称: registerEmailCheckoutUsername
// ? 函数描述: 查重邮箱注册用户名
// ? ?
// ? ?
public async registerEmailCheckoutUsername(
params: RegisterEmailCheckoutUsernameDto,
) {
// ! 从数据库用户身份表查询有没有已经使用的用户名
const [rows] = await this.database.DB.execute(
`SELECT * FROM user_info_verify WHERE username = ? AND state = 0`,
[params.username.trim()],
);
// ! 判断是否存在此用户名
const resd = {
data: {},
message: '此用户名已被使用,请尝试使用其他用户名注册!',
success: false,
}; };
if (rows.length == 0) {
resd.message = '此用户名未被使用,可以作为注册用户名!';
resd.success = true;
}
return resd;
} }
// ? 函数名称: registerEmailSendCode // ? 函数名称: registerEmailSendCode
// ? 函数描述: 发送邮箱注册验证码 // ? 函数描述: 发送邮箱注册验证码
// ? ? // ? ?
async registerEmailSendCode(params: RegisterEmailCheckoutEmailDto) { public async registerEmailSendCode(params: RegisterEmailCheckoutEmailDto) {
return { const { email } = params;
data: { ok: 'ok' }, // ! 1. 验证是否存在已经注册的
message: '', const checkoutEmail = await this.registerEmailCheckoutEmail({
success: true, email,
});
const resd = {
data: {},
message: '此邮箱已被使用,请尝试使用其他邮箱注册!',
success: false,
}; };
if (!checkoutEmail.success) {
return resd;
}
// ! 2. 验证是否存在验证码
let registerCode: any;
try {
const redisResd = await this.redis.setRegisterEmailCode(email);
registerCode = redisResd.registerCode as string;
} catch (e) {
this.logger.error(e);
resd.message = e.message;
return resd;
}
// ! 3. 发送验证码
try {
const result = await this.email.sendRegisterCodeMail(
email,
registerCode,
);
resd.success = true;
resd.message = '发送验证码成功,请注意查收!';
return resd;
} catch (e) {
this.logger.error(e);
resd.message = e.message;
return resd;
}
} }
// ? ? // ? ?
// ? 函数名称: registerEmailSignUpDto // ? 函数名称: registerEmailSignUpDto
// ? 函数描述: 邮箱注册 // ? 函数描述: 邮箱注册
// ? ? // ? ?
async registerEmailSignUpDto(body: RegisterEmailSignUpDto) { public async registerEmailSignUpDto(body: RegisterEmailSignUpDto) {
return { const { email, code, username } = body;
data: { ok: 'ok' }, // ! 1. 查重邮箱
message: '', const checkoutEmail = await this.registerEmailCheckoutEmail({
success: true, email,
});
const resd = {
data: {},
message: '此邮箱已被使用,请尝试使用其他邮箱注册!',
success: false,
}; };
if (!checkoutEmail.success) {
return resd;
}
// ! 2. 查询注册验证码是否存在,比对验证码
try {
const { registerCode } = await this.redis.getRegisterEmailCode(
email,
);
if (code.trim().toUpperCase() !== registerCode) {
resd.message = '验证码不匹配!';
return resd;
}
} catch (e) {
resd.message = e.message;
return resd;
}
// ! 3. 查重用户名
const checkoutUsername = await this.registerEmailCheckoutUsername({
username,
});
if (!checkoutUsername.success) {
resd.message = '此用户名已被使用,请尝试使用其他用户名注册!';
return resd;
}
// ! 4. 清除空格,加密密码并插入信息
const password = body.password.trim();
const passwordHASH = this.tools.makeHASH(password);
const uuid = this.tools.makeUUID();
const createTime = new Date();
const s = {
email: email.trim(),
username: username.trim(),
realname: body.realname?.trim(),
nickname: body.nickname?.trim(),
birthday: new Date(body.birthday),
sex: sex[body.sex],
address: body.address?.trim(),
country: body.country?.trim(),
profile: body.profile?.trim(),
github_url: body.githubUrl?.trim(),
personal_url: body.personalUrl?.trim(),
alibaba_id: body.alibabaId?.trim(),
tiktok_id: body.tiktokId?.trim(),
weibo_id: body.weiboId?.trim(),
};
Object.keys(s).map((item) => {
return s[item] != undefined ? '' : (s[item] = null);
});
// ! -- 基础表 user_info_base
const userInfoBaseSQL = `INSERT INTO user_info_base (uuid, createtime) VALUES (?, ?);`;
try {
const [rows] = await this.database.DB.execute(userInfoBaseSQL, [
uuid,
createTime,
]);
} catch (e) {
const message = '插入基础表失败!';
this.logger.error({
data: e,
message,
});
resd.message = message;
return resd;
}
// ! -- 验证表 user_info_verify
const userInfoVerifySQL = `INSERT INTO user_info_verify (uuid, email, username, createtime) VALUES (?, ?, ?, ?);`;
try {
const [rows] = await this.database.DB.execute(userInfoVerifySQL, [
uuid,
s.email,
s.username,
createTime,
]);
} catch (e) {
const message = '插入验证表失败!';
this.logger.error({
data: e,
message,
});
resd.message = message;
return resd;
}
// ! -- 密码表 user_info_passwd
const userInfoPasswdSQL = `INSERT INTO user_info_passwd (uuid, passwd, createtime) VALUES (?, ?, ?);`;
try {
const [rows] = await this.database.DB.execute(userInfoPasswdSQL, [
uuid,
passwordHASH,
createTime,
]);
} catch (e) {
const message = '插入密码表失败!';
this.logger.error({
data: e,
message,
});
// 删除用户验证表的这条记录
const DELSQL = `DELETE user_info_verify WHERE email = ?`;
try {
const [rows] = await this.database.DB.execute(DELSQL, [
s.email,
]);
this.logger.info(rows);
} catch (e) {
this.logger.error({
data: e,
message:
'删除用户验证表失败,此候补操作依然失效,系统错误!',
});
}
resd.message = message;
return resd;
}
// ! -- 拓展表 user_info_extra
const userInfoExtraSQL = `INSERT INTO user_info_extra (uuid, realname, nickname, birthday, sex, address, country, profile, alibaba_id, tiktok_id, weibo_id, github_url, personal_url, createtime) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?);`;
try {
const [rows] = await this.database.DB.execute(userInfoExtraSQL, [
uuid,
s.realname,
s.nickname,
s.birthday,
s.sex,
s.address,
s.country,
s.profile,
s.alibaba_id,
s.tiktok_id,
s.weibo_id,
s.github_url,
s.personal_url,
createTime,
]);
} catch (e) {
const message = '插入用户拓展信息时出现错误!';
this.logger.error({
data: e,
message,
});
resd.message = message;
return resd;
}
this.logger.info(s);
// ! 5. 清除验证码
try {
const result = await this.redis.removeRegisterEmailCode(email);
} catch (e) {
this.logger.warn(e);
}
resd.message = '注册成功!';
resd.success = true;
return resd;
} }
//#endregion //#endregion
//#region
//#region 登陆
// ? ?
// ? 函数名称: signInPasswdEntry
// ? 函数描述: 使用密码和用户名登陆
// ? ?
public async signInPasswdEntry(body: SignInPasswdEntryDto) {
const { username, password } = body;
// ! 加密密码
const passwordHash = this.tools.makeHASH(password);
// ! 验证密码
// 查找最后一条
// ! 生成token
const token = this.tools.createToken({
username,
signInTime: new Date().getTime(),
});
this.logger.info(token);
// ! Redis 登陆存储策略
// ! 返回数据
return {
message: '登陆成功',
data: {
token: '',
},
};
}
//#endredion
//#region 测试啊
create(createStarlightDto: CreateStarlightDto) { create(createStarlightDto: CreateStarlightDto) {
return 'This action adds a new starlight'; return 'This action adds a new starlight';
@ -94,5 +363,5 @@ export class StarlightService {
remove(id: number) { remove(id: number) {
return `This action removes a #${id} starlight`; return `This action removes a #${id} starlight`;
} }
//#endredion //#endregion
} }

@ -0,0 +1,42 @@
import {
CanActivate,
ExecutionContext,
Injectable,
UnauthorizedException,
} from '@nestjs/common';
import { Observable } from 'rxjs';
import { GloggerService } from '@/Gservice/GLOGGER/glogger.service';
import { GtoolsService } from '@/Gservice/GTOOLS/gtools.service';
// 此文件为守卫
@Injectable()
export class VerifyGuard implements CanActivate {
constructor(
private readonly logger: GloggerService,
private readonly tools: GtoolsService,
) {}
canActivate(
context: ExecutionContext,
): boolean | Promise<boolean> | Observable<boolean> {
const request = context.switchToHttp().getRequest();
const url = request.url;
// this.logger.info(url);
const token = request.headers.authorization;
// this.logger.info(token);
const data = this.tools.resolveToken(token);
// this.logger.info(data);
if (data.token) {
context.switchToHttp().getRequest().user = '32';
// this.logger.info(request.user);
}
// console.log(request);
// console.log(request.url);request.headers
// this.logger.info('GUARG', request.url, "TEST");
// 自定义返回消息
// throw new UnauthorizedException({
// statusCode: 403,
// message: '你没有访问权限',
// error: 'Forbidden',
// });
return true;
}
}
Loading…
Cancel
Save