Node(4)

一、一些小问题

1.文件操作路径和模块读取路径的问题

  • 我们使用fs核心模块系统进行文件操作时一般这样书写路径
fs.readFile('./views/index.html');//读取views目录下的idnex.html文件
  • 我们使用require()进行自定义模块加载时的路径一般这样写
require('js/main.js');//加载js目录下的main.js

注意区分两者的区别,./的含义是相对路径,代表当前目录。文件操作路径不可以省略,而模块读取路径可以省略。另外如果忽略.写成'/',那么/代表的是磁盘根目录。

2.让服务器自动重启的第三方模块nodemon

每次都要更新js后都需要手动重新启动服务器,比较麻烦,我们可以使用nodemon第三方模块来实现服务器自启动。

  • 安装nodemon
npm insatll --save nodemon -g
  • 使用
    在命令行中使用nodemon代替node命令即可:

当执行的js被执行保存时服务器就会重新启动。

二、在express中使用art-template

art-template专门为express配套了第三方模块:express-art-template,可参考官方文档进行使用:express-art-template

  • 安装
npm insatll --save art-template
npm insatll --save express-art-template
  • 注册模板引擎
//引入模块
var express = require('express');
var template =  require('express-art-template');

var app = express();

//在express中注册模板引擎
app.engine('.html',template);

使用app.engine(ext,callback)方法来注册模板引擎。callback是使用的模板引擎,ext是文件后缀。即当向页面呈现后缀为.ext的文件时,将使用指定的模板引擎进行渲染之后再呈现视图。

  • 使用
app.get('/',(req,res) => {
    res.render('./index.html',{comments:comments});
});

res.render(view [, locals] [, callback])方法就是上面说的呈现的视图的方法,即将某文件发送到页面上,一般就是html文件。
view是字符串,表示文件的路径;locals是一个对象,其属性定义了视图中的变量,模板引擎就会在内部根据传入的参数进行渲染;callback(err,html)是一个回调函数,err为错误对象,html为将要呈现是字符串。

  • 注意
    上面的视图资源路径为./index.html,但其实找的是views/index.html。这是express默认的规则,如果没有指定视图资源路径,默认查找views目录下的文件。如果需要自定义视图资源路径,请使用:
//设置views路径,可以传入单个目录或数组目录,查找时按照数组顺序查找
app.set('views',dir | dirArr)

三、案例:使用express实现留言板

1.express替代http

在之前的文章Node(2)中有一个留言板的案例,之前是使用http核心模块来实现的,现在我们使用express来实现。(html代码在之前的文章中,可自取)

var express = require('express');

//服务器
var app = express();
app.listen(8080,function(){
    console.log('server running at 8080');
});

var comments = [];


//注册art-template模板引擎
app.engine('html', require('express-art-template'));
//配置静态资源
app.use('/public',express.static('./public'));


//处理get请求
app.get('/',(req,res) => {
    res.render('./index.html',{comments:comments},function(err,html){
        res.send(html);
    });
});
app.get('/post',(req,res) => {
    res.render('./post.html');
});
app.get('/addComment',(req,res) => {
    //获取get提交的参数==>相当于使用url.parse(req.url,true).query;
    var query = req.query;
    query.dateTime = new Date().toLocaleString();
    comments.unshift(query);
    //重定向==>相当于res.statusCode=302;res.setHeader('Location','/')
    res.redirect(302,'/');
});

显而易见,之前的js代码有60余行,现在的js代码只有30余行。值得注意的几个点:

  • 我们在使用http核心模块时,还需要手动引入fs模块,art-template模块。现在使用了express之后不需要再手动引用,因为express内部会自动引用。
  • 之前的页面渲染逻辑是:fs读取html文件 --> 使用art-template的render()对读取的字符串进行渲染 --> 发送到页面。
  • 现在的页面渲染逻辑是:使用app.engine()注册模板引擎 --> 使用res.render()渲染页面并显示。
  • 其实express内部操作和我们之前的没有太大差别,但框架的这种封装特性极大地提高了我们的开发效率,然我们能更专注于业务。
  • 还有简化操作的api比如:req.query()其实就是之前的url.parse(req.url,true).query;;而res.redirect()是简化后的重定向。

2.post提交表单的处理

对于get提交,我们使用req.request即可获得提交的参数对象,但express没有提供post提交的参数获取的api。所以如果我们是post提交表单,就需要像之前一样使用req.on('data',backcall(postdata))来接受数据,且接受的postdata数据还需要进行解码,也是十分麻烦。所以这里我们推荐使用另一个第三方插件body-parser

  • 安装 body-parser
