基於Node的PetShop,RESTful API以及認證

前篇 - 基本認證,用戶名密碼
後篇 - OAuth2 認證javascript

因爲寵物店的業務發展須要,咱們須要一種更加便捷的方式來管理日益增多的寵物和客戶。最好的方法就是開發一個APP,我能夠用這個APP來添加、更新和刪除寵物。同時,業務要給寵物店的會員用戶有限查看某些寵物。html

咱們在開發中會用到NodeJs以及基於NodeJs的開發框架,如:Express,Mongoose(用來管理MongoDB的數據),Passport(認證)等工具。項目代碼在這裏java

開始

咱們這個項目的結構大概是這樣的:node

petshot/            //服務端和客戶端(android)
    server/         //服務端
        models/     //實體類
            pet.js
            user.js
        node_modules/    //npm安裝的包,無需手動修改
        package.json     //project定義和依賴聲明 
        server.js        //服務端的一切從這裏開始

注意node_modules這個目錄你不須要建立,在執行npm安裝命令以後這個目錄會自動生成,而且npm命令會自動管理這個目錄。android

定義項目和依賴的包

這裏假設你已經跳轉到petshop/server/目錄下了。以後在Terminal裏執行命令:git

npm init

按照提示,依次在Terminal裏輸入相應的信息。最後npm命令生成package.json文件。文件是這樣的:github

{
  "name": "petshop-server",
  "version": "0.1.0",
  "description": "petshop nodejs server",
  "main": "server.js",
  "dependencies": {
  }
}

內容不少,這裏是一部分。web

安裝所須要的包

爲何NodeJs能讓那麼多的開發者青睞有加,npm命令絕對也是一個數得上的緣由了。下面就體會一下,npm命令。mongodb

首先,使用npm命令安裝Express:chrome

npm install --save express

npm命令會選擇合適的Express版本下載安裝在本地。其餘的包也是這麼安裝的。很是簡單。

運行Server

有了Express,Server就已經能夠運行起來了。若是你尚未建立server.js,請建立。在server.js中添加以下的代碼:

// 引入咱們須要的包express
var express = require('express');
// 建立一個express的server
var app = express();
// server運行的端口號
var port = process.env.PORT || '3090';
// 使用express的路由器
var router = express.Router();
// 訪問http://localhost:3090/api的時候,
// 返回一個json
router.get('/', function (req, res) {
    res.json({'message': '歡迎來到寵物商店'});
});

// 給路由設定根路徑爲/api
app.use('/api', router);
// 運行server,並監聽指定的端口
app.listen(port, function () {
    console.log('server is running at http://localhost:3090');
});

經過require獲得的express,就能夠初始化出一個express的server。以後指定路由,並指定路由的相對根路徑。在app上調用listen方法,並指定端口。這樣express的server就運行起來了。

在Terminal中輸入命令:

node server.js

測試一下咱們的第一個server是否能夠運行。在瀏覽器中輸入地址:*http://localhost:3090/api*就能夠看到運行結果了

幾個工具

爲了開發的更加方便,僅僅以上介紹的內容是不夠的。好比,修改代碼以後,使用命令node server.js來重啓server。設置斷點,單步調試等。咱們來看看這幾個工具:

1. Visual Code

微軟改邪歸正依賴的第一個靠譜的工具。正好這個工具很是好的支持了NodeJs的開發。Visual Code還默認繼承了Git代碼管理工具。這讓我很是願意多安利幾句。

而且使用visual code能夠很是方便的調試。好比,設置斷點、單步調試,step in、step out等均可以。還能夠鼠標懸浮查看變量值等。

2. Postman

postman有chrome瀏覽器版本的應用,這樣不管你在什麼平臺上開發只要安裝了Chrome瀏覽器就能夠裝一個postman。這個工具是用來檢查RESTful API的。直接使用瀏覽器得出來的Json字符串有的時候沒有格式化,或者格式化不充分,很是難閱讀。

而且直接使用瀏覽器無法模擬Http post請求。而Postman很好的解決了以上問題。因此,開發必備神器之一postman,你值得擁有。

MongoDB

