百度360必应搜狗淘宝本站头条
当前位置:网站首页 > 编程网 > 正文

Nodejs之MEAN栈开发(四)-- form验证及图片上传

yuyutoo 2024-11-14 19:54 3 浏览 0 评论

这一节增加推荐图书的提交和删除功能,来学习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:

function info (res, status) {
    var title, content;
    if (status === 404) {
        title = "404, 页面没有找到";
        content = "亲,页面飞走了...";
    } else if (status === 500) {
        title = "500, 内部错误";
        content = "尴尬...,发生错误";
    } else {
        title = status + ", 有什么不对劲";
        content = "某些地方可能有些错误";
    }
    res.status(status);
    res.render('info', {
        title : title,
        content : content,
        status: status,
    });
};

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,该出场了。

相关推荐

JavaWeb开发入门需要学哪些?看完你就懂了

目前,国内外信息化建设已经进入基于Web应用为核心的阶段,Java作为应用于网络的最好语言,前景无限看好。然而,就算用Java建造一个不是很烦琐的web应用,也不是件轻松的事情。那么,本文章就来详细说...

Spring Boot 中如何实现通过Cookie来实现用户鉴权?

Cookie是存储在用户浏览器中的一小段数据,主要的作用是用来帮助Web服务器和客户端之间的通信。这段数据由服务端生成,然后通过HTTP的头部信息反馈给客户端,然后再后续的操作中客户端会自动将这些Co...

Session和cookie笔记 session与cookie的关系

会话技术1.会话:一次会话中包含多次请求和响应。*一次会话:浏览器第一次给服务器资源发送请求,会话建立,直到有一方断开为止2.功能:在一次会话的范围内的多次请求间,共享数据3.方式:1.客户...

精读大型网站架构的技术细节:后端架构规整化Cookie和Session

Cookie和Session接口请求(HTTP请求)是无状态的,每次请求都是完全独立的。也就是说,在处理请求时,Web应用服务器无法得知这个请求是哪个用户发送的,无法跟踪上一次请求的状态。但是,有些时...

一文彻底搞懂Cookie、Session、Token到底是什么

一、Cookie洛:大爷,楼上322住的是马冬梅家吧?大爷:马都什么?夏洛:马冬梅。大爷:什么都没啊?夏洛:马冬梅啊。大爷:马什么没?夏洛:行,大爷你先凉快着吧。在了解这三个概念之前我们...

Python常用的第三方包-Requests 5个常用python第三方库

在现代互联网时代,进行网络请求是开发人员经常需要处理的任务之一。Python的Requests包是一个强大而易于使用的工具,可以简化网络请求的过程,并提供了丰富的功能。本文将深入介绍如何使用Pytho...

干货 | REST-assured 获取日志到文件并结合 Allure 报告进行展示

使用Rest-assured集合Allure运行完用例之后,在生成的报告中只有断言信息,没有请求的日志信息。而当我们的用例失败时,特别是接口失败时,请求日志是分析原因的第一手资源。那如何将R...

前后端数据交互(四)——fetch 请求详解

fetch是XMLHttpRequest的升级版,使用js脚本发出网络请求,但是与XMLHttpRequest不同的是,fetch方式使用Promise,相比XMLHttpReques...

【0基础学爬虫】爬虫基础之网络请求库的使用

大数据时代,各行各业对数据采集的需求日益增多,网络爬虫的运用也更为广泛,越来越多的人开始学习网络爬虫这项技术,K哥爬虫此前已经推出不少爬虫进阶、逆向相关文章,为实现从易到难全方位覆盖,特设【0基础学爬...

ASP.NET MVC开发日常一:SessionID更新

在MVCWeb开发中临时存储数据一般会用到Session,Cookie,ViewBag,ViewData,TempData。每个的使用场景是不同,具体区别有空再补上。Session数据最敏感,最需要...

如何抓取有密码网页表格数据?简单易行!

众所周知,表格是一种常见的数据展示方式,而在网络世界中,许多网站也会采用表格的形式展示数据。但如果需要抓取这些表格中的数据,尤其是有帐号密码保护的网页,该如何实现呢?本文将为大家提供一种简单易行的方法...

不背锅运维:Grafana的自动登入(Go和Python分别实现)

1.实现目标想要达到的目标是:当在浏览器向http://192.168.11.254:3090/auto_login这个地址发起GET请求后能够自动登入Grafana...

Python每日一库之requests python爬虫之requests库的下载

Pythonurllib...

Javaweb知识 day16 Cookie&Session

今日内容1.会话技术1.Cookie2.Session2.JSP:入门学习一、会话技术1.1概念:会话:浏览器第一次给服务器资源发送请求,会话建立,直到有一方断开为止,一次会话中包含多次请...

全网最全的python网络爬虫常用技术

前言urllib模块urllib库是python中自带的模块,也是一个最基本的网络请求库,该模块提供了一个urlopen()方法,通过该方法指定URL发送网络请求来获取数据。正文urllib是一个收...

取消回复欢迎 发表评论: