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


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

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

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

開始

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

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

注意node_modules這個目錄你不需要創建,在執行npm安裝命令之后這個目錄會自動生成,並且npm命令會自動管理這個目錄。

定義項目和依賴的包

這里假設你已經跳轉到petshop/server/目錄下了。之后在Terminal里執行命令:

npm init

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

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

內容很多,這里是一部分。

安裝所需要的包

為什么NodeJs能讓那么多的開發者青睞有加,npm命令絕對也是一個數得上的原因了。下面就體會一下,npm命令。

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

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認證。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM