Contents
- 1 写在前面
- 2 依赖
- 3 目录结构
- 4 使用
- 4.1 开始开发
- 4.2 主要功能
- 4.3 接口
- 5 其他
写在前面
最近忙里偷闲,趁着学习Nest
的功夫,抽离写了一个Nest
模块。这里简单介绍一下什么是Nestjs
Nestjs
是一个用于构建高效且可伸缩的服务端应用程序的渐进式 Node.js 框架。
他主要有以下几个特点
- 完美支持 Typescript
- 面向 AOP 编程
- 支持 Typeorm
- 高并发,异步非阻塞 IO
- Node.js 版的 spring
- 构建微服务应用
依赖
- @nestjs/core 7.5.1 核心包
- @nestjs/config 环境变量治理
- @nestjs/swagger 生成接口文档
- swagger-ui-express 装@nestjs/swagger 必装的包 处理接口文档样式
- joi 校验参数
- log4js 日志处理
- helmet 处理基础 web 漏洞
- compression 服务端压缩中间件
- express-rate-limit 请求次数限制
- typeorm 数据库 orm 框架
- @nestjs/typeorm nest typeorm 集成
- ejs 模版引擎
- class-validator 校验参数
- ioredis redis 客户端
- nestjs-redis nest redis 配置模块
- uuid uuid 生成器
- @nestjs-modules/mailer 邮箱发送
目录结构
代码语言:javascript复制├─.vscode
├─public
│ ├─assets # 静态资源
│ └─views # ejs模板
└─src
├─assets
│ └─email-template # 邮箱模板
├─config
│ ├─env # 配置相关
│ └─module # 配置模块相关
├─controllers # 控制器层
│ ├─account
│ └─user
├─decorators # 装饰器
├─dtos
│ └─user
├─entities # 实体
├─enum # 枚举
├─exception # 异常分类
├─filters # 过滤器
├─guard # 守卫
├─interceptor # 转换器
├─interfaces # 所有类型接口文件
├─modules # 所有模块
│ ├─account # 业务账号模块
│ ├─base # 基础模块
│ ├─common # 公共模块
│ └─user # 业务用户模
├─pipes # 管道
├─services # 服务层
│ ├─account
│ ├─common
│ │ ├─code
│ │ ├─jwt
│ │ └─redis
│ └─user
└─utils # 工具类
使用
开始开发
- 复制根目录下
default.env
文件,重命名为.env
文件,修改其配置 yarn start:dev
开始开发- 本地新建数据库,
Redis
,修改.env
中相关配置 - 主要配置项
# ------- 环境变量模版 ---------
# 服务启动端口
SERVE_LISTENER_PORT=3000
# Swagger 文档相关
SWAGGER_UI_TITLE = Fast-nest-temp 接口文档
SWAGGER_UI_TITLE_DESC = 接口文档
SWAGGER_API_VERSION = 0.0.1
SWAGGER_SETUP_PATH = api-docs
SWAGGER_ENDPOINT_PREFIX = nest_api
# 开发模式相关
NODE_ENV=development
# 应用配置
# 数据库相关
DB_TYPE = mysql
DB_HOST = 127.0.0.1
DB_PORT = 3306
DB_DATABASE = fast_nest
DB_USERNAME = root
DB_PASSWORD = 123456
DB_SYNCHRONIZE = 1
DB_LOGGING = 1
DB_TABLE_PREFIX = t_
# Redis相关
REDIS_HOST = localhost
REDIS_PORT = 6379
REDIS_PASSWORD =
# Token相关
TOKEN_SECRET = secret
TOKEN_EXPIRES = 7d
# Email相关
EMAIL_HOST = smtp.126.com
EMAIL_PORT = 465
EAMIL_AUTH_USER = xxxxx
EMAIL_AUTH_PASSWORD = xxxxx
EMAIL_FROM = "FAST_NEST_TEMP ROBOT" <xxxx@126.com>
主要功能
- 基于守卫封装授权守卫,用于校验是否需要登录才可访问资源
# /guard/auth.guard.ts
@Injectable()
export class AuthGuard implements CanActivate {
constructor(private readonly jwtService: JwtService) {}
async canActivate(context: ExecutionContext): Promise<boolean> {
const request = context.switchToHttp().getRequest();
const requestToken =
request.headers['authtoken'] || request.headers['AuthToken'];
if (requestToken) {
try {
const ret = await this.jwtService.verifyToken(requestToken);
const { sub, account } = ret as IToken;
const currentUser: ICurrentUser = {
userId: sub,
account,
};
request.currentUser = currentUser;
} catch (e) {
throw new ApiException('token格式不正确', ApiCodeEnum.ERROR);
}
} else {
throw new ApiException('你还没登录,请先登录', ApiCodeEnum.SHOULD_LOGIN);
}
return true;
}
}
校验成功之后会在全局request
中注入curentUser
对象
使用守卫 accounnt
下接口都需要登录才可访问
@Controller('account')
@UseGuards(AuthGuard)
export class AccountController {
constructor(private readonly accountService: AccountService) {}
}
- 基于装饰器封装获取当前登录用户信息
import { createParamDecorator, ExecutionContext } from '@nestjs/common';
export const CurrentUser = createParamDecorator(
(key: string, ctx: ExecutionContext) => {
const request = ctx.switchToHttp().getRequest();
if (key && request.currentUser) {
return request.currentUser[key] || '';
} else {
return request.currentUser;
}
},
);
使用@currentUser
装饰器获取参数
async getInfo(@CurrentUser('userId') userId: number): Promise<IAccountInfo> {
return this.accountService.getUserInfo(userId);
}
- 基于邮箱模块封装邮箱服务
具体可查看 src/services/common/code/email-code.service.ts
@Injectable()
export class EmailCodeService {
constructor(private readonly mailerService: MailerService) {}
/**
* 邮箱发送
* @param params IEmailParams
*/
public async sendEmail(params: IEmailParams) {
const { to, title, content, template, context } = params;
return await this.mailerService.sendMail({
to: to,
subject: title,
text: content,
template,
context,
});
}
}
- 图形验证码获取工具
具体可查看
src/services/common/code/img-captcha.service.ts
@Injectable()
export class ImageCaptchaService {
/**
* 生成图形验证码
*/
public createSvgCaptcha(length?: number) {
const defaultLen = 4;
const captcha: { data: any; text: string } = svgCaptcha.create({
size: length || defaultLen,
fontSize: 50,
width: 100,
height: 34,
ignoreChars: '0o1i',
background: '#01458E',
inverse: false,
});
return captcha;
}
}
- 封装
Redis
工具类 具体可查看src/services/common/redis/redis-cache.service.ts
@Injectable()
export class RedisClientService {
public client: Redis;
constructor(private redisService: RedisService) {}
onModuleInit() {
this.getClient();
}
public getClient() {
this.client = this.redisService.getClient();
}
public async set(
key: string,
value: Record<string, unknown> | string,
second?: number,
) {
value = JSON.stringify(value);
// 如果没有传递时间就默认时间
if (!second) {
await this.client.setex(key, 24 * 60 * 60, value); // 秒为单位
} else {
await this.client.set(key, value, 'EX', second);
}
}
public async get(key: string): Promise<any> {
const data = await this.client.get(key);
if (data) {
return JSON.parse(data);
} else {
return null;
}
}
public async del(key: string): Promise<any> {
await this.client.del(key);
}
public async flushall(): Promise<any> {
await this.client.flushall();
}
}
- 封装全局异常过滤器
用于统一处理异常返回信息,更友好的提示用户
文件位于 src/filters/http-exception.filter.ts
@Catch()
export class HttpExceptionFilter implements ExceptionFilter {
catch(exception: HttpException, host: ArgumentsHost) {
const ctx = host.switchToHttp();
const response = ctx.getResponse();
const request = ctx.getRequest();
const timestamp = Date.now();
let errorResponse: IHttpResponse = null;
const message = exception.message;
const path = request.url;
const method = request.method;
const result = null;
if (exception instanceof ApiException) {
const message = exception.getErrorMessage();
errorResponse = {
result,
code: exception.getErrorCode(),
message,
path,
method,
timestamp,
};
} else {
errorResponse = {
result,
message:
typeof message === 'string'
? message || CommonText.REQUEST_ERROR
: JSON.stringify(message),
path,
method,
timestamp,
code: ApiCodeEnum.ERROR,
};
}
response.status(HttpStatus.OK);
response.header('Content-Type', 'application/json; charset=utf-8');
response.send(errorResponse);
}
}
- 封装全局日志打点转换
文件位于
src/interceptor/logger.interceptor.filter.ts
@Injectable()
export class LoggingInterceptor implements NestInterceptor {
private genAccessLog(request, time, res, status, context): any {
const log = {
statusCode: status,
responseTime: `${Date.now() - time}ms`,
ip: request.ip,
header: request.headers,
query: request.query,
params: request.params,
body: request.body,
response: res,
};
Logger.access(JSON.stringify(log), `${context.getClass().name}`);
}
intercept(context: ExecutionContext, next: CallHandler): Observable<any> {
const request = context.switchToHttp().getRequest();
const response = context.switchToHttp().getResponse();
const status = response.statusCode;
const now = Date.now();
return next.handle().pipe(
tap((res) => {
// 其他的都进access
this.genAccessLog(
request,
`${Date.now() - now}ms`,
res,
status,
context,
);
}),
catchError((err) => {
if (err instanceof ApiException) {
// 其他的都进access
this.genAccessLog(
request,
`${Date.now() - now}ms`,
err.getErrorMessage(),
status,
context,
);
Logger.error(err);
} else {
Logger.error(err);
}
// 返回原异常
throw err;
}),
);
}
}
- 更多功能可自行查看源码
接口
模板自带接口如下
- 登录注册
- 邮箱验证码
- 图形验证码
- 获取个人信息(token验证)
- 其他…
其他
- 源码地址 https://github.com/ahwgs/fast_nest_temp
- 本文首发于 https://cloud.tencent.com/developer/article/1764091