写点什么

web 前端培训带你学习 Midwayjs 实战

作者:@零度
  • 2022 年 6 月 01 日
  • 本文字数:7761 字

    阅读完需:约 25 分钟

经常有人问,现在都 2022 年了,还要学习 Node.js 么?我想这个问题,可能每个前端开发者,都会在工作到一定阶段思考这个问题。可以很明确的告诉大家,学习 Node.js 可能是将来每个前端开发者必备的一项技能。



点击并拖拽以移动

​编辑

 

在 Angular 发布的同一年(2009 年),Node.js 也随之登台,Node.js 的出现带来的第一个好处就是前端工程化的成熟,前端构建工具开始百花齐放。这时的前端已经不再是一个简单编写几行 JavaScript 即可完成的事情,前端开发开始出现了前端工程师这个职位,专职前端研发人员开始在各个公司中普及,前后端协作问题也开始加剧。

BFF

随着 Node.js 的成熟,在 2015 年,基于 BFF(Backgroud For Frontend, 服务于前端的后端)的架构理念被提出,BFF 架构通过在 UI 和服务端之间加入中间层,解决了前后端职责难以划分的问题。



点击并拖拽以移动

​编辑

 

如图所示,由于前端的逻辑复杂性不断增加,增加了专门用于处理用户界面逻辑的服务层,同时后端逻辑也完成下沉,基于微服务架构的后端服务逐渐成型,通过基于 Node.js 的 BFF 层,前后端形成了比较清晰的分工,也就是进入了前端工程师时代_前端培训

Node.js 的基本原理

先看一下早期的 Node.js 结构图,来自 Node.js 之父 Ryan Dahl 的演讲稿,它简要的介绍了 Node.js 是基于 Chrome V8 引擎构建的,由于事件循环 Event Loop 分发 I/O 任务, 最终工作线程 Work Thread 将任务丢到线程池 Thread Pool 里去执行, 而事件循环只要等待执行结果就可以了



点击并拖拽以移动

​编辑

 

核心

  • Chrome V8 解释并执行 JavaScript 代码(这就是为什么浏览器能执行 JavaScript 原因)

  • libuv 由事件循环和线程池组成,负责所有 I/O 任务的分发与执行

常用的框架



点击并拖拽以移动

​编辑

 

为什么选择 Midway

  • 如果说这两年那个语言在前端最火,我想 TypeScript 肯定有一席之地,强约束性的语言使得在构建 Node.js 应用时,提供了类型检查等约束能力,使得 Node.js 更安全等。Midway 基于 TypeScript 开发,对于 TypeScript 的支持更好一些。

  • 最近在深耕于公司的基础建设,使用的 Node.js 框架刚好是 Midwayjs。

  • Midwayjs 提供了 Web 中间件的能力。

Midway 简介

Midway 是阿里巴巴 - 淘宝前端架构团队,基于渐进式理念研发的 Node.js 框架。

Midway 基于 TypeScript 开发,结合了面向对象(OOP + Class + IoC)与函数式(FP + Function + Hooks)两种编程范式,并在此之上支持了 Web / 全栈 / 微服务 / RPC / Socket / Serverless 等多种场景,致力于为用户提供简单、易用、可靠的 Node.js 服务端研发体验。

多编程范式

Midway 支持面向对象与函数式两种编程范式,你可以根据实际研发的需要,选择不同的编程范式来开发应用。

面向对象(OOP + Class + IoC)

Midway 支持面向对象的编程范式,为应用提供更优雅的架构。

下面是基于面向对象,开发路由的示例。

// src/controller/home.ts

import { Controller, Get } from '@midwayjs/decorator';

import { Context } from '@midwayjs/koa';

@Controller('/')

export class HomeController {

@Inject()

ctx: Context

@Get('/')

async home() {

return {

message: 'Hello Midwayjs!',

query: this.ctx.ip

}

}

}

函数式(FP + Function + Hooks)

Midway 也支持函数式的编程范式,为应用提供更高的研发效率。

下面是基于函数式,开发路由接口的示例。

// src/api/index.ts

import { useContext } from '@midwayjs/hooks'

import { Context } from '@midwayjs/koa';

