问题描述
NPM(Node Package Manager)是Node.js的包管理工具,它允许开发者轻松地分享、安装和管理依赖包,促进了代码的复用性和项目协作。而npm插件库将是nodejs开发中不可缺失的重要组成因素。
在nestjs中,官方已经给我们开发了一系列各种各样的封装功能让我们能够快速的进行开发。然而实际应用中,我们在开发的过程中任然需要引入各种各样的插件工具,因此学会封装nestjs组件功能是非常重要一环。
如:nestjs文档中<技术、安全>这些栏目中就有许多官方将npm包封装成nestjs风格的功能包的例子。底层本质上都是npm插件库中的各类插件或者自写插件
需求知识
在nestjs文档中<基础知识>栏目就讲了一些自定义提供器和动态模块等
封装方式示例
方式一:封装成提供器
对于那些不需要初始化,不需要提供参数进行配置的插件库,可以直接封装成对应的提供器即可。需要的地方则引入足以。
如:封装’node-xlsx‘以此来对excel表格进行处理支持
import { Injectable } from '@nestjs/common';
import xlsx from 'node-xlsx';
import * as fs from 'fs';
import { join } from 'path';
import { RpcException } from '@nestjs/microservices';
/**
* excel文件处理服务
*/
@Injectable()
export class ExcelService {
/**
* 读取excel文件内容
* @param file 文件本身
* @returns workSheetsFromBuffer [
{ name: 'Sheet1', data: [ [第一行数据], [第二行数据] ] },
{ name: 'Sheet2', data: [] }
]
*/
async readExcel(file: any): Promise<Array<any>> {
try {
const buffer = await file.toBuffer();
const workSheetsFromBuffer = xlsx.parse(buffer); // 获取所有内容
return workSheetsFromBuffer;
} catch {
throw new RpcException('excel处理失败');
}
}
/**
* 导出为excel
*/
exportExcel(fileName: string, title: Array<string>, data: Array<any>): void {
const datas = [title, ...data];
try {
const buffer = xlsx.build([{ name: 'Sheet1', data: datas, options: {} }]);
// 生成的文件存储在本地
const pathLocal = join(__dirname, '../../../../public', `${fileName}`);
return fs.writeFileSync(pathLocal, buffer, { flag: 'w' });
} catch {
throw new RpcException('excel生成处理失败');
}
}
}
使用方式
// 某个模块中
@Module({
imports: [],
providers: [ExcelService, ...],
controllers: [],
})
方式二:封装成动态模块
这个是那种需要初始化或者需要配置相关参数等插件库进行封装的方式。需要时候在对应的模块中注册,或者全局注册即可。
如:我不喜欢@nestjs/cache-manager的缓存模块,我喜欢nodejs中的ioredis插件来管理我的redis。这个时候可以尝试封装一个属于自己的缓存模块
// redis.config.module.ts
import { DynamicModule, Module } from '@nestjs/common'
import { RedisConfigService } from './redis.config.service'
@Module({})
export class RedisConfigModule {
static register(options: Record<string, any>): DynamicModule {
return {
module: RedisConfigModule,
global: true,
providers: [
{
provide: 'REDIS_OPTIONS',
useValue: options,
},
RedisConfigService,
],
exports: [RedisConfigService],
};
}
}
// redis.config.service.ts
import {
Inject,
Injectable
} from '@nestjs/common'
import { RpcException } from '@nestjs/microservices';
import Redis from 'ioredis'
@Injectable()
export class RedisConfigService {
private redisClient: any;
constructor(
@Inject('REDIS_OPTIONS')
private options: Record<string, any>
) {
this.initRedis()
}
/**
* 初始化ioredis服务
* @param connectType
*/
private async initRedis() {
try {
const redis = new Redis(this.options)
redis.on('error', (err) => {
throw new RpcException(err)
})
redis.on('connect', () => {
this.redisClient = redis
})
} catch (err) {
throw new RpcException(err)
}
}
/*----字符串格式string----*/
/**
* 设置redis字符串格式内容存储
* @param key 健名
* @param string 字符串内容
* @param time 过期时间 单位s
* @returns
*/
async setRedis(key: string, value: string | number, time?: number): Promise<void> {
try {
// 存储
await this.redisClient.set(key, value)
// 设置过期时间
if (time) {
await this.setRedisTime(key, time)
}
} catch (err) {
throw new RpcException(err)
}
}
/**
* 获取字符串格式redis内容
* @param key
* @returns string | null
*/
async getRedis(key: string): Promise<string | null> {
try {
return await this.redisClient.get(key)
} catch (error) {
throw new RpcException(error)
}
}
/*----对象格式object----*/
/**
* 设置redis对象格式内容存储
* @param key 健名
* @param object 对象内容
* @param time 过期时间 单位s
* @returns
*/
async hsetRedis(key: string, value: object, time?: number): Promise<void> {
try {
// 存储
await this.redisClient.hset(key, value)
// 设置过期时间
if (time) {
await this.setRedisTime(key, time)
}
} catch (err) {
throw new RpcException(err)
}
}
/**
* 获取对象格式redis内容
* @param key
* @returns object | null
*/
async hgetRedis(key: string, objectKey?: string): Promise<null | object> {
try {
if (objectKey) {
return await this.redisClient.hget(key, objectKey)
}
return await this.redisClient.hgetall(key)
} catch (error) {
throw new RpcException(error)
}
}
/**---数组格式暂未封装按需可以自行封装-- */
/*-----设置过期时间----- */
/**
* 设定指定key对应的过期时间,单位s
* @param key
* @param time
* @returns 0 为设置失败, 1为设置成功
*/
async setRedisTime(key: string, time: number): Promise<number> {
try {
const result = await this.redisClient.expire(key, time)
if (result && result === 0) {
throw new RpcException('redis过期时间设置失败')
}
return result
} catch (error) {
throw new RpcException(error)
}
}
/**
* 设定key永不过期,因此需要主动删除
* @param key
* @returns 0 为设置失败, 1为设置成功
*/
async setRedisTimePersist(key: string): Promise<number> {
try {
const result = await this.redisClient.persist(key)
if (result && result === 0) {
throw new RpcException('redis过期时间设置失败')
}
return result
} catch (error) {
throw new RpcException(error)
}
}
/*-----删除指定key的内容----- */
/**
* 移除指定key
* @param key
*/
async delRedis(key: string): Promise<void> {
try {
return await this.redisClient.del(key)
} catch (error) {
throw new RpcException(error)
}
}
/*-----redis相关信息---- */
/**
* 扫描指定名称下的缓存键名列表
*/
async getRedisKeysByName(name: string, searchValue: string): Promise<{ cacheKey: string }[]> {
try {
if (searchValue) {
name = name + searchValue
}
const stream = this.redisClient.scanStream({
match: `${name}*`,
count: 100
})
const list:any = []
stream.on('data', (resultKeys:any) => {
for (let i = 0; i < resultKeys.length; i++) {
list.push({
cacheKey: resultKeys[i]
})
}
})
return new Promise((resolve) => {
stream.on('end', () => {
resolve(list)
})
})
} catch (error) {
throw new RpcException(error)
}
}
/**
* 获取 redis 服务器的各种信息和统计数值
*/
async getRedisInfo() {
try {
const serverInfo = await this.redisClient.info('Server')
const clientsInfo = await this.redisClient.info('Clients')
const memoryInfo = await this.redisClient.info('Memory')
const cpuInfo = await this.redisClient.info('CPU')
const statsInfo = await this.redisClient.info('Stats')
const commandstatsInfo = await this.redisClient.info('Commandstats')
const keyspaceInfo = await this.redisClient.info('Keyspace')
const Persistence = await this.redisClient.info('Persistence')
return {
...this.transArrayToObject(serverInfo.split('\r\n')),
...this.transArrayToObject(clientsInfo.split('\r\n')),
...this.transArrayToObject(memoryInfo.split('\r\n')),
...this.transArrayToObject(cpuInfo.split('\r\n')),
...this.transArrayToObject(statsInfo.split('\r\n')),
...this.transArrayToObject(commandstatsInfo.split('\r\n')),
...this.transArrayToObject(Persistence.split('\r\n')),
db: this.transArrayToObjectTotal(keyspaceInfo.split('\r\n'))
}
} catch (error) {
throw new RpcException(error)
}
}
// 处理redis字符串信息相关转换为对象
transArrayToObject(arr: any) {
if (arr.length === 0) return
const arrObj = {}
for (let i = 0; i < arr.length; i++) {
if (arr[i].indexOf(':') >= 0) {
const list = arr[i].split(':')
// @ts-ignore
arrObj[list[0]] = list[1]
}
}
return arrObj
}
// 处理redis字符串信息相关转换为对象并统计为数组
transArrayToObjectTotal(arr: any) {
if (arr.length === 0) return
const arrList = []
for (let i = 0; i < arr.length; i++) {
if (arr[i].indexOf(':') >= 0) {
const arrObj = {}
const list = arr[i].split(':')
// @ts-ignore
arrObj[list[0]] = list[1]
arrList.push(arrObj)
}
}
return arrList
}
}
使用方式
// app.module.ts中
@Module({
imports: [
// jwt 模块
RedisConfigModule.register({...}),
]
})
推荐一个项目便于学习
该项目基于知名的项目RuoYi-Vue项目进行改造而成。可方便参考开发
前端使用vue3、uniapp等开发框架。
后端使用nestjs
地址:https://gitee.com/twang-gitee/ry-vue-nestjs