一、咖啡店点评应用
应用概述
咖啡店点评是一个网站,您可以用来发布咖啡店的评论。
这个应用程序用到了两个不同的数据源:它会将评论者数据存储在MySQL数据库中,并把咖啡店和评论数据存储在MongoDB数据库中。
这个应用有三个数据模型:
- CoffeeShop(这个模型我们已经在上一步中定义好了)
- Review
- Reviewer
它们有如下关系:
- 一个CoffeeShop拥有多个review
- 一个CoffeeShop拥有多个reviewer
- 一个review属于一个CoffeeShop
- 一个review属于一个reviewer
- 一个reviewer拥有多个review
一般来说,用户可以创建,编辑,删除和阅读咖啡店的评论,并通过ACLs指定基本规则和权限:
- 任何人都可以阅读评论,但必须先登录才能创建,编辑或删除它们。
- 任何人都可以注册为用户,然后能够登录或者注销。
- 登录用户可以创建新的评论,编辑或删除自己的评论,但是他们不能修改一开始选择的咖啡店。
二、创建新的数据源
添加一个新的数据源
除了将API连接到上一步创建的MySQL数据源之外,现在还需要添加一个MongoDB数据源。
lb datasource
出现提示时,回复如下:
? Enter the data-source name: mongoDs
? Select the connector for mongoDs: MongoDB (supported by StrongLoop)
接下来输入一些数据源设置,如主机,端口,用户,密码和数据库名称,然后安装数据库连接器。
? Enter the datasource name: mongodb
? Select the connector for mongodb: MongoDB (supported by StrongLoop)
? Connection String url to override other settings (eg: mongodb://username:password@hostname:port/database):
? host: localhost
? port: 27017
? user: demo
? password: ****
? database: demo
? Install loopback-connector-mongodb@^1.4 Yes
数据库连接器可以使用npm自行安装,数据源设置也可以手动添加到server/ datasources.json
中。
安装MongoDB连接器:
npm install --save loopback-connector-mongodb
配置数据源
在server/datasources.json
中配置新的数据源。
server/datasources.json
...
"mongoDs": {
"name": "mongoDs",
"connector": "mongodb",
"host": "demo.strongloop.com",
"port": 27017,
"database": "getting_started_intermediate",
"username": "demo",
"password": "L00pBack"
}
三、添加新的数据模型
定义Review数据模型
输入:
lb model
出现提示时,输入或选择以下内容:
- Model name:Review
- Data source: mongoDs (mongodb)
- Base class: Use the down-arrow key to select PersistedModel.
- Expose Reviewer via the REST API? Press RETURN to accept the default, Yes.
- Custom plural form (used to build REST URL): Press RETURN to accept the default, Yes.
- Common model or server only: Press RETURN to accept the default, common model.
然后根据提示加入以下属性。
Property name | Property type | Required? |
---|---|---|
date | date | y |
rating | number | n |
comments | string | y |
定义Reviewer数据模型
输入:
lb model
出现提示时,输入或选择以下内容:
- Model name: Reviewer
- Data source: mongoDs (mongodb)
- Base class: 选择User.
- Expose Reviewer via the REST API?: 选择默认选项,yes
- Custom plural form (used to build REST URL): 选择默认选项,yes
接下来不需要给Reviewer添加任何属性,它们都是从基本的用户模型继承下来的。
更新启动脚本,添加一些原始数据
在启动脚本server/boot/create-sample-models.js
中添加一些代码,这个启动脚本有如下几个功能:
- createCoffeeShops()为CoffeeShop模型创建一个MySQL表,并将数据添加到表中。
- createReviewers()使用自动迁移在MongoDB中创建Reviewer数据结构,并向其添加数据。
- createReviews()使用自动迁移在MongoDB中创建评论数据结构,并向其添加数据。
server/boot/create-sample-models.js
var async = require('async');
module.exports = function(app) {
//data sources
var mongoDs = app.dataSources.mongoDs; // 'name' of your mongo connector, you can find it in datasource.json
var mysqlDs = app.dataSources.mysqlDs;
//create all models
async.parallel({
reviewers: async.apply(createReviewers),
coffeeShops: async.apply(createCoffeeShops),
}, function(err, results) {
if (err) throw err;
createReviews(results.reviewers, results.coffeeShops, function(err) {
console.log('> models created sucessfully');
});
});
//create reviewers
function createReviewers(cb) {
mongoDs.automigrate('Reviewer', function(err) {
if (err) return cb(err);
var Reviewer = app.models.Reviewer;
Reviewer.create([{
email: '[email protected]',
password: 'foobar'
}, {
email: '[email protected]',
password: 'johndoe'
}, {
email: '[email protected]',
password: 'janedoe'
}], cb);
});
}
//create coffee shops
function createCoffeeShops(cb) {
mysqlDs.automigrate('CoffeeShop', function(err) {
if (err) return cb(err);
var CoffeeShop = app.models.CoffeeShop;
CoffeeShop.create([{
name: 'Bel Cafe',
city: 'Vancouver'
}, {
name: 'Three Bees Coffee House',
city: 'San Mateo'
}, {
name: 'Caffe Artigiano',
city: 'Vancouver'
}, ], cb);
});
}
//create reviews
function createReviews(reviewers, coffeeShops, cb) {
mongoDs.automigrate('Review', function(err) {
if (err) return cb(err);
var Review = app.models.Review;
var DAY_IN_MILLISECONDS = 1000 * 60 * 60 * 24;
Review.create([{
date: Date.now() - (DAY_IN_MILLISECONDS * 4),
rating: 5,
comments: 'A very good coffee shop.',
publisherId: reviewers[0].id,
coffeeShopId: coffeeShops[0].id,
}, {
date: Date.now() - (DAY_IN_MILLISECONDS * 3),
rating: 5,
comments: 'Quite pleasant.',
publisherId: reviewers[1].id,
coffeeShopId: coffeeShops[0].id,
}, {
date: Date.now() - (DAY_IN_MILLISECONDS * 2),
rating: 4,
comments: 'It was ok.',
publisherId: reviewers[1].id,
coffeeShopId: coffeeShops[1].id,
}, {
date: Date.now() - (DAY_IN_MILLISECONDS),
rating: 4,
comments: 'I go here everyday.',
publisherId: reviewers[2].id,
coffeeShopId: coffeeShops[2].id,
}], cb);
});
}
};
四、定义模型的关系
介绍
LoopBack支持许多不同类型的模型关系:BelongsTo, HasMany, HasManyThrough, and HasAndBelongsToMany等等。
在“咖啡店评论”应用程序中,有以下几种关系:
- 一个CoffeeShop拥有多个review
- 一个CoffeeShop拥有多个reviewer
- 一个review属于一个CoffeeShop
- 一个review属于一个reviewer
- 一个reviewer拥有多个review
定义关系
现在,我们将使用lb relation
来定义这些模型之间的关系。
一个CoffeeShop拥有多个review,没有中间模型和外键。
? Select the model to create the relationship from: CoffeeShop
? Relation type: has many
? Choose a model to create a relationship with: Review
? Enter the property name for the relation: reviews
? Optionally enter a custom foreign key:
? Require a through model? No
一个CoffeeShop拥有多个reviewer,没有中间模型和外键
? Select the model to create the relationship from: CoffeeShop
? Relation type: has many
? Choose a model to create a relationship with: Reviewer
? Enter the property name for the relation: reviewers
? Optionally enter a custom foreign key:
? Require a through model? No
一个review属于一个CoffeeShop,没有外键。
? Select the model to create the relationship from: CoffeeShop
? Relation type: has many
? Choose a model to create a relationship with: Reviewer
? Enter the property name for the relation: reviewers
? Optionally enter a custom foreign key:
? Require a through model? No
一个review属于一个reviewer,外键是publisherId
。
? Select the model to create the relationship from: Review
? Relation type: belongs to
? Choose a model to create a relationship with: Reviewer
? Enter the property name for the relation: reviewer
? Optionally enter a custom foreign key: publisherId
一个reviewer拥有多个review,外键是publisherId
。
? Select the model to create the relationship from: Reviewer
? Relation type: has many
? Choose a model to create a relationship with: Review
? Enter the property name for the relation: reviews
? Optionally enter a custom foreign key: publisherId
? Require a through model? No
查看JSON模型文件
现在,查看common/models/review.json
。你应该会看到这些:
common/models/review.json
...
"relations": {
"coffeeShop": {
"type": "belongsTo",
"model": "CoffeeShop",
"foreignKey": ""
},
"reviewer": {
"type": "belongsTo",
"model": "Reviewer",
"foreignKey": "publisherId"
}
},
...
同样,其他的json文件中应该有如下代码:
common/models/reviewer.json
...
"relations": {
"reviews": {
"type": "hasMany",
"model": "Review",
"foreignKey": "publisherId"
}
},
...
common/models/coffee-shop.json
...
"relations": {
"reviews": {
"type": "hasMany",
"model": "Review",
"foreignKey": ""
},
"reviewers": {
"type": "hasMany",
"model": "Reviewer",
"foreignKey": ""
}
},
...
五、定义权限控制
权限控制简介
loopback应用通过模型访问数据,因此控制对数据的访问意味着对模型进行权限的控制:也就是说,指定什么角色可以在模型上执行读取和写入数据的方法。loopback权限控制由权限控制列表或ACL决定。
接下来,我们将为Review模型设置权限控制。
权限控制应执行以下规则:
- 任何人都可以阅读评论。但是创建、编辑和删除的操作必须在登录之后才有权限。
- 任何人都可以注册为用户,可以登录和登出。
- 登录用户可以创建新的评论,编辑或删除自己的评论。然而,他们不能修改咖啡店的评论。
定义权限控制
这次我们使用lb的acl子命令。
$ lb acl
首先,拒绝所有人操作所有接口,这通常是定义ACL的起点,因为您可以选择性地允许特定操作的访问。
? Select the model to apply the ACL entry to: (all existing models)
? Select the ACL scope: All methods and properties
? Select the access type: All (match all types)
? Select the role: All users
? Select the permission to apply: Explicitly deny access
现在允许所有人对reviews进行读操作
? Select the model to apply the ACL entry to: Review
? Select the ACL scope: All methods and properties
? Select the access type: Read
? Select the role: All users
? Select the permission to apply: Explicitly grant access
允许通过身份验证的用户对coffeeshops进行读操作,也就是说,已登录的用户可以浏览所有咖啡店。
? Select the model to apply the ACL entry to: CoffeeShop
? Select the ACL scope: All methods and properties
? Select the access type: Read
? Select the role: Any authenticated user
? Select the permission to apply: Explicitly grant access
允许经过身份验证的用户对reviews进行写操作,也就是说,已登录的用户可以添加一条评论。
? Select the model to apply the ACL entry to: Review
? Select the ACL scope: A single method
? Enter the method name: create
? Select the role: Any authenticated user
? Select the permission to apply: Explicitly grant access
使review的作者有权限(其“所有者”)对其进行任何更改。
$ lb acl
? Select the model to apply the ACL entry to: Review
? Select the ACL scope: All methods and properties
? Select the access type: Write
? Select the role: The user owning the object
? Select the permission to apply: Explicitly grant access
查看review.json文件
完成上述步骤,此时的common/models/review.json
中的ACL部分应如下所示:
六、定义一个远程钩子
远程钩子介绍
远程钩子(remote hook)是一个在远程方法(自定义远程方法或内置CRUD方法)之前或之后执行的功能。
在这个例子中,我们将定义一个远程钩子,每当在Review模型上调用create()方法时(在创建新的评论时),它将被调用。
您可以定义两种远程钩子:
-
beforeRemote()
在远程方法之前运行。 -
afterRemote()
在远程方法之后运行。
在这两种情况下,有两个参数可以供我们使用:一个与要钩子函数的远程方法匹配的字符串,和一个回调函数。
创建一个远程钩子
这里,您将在review模型中定义一个远程钩子,具体来说是Review.beforeRemote
。
修改common/models/review.js
,并添加以下代码:
common/models/review.js
module.exports = function(Review) {
Review.beforeRemote('create', function(context, user, next) {
context.args.data.date = Date.now();
context.args.data.publisherId = context.req.accessToken.userId;
next();
});
};
在创建Review模型的新实例之前调用此函数。The code:
- 设置publisherId为请求中的userId
- 设置日期为当前日期。