npm install body-parser --save
  • 在express中使用body-parser
//引入模块
var bodyParser = require('body-parser');

//使用
app.use(bodyParser.urlencoded({extended:false}));
app.use(bodyParser.json());

然后使用request.body就能获取到post提交的参数对象。
这里说一下app.use([path,] callback [, callback...])这一方法,该方法的作用是在指定的路径下安装指定的中间件函数,即每当请求路在是指定路径下时就执行中间件函数。
其中:
#参数path为可选参数,默认为/,表示执行根路径,即这个路劲下的所有请求都会执行。
#callback为中间件函数。如果需要执行多个函数,使用,隔开即可。
所以,use函数相当于一个过滤器,一般是增加一些属性或功能方便我们使用。
上面两个中间件函数:

// 创建 application/json 解析 
var jsonParser = bodyParser.json() 
// 创建 application/x-www-form-urlencoded 解析 
var urlencodedParser = bodyParser.urlencoded({ extended: false })

我们可以理解为,body-parser的这两个方法为response添加了body属性,值为post提交的参数对象。

四、案例:学生信息CRUD(增删改查)

1.准备相关文件

在bootstrap上随便扒的页面:

  • 主页index.html
<!DOCTYPE html>
<html lang="zh-CN">
  <head>
    <meta charset="utf-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1">
    <!-- 上述3个meta标签*必须*放在最前面,任何其他内容都*必须*跟随其后! -->
    <meta name="description" content="">
    <meta name="author" content="">
    <link rel="icon" href="../../favicon.ico">
    <title>Dashboard Template for Bootstrap</title>
    <!-- Bootstrap core CSS -->
   <link href="public/css/bootstrap.min.css" rel="stylesheet">
  </head>
  <body>
    <h2 class="text-center">学生信息表</h2>
    <h2 class="sub-header">
      <button class="btn btn-primary"><a style="color:white;" href="/students/add">添加学生</a></button>
    </h2>
    <table class="table table-striped">
        <thead>
          <tr>
            <th>#Id</th>
            <th>姓名</th>
            <th>性别</th>
            <th>年龄</th>
            <th>爱好</th>
            <th>操作</th>
          </tr>
        </thead>
        <tbody>
          {{each students}}
            <tr>
              <td>{{$value.id}}</td>
              <td>{{$value.name}}</td>
              <td>{{if $value.gender==0}}男{{else}}女{{/if}}</td>
              <td>{{$value.age}}</td>
              <td>{{$value.hobbies}}</td>
              <td><a href="/students/modify?id={{$value.id}}">
              修改
              </a>
              |<a href="/students/delete?id={{$value.id}}">
              删除
               </a></td>
            </tr>
          {{/each}}
        </tbody>
      </table>
  </body>
</html>
  • add.html
<!DOCTYPE html>
<html>
<head>
  <title>添加学生</title>
  <meta charset="utf-8">
   <link href="/public/css/bootstrap.min.css" rel="stylesheet">
</head>
<body>
  <h1 class="text-center">学生信息</h1>
  <form style="padding-left: 30px;" role='form' method="post" action="/students/add">
    <div class="form-group">
      <label>姓名</label>
      <input type="text" class="form-control" name="name" placeholder="请输入你的大名">
    </div>
     <div class="form-group">
      <label>性别</label>
      <div class="radio-inline">
        <label>
          <input type="radio" name="gender" value="0" checked>
         男
        </label>
      </div>
      <div class="radio-inline">
        <label>
          <input type="radio" name="gender" value="1">
         女
        </label>
      </div>
    </div>
    <div class="form-group">
      <label>年龄</label>
      <input type="number" name="age" min="0" max="800" value="18">
    </div>
    <div class="form-group">
      <label>爱好</label>
      <input type="text" class="form-control" name="hobbies" placeholder="请输入你的爱好">
    </div>
    <button type="submit" class="btn btn-primary">Submit</button>
  </form>
</body>
</html>
  • bootstrap.min.js自行下载

2.路由设计

路由可以理解为收发网络的设备,可以抽象为一个映射关系表。页面的请求和执行的回调函数也是一一对应的,为了方便管理,我们一般会进行的页面请求整体设计,也可以称为路由设计。

功能 请求方式 url get参数 post参数
学生信息页 get /students - -
增加学生页 get /students/add - -
增加学生 post /students/add - id&name&age&gender&hobbies
删除学生 get /students/delete id -
修改学生页 get /students/modify id
修改学生 post /students/modify - id&name&age&gender&hobbies

