文章学习参考:
基础认知
控制器 (controller)
负责解析用户的输入,处理后返回相应的结果.
- 接收、校验、处理 HTTP 请求参数
- 向下调用服务(Service)处理业务
- 通过 HTTP 将结果响应给用户
ctx.query:URL 中的请求参数(忽略重复 key)
ctx.quries:URL 中的请求参数(重复的 key 被放入数组中)
ctx.params:Router 上的命名参数
ctx.request.body:HTTP 请求体中的内容
ctx.request.files:前端上传的文件对象
ctx.getFileStream():获取上传的文件流
ctx.multipart():获取 multipart/form-data 数据
ctx.cookies:读取和设置 cookie
ctx.session:读取和设置 session
ctx.service.xxx:获取指定 service 对象的实例(懒加载)
ctx.status:设置状态码
ctx.body:设置响应体
ctx.set:设置响应头
ctx.redirect(url):重定向
ctx.render(template):渲染模板
服务 service
具体业务逻辑的实现,封装好一个 service 之后 通过 controller 去调用即可。 需要注意的是 service 是懒加载的,只有当访问到它的时候框架才会去实例化它。
通常情况下,在 Service 中会做如下几件事情:
- 处理复杂业务逻辑
- 调用数据库或第三方服务(例如 GitHub 信息获取等)
post
- 实际业务中,为了接口请求的安全性,和多数据请求参数,多类型请求方式,附件,图片上传等多采用post请求。
下面就介绍如何在Egg中使用post请求: 跨域等基础配置。
// 安装跨域插件
npm i egg-cors --save
// 配置跨域插件 配置config下的plugin.js
cors: {
enable: true,
package: 'egg-cors',
},
// 配置config下的config.default.js
// 关闭crsf,开启跨域
config.security = {
csrf: {
enable: false,
},
domainWhiteList: [ ],
};
// 允许跨域方法
config.cors = {
origin: '*',
allowMethods: 'GET, PUT, POST, DELETE, PATCH',
};
到这里我们 post 请求就通了~
router
路由定义了 请求路径(URL) 和 控制器(Controller) 之间的映射关系. router.verb(‘path-match’, controllerAction)
如果有很多模块的话建议采用分组模式,添加 router 文件夹创建 各个业务块路由.js ,然后在 router.js 统一导入。
异常处理-中间件
默认 Egg 框架中返回的错误html页面,这既不友好,也不知道是为了什么报错。
避免重复造轮子
egg基于koa,这里我们没必要重复造轮子,可以使用社区一个优秀的轮子koa-json-err;
npm install --save koa-json-error
// middlware/error.js
module.exports = require('koa-json-error');
// config/config.xxx.js中可以配置
config.middleware = ['error'];
config.error = {
// 这里使用appInfo.env来判断环境,仅仅在非生产环境下打开堆栈信息,用于调试
postFormat: (e, { stack, ...rest}) => appInfo.env === 'prod' ? rest: { stack, ...rest}
}
跨域
jwt 鉴权
配置MySQL数据库 (进行中)
之前的文章都是静态的数据,要完成持久化的真实数据,就需要用到数据库,这里我们使用的是工作中使用最频繁的数据库MySQL,通过 egg-sequelize 插件,就可以像操作一些对象属性一样,完成业务的增删改查,语义化的操作,而不用手动的去写SQL。
在 Node.js 社区中,sequelize 是一个广泛使用的 ORM 框架。
model
首先需要定义表结构映射,这个是通过 Sequelize 插件来的,完了之后才能正常使用 sequelize 操作数据库的方法。
编写接口基础服务
入参:get \ post 统一转换
默认:
// 1、params 获取参数方式
router.get('/user/findById/:id', controller.user.info);
const { ctx, params } = this;
const userId = params;
// 2、query
router.get('/user/findById?id', controller.user.info);
const { ctx } = this;
const userId = ctx.query.id;
出参:统一输出模板函数
在 controller 中 const { ctx } = this; 重点关注 ctx
封装 controller 基类
由于 Controller 是类,因此可以通过自定义基类的方式封装常用方法,例如:
// app/core/base_controller.js
const { Controller } = require('egg');
class BaseController extends Controller {
get user() {
return this.ctx.session.user;
}
success(data) {
this.ctx.body = { success: true, data };
}
notFound(msg) {
this.ctx.throw(404, msg || 'not found');
}
}
module.exports = BaseController;
然后让所有 Controller 继承这个自定义的 BaseController:
// app/controller/post.js
const Controller = require('../core/base_controller');
class PostController extends Controller {
async list() {
const posts = await this.service.listByUser(this.user);
this.success(posts);
}
}
参数验证
对于一些客户端的参数,有必要进行一些校验,保证了系统的安全性和数据的可靠性
// 1. 安装egg-valparams
npm i egg-valparams --save
// 2.配置
// config/plugin.js
valparams : {
enable : true,
package: 'egg-valparams'
},
// config/config.default.js
config.valparams = {
locale : 'zh-cn',
throwError: true
};
// 3.
// 创建单个用户
async createUser() {
const { ctx } = this;
ctx.validate({
username: { type: 'string', required: true, desc: '用户名' },
password: { type: 'string', required: true, desc: '密码' },
sex: { type: 'string', required: false, defValue: '男', desc: '性别' },
});
const { username, password } = ctx.request.body;
const result = await ctx.model.User.create({ username, password });
ctx.body = {
code: 200,
data: result,
msg: '操作成功',
};
}