1. 程式人生 > >NodeJS簡易部落格系統(八)功能需求描述及使用者模組實現

NodeJS簡易部落格系統(八)功能需求描述及使用者模組實現

一、功能需求描述

用一張導圖來說明:

二、頁面設計

頁面設計如下:

三、梳理下整個系統的業務流程

對這個小專案進行業務流程的梳理,流程圖大致如下:

四、使用者模組實現

1、資料庫設計及程式碼

(1)使用者表(users)

(2)博文分類表(categories)

(3)博文評論列表(contents)

從title往下依次是博文標題,分類,瀏覽次數,所屬使用者id,評論列表(評論內容,評論所屬使用者id),發表時間,文章描述,文章詳情,資料庫版本。

2、使用者模組

由上述流程圖得使用者模組有登入、註冊、博文列表、閱讀原文及評論功能。

(1)頁面程式碼

首頁的介面使用的是bootstrap+jquery框架設計,首頁總共有三個div塊,一個是header,content-details,login-register。下面是頁面的程式碼:

main_template.html:

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="utf-8">
    <title>簡單部落格系統</title>
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <link rel="stylesheet" type="text/css" href="/public/css/bootstrap.css">
    <link rel="stylesheet" type="text/css" href="/public/css/index.css">
    <script type="text/javascript" src="/public/js/jquery.js"></script>
    <script type="text/javascript" src="/public/js/bootstrap.js"></script>
    <script type="text/javascript" src="/public/js/index.js"></script>
</head>
<body>
<header>
    <div class="container-fluid header1">
        <span>NodeJS簡單部落格系統</span>
    </div>
    <nav class="navbar navbar-default">
        <div class="container-fluid">
            <div class="navbar-header">
                <button type="button" class="navbar-toggle collapsed" data-toggle="collapse" data-target="#bs-example-navbar-collapse-1" aria-expanded="false">
                    <span class="sr-only">Toggle navigation</span>
                    <span class="icon-bar"></span>
                    <span class="icon-bar"></span>
                    <span class="icon-bar"></span>
                </button>
            </div>
            <div class="collapse navbar-collapse" id="bs-example-navbar-collapse-1">
                <ul class="nav navbar-nav">
                    {%if category == ''%}
                    <li><a href="/" class="focus">首頁</a></li>
                    {%else%}
                    <li><a href="/">首頁</a></li>
                    {%endif%}
                    {%for cate in categories%}
                    {%if category == cate.id%}
                    <li><a href="/?category={{cate.id}}" class="focus">{{cate.name}}</a></li>
                    {%else%}
                    <li><a href="/?category={{cate.id}}">{{cate.name}}</a></li>
                    {%endif%}
                    {%endfor%}
                </ul>

            </div>

        </div>
    </nav>
</header>
<section>
    <div class="container">
        <div class="row">
            <div class="col-lg-8 col-md-8 moveup" id="content">
                <!--文章列表block-->
                {% block content %}
                {% endblock %}
            </div>
            <div class="col-lg-4 col-md-4" >
                {% if userInfo._id %}
                <div class="userinfo spindown" id="userinfo" >
                    <h2>使用者資訊</h2>
                    <h3 class="account">使用者名稱:{{userInfo.username}}</h3>
                    {% if userInfo.isadmin %}
                    <p>您好,管理員! <a href="/admin/"> 點選這裡</a>進入管理頁面</p>
                    {% else %}
                    <p>你好,歡迎光臨我的部落格!</p>
                    {% endif %}
                    <p><a href="javascript:;" class="logout">退出</a></p>
                </div>
                {% else %}
                <div class="register spindown" id="register"  style="display: none">
                    <h2>註冊</h2>
                    <div class="line">
                        <span>使用者名稱:</span>
                        <input type="text" name="username" title="username">
                    </div>
                    <div class="line">
                        <span>密碼:</span>
                        <input type="password" name="password" title="password">
                    </div>
                    <div class="line">
                        <span>確認:</span>
                        <input type="password" name="repassword" title="repassword">
                    </div>
                    <div class="line">
                        <input type="submit" name="submit" value="註冊" >
                    </div>
                    <p class="warning"></p>
                    <p>已有賬號? <a href="javascript:;">點選登入</a></p>
                </div>

                <div class="login spindown" id="login" >
                    <h2>登入</h2>
                    <div class="line">
                        <span>使用者名稱:</span>
                        <input type="text" name="username" title="username">
                    </div>
                    <div class="line">
                        <span>密碼:</span>
                        <input type="password" name="password" title="password">
                    </div>
                    <div class="line">
                        <input type="submit" name="submit" value="登入" >
                    </div>
                    <h2 class="warning"></h2>
                    <p>還沒註冊? <a href="javascript:;">點選註冊</a></p>
                </div>
                {% endif %}
            </div>
        </div>
    </div>
</section>
<footer>
    <p>Copyright © 小馬實驗室 | 京ICP備11951015號 | 京公網安備11011105210084</p>
    <a href="#"><span class="glyphicon glyphicon-arrow-up"></span></a>
</footer>
</body>
</html>

文章詳情article_detail.html

{% extends "main_template.html" %}
{% block content %}
<div class="papers">
    <h2>{{contents.title}}</h2>
    <p class="paperabout">
        作者:<span class="paperinfo">{{contents.user.username}}</span>-
        時間:<span class="paperinfo">{{contents.addtime|date('Y-m-d  H:i:s', -8*60)}}</span>-
        閱讀:<span class="paperinfo">{{contents.num}}</span>-
        分類於:<span class="paperinfo">{{contents.category.name}}</span>-
        評論:<span class="paperinfo">{{contents.comment.length}}</span>
    </p>
    <dfn><p>{{contents.composition}}</p></dfn>
    <div class="readmore"><a href="javascript:window.history.back()">返回</a></div>
</div>
<div id="comment">
    <h3 ><strong>評論 </strong> <span class="much"> 共 <em id="commentCount">0</em> 條評論</span></h3>
    <div style="font-size: 22px;">
        <div>
            <textarea  name="name" id="commentarea" placeholder="請填寫評論"
            style="height: 150px;width: 100%;"></textarea>
            <input  type="hidden" id="contentid" name="contentid" value="{{contents.id}}">
        </div>
        <button type="submit" id="addcomment" class="btn btn-primary btn-lg">發表評論</button>
    </div>
    {% if userInfo._id %}
    {% else %}
    <h4 class="loginfo" >你還沒有登入,請先登入!</h4>
    {% endif %}
    {% if contents.comment.length == 0 %}
    <h4 class="loginfo" >暫無評論,趕緊來評論吧!</h4>
    {% endif %}
    <div id="commentlist">
    </div>
    <div class="pages">
        <a id="prevpage" style="float: left;"><span>上一頁</span></a>
        <span id="currentpage"></span> / <span id="totalpage"></span>
        <a id="nextpage" style="float: right;"><span>下一頁</span></a>
    </div>
</div>
{% endblock %}

首頁index.html

<!--首頁-->
{% extends "main_template.html" %}
{% block content %}
 {% for content in contents %}
 <div class="papers">
     <h2>{{content.title}}</h2>
     <p class="paperabout">
         作者:<span class="paperinfo">{{content.user.username}}</span>-
         時間:<span class="paperinfo">{{content.addtime|date('Y-m-d  H:i:s', -8*60)}}</span>-
         閱讀:<span class="paperinfo">{{content.num}}</span>-
         分類於:<span class="paperinfo">{{content.category.name}}</span>-
         評論:<span class="paperinfo">{{content.comment.length}}</span>
     </p>
     <dfn><p>description:{{content.description}}</p></dfn>
     <div class="readmore"><a href="/article?contentid={{content.id}}">閱讀全文</a></div>
 </div>
 {% endfor %}
<div class="pages">
    <a href="/?category={{category}}&page={{page-1}}" style="float: left;"><span>上一頁</span></a>
    <span>{{page}}</span> / <span>{{pages}}</span>
    <a href="/?category={{category}}&page={{page+1}}" style="float: right;"><span>下一頁</span></a>
</div>
{% endblock %}

(2)頁面對應的js、css程式碼

index.js

$(function(){
    var loginbox = $("#login");
    var registerbox = $("#register");
    var userinfobox = $("#userinfo");
    loginbox.find("a").on("click",function(){
        loginbox.hide();
        registerbox.show();
    });
    registerbox.find("a").on("click",function(){
        loginbox.show();
        registerbox.hide();
    });    registerbox.find("input[name='submit']").on("click",function(){
        $.ajax({
            type: "post",
            url: "/api/user/register",
            dataType: "json",
            data: {
                username: registerbox.find('input[name="username"]').val(),
                password: registerbox.find('input[name="password"]').val(),
                repassword: registerbox.find('input[name="repassword"]').val()},
            success :function(result){
                console.log(result);                registerbox.find(".warning").html(result.message);
                setTimeout(function(){
                    registerbox.find(".warning").html("");
                },1500);
                if(!result.code){
                    setTimeout(function(){
                        loginbox.show();
                        registerbox.hide();                        registerbox.find('input[name="username"]').val("");
                        registerbox.find('input[name="password"]').val("");
                        registerbox.find('input[name="repassword"]').val("");
                    },1500);
                }
            }

        });

    });
loginbox.find("input[name='submit']").on("click",function(){
        $.ajax({
            type: "post",
            url: "/api/user/login",
            dataType: "json",
            data: {
                username: loginbox.find('input[name="username"]').val(),
                password: loginbox.find('input[name="password"]').val()
                },
            success :function(result){
                console.log(result);
                loginbox.find(".warning").html(result.message);
                setTimeout(function(){
                    loginbox.find(".warning").html("");
                },1500);
                if(!result.code){
                    setTimeout(function(){
                        window.location.reload();
                    },1500);
                }
            }

        });

    });
    userinfobox.find(".logout").on("click",function(){

        $.ajax({
            url:"/api/user/logout",
            success:function(result){
                console.log(result);
                if(!result.code){
                    window.location.reload();
                }
            }
        })

    });


//在頁面載入時獲取評論
    $.ajax({
        url: '/api/pinglun',
        type:"get",
        dataType:"json",
        data: {
            contentid: $('#contentid').val()
        },
        success: function(result) {
            //console.log(111111);
            render(result.postdata);
            quanju=result.postdata;
        }
    });

//提交評論
    $("#addcomment").on("click",function(){
        $.ajax({
            type:"post",
            url:"/api/comment",
            dataType:"json",
            data:{
                comment: $("#comment").find("textarea").val(),
                contentid: $("#contentid").val()
            },
            success:function(result){
                //console.log(result);
                $("#commentarea").val("");
                render(result.postdata);
                quanju=result.postdata;
            }
        })
    });
    var quanju=null;
    var page=1;
    var limit=3;
    var pagecount=0;
    $("#prevpage").on("click",function(){
       page--;
        render(quanju);
    });
    $("#nextpage").on("click",function(){
        page++;
        render(quanju);
    });
    function render(data) {
        var str = "";
        var start=(page-1)*limit;
        var end = start+limit;
        var comments=data.comment.reverse();
        var showcomments=comments.slice(start,end);
        pagecount = Math.ceil(data.comment.length/limit);
        page = Math.min(pagecount,page);
        page = Math.max(1,page);
        $("#totalpage").html(pagecount);
        $("#currentpage").html(page);
        $("#commentCount").html(comments.length);
        for (var i = 0; i < showcomments.length; i++) {
            str += `<div>
                <span class="commenter">${showcomments[i].user}</span>
                <span class="commenttime">${formatDate(showcomments[i].time)}</span>
                </div>
                <p class="contents">${showcomments[i].comment}</p>`;
        }
        $("#commentlist").html(str);
    }
    function formatDate(d) {
        var date1 = new Date(d);
        return date1.getFullYear() + '-' + (date1.getMonth()+1) + '-' + date1.getDate() + '- ' + date1.getHours() + ':' + date1.getMinutes() + ':' + date1.getSeconds();
    }
});

index.css

body{
    background:#ebebeb;
    min-width: 650px;
}
body h2, body h3 {
    padding: 0;
    margin: 0;
}
a{
    text-decoration: none !important;
    color: #fc6423;
}
.container .row .container-fluid {
    padding: 0;
}
.header1{
    height: 200px !important;
    background:url(/public/img/backimg.jpg);
    background-size:cover;

}
.header1 span{
    margin-left: 40px;
    line-height: 200px;
    font-size: 30px;
    color: #fc6423;
    opacity: 0.7;
    filter: alpha(opacity=70);
}
div[class*="col"]{
    padding: 0;
    color:#000;
}
#content .papers{
    height: 500px;
    margin-bottom: 20px;
    background:#fff;
    text-align: center;
    position: relative;
}
#content .papers h2{
    /*height: 80px;*/
    line-height: 50px;
    font-size: 25px;
}
#content .paperabout{
    font-size: 18px;
}
#content .paperinfo{
    color: #fc6423;
}
#content dfn p{
    height: 300px;
    font-size: 25px;
    padding: 20px;
    background: #ddd;
}
#content .readmore{
    width: 150px;
    height: 40px;
    line-height: 36px;
    font-size: 25px;
    background:#fff;
    border:2px solid #fc6423;
    position: absolute;
    bottom: 30px;
    left: 50px;
}
#content .readmore a{
    display: inline-block;
    width: 150px;
    height: 40px;
}
#content .readmore:hover{
    background:#fc6423;
    border:2px solid #fff;
}
#content .pages{
    font-size: 18px;
    height: 40px;
    line-height: 40px;
    text-align: center;
    margin-bottom:20px;
}
#content .pages a{
    color: #fc6423;
    display: block;
    height: 40px;
    line-height: 40px;
    width: 80px;
    border:1px solid #fc6423;
}
#content .pages a:hover{
    color: #fff;
    background-color: #fc6423;

}
#content .pages>span{
    color: #fc6423;
}
#register{
    background:#fff;
    margin-left: 30px;
    padding-bottom: 20px;
    margin-bottom: 20px;
    transition: 0.5s ease-out;
}
#register h2{
    height: 50px;
    margin-left: 30px;
    line-height: 50px;
    font-size: 24px;
    color: #fc6423;
}
#register .line{
    height: 50px;
    text-align: center;
    line-height: 50px;
    font-size: 18px;
}
#register .line input{
    height: 60%;
    outline: none;
    border-color: #fc6423;
}
#register .line input[type="submit"]{
    width: 200px;
    border:none;
    padding: 0;
    background-color: #fc6423;
    line-height: 100%;
}
#register .warning{
    height: 50px;
    text-align: center;
    line-height: 50px;
    font-size: 20px;
    color: #fc6423;
}
#register>p{
    margin-left: 30px;
}
#login{
    background:#fff;
    padding-bottom: 20px;
    margin-left: 30px;
    margin-bottom: 20px;
    transition: 0.5s ease-out;
}
#login h2{
    height: 50px;
    margin-left: 30px;
    line-height: 50px;
    font-size: 24px;
    color: #fc6423;
}
#login .line{
    height: 50px;
    text-align: center;
    line-height: 50px;
    font-size: 18px;
}
#login .line input{
    height: 60%;
    outline: none;
    border-color: #fc6423;
}
#login .line input[type="submit"]{
    width: 200px;
    border:none;
    padding: 0;
    background-color: #fc6423;
    line-height: 100%;
}
#login .warning{
    height: 50px;
    text-align: center;
    line-height: 50px;
    font-size: 20px;
    color: #fc6423;
}
#login>p{
    margin-left: 30px;
}
#userinfo, #community{
    background:#fff;
    margin-left: 30px;
    margin-bottom: 20px;
    padding-bottom: 20px;
}
#userinfo h2, #community h2{
    height: 50px;
    margin-left: 30px;
    line-height: 50px;
    font-size: 24px;
    color: #fc6423;
}
#userinfo h3, #community h3{
    height: 35px;
    margin-left: 30px;
    line-height: 35px;
    font-size: 16px;
}
#userinfo>p{
    text-align: center;
    font-size: 15px;
}
footer{
    height: 60px;
    background:#333;
    position: relative;
}
footer p{
    text-align: center;
    line-height: 60px;
    color: #fff;
    opacity: 0.7;
}
footer a{
    display: block;
    width: 60px;
    height: 60px;
    position: absolute;
    top: -70px;
    right: 10px;
    background-color: #fc6423;
    border-radius: 30px;
    text-align: center;
    line-height:100%;
}
footer a span{
    font-size: 30px;
    color: #fff;
    margin-top: 10px;
}
#bs-example-navbar-collapse-1 ul{
    width: 100%;
    padding-left: 15%;
    text-align: center;
}
#bs-example-navbar-collapse-1 li{
    height: 60px;
}
#bs-example-navbar-collapse-1 ul a{
    margin: 0;
    padding: 0;
    color: #fc6423;
    width: 150px;
    height: 60px;
    line-height: 60px;
    font-size: 20px;
}
#bs-example-navbar-collapse-1 ul a:hover{
    background: #fc6423;
    color:#fff;
    -webkit-transform: scale(1.1);
    transform: scale(1.1);
    transition: 0.5s ease-out;
}

#comment{
    padding: 30px;
    background: #fff;

}
#comment h3{
    text-align: left;
    height: 60px;
    line-height: 100%;
    color: #fc6423;
}
#comment h3 .much{
    float: right;
    color: #888;
    font-size: 18px;
}
#comment h4{
    color: red;
    text-align: center;
}
#commentlist{

    margin-bottom: 30px;
}
#commentlist>p{
    background: #eee;
    height: 80px;
    text-indent: 2em;
    padding:5px 20px;
    font-size: 18px;
    color: #555;
    margin-bottom: 10px;
}
#commentlist>div{
    line-height: 40px;
    padding: 5px;
    height: 40px;
}
#commentlist>div .commenter{
    float: left;
    margin-left: 20px;
    color: #fc6423;
}
#commentlist .commenttime{
    float: right;
    margin-right: 20px;
}
@keyframes moveup {
    0%{
        -webkit-transform: translateY(100px);
        transform: translateY(100px);
        opacity: 0;
        filter: alpha(opacity=0);
    }
    50%{
        -webkit-transform: translateY(30px);
        transform: translateY(30px);
        opacity: 0.25;
        filter: alpha(opacity=25);
    }
    75%{
         -webkit-transform: translateY(13px);
         transform: translateY(13px);
         opacity: 0.5;
         filter: alpha(opacity=50);
     }
    100%{
          -webkit-transform: translateY(0);
          transform: translateY(0);
          opacity: 1;
          filter: alpha(opacity=100);
      }

}
@keyframes turndown {
    0%{
        opacity: 0;
        filter: alpha(opacity=0);
    }
    100%{
        opacity: 1;
        filter: alpha(opacity=100);
    }
}
.moveup{
    -webkit-animation: moveup 0.7s ease-in;
    animation: moveup 0.7s ease-in;
    -webkit-animation-fill-mode: forwards;
}
.spindown{
    -webkit-animation: turndown 0.7s  ease-in;
    animation: turndown 0.7s  ease-in;
    -webkit-animation-fill-mode: forwards;
}

3、後臺程式碼

(1)專案配置

package.json

{
  "name": "myblog",
  "version": "1.0.0",
  "description": "",
  "main": "index.js",
  "scripts": {
    "test": "echo \"Error: no test specified\" && exit 1"
  },
  "author": "",
  "license": "ISC",
  "dependencies": {
    "body-parser": "^1.18.2",
    "cookies": "^0.7.1",
    "express": "^4.16.2",
    "mongoose": "^4.12.4",
    "promise": "^8.0.1",
    "swig": "^1.4.2"
  }
}

app.js

//載入express模組
var express = require("express");
//載入swig模組
var swig = require("swig");
var User = require("./models/user");
//載入mongoose資料庫,這個中介軟體是nodejs與mongoDB資料庫的橋樑
var mongoose = require("mongoose");
var Cookies = require('cookies');
//建立一個新的伺服器,相當於httpcreateServer
var app = express();

//靜態檔案資源託管的,js css img等
app.use("/public",express.static( __dirname+"/public"));

//定義應用使用的模板引擎,第一個引數:所要渲染模板檔案的字尾,也是模板引擎的名稱,第二個引數:渲染的方法
app.engine("html",swig.renderFile);
//定義模板檔案存放的路徑,第一個引數必須是views,這是模組內指定的解析欄位,第二個引數為路徑:./表示根目錄
app.set("views","./views");
//註冊使用模板引擎;第一個引數不能變,第二個引數和上面的html一致
app.set("view engine","html");
//設定完就可以直接在res中渲染html檔案了:res.render("index.html",{要渲染的變數})第一個引數是相對於views資料夾

//在開發過程中要取消模板快取,便於除錯
swig.setDefaults({cache : false});

//載入bodyparser模組,用來解析前端提交過來的資料
var bodyparser = require("body-parser");
app.use(bodyparser.urlencoded({extended:true}));

app.use( function(req, res, next) {
    req.cookies = new Cookies(req, res);
    req.userInfo = {};
    if(req.cookies.get('userInfo')){
        var str1 = req.cookies.get('userInfo');
        req.userInfo=JSON.parse(str1);
        User.findById(req.userInfo._id).then(function(userInfodata){
            req.userInfo.isadmin = Boolean(userInfodata.isadmin);
        });
    }
    next();

} );
//瀏覽器地址對映
app.use("/admin" ,require("./routers/admin"));
app.use("/" ,require("./routers/main"));
app.use("/api" ,require("./routers/api"));
// 連線資料庫
mongoose.connect("mongodb://localhost:27017/myBlog",{useMongoClient:true},function (err) {
    if(err){
        console.log("資料庫連線失敗!");
    }else{
        console.log("資料庫連線成功!");
        app.listen(3000);
    }
});

(2)schemas

users.js

var mongoose = require("mongoose");
module.exports = new mongoose.Schema({
    username: String,
    password: String,
    isadmin:{
        type:Boolean,
        default:false
    }
});

categories.js

var mongoose = require("mongoose");
module.exports = new mongoose.Schema({
    name: String
});

contents.js

var mongoose = require("mongoose");
module.exports = new mongoose.Schema({
    title: String,
    category : {
        type:mongoose.Schema.Types.ObjectId,
        ref : "Category"
    },
    composition:{
        type: String,
        default : ""
    },
    description :{
        type: String,
        default : ""
    },
    user:{
        type:mongoose.Schema.Types.ObjectId,
        ref : "User"
    },
    num:{
        type:Number,
        dafault:0
    },
    addtime:{
        type:Date,
        default: new Date()
    },
    comment:{
        type:Array,
        default:[]
    }
});

(3)models

user.js

var mongoose = require("mongoose");
var userschama = require("../schemas/users");
module.exports = mongoose.model("User",userschama);

category.js

var mongoose = require("mongoose");
var userschama = require("../schemas/users");
module.exports = mongoose.model("User",userschama);

contents.js

var mongoose = require("mongoose");
var contentschama = require("../schemas/contents");
module.exports = mongoose.model("Content",contentschama);

(4)路由(routers)

api.js

var express = require("express");
var User = require("../models/user");
var Content = require("../models/content");
var router= express.Router();

//統一返回給前端的資料格式
var resdata;
router.use(function(req,res,next){
    resdata = {
        code:0,
        message:""
    };
    next();
});
router.post("/user/register",function(req ,res ){
    var username = req.body.username;
    var password = req.body.password;
    var repassword = req.body.repassword;
    if(username == ""){
        resdata.code=1;
        resdata.message="使用者名稱不能為空!";
        res.json(resdata);
        return;
    }
    if(password == ""){
        resdata.code=2;
        resdata.message="密碼不能為空!";
        res.json(resdata);
        return;
    }
    if(password != repassword){
        resdata.code=3;
        resdata.message="兩次輸入的密碼不一致!";
        res.json(resdata);
        return;
    }
    User.findOne({
        username:username
    },function(err,userinfo){
        if(err){
            console.log(err);
        }
        if(userinfo){
            resdata.code = 4;
            resdata.message = "該使用者已被註冊!";
            res.json(resdata);
            return false;
        }else{
            var newuser = new User({
                username: username,
                password: password
            });
            newuser.save();
            resdata.message = "註冊成功!";
            res.json(resdata);
        }
    });
});
router.post("/user/login",function(req ,res ){
    var username = req.body.username;
    var password = req.body.password;
    if(username == ""||password==""){
        resdata.code=1;
        resdata.message="使用者名稱和密碼不能為空!";
        res.json(resdata);
        return;
    }
    User.findOne({
        username:username,
        password:password
    },function(err,userinfo){
        if(err){
            console.log(err);
        }
        if(!userinfo){
            resdata.code = 2;
            resdata.message = "使用者名稱或密碼錯誤!";
            res.json(resdata);
            return false;
        }
        resdata.message = "登入成功!";
        resdata.userinfo={
            id:userinfo._id ,
            username:userinfo.username,
            isadmin:userinfo.isadmin
        };
        req.cookies.set('userInfo', JSON.stringify({
            "_id": userinfo._id,
            "username": userinfo.username,
            "isadmin":userinfo.isadmin
        }));

        res.json(resdata);

    })

});
router.get("/user/logout",function(req ,res ){
    req.cookies.set('userInfo', null);
    res.message="退出成功!";
    res.json(resdata);
});
router.get('/pinglun', function(req, res) {
    var contentid = req.query.contentid || '';
    Content.findOne({
        _id: contentid
    }).then(function(content) {
        //content.comment.reverse();
        resdata.postdata = content;
        //resdata.data.comments.reverse();
        res.json(resdata);
    })
});
router.post("/comment",function(req,res){
    var id = req.body.contentid;
    var commentdata={
        comment:req.body.comment||"",
        user:req.userInfo.username,
        time: new Date()
    };
    Content.findOne({_id:id}).then(function(thiscon){
        if(commentdata.comment!=""){
            thiscon.comment.push(commentdata);
        }
        //thiscon.comment.reverse();
        thiscon.save().then(function(newcon){
            resdata.postdata = newcon;
            resdata.message="評論成功!";
            res.json(resdata);
            //console.log(newcon);
        });

    });

});

module.exports=router;

main.js

var express = require("express");
var router= express.Router();
var Category = require("../models/category");
var Content = require("../models/content");

var data;

//處理通用的資料,首頁,分類頁,每篇文章詳情頁均需要的變數
router.use(function (req, res, next) {
    data = {
        userInfo: req.userInfo,
        categories: []
    };
    Category.find().then(function(categories) {
        data.categories = categories;
        next();
    });
});

//渲染首頁
router.get("/", function(req, res) {
    data.category = req.query.category ||"";
    data.count = 0;
    data.page = Number(req.query.page || 1);
    // 預設兩條資料
    data.limit = 2;
    data.pages = 0;
    var where = {};
    if (data.category) {
        where.category = data.category
    }
    Content.where(where).count().then(function(count) {
        data.count = count;
        //計算總頁數
        data.pages = Math.ceil(data.count / data.limit);
        //取值不能超過pages
        data.page = Math.min( data.page, data.pages );
        //取值不能小於1
        data.page = Math.max( data.page, 1 );
        var skip = (data.page - 1) * data.limit;
        return Content.where(where).find().limit(data.limit).skip(skip).populate(['category', 'user']).sort({
            addtime:-1
        });
    }).then(function(contents) {
        data.contents = contents;
        //console.log(data);
        res.render('main/index', data);
    })
});
//進入詳細閱讀部分
router.get("/article",function(req,res){
    var id = req.query.contentid||"";
    Content.findOne({_id:id}).populate(["category","user"]).then(function(content){
        data.contents = content;
        content.num++;
        content.save();
        //console.log(data);
        res.render("main/article_detail",data);
    });
});

module.exports = router;

(五)執行效果

(1)首頁

(2)博文詳情頁面

下篇博文將說明後臺管理的設計與實現。