一文看懂 Eggjs-基础全面讲解(完结)

对上篇文章回顾下,上篇讲到了javascript

Cookie 与 Session

Cookie

经过 ctx.cookies,咱们能够在 controller 中便捷、安全的设置和读取 Cookie。html

class HomeController extends Controller {
  async add() {
    const ctx = this.ctx;
    let count = ctx.cookies.get('count');
    count = count ? Number(count) : 0;
    ctx.cookies.set('count', ++count);
    ctx.body = count;
  }
  async remove() {
    const ctx = this.ctx;
    ctx.cookies.set('count', null);
    ctx.status = 204;
  }
}
复制代码

ctx.cookies.set(key, value, options)

设置 Cookie 实际上是经过在 HTTP 响应中设置 set-cookie 头完成的,每个 set-cookie 都会让浏览器在 Cookie 中存一个键值对。在设置 Cookie 值的同时,协议还支持许多参数来配置这个 Cookie 的传输、存储和权限。前端

  • {Number} maxAge: 设置这个键值对在浏览器的最长保存时间。是一个从服务器当前时刻开始的毫秒数。
  • {Date} expires: 设置这个键值对的失效时间,若是设置了 maxAge,expires 将会被覆盖。若是 maxAge 和 expires 都没设置,Cookie 将会在浏览器的会话失效(通常是关闭浏览器时)的时候失效。
  • {String} path: 设置键值对生效的 URL 路径,默认设置在根路径上(/),也就是当前域名下的全部 URL 均可以访问这个 Cookie。
  • {String} domain: 设置键值对生效的域名,默认没有配置,能够配置成只在指定域名才能访问。
  • {Boolean} httpOnly: 设置键值对是否能够被 js 访问,默认为 true,不容许被 js 访问。
  • {Boolean} secure: 设置键值对只在 HTTPS 链接上传输,框架会帮咱们判断当前是否在 HTTPS 链接上自动设置 secure 的值。

除了这些属性以外,框架另外扩展了 3 个参数的支持:java

  • {Boolean} overwrite:设置 key 相同的键值对如何处理,若是设置为 true,则后设置的值会覆盖前面设置的,不然将会发送两个 set-cookie 响应头。
  • {Boolean} signed:设置是否对 Cookie 进行签名,若是设置为 true,则设置键值对的时候会同时对这个键值对的值进行签名,后面取的时候作校验,能够防止前端对这个值进行篡改。默认为 true。
  • {Boolean} encrypt:设置是否对 Cookie 进行加密,若是设置为 true,则在发送 Cookie 前会对这个键值对的值进行加密,客户端没法读取到 Cookie 的明文值。默认为 false。

Cookie 是加签不加密的,浏览器能够看到明文,js 不能访问,不能被客户端(手工)篡改。mysql

若是想要 Cookie 在浏览器端能够被 js 访问并修改:linux

ctx.cookies.set(key, value, {
  httpOnly: false,
  signed: false,
});
复制代码
  • 因为浏览器和其余客户端实现的不肯定性,为了保证 Cookie 能够写入成功,建议 value 经过 base64 编码或者其余形式 encode 以后再写入。
  • 因为浏览器对 Cookie 有长度限制限制,因此尽可能不要设置太长的 Cookie。通常来讲不要超过 4093 bytes。当设置的 Cookie value 大于这个值时,框架会打印一条警告日志。

ctx.cookies.get(key, options)

上面在设置 Cookie 的时候,咱们能够设置 options.signed 和 options.encrypt 来对 Cookie 进行签名或加密,所以对应的在获取 Cookie 的时候也要传相匹配的选项。git

  • 若是设置的时候指定为 signed,获取时未指定,则不会在获取时对取到的值作验签,致使可能被客户端篡改。
  • 若是设置的时候指定为 encrypt,获取时未指定,则没法获取到真实的值,而是加密事后的密文。

Cookie 秘钥

因为咱们在 Cookie 中须要用到加解密和验签,因此须要配置一个秘钥供加密使用。在 config/config.default.jsgithub