根据路由设计,写出请求结构:

//主页
app.get('/',(req,res) => {

});
//学生信息页
app.get('/students',(req,res) => {

});
//增加学生信息页
app.get('/students/add',(req,res) => {

});
//增加学生
app.post('/students/add',(req,res) => {

});
//删除学生
app.get('/students/delete',(req,res) => {

});
//修改学生
app.post('/students/modify',(req,res) => {

});

一般为了方便管理,也符合更好地设计思路,我们把这些请求单独放到一个js中,让每个js即每个模块都有各自统一的功能,而不是很杂:

  • app.js:程序的入口,负责服务器的创建及相关配置。
  • router.js:路由管理模块,存放路由设计信息。
    那么现在就有一个问题,如果将上面的代码直接放到router.js中,该如何调用?使用exports | module.exports即可。

3.使用json文件来充当数据库

既然要实现信息的CRUD,就需要一个数据库,但我们暂不需要使用数据库,使用一个json文件保存学生信息即可。

  • db.json
{
    "students":[
        {"id":1,"name":"Tom","gender":0,"age":18,"hobbies":"吃饭睡觉打豆豆"},
        {"id":2,"name":"Lucy","gender":0,"age":20,"hobbies":"吃饭睡觉打豆豆"},
        {"id":3,"name":"Michel","gender":0,"age":4,"hobbies":"吃饭睡觉打豆豆"},
        {"id":4,"name":"Afile","gender":0,"age":33,"hobbies":"吃饭睡觉打豆豆"},
        {"id":5,"name":"Joky","gender":0,"age":45,"hobbies":"吃饭睡觉打豆豆"}
    ]
}

测试一下:

  • app.js
var express = require('express');
var bodyParser = require('body-parser');


var app = express();
app.listen('8080',() => {
    console.log('server running at 8080');
});

//配置
app.use('/public',express.static('./public'));
app.use(bodyParser.json());
app.use(bodyParser.urlencoded({ extended: false }))

//路由
var router = require('./router.js');
router(app);
  • router.js
module.exports = function(app){
    var fs = require('fs');

    //主页(直接跳到学生信息页好了)
    app.get('/',(req,res) => {
        res.redirect(302,'/students');
    });
    //学生信息页
    app.get('/students',(req,res) => {
        //使用fs读取json文件
        fs.readFile('./db.json','utf8',(err,data) => {
            if(err)
                return res.status(500).send('500');
            var students = JSON.parse(data).students;
            res.render('./index.html',{
                "students":students
            });
        });
    });
    //增加学生信息页
    app.get('/students/add',(req,res) => {
        res.render('./add.html');
    });
    //增加学生
    app.post('/students/add',(req,res) => {
        
    });
    //删除学生
    app.get('/students/delete',(req,res) => {
        res.send('delete ok');
    });
    //修改学生
    app.post('/students/modify',(req,res) => {
        res.send('modify ok');
    });
}
  • 效果:

4.封装操作db.json文件的方法

在CRUD中我们会多次使用fs来读取文件,为了方便调用,我们有必要对重复性的工作进行封装。

  • student.js
var fs = require('fs');

/*
    将students放到db.json中
*/
function write(students){
    var db = {
        "students":students
    };
    var dbStr = JSON.stringify(db);
    fs.writeFile('./db.json',dbStr,'utf8',(err) => {
        if(err){
            return false;
        }
    });
}

/*获取全部学生信息
    return students[]
*/
function queryAll(callback){
    fs.readFile('./db.json','utf8',(err,data) => {
        if(err){
            callback({});
        }
        var students = JSON.parse(data).students;
        callback(students);
    });
}

/*根据id获取一个学生信息
    return Obj || null
*/
function queryById(id,callback){
    queryAll((students) => {
        var stu = null;
        students.forEach((value,index) => {
            if(value.id === id){
                stu = value;
                return;
            }
        });
        callback(stu);
    });
}

/*增加一个学生
*/
function add(stu){
    queryAll((students) => {
        students.push(stu);
        write(students);
    });
}

/*删除一个学生
    return stu
*/
function del(id,callback){
    queryById(id,(stu) => {
        if(stu === null){
            callback(stu);
            return;
        }
        queryAll((students) => {
             students = students.filter((value,index) => {
                return value.id !== id;
            });
            write(students);
            callback(stu);
        });
    });
}

