前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >​eggjs实战

​eggjs实战

作者头像
一粒小麦
发布2019-07-26 19:01:06
3K0
发布2019-07-26 19:01:06
举报
文章被收录于专栏:一Li小麦一Li小麦
代码语言:javascript
复制
本文主要内容- 文档生成工具:基于插件的swagger-doc接口定义- 统一异常处理- 基于扩展的helper响应统一处理- validate接口格式检查- 三层结构

初步

安装

推荐直接使用脚手架,只需几条简单指令,即可快速生成项目:

代码语言:javascript
复制
$ mkdir egg-example && cd egg-example$ npm init egg --type=simple$ npm i

启动项目:

代码语言:javascript
复制
$ npm run dev$ open http://localhost:7001

构建swagger-doc:

Swagger – The World's Most Popular Framework for APIs.

swagger会扫描配置的API文档格式自动生成一份json数据,而swagger官方也提供了ui来做通常的展示,当然也支持自定义ui的。不过对后端开发者来说,能用就可以了,官方就可以了。

swagger-doc可以做两件事。

  • 自动生成路由
  • 生成一个文档,描述你写的接口。
安装
代码语言:javascript
复制
npm install egg-swagger-doc-feat -s

在插件中注册:

代码语言:javascript
复制
// config/plugin.js'use strict';
/** @type Egg.EggPlugin */module.exports = {  // had enabled by egg  // static: {  //   enable: true,  // }
  // config/plugin  swaggerdoc: {    enable: true,    package: 'egg-swagger-doc-feat',  }};

在config下的config.default.js添加如下配置

代码语言:javascript
复制
  config.swaggerdoc = {    dirScanner: './app/controller',    apiInfo: {      title: 'djtao接口',      description: 'djtao接口 swagger-ui for egg', version: '1.0.0',    },    schemes: ['http', 'https'],    consumes: ['application/json'],    produces: ['application/json'],    enableSecurity: false,    // enableValidate: true,    routerMap: true,    enable: true,  }
使用

在controller下新建user.js,写一个创建用户接口

代码语言:javascript
复制
// app/controller/user.jsconst Controller = require('egg').Controller/*** @Controller 用户管理*/class UserController extends Controller {    constructor(ctx) {        super(ctx)    }
    /** 注意这段描述    * @summary 创建用户    * @description 创建用户,记录用户账户/密码/类型    * @router post /api/user    * @request body createUserRequest *body    * @response 200 baseResponse 创建成功    */    async create() {        const { ctx } = this        ctx.body = 'user ctrl'    }}module.exports = UserController

注意,描述里用到了createUserRequest对象 在app下新建一个contract目录,新建index.js(定义标准接口的请求和返回格式)

代码语言:javascript
复制
// app/contract/index.jsmodule.exports = {    baseRequest: {        id: { type: 'string', description: 'id 唯一键', required: true, example: '1' },    },    baseResponse: {        code: { type: 'integer', required: true, example: 0 }, data: { type: 'string', example: '请求成功' }, errorMessage: { type: 'string', example: '请求成功' },    },};

再在contract下新建user.js(定义添加用户接口的字段)

代码语言:javascript
复制
// app/contract/index.jsmodule.exports = {    createUserRequest:{        mobile:{type:'string',required:true,descption:'手机号码',example:'13800138000',format:/^1[34578]\d{9}$/ },        password:{type:'string',required:true,descption:'密码',example:'123456'},        realName:{type:'string',required:true,descption:'姓名',example:'djtao'},    }};

配置完成后,启动项目访问http://localhost:7001/swagger-ui.html,就可以看到文档了。包括类型,还带上了测试用例。

全部接口文档包括:http://localhost:7001/swagger-doc

有了它,可以很方便地在注释创建接口,不需要再创建路由了!

数据格式规范化

异常处理

前一篇文章讲了手撸degg,如何实现捕获异常的中间件,现在看看在egg中怎么做(方法和思维几乎一样):

代码语言:javascript
复制
// /middleware/error_handler.js'use strict'module.exports = (option, app) => {    return async function (ctx, next) {        try {            await next()        } catch (err) {            // 所有的异常都在 app 上触发一个 error 事件,框架会记录一条错误日志            app.emit('error', err, this)            const status = err.status || 500            // 生产环境时 500 错误的详细错误内容不返回给客户端,因为可能包含敏感信息            const error = status === 500 && app.config.env === 'prod' ?                'Internal Server Error' :                err.message            // 从 error 对象上读出各个属性,设置到响应中            ctx.body = {                code: status, // 服务端自身的处理逻辑错误(包含框架错误500 及 自定义业务逻辑错误533开 始 ) 客户端请求参数导致的错误(4xx开始),设置不同的状态码                error: error            }            if (status === 422) {                ctx.body.detail = err.errors            }            ctx.status = 200        }    }}

写好之后再config中注册

代码语言:javascript
复制
  // config.default.jsconfig.middleware = ['errorHandler']

我们测试一下:

代码语言:javascript
复制
在del接口添加一个错误的a.a.a=1,运行:

异常处理的好处在于:统一数据返回格式。这是对前端最友好的报错。

helper方法实现统一响应格式

Helper 函数用来提供一些实用的 通用方法。

它的作用在于我们可以将一些常用的动作抽离在 helper.js 里面成为一个独立的函数,这样可以用 JavaScript 来写复杂的逻辑,避免逻辑分散各处。另外还有一个好处是 Helper 这样一个简单的函数,可以让我们更容易 编写测试用例。

框架内置了一些常用的 Helper 函数。

代码语言:javascript
复制
你可以通过 ctx.helper 调用

我们也可以编写自定义扩展的 Helper 。

应用开发中,我们可能经常要自定义一些 helper 方法,例如上面例子中的 formatUser,我们可以通过框架扩展的形式来自定义 helper 方法。

在这里我们定义了两个方法。

代码语言:javascript
复制
// extend/helper.jsconst moment = require('moment') // 格式化时间exports.formatTime = time => moment(time).format('YYYY-MM-DD HH:mm:ss')// 处理成功响应exports.success = ({ ctx, res = null, msg = '请求成功' })=> {  ctx.body = {    code: 0,data: res,msg}  ctx.status = 200}

调用时可以直接 ctx.sucess({ctx,res})

代码语言:javascript
复制
    /**    * @summary 获取当前时间    * @description 删除* @router get /api/user/now    * @response 200 baseResponse 创建成功    */    async now() {        const { ctx } = this        let now = ctx.helper.formatTime(new Date())        // a.a.a=1        ctx.helper.success({ ctx, res: now })    }

数据校验

安装egg-validate校验插件:

代码语言:javascript
复制
npm i egg-validate -s

注册插件;

代码语言:javascript
复制
  // plugin.js    validate: {    enable: true,    package: 'egg-validate',  },

对注册用户进行校验:

代码语言:javascript
复制
    /**    * @summary 创建用户    * @description 创建用户,记录用户账户/密码/类型    * @router post /api/user    * @request body createUserRequest *body    * @response 200 baseResponse 创建成功    */    async create() {        const { ctx, service } = this        // 校验参数        ctx.validate(ctx.rule.createUserRequest)        ctx.body = 'user ctrl'    }

现在模拟一下post请求:

连接文档型数据库(mongodb)

代码语言:javascript
复制
npm install egg-mongoose -s

在插件里补上这么一句:

代码语言:javascript
复制
// plugin.jsmongoose : {  enable: true,  package: 'egg-mongoose',},
代码语言:javascript
复制
// config.default.js
  // 连接到mongodb  config.mongoose = {    url: 'mongodb://127.0.0.1:27017/egg_x',    options: {      // useMongoClient: true,      autoReconnect: true,      reconnectTries: Number.MAX_VALUE,      bufferMaxEntries: 0,    },  }

接下来就是定义MVC框架了。

首先一定要明白:model层定义数据表结构,service定义业务方法,controller负责业务处理

model
代码语言:javascript
复制
// model/user.jsmodule.exports = app => {  const mongoose = app.mongoose  const UserSchema = new mongoose.Schema({    mobile: { type: String, unique: true, required: true },    password: { type: String, required: true },    realName: { type: String, required: true },    avatar: { type: String, default:'https://1.gravatar.com/avatar/a3e54af3cb6e157e496ae430aed4f4a3?s=96&d=mm'},    extra: { type: mongoose.Schema.Types.Mixed },    createdAt: { type: Date, default: Date.now }})  return mongoose.model('User', UserSchema)}
service

先安装哈希依赖:

代码语言:javascript
复制
 npm install egg-bcrypt -s

在插件中定义:

代码语言:javascript
复制
    bcrypt : {    enable: true,    package: 'egg-bcrypt'  }

创建server层,直接调用mongoose的 create API。

代码语言:javascript
复制
// server/user.jsconst Service =require('egg').Service;
class UserService extends Service{
    /**     * 创建用户     * @param {*} payload     */    async create(payload){        const {ctx}=this;        payload.password=await ctx.genHash(payload.password);        return ctx.model.User.create(payload)    }
}
module.exports=UserService;
修改controller层
代码语言:javascript
复制
   /**    * @summary 创建用户    * @description 创建用户,记录用户账户/密码/类型    * @router post /api/user    * @request body createUserRequest *body    * @response 200 baseResponse 创建成功    */    async create() {        const { ctx, service } = this        // 校验参数        ctx.validate(ctx.rule.createUserRequest)        // 组装参数        const payload = ctx.request.body || {}        // 调用 Service 进行业务处理        const res = await service.user.create(payload)         // 设置响应内容和响应状态码        ctx.helper.success({ctx, res})    }

结果显示请求成功。

练习:实现增删改查整套接口(熟悉API调用)

基本思路:就是在service层定义方法,在controller层调用方法。

更新数据(findByIdAndUpdate)
代码语言:javascript
复制
 // service/user.js        /**     * 更新用户信息     * @param {*} id     * @param {*} values     */    async findByIdAndUpdate(id, values) {        return this.ctx.model.User.findByIdAndUpdate(id, values)    }
// controller/user.js    /**     * @summary 修改用户     * @description 获取用户信息     * @router put /api/user/     * @response 200 baseResponse 创建成功     * @ignore     */    async update() {        const { ctx, service } = this        // 校验参数        ctx.validate(ctx.rule.createUserRequest)        // 组装参数        const { id } = ctx.params        const payload = ctx.request.body || {}        // 调用 Service 进行业务处理        await service.user.update(id, payload)        // 设置响应内容和响应状态码        ctx.helper.success({ ctx })    }
删除
单个用户(findByIdAndRemove)
代码语言:javascript
复制
// service/user.js    /**     * 删除用户     * @param {*} _id     */    async destroy(_id) {        const { ctx, service } = this        const user = await ctx.service.user.find(_id)        if (!user) {            ctx.throw(404, 'user not found')        }        return ctx.model.User.findByIdAndRemove(_id)    }
// controller/user.js    /**     * @summary 删除单个用户     * @description 删除单个用户     * @router delete /api/user/{id}     * @request path string *id eg:1 用户ID     * @response 200 baseResponse 创建成功     */    async destroy() {        const { ctx, service } = this        // 校验参数        const { id } = ctx.params        // 调用 Service 进行业务处理        await service.user.destroy(id)        // 设置响应内容和响应状态码        ctx.helper.success({ ctx })    }
批量删除(removes(【id数组】))
代码语言:javascript
复制
// service/user.js    /**     * 删除多个用户     * @param {*} payload     */    async removes(payload) {        return this.ctx.model.User.remove({ _id: { $in: payload } })    }
// controller/user.js    /**     * @summary 删除所选用户     * @description 获取用户信息     * @router delete /api/user/{id}     * @request path string *id     * @response 200 baseResponse 创建成功     */    async removes() {        const { ctx, service } = this        // 组装参数        // const payload = ctx.queries.id        const { id } = ctx.request.body        const payload = id.split(',') || []        // 调用 Service 进行业务处理        const result = await service.user.removes(payload)        // 设置响应内容和响应状态码        ctx.helper.success({ ctx })    }
查询
查询一个(indById(_id))
代码语言:javascript
复制
// service/user.js    /**     * 查看单个用户     */    async show(_id) {        const user = await this.ctx.service.user.find(_id)        if (!user) {            this.ctx.throw(404, 'user not found')        }        return this.ctx.model.User.findById(_id).populate('role')    }
// controller/user.js    /**     * @summary 获取单个用户     * @description 获取用户信息     * @router get /api/user/{id}     * @request url baseRequest     * @response 200 baseResponse 创建成功     */    async show() {        const { ctx, service } = this        // 组装参数        const { id } = ctx.params        // 调用 Service 进行业务处理        const res = await service.user.show(id)        // 设置响应内容和响应状态码        ctx.helper.success({ ctx, res })    }
列表筛选及模糊查询(带分页)

列表筛查的条件就比较多了。

代码语言:javascript
复制
// service/user.js/**   * 查看用户列表   * @param {*} payload   */    async index(payload) {        const { currentPage, pageSize, search } = payload        let res = []        let count = 0        let skip = ((Number(currentPage)) - 1) * Number(pageSize || 10)
        if (search) {            res = await this.ctx.model.User.find({ mobile: { $regex: search } }).populate('role').skip(skip).limit(Number(pageSize)).sort({ createdAt: -1 }).exec()            count = res.length        } else {            res = await this.ctx.model.User.find({}).populate('role').skip(skip).limit(Number(pageSize)).sort({ createdAt: -1 }).exec()            count = await this.ctx.model.User.count({}).exec()        }
        // 整理数据源 -> Ant Design Pro        let data = res.map((e, i) => {            const jsonObject = Object.assign({}, e._doc)            jsonObject.key = i            jsonObject.password = 'Are you ok?'            jsonObject.createdAt = this.ctx.helper.formatTime(e.createdAt)            return jsonObject        })
        return { count: count, list: data, pageSize: Number(pageSize), currentPage: Number(currentPage) }    }
// controller/user.js    /**     * @summary 获取所有用户(分页/模糊)     * @description 获取用户信息     * @router get /api/user     * @request query integer *currentPage eg:1 当前页     * @request query integer *pageSize eg:10 单页数量     * @request query string search eg: 搜索字符串     * @request query boolean isPaging eg:true 是否需要翻页     * @response 200 baseResponse 创建成功     */    async index() {        const { ctx, service } = this        // 组装参数        const payload = ctx.query        // 调用 Service 进行业务处理        const res = await service.user.index(payload)        // 设置响应内容和响应状态码        ctx.helper.success({ ctx, res })    }

通过生命周期执行初始化

https://eggjs.org/zh-cn/basics/app-start.html

我们常常需要在应用启动期间进行一些初始化工作,等初始化完成后应用才可以启动成功,并开始对外提供服务。 框架提供了统一的入口文件( app.js)进行启动过程自定义,这个文件返回一个 Boot 类,我们可以通过定义 Boot 类中的生命周期方法来执行启动应用过程中的初始化工作。 框架提供了这些 生命周期函数供开发人员处理:

  • 配置文件即将加载,这是最后动态修改配置的时机( configWillLoad
  • 配置文件加载完成( configDidLoad
  • 文件加载完成( didLoad
  • 插件启动完毕( willReady
  • worker 准备就绪( didReady
  • 应用启动完成( serverDidReady
  • 应用即将关闭( beforeClose

这七个全局相关的api,必须在根目录下app.js进行定义。

代码语言:javascript
复制
//app.js/*** 全局定义* @param app*/// app.jsclass AppBootHook {  constructor(app) {    this.app = app;  }
  configWillLoad() {    // 此时 config 文件已经被读取并合并,但是还并未生效    // 这是应用层修改配置的最后时机    // 注意:此函数只支持同步调用
    // 例如:参数中的密码是加密的,在此处进行解密    // this.app.config.mysql.password = decrypt(this.app.config.mysql.password);    // 例如:插入一个中间件到框架的 coreMiddleware 之间    // const statusIdx = this.app.config.coreMiddleware.indexOf('status');    // this.app.config.coreMiddleware.splice(statusIdx + 1, 0, 'limit');  }
  async didLoad() {    // 所有的配置已经加载完毕    // 可以用来加载应用自定义的文件,启动自定义的服务
    // 例如:创建自定义应用的示例    // this.app.queue = new Queue(this.app.config.queue);    // await this.app.queue.init();
    // 例如:加载自定义的目录    // this.app.loader.loadToContext(path.join(__dirname, 'app/tasks'), 'tasks', {    //  fieldClass: 'tasksClasses',    // });  }
  async willReady() {    // 所有的插件都已启动完毕,但是应用整体还未 ready    // 可以做一些数据初始化等操作,这些操作成功才会启动应用
    // 例如:从数据库加载数据到内存缓存    // this.app.cacheData = await this.app.model.query(QUERY_CACHE_SQL);  }
  async didReady() {    // 应用已经启动完毕    console.log('========Init Data=========')    // 创建匿名上下文    const ctx = await this.app.createAnonymousContext();    await ctx.model.User.remove(); //清理    // 新增    await ctx.service.user.create({        mobile: '13800138000',      password: '123456',       realName: 'dangjingtao',    })  }
  async serverDidReady() {    // http / https server 已启动,开始接受外部请求    // 此时可以从 app.server 拿到 server 的实例
    // this.app.server.on('timeout', socket => {       // handle socket timeout    // });  }}
module.exports = AppBootHook;
本文参与 腾讯云自媒体分享计划,分享自微信公众号。
原始发表:2019-07-22,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 一Li小麦 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体分享计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 初步
    • 安装
    • 构建swagger-doc:
      • 安装
        • 使用
        • 数据格式规范化
          • 异常处理
            • helper方法实现统一响应格式
            • 数据校验
            • 连接文档型数据库(mongodb)
              • model
                • service
                  • 修改controller层
                    • 练习:实现增删改查整套接口(熟悉API调用)
                      • 更新数据(findByIdAndUpdate)
                      • 删除
                      • 查询
                  • 通过生命周期执行初始化
                  相关产品与服务
                  消息队列 TDMQ
                  消息队列 TDMQ (Tencent Distributed Message Queue)是腾讯基于 Apache Pulsar 自研的一个云原生消息中间件系列,其中包含兼容Pulsar、RabbitMQ、RocketMQ 等协议的消息队列子产品,得益于其底层计算与存储分离的架构,TDMQ 具备良好的弹性伸缩以及故障恢复能力。
                  领券
                  问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档