export default async function home () {

const ctx = useContext<Context>()

return {

message: 'Hello Midwayjs!',

query: ctx.ip

}

}

环境准备

首先确保你已经安装了 Node.js,Node.js 安装会附带 npx 和一个 npm 包运行程序,Midway 3.0.0 最低版本要求 12.x。

项目创建

使用 npm init midway 来创建项目

npm init midway



点击并拖拽以移动

​编辑

 

我们这里使用 3.0 版本,因此我们这里选择 koa-v3,输入项目名称, 脚手架会帮我们创建一个简单的项目工程,等安装完成。



点击并拖拽以移动

​编辑

 

我们使用 Vscode 打开项目。可以得到现在的工程目录

midway-demo

├── README.md

├── README.zh-CN.md

├── bootstrap.js

├── jest.config.js

├── package.json

├── src

│ ├── config

│ │ ├── config.default.ts

│ │ └── config.unittest.ts

│ ├── configuration.ts

│ ├── controller

│ │ ├── api.controller.ts

│ │ └── home.controller.ts

│ ├── filter

│ │ ├── default.filter.ts

│ │ └── notfound.filter.ts

│ ├── interface.ts

│ ├── middleware

│ │ └── report.middleware.ts

│ └── service

│ └── user.service.ts

├── test

│ └── controller

│ ├── api.test.ts

│ └── home.test.ts

└── tsconfig.json

整个项目包括了一些最基本的文件和目录

  • src 整个工程的源码目录,之后所有的开发代码都将放在这个文件夹下面

  • test 测试目录,之后所有的代码测试文件都在这里

  • package.json Node.js 项目基础的包管理配置文件,这个想必大家都很熟悉

  • tsconfig.json TypeScript 编译配置文件.

在 src 目录下面,常用的有:

  • config 业务的配置目录

  • controller web controller 目录

  • filter 过滤器目录

  • interface.ts 业务的 ts 定义文件

  • middleware 中间件目录

  • service 服务逻辑目录

启动项目

yarn dev

warning ../../../../../package.json: No license field

$ cross-env NODE_ENV=local midway-bin dev --ts

[ Midway ] Start Server at http://127.0.0.1:7001

在浏览器中输入 127.0.0.1:7001



点击并拖拽以移动

​编辑

 

路由

我们来看一下代码中的 controller 文件夹下面的 home.controller.ts 文件

import { Controller, Get } from '@midwayjs/decorator';

@Controller('/')

export class HomeController {

@Get('/')

async home(): Promise<string> {

return 'Hello Midwayjs!';

}

}

我们找到了浏览器中的输出 Hello Midwayjs!

路由装饰器

@controller 装饰器标注了控制器,装饰器有一个可选参数,用于进行路由前缀,这样控制器下面的所有路由都会带上这个前缀。

我们修改一下装饰器中的内容

import { Controller, Get } from '@midwayjs/decorator';

@Controller('/test')

export class HomeController {

@Get('/')

async home(): Promise<string> {

return 'Hello Midwayjs!';

}

}

在浏览器中输入 127.0.0.1:7001 报错



点击并拖拽以移动

​编辑

 

报错信息告诉我们路由找不到,那么我们改一下浏览器中的路由 127.0.0.1:7001/test,我们得到了我们想要的结果,这里我们可以知道装饰器中的参数匹配我们的路由



点击并拖拽以移动

​编辑

 

Http 装饰器

常见的 Http 装饰器, @Get 、 @Post 、 @Put() 、 @Del() 、 @Patch() 、 @Options() 、 @Head() 和 @All() ,表示各自的 HTTP 请求方法。

我们改写一下代码

import { Controller, Get, Post } from '@midwayjs/decorator';

@Controller('/test')

export class HomeController {

@Post('/')

async home(): Promise<string> {

return 'Hello Midwayjs!';

}

}

通过使用 Postman 调用接口,将请求方式改为 post,可以看到我们拿到我们请求的接口了。



点击并拖拽以移动

​编辑

全局路由前缀

在工程项目中,我们常常使用一些路由前缀去区分不同服务之间的作用,那么相同的路由前缀,在每个 controller 里面加入,显然很麻烦,如果要改变前缀名称,在后期工程相对较大,接口较多的时候,岂不是要一个个去改,在这里我们配置全局的路由前缀。

