成人国产在线小视频_日韩寡妇人妻调教在线播放_色成人www永久在线观看_2018国产精品久久_亚洲欧美高清在线30p_亚洲少妇综合一区_黄色在线播放国产_亚洲另类技巧小说校园_国产主播xx日韩_a级毛片在线免费

資訊專欄INFORMATION COLUMN

MEAN實(shí)踐——LAMP的新時(shí)代替代方案(下)

JasonZhang / 2984人閱讀

摘要:下一步,關(guān)閉數(shù)據(jù)庫。出于多個(gè)原因,這個(gè)文件不應(yīng)該被置入源代碼控制。主中間件被稱為路由器,它會(huì)監(jiān)聽,并將動(dòng)作傳遞到個(gè)指定的處理函數(shù)。當(dāng)索引不存在于數(shù)據(jù)庫中時(shí),將會(huì)負(fù)責(zé)索引的建立。唯一性約束保障將去除重復(fù)出現(xiàn)的可能。

在本系列文章的第一部分旨在介紹一些應(yīng)用程序的基礎(chǔ)技術(shù)細(xì)節(jié)和如何進(jìn)行數(shù)據(jù)建模,而這個(gè)部分文章將著手建立驗(yàn)證應(yīng)用程序行為的測試,并會(huì)指出如何啟動(dòng)和運(yùn)行應(yīng)用程序。

首先,編寫測試

首先定義一些小型配置庫。文件名:test/config/test_config.js

module.exports = {
    url : "http://localhost:8000/api/v1.0"
}

服務(wù)器運(yùn)行端口是 localhost 8000 ,對于初始的測試來說非常適合。之后,如果改變產(chǎn)品系統(tǒng)的位置或者端口號,只需要簡單地修改這個(gè)文件就可以。為了良好地測試,首先應(yīng)該建立 1 個(gè)好的測試環(huán)境,這點(diǎn)可以通過下面的代碼保證。首先,連接到數(shù)據(jù)庫。

文件名: est/setup_tests.js 。

function connectDB(callback) {
    mongoClient.connect(dbConfig.testDBURL, function(err, db) {
        assert.equal(null, err);
        reader_test_db = db;
        console.log("Connected correctly to server");
        callback(0);
    });
}

下一步,drop user collection,這么做可以了解數(shù)據(jù)庫狀態(tài)。

function dropUserCollection(callback) {
        console.log("dropUserCollection");
        user = reader_test_db.collection("user");
        if (undefined != user) {
            user.drop(function(err, reply) {
                console.log("user collection dropped");
                callback(0);
            });
        } else {
            callback(0);
        }
    },

下一步,drop user feed entry collection。

function dropUserFeedEntryCollection(callback) {
    console.log("dropUserFeedEntryCollection");
    user_feed_entry = reader_test_db.collection("user_feed_entry");
    if (undefined != user_feed_entry) {
        user_feed_entry.drop(function(err, reply) {
            console.log("user_feed_entry collection dropped");
            callback(0);
        });
    } else {
        callback(0);
    }
}

下一步,連接到Stormpath,隨后刪點(diǎn)所有測試應(yīng)用程序中的用戶。

function getApplication(callback) {
    console.log("getApplication");
    client.getApplications({
        name: SP_APP_NAME
    }, function(err, applications) {
        console.log(applications);
        if (err) {
            log("Error in getApplications");
            throw err;
        }
        app = applications.items[0];
        callback(0);
    });
},
function deleteTestAccounts(callback) {
    app.getAccounts({
        email: TU_EMAIL_REGEX
    }, function(err, accounts) {
        if (err) throw err;
        accounts.items.forEach(function deleteAccount(account) {
            account.delete(function deleteError(err) {
                if (err) throw err;
            });
        });
        callback(0);
    });
}

下一步,關(guān)閉數(shù)據(jù)庫。

function closeDB(callback) {
    reader_test_db.close();
}

最終,調(diào)用 async.series 來保證所有函數(shù)都按次序運(yùn)行。

async.series([connectDB, dropUserCollection,    dropUserFeedEntryCollection, dropUserFeedEntryCollection, getApplication, deleteTestAccounts, closeDB]);

Frisby 在初期就被建立,這里將使用它定義測試用例,如下:

文件名:test/createaccountserror_spec.js

TU1_FN = "Test";
TU1_LN = "User1";
TU1_EMAIL = "[email protected]";
TU1_PW = "testUser123";
TU_EMAIL_REGEX = "testuser*";
SP_APP_NAME = "Reader Test";

var frisby = require("frisby");
var tc = require("./config/test_config");

下面代碼將從 enroll route 開始。這個(gè)用例故意丟掉了 first name 字段,因此獲得 1 個(gè) 400 與 1 個(gè) JSON error(顯示 first name 未定義)返回,下面就 toss that frisby:

frisby.create("POST missing firstName")
.post(tc.url + "/user/enroll",
      { "lastName" : TU1_LN,
        "email" : TU1_EMAIL,
        "password" : TU1_PW })
.expectStatus(400)
.expectHeader("Content-Type", "application/json; charset=utf-8")
.expectJSON({"error" : "Undefined First Name"})
.toss()

下面用例將測試不包含小寫字母,這同樣會(huì)導(dǎo)致 Stormpath 返回錯(cuò)誤,以及返回400 狀態(tài)。


下面將測試一個(gè)無效郵箱地址。因此,期望返回的是未發(fā)現(xiàn) @ 標(biāo)志,以及 emali地址缺少域名,同時(shí)也會(huì)獲得 1 個(gè) 400 狀態(tài)。

文件名:test/createaccountsspec.js

frisby.create("POST invalid email address")
.post(tc.url + "/user/enroll",
      { "firstName" : TU1_FN,
        "lastName" : TU1_LN,
        "email" : "invalid.email",
        "password" : "testUser" })
.expectStatus(400)
.expectHeader("Content-Type", "application/json; charset=utf-8")
.expectJSONTypes({"error" : String})
.toss()

下面著眼一些可以運(yùn)行的例子,首先需要定義 3 個(gè)用戶。

文件名:test/createaccountsspec.js

TEST_USERS = [{"fn" : "Test", "ln" : "User1",
         "email" : "[email protected]", "pwd" : "testUser123"},
          {"fn" : "Test", "ln" : "User2",
           "email" : "[email protected]", "pwd" : "testUser123"},
          {"fn" : "Test", "ln" : "User3",
           "email" : "[email protected]", "pwd" : "testUser123"}]

SP_APP_NAME = "Reader Test";

var frisby = require("frisby");
var tc = require("./config/test_config");

下面用例將發(fā)送 1 個(gè)包含上文已定義 3 個(gè)用戶的數(shù)組,當(dāng)然期望獲得代表成功的 201 狀態(tài)。返回的 JSON document 將展示已建立的用戶對象,因此這里可以檢查測試數(shù)據(jù)匹配與否。

TEST_USERS.forEach(function createUser(user, index, array) {
    frisby.create("POST enroll user " + user.email)
    .post(tc.url + "/user/enroll",
          { "firstName" : user.fn,
            "lastName" : user.ln,
            "email" : user.email,
            "password" : user.pwd })
    .expectStatus(201)
    .expectHeader("Content-Type", "application/json; charset=utf-8")
    .expectJSON({ "firstName" : user.fn,
                  "lastName" : user.ln,
                  "email" : user.email })
    .toss()
});

下一步將測試重復(fù)用戶。下例將驗(yàn)證這個(gè)用戶注冊的 email 地址已經(jīng)被使用。

frisby.create("POST enroll duplicate user ")
    .post(tc.url + "/user/enroll",
      { "firstName" : TEST_USERS[0].fn,
        "lastName" : TEST_USERS[0].ln,
        "email" : TEST_USERS[0].email,
        "password" : TEST_USERS[0].pwd })
.expectStatus(400)
.expectHeader("Content-Type", "application/json; charset=utf-8")
.expectJSON({"error" : "Account with that email already exists.  Please choose another email."})
.toss()

這里存在一個(gè)重要問題,無法知道 Stormpath 會(huì)優(yōu)先返回哪個(gè) API key。因此,這里需要建立一個(gè)動(dòng)態(tài)文件。隨后可以使用這個(gè)對文件來驗(yàn)證測試用例——用戶身份驗(yàn)證組件。

文件名稱: /tmp/readerTestCreds.js

TEST_USERS = 
[{    "_id":"54ad6c3ae764de42070b27b1",
"email":"[email protected]",
"firstName":"Test",
"lastName":"User1",
"sp_api_key_id":”",
"sp_api_key_secret":””
},
{    "_id":"54ad6c3be764de42070b27b2”,
    "email":"[email protected]",
    "firstName":"Test",
    "lastName":"User2”,
    "sp_api_key_id":”",
    "sp_api_key_secret":””
}];
module.exports = TEST_USERS;

為了建立上面這個(gè)臨時(shí)文件,這里需要連接 MongoDB 從而檢索用戶信息。代碼如下:

文件名:tests/writeCreds.js

TU_EMAIL_REGEX = new RegExp("^testuser*");
SP_APP_NAME = "Reader Test";
TEST_CREDS_TMP_FILE = "/tmp/readerTestCreds.js";

var async = require("async");
var dbConfig = require("./config/db.js");
var mongodb = require("mongodb");
assert = require("assert");

var mongoClient = mongodb.MongoClient
var reader_test_db = null;
var users_array = null;

function connectDB(callback) {
     mongoClient.connect(dbConfig.testDBURL, function(err, db) {
     assert.equal(null, err);
     reader_test_db = db;
     callback(null);
     });
 }

 function lookupUserKeys(callback) {
     console.log("lookupUserKeys");
     user_coll = reader_test_db.collection("user");
     user_coll.find({email :    TU_EMAIL_REGEX}).toArray(function(err, users) {
         users_array = users;
         callback(null);
     });
 }

function writeCreds(callback) {
     var fs = require("fs");
     fs.writeFileSync(TEST_CREDS_TMP_FILE, "TEST_USERS = ");
     fs.appendFileSync(TEST_CREDS_TMP_FILE,   JSON.stringify(users_array));
     fs.appendFileSync(TEST_CREDS_TMP_FILE, "; module.exports =  TEST_USERS;");
     callback(0);
 }

 function closeDB(callback) {
     reader_test_db.close();
 }

 async.series([connectDB, lookupUserKeys, writeCreds, closeDB]);

著眼下面代碼,上文建立的臨時(shí)文件在第一行就會(huì)被使用。同時(shí),有多個(gè) feeds 被建立,比如 Dilbert 和 the Eater Blog 。

文件名:tests/feed_spec.js

TEST_USERS = require("/tmp/readerTestCreds.js");

var frisby = require("frisby");
var tc = require("./config/test_config");
var async = require("async");
var dbConfig = require("./config/db.js");

var dilbertFeedURL = "http://feeds.feedburner.com/DilbertDailyStrip";
var nycEaterFeedURL = "http://feeds.feedburner.com/eater/nyc";

首先,一些用戶會(huì)被建立,當(dāng)然他們并沒有訂閱任何 feeds。下面代碼將測試 feeds 的訂閱。請注意,這里同樣需要進(jìn)行身份驗(yàn)證,通過使用 .auth 和 Stormpath API keys 完成。

function addEmptyFeedListTest(callback) {
     var user = TEST_USERS[0];
     frisby.create("GET empty feed list for user " + user.email)
             .get(tc.url + "/feeds")
         .auth(user.sp_api_key_id, user.sp_api_key_secret)
         .expectStatus(200)
         .expectHeader("Content-Type", "application/json; charset=utf-8")
         .expectJSON({feeds : []})
         .toss()
         callback(null);
}

下面用例將為第一個(gè)測試用戶訂閱 Dilbert feed 。


這個(gè)用例將嘗試為用戶 feed 重復(fù)訂閱。

function subDuplicateFeed(callback) {
 var user = TEST_USERS[0];
 frisby.create("PUT Add duplicate feed sub for user " + user.email)
         .put(tc.url + "/feeds/subscribe",
              {"feedURL" : dilbertFeedURL})
         .auth(user.sp_api_key_id, user.sp_api_key_secret)
         .expectStatus(201)
         .expectHeader("Content-Type", "application/json; charset=utf-8")
         .expectJSONLength("user.subs", 1)
         .toss()
 callback(null);
}

下一步,將為測試用戶添加一個(gè)新的 feed,返回的結(jié)果應(yīng)該是用戶當(dāng)下已經(jīng)訂閱了 2 個(gè) feed。

function subSecondFeed(callback) {
 var user = TEST_USERS[0];
 frisby.create("PUT Add second feed sub for user " + user.email)
         .put(tc.url + "/feeds/subscribe",
              {"feedURL" : nycEaterFeedURL})
         .auth(user.sp_api_key_id, user.sp_api_key_secret)
         .expectStatus(201)
         .expectHeader("Content-Type", "application/json; charset=utf-8")
         .expectJSONLength("user.subs", 2)
         .toss()
 callback(null);
 }

下一步,將使用第 2 個(gè)測試用戶來訂閱 1 個(gè) feed 。

function subOneFeedSecondUser(callback) {
     var user = TEST_USERS[1];
 frisby.create("PUT Add one feed sub for second user " + user.email)
         .put(tc.url + "/feeds/subscribe",
              {"feedURL" : nycEaterFeedURL})
         .auth(user.sp_api_key_id, user.sp_api_key_secret)
         .expectStatus(201)
         .expectHeader("Content-Type", "application/json; charset=utf-8")
         .expectJSONLength("user.subs", 1)
         .toss()
 callback(null);
}

async.series([addEmptyFeedListTest, subOneFeed, subDuplicateFeed, subSecondFeed, subOneFeedSecondUser]);
REST API

在開始編寫 REST API 代碼之前,首先需要定義一些實(shí)用工具庫。首先,需求定義應(yīng)用程序如何連接到數(shù)據(jù)庫。將這個(gè)信息寫入一個(gè)獨(dú)立的文件允許應(yīng)用程序靈活地添加新數(shù)據(jù)庫 URL,以應(yīng)對開發(fā)或者生產(chǎn)系統(tǒng)。

文件名:config/db.js


如果期望打開數(shù)據(jù)庫驗(yàn)證,這里需要將信息存入 1 個(gè)文件,如下文代碼所示。出于多個(gè)原因,這個(gè)文件不應(yīng)該被置入源代碼控制。

文件名稱:config/security.js

module.exports = {
 stormpath_secret_key : ‘YOUR STORMPATH APPLICATION KEY’;
}

Stormpath API 和 Secret keys 應(yīng)該被保存到屬性文件,如下文代碼所示,同事還需要嚴(yán)加注意。

文件名:config/stormpath_apikey.properties

apiKey.id = YOUR STORMPATH API KEY ID
apiKey.secret = YOUR STORMPATH API KEY SECRET
Express.js 簡述

在 Express.js 中會(huì)建立應(yīng)用程序(APP)。這個(gè)應(yīng)用程序會(huì)監(jiān)聽制定的端口來響應(yīng) HTTP 請求。當(dāng)請求涌入,它們會(huì)被傳輸?shù)?1 個(gè)中間件鏈。中間件鏈中的每個(gè) link 都會(huì)被給予 1 個(gè)請求和 1 個(gè)響應(yīng)對象用以存儲(chǔ)結(jié)果。link 分為兩種類型,工作或者傳遞到下一個(gè) link 。這里會(huì)通過 app.use() 來添加新的中間件。主中間件被稱為「router(路由器)」,它會(huì)監(jiān)聽 URL,并將 URL/ 動(dòng)作傳遞到 1 個(gè)指定的處理函數(shù)。

建立應(yīng)用程序

現(xiàn)在開始聚焦應(yīng)用程序代碼,鑒于可以在獨(dú)立文件中為不同的 routes 嵌入處理器,所以應(yīng)用程序的體積非常小。

文件名:server.js

在 chain 中末尾定義中間件來處理壞 URLs。


現(xiàn)在,應(yīng)用程序就會(huì)監(jiān)聽 8000 端口。


在控制臺將消息打印給用戶。

console.log("Magic happens on port " + port);

exports = module.exports = app;
定義 Mongoose 數(shù)據(jù)模型

這里會(huì)使用 Mongoose 將 Node.js 上的對象映射成 MongoDB 文檔。如上文所述,這里將建立 4 個(gè) collections:

Feed collection。

Feed entry collection。

User collection。

User feed-entry-mapping collection。

下一步,將為 4 個(gè) collections 定義 schema。首先,從 user schema 開始。注意,這里同樣可以格式化數(shù)據(jù),比如講字母都轉(zhuǎn)換成小寫,使用 trim 消除首/末空格。

文件名:app/routes.js

var userSchema = new mongoose.Schema({
         active: Boolean,
     email: { type: String, trim: true, lowercase: true },
     firstName: { type: String, trim: true },
     lastName: { type: String, trim: true },
     sp_api_key_id: { type: String, trim: true },
     sp_api_key_secret: { type: String, trim: true },
     subs: { type: [mongoose.Schema.Types.ObjectId], default: [] },
     created: { type: Date, default: Date.now },
     lastLogin: { type: Date, default: Date.now },
 },
 { collection: "user" }
);

下面代碼將告訴 Mongoose 需要哪些索引。當(dāng)索引不存在于 MongoDB 數(shù)據(jù)庫中時(shí),Mongoose 將會(huì)負(fù)責(zé)索引的建立。唯一性約束保障將去除重復(fù)出現(xiàn)的可能?!竐mail : 1」 將以升序的方式維護(hù)地址,而「email : -1」則是降序。

在其他 3 個(gè) collections 上重復(fù)這個(gè)步驟。

var UserModel = mongoose.model( "User", userSchema );

