这篇文章是我学习node+typescript的笔记,有一些是搬运的,下一篇是具体的登录接口的代码。
项目准备
一、技术栈的选择
开发类
Typescript
TypeScript是一个开源的编程语言,通过在JavaScript(世界上最常用的语言之一)的基础上添加静态类型定义生成,TypeScript可以在您运行代码之前发现错误并提供修复,从而改善您的开发体验,它是 JavaScript 的一个超集,而且本质上向这个语言添加了可选的静态类型和基于类的面向对象编程。
框架
Koa 是一种简单好用的Node.js Web框架,也提供一个更小型、更富有表现力、更可靠的 Web 应用和 API 的开发基础。
数据库
MySQL数据库
sequelize-typescript
一个功能更丰富和强大的数据库操作库。
Day.js
Day.js 是一个轻量的处理时间和日期的 JavaScript 库,和 Moment.js 的 API 设计保持完全一样
Jsonwebtoken
JSON Web Token是一种跨域认证解决方案,简称JWT。
二、规范接口
参考文章:developer.aliyun.com/article/283…
在开发程序的时候,好的接口规范能让前后端的合作更顺利,前端和后端制定接口的时候,1、可以写一个统一的网络请求函数和数据返回处理函数,2、可以对对错误进行统一的处理和解析。
后端一些基本字段
接口地址
一般的规则是:basePath/api/v1/login
接口的请求方式
一般的接口请求方式有:get/post
接口参数
一般都是一个object,例如:
{
userAccount:'',
userPassword:''
}
接口版本号
前端可以传递version=1.0来请求响应版本的接口。
接口返回的状态码
一般这个字段使用code来表示
我们借鉴http的状态码,前后端的接口规范辨别一个接口是否调用成功,最好也采用状态码来识别接口是否调用成功,比如200表示成功、4开头的表示接口不存在、5开头的表示接口调用错误等
接口返回的主数据
该字段一般使用data来表示
接口返回给用户的提示信息
当服务器出错时,需要提示给用户
data字段说明
数组字段list
当返回的结构为数组的时候,往往需要很多附加字段来做标识,所以数组的数据需要放在list里。
page:页码
有分页的时候需要返回 ,在分页中,page的起页为1,而不能为0
pageSize每页显示的数量
有分页的时候需要返回
totallPage总页数
数据的总页数,数据分页的时候需要返回
totalCount总数目
有时候前端需要返回数据的总数,这时候需要把总数返回来
data:{
page:1,
pageSize: 1,
list: [{},{}, {}]
}
三、熟悉http响应状态
HTTP响应图示:
四、Typescript配置
配置文件tsconfig.json,配置代码如下:
{
"compilerOptions": {
"allowJs": true,//允许JS
"outDir": "./dist",//输出文件
"module": "commonjs",//模块类型
"target": "es2016",//编译目标
"sourceMap": true,//显示原始TypeScript源代码
"noImplicitAny": true,//隐式类型检查
"strictNullChecks": true,//严格的空值检查
"experimentalDecorators": true,//元数据
"emitDecoratorMetadata": true,//装饰器
"resolveJsonModule": true,//允许导入.json模块
"esModuleInterop": true,//es模块互相操作
"allowSyntheticDefaultImports": true//允许默认导入
},
"include": [
"./src/**/*"
]
}
ts-node-dev
实现了热重载(修改代码即可自动重启服务)。
安装:
npm i ts-node-dev --save-dev
yarn add ts-node-dev --dev
五、koa-ts-controllers
在 koa-ts-controllers 中,利用了TypeScript的装饰器,我们可以直接在指定类的某个方法中对其进行路由绑定。而且通过它提供的功能,我们可以更方便就能实现更符合 RESTful 规范的接口,下面我们就来看具体的使用:
安装与配置
安装koa-ts-controllers
npm i koa-ts-controllers
入口文件添加代码
import {bootstrapControllers} from 'koa-ts-controllers';
import path from 'path';
import KoaRouter from 'koa-router';
const app = new Koa();
const router = new KoaRouter();
(async () => {
await bootstrapControllers(app, {
router,
basePath: '/api',
versions: [1],
controllers: [
__dirname + '/controllers/**/*'
]
});
app.use( router.routes() );
app.listen(configs.server.port, configs.server.host, () => {
console.log(`访问启动成功: http://${configs.server.host}:${configs.server.port}`);
});
})()
其中bootstrapControllers是koa-ts-controllers 得一个主要函数,主要的功能是用来初始化应用控制器和路由的绑定。
配置
router
使用的路由库
basePath
设置访问接口路径的前缀,如basePath/api
versions
接口版本,会附加到 basePath
之后,如:basePath/api/v1
controllers
控制器类文件的存放目录。
控制器
我们可以把控制类的文件放在controllers目录下,当然仅仅只是这么一个类是没有什么作用的,我们还需要把这个类变成控制类,并绑定其中的类方法到指定的路由中,这就需要用到 装饰器。
装饰器
Controller 装饰器
语法:@Controller(basePath?)
针对类的装饰器,被装饰的类就会成为一个控制器类,只有控制器类下的方法才能与路由进行绑定。
basePath:与该类中方法绑定的路由的前缀路径。
HTTP请求方法装饰器
koa-ts-controllers支持如下几个方法装饰器:
- @Get(path)
- @Post(path)
- @Patch(path)
- @Put(path)
- @Delete(path)
这些装饰器使用在类的方法中,接受一个 path参数,作用是把被装饰的类方法与指定的 HTTP方法及其 融合后的 path进行绑定。
代码实例
import {Controller, Get} from 'koa-ts-controllers';
@Controller('/test')
class TestController {
@Get('/hello')
async sayWorld() {
return 'Hello Test!';
}
}
启动该服务,我们可以通过:
http://localhost:8080/api/v1/test/hello
这样的路径去访问该API。
六、后端-请求数据获取
获取请求数据
通常客户端的请求会根据业务的需求发送一些额外数据,数据的传输携带方式有如下几种场景:
Params
动态路由可变的部分:http://localhost:8080/api/v1/test/hello/1
Query
地址问号后的部分:http://localhost:8080/api/v1/test/hello?id=1
Body
请求正文部分:
{
account:'shaobing',
password:'123456'
}
headers
除了一些内置头,还可以自定义一些头信息,我们应用中的用户授权 token
就是通过头**息来传递的。
koa-ts-controllers 不仅仅提供请求方法装饰器来处理请求,同时也提供了一些 参数装饰器来处理请求数据。
-
@Params(),代码如下:
import {Controller, Get, Params} from "koa-ts-controllers"; @Controller('/test') class UserController { @Get('/user/:id') async getUser(@Params() params: {id: number}) { return `当前id是:${params.id}` } @Get('/user/:id') async getUser(@Params('id') id: number) { return `当前id是:${id}` } }
-
@Query(),代码如下:
import {Controller, Get, Query} from "koa-ts-controllers"; @Controller('/test') class UserController { @Get('/user') async getUser(@Query() params: {id: number}) { return `当前id是:${params.id}` } @Get('/user') async getUser(@Query('id') id: number) { return `当前id是:${id}` } }
-
@Body(),代码如下:
import {Controller, Get, Body} from "koa-ts-controllers"; @Controller('/test') class LoginController { @Post('/login') async index(@Body() body: { account: string, password:string }) { return `当前账号密码是是:${JSON.stringify(body)}` } }
-
@Header(),代码如下:
import {Controller, Get, Header} from "koa-ts-controllers"; @Controller('/test') class LoginController { @Post('/login') async getUser(@Header() header:any) { console.log(header); } }
七、后端-统一响应处理
成功统一响应处理
一般情况下,成功响应的处理都比较简单,返回的状态码一般是200
错误统一响应处理
一般情况下,错误响应的处理可分为服务器错误、业务逻辑错误、请求的接口不存在等等。
服务器错误一般的状态码是500,请求接口不存在一般的状态码是(404)、业务逻辑错误一般的状态码可以是(422、401等)。
错误捕获处理
我们可以通过koa-ts-controllers的统一错误处理函数来捕获错误、并且同时输出。
await bootstrapControllers(app, {
router,
basePath: '/api',
versions: [1],
controllers: [
__dirname + '/controllers/**/*'
],
errorHandler: async (err: any, ctx: Context) => {
let status = 500;
let body: any = {
"statusCode": 500,
"error": "Internal Server error",
"message": "An internal server error occurred"
};
ctx.status = status;
ctx.body = body;
}
});
验证请求数据
在使用params的场景下,我们可以直接通过正则的方法进行验证,例如:
@Get('/user/:id(\\d+)')
async index(@Params() params:{id:number}){
return params.id;
}
在使用Query和Body的场景下,我们可以通过class-validator库来进行统一的验证处理,class-validator地址。
比如我们要验证传入的字符串是否为数字,我们可以使IsNumberString来验证,代码如下:
import {Controller, Get,Query} from "koa-ts-controllers";
import {IsNumberString} from 'class-validator';
class GetUsersQuery {
@IsNumberString()
page: number;
}
@Get('/users')
public async getUsers(@Query() query: GetUsersQuery){
console.log(query);
}
验证返回失败返回的数据
{
"data": [
{
"field": "page",
"violations": {
"isNumberString": "page must be a number string"
}
}
],
"isBoom": true,
"isServer": false,
"output": {
"statusCode": 422,
"payload": {
"statusCode": 422,
"error": "Unprocessable Entity",
"message": "validation error for argument type: query"
},
"headers": {}
}
}
通过上面的数据我们可以把错误捕获处理的函数进行更进一步的处理,
await bootstrapControllers(app, {
router: router,
basePath: '/api',
versions: [1],
controllers: [
__dirname + '/controllers/**/*'
],
errorHandler: async (err: any, ctx: Context) => {
let status = 500;
let body: any = {
"statusCode": 500,
"error": "Internal Server error",
"message": "An internal server error occurred"
};
if (err.output) {
status = err.output.statusCode;
body = {...err.output.payload};
if (err.data) {
body.errorDetails = err.data;
}
}
ctx.status = status;
ctx.body = body;
}
});