十年网站开发经验 + 多家企业客户 + 靠谱的建站团队
量身定制 + 运营维护+专业推广+无忧售后,网站问题一站解决
本篇内容介绍了“MEAN怎么安装配置”的有关知识,在实际案例的操作过程中,不少人都会遇到这样的困境,接下来就让小编带领大家学习一下如何处理这些情况吧!希望大家仔细阅读,能够学有所成!
创新互联坚持“要么做到,要么别承诺”的工作理念,服务领域包括:成都网站制作、成都网站设计、企业官网、英文网站、手机端网站、网站推广等服务,满足客户于互联网时代的商河网站设计、移动媒体设计的需求,帮助企业找到有效的互联网解决方案。努力成为您成熟可靠的网络建设合作伙伴!
首先定义一些小型配置库。文件名:test/config/test_config.js
module.exports = { url : 'http://localhost:8000/api/v1.0' }
服务器运行端口是 localhost 8000 ,对于初始的测试来说非常适合。之后,如果改变产品系统的位置或者端口号,只需要简单地修改这个文件就可以。为了良好地测试,首先应该建立 1 个好的测试环境,这点可以通过下面的代码保证。首先,连接到数据库。
文件名: 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,这么做可以了解数据库状态。
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,随后删点所有测试应用程序中的用户。
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); }); }
下一步,关闭数据库。
function closeDB(callback) { reader_test_db.close(); }
最终,调用 async.series 来保证所有函数都按次序运行。
async.series([connectDB, dropUserCollection, dropUserFeedEntryCollection, dropUserFeedEntryCollection, getApplication, deleteTestAccounts, closeDB]);
Frisby 在初期就被建立,这里将使用它定义测试用例,如下:
文件名:test/createaccountserror_spec.js
TU1_FN = "Test"; TU1_LN = "User1"; TU1_EMAIL = "testuser1@example.com"; TU1_PW = "testUser123"; TU_EMAIL_REGEX = 'testuser*'; SP_APP_NAME = 'Reader Test'; var frisby = require('frisby'); var tc = require('./config/test_config');
下面代码将从 enroll route 开始。这个用例故意丢掉了 first name 字段,因此获得 1 个 400 与 1 个 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()
下面用例将测试不包含小写字母,这同样会导致 Stormpath 返回错误,以及返回400 状态。
下面将测试一个无效邮箱地址。因此,期望返回的是未发现 @ 标志,以及 emali地址缺少域名,同时也会获得 1 个 400 状态。
文件名: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()
下面着眼一些可以运行的例子,首先需要定义 3 个用户。
文件名:test/createaccountsspec.js
TEST_USERS = [{'fn' : 'Test', 'ln' : 'User1', 'email' : 'testuser1@example.com', 'pwd' : 'testUser123'}, {'fn' : 'Test', 'ln' : 'User2', 'email' : 'testuser2@example.com', 'pwd' : 'testUser123'}, {'fn' : 'Test', 'ln' : 'User3', 'email' : 'testuser3@example.com', 'pwd' : 'testUser123'}] SP_APP_NAME = 'Reader Test'; var frisby = require('frisby'); var tc = require('./config/test_config');
下面用例将发送 1 个包含上文已定义 3 个用户的数组,当然期望获得代表成功的 201 状态。返回的 JSON document 将展示已建立的用户对象,因此这里可以检查测试数据匹配与否。
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() });
下一步将测试重复用户。下例将验证这个用户注册的 email 地址已经被使用。
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()
这里存在一个重要问题,无法知道 Stormpath 会优先返回哪个 API key。因此,这里需要建立一个动态文件。随后可以使用这个对文件来验证测试用例——用户身份验证组件。
文件名称: /tmp/readerTestCreds.js
TEST_USERS = [{ "_id":"54ad6c3ae764de42070b27b1", "email":"testuser1@example.com", "firstName":"Test", "lastName":"User1", "sp_api_key_id":”", "sp_api_key_secret":” ” }, { "_id":"54ad6c3be764de42070b27b2”, "email":"testuser2@example.com", "firstName":"Test", "lastName":"User2”, "sp_api_key_id":” ", "sp_api_key_secret":” ” }]; module.exports = TEST_USERS;
为了建立上面这个临时文件,这里需要连接 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]);
着眼下面代码,上文建立的临时文件在第一行就会被使用。同时,有多个 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';
首先,一些用户会被建立,当然他们并没有订阅任何 feeds。下面代码将测试 feeds 的订阅。请注意,这里同样需要进行身份验证,通过使用 .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); }
下面用例将为第一个测试用户订阅 Dilbert feed 。
这个用例将尝试为用户 feed 重复订阅。
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); }
下一步,将为测试用户添加一个新的 feed,返回的结果应该是用户当下已经订阅了 2 个 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 个测试用户来订阅 1 个 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 代码之前,首先需要定义一些实用工具库。首先,需求定义应用程序如何连接到数据库。将这个信息写入一个独立的文件允许应用程序灵活地添加新数据库 URL,以应对开发或者生产系统。
文件名:config/db.js
如果期望打开数据库验证,这里需要将信息存入 1 个文件,如下文代码所示。出于多个原因,这个文件不应该被置入源代码控制。
文件名称:config/security.js
module.exports = { stormpath_secret_key : ‘YOUR STORMPATH APPLICATION KEY’; }
Stormpath API 和 Secret keys 应该被保存到属性文件,如下文代码所示,同事还需要严加注意。
文件名:config/stormpath_apikey.properties
apiKey.id = YOUR STORMPATH API KEY ID apiKey.secret = YOUR STORMPATH API KEY SECRET
##Express.js 简述
在 Express.js 中会建立应用程序(APP)。这个应用程序会监听制定的端口来响应 HTTP 请求。当请求涌入,它们会被传输到 1 个中间件链。中间件链中的每个 link 都会被给予 1 个请求和 1 个响应对象用以存储结果。link 分为两种类型,工作或者传递到下一个 link 。这里会通过 app.use() 来添加新的中间件。主中间件被称为「router(路由器)」,它会监听 URL,并将 URL/ 动作传递到 1 个指定的处理函数。 ##建立应用程序
现在开始聚焦应用程序代码,鉴于可以在独立文件中为不同的 routes 嵌入处理器,所以应用程序的体积非常小。
文件名:server.js
在 chain 中末尾定义中间件来处理坏 URLs。
现在,应用程序就会监听 8000 端口。
在控制台将消息打印给用户。
console.log('Magic happens on port ' + port); exports = module.exports = app;
##定义 Mongoose 数据模型
这里会使用 Mongoose 将 Node.js 上的对象映射成 MongoDB 文档。如上文所述,这里将建立 4 个 collections:
Feed collection。
Feed entry collection。
User collection。
User feed-entry-mapping collection。
下一步,将为 4 个 collections 定义 schema。首先,从 user schema 开始。注意,这里同样可以格式化数据,比如讲字母都转换成小写,使用 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 需要哪些索引。当索引不存在于 MongoDB 数据库中时,Mongoose 将会负责索引的建立。唯一性约束保障将去除重复出现的可能。「email : 1」 将以升序的方式维护地址,而「email : -1」则是降序。
在其他 3 个 collections 上重复这个步骤。
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' } );
下面是复合索引实例,每个索引都以升序维护。
userFeedEntrySchema.index({userID : 1, feedID : 1, feedEntryID : 1, read : 1}); var UserFeedEntryModel = mongoose.model('UserFeedEntry', userFeedEntrySchema );
每个用于 GET、POST、PUT 和 DELETE 的请求需要拥有 1 个正确的内容类型,也就是 application/json。然后下一个 link 会被调用。
下一步需要为每个 URL/verb 定义处理器。参考资料部分附上了所有代码,下面只是代码片段。在这些代码中,Stormpath 带来的便捷一览无余。此外,这里定义的是 /api/v1.0 ,举个例子,这里客户端可以调用的是 /api/v1.0/user/enroll。如果使用 /api/v2.0,/api/v2.0 则可以被使用,当然向下兼容。
要启动服务器和运行测试,这里需要遵循几个步骤。
保证 MongoDB 实例运行,mongod。
安装 Node 库,npm install。
开启 REST API 服务器,node server.js。
运行测试用例: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。
“MEAN怎么安装配置”的内容就介绍到这里了,感谢大家的阅读。如果想了解更多行业相关的知识可以关注创新互联网站,小编将为大家输出更多高质量的实用文章!