鍍金池/ 教程/ HTML/ 第15章 增加轉(zhuǎn)載功能和轉(zhuǎn)載統(tǒng)計
第9章 增加標簽和標簽頁面
番外篇之——使用 Mongoose
番外篇之——使用 Async
第4章 實現(xiàn)用戶頁面和文章頁面
第12章 增加友情鏈接
第14章 增加頭像
第7章 實現(xiàn)分頁功能
第5章 增加編輯與刪除功能
第11章 增加文章檢索功能
第3章 增加文件上傳功能
番外篇之——部署到 Heroku
第2章 使用 Markdown
第13章 增加404頁面
第16章 增加日志功能
第1章 一個簡單的博客
番外篇之——使用 Handlebars
第10章 增加pv統(tǒng)計和留言統(tǒng)計
番外篇之——使用 Passport
第15章 增加轉(zhuǎn)載功能和轉(zhuǎn)載統(tǒng)計
第8章 增加存檔頁面
番外篇之——使用 generic pool
番外篇之——使用 _id 查詢
番外篇之——使用 Disqus
番外篇之——使用 KindEditor
第6章 實現(xiàn)留言功能

第15章 增加轉(zhuǎn)載功能和轉(zhuǎn)載統(tǒng)計

現(xiàn)在,我們來給博客添加轉(zhuǎn)載文章和轉(zhuǎn)載數(shù)統(tǒng)計的功能。

我們的設計思路是:當在線用戶滿足特定條件時,文章頁面才會顯示 轉(zhuǎn)載 鏈接字樣,當用戶點擊 轉(zhuǎn)載 后,復制一份存儲當前文章的文檔,修改后以新文檔的形式存入數(shù)據(jù)庫,而不是單純的添加一條指向被轉(zhuǎn)載的文檔的 "引用" ,這種設計是合理的,因為這樣我們也可以將轉(zhuǎn)載來的文章進行修改。

首先,我們來完成轉(zhuǎn)載文章的功能。

打開 post.js ,將 Post.prototype.save 內(nèi)的:

var post = {
    name: this.name,
    head: this.head,
    time: time,
    title:this.title,
    tags: this.tags,
    post: this.post,
    comments: [],
    pv: 0
};

修改為:

var post = {
    name: this.name,
    head: this.head,
    time: time,
    title:this.title,
    tags: this.tags,
    post: this.post,
    comments: [],
    reprint_info: {},
    pv: 0
};

這里我們給文檔里添加了 reprint_info 鍵,最多為以下形式:

{
  reprint_from: {name: xxx, day: xxx, title: xxx},
  reprint_to: [
    {name: xxx, day: xxx, title: xxx},
    {name: xxx, day: xxx, title: xxx},
    ...
  ]
}

reprint_from 表示轉(zhuǎn)載來的原文章的信息,reprint_to 表示該文章被轉(zhuǎn)載的信息。為了節(jié)約存儲空間,我們初始設置 reprint_info 鍵為 {},而不是以下形式:

{
  reprint_from: {},
  reprint_to: []
}

這是因為大多數(shù)文章是沒有經(jīng)過任何轉(zhuǎn)載的,所以為每個文檔都添加以上形式的 reprint_info 是有點浪費的。假如某篇文章是轉(zhuǎn)載來的,我們只需給 reprint_info 添加上 reprint_from 鍵即可,假如某篇文章被轉(zhuǎn)載了,我們只需給 reprint_info 添加上 reprint_to 鍵即可,假如某篇文章是轉(zhuǎn)載來的且又被轉(zhuǎn)載了,那我們就給 reprint_info 添加上 reprint_from 和 reprint_to 鍵即可。

打開 post.js ,在最后添加如下代碼:

//轉(zhuǎn)載一篇文章
Post.reprint = function(reprint_from, reprint_to, callback) {
  mongodb.open(function (err, db) {
    if (err) {
      return callback(err);
    }
    db.collection('posts', function (err, collection) {
      if (err) {
        mongodb.close();
        return callback(err);
      }
      //找到被轉(zhuǎn)載的文章的原文檔
      collection.findOne({
        "name": reprint_from.name,
        "time.day": reprint_from.day,
        "title": reprint_from.title
      }, function (err, doc) {
        if (err) {
          mongodb.close();
          return callback(err);
        }

        var date = new Date();
        var time = {
            date: date,
            year : date.getFullYear(),
            month : date.getFullYear() + "-" + (date.getMonth() + 1),
            day : date.getFullYear() + "-" + (date.getMonth() + 1) + "-" + date.getDate(),
            minute : date.getFullYear() + "-" + (date.getMonth() + 1) + "-" + date.getDate() + " " + 
            date.getHours() + ":" + (date.getMinutes() < 10 ? '0' + date.getMinutes() : date.getMinutes())
        }

        delete doc._id;//注意要刪掉原來的 _id

        doc.name = reprint_to.name;
        doc.head = reprint_to.head;
        doc.time = time;
        doc.title = (doc.title.search(/[轉(zhuǎn)載]/) > -1) ? doc.title : "[轉(zhuǎn)載]" + doc.title;
        doc.comments = [];
        doc.reprint_info = {"reprint_from": reprint_from};
        doc.pv = 0;

        //更新被轉(zhuǎn)載的原文檔的 reprint_info 內(nèi)的 reprint_to
        collection.update({
          "name": reprint_from.name,
          "time.day": reprint_from.day,
          "title": reprint_from.title
        }, {
          $push: {
            "reprint_info.reprint_to": {
              "name": doc.name,
              "day": time.day,
              "title": doc.title
          }}
        }, function (err) {
          if (err) {
            mongodb.close();
            return callback(err);
          }
        });

        //將轉(zhuǎn)載生成的副本修改后存入數(shù)據(jù)庫,并返回存儲后的文檔
        collection.insert(doc, {
          safe: true
        }, function (err, post) {
          mongodb.close();
          if (err) {
            return callback(err);
          }
          callback(err, post[0]);
        });
      });
    });
  });
};

這里我們在 Post.reprint() 內(nèi)實現(xiàn)了被轉(zhuǎn)載的原文章的更新和轉(zhuǎn)載后文章的存儲。

打開 index.js ,在 app.get('/remove/:name/:day/:title') 后添加如下代碼:

app.get('/reprint/:name/:day/:title', checkLogin);
app.get('/reprint/:name/:day/:title', function (req, res) {
  Post.edit(req.params.name, req.params.day, req.params.title, function (err, post) {
    if (err) {
      req.flash('error', err); 
      return res.redirect(back);
    }
    var currentUser = req.session.user,
        reprint_from = {name: post.name, day: post.time.day, title: post.title},
        reprint_to = {name: currentUser.name, head: currentUser.head};
    Post.reprint(reprint_from, reprint_to, function (err, post) {
      if (err) {
        req.flash('error', err); 
        return res.redirect('back');
      }
      req.flash('success', '轉(zhuǎn)載成功!');
      var url = encodeURI('/u/' + post.name + '/' + post.time.day + '/' + post.title);
      //跳轉(zhuǎn)到轉(zhuǎn)載后的文章頁面
      res.redirect(url);
    });
  });
});

至此,我們給 轉(zhuǎn)載 鏈接注冊了路由響應。

注意:我們需要通過 Post.edit() 返回一篇文章 markdown 格式的文本,而不是通過 Post.getOne 返回一篇轉(zhuǎn)義后的 HTML 文本,因為我們還要將修改后的文檔存入數(shù)據(jù)庫,而數(shù)據(jù)庫中應該存儲 markdown 格式的文本。

最后,我們在文章頁(article.ejs)添加轉(zhuǎn)載鏈接。

打開 article.ejs ,在:

<% if (user && (user.name == post.name)) { %>
  <span><a class="edit" href="/edit/<%= post.name %>/<%= post.time.day %>/<%= post.title %>">編輯</a></span>
  <span><a class="edit" href="/remove/<%= post.name %>/<%= post.time.day %>/<%= post.title %>">刪除</a></span>
<% } %>

后添加如下代碼:

<% var flag = 1 %>
<% if (user && (user.name != post.name)) { %>
  <% if ((post.reprint_info.reprint_from != undefined) && (user.name == post.reprint_info.reprint_from.name)) { %>
    <% flag = 0 %>
  <% } %>
  <% if ((post.reprint_info.reprint_to != undefined)) { %>
    <% post.reprint_info.reprint_to.forEach(function (reprint_to, index) { %>
      <% if (user.name == reprint_to.name) { %>
        <% flag = 0 %>
      <% } %>
    <% }) %>
  <% } %>
<% } else { %>
  <% flag = 0 %>
<% } %>
<% if (flag) { %>
  <span><a class="edit" href="/reprint/<%= post.name %>/<%= post.time.day %>/<%= post.title %>">轉(zhuǎn)載</a></span>
<% } %>

