前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >基于Nest快速构建Web应用

基于Nest快速构建Web应用

作者头像
w候人兮猗
发布2020-12-22 15:25:17
1.6K0
发布2020-12-22 15:25:17
举报

Contents

写在前面

最近忙里偷闲,趁着学习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中相关配置
  • 主要配置项
代码语言:javascript
复制
# ------- 环境变量模版 ---------

# 服务启动端口
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>

主要功能

  • 基于守卫封装授权守卫,用于校验是否需要登录才可访问资源
代码语言:javascript
复制
# /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 下接口都需要登录才可访问

代码语言:javascript
复制
@Controller('account')
@UseGuards(AuthGuard)
export class AccountController {
  constructor(private readonly accountService: AccountService) {}
}
  • 基于装饰器封装获取当前登录用户信息
代码语言:javascript
复制
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装饰器获取参数

代码语言:javascript
复制
async getInfo(@CurrentUser('userId') userId: number): Promise<IAccountInfo> {
    return this.accountService.getUserInfo(userId);
}
  • 基于邮箱模块封装邮箱服务

具体可查看 src/services/common/code/email-code.service.ts

代码语言:javascript
复制
@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
代码语言:javascript
复制
@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
代码语言:javascript
复制
@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

代码语言:javascript
复制
@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
代码语言:javascript
复制
@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验证)
  • 其他…

其他

本文参与 腾讯云自媒体分享计划,分享自作者个人站点/博客。
如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体分享计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 写在前面
  • 依赖
  • 目录结构
  • 使用
    • 开始开发
      • 主要功能
        • 接口
        • 其他
        相关产品与服务
        云数据库 Redis
        腾讯云数据库 Redis(TencentDB for Redis)是腾讯云打造的兼容 Redis 协议的缓存和存储服务。丰富的数据结构能帮助您完成不同类型的业务场景开发。支持主从热备,提供自动容灾切换、数据备份、故障迁移、实例监控、在线扩容、数据回档等全套的数据库服务。
        领券
        问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档