var feedSchema = new mongoose.Schema({
     feedURL: { type: String, trim:true },
     link: { type: String, trim:true },
     description: { type: String, trim:true },
     state: { type: String, trim:true, lowercase:true, default: "new" },
     createdDate: { type: Date, default: Date.now },
     modifiedDate: { type: Date, default: Date.now },
 },
 { collection: "feed" }
);

feedSchema.index({feedURL : 1}, {unique:true});
feedSchema.index({link : 1}, {unique:true, sparse:true});

var FeedModel = mongoose.model( "Feed", feedSchema );

var feedEntrySchema = new mongoose.Schema({
     description: { type: String, trim:true },
     title: { type: String, trim:true },
     summary: { type: String, trim:true },
     entryID: { type: String, trim:true },
     publishedDate: { type: Date },
     link: { type: String, trim:true  },
     feedID: { type: mongoose.Schema.Types.ObjectId },
     state: { type: String, trim:true, lowercase:true, default: "new" },
     created: { type: Date, default: Date.now },
 },
 { collection: "feedEntry" }
);

feedEntrySchema.index({entryID : 1});
feedEntrySchema.index({feedID : 1});

var FeedEntryModel = mongoose.model( "FeedEntry", feedEntrySchema     );

var userFeedEntrySchema = new mongoose.Schema({
     userID: { type: mongoose.Schema.Types.ObjectId },
     feedEntryID: { type: mongoose.Schema.Types.ObjectId },
     feedID: { type: mongoose.Schema.Types.ObjectId },
     read : { type: Boolean, default: false },
 },
 { collection: "userFeedEntry" }
 );

下面是復(fù)合索引實(shí)例,每個(gè)索引都以升序維護(hù)。

userFeedEntrySchema.index({userID : 1, feedID : 1, feedEntryID : 1, read : 1});

var UserFeedEntryModel = mongoose.model("UserFeedEntry", userFeedEntrySchema );

每個(gè)用于 GET、POST、PUT 和 DELETE 的請求需要擁有 1 個(gè)正確的內(nèi)容類型,也就是 application/json。然后下一個(gè) link 會(huì)被調(diào)用。


下一步需要為每個(gè) URL/verb 定義處理器。參考資料部分附上了所有代碼,下面只是代碼片段。在這些代碼中,Stormpath 帶來的便捷一覽無余。此外,這里定義的是 /api/v1.0 ,舉個(gè)例子,這里客戶端可以調(diào)用的是 /api/v1.0/user/enroll。如果使用 /api/v2.0,/api/v2.0 則可以被使用,當(dāng)然向下兼容。

啟動(dòng)服務(wù)器并運(yùn)行測試

要啟動(dòng)服務(wù)器和運(yùn)行測試,這里需要遵循幾個(gè)步驟。

保證 MongoDB 實(shí)例運(yùn)行,mongod。

安裝 Node 庫,npm install。

開啟 REST API 服務(wù)器,node server.js。

運(yùn)行測試用例:node setup_tests.js;jasmine-node create_accounts_error_spec.js;jasmine-node create_accounts_spec.js;node write_creds.js;jasmine-node feed_spec.js

原文鏈接:Building your first application with MongoDB: Creating a REST API using the MEAN Stack - Part 2

參考文獻(xiàn):

HTTP status code definitions

Chad Tindel’s Github Repository

M101JS: MongoDB for Node.js Developers

Data Models

Data Modeling Considerations for MongoDB Applications

本文系 OneAPM 工程師編譯整理。想閱讀更多技術(shù)文章,請?jiān)L問 OneAPM 官方博客。

文章版權(quán)歸作者所有,未經(jīng)允許請勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。

轉(zhuǎn)載請注明本文地址:http://systransis.cn/yun/18764.html

相關(guān)文章

  • MEAN實(shí)踐——LAMP時(shí)代替代方案(上)

    摘要:近日,在中,介紹了一個(gè)基于新時(shí)代架構(gòu)的實(shí)踐,和。是的一個(gè)現(xiàn)代替代者,在九十年代末,曾是應(yīng)用程序的主流構(gòu)建方式。指定了對象的作用范圍,而服務(wù)器則會(huì)通過結(jié)果代碼和有效的進(jìn)行響應(yīng)。代表創(chuàng)建讀取更新和刪除。下圖顯示了使用后的請求和響應(yīng)流。 摘要:90 年代,LAMP 曾風(fēng)靡一時(shí),然而隨著需求的變遷和數(shù)據(jù)流量的激增,LAMP 已不可避免的走下神壇。近日,在 MongoDB Blog 中,Dana...

    singerye 評論0 收藏0

發(fā)表評論

0條評論

最新活動(dòng)
閱讀需要支付1元查看
<