#Eggjs入门系列-基础全面讲解(完结)

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

服务(Service)

插件

定时任务

框架扩展

启动自定义

应用部署

日志

HttpClient

Cookie 与 Session

Cookie

通过 ctx.cookies,我们可以在 controller 中便捷、安全的设置和读取 Cookie。

classHomeControllerextendsController{

asyncadd() {

constctx =this.ctx;

letcount = ctx.cookies.get('count');

count = count ?Number(count) :0;

ctx.cookies.set('count', ++count);

    ctx.body = count;

  }

asyncremove() {

constctx =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 个参数的支持:

{Boolean} overwrite:设置 key 相同的键值对如何处理,如果设置为 true,则后设置的值会覆盖前面设置的,否则将会发送两个 set-cookie 响应头。

{Boolean} signed:设置是否对 Cookie 进行签名,如果设置为 true,则设置键值对的时候会同时对这个键值对的值进行签名,后面取的时候做校验,可以防止前端对这个值进行篡改。默认为 true。

{Boolean} encrypt:设置是否对 Cookie 进行加密,如果设置为 true,则在发送 Cookie 前会对这个键值对的值进行加密,客户端无法读取到 Cookie 的明文值。默认为 false。

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

如果想要 Cookie 在浏览器端可以被 js 访问并修改:

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 的时候也要传相匹配的选项。

如果设置的时候指定为 signed,获取时未指定,则不会在获取时对取到的值做验签,导致可能被客户端篡改。

如果设置的时候指定为 encrypt,获取时未指定,则无法获取到真实的值,而是加密过后的密文。

Cookie 秘钥

由于我们在 Cookie 中需要用到加解密和验签,所以需要配置一个秘钥供加密使用。在 config/config.default.js 中

module.exports = {

keys:'key1,key2',

};

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

加密和加签时只会使用第一个秘钥。

解密和验签时会遍历 keys 进行解密。

如果我们想要更新 Cookie 的秘钥,但是又不希望之前设置到用户浏览器上的 Cookie 失效,可以将新的秘钥配置到 keys 最前面,等过一段时间之后再删去不需要的秘钥即可。

Session

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

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

classHomeControllerextendsController{

asyncfetchPosts() {

constctx =this.ctx;

// 获取 Session 上的内容

constuserId = ctx.session.userId;

constposts =awaitctx.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

asyncget (key) {

// return value;

    },

asyncset (key, value, maxAge) {

// set key to store

    },

asyncdestroy (key) {

// destroy key

    },

  };

};

sessionStore 的实现我们也可以封装到插件中,例如 egg-session-redis 就提供了将 Session 存储到 redis 中的能力,在应用层,我们只需要引入 egg-redis 和 egg-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= 来实现。

constms =require('ms');

classUserControllerextendsController{

asynclogin() {

constctx =this.ctx;

const{ username, password, rememberMe } = ctx.request.body;

constuser =awaitctx.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>404Not Found</h1>

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

// config/config.default.js

module.exports = {

notfound: {

pageUrl:'/404.html',

  },

};

自定义 404 响应

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

// app/middleware/notfound_handler.js

module.exports = () => {

returnasyncfunctionnotFoundHandler(ctx, next){

awaitnext();

if(ctx.status ===404&& !ctx.body) {

if(ctx.acceptJSON) {

ctx.body = {error:'Not Found'};

}else{

ctx.body ='&lt;h1&gt;Page Not Found&lt;/h1&gt;';

      }

    }

  };

};

在配置中引入中间件:

// 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,

};

使用方式:

awaitapp.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,

};

使用方式:

constclient1 = app.mysql.get('db1');

awaitclient1.query(sql, values);

constclient2 = app.mysql.get('db2');

awaitclient2.query(sql, values);

Service 层

由于对 MySQL 数据库的访问操作属于 Web 层中的数据处理层,因此我们强烈建议将这部分代码放在 Service 层中维护。

// app/service/user.js

classUserServiceextendsService{

asyncfind(uid) {

// 假如 我们拿到用户 id 从数据库获取用户详细信息

constuser =awaitthis.app.mysql.get('users', {id:11});

return{ user };

  }

}

// app/controller/user.js

classUserControllerextendsController{

asyncinfo() {

constctx =this.ctx;

constuserId = ctx.params.id;

constuser =awaitctx.service.user.find(userId);

    ctx.body = user;

  }

}

如何编写 CRUD 语句

Create

// 插入

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

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

console.log(result);

=&gt;

{

fieldCount:0,

affectedRows:1,

insertId:3710,

serverStatus:2,

warningCount:2,

message:'',

protocol41:true,

changedRows:0

}

// 判断插入成功

constinsertSuccess = result.affectedRows ===1;

Read

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

constpost =awaitthis.app.mysql.get('posts', {id:12});

=> SELECT * FROM`posts`WHERE`id`=12LIMIT0,1;

查询全表

constresults =awaitthis.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 偏移量

constresults =awaitthis.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 LIMIT0,10;

Update

// 修改数据,将会根据主键 ID 查找,并更新

constrow = {

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

};

constresult =awaitthis.app.mysql.update('posts', row);// 更新 posts 表中的记录

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

// 判断更新成功

constupdateSuccess = result.affectedRows ===1;

// 如果主键是自定义的 ID 名称,如 custom_id,则需要在 `where` 里面配置

constrow = {

name:'fengmk2',

otherField:'other field value',// any other fields u want to update

modifiedAt:this.app.mysql.literals.now,// `now()` on db server

};

constoptions = {

where: {

custom_id:456

  }

};

constresult =awaitthis.app.mysql.update('posts', row, options);// 更新 posts 表中的记录

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

// 判断更新成功

constupdateSuccess = result.affectedRows ===1;

Delete

constresult =awaitthis.app.mysql.delete('posts', {

author:'fengmk2',

});

=> DELETE FROM`posts`WHERE`author`='fengmk2';

直接执行 sql 语句

使用 query 可以执行合法的 sql 语句。

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

如果必须要自己拼接 sql 语句,请使用 mysql.escape 方法。

constpostId =1;

constresults =awaitthis.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, commit 或 rollback 都由开发者来完全控制,可以做到非常细粒度的控制。

缺点:手写代码比较多,不是每个人都能写好。忘记了捕获异常和 cleanup 都会导致严重 bug。

constconn =awaitapp.mysql.beginTransaction();// 初始化事务

try{

awaitconn.insert(table, row1);// 第一步操作

awaitconn.update(table, row2);// 第二步操作

awaitconn.commit();// 提交事务

}catch(err) {

// error, rollback

awaitconn.rollback();// 一定记得捕获异常后回滚事务!!

throwerr;

}

自动控制:Transaction with scope

API:beginTransactionScope(scope, ctx)

scope: 一个 generatorFunction,在这个函数里面执行这次事务的所有 sql 语句。

ctx: 当前请求的上下文对象,传入 ctx 可以保证即便在出现事务嵌套的情况下,一次请求中同时只有一个激活状态的事务。

constresult =awaitapp.mysql.beginTransactionScope(asyncconn => {

// don't commit or rollback by yourself

awaitconn.insert(table, row1);

awaitconn.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 获取。

awaitthis.app.mysql.insert(table, {

create_time:this.app.mysql.literals.now,

});

=> INSERT INTO`$table`(`create_time`) VALUES(NOW())

自定义表达式

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

constLiteral =this.app.mysql.literals.Literal;

constfirst ='James';

constlast ='Bond';

awaitthis.app.mysql.insert(table, {

id:123,

fullname:newLiteral(`CONCAT("${first}", "${last}"`),

});

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

打印查询日志

启动的时候

windows

set DEBUG=ali-rds* &amp;&amp; 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';

constpath =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;

awaitqueryInterface.createTable('users', {

id: {type: INTEGER,primaryKey:true,autoIncrement:true},

name: STRING(30),

age: INTEGER,

created_at: DATE,

updated_at: DATE,

    });

  },

// 在执行数据库降级时调用的函数,删除 users 表

down:asyncqueryInterface => {

awaitqueryInterface.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;

constUser = app.model.define('user', {

id: {type: INTEGER,primaryKey:true,autoIncrement:true},

name: STRING(30),

age: INTEGER,

created_at: DATE,

updated_at: DATE,

  });

returnUser;

};

这个 Model 就可以在 Controller 和 Service 中通过 app.model.User 或者 ctx.model.User 访问到了,例如我们编写 app/controller/users.js:

// app/controller/users.js

constController =require('egg').Controller;

functiontoInt(str){

if(typeofstr ==='number')returnstr;

if(!str)returnstr;

returnparseInt(str,10) ||0;

}

classUserControllerextendsController{

asyncindex() {

constctx =this.ctx;

constquery = {limit: toInt(ctx.query.limit),offset: toInt(ctx.query.offset) };

ctx.body =awaitctx.model.User.findAll(query);

  }

asyncshow() {

constctx =this.ctx;

ctx.body =awaitctx.model.User.findByPk(toInt(ctx.params.id));

  }

asynccreate() {

constctx =this.ctx;

const{ name, age } = ctx.request.body;

constuser =awaitctx.model.User.create({ name, age });

ctx.status =201;

    ctx.body = user;

  }

asyncupdate() {

constctx =this.ctx;

constid = toInt(ctx.params.id);

constuser =awaitctx.model.User.findByPk(id);

if(!user) {

ctx.status =404;

return;

    }

const{ name, age } = ctx.request.body;

awaituser.update({ name, age });

    ctx.body = user;

  }

asyncdestroy() {

constctx =this.ctx;

constid = toInt(ctx.params.id);

constuser =awaitctx.model.User.findByPk(id);

if(!user) {

ctx.status =404;

return;

    }

awaituser.destroy();

ctx.status =200;

  }

}

module.exports = UserController;

工具总结

sequelize-cli

sequelize-auto 自动从数据库导出 bare sequelize models

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 211,290评论 6 491
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 90,107评论 2 385
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 156,872评论 0 347
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 56,415评论 1 283
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 65,453评论 6 385
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,784评论 1 290
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,927评论 3 406
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,691评论 0 266
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,137评论 1 303
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,472评论 2 326
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,622评论 1 340
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,289评论 4 329
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,887评论 3 312
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,741评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,977评论 1 265
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,316评论 2 360
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,490评论 2 348