最近在慕课网学习使用egg.js这个框架来编写服务端,所以简单的做一下笔记。
这里我直接按照官网的教程使用的脚手架,前提是你的npm版本 >= 6.1.0,然后随便找一个目录来开始创建我们的项目。
mkdir erdan-lego && cd erdan-lego
$ npm init egg --type=simple
$ npm i
启动项目:
npm run dev
这个时候我们的项目就已经创建好了,是不是很快?就是简单的几条命令而已。如果你有koa2的使用经验,那么对于上手egg.js还是非常有帮助的,因为egg.js就是在koa的模型基础上,做了进一步的增强。
编写controller
配置路由映射
import { Application } from 'egg';
export default (app: Application) => {
const { controller, router } = app;
router.get('/', controller.home.index);
};
这时,当我们输入npm run dev来启动我们的项目,然后在地址栏输入localhost:7001 就可以看到会有我们编写好的内容啦,是不是非常方便的就创建好了一个接口?
静态资源
egg.js内置了static插件,所以我们将静态资源放到app/public目录下即可。
模板渲染
绝大多数情况下,我们读取数据后渲染模板,然后呈现给用户,所以我们需要引入对应的模板引擎,这里框架并不限制我们使用何种模板引擎,所以开发者可以引入不同的插件来实现差异化定制。我这里使用的是Nunjucks这个模板引擎。首先,我们来安装一下对应的插件egg-view-nunjucks
npm i egg-view-nunjucks --save
安装好了以后,我们需要在配置中开启插件:
// config/plugin.ts
import { EggPlugin } from 'egg';
const plugin: EggPlugin = {
// static: true,
nunjucks: {
enable: true,
package: 'egg-view-nunjucks'
}
};
export default plugin;
// config/config.default.ts
config.view = {
defaultViewEngine: 'nunjucks',
};
紧接着,让我们来写一个模板文件,放在app/view目录下
The Dog Show
Welcome to the dog show
添加对应的Controller 和 Router
import { Controller } from 'egg';
export default class TestController extends Controller {
async getDog() {
const { service, ctx } = this;
const resp = await service.dog.show();
await ctx.render('test.nj', { url: resp.message });
}
}
在这里,我们使用ctx.render来渲染模板引擎, 并向它传递我们的参数,是一个url地址。
import { Application } from 'egg';
export default (app: Application) => {
const { controller, router } = app;
router.get('/api/getDog', controller.test.getDog)
};
可能有朋友注意到了,上面controller的逻辑中我使用到了service,是的,在实际应用中Controller一般不会自己产出数据,也不包含复杂的逻辑,复杂的过程应该抽象为业务逻辑层Service。因为我这里请求了第三方的api接口,所以把逻辑写在了service中,下面我们来看一下egg.js中如何向第三方发起请求:
import { Service } from 'egg';
interface DogResp {
message: string;
status: string;
}
export default class Test extends Service {
public async show() {
const resp = await this.ctx.curl('https://dog.ceo/api/breeds/image/random', { dataType: 'json' });
return resp.data;
}
}
在egg.js中,我们向第三方发起请求用的是挂载在ctx对象上的curl方法,拿到我们要的数据以后return出去就好了,这样在controller就可以接受到。下面我们来看一下访问这个接口的效果吧:
是的,页面上出现了一张狗狗的照片,这就是使用了模板引擎以后的效果,模板引擎会拿到我们传递给它的参数,然后决定如何显示。
编写扩展
写着写着,我发现我需要将定义两个辅助函数来帮我们格式化一下返回给前端的数据,比如成功了我们返回给前端什么样的数据以及失败以后我们返回给前端什么样的错误码之类的。egg.js也提供了一种快速扩展的方式,只需要在app/extend目录下提供扩展脚本即可。下面我们来写两个辅助函数来帮助我们吧~
编写success辅助函数
// app/extends/helper.ts
import { Context } from 'egg'
interface RespType {
ctx: Context;
res?: any;
msg?: string;
}
export default {
success({ ctx, res, msg }: RespType) {
ctx.body = {
errno: 0,
data: res ? res : null,
message: msg ? msg : '请求成功'
}
ctx.status = 200
}
}
这里我们规范好了当请求成功的时候返回给errno等于0给前端;可是紧接着又有一个新的问题接踵而来,那就是,当请求错误的时候,我们应该如何告知前端具体的错误是什么呢?答案就是我们可以定义一个枚举文件来写上可能存在的错误类型。
// 定义错误枚举对象
export const userErrorMessages = {
userValidateFail: {
errno: 101001,
message: '输入信息验证失败'
},
createUserAlreadyExists: {
errno: 101002,
message: '该邮箱已被注册,请重新登录'
},
loginCheckFailInfo: {
errno: 101003,
message: '该用户不存在或者密码错误'
},
loginValidateFail: {
errno: 101004,
message: '登录校验失败'
},
sendVeriCodeFrequentlyFailInfo: {
errno: 101005,
message: '请勿频繁获取短信验证码'
},
loginVeriCodeIncorrectFailInfo: {
errno: 101006,
message: '验证码不正确'
},
sendVeriCodeError: {
errno: 101007,
message: '验证码发送失败'
},
giteeOauthError: {
errno: 101008,
message: 'giee 授权出错'
}
}
// app/extend/helper.ts
import { Context } from 'egg'
import { userErrorMessages } from '../controller/user'
interface ErrorRespType {
ctx: Context;
errorType: keyof (typeof userErrorMessages);
error?: any;
}
export default {
error({ ctx, errorType, error }: ErrorRespType) {
const { message, errno } = userErrorMessages[errorType]
ctx.body = {
errno,
message,
...(error && { error })
}
ctx.status = 200
},
}
经过这两个辅助函数返回给前端,前端看到以后就可以更加清楚的知道是哪里出错了,这就是egg.js提供的扩展能力。
编写middleware
前面我们说过egg.js是基于Koa实现的,所以他们两者的中间件形式是一样的,都是基于洋葱圈模型。每次我们编写一个中间件,相当于在洋葱外面包了一层。
下面我们编写一个简单的中间件:
import { Context, Application, EggAppConfig } from 'egg';
import { appendFileSync } from 'fs';
export default (options: EggAppConfig['myLogger'], app: Application) => {
return async (ctx:Context, next: () => Promise) => {
const startTime = Date.now();
const requestTime = new Date();
await next();
const ms = Date.now() - startTime;
const logTime = `${requestTime}--${ctx.method}--${ctx.url}--${ms}ms`;
if (options.allowedMethod.includes(ctx.method)) {
appendFileSync('./log.txt', logTime + '\n');
}
};
};
这个中间件也是十分的简单,就是打印出来每一个接口的日志信息到我们新建好的log.txt文件中去。中间件文件放在app/middleware目录下,它需要exports一个普通的function,接受两个参数:
使用中间件
我们编写好了一个中间件以后,如何使用它呢?egg.js支持以下方式:
在应用中使用中间件
在应用中,我们可以完全通过配置来加载自定义中间件,并决定它们的顺序。
export default (appInfo: EggAppInfo) => {
const config = {} as PowerPartial;
config.view = {
defaultViewEngine: 'nunjucks',
};
// add your egg config in here
config.middleware = [ 'myLogger' ];
// add your special config in here
const bizConfig = {
sourceUrl: `https://github.com/eggjs/examples/tree/master/${appInfo.name}`,
myLogger: {
allowedMethod: [ 'POST' ]
}
};
// the return config will combines to EggAppConfig
return {
...config as {},
...bizConfig,
};
};
在单个路由中使用:
import { Application } from 'egg';
export default (app: Application) => {
const { controller, router } = app;
const logger = app.middleware.myLogger({
allowedMethod: [ 'GET' ],
}, app)
router.get('/', controller.home.index);
router.get('/dog', logger, controller.test.getDog)
};
这是第一篇学习egg.js的文章,其实都挺基础的,官网也基本都有对应的例子,自己写下来是为了帮助自己理解egg.js,其实觉得和koa真的好像,希望接下来有时间了再继续写关于学习egg.js的文章,加油啦~