(五)NodeJs構(gòu)建web應(yīng)用

說明:該學習筆記參考《Node.js開發(fā)指南》,但是選用的模板和書中不同,添加了自己的理解和適當?shù)难a充!僅供參考!

我們打算從零開始用Node.js實現(xiàn)一個微博系統(tǒng),功能包括路由控制、頁面模板、數(shù)據(jù)庫訪問、用戶注冊、登錄、用戶會話等內(nèi)容。在這里我們會使用Express框架、MVC設(shè)計模式、Jkig模板和MongoDB數(shù)據(jù)庫的操作。

構(gòu)建項目

1 Express 應(yīng)用生成器

通過應(yīng)用生成器工具 express 可以快速創(chuàng)建一個應(yīng)用的骨架。執(zhí)行一下命令進行安裝到全局環(huán)境中

npm install express-generator -g
安裝應(yīng)用生成器.png

我們可以看到同時安裝了一些依賴。在當前目錄下創(chuàng)建blog的應(yīng)用。

express blog

創(chuàng)建完成后進入應(yīng)用。應(yīng)用目錄如下:

目錄結(jié)構(gòu)

然后安裝所有依賴包。

npm install

啟動項目。

(windows下)

set DEBUG=blog & npm start

(Linux平臺下)

DEBUG=blog npm start

打開瀏覽器,訪問http://127.0.0.1:3000即可看到應(yīng)用。

選定swig模板引擎

express構(gòu)建的應(yīng)用默認使用ejs模板引擎,本人覺得這種奇怪的東東暫時難以接受,之前做過django的項目,最后選定swig,也比較看好它!

1 安裝swig

npm install swig

2 在app.js中配置如下:

var swig = require('swig');

var swig  = new swig.Swig();
app.engine('html', swig.readerFile);
app.set('view engine', 'html');

3 模板編寫

移除views下的*.jade,創(chuàng)建同名的*.html

編寫index.html

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>index</title>
</head>
<body>
    Welcome, {{ title }}
</body>
</html>

啟動服務(wù)

set DEBUG=blog &npm start

打開瀏覽器訪問http://127.0.0.1:3000,能夠看到:Welcome, Express

使用Bootstrap和界面設(shè)計

我們選定Bootstrap開始設(shè)計我們的界面。首先下載,解壓之后,將文件夾改名為bootstrap-dist并放在public/中,同時下載最新的jquery,放在public/javascripts/中。

添加bootstrap文件夾到項目中

這里補充一下Swig的使用參考。

修改layout.html中的代碼:

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>{% block title %}My Blog{% endblock %}</title>
    {% block head %}
        <link rel="stylesheet" href="/bootstrap-dist/css/bootstrap.min.css">
    {% endblock %}
</head>
<body>
    {% block content %}{% endblock %}
    <script src="/javascripts/jquery-3.2.1.js"></script>
    <script src="/bootstrap-dist/js/bootstrap.js"></script>
</body>
</html>

views/下創(chuàng)建login.html,并編寫代碼如下:

{% extends 'layout.html' %}

{% block title %}用戶登錄{% endblock %}

{% block content %}

    <div class="container" style="margin-top: 30px;">
        <div class="row clearfix">
            <div class="col-md-12 column">
                <form class="form-horizontal" role="form" method="post" action="/users/login">
                    <div class="form-group">
                         <label for="inputEmail3" class="col-sm-2 control-label">用戶名</label>
                        <div class="col-sm-10">
                            <input type="text" class="form-control" id="inputText3" />
                        </div>
                    </div>
                    <div class="form-group">
                         <label for="inputPassword3" class="col-sm-2 control-label">密碼</label>
                        <div class="col-sm-10">
                            <input type="password" class="form-control" id="inputPassword3" />
                        </div>
                    </div>
                    <div class="form-group">
                        <div class="col-sm-offset-2 col-sm-10">
                            <div class="checkbox">
                                 <label><input type="checkbox" />記住我</label>
                            </div>
                        </div>
                    </div>
                    <div class="form-group">
                        <div class="col-sm-offset-2 col-sm-10">
                             <button type="submit" class="btn btn-default">登陸</button>
                        </div>
                    </div>
                </form>
            </div>
        </div>
    </div>

{% endblock %}

目前登錄的界面已經(jīng)創(chuàng)建完成,接下來就是添加路由了。修改routes/users.js文件,添加login GET視圖后文件中代碼如下:

var express = require('express');
var router = express.Router();

/* GET users listing. */
router.get('/', function(req, res, next) {
  res.send('respond with a resource');
});


router.get('/login', function(req, res){
    res.render('login');
})

module.exports = router;

重新啟動服務(wù),瀏覽器訪問http://127.0.0.1:3000/users/login,即可以看到登錄輸入框,則成功!

登錄界面

數(shù)據(jù)持久化——MongoDB

從官網(wǎng)下載并安裝。

連接數(shù)據(jù)庫

打開工
程目錄中的package.json,在dependencies屬性中添加以下代碼代碼:

"connect-mongo": ">= 0.1.7",
"mongodb": ">= 0.9.9"

然后運行npm install更新依賴的模塊。接下來在工程的目錄中創(chuàng)建settings.js文件,這個文件用于保存數(shù)據(jù)庫的連接信息。

將用到的數(shù)據(jù)庫起名為blog

module.exports = {
    cookieSecret: "blogid",
    db: "blog",
    host: "localhost",
    port: 27017
};

db是數(shù)據(jù)庫的名稱,host是數(shù)據(jù)庫的地址,port是數(shù)據(jù)庫的端口。cookieSecret用于Cookie加密與數(shù)據(jù)庫無關(guān),我們留作后用。

接下來在項目中創(chuàng)建modules目錄,然后在其下中創(chuàng)建db.js

var settings = require("../settings");
var mongodb = require("mongodb");
var Db = mongodb.Db;
var Server = mongodb.Server;

module.exports = new Db(settings.db, new Server(settings.host, settings.port, {}));

以上代碼通過module.exports輸出了創(chuàng)建的數(shù)據(jù)庫連接。

會話支持

打開app.js添加以下內(nèi)容:

var session = require('express-session');
var settings = require('./settings');
var MongoStore = require('connect-mongo')(session);

app.use(session({
    secret: settings.cookieSecret,
    store: new MongoStore({
        db: settings.db,
        url: 'mongodb://localhost/blog'  
    })
}));

其中express.cookieParser()是Cookie解析的中間件。express.session()則提供會話支持,設(shè)置它的store參數(shù)為MongoStore實例,把會話信息存儲到數(shù)據(jù)庫中,以避免丟失。

這里需要注意的是在Express4.X版本中,session已經(jīng)分離出來,所以這里需要去手動下載:

npm install express-session

還需要注意的是store中的url是需要填上去的!這些地方由于書中所用版本較低的原因,這些都是“坑”,就是改這些東西,和查資料折騰了一下午!

注冊功能的實現(xiàn)

1 注冊界面

設(shè)計注冊界面在views中添加registered.html

{% extends 'layout.html' %}

{% block title %}用戶注冊{% endblock %}

{% block content %}

<div class="container" style="margin-top: 30px;width: 500px;">

    {% if success %}
    <div class="alert alert-success alert-dismissable">
        <button type="button" class="close" data-dismiss="alert" aria-hidden="true">×</button>
        <h4>
            {{success}}
        </h4>
    </div>

    {% endif %}
    {% if error %}
    <div class="alert alert-danger alert-dismissable">
        <button type="button" class="close" data-dismiss="alert" aria-hidden="true">×</button>
        <h4>
            {{error[0]}}
        </h4>
    </div>
    {% endif %}

    <div class="row clearfix">
        <div class="col-md-12 column">
            <form class="form-horizontal" role="form" method="post" action="/users/registered">
                <div class="form-group">
                    <label for="inputText3" class="col-sm-2 control-label">用戶名</label>
                    <div class="col-sm-10">
                        <input type="text" class="form-control" id="inputText3" name="username"/>
                    </div>
                </div>
                <div class="form-group">
                    <label for="inputPassword3" class="col-sm-2 control-label">密碼</label>
                    <div class="col-sm-10">
                        <input type="password" class="form-control" id="inputPassword3" name="password" />
                    </div>
                </div>
                <div class="form-group">
                    <div class="col-sm-offset-2 col-sm-10">
                        <div class="checkbox">
                            <label><input type="checkbox" />記住我</label>
                        </div>
                    </div>
                </div>
                <div class="form-group">
                    <div class="col-sm-offset-2 col-sm-10">
                        <button type="submit" class="btn btn-default">注冊</button>
                    </div>
                </div>
            </form>
        </div>
    </div>
</div>

{% endblock %}

界面完成之后,添加路由,在users.js下添加以下內(nèi)容:

router.get('/registered', function(req, res){
    res.render('registered');
});

瀏覽器打開http://127.0.0.1:3000/users/registered,這個頁面剛開始進去是這樣子的:

注冊界面

當注冊失敗是這樣子的:

注冊失敗

成功我就不貼了。接下來繼續(xù):

2 注冊響應(yīng)

在user.js中添加registeredpost請求:

var User = require('../modules/user')

router.post('/registered', function(req, res){
    if (!(req.body['username'])){
        req.flash("error", '用戶名不能為空!');
        return res.redirect("/users/registered");
    }
    var user = new User({
        name: req.body['username'],
        password: req.body['password'],
    });

    // 檢查用戶是否存在
    User.get(user.name, function(err, has_user){
        if(has_user){
            err = { "errmsg": "用戶已存在"};
        }
        if(err){
            req.flash("error", err.errmsg);
            return res.redirect("/users/registered");
        }

        // 保存新用戶
        user.save(function(err){
            if(err){
                req.flash("error", err.errmsg);
                return res.redirect("/users/registered");
            }
            req.flash("success", "注冊成功!");
            res.redirect("/users/login");
        });
    });
});

在這段代碼中:

  • req.body就是POST請求信息解析過后的對象。
  • req.flash是Express提供的一個奇妙的工具,通過它保存的變量只會在用戶當前和下一次的請求中被訪問,之后會被清除。當然這里使用的時候需要就行一些設(shè)置,待會再說。
  • res.redirect是重定向功能。
  • User對象,是接下來要創(chuàng)建的用戶模型。實現(xiàn)了用戶的判斷和保存等。

3 創(chuàng)建用戶模型

User是一個描述數(shù)據(jù)的對象,即MVC架構(gòu)中的模型。在modules目錄下創(chuàng)建user.js,編寫內(nèi)容如下:

var mongodb = require('./db');

function User(user){
    this.name = user.name;
    this.password = user.password;
}

module.exports = User;

User.prototype.save = function save(callback){

    var user = {
        name: this.name,
        password: this.password
    };

    mongodb.open(function(err, db){
        if (err){
            return callback(err);
        }
        db.collection('users', function(err, collection){
            if(err){
                mongodb.close();
                return callback(err);
            }

            // 將name屬性添加為索引
            collection.ensureIndex('name', {unique: true});
            // 寫入新用戶到文檔
            collection.insert(user, function(err, user){
                mongodb.close();
                callback(err, user);
            });
        });

    });

};


User.get = function get(username, callback){

    mongodb.open(function(err, db){
        if (err){
            return callback(err);
        }

        db.collection('users', function(err, collection){
            if(err) {
                mongodb.close();
                return callback(err);
            }
            collection.findOne({name: username}, function(err, doc){

                // 對數(shù)據(jù)庫操作完成之后,及時關(guān)閉數(shù)據(jù)庫
                mongodb.close();

                // 如果同名的用戶存在,那么就直接返回這個用戶信息
                if(doc){
                    return callback(err, doc);
                }else{
                    return callback(err, null);
                }
            });

        });
    });
};

以上代碼實現(xiàn)了兩個接口,User.prototype.saveUser.get,前者是對象實例的方法,用于將用戶對象的數(shù)據(jù)保存到數(shù)據(jù)庫中,后者是對象構(gòu)造函數(shù)的方法,用于從數(shù)據(jù)庫中查找指定的用戶。

注意:以上兩個接口的類型是不一樣的,所以在前面users.js中,是使用User.get(...)var user = new User(..);user.save(...);。

4 視圖交互

在視圖中訪問會話中的用戶數(shù)據(jù),同時為了顯示錯誤和成功的信息,也要增加響應(yīng)的函數(shù)。在app.js中添加以下內(nèi)容:

var flash = require('connect-flash');
app.use(flash());
app.use(function(req,res,next){
  res.locals.user=req.session.user;

  var err = req.flash('error');
  var success = req.flash('success');
  res.locals.error = err.length ? err : null;
  res.locals.success = success.length ? success : null;
   
  next();
});

'connect-flash也是需要通過npm install connect-flash進行安裝的。這里還需要注意的是以上在app.js中添加的代碼在文件中的順序很重要。這點也是一個大坑:

數(shù)據(jù)庫連接在相應(yīng)函數(shù)前面,響應(yīng)函數(shù)在路由前面,這樣才能在代碼中正確調(diào)用flash()方法。

5 小段總結(jié)

到這里注冊的功能差不多就完成了,假設(shè)沒有出現(xiàn)意外的話。祝你我都好運,以上代碼折騰了一天,但是基本上讓自己了解了Express和MongoDB的相互配合使用,也對整個系統(tǒng)的數(shù)據(jù)流了解的更加透徹。

接下來就是實現(xiàn)登錄和首頁的問題了,到這里就不貼代碼了,會將現(xiàn)在實現(xiàn)的這些代碼放在github上,做一個tab,感興趣的同學可以去看看。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容