一、咖啡店點(diǎn)評(píng)應(yīng)用
應(yīng)用概述
咖啡店點(diǎn)評(píng)是一個(gè)網(wǎng)站,您可以用來(lái)發(fā)布咖啡店的評(píng)論。

這個(gè)應(yīng)用程序用到了兩個(gè)不同的數(shù)據(jù)源:它會(huì)將評(píng)論者數(shù)據(jù)存儲(chǔ)在MySQL數(shù)據(jù)庫(kù)中,并把咖啡店和評(píng)論數(shù)據(jù)存儲(chǔ)在MongoDB數(shù)據(jù)庫(kù)中。
這個(gè)應(yīng)用有三個(gè)數(shù)據(jù)模型:
- CoffeeShop(這個(gè)模型我們已經(jīng)在上一步中定義好了)
- Review
- Reviewer
它們有如下關(guān)系:
- 一個(gè)CoffeeShop擁有多個(gè)review
- 一個(gè)CoffeeShop擁有多個(gè)reviewer
- 一個(gè)review屬于一個(gè)CoffeeShop
- 一個(gè)review屬于一個(gè)reviewer
- 一個(gè)reviewer擁有多個(gè)review
一般來(lái)說(shuō),用戶可以創(chuàng)建,編輯,刪除和閱讀咖啡店的評(píng)論,并通過(guò)ACLs指定基本規(guī)則和權(quán)限:
- 任何人都可以閱讀評(píng)論,但必須先登錄才能創(chuàng)建,編輯或刪除它們。
- 任何人都可以注冊(cè)為用戶,然后能夠登錄或者注銷。
- 登錄用戶可以創(chuàng)建新的評(píng)論,編輯或刪除自己的評(píng)論,但是他們不能修改一開(kāi)始選擇的咖啡店。
二、創(chuàng)建新的數(shù)據(jù)源
添加一個(gè)新的數(shù)據(jù)源
除了將API連接到上一步創(chuàng)建的MySQL數(shù)據(jù)源之外,現(xiàn)在還需要添加一個(gè)MongoDB數(shù)據(jù)源。
lb datasource
出現(xiàn)提示時(shí),回復(fù)如下:
? Enter the data-source name: mongoDs
? Select the connector for mongoDs: MongoDB (supported by StrongLoop)
接下來(lái)輸入一些數(shù)據(jù)源設(shè)置,如主機(jī),端口,用戶,密碼和數(shù)據(jù)庫(kù)名稱,然后安裝數(shù)據(jù)庫(kù)連接器。
? 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
數(shù)據(jù)庫(kù)連接器可以使用npm自行安裝,數(shù)據(jù)源設(shè)置也可以手動(dòng)添加到server/ datasources.json中。
安裝MongoDB連接器:
npm install --save loopback-connector-mongodb
配置數(shù)據(jù)源
在server/datasources.json中配置新的數(shù)據(jù)源。
server/datasources.json
...
"mongoDs": {
"name": "mongoDs",
"connector": "mongodb",
"host": "demo.strongloop.com",
"port": 27017,
"database": "getting_started_intermediate",
"username": "demo",
"password": "L00pBack"
}
三、添加新的數(shù)據(jù)模型
定義Review數(shù)據(jù)模型
輸入:
lb model
出現(xiàn)提示時(shí),輸入或選擇以下內(nèi)容:
- 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.
然后根據(jù)提示加入以下屬性。
<table>
<tr>
<th>Property name</th>
<th>Property type</th>
<th>Required?</th>
</tr>
<tr>
<td> date </td>
<td> date </td>
<td> y </td>
</tr>
<tr>
<td> rating </td>
<td> number </td>
<td> n </td>
</tr>
<tr>
<td> comments </td>
<td> string </td>
<td> y </td>
</tr>
</table>
定義Reviewer數(shù)據(jù)模型
輸入:
lb model
出現(xiàn)提示時(shí),輸入或選擇以下內(nèi)容:
- Model name: Reviewer
- Data source: mongoDs (mongodb)
- Base class: 選擇User.
- Expose Reviewer via the REST API?: 選擇默認(rèn)選項(xiàng),yes
- Custom plural form (used to build REST URL): 選擇默認(rèn)選項(xiàng),yes
接下來(lái)不需要給Reviewer添加任何屬性,它們都是從基本的用戶模型繼承下來(lái)的。
更新啟動(dòng)腳本,添加一些原始數(shù)據(jù)
在啟動(dòng)腳本server/boot/create-sample-models.js中添加一些代碼,這個(gè)啟動(dòng)腳本有如下幾個(gè)功能:
- createCoffeeShops()為CoffeeShop模型創(chuàng)建一個(gè)MySQL表,并將數(shù)據(jù)添加到表中。
- createReviewers()使用自動(dòng)遷移在MongoDB中創(chuàng)建Reviewer數(shù)據(jù)結(jié)構(gòu),并向其添加數(shù)據(jù)。
- createReviews()使用自動(dòng)遷移在MongoDB中創(chuàng)建評(píng)論數(shù)據(jù)結(jié)構(gòu),并向其添加數(shù)據(jù)。
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: 'foo@bar.com',
password: 'foobar'
}, {
email: 'john@doe.com',
password: 'johndoe'
}, {
email: 'jane@doe.com',
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);
});
}
};
四、定義模型的關(guān)系
介紹
LoopBack支持許多不同類型的模型關(guān)系:BelongsTo, HasMany, HasManyThrough, and HasAndBelongsToMany等等。
在“咖啡店評(píng)論”應(yīng)用程序中,有以下幾種關(guān)系:
- 一個(gè)CoffeeShop擁有多個(gè)review
- 一個(gè)CoffeeShop擁有多個(gè)reviewer
- 一個(gè)review屬于一個(gè)CoffeeShop
- 一個(gè)review屬于一個(gè)reviewer
- 一個(gè)reviewer擁有多個(gè)review
定義關(guān)系
現(xiàn)在,我們將使用lb relation來(lái)定義這些模型之間的關(guān)系。
一個(gè)CoffeeShop擁有多個(gè)review,沒(méi)有中間模型和外鍵。
? 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
一個(gè)CoffeeShop擁有多個(gè)reviewer,沒(méi)有中間模型和外鍵
? 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
一個(gè)review屬于一個(gè)CoffeeShop,沒(méi)有外鍵。
? 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
一個(gè)review屬于一個(gè)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
一個(gè)reviewer擁有多個(gè)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模型文件
現(xiàn)在,查看common/models/review.json。你應(yīng)該會(huì)看到這些:
common/models/review.json
...
"relations": {
"coffeeShop": {
"type": "belongsTo",
"model": "CoffeeShop",
"foreignKey": ""
},
"reviewer": {
"type": "belongsTo",
"model": "Reviewer",
"foreignKey": "publisherId"
}
},
...
同樣,其他的json文件中應(yīng)該有如下代碼:
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": ""
}
},
...
五、定義權(quán)限控制
權(quán)限控制簡(jiǎn)介
loopback應(yīng)用通過(guò)模型訪問(wèn)數(shù)據(jù),因此控制對(duì)數(shù)據(jù)的訪問(wèn)意味著對(duì)模型進(jìn)行權(quán)限的控制:也就是說(shuō),指定什么角色可以在模型上執(zhí)行讀取和寫(xiě)入數(shù)據(jù)的方法。loopback權(quán)限控制由權(quán)限控制列表或ACL決定。
接下來(lái),我們將為Review模型設(shè)置權(quán)限控制。
權(quán)限控制應(yīng)執(zhí)行以下規(guī)則:
- 任何人都可以閱讀評(píng)論。但是創(chuàng)建、編輯和刪除的操作必須在登錄之后才有權(quán)限。
- 任何人都可以注冊(cè)為用戶,可以登錄和登出。
- 登錄用戶可以創(chuàng)建新的評(píng)論,編輯或刪除自己的評(píng)論。然而,他們不能修改咖啡店的評(píng)論。
定義權(quán)限控制
這次我們使用lb的acl子命令。
$ lb acl
首先,拒絕所有人操作所有接口,這通常是定義ACL的起點(diǎn),因?yàn)槟梢赃x擇性地允許特定操作的訪問(wèn)。
? 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
現(xiàn)在允許所有人對(duì)reviews進(jìn)行讀操作
? 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
允許通過(guò)身份驗(yàn)證的用戶對(duì)coffeeshops進(jìn)行讀操作,也就是說(shuō),已登錄的用戶可以瀏覽所有咖啡店。
? 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
允許經(jīng)過(guò)身份驗(yàn)證的用戶對(duì)reviews進(jìn)行寫(xiě)操作,也就是說(shuō),已登錄的用戶可以添加一條評(píng)論。
? 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的作者有權(quán)限(其“所有者”)對(duì)其進(jìn)行任何更改。
$ 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文件
完成上述步驟,此時(shí)的common/models/review.json中的ACL部分應(yīng)如下所示:
六、定義一個(gè)遠(yuǎn)程鉤子
遠(yuǎn)程鉤子介紹
遠(yuǎn)程鉤子(remote hook)是一個(gè)在遠(yuǎn)程方法(自定義遠(yuǎn)程方法或內(nèi)置CRUD方法)之前或之后執(zhí)行的功能。
在這個(gè)例子中,我們將定義一個(gè)遠(yuǎn)程鉤子,每當(dāng)在Review模型上調(diào)用create()方法時(shí)(在創(chuàng)建新的評(píng)論時(shí)),它將被調(diào)用。
您可以定義兩種遠(yuǎn)程鉤子:
-
beforeRemote()在遠(yuǎn)程方法之前運(yùn)行。 -
afterRemote()在遠(yuǎn)程方法之后運(yùn)行。
在這兩種情況下,有兩個(gè)參數(shù)可以供我們使用:一個(gè)與要鉤子函數(shù)的遠(yuǎn)程方法匹配的字符串,和一個(gè)回調(diào)函數(shù)。
創(chuàng)建一個(gè)遠(yuǎn)程鉤子
這里,您將在review模型中定義一個(gè)遠(yuǎn)程鉤子,具體來(lái)說(shuō)是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();
});
};
在創(chuàng)建Review模型的新實(shí)例之前調(diào)用此函數(shù)。The code:
- 設(shè)置publisherId為請(qǐng)求中的userId
- 設(shè)置日期為當(dāng)前日期。