我们修改 config/config.default.ts 文件,代码修改如下

import { MidwayConfig } from '@midwayjs/core';

export default {

// use for cookie sign key, should change to your own and keep security

keys: '1653223786698_4903',

koa: {

port: 7001,

globalPrefix: '/demo',

},

} as MidwayConfig;

保存文件之后,服务不需要我们手动重启,我们请求一下http://127.0.0.1/demo/test,服务返回了我们的内容。



点击并拖拽以移动

​编辑

 

依赖注入

依赖注入(DI)、控制反转(IoC)等是 Spring 的核心思想,那么在 midwayjs 中通过装饰器的轻量特性,让依赖注入变得非常优雅.

举个例子:

.

├── package.json

├── src

│ ├── controller # 控制器目录

│ │ └── api.controller.ts

│ └── service # 服务目录

│ └── user.service.ts

└── tsconfig.json

我们实现一下文件的代码

// api.controller.ts

import { Inject, Controller, Get, Query } from '@midwayjs/decorator';

import { Context } from '@midwayjs/koa';

import { UserService } from '../service/user.service';

@Controller('/api')

export class APIController {

@Inject()

ctx: Context;

@Inject()

userService: UserService;

@Get('/get_user')

async getUser(@Query('uid') uid) {

const user = await this.userService.getUser({ uid });

return { success: true, message: 'OK', data: user };

}

}

// user.service.ts

import { Provide } from '@midwayjs/decorator';

import { IUserOptions } from '../interface';

@Provide()

export class UserService {

async getUser(options: IUserOptions) {

return {

uid: options.uid,

username: 'mockedName',

phone: '12345678901',

email: 'xxx.xxx@xxx.com',

};

}

}

@Provide 的作用是告诉 依赖注入容器 ,我需要被容器所加载。@Inject 装饰器告诉容器,我需要将某个实例注入到属性上。

上面例子上,我们实现了一个 UserService 并通过 @Provide 注入到容器中,在 app.controller 中,我们通过 @Inject 拿到了 userService 的实例。

那么我们请求一下接口:



点击并拖拽以移动

​编辑

 

调试

我们在扩展里面搜索 JavaScript Debugger



点击并拖拽以移动

​编辑

 

点击下拉箭头,选择 JavaScript Debug Terminal, .



点击并拖拽以移动

​编辑

 

输入命令 yarn dev,在需要 debugger 的位置打上断点



点击并拖拽以移动

​编辑

 

在 Postman 中请求接口,可以看到代码执行到断点位置



点击并拖拽以移动

​编辑

 

连接 Mysql

前面我们已经实现了接口的请求,那么作为后端项目,必然会涉及到数据的 CURD,这里必须得使用数据库实现数据的持久化了,数据库我们这篇文章使用的是 Mysql, 如果是使用的 Mongoose 可以参考笔者的另一篇文章 MidwayJs 多数据库配置,并实现 Mongoose 自增 Id。

数据库安装

笔者使用的是 Homebrew 来安装的 Mysql,如果没有安装 Homebrew,可以直接下载安装包安装,或者先安装 Homebrew_web前端培训



点击并拖拽以移动

​编辑

 

// 确认 brew 在正常工作

brew doctor

// 更新包

brew update

// 或者更新全局所有包

brew upgrade

// 安装 mysql

brew install mysql

数据库服务启动

安装完成之后启动 Mysql 服务

mysql.server start



点击并拖拽以移动

​编辑

 

启动完成。

Mysql 可视化

我们使用可视化工具来管理数据库,这里笔者使用的是 Navicat Premium,可视化工具相对比较多,你可以使用自己喜欢的可视化工具管理数据库。

我们创建一个 Mysql 数据库连接,连接名称可以随意取自己喜欢的,输入默认的端口,输入自己数据库的密码。



点击并拖拽以移动

​编辑

 

连接成功之后,我们创建一个 Midway 的数据表



点击并拖拽以移动

​编辑

 

创建成功之后



点击并拖拽以移动

​编辑

 

引入 TypeORM

TypeORM 是 node.js 现有社区最成熟的对象关系映射器(ORM )。Midway 和 TypeORM 搭配,使开发更简单。

