zoukankan      html  css  js  c++  java
  • 使用egg.js开发后端API接口系统

    什么是Egg.js

    Egg.js 为企业级框架和应用而生,我们希望由 Egg.js 孕育出更多上层框架,帮助开发团队和开发人员降低开发和维护成本。详细的了解可以参考Egg.js的官网:https://eggjs.org/zh-cn/intro/ 

    Egg.js 奉行『约定优于配置』,按照一套统一的约定进行应用开发,Egg 有很高的扩展性,可以按照团队的约定定制框架,团队内部采用这种方式可以减少开发人员的学习成本。

    可以理解Egg.js是一个Node框架,同时它也是基于Koa框架基础上的框架,我们大概了解一下它的前身和主要特点即可。

    它的特点有:

    本篇随笔不是细说Egg.js 的详细内容,毕竟官网介绍还是比较清晰的,我们主要说使用它来做一个后端的API接口系统,后端肯定需要对数据库进行各种操作,用一个JS的方式来访问数据库,利用egg-sequelize插件,创建和数据库表进行绑定的模型进行操作,还是比较新鲜的,用了会发现确实很方便。用Egg.js来开发后端系统,相当于用前端的语言、做法,来开发后端系统了(虽然Egg.js 也可以用来做前端)。

    我们知道,常规的Asp.net或者WebAPI 应用里面,一般有MVC,模型、视图、控制器这些对象,Egg.js 里面也有类似的概念,我们这里没有用用来做前端,那么可以不用它的视图(Egg.js 视图就是一个带变量的模板文件);
    控制器就是我们这里用到需要为前端提供API入口和返回JSON的地方,类似我们Web API里面的控制器概念;模型这里可以理解为对数据库对象的封装对象吧;另外和我们常规前端开发一样(类似Vue+Element系统),获取数据的操作逻辑,我们可以封装在Service层,这样可以降低我们控制器里面的逻辑代码,同时也方便重用逻辑处理函数。MVC+Service的关系,大概如下所示。
     

    2、 使用egg.js开发后端API接口系统所需插件

    我依照官网的简单案例进行快速初始化,如下所示。

    我们推荐直接使用脚手架,只需几条简单指令,即可快速生成项目(npm >=6.1.0):

    $ mkdir egg-example && cd egg-example
    $ npm init egg --type=simple
    $ npm i

    启动项目:

    $ npm run dev

    其实我们还需要一些额外的插件来跑起来,我的包依赖文件如下所示。

    package.json

    {
      "name": "example",
      "version": "1.0.0",
      "description": "## Development",
      "dependencies": {
        "egg": "^2.10.0",
        "egg-cors": "^2.2.3",
        "egg-jwt": "^3.1.7",
        "egg-mysql": "^3.0.0",
        "egg-redis": "^2.4.0",
        "egg-scripts": "^2.5.0",
        "egg-sequelize": "^4.0.2",
        "egg-view-nunjucks": "^2.3.0",
        "moment": "^2.29.1",
        "mysql2": "^2.2.5",
        "node": "^15.10.0"
      },
      "devDependencies": {
        "autod": "^3.0.1",
        "autod-egg": "^1.0.0",
        "egg-bin": "^4.15.0",
        "egg-mock": "^3.19.2",
        "eslint": "^4.18.1",
        "eslint-config-egg": "^7.0.0",
        "factory-girl": "^5.0.2",
        "sequelize-cli": "^4.0.0"
      },

    我们来看看红色部分的内容,其中

    egg 是本身的框架需要的插件,这个是整个框架的核心基础;egg-scripts 这是部署eggjs项目的工具;

    egg-corss 是跨域处理所需要的,用于设置csrf的配置等;

    egg-jwt 是用于对用户身份认证的处理插件;

    egg-mysql + Mysql2 是我们做Mysql数据库处理说需要的插件;

    egg-redis 是我们用到redis操作,所需要的插件,可选。

    egg-sequelize 是我们操作数据库的一个插件,提供很多方便的接口进行处理,可以搭配Mysql或者PostgreSQL、MS SQLServer数据库插件进行处理的

    egg-view-nunjucks 是展示视图模板的一个插件。

    moment 是一个日期处理插件,可以处理各种日期格式、转换的一个插件库。

    大概就是这些,如果需要结合前端JS的处理插件,可以引入更多的内容,不过我们这里主要介绍后端访问Mysql数据库的处理操作,提供JSON数据接口的,基本上这些也够了。
     
    另外,我们需要知道egg.js的目录很多是约定位置的,因此我们需要知道常规的几个文件夹的意义。我们简单了解下目录约定规范。
    egg-project
    ├── package.json
    ├── app.js (可选)
    ├── agent.js (可选)
    ├── app
    |   ├── router.js
    │   ├── controller
    │   |   └── home.js
    │   ├── service (可选)
    │   |   └── user.js
    │   ├── middleware (可选)
    │   |   └── response_time.js
    │   ├── view (可选)
    │   |   └── home.tpl
    │   └── extend (可选)
    │       ├── helper.js (可选)
    ├── config
    |   ├── plugin.js
    |   ├── config.default.js
    │   ├── config.prod.js

    我们这里大概知道以上文件夹和文件的意思即可。

    • app/router.js 用于配置 URL 路由规则,具体参见 Router
    • app/controller/** 用于解析用户的输入,处理后返回相应的结果,具体参见 Controller
    • app/service/** 用于编写业务逻辑层,可选,建议使用,具体参见 Service
    • app/middleware/** 用于编写中间件,可选,具体参见 Middleware
    • app/extend/** 用于框架的扩展,可选,具体参见框架扩展
    • config/config.{env}.js 用于编写配置文件,具体参见配置
    • config/plugin.js 用于配置需要加载的插件,具体参见插件
     
    1)插件的配置
    我们引入的插件模块,需要在app/plugin.js里面启用,如下代码所示。
    app/plugin.js
    'use strict';
    
    exports.sequelize = {
      enable: true,
      package: 'egg-sequelize',
    };
    exports.mysql = {
      enable: true,
      package: 'egg-mysql',
    };
    
    exports.nunjucks = {
      enable: true,
      package: 'egg-view-nunjucks'
    };
    exports.redis = {
      enable: true,
      package: 'egg-redis',
    };
    exports.jwt = {
      enable: true,
      package: 'egg-jwt',
    };
    
    exports.cors = {
      enable: true,
      package: 'egg-cors',
    };

    为了访问Mysql数据库,我们还需要在config/config.default.js文件中配置好对应的关系。

    config/config.default.js

    'use strict';
    
    module.exports = appInfo => {
      const config = exports = {};
    
      // use for cookie sign key, should change to your own and keep security
      config.keys = appInfo.name + '_{{keys}}';
    
      config.jwt = {
        secret: '123456', //自定义token的加密条件字符串,可按各自的需求填写
      };
    
      // Mysql
      config.sequelize = {
        dialect: 'mysql',
        host: 'localhost',
        port: 3306,
        database: 'myprojectdb',
        username: 'root',
        password: '123456',
        define: {
          //freezeTableName默认值为false,会自动在表名后加s
          freezeTableName: true,
          // timestamps默认值为true,会自动添加create_time和update_time
          timestamps: false
        }
      };
    
      // csrf 安全配置
      config.security = {
        csrf: {
          enable: false,
          ignoreJSON: true
        },
        // 允许访问接口的白名单
        domainWhiteList: ['*'] // ['http://localhost:8080']
      };
      config.cors = {
        origin: '*',
        allowMethods: 'GET, HEAD, PUT, POST, DELETE, PATCH'
      };
    
      //........其他配置...............
    
      return config;
    };

    为了给前端提供Web API接口,我们需要为不同的业务对象提供路由入口,路由定义,统一在app/route.js文件中定义。

    app/route.js

     module.exports = app => {
        const { router, controller, jwt } = app; 
    
        router.get('/', controller.home.index);
        router.get('/news', controller.news.list);
        router.post('/login', controller.users.login);  //登录并生成Token
        router.resources('users', '/users', controller.users);
      };

    以上我们users 是RESTful 的方式来定义路由, 我们提供了 app.router.resources('routerName', 'pathMatch', controller) 快速在一个路径上生成 CRUD 路由结构。

    类似RESTful定义

     router.resources('posts', '/api/posts', controller.posts);

    我们只需要在 posts.js 里面实现对应的函数就可以了。

     我这里的users实现了上面部分的接口,以提供列表展示-L、创建-C、获取-R、更新-U、删除-D等操作。

    appcontrollerusers.js

    'use strict';
    
     const Controller = require('egg').Controller;
    
    //控制器类入口
    //实现路由几个常规函数,包括列表及CRUD的操作
    class UserController extends Controller {
    
      async index() { //展示列表数据-L
        const ctx = this.ctx;
        const query = {
          limit: ctx.helper.parseInt(ctx.query.limit),
          offset: ctx.helper.parseInt(ctx.query.offset),
        };
    
        var data = await ctx.service.user.list(query);
        var json = ctx.helper.json(data)
        ctx.body = json
      }
    
      async show() { //显示某记录具体的数据-R
        const ctx = this.ctx;
        ctx.body = await ctx.service.user.find(ctx.helper.parseInt(ctx.params.id));
      }
    
      async create() { //新增一个记录-C
        const ctx = this.ctx;
        const user = await ctx.service.user.create(ctx.request.body);
        ctx.status = 201;
        ctx.body = user;
      }
    
      async update() { //更新指定的记录-U
        const ctx = this.ctx;
        const id = ctx.helper.parseInt(ctx.params.id);
        const body = ctx.request.body;
        ctx.body = await ctx.service.user.update({
          id,
          updates: body
        });
      }
    
      async destroy() { //删除指定的记录-D
        const ctx = this.ctx;
        const id = ctx.helper.parseInt(ctx.params.id);
        await ctx.service.user.del(id);
        ctx.status = 200;
      }
    }
    
    module.exports = UserController;

    这里UserController 控制器没有直接访问数据库,而是间接通过service对象进行操作数据库的。service中的user.js代码如下所示。

    appserviceuser.js

    'use strict';
    
    const Service = require('egg').Service;
    
    //服务类入口,用于封装具体的数据库访问
    class User extends Service {
    
      async login(usernameOrEmail, password) {
        var user = await this.ctx.model.User.findOne({ 
          where: {
              $or: [
                { username: usernameOrEmail },
                { emailaddress: usernameOrEmail }
              ]
          }
        });
        
        var success = false;
        var error = "";
        if(user) {
          success = true
        }
    
        return {
          success,
          error
        }
      }
      
      async list({ offset = 0, limit = 10 }) {
        return this.ctx.model.User.findAndCountAll({
          offset,
          limit,
          order: [[ 'creationtime', 'desc' ], [ 'id', 'desc' ]],
        });
      }
    
      async find(id) {
        const user = await this.ctx.model.User.findByPk(id);
        if (!user) {
          this.ctx.throw(404, 'user not found');
        }
        return user;
      }
    
      async create(user) {
        return this.ctx.model.User.create(user);
      }
    
      async update({ id, updates }) {
        const user = await this.ctx.model.User.findByPk(id);
        if (!user) {
          this.ctx.throw(404, 'user not found');
        }
        return user.update(updates);
      }
    
      async del(id) {
        const user = await this.ctx.model.User.findByPk(id);
        if (!user) {
          this.ctx.throw(404, 'user not found');
        }
        return user.destroy();
      }
    }
    
    module.exports = User;

    而Service中,访问数据库主要通过 egg-sequelize 插件中提供的 this.ctx.model.User 对象进行操作数据库的

    sequelize 是一个广泛使用的 ORM 框架,它支持 MySQL、PostgreSQL、SQLite 和 MSSQL 等多个数据源。

    appmodeluser.js

    'use strict';
    
    module.exports = app => {
      const { STRING, INTEGER, DATE } = app.Sequelize;
    
      const User = app.model.define('abpusers', {
        id: { type: INTEGER, primaryKey: true, autoIncrement: true },
        name: STRING(64),
        username: STRING(64),  
        phonenumber: STRING(64),  
        creationtime: DATE,
        lastmodificationtime: DATE,
      });
    
      return User;
    };

    sequelize 定义了数据库不同的类型,它的类型定义如下所示。

    Sequelize.STRING                      // VARCHAR(255)
    Sequelize.STRING(1234)                // VARCHAR(1234)
    Sequelize.STRING.BINARY               // VARCHAR BINARY
    Sequelize.TEXT                        // TEXT
    Sequelize.TEXT('tiny')                // TINYTEXT
    Sequelize.CITEXT                      // CITEXT      PostgreSQL and SQLite only.
    
    Sequelize.INTEGER                     // INTEGER
    Sequelize.BIGINT                      // BIGINT
    Sequelize.BIGINT(11)                  // BIGINT(11)
    
    Sequelize.FLOAT                       // FLOAT
    Sequelize.FLOAT(11)                   // FLOAT(11)
    Sequelize.FLOAT(11, 10)               // FLOAT(11,10)
    
    Sequelize.REAL                        // REAL        PostgreSQL only.
    Sequelize.REAL(11)                    // REAL(11)    PostgreSQL only.
    Sequelize.REAL(11, 12)                // REAL(11,12) PostgreSQL only.
    
    Sequelize.DOUBLE                      // DOUBLE
    Sequelize.DOUBLE(11)                  // DOUBLE(11)
    Sequelize.DOUBLE(11, 10)              // DOUBLE(11,10)
    
    Sequelize.DECIMAL                     // DECIMAL
    Sequelize.DECIMAL(10, 2)              // DECIMAL(10,2)
    
    Sequelize.DATE                        // DATETIME for mysql / sqlite, TIMESTAMP WITH TIME ZONE for postgres
    Sequelize.DATE(6)                     // DATETIME(6) for mysql 5.6.4+. Fractional seconds support with up to 6 digits of precision
    Sequelize.DATEONLY                    // DATE without time.
    Sequelize.BOOLEAN                     // TINYINT(1)
    
    Sequelize.ENUM('value 1', 'value 2')  // An ENUM with allowed values 'value 1' and 'value 2'
    Sequelize.ARRAY(Sequelize.TEXT)       // Defines an array. PostgreSQL only.
    Sequelize.ARRAY(Sequelize.ENUM)       // Defines an array of ENUM. PostgreSQL only.
    
    Sequelize.JSON                        // JSON column. PostgreSQL, SQLite and MySQL only.
    Sequelize.JSONB                       // JSONB column. PostgreSQL only.
    
    Sequelize.BLOB                        // BLOB (bytea for PostgreSQL)
    Sequelize.BLOB('tiny')                // TINYBLOB (bytea for PostgreSQL. Other options are medium and long)
    
    Sequelize.UUID                        // UUID datatype for PostgreSQL and SQLite, CHAR(36) BINARY for MySQL (use defaultValue: Sequelize.UUIDV1 or Sequelize.UUIDV4 to make sequelize generate the ids automatically)
    
    Sequelize.CIDR                        // CIDR datatype for PostgreSQL
    Sequelize.INET                        // INET datatype for PostgreSQL
    Sequelize.MACADDR                     // MACADDR datatype for PostgreSQL
    
    Sequelize.RANGE(Sequelize.INTEGER)    // Defines int4range range. PostgreSQL only.
    Sequelize.RANGE(Sequelize.BIGINT)     // Defined int8range range. PostgreSQL only.
    Sequelize.RANGE(Sequelize.DATE)       // Defines tstzrange range. PostgreSQL only.
    Sequelize.RANGE(Sequelize.DATEONLY)   // Defines daterange range. PostgreSQL only.
    Sequelize.RANGE(Sequelize.DECIMAL)    // Defines numrange range. PostgreSQL only.
    
    Sequelize.ARRAY(Sequelize.RANGE(Sequelize.DATE)) // Defines array of tstzrange ranges. PostgreSQL only.
    
    Sequelize.GEOMETRY                    // Spatial column.  PostgreSQL (with PostGIS) or MySQL only.
    Sequelize.GEOMETRY('POINT')           // Spatial column with geometry type. PostgreSQL (with PostGIS) or MySQL only.
    Sequelize.GEOMETRY('POINT', 4326)     // Spatial column with geometry type and SRID.  PostgreSQL (with PostGIS) or MySQL only.

    关于它的接口,可以参考下文档https://itbilu.com/nodejs/npm/sequelize-docs-v5.html 了解下。

    另外,我们可以在appextendhelper.js中定义一些常规的辅助函数,方便在控制器或者service对象中使用。

    appextendhelper.js

    'use strict';
    const moment = require('moment');
    
    module.exports = {
      json(data, code, msg, addition) {
        return Object.assign({
          result: code ? 'fail' : 'success',
          code: code || 0,
          message: msg,
          data,
        }, addition);
      },
      parseInt(string) {
        if (typeof string === 'number') return string;
        if (!string) return string;
        return parseInt(string) || 0;
      },
    
      changeTime(time) {
        return moment(time * 1000).format('YYYY-MM-DD HH:mm:ss');
      },
      relativeTime(time) {
        return moment(new Date(time * 1000)).fromNow()
      },

    最后,我们使用npm run dev跑项目

    测试下我们用户列表部分的处理。

     其他CRUD接口,可以结合C#代码进行客户端的测试,也可以在一个新建的Vue+Element前端项目中进行axios的调用,获取对应的JSON进行测试。

    在使用egg.js开发的时候,总体还是很方便,不过就是有时候一些拼写错误,或者一些配置原因,控制台 提示信息不是很明确,需要自己掌握各种排错的经验才行。

    主要研究技术:代码生成工具、会员管理系统、客户关系管理软件、病人资料管理软件、Visio二次开发、酒店管理系统、仓库管理系统等共享软件开发
    专注于Winform开发框架/混合式开发框架Web开发框架Bootstrap开发框架微信门户开发框架的研究及应用
      转载请注明出处:
    撰写人:伍华聪  http://www.iqidi.com 
        
  • 相关阅读:
    CSS样式权值
    JS正则表达式总结
    call, apply, bind作用
    JSON和JSONP区别
    重重保护下的堆
    [转载]舌尖上的清华 I
    [转载]Windows Phone学生开发者注册教程2月版
    忙碌的生活没有空写博客
    Qt应用之手机截图
    [转]我是设计院的
  • 原文地址:https://www.cnblogs.com/wuhuacong/p/14504091.html
Copyright © 2011-2022 走看看