業界著名的非關係數據庫MongoDB。咱們在petshot的server端使用該庫來存儲數據。請按照官網說明下載安裝(其實就是把編譯好的二進制文件放到一個指定目錄)。

數據庫安裝好以後,就須要鏈接數據庫的框架了。這就須要用到mongoose。Mongoose是處理MongoDB的一個ORM框架。

npm install --save mongoose

安裝好以後在代碼中引入(require)。

var express = require('express');
var mongoose = require('mongoose');

鏈接到數據庫:

// 鏈接數據庫
mongoose.connect('mongodb://localhost:27017/petshot');

建立model

MVC的開發模式這裏就再也不科普了。凡是MVC模式下開發,就必定會有Model,MVC的M。通常每個model都和數據庫中的一個「表」對應,在mongodb裏「表」正式名稱爲「Collection」。咱們這裏只使用英文,專有名詞不必翻譯。而「表」裏的每一條「記錄」又叫作「Document」。

咱們首先在petshop/server/models/目錄下新建一個文件pet.js。以後添加以下代碼:

// 1. 引入mongoose
var mongoose = require('mongoose');

var Schema = mongoose.Schema;
// 2. 定義了Pet的Schema
var petSchema = new Schema({
    name: {type: String, required: true},
    type: {type: String, required: true},
    quantity: Number
});
// 3. 定義並export了一個Model
module.exports = mongoose.Model('pet', petSchema);

相關解釋:

  1. 引入mongoose庫。
  2. 定義了一個mongoose的Schema,這個Schema和一個mongodb的collection的定義相對應。這個schema有兩個字符串和一個數字類型的屬性。
  3. 把mongoose的Model export出去給server端的其餘代碼使用。哪裏使用哪裏就require這個model。

準備接收HTTP數據

首先,須要一個包(package)來解析http發送過來的數據。那麼安裝之:

npm install --save body-parser

在代碼中引入:

var express = require('express');
var mongoose = require('mongoose');
// 稍後處理數據使用
var Pet = require('./models/pet');
// 解析http數據
var bodyParser = require('body-parser');

下面還須要設置body-parser:

var app = express();

app.use(bodyParser.urlencoded({
    extended: true
}));

添加些許寵物

咱們的server終於迎來可使用的一個功能了:給寵物商店添加寵物。

router.get('/', function (req, res) {
    res.json({'message': '歡迎來到寵物商店'});
});

var petRouter = router.route('/pets');

petRouter.post(function (req, res) {
    var pet = new Pet();
    pet.name = req.body.name;
    pet.type = req.body.type;
    pet.quantity = req.body.quantity;

    pet.save(function (err) {
        if (err) {
            res.json({message: 'error', data: err});
            return;
        }

        res.json({message: 'done', data: pet});
    });
});

咱們增長了一個/pets的相對路徑來處理POST請求,並在請求中獲取信息,把用這個信息來給初始化的pet的model賦值,並調用這個model的save方法保存數據到數據庫。

打開postman,各個設置如圖:

這樣就能夠往數據庫添加pet數據了。

重構代碼

如今的代碼雖然已經能夠添加寵物寶寶了。可是,後面若是咱們還要添加其餘功能,好比:查找,更新和刪除等的時候,server.js文件勢必會不斷增長。這樣給之後代碼的維護帶來困擾。因此咱們要重構代碼。

petshop/server/目錄下添加controllers目錄。根據MVC的模式開發,把處理業務方面的代碼都存放在某個controller裏。新建pet.js文件。這個文件就做爲pet的controller。並將寵物的增刪改查都放在這個文件中處理:

var Pet = require('../models/pet');

var postPets = function(req, res) {
    var pet = new Pet();
    pet.name = req.body.name;
    pet.type = req.body.type;
    pet.quantity = req.body.quantity;

    pet.save(function (err) {
        if (err) {
            res.json({message: 'error', data: err});
            return;
        }

        res.json({message: 'done', data: pet});
    });
};

var getPets = function(req, res) {
    Pet.find(function (err, pets) {
        if (err) {
            res.json({message: 'error', data: err});
            return;
        }

        res.json({message: 'done', data: pets});
    });
};

