nest 学习3

学习小册(nest通关秘籍)

邮箱验证码登陆

流程图:

在这里插入图片描述
邮箱作为key,生成随机验证码,然后放到redis中。调用邮箱api发送邮箱。

前端获取到code后,将验证码输入传给后端,后端根据邮箱取出redis数据,比对验证码,通过则去mysql查用户数据,生成jwt token返回。

定时任务+Redis实现阅读量计数
  • 1 在redis中存储user和article的关系,比如user_test_article_nest_sturdy,10分钟后删除。期间,如果判断存在这个key,则说明该用户看过这篇文章,不更新阅读量,否则才更新。
  • 10分钟后,这个人在看文章,就算一次新的阅读量。
  • 访问文章的时候,把阅读量写到redis中,不更新数据库,等业务低峰期再把最新的阅读量写入数据库,比如凌晨4点写入数据库。sql压力就不大。
定时任务表达式

在这里插入图片描述
比如

7 12 13 10 * ?
每个月的10号的13:12:07执行,然后星期需要用?,因为不知道具体星期几,用*会每天都执行。

事件通信

两个不相关的业务模块,需要互相调用的时候,注入对应的模块是不合理的,这时候可以用到event emitter(事件总线)通信,达到解耦的目的。

import { Module } from '@nestjs/common';
import { AppController } from './app.controller';
import { AppService } from './app.service';
import { EventEmitterModule } from '@nestjs/event-emitter';

@Module({
  imports: [
    EventEmitterModule.forRoot(),
  ],
  controllers: [AppController],
  providers: [AppService],
})
export class AppModule {}

触发,
在这里插入图片描述
监听

@OnEvent('aaa.find')
handleAaaFind(data) {
    console.log('aaa find 调用', data)
    this.create(new CreateBbbDto());
}

当调用findAll的时候,就会触发aaa.find事件,这时候handleAaaFind就会被执行。

pinyin+和风天气api实现天气查询

和风天气提供api调用查询天气。
步骤,先查询地方的id,再通过id查询天气。

https://geoapi.qweather.com/v2/city/lookup?location=qingdao&key=注册和风项目的key//获取通过地方中文名地方id
https://api.qweather.com/v7/weather/7d?location=101120201&key=注册和风项目的key //通过地方id获取天气信息。

pinyin这个包可以拿到中文的拼音。

让用户直接选择城市,网上也有很多json数据,可以直接拿到城市对应的id。

记录请求id

通过header里的X-Forwarded-For可以拿到请求浏览器的ip地址,通过ip可以获取对应地区信息。

短链服务

长链接分享不方便,可以用短链服务。
用递增id,记录对应每个url,再把映射关系存到数据库。
用户访问短链的时候,从数据库中取出链接,返回302/301重定向。
302是临时重定向,301是永久重定向。
301对短链服务的压力较小,但302可以记录链接的点击次数,做分析,一般都是用302来重定向。

一般每个url的id会使用base64/62编码实现。
base64就是26个大/小写字母,10个数字,两个特殊字符,一共64
base62则是去掉了两个特殊字符,一共62.

压缩码

创建一张存放映射关系的表,用mysql的自增id进行base62后,最为压缩吗,访问短链的时候,根据压缩码查询这个表,拿到长链接。

这样有个问题,用自增id做压缩吗,别人容易拿到上一个/下一个压缩码,会有安全问题。

用crypto加盐呢?

const crypto = require('crypto');

function md5(str) {
  const hash = crypto.createHash('md5');
  hash.update(str);
  return hash.digest('hex');
}

console.log(md5('111222'))

比较长,有32位。

用随机数的方式生产压缩码。

const base62 = require("base62/lib/ascii");

function generateRandomStr(len) {
    let str = '';
    for(let i = 0; i < len; i++) {
        const num = Math.floor(Math.random() * 62);
        str += base62.encode(num);
    }
    return str;
}

console.log(generateRandomStr(6));

随机生成0-61的数字,转成字符。但是随机数也有碰撞的可能,可以在查表的时候检查下是否有重复,有的话重新生成,这样会有性能问题。

可以提前生成一批压缩码,存到数据库里面,用的时候直接取,定时任务每天4点生成一批。

小结:

  • 自增id作为压缩码,保证唯一但不安全。
  • url加盐后取一部分,有碰撞可能,不唯一
  • 随机生成再检测重复,保证唯一且不连续,但是有性能问题,用提前批量生产方式可以解决。