module.exports = {
  keys: 'key1,key2',
};
复制代码

keys 配置成一个字符串,能够按照逗号分隔配置多个 key。Cookie 在使用这个配置进行加解密时:redis

  • 加密和加签时只会使用第一个秘钥。
  • 解密和验签时会遍历 keys 进行解密。

若是咱们想要更新 Cookie 的秘钥,可是又不但愿以前设置到用户浏览器上的 Cookie 失效,能够将新的秘钥配置到 keys 最前面,等过一段时间以后再删去不须要的秘钥便可。sql

Session

Cookie 在 Web 应用中常常承担标识请求方身份的功能,因此 Web 应用在 Cookie 的基础上封装了 Session 的概念,专门用作用户身份识别。

框架内置了 Session 插件,给咱们提供了 ctx.session 来访问或者修改当前用户 Session 。

class HomeController extends Controller {
  async fetchPosts() {
    const ctx = this.ctx;
    // 获取 Session 上的内容
    const userId = ctx.session.userId;
    const posts = await ctx.service.post.fetch(userId);
    // 修改 Session 的值
    ctx.session.visited = ctx.session.visited ? (ctx.session.visited + 1) : 1;
    ctx.body = {
      success: true,
      posts,
    };
  }
}
复制代码

Session 的使用方法很是直观,直接读取它或者修改它就能够了,若是要删除它,直接将它赋值为 null:

ctx.session = null;
复制代码

须要 特别注意 的是:设置 session 属性时须要避免如下几种状况(会形成字段丢失,详见 koa-session 源码)

  • 不要以 _ 开头
  • 不能为 isNew
// ❌ 错误的用法
ctx.session._visited = 1;   // --> 该字段会在下一次请求时丢失
ctx.session.isNew = 'HeHe'; // --> 为内部关键字, 不该该去更改

// ✔️ 正确的用法
ctx.session.visited = 1;    // --> 此处没有问题
复制代码

Session 的实现是基于 Cookie 的,默认配置下,用户 Session 的内容加密后直接存储在 Cookie 中的一个字段中,用户每次请求咱们网站的时候都会带上这个 Cookie,咱们在服务端解密后使用。Session 的默认配置以下:

exports.session = {
  key: 'EGG_SESS',
  maxAge: 24 * 3600 * 1000, // 1 天
  httpOnly: true,
  encrypt: true,
};
复制代码

能够看到这些参数除了 key 都是 Cookie 的参数,key 表明了存储 Session 的 Cookie 键值对的 key 是什么。在默认的配置下,存放 Session 的 Cookie 将会加密存储、不可被前端 js 访问,这样能够保证用户的 Session 是安全的。

扩展存储

Session 默认存放在 Cookie 中,可是若是咱们的 Session 对象过于庞大,就会带来一些额外的问题:

  • 前面提到,浏览器一般都有限制最大的 Cookie 长度,当设置的 Session 过大时,浏览器可能拒绝保存。
  • Cookie 在每次请求时都会带上,当 Session 过大时,每次请求都要额外带上庞大的 Cookie 信息。

咱们只须要设置 app.sessionStore 便可将 Session 存储到指定的存储中。

// app.js
module.exports = app => {
  app.sessionStore = {
    // support promise / async
    async get (key) {
      // return value;
    },
    async set (key, value, maxAge) {
      // set key to store
    },
    async destroy (key) {
      // destroy key
    },
  };
};
复制代码

sessionStore 的实现咱们也能够封装到插件中,例如 egg-session-redis 就提供了将 Session 存储到 redis 中的能力,在应用层,咱们只须要引入 egg-redisegg-session-redis 插件便可。

// plugin.js
exports.redis = {
  enable: true,
  package: 'egg-redis',
};
exports.sessionRedis = {
  enable: true,
  package: 'egg-session-redis',
};
复制代码

