0
点赞
收藏
分享

微信扫一扫

NestJS搭建前端路由服务

有态度的萌狮子 2022-02-12 阅读 77

通常,为了更好地管理和维护项目,项目一般都会以业务范畴进行拆分,比如商品、订单、会员等等,从而产生业务职责不同的众多前端工程(SPA,单页面应用)。假设现在有个需求,所有的前端工程都需要接入神策埋点Web JS SDK,如果采用每个前端工程静态页面index.html各自引入Web JS SDK的方案,那么每个工程引入之后都需要重新部署一遍,并且以后需要更换第三方埋点SDK时,前面步骤需要重新来一遍,相当麻烦。而如果在访问所有前端工程前面加一个路由转发层,有点像前端网关,拦截响应,统一引入Web JS SDK。

七牛云模拟实际项目对象存储服务
前端项目都会部署到对象存储服务中,比如阿里云对象存储服务OSS,华为云对象存储服务OBS,这儿我使用七牛云对象存储服务模拟实际的部署环境

一、创建存储空间,创建三级静态资源目录www/cassmall/inquiry,然后上传一个index.html模拟实际项目部署

image-20220211201658075.png

二、给存储空间配置源站域名和CDN域名(实际配置需要先给域名备案),请求index.html使用源站域名,请求js、css、img等静态资源使用CDN域名

image-20220212182738052.png

这里解释一下为什么到源站获取index.html,而不是通过CDN域名获取?假设通过CDN获取index.html,当第一次部署单页面应用,假设浏览器访问http://localhost:3000/mall/inquiry/#/xxx,CDN上没有index.html则去源站拉取index.html,然后CDN缓存一份;当对index.html做了修改,第二次部署(部署到源站),浏览器还是访问http://localhost:3000/mall/inquiry/#/xxx,发现CDN上已经有index.html(旧),直接返回给浏览器,而不是返回源站最新的index.html,毕竟请求index.html的路径版本号参数,会走CDN。如果直接使用源站域名请求index.html,那么每次获取到的都是最新index.html。

其实,通过CDN域名获取index.html也可以,不过需要设置CDN缓存配置,让其对html后缀的文件不做缓存处理。

推荐配置

另外,js、css、img、video这类静态资源我们希望页面能够快速加载,因此通过CDN加速获取。js、css可能改动比较频繁,但在构建后都会根据内容生成hash重新命名文件,若文件有更改,其hash也会变化,请求时不会命中CDN缓存,会回源;若文件没有更改,其hash不会变化,则会命中CDN缓存。img、video改动不会很频繁,如需要改动,则重新命名上传即可,防止同样名称命中CDN缓存。

项目创建
首先确定你已经安装了Node.js, Node.js 安装会附带npx和一个npm 包运行程序。请确保在您的操作系统上安装了Node.js (>= 10.13.0,v13 除外)。要创建新的Nest.js 应用程序,请在终端上运行以下命令:

npm i -g @nestjs/cli // 全局安装Nest
nest new web-node-router-serve // 创建项目
执行完创建项目, 会初始化下面这些文件, 并且询问你要是有什么方式来管理依赖包:

image-20220210175913130.png

如果你有安装yarn,可以选择yarn,能更快一些,npm在国内安装速度会慢一些

image-20220210180021849.png

接下来按照提示运行项目:

image-20220210184505958.png

项目结构
进入项目,看到的目录结构应该是这样的:

image-20220210184609655.png

这里简单说明一下这些核心文件:

src
├── app.controller.spec.ts
├── app.controller.ts
├── app.module.ts
├── app.service.ts
├── main.ts
app.controller.ts 单个路由的基本控制器(Controller)
app.controller.spec.ts 针对控制器的单元测试
app.module.ts 应用程序的根模块(Module)
app.service.ts 具有单一方法的基本服务(Service)
main.ts 应用程序的入口文件,它使用核心函数 NestFactory 来创建 Nest 应用程序的实例。
main.ts 文件中包含了一个异步函数,此函数将 引导(bootstrap) 应用程序的启动过程:

import { NestFactory } from ‘@nestjs/core’;
import { AppModule } from ‘./app.module’;

async function bootstrap() {
const app = await NestFactory.create(AppModule);
await app.listen(3000);
}
bootstrap();
要创建一个 Nest 应用程序的实例,我们使用了 NestFactory 核心类。NestFactory 暴露了一些静态方法用于创建应用程序的实例。其中,create() 方法返回一个应用程序的对象,该对象实现了 INestApplication 接口。在上面的 main.ts 示例中,我们仅启动了 HTTP 侦听器,该侦听器使应用程序可以侦听入栈的 HTTP 请求。

应用程序的入口文件
我们调整一下入口文件main.ts,端口可以通过命令输入设置:

import { INestApplication } from ‘@nestjs/common’;
import { NestFactory } from ‘@nestjs/core’;
import { AppModule } from ‘./app.module’;

const PORT = parseInt(process.env.PORT, 10) || 3334; // 端口

async function bootstrap() {
const app = await NestFactory.create(AppModule);
await app.listen(PORT);
}
bootstrap();
配置请求路径与静态资源目录的映射关系
不同环境的对象存储服务域名不一样,需要不同的配置文件,使用第三方模块config模块管理操作配置文件。安装config:

yarn add config
在根目录下新建config目录,目录下新增default.js、development.js、production.js,添加如下配置:

// default.js
module.exports = {
ROUTES: [
{
cdnRoot: ‘www/cassmall/inquiry’, // 对象存储服务对应的静态资源目录
url: [’/cassmall/inquiry’], // 请求路径
},
{
cdnRoot: ‘www/admin/vip’,
url: [’/admin/vip’],
},
],
};

// development.js
module.exports = {
OSS_BASE_URL: ‘http://r67b3sscj.hn-bkt.clouddn.com/’, // 开发环境对象存储服务源站域名
};

// production.js
module.exports = {
OSS_BASE_URL: ‘http://r737i21yz.hn-bkt.clouddn.com/’, // 生产环境对象存储服务源站域名
};
说一下config.get()查找环境参数的规则:如果NODE_ENV为空,使用development.js,如果没有development.js,则使用default.js。 若NODE_ENV不为空,则到config目录中找相应的文件,若文件没找到则使用default.js中的内容。 若在指定的文件中没找到配置项,则去default.js找。

htts://zhuanlan.zhihu.com/p/462158208
htts://zhuanlan.zhihu.com/p/462158873
http://zhuanlan.zhihu.com/p/462161131
http://zhuanlan.zhihu.com/p/462163302
http://zhuanlan.zhihu.com/p/462496725
http://zhuanlan.zhihu.com/p/462901470
http://zhuanlan.zhihu.com/p/462902786
http://zhuanlan.zhihu.com/p/462902203
http://zhuanlan.zhihu.com/p/462901776
http://zhuanlan.zhihu.com/p/462910603
创建路由控制器
// app.controller.ts
import {
Controller,
Get,
Header,
HttpException,
HttpStatus,
Req,
} from ‘@nestjs/common’;
import { AppService } from ‘./app.service’;
import { Request } from ‘express’;
import config from ‘config’;

type Route = { gitRepo: string; cdnRoot: string; url: string[] };
const routes = config.get(‘ROUTES’);
const routeMap: { [key: string]: Route } = {};
routes.forEach((route) => {
for (const url of route.url) {
routeMap[url] = route;
}
});

@Controller()
export class AppController {
constructor(private readonly appService: AppService) {}

@Get(Object.keys(routeMap))
@Header(‘X-UA-Compatible’, ‘IE=edge,chrome=1’)
async route(@Req() request: Request): Promise {
const path = request.path.replace(//$/g, ‘’);
const route = routeMap[request.path];
if (!route) {
throw new HttpException(
‘没有找到当前url对应的路由’,
HttpStatus.NOT_FOUND,
);
}
// 获取请求路径对应的静态页面
return this.appService.fetchIndexHtml(route.cdnRoot);
}
}
esm引入cjs
第三方模块config是cjs规范的模块,使用esm方式引入cjs之前需要在tsconfig.json添加配置:

{
“compilerOptions”: {
“allowSyntheticDefaultImports”: true, // ESM导出没有设置default,被引入时不报错
“esModuleInterop”: true, // 允许使用ESM带入CJS
}
}
当然你可以直接使用cjs规范引入const config = require(‘config’)或者改成import * as config from 'config’引入,不然运行时会报下面错误:

image-20220210202810409.png

因为esm 导入 cjs,esm 有 default 这个概念,而 cjs 没有。导致导入的config值为undefined

任何导出的变量在 cjs 看来都是 module.exports 这个对象上的属性,esm 的 default 导出也只是 cjs 上的 module.exports.default 属性而已。设置esModuleInterop:true;后tsc编译时会给module.exports添加default属性

// before
import config from ‘config’;

console.log(config);
// after
“use strict”;

var _config = _interopRequireDefault(require(“config”));

function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { “default”: obj }; }

console.log(_config[“default”]);
想了解这部分模块化处理,可以参考tsc、babel、webpack对模块导入导出的处理

@Get接受路由路径数组
@Get() HTTP 请求方法装饰器可以接受路由路径数组类型,告诉控制器可以处理哪些路由路径的请求

/**

  • Route handler (method) Decorator. Routes HTTP GET requests to the specified path.
  • @see Routing
  • @publicApi
    */
    export declare const Get: (path?: string | string[]) => MethodDecorator;
    异常处理
    当路由配置没有对应路由时抛出异常,如果没有自定义异常拦截处理,则Nest内置异常层会自动处理,生成JSON响应

const path = request.path.replace(//$/g, ‘’);
const route = routeMap[request.path];
if (!route) {
throw new HttpException(
‘没有找到当前url对应的路由’,
HttpStatus.NOT_FOUND,
);
}

// 异常将会被Nest自动处理,生成下面JSON响应
{
“statusCode”: 404,
“message”: “没有找到当前url对应的路由”
}
Nest 带有一个内置的异常层,负责处理应用程序中所有未处理的异常。当您的应用程序代码未处理异常时,该层将捕获该异常,然后自动发送适当的用户友好响应。

举报

相关推荐

0 条评论