var getPet = function(req, res) {
    Pet.findById(req.params.pet_id, function (err, pet) {
        if (err) {
            res.json({message: 'error', data: err});
            return;
        }
        res.json({message: 'done', data: pet});
    });
};

var updatePet = function(req, res) {
    Pet.findById(req.params.pet_id, function(err, pet) {
        if (err) {
            res.json({message: 'error', data: err});
            return;
        }

        pet.quantity = req.params.quantity;

        pet.save(function (err) {
            if (err) {
                res.json({message: 'error', data: err});
                return;
            }

            res.json({message: 'done', data: pet});
        });
    });
};

var deletePet = function(req, res) {
    Pet.findByIdAndRemove(req.params.pet_id, function(err) {
        if (err) {
            res.json({message: 'error', data: err});
            return;
        }

        res.json({message: 'done', data: {}});
    });
}

module.exports = {
    postPets: postPets,
    getPets: getPets,
    getPet: getPet,
    updatePet: updatePet,
    deletePet: deletePet
};

原來的server.js也須要重構:

var Pet = require('../models/pet');

var postPets = function(req, res) {
    var pet = new Pet();
    pet.name = req.body.name;
    pet.type = req.body.type;
    pet.quantity = req.body.quantity;

    pet.save(function (err) {
        if (err) {
            res.json({message: 'error', data: err});
            return;
        }

        res.json({message: 'done', data: pet});
    });
};

var getPets = function(req, res) {
    Pet.find(function (err, pets) {
        if (err) {
            res.json({message: 'error', data: err});
            return;
        }

        res.json({message: 'done', data: pets});
    });
};

var getPet = function(req, res) {
    Pet.findById(req.params.pet_id, function (err, pet) {
        if (err) {
            res.json({message: 'error', data: err});
            return;
        }
        res.json({message: 'done', data: pet});
    });
};

var updatePet = function(req, res) {
    Pet.findById(req.params.pet_id, function(err, pet) {
        if (err) {
            res.json({message: 'error', data: err});
            return;
        }

        pet.quantity = req.params.quantity;

        pet.save(function (err) {
            if (err) {
                res.json({message: 'error', data: err});
                return;
            }

            res.json({message: 'done', data: pet});
        });
    });
};

var deletePet = function(req, res) {
    Pet.findByIdAndRemove(req.params.pet_id, function(err) {
        if (err) {
            res.json({message: 'error', data: err});
            return;
        }

        res.json({message: 'done', data: {}});
    });
}

module.exports = {
    postPets: postPets,
    getPets: getPets,
    getPet: getPet,
    updatePet: updatePet,
    deletePet: deletePet
};

認證

咱們當讓不行讓誰均可以添加寵物寶寶了。查看是能夠的,添加須要控制。

passport

Passport就是給基於Express開發的web應用的,專一於認證中間件。也有和body-parser相相似的使用方法。passport的功能很是豐富,不過咱們先使用最簡單的一種認證策略

安裝:

npm install --save passport-http

認證之前首先要有用戶數據。

同時還有一個包須要安裝:

npm install --save bcrypt-nodejs

這個包是用來給密碼hash用的。

用戶model

全部關於用戶的數據都放在MongoDB的user colleciton裏,並有user model與之對應。在models目錄下新建user.js文件。

var mongoose = require('mongoose'),
    bcrypt = require('bcrypt-nodejs');

var Schema = mongoose.Schema;

var userSchema = new Schema({
    username: {type: String, unique: true, required: true},
    password: {type: String, required: true}
});

// * called before 'save' method.
userSchema.pre('save', function (next) {
    var self = this;

    if (!self.isModified('password')) {
        return next();
    }

    bcrypt.genSalt(5, function (err, salt) {
        if (err) {
            return next(err);
        }

        bcrypt.hash(self.password, salt, null, function (err, hash) {
            if (err) {
                return next(err);
            }

            self.password = hash;
            next();
        });
        
    });
});

module.exports = mongoose.model('User', userSchema);

使用userSchema.pre('save', function(next){})給model添加了一個在save方法調用以前先執行的方法。在這個方法裏首先檢查用戶的密碼是否有修改,若是有則使用包bcrypt-nodejs來hash用戶的密碼。