一旦选择了将 Session 存入到外部存储中,就意味着系统将强依赖于这个外部存储,当它挂了的时候,咱们就彻底没法使用 Session 相关的功能了。所以咱们更推荐你们只将必要的信息存储在 Session 中,保持 Session 的精简并使用默认的 Cookie 存储,用户级别的缓存不要存储在 Session 中。

Session 实践

修改用户 Session 失效时间

虽然在 Session 的配置中有一项是 maxAge,可是它只能全局设置 Session 的有效期,咱们常常能够在一些网站的登录页上看到有 记住我 的选项框,勾选以后可让登录用户的 Session 有效期更长。这种针对特定用户的 Session 有效时间设置咱们能够经过 ctx.session.maxAge= 来实现。

const ms = require('ms');
class UserController extends Controller {
  async login() {
    const ctx = this.ctx;
    const { username, password, rememberMe } = ctx.request.body;
    const user = await ctx.loginAndGetUser(username, password);

    // 设置 Session
    ctx.session.user = user;
    // 若是用户勾选了 `记住我`,设置 30 天的过时时间
    if (rememberMe) ctx.session.maxAge = ms('30d');
  }
}
复制代码
延长用户 Session 有效期

默认状况下,当用户请求没有致使 Session 被修改时,框架都不会延长 Session 的有效期,可是在有些场景下,咱们但愿用户若是长时间都在访问咱们的站点,则延长他们的 Session 有效期,不让用户退出登陆态。

// config/config.default.js
module.exports = {
  session: {
    renew: true,
  },
};
复制代码

异常处理

errorPageUrl

onerror 插件的配置中支持 errorPageUrl 属性,当配置了 errorPageUrl 时,一旦用户请求线上应用的 HTML 页面异常,就会重定向到这个地址。

config/config.default.js 中 先配置静态文件地址

// config/config.default.js
module.exports = {
  static: {
    prefix: '/',
      dir: path.join(appInfo.baseDir, 'app/public'),
  },
};
复制代码
// config/config.default.js
module.exports = {
  onerror: {
    // 线上页面发生异常时,重定向到这个页面上
    errorPageUrl: '/50x.html',
  },
};
复制代码

自定义统一异常处理

// config/config.default.js
module.exports = {
  onerror: {
    all(err, ctx) {
      // 在此处定义针对全部响应类型的错误处理方法
      // 注意,定义了 config.all 以后,其余错误处理方法不会再生效
      ctx.body = 'error';
      ctx.status = 500;
    },
    html(err, ctx) {
      // html hander
      ctx.body = '<h3>error</h3>';
      ctx.status = 500;
    },
    json(err, ctx) {
      // json hander
      ctx.body = { message: 'error' };
      ctx.status = 500;
    },
    jsonp(err, ctx) {
      // 通常来讲,不须要特殊针对 jsonp 进行错误定义,jsonp 的错误处理会自动调用 json 错误处理,并包装成 jsonp 的响应格式
    },
  },
};
复制代码

404

框架并不会将服务端返回的 404 状态当作异常来处理,可是框架提供了当响应为 404 且没有返回 body 时的默认响应。

  • 当请求被框架断定为须要 JSON 格式的响应时,会返回一段 JSON:
{ "message": "Not Found" }
复制代码
  • 当请求被框架断定为须要 HTML 格式的响应时,会返回一段 HTML:
<h1>404 Not Found</h1>
复制代码

框架支持经过配置,将默认的 HTML 请求的 404 响应重定向到指定的页面。

// config/config.default.js
module.exports = {
  notfound: {
    pageUrl: '/404.html',
  },
};
复制代码

自定义 404 响应

在一些场景下,咱们须要自定义服务器 404 时的响应,和自定义异常处理同样,咱们也只须要加入一个中间件便可对 404 作统一处理:

// app/middleware/notfound_handler.js
module.exports = () => {
  return async function notFoundHandler(ctx, next) {
    await next();
    if (ctx.status === 404 && !ctx.body) {
      if (ctx.acceptJSON) {
        ctx.body = { error: 'Not Found' };
      } else {
        ctx.body = '<h1>Page Not Found</h1>';
      }
    }
  };
};
复制代码