/*修改一个学生
    return Boolean
*/
function modify(stu,callback){
    queryById(stu.id,(flag) => {
        if(flag === null){
            callback(false);
            return;
        }
        queryAll((students) => {
            students = students.map((value,index) => {
                if(value.id === stu.id){
                    return stu;
                }else{
                    return value;
                }
            });
            write(students);
            callback(true);
        });
    });
}

//将方法导出
exports.queryAll = queryAll;
exports.queryById = queryById;
exports.add = add;
exports.delete = del;
exports.modify = modify;
  • 值得注意的是,由于fs读取文件是异步操作,而我们又需要异步操作的结果,那我们就需要使用回调函数来获取异步操作的结果

  • 修改router.js

module.exports = function(app){
    var stuUtil = require('./student.js');

    //主页(直接跳到学生信息页好了)
    app.get('/',(req,res) => {
        res.redirect(302,'/students');
    });
    //学生信息页
    app.get('/students',(req,res) => {
        stuUtil.queryAll((students) => {
            res.render('./index.html',{
                "students":students
            });
        });
    });
    //增加学生信息页
    app.get('/students/add',(req,res) => {
        res.render('./add.html');
    });
    //增加学生
    app.post('/students/add',(req,res) => {
        var stu = req.body;
        stu.id = Math.floor(Math.random()*1000+1);//产生随机整数
        stuUtil.add(stu);
        res.redirect(302,'/students');
    });
    //删除学生
    app.get('/students/delete',(req,res) => {
        stuUtil.delete(parseInt(req.query.id),(flag) =>{
            if(flag)
                res.redirect(302,'/students');
            else
                res.send('500');
        });
    });
    //修改学生信息页
    app.get('/students/modify',(req,res) => {
        stuUtil.queryById(parseInt(req.query.id),(stu) => {
            res.render('./modify.html',{"stu":stu});
        });
    });
    //修改学生
    app.post('/students/modify',(req,res) => {
        req.body.id = parseInt(req.body.id);
        stuUtil.modify(req.body,(flag) => {
            if(flag)
                res.redirect(302,'/students');
            else
                res.send('500');
        });
    });
}
  • 效果

5.使用express提供的路由管理

具体参考官方文档

  • app.js
var express = require('express');
var bodyParser = require('body-parser');
//路由
var router = require('./router.js');

var app = express();
app.listen('8080',() => {
    console.log('server running at 8080');
});

//配置
app.use('/public',express.static('./public'));
app.engine('html',require('express-art-template'));
app.use(bodyParser.urlencoded({extended:false}));
app.use(bodyParser.json());
app.use(router);//挂载路由

module.exports = app;
  • router.js
var stuUtil = require('./student.js');
var express = require('express');
var router = express.Router();

//主页(直接跳到学生信息页好了)
router.get('/',(req,res) => {
    res.redirect(302,'/students');
});
//学生信息页
router.get('/students',(req,res) => {
    stuUtil.queryAll((students) => {
        res.render('./index.html',{
            "students":students
        });
    });
});
//增加学生信息页
router.get('/students/add',(req,res) => {
    res.render('./add.html');
});
//增加学生
router.post('/students/add',(req,res) => {
    var stu = req.body;
    stu.id = Math.floor(Math.random()*1000+1);//产生随机整数
    stuUtil.add(stu);
    res.redirect(302,'/students');
});
//删除学生
router.get('/students/delete',(req,res) => {
    stuUtil.delete(parseInt(req.query.id),(flag) =>{
        if(flag)
            res.redirect(302,'/students');
        else
            res.send('500');
    });
});
//修改学生信息页
router.get('/students/modify',(req,res) => {
    stuUtil.queryById(parseInt(req.query.id),(stu) => {
        res.render('./modify.html',{"stu":stu});
    });
});
//修改学生
router.post('/students/modify',(req,res) => {
    req.body.id = parseInt(req.body.id);
    stuUtil.modify(req.body,(flag) => {
        if(flag)
            res.redirect(302,'/students');
        else
            res.send('500');
    });
});

module.exports = router;
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 218,546评论 6 507
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 93,224评论 3 395
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 164,911评论 0 354
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 58,737评论 1 294
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 67,753评论 6 392
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 51,598评论 1 305
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 40,338评论 3 418
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 39,249评论 0 276
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 45,696评论 1 314
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,888评论 3 336
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 40,013评论 1 348
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,731评论 5 346
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 41,348评论 3 330
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,929评论 0 22
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 33,048评论 1 270
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 48,203评论 3 370
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,960评论 2 355

推荐阅读更多精彩内容