推送数据

websocket双向数据通信。
在这里插入图片描述
通过http切换协议,然后进行websocket各级数据的通信。

Http: Server Sent Event
在这里插入图片描述
服务端返回的Content-Type是text/event-stream,这是一个流,可以多次返回内容。
Server Sent Event就是通过这种消息来随时推送数据的。

比如CICD平台的构建日志,通义千问回答的问题。
在这里插入图片描述
nest实现Sse

@Sse('stream')
stream() {
    return new Observable((observer) => {
      observer.next({ data: { msg: 'aaa'} });

      setTimeout(() => {
        observer.next({ data: { msg: 'bbb'} });
      }, 2000);

      setTimeout(() => {
        observer.next({ data: { msg: 'ccc'} });
      }, 5000);
    });
}

前端请求

import { useEffect } from 'react';

function App() {

  useEffect(() => {
    const eventSource = new EventSource('http://localhost:3000/stream');
    eventSource.onmessage = ({ data }) => {
      console.log('New message', JSON.parse(data));
    };
  }, []);

  return (
    <div>hello</div>
  );
}

export default App;

EventSource是浏览器原生api,用来获取sse接口的响应。

SSE连接断了后,会通过浏览器自动重连,websocket断了后需要手动重连。

日志实时推送:
tail -f xx.log 可以看到xx.log的最新内容。
通过child_proces模块的exec执行这个命令,可以监听他的stdout输出。

const { exec } = require("child_process");

const childProcess = exec('tail -f ./log');

childProcess.stdout.on('data', (msg) => {
    console.log(msg);
});

然后添加一个sse接口