安装组件

安装 ORM 组件,提供数据库 ORM 能力

yarn add @midwayjs/orm typeorm --save

引入组件

在 src/configuration.ts 引入 ORM 组件,代码如下:

// configuration.ts

import { Configuration } from '@midwayjs/decorator';

import * as orm from '@midwayjs/orm';

import { join } from 'path';

@Configuration({

imports: [

// ...

orm // 加载 orm 组件

],

importConfigs: [

join(__dirname, './config')

]

})

export class ContainerConfiguratin {

}

安装数据库 Driver

yarn add mysql mysql2 --save

配置数据库连接

在 src/config/config.default.ts 中配置 mysql 连接。

import { MidwayConfig } from '@midwayjs/core';

export default {

// use for cookie sign key, should change to your own and keep security

keys: '1653223786698_4903',

koa: {

port: 7001,

globalPrefix: '/demo',

},

orm: {

type: 'mysql',

host: '127.0.0.1',

port: 3306,

username: 'root',

password: '', // 数据库密码

database: 'midway', // 数据表

synchronize: true,

logging: false,

},

} as MidwayConfig;

保存之后重启,数据库连接成功



点击并拖拽以移动

​编辑

 

实现 model

在 src 文件夹下面创建 model 文件夹,创建一个数据库表

声明一个实体 table

// user.ts

import { EntityModel } from '@midwayjs/orm';

import { Column, PrimaryGeneratedColumn } from 'typeorm';

// 映射 user table

@EntityModel({ name: 'user' })

export class UserModel {

// 声明主键

@PrimaryGeneratedColumn('increment') id: number;


// 映射 userName 和 user 表中的 user_name 对应

@Column({ name: 'user_name' }) userName: string;

@Column({ name: 'age' }) age: number;

@Column({ name: 'description' }) description: string;

}

修改 src/user.service.ts 文件

import { Provide } from '@midwayjs/decorator';

import { InjectEntityModel } from '@midwayjs/orm';

import { Repository } from 'typeorm';

import { IUserOptions } from '../interface';

import { UserModel } from '../model/user';

@Provide()

export class UserService {

@InjectEntityModel(UserModel) userModel: Repository<UserModel>;

async getUser(options: IUserOptions) {

return {

uid: options.uid,

username: 'mockedName',

phone: '12345678901',

email: 'xxx.xxx@xxx.com',

};

}


async addUser() {

let record = new UserModel();

record = this.userModel.merge(record, {

userName: 'migor',

age: 18,

description: 'test',

});

try {

const created = await this.userModel.save(record);

return created;

} catch (e) {

console.log(e);

}

}

}

通过 InjectEntityModel 装饰器,注入实例化 userModel,启动服务之后,我们在 midway 数据表中增加 user table



点击并拖拽以移动

​编辑

 

修改 src/controller/api.controller.ts

import { Inject, Controller, Get, Query } from '@midwayjs/decorator';

import { Context } from '@midwayjs/koa';

import { UserService } from '../service/user.service';

@Controller('/api')

export class APIController {

@Inject()

ctx: Context;

@Inject()

userService: UserService;

@Get('/get_user')

async getUser(@Query('uid') uid) {

const user = await this.userService.getUser({ uid });

return { success: true, message: 'OK', data: user };

}

@Get('/add_user')

async addUser() {

const user = await this.userService.addUser();

return { success: true, message: 'OK', data: user };

}

}

在 Postman 中调用 add_user 接口



点击并拖拽以移动

​编辑

 

我们可以看到已经能正常返回我们保存的值了,那么我们去数据库看一下,数据是否保存了,刷新一下数据库,我们可以看到数据已经保存成功。



点击并拖拽以移动

​编辑

 

大功告成,至此我们完成数据的保存,那么后面我们可以进行数据的查询,删除,更新等。代码如下

在 user.service.ts 中添加如下代码

// 删除用户

async deleteUser() {

const record = await this.userModel

.createQueryBuilder()

.delete()

.where({ userName: 'migor' })

.execute();

const { affected } = record || {};

return affected > 0;

}

// 更新用户信息

