form驗證及圖片上傳
這一節增加推薦圖書的提交和刪除功能,來學習node的form提交以及node的圖片上傳功能。開始之前需要源碼同學可以先在git上fork:https://github.com/stoneniqiu/ReadingClub
一、form驗證
MVC的form驗證有三個地方可以做,第一道關就是前端提交之前,第二道關就是在數據保存之前,也就是在controller中做驗證,第三道關就是數據保存的時候,也就是如果提交的數據模型不符合實體定義的約束,數據是無法保存的,這是最后一道防線。第一道關主要是依賴於js或者jquery框架,比較常用的是jquery.validate.js。如果是Asp.net MVC 可以自動生成驗證規則,這里就不細究了,網上有很多文章。第二層和各自的業務邏輯有關,也需要做一些必要驗證,防止前端禁止JavaScript,而提交不合法數據,這里是要講基於Mongoose的第三層驗證。
1.回顧模型定義
我們先回顧一下之前用Mongoose定義的book模型:
var bookSchema = new mongoose.Schema({
title: { type: String, required: true },
rating: {
type: Number,
required: true,
min: 0,
max: 5
},
info: { type: String, required: true },
img: String,
tags: [String],
brief: { type: String, required: true },
ISBN: String,
});
每個屬性定義了類型和是否必須,還可以添加min,max,默認值等其他約束。如果提交的模型不滿足這些約束,將不能保存成功。相當於Asp.net MVC中的DataAnnotations的作用。后面的form驗證就基於此。
2.添加路由
我們需要增加4個路由規則,2個用於添加(一個get,一個post),一個用於刪除,一個用於上傳圖片:
router.get('/book/create', homeController.bookcreateview);
router.post('/book/create', homeController.doBookCreate);
router.delete('/book/:id', homeController.delete);
router.post('/uploadImg', homeController.uploadImg);
基於Express的路由,我們可以創建Restful的路由規則。路由位於app_server文件夾下。
3.添加控制器方法
home.bookcreateview:
module.exports.bookcreateview = function (req, res) {
res.render('bookCreate', { title: '新增推薦圖書' });
};
這里直接是一個get請求,所以直接用render去渲染視圖,當然這個bookCreate視圖接下來會創建。
doBookCreate:
module.exports.doBookCreate = function (req, res) {
var requestOptions, path, postdata;
path = "/api/book";
postdata = {
title: req.body.title,
info: req.body.info,
ISBN: req.body.ISBN,
brief: req.body.brief,
tags: req.body.tags,
img: req.body.img,
rating:req.body.rating,
};
requestOptions = {
url: apiOptions.server + path,
method: "POST",
json: postdata,
};
request(requestOptions, function (err, response, body) {
console.log("body.name", body.name, response.statusCode);
if (response.statusCode === 201) {
res.redirect("/detail/"+body._id);
}
else if (response.statusCode == 400 && body.name && body.name == "ValidationError") {
res.render('bookCreate', { title: '新增推薦圖書', error:"val"});
}
else {
console.log("body.name",body.name);
info(res, response.statusCode);
}
});
};
info:
View Code
在上一節,我們創建了數據操作的api部分。代碼的流程就是先從req中獲取到前端傳過來的數據,然后用request模塊調用api,如果添加成功(狀態碼是201)就返回到detail頁面,如果驗證失敗,就原路返回,並給出提示。如果錯誤,交給info方法去處理。
delete:
module.exports.delete = function (req, res) {
var requestOptions, path;
path = "/api/book/" + req.params.id;
requestOptions = {
url: apiOptions.server + path,
method: "delete",
json: {},
};
request(requestOptions, function (err, response, body) {
if (response.statusCode == 204) {
res.json(1);
}
else {
res.json(0);
}
});
};
如果刪除成功,返回的狀態碼是204,然后返回json(1)讓前端去處理界面。
4.添加視圖
1) 先需要在圖書列表的右側邊欄增加一個按鈕:

在books視圖中修改:
.col-md-3
.userinfo
p stoneniqiu
a(href='/book/create').btn.btn-info 新增推薦
當用戶點擊會跳轉到/book/create頁面
2)新增推薦頁面:
extends layout
include _includes/rating
block content
.row
.col-md-12.page.bookdetail
h3 新增推薦書籍
.row
.col-xs-12.col-md-6
form.form-horizontal(action='',method="post",role="form")
- if (error == "val")
.alert.alert-danger(role="alert") All fields required, please try again
.form-group
label.control-label(for='title') 書名
input#name.form-control(name='title')
.form-group
label.control-label(for='info') 信息
input#name.form-control(name='info')
.form-group
label.control-label(for='ISBN') ISBN
input#name.form-control(name='ISBN')
.form-group
label.control-label(for='brief') 簡介
input#name.form-control(name='brief')
.form-group
label.control-label(for='tags') 標簽
input#name.form-control(name='tags')
.form-group
label.control-label(for='rating') 推薦指數
select#rating.form-control.input-sm(name="rating")
option 5
option 4
option 3
option 2
option 1
.form-group
p 上傳圖片
a.btn.btn-info(id="upload", name="upload") 上傳圖片
br
img(id='img')
.form-group
button.btn.btn-primary(type='submit') 提交
if語句的地方是用來顯示錯誤提示;圖片上傳,稍后完整介紹;所以提交頁面基本長成這樣:

3)Mongoose驗證
這個時候沒有加前端驗證,form可以直接提交。但是node打印出了錯誤日志,Book validation failed,驗證失敗。

這是Mongoose給我們返回的驗證信息,這時界面上回顯示一個提示信息:

這是因為在controller中的處理:
else if (response.statusCode == 400 && body.name && body.name == "ValidationError") {
res.render('bookCreate', { title: '新增推薦圖書', error:"val"});
}
以上說明了Mongoose會在數據保存的時候驗證實體,如果實體不滿足path規則,將不能保存。但至此有三個問題,第一個問題是提示信息不明確,當然我們可以遍歷輸出ValidatorError;第二個就是,驗證錯誤之后,頁面原來的數據沒有了,需要再輸入一遍,這個我們可以參考Asp.net MVC將模型數據填充到視圖中可以解決;第三個問題就是頁面前端還沒有驗證,form直接就可以提交了,這個可以通過簡單的Jquery腳本就可以做到;這三點先不細究。繼續往下看,如果規范輸入,這個時候是可以提交的,提交之后在books頁面可以看到:

4)刪除
在標題的右側增加了一個刪除符號(books視圖中):
.col-md-10
p
a(href="/Detail/#{book._id}")=book.title
span.close(data-id='#{book._id}') ×
並添加腳本:
$(".close").click(function() {
if (confirm("確定刪除?")) {
var id = $(this).data("id");
var row = $(this).parents(".booklist");
$.ajax({
url: "/book/" + id,
method: "delete",
}).done(function(data) {
console.log(data);
row.fadeOut();
});
}
});
腳本可以先位於layout視圖下方:
script(src='/javascripts/books.js')
這樣,刪除完成之后會隱藏當前行。下面解決圖片上傳問題。
二、圖片上傳
前面我們在路由里面定義了一個uploadimg方法,現在實現它。一般都涉及兩個部分,一個是前台圖片的提交,一個是后端數據的處理。
1.uploadimg 方法實現
先需要安裝formidable模塊。

然后在Public文件下創建一個upload/temp文件夾
腳本:
var fs = require('fs');
var formidable = require('formidable');
module.exports.uploadImg = function (req, res) {
var form = new formidable.IncomingForm(); //創建上傳表單
form.encoding = 'utf-8'; //設置編輯
form.uploadDir = './../public/upload/temp/'; //設置上傳目錄
form.keepExtensions = true; //保留后綴
form.maxFieldsSize = 3 * 1024 * 1024; //文件大小
form.parse(req, function(err, fields, files) {
console.log(files);
if (err) {
console.log(err);
return res.json(0);
}
for (var key in files) {
console.log(files[key].path);
var extName = ''; //后綴名
switch (key.type) {
case 'image/pjpeg':
extName = 'jpg';
break;
case 'image/jpeg':
extName = 'jpg';
break;
case 'image/png':
case 'image/x-png':
default:
extName = 'png';
break;
}
var avatarName = (new Date()).getTime() + '.' + extName;
var newPath = form.uploadDir + avatarName;
fs.renameSync(files[key].path, newPath); //重命名
return res.json("/upload/temp/"+ avatarName);
}
});
};
這個form會自動將文件保存到upLoadDir目錄,並以upload_xxxx格式重新命名,所以最后使用fs模塊對文件進行重命名。然后返回給前端。
2.前端
我喜歡用插件,前端我用的是plupload-2.1.8,擁有多種上傳方式,比較方便。放置在Public文件下。在layout.jade中引用js:
script(src='/plupload-2.1.8/js/plupload.full.min.js') script(src='/javascripts/books.js')
而在bookCreate.jade視圖中,修改如下:
a.btn.btn-info(id="upload", name="upload") 上傳圖片
br
img(id='img')
input#imgvalue(type='hidden',name='img',value='')
a標簽用來觸發上傳,img用來預覽,input用來存放路徑。在books.js下增加以下代碼:
var uploader = new plupload.Uploader({
runtimes: 'html5,flash,silverlight,html4',
browse_button: "upload",
url: '/uploadImg',
flash_swf_url: '/plupload-2.1.8/js/Moxie.swf',
silverlight_xap_url: '/plupload-2.1.8/js/Moxie.xap',
filters: {
max_file_size: "3mb",
mime_types: [
{ title: "Image files", extensions: "jpg,gif,png" },
{ title: "Zip files", extensions: "zip" }
]
},
init: {
PostInit: function () {
},
FilesAdded: function (up, files) {
plupload.each(files, function (file) {
uploader.start();
});
},
UploadProgress: function (up, file) {
},
Error: function (up, err) {
}
}
});
uploader.init();
uploader.bind('FileUploaded', function (upldr, file, object) {
var data = JSON.parse(object.response);
console.log(data);
$("#img").attr("src", data);
$("#imgvalue").val(data);
});
提交:

上傳成功后跳轉到detail頁面。

至此,圍繞form的提交這一節學習了Mongoose的數據驗證,以及使用plupload上傳,以及后端用formidable和fs模塊處理圖片。相對於Asp.net MVC而言,Asp.net MVC因為有自動化的form相對快捷一些。下一節將介紹Angular,作為MEAN中的A,該出場了。
你的關注和支持是我寫作的最大動力~
書山有路群:452450927
出處:http://www.cnblogs.com/stoneniqiu/
github:https://github.com/stoneniqiu

