65.9K
CodeProject 正在变化。 阅读更多。
Home

使用 BrightstarDB、Express 和 Node.js 进行博客滚动

starIconstarIconstarIconstarIconstarIcon

5.00/5 (3投票s)

2012年7月11日

CPOL

8分钟阅读

viewsIcon

19419

BrightstarDB 如何也能与 Node.js 结合使用来创建一个简单的博客系统

受到这篇 How To Node 文章 中 Node.js 和 mongoDB 用法的启发,本教程旨在展示 BrightstarDB 如何也能与 Node.js 结合使用来创建一个简单的博客系统。

开始之前,请确保已安装以下软件

  1. BrightstarDB
  2. Node.js
  3. ASP.NET MVC

Node.js 需要能够访问 BrightstarDB 中的数据,遗憾的是,我们无法在 Node.js 中使用 WCF,因此,首先,我们将创建一个简单的 ASP.NET MVC REST 服务,它将处理与 BrightstarDB 的查询和交互。

如果您只想直接查看代码,可以在文章末尾下载两个项目的源代码。

为 BrightstarDB 创建 MVC REST 接口

对于博客系统,我们的 REST 接口将允许以下命令

  • 按名称使用 sparql 查询存储,并将结果返回为 JTriples
  • 使用 N-Triples 发布新文章

首先,创建一个新的 **ASP.NET MVC 3 项目**。对于模板,请选择 **Empty**(空)。

在项目中,**References**(引用)中添加对 NetworkedPlanet.Brightstar.dll 的引用。

向项目中添加一个名为 StoreController.cs 的新 **Controller**(控制器)。它将作为 BrightstarDB 存储的主要接口。我们将使用 BrightstarService 类来创建一个嵌入式客户端,因此我们需要在控制器顶部包含以下 using 语句

using NetworkedPlanet.Brightstar.Client;

在使用嵌入式客户端之前,我们需要一个 BrightstarDB 数据的位置。打开 BrightstarDB 管理应用程序 **Polaris**,并创建一个指向您计算机上某个位置的新嵌入式连接。

BrightstarDB Embedded Connection

在 Polaris 中创建 BrightstarDB 嵌入式连接。

从 **Server**(服务器)菜单中,选择 **New Store…**(新建存储…),并将其命名为 ‘Blog’

BrightstarDB Create 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 应该会将 nodenpm 添加到系统路径中,如果未添加,请将以下内容添加到您的 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 内容。我们将在博客应用程序中创建以下**操作**。它们是

  1. 显示所有博客文章
  2. 创建博客文章
  3. 显示单篇博客文章

为了创建我们的应用程序和前面创建的 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" .

BrightstarDB Add Triples using Polaris

使用 Polaris 添加三元组

添加测试数据后,重新启动 Node.js 服务器并再次运行 node app.js。刷新网站,应该会在页面上显示三篇博客文章。

Blog test data

显示我们测试数据的博客第一页

现在我们能够显示我们的帖子,我们需要添加**创建新文章**的功能。创建一个名为 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.jsarticleprovider-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.jsBrightstarDB 结合使用的可能性。

© . All rights reserved.