zoukankan      html  css  js  c++  java
  • Nodejs之MEAN栈开发(四)---- 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:

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

    源码:https://github.com/stoneniqiu/ReadingClub

  • 相关阅读:
    算法
    autoreleasepool和weak
    记录一下锁如何使用
    RunLoop相关知识
    NSTimer内存泄漏的解决方案
    ARC下block循环引用的解决方式
    MGJRouter源码解析及使用方法
    Instruments 中Time Profile的使用
    NSFileManager的基本使用
    Runtime相关知识
  • 原文地址:https://www.cnblogs.com/stoneniqiu/p/5613823.html
Copyright © 2011-2022 走看看