在配置中引入中间件:

// config/config.default.js
module.exports = {
  middleware: [ 'notfoundHandler' ],
};
复制代码

MySQL

egg-mysql

框架提供了 egg-mysql 插件来访问 MySQL 数据库。这个插件既能够访问普通的 MySQL 数据库,也能够访问基于 MySQL 协议的在线数据库服务

安装与配置

npm i --save egg-mysql
复制代码

开启插件:

// config/plugin.js
exports.mysql = {
  enable: true,
  package: 'egg-mysql',
};
复制代码

config/config.${env}.js 配置各个环境的数据库链接信息。

单数据源

若是咱们的应用只须要访问一个 MySQL 数据库实例,能够以下配置:

// config/config.${env}.js
exports.mysql = {
  // 单数据库信息配置
  client: {
    // host
    host: 'mysql.com',
    // 端口号
    port: '3306',
    // 用户名
    user: 'test_user',
    // 密码
    password: 'test_password',
    // 数据库名
    database: 'test',
  },
  // 是否加载到 app 上,默认开启
  app: true,
  // 是否加载到 agent 上,默认关闭
  agent: false,
};
复制代码

使用方式:

await app.mysql.query(sql, values); // 单实例能够直接经过 app.mysql 访问
复制代码

多数据源

exports.mysql = {
  clients: {
    // clientId, 获取client实例,须要经过 app.mysql.get('clientId') 获取
    db1: {
      // host
      host: 'mysql.com',
      // 端口号
      port: '3306',
      // 用户名
      user: 'test_user',
      // 密码
      password: 'test_password',
      // 数据库名
      database: 'test',
    },
    db2: {
      // host
      host: 'mysql2.com',
      // 端口号
      port: '3307',
      // 用户名
      user: 'test_user',
      // 密码
      password: 'test_password',
      // 数据库名
      database: 'test',
    },
    // ...
  },
  // 全部数据库配置的默认值
  default: {

  },

  // 是否加载到 app 上,默认开启
  app: true,
  // 是否加载到 agent 上,默认关闭
  agent: false,
};
复制代码

使用方式:

const client1 = app.mysql.get('db1');
await client1.query(sql, values);

const client2 = app.mysql.get('db2');
await client2.query(sql, values);
复制代码

Service 层

因为对 MySQL 数据库的访问操做属于 Web 层中的数据处理层,所以咱们强烈建议将这部分代码放在 Service 层中维护。

// app/service/user.js
class UserService extends Service {
  async find(uid) {
    // 假如 咱们拿到用户 id 从数据库获取用户详细信息
    const user = await this.app.mysql.get('users', { id: 11 });
    return { user };
  }
}

// app/controller/user.js
class UserController extends Controller {
  async info() {
    const ctx = this.ctx;
    const userId = ctx.params.id;
    const user = await ctx.service.user.find(userId);
    ctx.body = user;
  }
}
复制代码

如何编写 CRUD 语句

Create

// 插入
const result = await this.app.mysql.insert('posts', { title: 'Hello World' }); // 在 post 表中,插入 title 为 Hello World 的记录

=> INSERT INTO `posts`(`title`) VALUES('Hello World');

console.log(result);
=>
{
  fieldCount: 0,
  affectedRows: 1,
  insertId: 3710,
  serverStatus: 2,
  warningCount: 2,
  message: '',
  protocol41: true,
  changedRows: 0
}

// 判断插入成功
const insertSuccess = result.affectedRows === 1;
复制代码

Read

能够直接使用 get 方法或 select 方法获取一条或多条记录。select 方法支持条件查询与结果的定制。

const post = await this.app.mysql.get('posts', { id: 12 });

=> SELECT * FROM `posts` WHERE `id` = 12 LIMIT 0, 1;
复制代码
查询全表
const results = await this.app.mysql.select('posts');

=> SELECT * FROM `posts`;
复制代码
条件查询和结果定制
  • where 查询条件 { status: 'draft', author: ['author1', 'author2'] }
  • columns 查询的列名 ['author', 'title']
  • orders 排序方式 [['created_at','desc'], ['id','desc']]
  • limit 10 查询条数
  • offset 0 偏移量
const results = await this.app.mysql.select('posts', { // 搜索 post 表
  where: { status: 'draft', author: ['author1', 'author2'] }, // WHERE 条件
  columns: ['author', 'title'], // 要查询的表字段
  orders: [['created_at','desc'], ['id','desc']], // 排序方式
  limit: 10, // 返回数据量
  offset: 0, // 数据偏移量
});

=> SELECT `author`, `title` FROM `posts`
  WHERE `status` = 'draft' AND `author` IN('author1','author2')
  ORDER BY `created_at` DESC, `id` DESC LIMIT 0, 10;
复制代码

Update

// 修改数据,将会根据主键 ID 查找,并更新
const row = {
  id: 123,
  name: 'fengmk2',
  otherField: 'other field value',    // any other fields u want to update
  modifiedAt: this.app.mysql.literals.now, // `now()` on db server
};
const result = await this.app.mysql.update('posts', row); // 更新 posts 表中的记录

=> UPDATE `posts` SET `name` = 'fengmk2', `modifiedAt` = NOW() WHERE id = 123 ;

// 判断更新成功
const updateSuccess = result.affectedRows === 1;


// 若是主键是自定义的 ID 名称,如 custom_id,则须要在 `where` 里面配置
const row = {
  name: 'fengmk2',
  otherField: 'other field value',    // any other fields u want to update
  modifiedAt: this.app.mysql.literals.now, // `now()` on db server
};

const options = {
  where: {
    custom_id: 456
  }
};

const result = await this.app.mysql.update('posts', row, options); // 更新 posts 表中的记录

=> UPDATE `posts` SET `name` = 'fengmk2', `modifiedAt` = NOW() WHERE custom_id = 456 ;

// 判断更新成功
const updateSuccess = result.affectedRows === 1;
复制代码

Delete

const result = await this.app.mysql.delete('posts', {
  author: 'fengmk2',
});

=> DELETE FROM `posts` WHERE `author` = 'fengmk2';
复制代码

直接执行 sql 语句

使用 query 能够执行合法的 sql 语句。

咱们极其不建议开发者拼接 sql 语句,这样很容易引发 sql 注入!!

若是必需要本身拼接 sql 语句,请使用 mysql.escape 方法。

const postId = 1;
const results = await this.app.mysql.query('update posts set hits = (hits + ?) where id = ?', [1, postId]);

=> update posts set hits = (hits + 1) where id = 1;
复制代码

使用事务

通常来讲,事务是必须知足4个条件(ACID): Atomicity(原子性)、Consistency(一致性)、Isolation(隔离性)、Durability(可靠性)

  • 原子性:确保事务内的全部操做都成功完成,不然事务将被停止在故障点,之前的操做将回滚到之前的状态。
  • 一致性:对于数据库的修改是一致的。
  • 隔离性:事务是彼此独立的,不互相影响
  • 持久性:确保提交事务后,事务产生的结果能够永久存在。

所以,对于一个事务来说,必定伴随着 beginTransaction、commit 或 rollback,分别表明事务的开始,成功和失败回滚。

手动控制

  • 优势:beginTransaction, commitrollback 都由开发者来彻底控制,能够作到很是细粒度的控制。
  • 缺点:手写代码比较多,不是每一个人都能写好。忘记了捕获异常和 cleanup 都会致使严重 bug。
const conn = await app.mysql.beginTransaction(); // 初始化事务

try {
  await conn.insert(table, row1);  // 第一步操做
  await conn.update(table, row2);  // 第二步操做
  await conn.commit(); // 提交事务
} catch (err) {
  // error, rollback
  await conn.rollback(); // 必定记得捕获异常后回滚事务!!
  throw err;
}
复制代码

自动控制:Transaction with scope

  • API:beginTransactionScope(scope, ctx)
    • scope: 一个 generatorFunction,在这个函数里面执行此次事务的全部 sql 语句。
    • ctx: 当前请求的上下文对象,传入 ctx 能够保证即使在出现事务嵌套的状况下,一次请求中同时只有一个激活状态的事务。
const result = await app.mysql.beginTransactionScope(async conn => {
  // don't commit or rollback by yourself
  await conn.insert(table, row1);
  await conn.update(table, row2);
  return { success: true };
}, ctx); // ctx 是当前请求的上下文,若是是在 service 文件中,能够从 `this.ctx` 获取到
// if error throw on scope, will auto rollback
复制代码

表达式(Literal)

若是须要调用 MySQL 内置的函数(或表达式),可使用 Literal

内置表达式

  • NOW():数据库当前系统时间,经过 app.mysql.literals.now 获取。
await this.app.mysql.insert(table, {
  create_time: this.app.mysql.literals.now,
});

=> INSERT INTO `$table`(`create_time`) VALUES(NOW())
复制代码

自定义表达式

下例展现了如何调用 MySQL 内置的 CONCAT(s1, ...sn) 函数,作字符串拼接。

const Literal = this.app.mysql.literals.Literal;
const first = 'James';
const last = 'Bond';
await this.app.mysql.insert(table, {
  id: 123,
  fullname: new Literal(`CONCAT("${first}", "${last}"`),
});

=> INSERT INTO `$table`(`id`, `fullname`) VALUES(123, CONCAT("James", "Bond"))
复制代码

打印查询日志

启动的时候

windows

set DEBUG=ali-rds* && npm run dev
复制代码

linux、 mac

DEBUG=ali-rds* npm run dev
复制代码

Sequelize

在 Node.js 社区中,sequelize 是一个普遍使用的 ORM 框架,它支持 MySQL、PostgreSQL、SQLite 和 MSSQL 等多个数据源。

初始化项目

安装依赖

npm install --save egg-sequelize mysql2
复制代码

config/plugin.js 中引入 egg-sequelize 插件

exports.sequelize = {
  enable: true,
  package: 'egg-sequelize',
};
复制代码

config/config.default.js 中编写 sequelize 配置

config.sequelize = {
  dialect: 'mysql',
  host: '127.0.0.1',
  port: 3306,
  database: 'egg-sequelize-doc-default',
};
复制代码

咱们能够在不一样的环境配置中配置不一样的数据源地址,用于区分不一样环境使用的数据库,例如咱们能够新建一个 config/config.unittest.js 配置文件,写入以下配置,将单测时链接的数据库指向 egg-sequelize-doc-unittest。

exports.sequelize = {
  dialect: 'mysql',
  host: '127.0.0.1',
  port: 3306,
  database: 'egg-sequelize-doc-unittest',
};
复制代码

初始化数据库和 Migrations

在项目的演进过程当中,每个迭代都有可能对数据库数据结构作变动,怎样跟踪每个迭代的数据变动,并在不一样的环境(开发、测试、CI)和迭代切换中,快速变动数据结构呢?这时候咱们就须要 Migrations 来帮咱们管理数据结构的变动了。

sequelize 提供了 sequelize-cli 工具来实现 Migrations,咱们也能够在 egg 项目中引入 sequelize-cli。

安装 sequelize-cli

npm install --save-dev sequelize-cli
复制代码

在 egg 项目中,咱们但愿将全部数据库 Migrations 相关的内容都放在 database 目录下,因此咱们在项目根目录下新建一个 .sequelizer 配置文件:

'use strict';

const path = require('path');

module.exports = {
  config: path.join(__dirname, 'database/config.json'),
  'migrations-path': path.join(__dirname, 'database/migrations'),
  'seeders-path': path.join(__dirname, 'database/seeders'),
  'models-path': path.join(__dirname, 'app/model'),
};
复制代码

