使用 BrightstarDB、Express 和 Node.js 进行博客滚动
BrightstarDB 如何也能与 Node.js 结合使用来创建一个简单的博客系统
受到这篇 How To Node 文章 中 Node.js 和 mongoDB 用法的启发,本教程旨在展示 BrightstarDB 如何也能与 Node.js 结合使用来创建一个简单的博客系统。
开始之前,请确保已安装以下软件
BrightstarDB
- Node.js
- ASP.NET MVC
Node.js 需要能够访问 BrightstarDB 中的数据,遗憾的是,我们无法在 Node.js 中使用 WCF,因此,首先,我们将创建一个简单的 ASP.NET MVC REST 服务,它将处理与 BrightstarDB 的查询和交互。
如果您只想直接查看代码,可以在文章末尾下载两个项目的源代码。
为 BrightstarDB 创建 MVC REST 接口
对于博客系统,我们的 REST 接口将允许以下命令
首先,创建一个新的 **ASP.NET MVC 3 项目**。对于模板,请选择 **Empty**(空)。
在项目中,**References**(引用)中添加对 NetworkedPlanet.Brightstar.dll 的引用。
向项目中添加一个名为 StoreController.cs 的新 **Controller**(控制器)。它将作为 BrightstarDB 存储的主要接口。我们将使用 BrightstarService 类来创建一个嵌入式客户端,因此我们需要在控制器顶部包含以下 using 语句
using NetworkedPlanet.Brightstar.Client;
在使用嵌入式客户端之前,我们需要一个 BrightstarDB 数据的位置。打开 BrightstarDB 管理应用程序 **Polaris**,并创建一个指向您计算机上某个位置的新嵌入式连接。
从 **Server**(服务器)菜单中,选择 **New Store…**(新建存储…),并将其命名为 ‘Blog’。
这将在我们的 BrightstarDB 数据文件夹中创建一个名为“Blog”的新存储。回到我们的 MVC 项目和 StoreController.cs,我们现在可以创建一个嵌入式客户端,并将其指向我们在 Polaris 中用于创建“Blog
”存储的路径。
在 StoreController.cs 中,添加以下属性,请务必使用 BrightstarDB 数据文件夹的位置,而不是“Blog
”存储文件夹的位置
private IBrightstarService _context;
private IBrightstarService Context
{
get
{
return _context ??
(_context = BrightstarService.GetEmbeddedClient(
"C:\\Projects\\node.js\\BrightstarRest\\BSData"));
}
}
这将创建一个名为 Context
的 BrightstarService 属性,我们将使用它来对我们的“Blog
”存储执行查询和事务。
接下来,我们首先需要能够查询我们的存储,此 REST 方法的路径将如下所示
/store/{storename}?query=<sparql query>
添加以下方法,该方法接受一个 storeName
(存储名称)和一个查询变量
// GET: /Store/{storeName}?query=
[HttpGet, ValidateInput(false)]
public ActionResult Store(string storeName, string query)
{
if (!string.IsNullOrWhiteSpace(query))
{
if (Context.DoesStoreExist(storeName))
{
Stream queryResult = Context.ExecuteQuery(storeName, query);
XDocument results = XDocument.Load(queryResult);
var triples = new List<Dictionary<string, string>>();
foreach (XElement result in results.SparqlResultRows())
{
var columnValues = new Dictionary<string, string>();
foreach (string name in results.GetVariableNames())
{
// Add the column name and its value to the row dictionary
columnValues.Add(name, result.GetColumnValue(name).ToString());
}
triples.Add(columnValues);
}
return new JsonResult()
{
Data = triples,
JsonRequestBehavior = JsonRequestBehavior.AllowGet
};
}
}
return new JsonResult() { Data = Context.DoesStoreExist(storeName),
JsonRequestBehavior = JsonRequestBehavior.AllowGet };
}
上述方法使用 BrightstarService Context 来对我们的命名存储执行 Sparql 查询。结果按行迭代,并将每行的列名及其值添加到 Dictionary(字典)中。List<Dictionary<string,string>>
允许我们使用 ASP.NET 的 JsonResult
类以 JTriples 格式返回结果。
注意,上述方法使用了一个 Sparql 扩展方法 GetVariableNames
,如果您的版本中没有此方法,请将以下代码添加到 StoreController.cs 中,并将上述代码更改为使用下面的私有方法 GetVariableNames
。
private static readonly XNamespace SparqlResultsNamespace = "http://www.w3.org/2005/sparql-results#";
/// <summary>
/// Returns the variable column names in the Sparql result.
/// </summary>
/// <param name="doc">The XDocument containing the result data.</param>
/// <returns>An IEnumerable string of column variable names.</returns>
private static IEnumerable<string> GetVariableNames(XDocument doc)
{
if (doc.Root != null)
{
var head = doc.Root.Element(SparqlResultsNamespace + "head");
if (head != null)
{
return
head.Elements(SparqlResultsNamespace + "variable").Where(
e => (e.Attribute("name") != null)).Select(
e => e.Attribute("name").Value);
}
}
return new string[0];
}
为了支持添加新的博客文章,我们需要添加一个 Insert
(插入)方法。我们的 insert
方法将使用 REST 路径
/store/{storename}/insert?triples=<triples>
在 StoreController.cs 中,添加以下方法
// POST: /Store/{storeName}?triples=
[HttpPost, ValidateInput(false)]
public ActionResult Insert(string storeName, string triples)
{
IJobInfo jobInfo = Context.ExecuteTransaction(storeName,
string.Empty, string.Empty, triples, true);
while (!jobInfo.JobCompletedOk)
{
Thread.Sleep(30);
}
return new JsonResult() { Data = true };
}
上述方法接受 NTriple 数据,并使用 BrightstarService Context 将数据插入到命名存储中。因为我们想在作业完成后返回,所以我们会等到作业状态为 OK 后再返回。当然,我们假设 NTriples 有效,并且作业永远不会失败,但这并非总是如此,因此您需要进行检查。
最后,为了完成 MVC 项目,我们需要在 Global.asax.cs 中添加其他路由。
使用以下内容更新 RegisterRoutes
方法
public static void RegisterRoutes(RouteCollection routes)
{
routes.IgnoreRoute("{resource}.axd/{*pathInfo}");
routes.MapRoute("Store",
"store/{storeName}",
new { controller = "Store", action = "Store" });
routes.MapRoute("Insert",
"store/{storeName}/insert",
new { controller = "Store", action = "Insert" });
routes.MapRoute(
"Default", // Route name
"{controller}/{action}/{id}", // URL with parameters
new { controller = "Home", action = "Index", id = UrlParameter.Optional }
// Parameter defaults
);
}
这将把 StoreController.cs 中的方法映射到前面指定的路由。请务必按 **F5** 运行服务器,并记下服务器使用的**端口号**。
创建 Node.js 博客应用程序
在开始编写 Node.js 应用程序之前,首先应该查看我们将要在 BrightstarDB 中为我们的博客文章存储的三元组。
@prefix np:<http://www.networkedplanet.com/schema/blog/> .
@prefix dc:<http://purl.org/dc/elements/1.1/> .
@prefix rdf:<http://www.w3.org/1999/02/22-rdf-syntax-ns#> .
np:post-id-0 rdf:type np:post .
np:post-id-0 dc:title "post one" .
np:post-id-0 np:body "body one" .
np:post-id-0 dc:date "2012-01-14T10:48:53.280Z" .
在上面的三元组中,我们定义了一个名为“post
”的“type
”(类型),它具有“title
”(标题)、“body
”(正文)和“date
”(日期)。为简单起见,我们将在此帖子中省略存储评论。要开始使用 Node.js 应用程序,以及安装了 Node.js,我们需要安装 Express。为此,请在命令提示符中执行以下操作
npm install express -g
注意:在 Windows 上安装 Node.js 应该会将 node 和 npm 添加到系统路径中,如果未添加,请将以下内容添加到您的 Path 系统环境变量中
C:\Users\*username*\AppData\Roaming\npm;C:\Program Files (x86)\nodejs\;
安装 Express 后,我们现在可以创建一个 Express 入门项目。在命令提示符中,运行以下命令
mkdir blog
cd blog
express -c stylus
npm install -d
这将生成一个使用 stylus CSS 引擎的 Express 应用程序,并通过 npm 下载任何依赖项。要测试应用程序是否成功生成,请在 main app.js 上运行 node。
node app.js
现在您应该可以访问 localhost:3000 并看到默认的 Express 内容。我们将在博客应用程序中创建以下**操作**。它们是
- 显示所有博客文章
- 创建博客文章
- 显示单篇博客文章
为了创建我们的应用程序和前面创建的 BrightstarDB ASP.NET MVC 服务之间的接口,我们将向我们的应用程序添加一个名为 brightstar-server.js 的通信层。
在 /blog 文件夹下创建一个名为“brightstar-server.js”的新文件,并添加以下内容
var util = require('util'),
http = require('http');
BrightstarServer = function (host, port, store) {
this.host = host,
this.port = port,
this.path = "/store/" + store;
};
BrightstarServer.prototype = {
/* Defaults */
host: "localhost",
port: 88,
path: "/store/",
collection: function (item, callback) {
console.log("Fetching all articles...");
var articlesQuery = "select ?_id ?title ?body ?created_at where { ?_id " +
"<http://www.w3.org/1999/02/22-rdf-syntax-ns#type> " +
"<http://www.networkedplanet.com/schema/blog/post> . " +
"?_id <http://purl.org/dc/elements/1.1/title> ?title . " +
"?_id <http://www.networkedplanet.com/schema/blog/body> ?body . " +
"?_id <http://purl.org/dc/elements/1.1/date> ?created_at }",
options = {
host: this.host,
port: this.port,
path: this.path
};
options.path = options.path + "/?query=" + encodeURIComponent(articlesQuery);
var req = http.request(options, function (res) {
res.setEncoding('utf8');
res.on('data', function (chunk) {
var jsonData = JSON.parse(chunk),
i;
for (i = 0; i < jsonData.length; i++) {
jsonData[i]._id = jsonData[i]._id.replace(
/http:\/\/www.networkedplanet.com\/schema\/blog\//i, '');
jsonData[i].created_at = new Date(jsonData[i].created_at);
}
console.log("Returning articles");
callback(null, jsonData);
});
});
req.end();
}
};
exports.BrightstarServer = BrightstarServer;
BrightstarServer
类提供了一个方法集合,它将使用 HTTP 请求联系 BrightstarDB ASP.NET MVC 服务器,执行 Sparql 查询以返回所有博客文章,并处理生成的 JSON。Sparql 查询请求每个博客文章的“_id
”、“title
”、“body
”和“created_at
”属性。返回的 JSON 将类似于以下数据
[{
_id: 'post-id-0',
title: '',
body: '',
created_at: new Date()
}]
接下来,我们将创建一个使用 BrightstarServer
类的 articleprovider
。添加一个名为 articleprovider-brightstar.js 的新文件,并包含以下内容
var BsS = require('./brightstar-server').BrightstarServer;
ArticleProvider = function (host, port, store) {
this.db = new BsS(host, port, store);
};
ArticleProvider.prototype.findAll = function (callback) {
this.db.collection(function (error, articleCollection) {
if (error) callback(error);
else {
callback(null, articleCollection);
}
});
};
ArticleProvider
提供了一个名为 findAll
的方法,该方法使用 BrightstarServer
类来获取所有博客文章。为了让我们的博客应用程序使用我们的 ArticleProvider
,我们需要更新 app.js,以便在调用“/”路由时渲染视图并显示所有博客文章。
使用以下内容更新 app.js,在初始化 ArticleProvider
时确保使用前面 ASP.NET 服务器的端口号;
/**
* Module dependencies.
*/
var express = require('express'), routes = require('./routes');
var ArticleProvider = require('./articleprovider-brightstar').ArticleProvider;
var app = module.exports = express.createServer();
// Configuration
app.configure(function () {
app.set('views', __dirname + '/views');
app.set('view engine', 'jade');
app.use(express.bodyParser());
app.use(express.methodOverride());
app.use(require('stylus').middleware({ src: __dirname + '/public' }));
app.use(app.router);
app.use(express.static(__dirname + '/public'));
});
app.configure('development', function () {
app.use(express.errorHandler({ dumpExceptions: true, showStack: true }));
});
app.configure('production', function () {
app.use(express.errorHandler());
});
// Routes
var articleProvider = new ArticleProvider('localhost', 54543, 'blog');
app.get('/', function (req, res) {
articleProvider.findAll(function (error, docs) {
res.render('index.jade', {
locals: {
title: 'Blog',
articles: docs
}
});
});
});
app.listen(3000);
console.log("Express server listening on port %d in %s mode",
app.address().port, app.settings.env);
我们的应用程序现在正在使用 **BrightstarDB ArticleProvider**,调用其 findAll
方法获取所有博客文章,并将它们传递给一个名为“index.jade”的视图,我们现在将添加该视图。创建一个名为 index.jade 的新文件,并添加以下代码
h1= title
#articles
- each article in articles
div.article
div.created_at= article.created_at
div.title
a(href="https://codeproject.org.cn/blog/"+article._id)!= article.title
div.body= article.body
使用以下 CSS 规则更新 style.styl 文件
body
font-family "Helvetica Neue", "Lucida Grande", "Arial"
font-size 13px
text-align center
text-stroke 1px rgba(255, 255, 255, 0.1)
color #555
h1, h2
margin 0
font-size 22px
color #343434
h1
text-shadow 1px 2px 2px #ddd
font-size 60px
#articles
text-align left
margin-left auto
margin-right auto
width 320px
.article
margin 20px
.created_at
display none
.title
font-weight bold
text-decoration underline
background-color #eee
.body
background-color #ffa
在我们可以测试应用程序是否正常工作并获取博客文章之前,我们需要用一些**测试数据**填充我们的存储。使用 **Polaris**,在“Blog
”存储上运行一个新事务,使用以下测试数据
<http://www.networkedplanet.com/schema/blog/post-id-0>
<http://www.w3.org/1999/02/22-rdf-syntax-ns#type> <http://www.networkedplanet.com/schema/blog/post> .
<http://www.networkedplanet.com/schema/blog/post-id-0>
<http://purl.org/dc/elements/1.1/title> "post one" .
<http://www.networkedplanet.com/schema/blog/post-id-0>
<http://www.networkedplanet.com/schema/blog/body> "body one" .
<http://www.networkedplanet.com/schema/blog/post-id-0>
<http://purl.org/dc/elements/1.1/date> "2012-01-14T10:48:53.280Z" .
<http://www.networkedplanet.com/schema/blog/post-id-1>
<http://www.w3.org/1999/02/22-rdf-syntax-ns#type>
<http://www.networkedplanet.com/schema/blog/post> .
<http://www.networkedplanet.com/schema/blog/post-id-1>
<http://purl.org/dc/elements/1.1/title> "post two" .
<http://www.networkedplanet.com/schema/blog/post-id-1>
<http://www.networkedplanet.com/schema/blog/body> "body two" .
<http://www.networkedplanet.com/schema/blog/post-id-1>
<http://purl.org/dc/elements/1.1/date> "2012-01-16T10:49:53.280Z" .
<http://www.networkedplanet.com/schema/blog/post-id-2>
<http://www.w3.org/1999/02/22-rdf-syntax-ns#type>
<http://www.networkedplanet.com/schema/blog/post> .
<http://www.networkedplanet.com/schema/blog/post-id-2>
<http://purl.org/dc/elements/1.1/title> "post three" .
<http://www.networkedplanet.com/schema/blog/post-id-2>
<http://www.networkedplanet.com/schema/blog/body> "body three" .
<http://www.networkedplanet.com/schema/blog/post-id-2>
<http://purl.org/dc/elements/1.1/date> "2012-01-18T10:50:53.280Z" .
添加测试数据后,重新启动 Node.js 服务器并再次运行 node app.js。刷新网站,应该会在页面上显示三篇博客文章。
现在我们能够显示我们的帖子,我们需要添加**创建新文章**的功能。创建一个名为 blog_new.jade 的新视图,并添加以下代码
h1= title
form( method="post")
div
div
span Title :
input(type="text", name="title", id="editArticleTitle")
div
span Body :
textarea( name="body", rows=20, id="editArticleBody)
div#editArticleSubmit
input(type="submit", value="Send")
在 app.js 中,添加另外两个路由来处理创建新博客文章
app.get('/blog/new', function (req, res) {
res.render('blog_new.jade', {
locals: {
title: 'New Post'
}
});
});
app.post('/blog/new', function (req, res) {
articleProvider.save({
title: req.param('title'),
body: req.param('body')
}, function (error, docs) {
res.redirect('/');
});
});
在 brightstar-server.js 和 articleprovider-brightstar.js 中,我们需要添加 save
方法来处理保存博客文章。打开 brightstar-server.js,并在 collection 方法之后添加以下方法
save: function (item, callback) {
console.log("Saving article...");
var options = {
host: this.host,
port: this.port,
path: this.path,
method: 'POST'
},
postId = "<http://www.networkedplanet.com/schema/blog/post-id-" + item._id + ">",
triple = postId + " <http://www.w3.org/1999/02/22-rdf-syntax-ns#type> " +
"<http://www.networkedplanet.com/schema/blog/post> .\n";
triple = triple + postId + " <http://purl.org/dc/elements/1.1/title> \"" + item.title + "\" .\n";
triple = triple + postId + " <http://www.networkedplanet.com/schema/blog/body> \"" +
item.body + "\" .\n";
triple = triple + postId + " <http://purl.org/dc/elements/1.1/date> \"" +
item.created_at.toISOString() + "\" .";
options.path = options.path + "/insert/?triples=" + encodeURIComponent(triple);
var req = http.request(options, function (res) {
res.setEncoding('utf8');
res.on('data', function (chunk) {
var jsonData = JSON.parse(chunk);
console.log("Article saved");
callback(null, jsonData);
});
});
req.end();
}
save
方法根据我们的 post 对象创建一个 NTriples 集合,并将它们发送到我们的 ASP.NET MVC REST 服务器。接下来,我们需要将 save
方法添加到我们的 ArticleProvider
。
在 articleprovider-brightstar.js 中,添加另一个用于保存帖子的方法
ArticleProvider.prototype.save = function (article, callback) {
var that = this;
this.findAll(function (error, articleCollection) {
if (error) callback(error);
article.created_at = new Date();
if (article.comments === undefined) article.comments = [];
for (var j = 0; j < article.comments.length; j++) {
article.comments[j].created_at = new Date();
}
article._id = articleCollection.length;
that.db.save(article, function () {
callback(null, article);
});
});
};
在上述方法中,我们首先使用 findAll
方法获取所有帖子,并使用现有帖子的数量来创建 Id
,然后将文章传递给 BrightstarServer
以将帖子保存到服务器。重新加载 **Node.js** 服务器,导航到 localhost:3000/blog/new 并检查帖子是否可以保存。注意,由于在将帖子保存为三元组时未转义字符,使用回车符将导致错误!
最后,我们将添加根据 ID**查看单个帖子**的功能。打开 brightstar-server.js,并在前面添加的 save 方法下方添加另一个方法。
findById: function (id, callback) {
var articleQuery = "select ?title ?body ?created_at where { " +
"<http://www.networkedplanet.com/schema/blog/{{id}}> " +
"<http://www.w3.org/1999/02/22-rdf-syntax-ns#type> " +
"<http://www.networkedplanet.com/schema/blog/post> . " +
"<http://www.networkedplanet.com/schema/blog/{{id}}> " +
"<http://purl.org/dc/elements/1.1/title> ?title . " +
"<http://www.networkedplanet.com/schema/blog/{{id}}> " +
"<http://www.networkedplanet.com/schema/blog/body> " +
"?body . <http://www.networkedplanet.com/schema/blog/{{id}}> " +
"<http://purl.org/dc/elements/1.1/date> ?created_at }",
options = {
host: this.host,
port: this.port,
path: this.path
};
options.path = options.path + "/?query=" +
encodeURIComponent(articleQuery.replace(/\{\{id\}\}/g, id));
console.log("Finding post by Id:" + id);
var req = http.request(options, function (res) {
res.setEncoding('utf8');
res.on('data', function (chunk) {
var jsonData = JSON.parse(chunk),
i;
for (i = 0; i < jsonData.length; i++) {
jsonData[i].created_at = new Date(jsonData[i].created_at);
jsonData[i].comments = [];
}
console.log("Returning post");
callback(null, jsonData[0]);
});
});
req.end();
},
在上述方法中,我们使用另一个 Sparql 查询来返回给定 ID 的“title
”、“body
”和“created_at
”属性。由于我们存储的是 ISO 日期,因此我们需要为每一行使用 date string
创建一个 Date()
对象。
接下来,使用以下代码更新 articleprovider-brightstar.js 以获取单个帖子
ArticleProvider.prototype.findById = function (id, callback) {
this.db.findById(id, function (error, article) {
if (error) callback(error);
else {
callback(null, article);
}
});
};
在上面的代码中,我们只是调用 **BrightstarServer** 方法 **findById**。最后,我们需要添加一个新的视图来显示单个文章,并在 ***app.js*** 中添加一个路由。
创建一个名为 blog_show.jade 的新视图,并添加以下代码
h1= title
div.article
div.created_at= article.created_at
div.title= article.title
div.body= article.body
- each comment in article.comments
div.comment
div.person= comment.person
div.comment= comment.comment
div
form( method="post", action="/blog/addComment")
input( type="hidden", name="_id", value=article._id.toHexString())
div
span Author :
input( type="text", name="person", id="addCommentPerson")
div
span Comment :
textarea( name="comment", rows=5, id="addCommentComment")
div#editArticleSubmit
input(type="submit", value="Send")
在 app.js 中,添加另一个用于查看单个帖子的路由
app.get('/blog/:id', function(req, res) {
articleProvider.findById(req.params.id, function(error, article) {
res.render('blog_show.jade',
{ locals: {
title: article.title,
article:article
}
});
});
});
重新启动 Node.js 服务器,现在我们应该能够查看所有帖子、打开单个帖子以及创建新帖子。还应该可以在博客系统中添加评论,因此添加**评论**已留给读者作为练习。
希望这展示了将 Node.js 和 BrightstarDB
结合使用的可能性。
- 下载两个项目的源代码 (BrightstarNodeJS-source)