async updateUser() {

try {

const result = await this.userModel

.createQueryBuilder()

.update()

.set({

description: '测试更新',

})

.where({ userName: 'migor' })

.execute();

const { affected } = result || {};

return affected > 0;

} catch (e) {

console.log('接口更新失败');

}

}

// 查询

async getUserList() {

const users = await this.userModel

.createQueryBuilder()

.where({ userName: 'migor' })

.getMany();

return users;

}

在 api.controller.ts 中增加相应的接口

@Get('/get_user_list')

async getUsers() {

const user = await this.userService.getUserList();

return { success: true, message: 'OK', data: user };

}

@Get('/update_user')

async updateUser() {

const user = await this.userService.updateUser();

return { success: true, message: 'OK', data: user };

}

@Get('/delete_user')

async deleteUser() {

const user = await this.userService.deleteUser()

return { success: true, message: 'OK', data: user };

}

接入 Swagger

安装组件

接入 swagger 组件和 swagger ui 组件

yarn add @midwayjs/swagger swagger-ui-dist

开启组件

在 configuration.ts 中增加组件

import { Configuration, App } from '@midwayjs/decorator';

import * as koa from '@midwayjs/koa';

import * as validate from '@midwayjs/validate';

import * as info from '@midwayjs/info';

import { join } from 'path';

import * as orm from '@midwayjs/orm';

import * as swagger from '@midwayjs/swagger';

// import { DefaultErrorFilter } from './filter/default.filter';

// import { NotFoundFilter } from './filter/notfound.filter';

import { ReportMiddleware } from './middleware/report.middleware';

@Configuration({

imports: [

koa,

validate,

{

component: info,

enabledEnvironment: ['local'],

},

orm,

swagger,

],

importConfigs: [join(__dirname, './config')],

})

export class ContainerLifeCycle {

@App()

app: koa.Application;

async onReady() {

// add middleware

this.app.useMiddleware([ReportMiddleware]);

// add filter

// this.app.useFilter([NotFoundFilter, DefaultErrorFilter]);

}

}

项目自动重启成功之后,访问地址

  • UI: http://127.0.0.1:7001/swagger-ui/index.html

  • JSON: http://127.0.0.1:7001/swagger-ui/index.json

启用之后可以查看到对应的接口



点击并拖拽以移动

​编辑

 

swagger 组件会自动识别各个 @Controller 中每个路由方法的 @Body()、@Query()、@Param() 装饰器,提取路由方法参数和类型。

增加接口标签

我们希望给接口增加标签注释,这样才能更好的列举接口的定义

import { Inject, Controller, Get, Query } from '@midwayjs/decorator';

import { Context } from '@midwayjs/koa';

import { ApiOperation } from '@midwayjs/swagger';

import { UserService } from '../service/user.service';

@Controller('/api')

export class APIController {

@Inject()

ctx: Context;

@Inject()

userService: UserService;

@ApiOperation({ summary: '获取单个用户' })

@Get('/get_user')

async getUser(@Query('uid') uid) {

const user = await this.userService.getUser({ uid });

return { success: true, message: 'OK', data: user };

}

@ApiOperation({ summary: '增加单个用户' })

@Get('/add_user')

async addUser() {

const user = await this.userService.addUser();

return { success: true, message: 'OK', data: user };

}

@ApiOperation({ summary: '获取用户列表' })

@Get('/get_user_list')

async getUsers() {

const user = await this.userService.getUserList();

return { success: true, message: 'OK', data: user };

}

@ApiOperation({ summary: '更新单个用户' })

@Get('/update_user')

async updateUser() {

const user = await this.userService.updateUser();

return { success: true, message: 'OK', data: user };

}

@ApiOperation({ summary: '删除单个用户' })

@Get('/delete_user')

async deleteUser() {

const user = await this.userService.deleteUser();

return { success: true, message: 'OK', data: user };

}

}

重启之后,可以查看 swagger ui 界面,标签增加成功。



点击并拖拽以移动

​编辑

 

文章来源于程序员成长指北

用户头像

@零度

关注

关注尚硅谷,轻松学IT 2021.11.23 加入

IT培训 www.atguigu.com

评论

发布
暂无评论
web前端培训带你学习 Midwayjs 实战_node.js_@零度_InfoQ写作社区