初始化 Migrations 配置文件和目录

npx sequelize init:config
npx sequelize init:migrations
复制代码

执行完后会生成 database/config.json 文件和 database/migrations 目录,咱们修改一下 database/config.json 中的内容,将其改为咱们项目中使用的数据库配置:

{
  "development": {
    "username": "root",
    "password": null,
    "database": "egg-sequelize-doc-default",
    "host": "127.0.0.1",
    "dialect": "mysql"
  },
  "test": {
    "username": "root",
    "password": null,
    "database": "egg-sequelize-doc-unittest",
    "host": "127.0.0.1",
    "dialect": "mysql"
  }
}
复制代码

时 sequelize-cli 和相关的配置也都初始化好了,咱们能够开始编写项目的第一个 Migration 文件来建立咱们的一个 users 表了。

npx sequelize migration:generate --name=init-users
复制代码

执行完后会在 database/migrations 目录下生成一个 migration 文件(${timestamp}-init-users.js),咱们修改它来处理初始化 users 表:

'use strict';

module.exports = {
  // 在执行数据库升级时调用的函数,建立 users 表
  up: async (queryInterface, Sequelize) => {
    const { INTEGER, DATE, STRING } = Sequelize;
    await queryInterface.createTable('users', {
      id: { type: INTEGER, primaryKey: true, autoIncrement: true },
      name: STRING(30),
      age: INTEGER,
      created_at: DATE,
      updated_at: DATE,
    });
  },
  // 在执行数据库降级时调用的函数,删除 users 表
  down: async queryInterface => {
    await queryInterface.dropTable('users');
  },
};
复制代码
# 升级数据库
npx sequelize db:migrate
# 若是有问题须要回滚,能够经过 `db:migrate:undo` 回退一个变动
# npx sequelize db:migrate:undo
# 能够经过 `db:migrate:undo:all` 回退到初始状态
# npx sequelize db:migrate:undo:all
复制代码

编写代码

首先咱们来在 app/model/ 目录下编写 user 这个 Model:

'use strict';

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

  const User = app.model.define('user', {
    id: { type: INTEGER, primaryKey: true, autoIncrement: true },
    name: STRING(30),
    age: INTEGER,
    created_at: DATE,
    updated_at: DATE,
  });

  return User;
};
复制代码

这个 Model 就能够在 Controller 和 Service 中经过 app.model.User 或者 ctx.model.User 访问到了,例如咱们编写 app/controller/users.js

// app/controller/users.js
const Controller = require('egg').Controller;

function toInt(str) {
  if (typeof str === 'number') return str;
  if (!str) return str;
  return parseInt(str, 10) || 0;
}

class UserController extends Controller {
  async index() {
    const ctx = this.ctx;
    const query = { limit: toInt(ctx.query.limit), offset: toInt(ctx.query.offset) };
    ctx.body = await ctx.model.User.findAll(query);
  }

  async show() {
    const ctx = this.ctx;
    ctx.body = await ctx.model.User.findByPk(toInt(ctx.params.id));
  }

  async create() {
    const ctx = this.ctx;
    const { name, age } = ctx.request.body;
    const user = await ctx.model.User.create({ name, age });
    ctx.status = 201;
    ctx.body = user;
  }

  async update() {
    const ctx = this.ctx;
    const id = toInt(ctx.params.id);
    const user = await ctx.model.User.findByPk(id);
    if (!user) {
      ctx.status = 404;
      return;
    }

    const { name, age } = ctx.request.body;
    await user.update({ name, age });
    ctx.body = user;
  }

  async destroy() {
    const ctx = this.ctx;
    const id = toInt(ctx.params.id);
    const user = await ctx.model.User.findByPk(id);
    if (!user) {
      ctx.status = 404;
      return;
    }

    await user.destroy();
    ctx.status = 200;
  }
}

module.exports = UserController;
复制代码

工具总结

相关文章
相关标签/搜索