User Controller

有了model,就須要對應的controller來處理。在controllers目錄下新建一個user.js文件做爲user controller。注意:實際開發的時候你確定是不會把所有用戶的信息都發到客戶端的,裏面包含了hash的用戶密碼

var User = require('../models/user');

var postUsers = function (req, res) {
    var user = new User({
        username: req.body.username,
        password: req.body.password
    });

    user.save(function (err) {
        if (err) {
            res.json({message: 'error', data: err});
            return;
        }

        res.json({message: 'done', data: user});
    });
};

var getUsers = function (req, res) {
    User.find(function (err, users) {
        if (err) {
            res.json({message: 'error', data: err});
            return;
        }

        res.json({message: 'done', data: users});
    });
};

module.exports = {
    postUsers: postUsers,
    getUsers: getUsers
};

定義user controller的路由:

...

var petController = require('./controllers/pet')
    userController = require('./controllers/user');

...

// path: /users, for users
router.route('/users')
    .post(userController.postUsers)
    .get(userController.getUsers);

你已經能夠在這個路徑:*http://localhost:3090/api/users*下POST添加用戶,GET獲取所有用戶

認證

在開始之前首先確保你已經安裝了認證須要的包:

npm install --save passport
npm install --save passport-http

以後給在user model裏添加一個方法驗證password:

userSchema.methods.verifyPassword = function (password, callback) {
    bcrypt.compare(password, this.password, function (err, match) {
        if (err) {
            return callback(err);
        }

        callback(null, match);
    });
};

接下來,在controllers目錄下添加auth.js文件。

var passport =          require('passport'),
    BasicStrategy =     require('passport-http').BasicStrategy,
    User =              require('../models/user');

passport.use(new BasicStrategy(
    function(username, password, done) {
        User.findOne({username: username}, function(err, user) {
            if (err) {
                return done(err);
            }

            // 用戶不存在
            if (!user) {
                return done(null, false);
            }

            // 檢查用戶的密碼
            user.verifyPassword(passowrd, function(err, match) {
                // 密碼不匹配
                if (!match) {
                    return done(null, false);
                }

                // 成功
                return done(null, user);
            });
        });
    }
));

module.exports.isAuthenticated = passport.authenticate('basic', {session: false});

咱們使用包passport-httpBasicStrategy來處理http的用戶認證。首先,咱們經過用戶名查找用戶。若是用戶存在,接着驗證用戶的密碼是否與數據庫的數據一致。若是以上兩步經過驗證則用戶認證成功,不然不成功。

最後一句就是告知passport使用BasicStrategy來認證用戶。session爲false,是告訴passport不存儲用戶的session。用戶每一次的http請求都須要提供用戶名和密碼。

相應的更新server.js:

// 引入咱們須要的包express
var express             = require('express'),
    mongoose            = require('mongoose'),
    bodyParser          = require('body-parser'),
    passport            = require('passport'),

    petController       = require('./controllers/pet'),
    userController      = require('./controllers/user'),
    authController      = require('./controllers/auth');

// 建立一個express的server
var app = express();

app.use(bodyParser.urlencoded({
    extended: true
}));

// 鏈接數據庫
mongoose.connect('mongodb://localhost:27017/petshot');

...

router.route('/pets')
    .post(authController.isAuthenticated, petController.postPets)
    .get(authController.isAuthenticated, petController.getPets);

router.route('/pets/:pet_id')
    .get(authController.isAuthenticated, petController.getPet)
    .put(authController.isAuthenticated, petController.updatePet)
    .delete(authController.isAuthenticated, petController.deletePet);

// path: /users, for users
router.route('/users')
    .post(userController.postUsers)
    .get(authController.isAuthenticated, userController.getUsers);

...

若是尚未數據的話,首先使用POST方法添加幾個用戶。以後GET用戶測試一下。若是用戶名、密碼都對的話就會得到數據了。

使用passport包認證還有一個好處,你能夠直接從req獲取user數據。如:req.user._id得到用戶的_id。有些數據須要記錄更新數據的用戶,這樣就很是方便了。

最後

下文使用更加安全的oauth2認證。

相關文章
相關標籤/搜索