阅读 771

使用Egg框架进行server端开发

一、server端开发必备知识

针对前端开发者而言,学习node开发的难点,其实不在于学习一个框架,也不在于学习一门语言,而是在于无法从server端开发的全局,系统性的把所有东西串联起来。

node-server开发到底需要哪些知识储备呢?

总的来说,就是语言 + 框架 + mysql + ORM框架。学习成本其实主要是语言+框架。

学习成本

语言是基础,能跑demo的角度上来说,少则一两周,多则一两月;框架跑起来,能写demo,也应该是一两周的时间,mysql用一两天知道最基本的知识就行,ORM框架也用一两天搞定。(这里的时间是最小化的链路跑通时间,个人主张在链路跑通的情况下,在场景中学习具体的东西)

语法+框架

服务端开发,至少要学习一门语言,比如node、Java、go等等;学习完这门语言之后,需要学习这门语言对应的框架,比如针对node的egg框架,比如针对Java的spring框架,语言学习的是语法,框架是用一种更高效的方式用这种语法来完成某项功能;

数据库

学完框架,还需要了解数据库的知识,因为对于后端同学来说,需要和数据打交道,这是我们进行数据存储的地方,我们通过接口,对前端提供的是数据的curd能力,选型不用什么特殊的,就用mysql,无论大小厂,基本都用它,从学习成本上说,我们花一两天时间,学会建库和建表,以及简单的查询所有数据就行,具体的细节等具体业务场景再去学习。

数据库连接

学完数据库,还有一个东西需要学习,就是数据库连接,java的数据库连接,底层提供了jdbc的方案,node提供了mysql包进行数据库的连接,它相当于开发语言和数据库之间的桥梁,遵循一般的语法规则,就能上手业务开发。但是呢,这种方式在使用上不友好,因为前后端是通过对象的方式进行通信,而数据库字段和对象的映射关系我们就需要维护好,很费时费力。这个时候就提出了ORM框架。

ORM

ORM框架解决的问题就是把对象和数据库映射的复杂内部关系,框架来解决,我们仅仅进行简单的使用就好。java的ORM框架就是mybatis、而egg也提供了这样的ORM框架egg-sequelize。

二、技术层面

有了上面的整体的server端开发思维,我们再看egg,就很明确他的定位了。

\

egg就是基于node的一个企业级框架,它提供了开发node服务端的一整套的能力。下面的这张图,左边部分是框架涉及到的一些具体的实体对象,右边是egg框架的业务分层,是一种概念型的东西。具体可以结合egg官方文档通读一遍。

\

1、egg是一种node方式的MVC框架

它由这么几个步骤组成,路由的定义,指向向一个controller;controller为控制器,主要进行参数获取、数据返回处理;service进行处理具体的业务逻辑,比如数据库数据获取等等;

  • 路由定义:app/router.js
module.exports = app => {
  const { router, controller } = app;

  router.get('/', controller.home.index);// 表示在匹配到'/',会定向到controller/home.js里面,这里面的class必然有一个index的方法。通过这个方法,决定返回的结果是什么
};
复制代码
  • 控制器controller
'use strict';

const Controller = require('egg').Controller;
class HomeController extends Controller {
  async index() {
    // 通常,在一个方法里面会进行参数获取,this.ctx.request.query(get方式)、this.ctx.request.body(post方式)、this.ctx.request.files(获取文件)
    const { ctx } = this;
    ctx.body = 'hi, egg';
    // 通常,this.ctx.body,就是服务端向前端返回的字段。
  }
}

module.exports = HomeController;
复制代码
  • service处理业务逻辑
'use strict'

const Service = require('egg').Service;
// service层就是具体的业务逻辑的处理。一般而言,通常进行数据库的数据查询、逻辑判断等等。