@Sse('stream2')
stream2() {
const childProcess = exec('tail -f ./log');

return new Observable((observer) => {
  childProcess.stdout.on('data', (msg) => {
    observer.next({ data: { msg: msg.toString() }});
  })
});

将childProcess的输出实时返回给前端。

这样当我们修改log文件的时候,就可以实时响应在浏览器。

minio搭建自己的oss服务

文件上传一般用oss(Object storage Service)对象存储服务来存文件,支持分布式扩展,不用担心容量问题,比如阿里的oss。如果要死有的oss服务,可以用minio来做。
用docker拉minio镜像,创建一个容器运行minio,然后可以在node里面用他的sdk做上传和下载操作,跟阿里的sso操作类似。因为sso一般都实现了亚马逊(AWS Simple Storage Serivce)S3的规范。

在这里插入图片描述
前端如何直传给oss服务,再将地址给服务器在这里插入图片描述
阿里云oss可以通过临时凭证的方式直穿oss,也就是应用服务器返回一个临时凭证,前端用这个临时凭证上传oss,不需要把sccessKey暴露给前端。

基于sharp实现图片压缩。

sharp这个包可以处理各种图片,调整大小,旋转,压缩,适用于上传一些网站的限制。

大文件流式下载

正常我们下载文件

@Get('download')
@Header('Content-Disposition', `attachment; filename="test.json"`)
download(@Res() res: Response) {
    const content = fs.readFileSync('package.json');

    res.end(content);
}

只要设置了响应头,就会触发浏览器对应的下载操作,但这样是文件全部读出后才返回,文件太大就会占内存。
可以读出一部分返回一部分。http支持这个功能。transfer-encoding:chunked

从服务器下载一个文件的时候,如何知道文件下载完了呢?

  • 1 header带上Content-Length,浏览器下载到这个长度就结束。
  • 2设置 transfer-encoding:chunked,他是不固定长度的,服务器不断返回内容,直接返回一个空的内容代表结束,这样不管内容多少都可以分块返回,而不用指定Content-Length;
@Get('download2')
@Header('Content-Disposition', `attachment; filename="test.json"`)
download2(@Res() res: Response) {
    const stream = fs.createReadStream('package.json');

    stream.pipe(res);
}

@Get('download3')
download3() {
    const stream = fs.createReadStream('package.json');

    return new StreamableFile(stream, {
      type: 'text/plain',
      disposition: `attachment; filename="test.json"`
    });
}

node的stream是分块读取内容的,配合流失返回数据很合适。
默认会设置响应头的Transfer-Encoding为chunked

这里可以使用nest封装的StreamableFile来实现,避免自己处理data error事件等。

大文件上传用分片上传,大文件下载用分片下载。

扫二维码登陆

二维码识别出来就是一个url,比如常用的登陆二维码。在浏览器打开就是下载app,在app打开就是登陆确认界面。
二维码一共有5个状态:

  • 未扫描
  • 已扫瞄,等待确认
  • 已扫瞄,用户同意授权。
  • 已扫瞄,用户取消授权。
  • 已过期

二维码的状态一般用轮询来做。
未扫描前,可能返回status为0,然后一秒轮询,扫描后可能返回status为1,此时手机点击确认登陆或者取消后,会发请求修改id对应的二维码状态,pc端通过轮询也能实时修改状态。

流程是
在这里插入图片描述

  • 服务端有个generator接口,负责生成随机二维码id,存到redis,并返回二维码。
  • 有个check接口,返回redis的二维码状态。
  • 手机app扫码后,没登陆,回调到登陆页面,登陆之后会进入登陆确认页面。
  • 从二维码中得到的url包括id,调用scan/cacnl/confirm接口修改二维码不同状态。
  • 如果用户登录后,我们这里采用jwt,会携带token,服务端可以从token取出用户信息,修改redis状态,并且将用户信息存入redis。
  • 另一边check接口判断状态是确认后,取出用户信息,生成jwt返回给pc端,这样就实现了登陆,
nest excel导入导出

前端使用xlsx这个包解析处理 excel文件,node里用exceljs这个包来处理


async function main(){
    const workbook = new Workbook();

    const workbook2 = await workbook.xlsx.readFile('./data.xlsx');

    workbook2.eachSheet((sheet, index1) => {
        console.log('工作表' + index1);

        sheet.eachRow((row, index2) => {
            const rowData = [];
    
            row.eachCell((cell, index3) => {
                rowData.push(cell.value);
            });

            console.log('行' + index2, rowData);
        })
    })
}

main();

在这里插入图片描述

excel支持最下面展示多个工作表。eachSheet就是遍历工作表的
层级关系:workbook(工作簿) > workSheet(工作表) > row(行) > cell(列)
如上,每一层都可以遍历,eachSheeteachRow eachCell,遍历工作表,遍历行,每一行还可以遍历列。
导出:

const { Workbook } = require('exceljs');

async function main(){
    const workbook = new Workbook();

    const worksheet = workbook.addWorksheet('guang111');

    worksheet.columns = [
        { header: 'ID', key: 'id', width: 20 },
        { header: '姓名', key: 'name', width: 30 },
        { header: '出生日期', key: 'birthday', width: 30},
        { header: '手机号', key: 'phone', width: 50 }
    ];

    const data = [
        { id: 1, name: '光光', birthday: new Date('1994-07-07'), phone: '13255555555' },
        { id: 2, name: '东东', birthday: new Date('1994-04-14'), phone: '13222222222' },
        { id: 3, name: '小刚', birthday: new Date('1995-08-08'), phone: '13211111111' }
    ]
    worksheet.addRows(data);

    workbook.xlsx.writeFile('./data2.xlsx');    
}

main();

先addWroksheet,设置columns后,再addRows,还可以设置格式,字体,背景色等等。

代码动态生成ppt

demo: 整理一份中国所有大学的ppt

用 puppeteer 来爬取大学的校徽、名字、介绍,然后用这些信息来生成 pdf 等。
用SSE(server sent event)的方式创建接口,不断返回爬取到的信息,用pptxgenjs来生成ppt。

获取服务器的cpu, 内存,磁盘, ip等信息。

通过 node 的 os 模块的 api 以及 node-disk-info 这个包。
如获取cpu占用情况。

@Get('status')
status() {
    const cpus = os.cpus();
    const cpuInfo = cpus.reduce(
      (info, cpu) => {
        info.cpuNum += 1;
        info.user += cpu.times.user;
        info.sys += cpu.times.sys;
        info.idle += cpu.times.idle;
        info.total += cpu.times.user + cpu.times.sys + cpu.times.idle;
        return info;
      },
      { user: 0, sys: 0, idle: 0, total: 0, cpuNum: 0 },
    );
    const cpu = {
      cpuNum: cpuInfo.cpuNum,
      sys: ((cpuInfo.sys / cpuInfo.total) * 100).toFixed(2),
      used: ((cpuInfo.user / cpuInfo.total) * 100).toFixed(2),
      free: ((cpuInfo.idle / cpuInfo.total) * 100).toFixed(2),
    };
    return cpu;
}
Nest实现国际化

nestjs-i18n

import { I18nModule, QueryResolver } from 'nestjs-i18n';
import * as path from 'path';

@Module({
  imports: [
    I18nModule.forRoot({
      fallbackLanguage: 'en',
      loaderOptions: {
        path: path.join(__dirname, '/i18n/'), //语言的资源包
        watch: true,
      },
      resolvers: [
        new QueryResolver(["lang", "l"]), //query中获取信息 ?lang=en
        new HeaderResolver(["x-custom-lang"]), //header中获取
        new CookieResolver(['lang']), //cookie中获取
        AcceptLanguageResolver,
      ]
    }),
  ],
  controllers: [AppController],
  providers: [AppService],
})
export class AppModule {}

使用

import { Inject, Injectable } from '@nestjs/common';
import { I18nContext, I18nService } from 'nestjs-i18n';

@Injectable()
export class AppService {

  @Inject()
  i18n: I18nService;

  getHello(): string {
    return this.i18n.t('test.hello', { lang: I18nContext.current().lang })
  }
}

像验证body的一些class,不在ioc容器中,不能注入I18NSerivce,可以用nest-i18n提供的I18nValidationPipe来替换ValidationPipe。再把对应的报错改成资源的key
在这里插入图片描述
还有占位符,比如
test: 密码不能少于{num}位
test1: "你好世界,{name}"
使用的时候

@MinLength(6, {
    message: i18nValidationMessage("validate.test", {
        num: 88
    })
})

getHello(): string {
    return this.i18n.t('test.test1', {
      lang: I18nContext.current().lang,
      args: {
        name: 'test'
      }
    })
  }
集成日志

nest 集成 winston 直接用 nest-winston 这个包
在app.module.ts中引入

WinstonModule.forRootAsync({
  useFactory: () => ({
    level: 'debug',
    transports: [
     //保存在一个文件下
      //new winston.transports.File({
        //filename: `${process.cwd()}/log`,
      //}),
      // 通过日期切割,保存在一个文件夹下,超过10k新开文件
      new winston.transports.DailyRotateFile({
		  level: 'debug',
		  dirname: 'daily-log',
		  filename: 'log-%DATE%.log',
		  datePattern: 'YYYY-MM-DD',
		  maxSize: '10k'
		}),
      new winston.transports.Console({
        format: winston.format.combine(
          winston.format.timestamp(),
          utilities.format.nestLike(),
        ),
      }),
      // 如果有日志服务,可以上传日志
     new winston.transports.Http({
		  host: 'localhost',
		  port: 3002,
		  path: '/log'
		})
    ],
  })
})

然后再

app.useLogger(app.get(WINSTON_MODULE_NEST_PROVIDER));

把 winston 的 logger 设置为 Nest 的默认 Logger,这样nest应用打印的console.log也会保存到log文件下
在这里插入图片描述

typeorm有自己的日志接口,我们需要自定义Logger,然后传给Typeorm

import { WinstonLogger } from 'nest-winston';
import { Logger, QueryRunner } from 'typeorm';

export class CustomTypeOrmLogger implements Logger {

    constructor(private winstonLogger: WinstonLogger) {

    }

    log(level: 'log' | 'info' | 'warn', message: any) {
        this.winstonLogger.log(message);
    }

    logQuery(query: string, parameters?: any[], queryRunner?: QueryRunner) {
        this.winstonLogger.log({
            sql: query,
            parameters
        });
    }

    logQueryError(error: string | Error, query: string, parameters?: any[], queryRunner?: QueryRunner) {
        this.winstonLogger.error({
            sql: query,
            parameters
        });
    }

    logQuerySlow(time: number, query: string, parameters?: any[], queryRunner?: QueryRunner) {
        this.winstonLogger.log({
            sql: query,
            parameters,
            time
        });
    }

    logSchemaBuild(message: string, queryRunner?: QueryRunner) {
        this.winstonLogger.log(message);
    }

    logMigration(message: string, queryRunner?: QueryRunner) {
        this.winstonLogger.log(message);
    }

}

在这里插入图片描述
这样typeorm在打印日志的时候,也会执行额外的逻辑,将日志写入文件,

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

coderlin_

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值