以上代碼的意思是:我們設置一個 flag 標志,如果用戶是游客,或者是該文章的目前作者,或者是該文章的上一級作者,或者已經(jīng)轉(zhuǎn)載過該文章,都會將 flag 設置為 0 ,即不顯示 轉(zhuǎn)載 鏈接,即不能轉(zhuǎn)載該文章。最后判斷 flag 為 1 時才會顯示 轉(zhuǎn)載 鏈接,即才可以轉(zhuǎn)載這篇文章。

最后,我們需要添加一個 原文鏈接 來指向被轉(zhuǎn)載的文章。 打開 index.ejs 、user.ejs 、article.ejs,在第一個

里最后添加如下代碼:

<% if (post.reprint_info.reprint_from) { %>
  <br><a href="/u/<%= post.reprint_info.reprint_from.name %>/<%= post.reprint_info.reprint_from.day %>/<%= post.reprint_info.reprint_from.title %>">原文鏈接</a>
<% } %>

現(xiàn)在我們就給博客添加了轉(zhuǎn)載功能。

接下來我們添加轉(zhuǎn)載統(tǒng)計。 添加轉(zhuǎn)載統(tǒng)計就簡單多了,我們只需使用 reprint_info.reprint_to.length 即可。

打開 index.ejs 、user.ejs 、article.ejs ,將:

<p class="info">閱讀:<%= post.pv %> | 評論:<%= post.comments.length %></p>

修改為:

<p class="info">
  閱讀:<%= post.pv %> | 
  評論:<%= post.comments.length %> | 
  轉(zhuǎn)載:
  <% if (post.reprint_info.reprint_to) { %>
    <%= post.reprint_info.reprint_to.length %>
  <% } else { %>
    <%= 0 %>
  <% } %>
</p>

現(xiàn)在我們就給博客添加了轉(zhuǎn)載統(tǒng)計的功能。但工作還沒有完成,假如我們要刪除一篇轉(zhuǎn)載來的文章時,還要將被轉(zhuǎn)載的原文章所在文檔的 reprint_to 刪除遺留的轉(zhuǎn)載信息。

打開 post.js ,將 Post.remove 修改為:

//刪除一篇文章
Post.remove = function(name, day, title, callback) {
  //打開數(shù)據(jù)庫
  mongodb.open(function (err, db) {
    if (err) {
      return callback(err);
    }
    //讀取 posts 集合
    db.collection('posts', function (err, collection) {
      if (err) {
        mongodb.close();
        return callback(err);
      }
      //查詢要刪除的文檔
      collection.findOne({
        "name": name,
        "time.day": day,
        "title": title
      }, function (err, doc) {
        if (err) {
          mongodb.close();
          return callback(err);
        }
        //如果有 reprint_from,即該文章是轉(zhuǎn)載來的,先保存下來 reprint_from
        var reprint_from = "";
        if (doc.reprint_info.reprint_from) {
          reprint_from = doc.reprint_info.reprint_from;
        }
        if (reprint_from != "") {
          //更新原文章所在文檔的 reprint_to
          collection.update({
            "name": reprint_from.name,
            "time.day": reprint_from.day,
            "title": reprint_from.title
          }, {
            $pull: {
              "reprint_info.reprint_to": {
                "name": name,
                "day": day,
                "title": title
            }}
          }, function (err) {
            if (err) {
              mongodb.close();
              return callback(err);
            }
          });
        }

        //刪除轉(zhuǎn)載來的文章所在的文檔
        collection.remove({
          "name": name,
          "time.day": day,
          "title": title
        }, {
          w: 1
        }, function (err) {
          mongodb.close();
          if (err) {
            return callback(err);
          }
          callback(null);
        });
      });
    });
  });
};

注意:我們使用了 $pull 來刪除數(shù)組中的特定項,關于 $pull 的詳細使用請查閱 《MongoDB 權威指南》。

至此,我們給博客添加了轉(zhuǎn)載功能和轉(zhuǎn)載統(tǒng)計。