// 该Service的register的逻辑就是,从数据库查询是否存在当前用户,不存在就创建用户,存在就返回‘账号已存在的’异常
class User extends Service {
    async register(user) {
        const hasUser = await this.ctx.model.User.findOne({
            where: {
                username: user.username
            }
        });

        if (!hasUser) {
            const userInfo = await this.ctx.model.User.create(user);
            if (userInfo) {
                return this.ctx.response.ServerResponse.successRes(true)
            }
            return this.ctx.response.ServerResponse.errorRes('用户注册异常', true)
        }

        return this.ctx.response.ServerResponse.errorRes('账号已存在', true)
    }
}
复制代码

2、获取请求参数

  • get方式获取请求参数:this.ctx.request.query
  • post方式获取请求参数:this.ctx.request.body
  • file文件的获取:this.ctx.request.files

3、能力拓展-extends

helper,进行工具包扩展。

前端开发中,针对公用函数,一般会在一个utils公用工具文件中。

而在egg中,一般使用helper,进行工具的扩展。

// app/extend/helper.js
module.exports = {
  formatUser(user) {
    return only(user, [ 'name', 'phone' ]);
  }
};
复制代码

在使用的时候:this.ctx.helper.formatUser(obj)。

response,针对数据请求的返回response进行扩展

一般而言,在这个部分,我会针对数据的返回格式,进行限制。

'use strict'
// code码定义
const SUCCESS = 0;
const ERROR = 1;

class MyResponseData {
    constructor(status, msg, data) {
        this.msg = msg;
        this.data = data;
        this.status = status;
    }

    static successRes(data) {
        return new MyResponseData(SUCCESS, 'success', data);
    }

    static errorRes(msg, data) {
        return new MyResponseData(ERROR, msg, data);
    }
}

// 这个事例最终返回的数据格式如下:interface ServerResponse {msg: string, status: number, data: any}

module.exports = {
    ServerResponse: MyResponseData
}
复制代码

其他的extends,还包括 application、request、context等。

4、数据库部分

一方面,这是前端本身技术的壁垒,没了解过后端开发的前端本身就对数据库不了解。

另一方面,通过egg官网,跑出来的demo,并不能直接连接到数据库,仅仅是一个运行示例。很多人在这里就断了。

想要这一部分跑通,在思维上一定要有这么一些概念。

1、一般而言,我们必须在自己的本机上安装了mysql,然后得记住安装的时候的账号和密码,这样我们的本地项目才有可能连接到我们的数据库。所谓的数据库,就是运行在机器上的数据存储系统,这是数据库厂商提供的工具。

2、线上环境,我们的数据库一般会由一个单独的服务器来提供。专业名词叫RDS。正常情况下,我们只需要关注数据库的账号和密码就行。

当你的本地安装了mysql,可以直接在egg中进行配置。

config.sequelize = {
    dialect: 'mysql', // support: mysql, mariadb, postgres, mssql
    database: 'template-test',// 数据库名
    host: '127.0.0.1',
    port: 3306,
    username: 'root',
    password: '*******',// 自己的密码
    delegate: 'model',
    baseDir: 'model',
    define: {
      // raw: true,
      underscored: true,
      freezeTableName: true, //直接查找设置的表名,默认是表名加s或者es
      timestamps: false,
      createdAt: "CreatedAt",  //自定义时间戳
      updatedAt: "UpdatedAt", // 自定义时间戳
      timezone: '+08:00' // 保存为本地时区
    }
  };
复制代码

\

5、如何通过node对数据库进行操作

这一部分,和我们选择的ORM框架有关系

在我们团队内部的ORM框架中,我们选择了egg-sequelize。

每一种ORM都有它自己的一些语法规则,通过相关文档直接操作即可。

这是egg-sequelize中,个人定义了一个简单的user的model。egg-sequelize通过定义的model,进行数据库的所有的数据的CURD。

// app/model/user.js
const crypto =require('crypto');

module.exports = app => {
    const {STRING, INTEGER, DATE} = app.Sequelize;

    const User = app.model.define('user', {
        id: {
            type: INTEGER,
            primaryKey: true,
            autoIncrement: true
        },
        username: STRING(30),
        password: {
            type: STRING(100),
            set: function(password) {
                // let pas = crypto.createHash('md5').update(password).digest('hex');
                let pas = crypto.createHash('md5').update(password).digest('hex');
                console.log('pas', pas)
                this.setDataValue("password", pas);
            },
            get: function() {
                return this.getDataValue('password');
            }
        },
        created_at: DATE,
        updated_at: DATE,
    })

    User.prototype.validPassword = function (psd) {
        console.log('psd', psd);
        console.log('this.password', this.password);
        let md5 = crypto.createHash('md5').update(psd).digest('hex')
        console.log('md5', md5);
        return this.password === crypto.createHash('md5').update(psd).digest('hex');
    }
    return User
}
复制代码

数据查询-findOne

const user = this.ctx.model.User.findOne({
  where: {
    username: user.username
  }
});
复制代码

数据插入-create

const userInfo = await this.ctx.model.User.create(user);
复制代码

\

数据分页查询-findAndCountAll

const userList = await this.ctx.model.User.findAndCountAll(query);
复制代码

通过id查询-findByPk

const user = await this.ctx.model.User.findByPk(id);
复制代码

数据更新-update

const user = await this.ctx.model.User.update(data,{where: {id}});
复制代码

三、业务层面

从开发的角度上来说,我们在具备了基础的技术方面的能力,我们发现作为前端,开发服务端还是会有问题,这些问题可能是经验上的,也有可能是一些常规性的,但是我们前端又比较欠缺的。

比如,我们做一个登陆,密码必须要进行密文存储,如何加密?常规的加密方式有哪些?所以我这边也会针对这些问题,总结我们开发需要了解的一些细节。

1、crypto

node提供的一个包,进行算法加密(md5、sha1等等),作用就是密码的密文存储。

为什么不进行明文存储?防止数据库数据泄露,用户的账号被窃取。

\

2、egg-sequelize

关于egg框架的一个ORM框架,进行数据的CURD。

文档:eggjs.org/zh-cn/tutor…

3、jwt

jwt是一种常用的用户验证的解决方案,这里不进行细讲。

jsonwebtoken,一个node包,用于登陆验证。核心有两个api。

// 这个是签名,生成的token,是要返回给前端的。后续的接口都会走校验,需要在header中,拿到这个token。
token = jwt.sign(data, key)

// 校验
jwt.verify(token, key)
复制代码

4、egg-validate

进行参数校验的工具

代码示例

const query = this.ctx.request.body;
const params = {
  username: query.username,
  password: query.password
};
try {
  this.ctx.validate({
    username: 'string',
    password: 'string'
  }, params);
} catch(e) {
  this.ctx.body = e;
  return
}
复制代码

文档:www.npmjs.com/package/egg…

5、egg-multipart

文件上传的插件

文档:github.com/eggjs/egg-m…

6、多环境配置

一般来说,会分为三个环境:local、dev、prod

egg启动的时候,默认为local环境。如果想要设置环境,可以在命令行中设置。EGG_SERVER_ENV是对运行环境进行指定的命令行参数。

"scripts": {
  "start": "egg-scripts start --daemon --title=egg-server-template-node",// prod环境
  "stop": "egg-scripts stop --title=egg-server-template-node",
  "dev": "egg-bin dev",// local环境
  "dev:dev": "EGG_SERVER_ENV=dev npm run dev",// dev环境
  "debug": "egg-bin debug",
  "test": "npm run lint -- --fix && npm run test-local",
  "test-local": "egg-bin test",
  "cov": "egg-bin cov",
  "lint": "eslint .",
  "ci": "npm run lint && npm run cov",
  "autod": "autod"
},
复制代码

不同环境的config,需要进行配置。

  • config.default.js,默认环境local。
  • config.dev.js,dev环境下会使用该配置,会和default合并。
  • config.prod.js,prod的配置。

这一块不同的配置,其实大多数情况下,主要是数据库的不同配置。

总的来说,对于新手,egg官方文档不能完全解决大家对于node server端开发的疑惑,本篇文章更多的是对于egg官方文档的一个补充,在整个大的思维框架下,帮助新手上手server端开发。

以上,关于egg的介绍就基本完毕。

文章分类
后端