使用eggjs写一个简易登录接口

familyAboveAll
eggjs快速初始化
$ mkdir egg-example && cd egg-example
$ npm init egg --type=simple
$ npm i

初始化后的目录结构如下:

egg-example
├── app
│   ├── controller
│   │   └── home.js
│   ├── service
│   │   └── user.js
│   ├── model
│   │   └── user.js
│   ├── view
│   │   └── login.html
│   └── router.js
├── config
│   └── config.default.js
└── package.json

业务需求在app里面开发,config里面主要是一些配置信息

启动项目:

$ npm run dev  
$ open http://localhost:7001

任务启动后我们先来理解一下Router和Control

Router

主要用来描述请求 URL 和具体承担执行动作的 Controller 的对应关系

// app/router.js
module.exports = app => {
  const { router, controller } = app;
  router.get('/user/:id', controller.user.info);
};

框架约定了app/router.js文件用于统一所有路由规则。

我们通过 Router 将用户的请求基于 method 和 URL 分发到了对应的 Controller 上,那 Controller 负责做什么?

Controller

负责解析用户的输入,处理后返回相应的结果

  • RESTful接口中,Controller 接受用户的参数,从数据库中查找内容返回给用户或者将用户的请求更新到数据库中。
  • 在 HTML 页面请求中,Controller 根据用户访问不同的 URL,渲染不同的模板得到 HTML 返回给用户。
  • 在代理服务器中,Controller 将用户的请求转发到其他服务器上,并将其他服务器的处理结果返回给用户。

框架推荐 Controller 层主要对用户的请求参数进行处理(校验、转换),然后调用对应的service方法处理业务,得到业务结果后封装并返回:

  1. 获取用户通过 HTTP 传递过来的请求参数。
  2. 校验、组装参数。
  3. 调用 Service 进行业务处理,必要时处理转换 Service 的返回结果,让它适应用户的需求。
  4. 通过 HTTP 将结果响应给用户。

eggjs官方文档写的十分清楚,大家可以多看看文档。

接口搭建

上面我们简单了解了Router和Controller,接下来就开始写代码,首先我们先来梳理一遍功能,前端页面发送一个ajax请求,参数为账号和密码,后台接收到这个请求后先到数据库里查这个账号,如果没有就返回没找到这个账号;如果有再和数据库里的密码比对,当比对结果一样时才算登录成功。
首先在 app/view 里面创建一个login.html作为登录页面,并且写上ajax请求

<div class="login-page">
  <form id='loginForm'>
    <div>账号</label><input type="text" name="name"></div>
    <div>密码</label><input type="password" name="password"></div>
    <button type="submit">登录</button>
  </form>
</div>
<script src="https://cdn.bootcss.com/jquery/3.4.1/jquery.min.js"></script>
<script>
  $(function () {
    $('#loginForm').submit((e) => {
      e.preventDefault()
      const name = $('input[name=name]').val()
      const password = $('input[name=password]').val()
      if (!name || !password) alert('账号密码不能为空')
      $.ajax({
        url: "/v1/login",
        method: 'post',
        data: {
          name: name,
          password: password
        },
        headers:{
          'x-csrf-token': getCookie("csrfToken"),
        }
      }).done(res => {
        location.href = "/index"
      }).catch(res => {
        alert(res.statusText)
      })
    })
  })
</script>
如果post请求失败 { message: 'invalid csrf token' },这是因为Egg.js默认开启了csrf,POST请求都需要附带csrf请求头,所以我们需要读取一个cookie并在请求头里加上'x-csrf-token'字段。 解决方案

接着在 app/controller 新建一个index.js, 并定一个HomeController的类。类里面的每一个方法都可以作为一个 Controller 在 Router 中引用到,我们可以从app.controller根据文件名和方法名定位到它。

下面代码定义了一个login方法,使用this.ctx.render(template)来渲染模板生成 html,它会自动到app/views文件夹里面找对应的html。

const Controller = require('egg').Controller;

class HomeController extends Controller {
  async login() {
    await this.ctx.render('login');
  }
}

module.exports = HomeController;

然后在 app/router.js 里面配置/login路由

module.exports = app => {
  const { router, controller } = app
  router.get('/login', controller.home.login)
};

此时访问 http://127.0.0.1:7001/login 就能看到login.html里面的内容了。
WeChatd36156e1383c4c3521706443203a6482.png

这时点击登录按钮返回404,这是因为我们没有配置/v1/login这个路由,所以我们要在router.js里面配置一下/v1/login作为登录接口,由于是登录功能所以这里配置的是post请求

module.exports = app => {
  const { router, controller } = app
  router.get('/login', controller.home.login)
  
  router.post('/v1/login', controller.v1.users.login);
};

因为这个需求用到了数据库,需要在数据库里面插入一条数据进行测试,所以先搞一下egg里面的mysql操作,因为文档里面有详细的说明这里不多阐述,直接开始在项目里面用。

先在config/config.default.js配置数据库连接信息

module.exports = appInfo => {
  const config = exports = {
    sequelize: {
      dialect: 'mysql', // support: mysql, mariadb, postgres, mssql
      database: 'test',
      host: 'localhost',
      port: '3306',
      username: 'root',
      password: '123456',
    },
  };
  config.keys = appInfo.name + '_1583298043384_5771';
  config.view = {
    defaultViewEngine: 'nunjucks',
    mapping: {
      '.html': 'nunjucks' //左边写成.html后缀,会自动渲染.html文件
    }
  }
  return {
    ...config
  };
};

然后在app里面创建model文件夹,新建一个user.js,里面是这次登录功能用到的所有操作数据库的方法

Model

app/model/**用于放置领域模型,可选,由领域类相关插件约定,如egg-sequelize

我们使用egg-sequelize来操作数据库,通过映射数据库条目到对象,或者对象到数据库条目,这样,我们读写的都是JavaScript对象,并且还会辅助我们将定义好的 Model 对象加载到 app 和 ctx 上,对前端更加友好。

用到Sequelize的方法

  • findOne(options) - 查询单条数据 -> Promise
  • create(options) - 构建一个新的模型实例,并保存到对应数据库表中。-> Promise

[options.where] - Object 一个描述查询限制范围(WHERE条件)的对象
[options.attributes] - Array.<String> | Object 要查询的属性(字段)列表,或一个includeexclude对象的键。
注意Sequelize的方法都是返回Promise对象,其余的方法可以到官网里查询Sequelize

// app/model/user.js
const bcrypt = require('bcryptjs');
module.exports = app => {
  const { STRING } = app.Sequelize;
  const User = app.model.define('users', { //定义模型`model`和表之间的映射关系使用`define`方法
    name: { type: STRING(30), unique: true, allowNull: false }, // 用户名
    password: STRING
  }, {
    timestamps: false
  })
  // 根据参数获取用户
  User.getUserByArgs = function (params, exclude) {
    return this.findOne({ // 搜索数据库中的一个特定元素
      where: params,
      attributes: {
        exclude: exclude.split(',')
      }
    })
  }
  // 查询指定参数
  User.queryUser = async function (params) {
    return this.findOne({
      where: params,
      attributes: ['name']
    })
  }
  // 密码hash
  User.hashPassword = function (password) {
    const salt = bcrypt.genSaltSync(10); // 处理数据的轮次数
    return bcrypt.hashSync(password, salt); // 将密码转为加密值
  }
  // 注册用户
  User.register = function (fields) {
    fields.password = User.hashPassword(fields.password);
    return this.create(fields) // 在数据库里创建一条数据
  }
  // 将密码跟加密的密码对比
  User.compareSync = function (password, hashedPassword) {
    return bcrypt.compareSync(password, hashedPassword)
  }
  return User
}
由于密码特殊所以我们需要将密码加密后再存到数据库里面,比对的时候也是比对的加密后的密码,这里用到的是加密算法BCrypt

然后我们在app.js的didLoad里面初始一条数据 作为测试用,didLoad是eggjs里的生命周期函数 在这里所有文件加载完成

// app.js
class AppBootHook {
  constructor(app) {
    this.app = app;
  }
  async didLoad() {
    // init数据库操作
    const user = {
      id: 1,
      name: 'admin',
      password: '123456'
    };
    // 只有数据库里面没有这个账号才创建
    if (!(await this.app.model.User.queryUser({ name: user.name }))) {
      await this.app.model.User.register(user);
    }
  }
}

module.exports = AppBootHook;

接下来写/v1/login对应的Control,,新建一个v1文件夹用于管理所有的RESTful接口,并新建一个user.js并定义login方法

通过this.ctx.body能拿到用户参数,然后拿参数里的name到数据库里面查找这条数据,如果没有数据就返回没找到,如果找到了将参数的password和数据库里的加密密码对比,通过bcrypt.compareSync(password, hashedPassword)这个方法返回true就证明登录成功了

const Controller = require('egg').Controller;

class UsersController extends Controller {
  async login() {
    const ctx = this.ctx
    const name = ctx.request.body.name
    const password = ctx.request.body.password
    const user = await ctx.model.User.getUserByArgs({name}, '')
    if (!user) {
      return ctx.body = {
        errmsg: '没有找到该用户',
        errcode: 10001
      }
    }
    if (!(ctx.model.User.compareSync(password, user.dataValues.password))) {
      return ctx.body = {
        errmsg: '密码错误',
        errcode: 10002
      }
    }
    ctx.body = {
      user,
      errcode: 200
    }
  }
}
module.exports = UsersController;

下图是 await ctx.model.User.getUserByArgs({name}, '')返回的数据
1586854685356.jpg

这样一个使用eggjs做的简易登录接口就做好了

阅读 3.8k
156 声望
17 粉丝
0 条评论
156 声望
17 粉丝
文章目录
宣传栏