loading

Node


Nodejs编程


第0章 Node介绍

0.0 回顾 JavaScript

  • *历史及发展 *

    1995年 网景公司的布兰登开发;

    1997年7月,ECMA组织发布ECMAScript 1.0版;

    2007年10月发布3.1版本后不久,ECMAScript 3.1改名为 ECMAScript 5。

    2008年,为Chrome浏览器而开发的V8编译器诞生

    2011年6月,ECMAscript 5.1版发布,现在使用最为广泛的版本;

    2015年6月,ECMAScript 6正式发布,并且更名为“ECMAScript 2015”;

  • 如何学习JavaScript

    JavaScript 的核心语法部分相当精简,也就是语言本身,只包括两个部分:

    • 基本的语法构造(比如操作符、控制结构、语句)

    • 标准库(就是一系列具有各种功能的对象比如Array、Date、Math等)。

想要实现其他复杂的操作和效果,都要依靠宿主环境提供API,目前,已经嵌入 JavaScript 的宿主环境有多种,最常见的环境就是浏览器操作系统;

  • 回顾 JavaScript 语法特性
    • 变量、数据类型、流程控制
    • 函数(基本声明参数,作用域,回调函数)、面向对象(原型,构造函数,this的指向,new的过程)

0.1 Node是什么

Node是一个基于Chrome V8引擎的JavaScript运行环境。

Node不是一种独立的语言、Node不是JavaScript框架,

Node是一个除了浏览器之外的、可以让JavaScript运行的环境

Node.js 是一个让 JavaScript 运行在服务端的开发平台,是使用 事件驱动, 异步非阻塞I/O,单线程,跨平台的 JS 运行环境;

0.2. 为什么要学习 Node

  • 打开服务器的黑盒子
  • 企业需求
  • 大前端必备技能
  • 为了更好的学习前端框架

0.3. Node 能做什么

知乎 - Node.js能做什么,该做什么?

  • Web 服务器(重点)
  • 命令行工具
  • 网络爬虫:是一种按照一定的规则,自动地抓取网站信息的程序
  • 桌面应用程序开发

0.4. 一些资源

  1. 文档

    Node.js 官方文档
    Node.js 中文文档(非官方)

  2. 书籍

    深入浅出 Node.js
    Node.js 权威指南
    Node.js 实战
    Node.js实战(第2季)

  3. github资源

    Node.js 包教不包会
    ECMAScript 6 入门
    七天学会 NodeJS

  4. 社区

    Node.js 中文社区

0.5. Node 发展历史

聊聊 Node.js 的历史
来自朴灵大大的 – Node.js 简史

第1章 NodeJS起步

1.1 下载安装

windows下安装过程:

对于已经装过的,重新安装就会升级

安装成功后,打开命令行,输入

node –version 或者 node -v (显示node的版本号)

表示安装成功

其他平台的安装方式:

https://nodejs.org/zh-cn/download/package-manager/

1.2 REPL环境

node中的REPL环境类似于浏览器中的 Console控制台 ,可以做一些代码测试。

按ctrl + 两次c 退出REPL环境

但是, 我们写代码肯定不是在控制台中写,而是写在一个单独的.js文件中.

1.3 node运行js代码

1.4 Node 中的模块

浏览器(客户端)中的JS

Node中的JS

Buffer(二进制数据操作)

let buffer=new Buffer('abc\r\nddasdfafd\r\ndfaerewtwert');
let buffer2=new Buffer('\r\n');

console.log(buffer.indexOf(buffer2));
//Buffer.concat 可以传入一个装有buffer的数组,之后会自动拼接返回
//buffer数据可以用toString、queryString模块的的parse 转换为看的懂的数据

第2章 核心模块的使用

2.1 FS模块

node核心模块之一,用于操作文件;

中文手册 :http://nodejs.cn/api/fs.html

  • 文件读写
// 引入模块
var fs = require('fs');
// console.log(typeof fs); //object 

// 向文件中写入内容
fs.writeFile('./2.1.txt','itcast',function(cb,cb2){
    // 回调函数 (写入成功后执行的函数)
    console.log(cb);
    console.log(cb2);
})

// 从文件中读取内容
fs.readFile('./2.1.txt','utf8',function(e,d){
    // 回调函数 (读取成功后执行的函数)
    console.log(e);
    console.log(d);
});
  • 追加内容
// 引入模块
var fs = require('fs');

// 向文件中追加内容
fs.readFile('./2.1.txt','utf8',function(e,d){
    d+='2344';
    fs.writeFile('./2.1.txt',d,function(e){
        if(e){
            console.log('写入失败')
        }else{
            console.log('写入成功')
        }
    })
});

Zilb模块

2.2 HTTP模块

node核心模块之一,用于搭建HTTP服务器;

中文手册http://nodejs.cn/api/http.html

2.2.1 开启服务器

// 1. 导入http模块
var http = require('http');

// 2. 使用http这个模块中的createServer()创建一个服务器实例对象
var server = http.createServer();

// 3. 绑定端口号,启动web服务器
server.listen(8000, function() {
    console.log(' 请访问http://localhost:8000');
});

// 4. 为这个服务器实例对象注册 request 请求处理函数
// 请求处理函数function(形参1,形参2){}
// 形参1:request请求对象 获取到当前请求的路径,方法等本次请求的所有信息
// 形参2:response响应对象 发送响应数据
server.on('request', function(request, response) {
    console.log('服务端收到客户端的请求啦!!!');
    // 向客户端页面返回字符串
    response.write("hello node");
    // 结束响应
    response.end();
});

因为我们的服务器接受请求处理并响应数据时,并没有指定响应数据的类型,所以出现了乱码;

而在http中,我们可以通过服务器的响应头指定数据类型,在http.ServerResponse 类中为我们提供了setHeader 方法:

2.2.2 响应 HTML 页面

但是,我们不能一直将html代码写到服务器的方法中,而是需要建一个xx.html的文件,将html文件中的内容返回给客户端;

2.2.2 .html :

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Document</title>
</head>
<body>
    <h1>你好,我是jinghong</h1>
    <h2>另外,我还很帅……</h2>
</body>
</html>

nodejs代码

var http = require('http');
// 1:引入文件操作模块
var fs = require('fs');
var server = http.createServer();
server.on('request', function(request, response) {
    // 2:读取html文件中的内容
    fs.readFile('./2.2.2.html','utf8',function(error,html_data){
        // 设置响应头
        response.setHeader('Content-Type', 'text/html;charset=utf-8');
        // 将html中的内容响应回客户端,结束响应
        response.end(html_data);
    })
});

2.2.3 响应图片

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Document</title>
</head>
<body>
    <h1>你好,我是jinghong</h1>
    <h2>另外,我还很帅……</h2>
    <img src="./img/03.jpg" alt="野生脆脆.jpg">
</body>
</html>
server.on('request', function(request, response) {
    // url 属性返回请求的URL字符串
    var urls = request.url;
    if( urls =='/'){
        fs.readFile('./2.2.2.html','utf8',function(error,html_data){
            // 设置响应头
            response.setHeader('Content-Type', 'text/html;charset=utf-8');
            // 将html中的内容响应回客户端,结束响应
            response.end(html_data);
        })
    }else if(urls.indexOf('jpg')>=0){ // 判断请求图片
        fs.readFile('./img/03.jpg',function(error,html_data){
            response.end(html_data);
        })
    }
}

2.2.4 响应其他静态资源

<head>
    <meta charset="UTF-8">
    <title>Document</title>
    <link rel="stylesheet" href="./public/h.css">
</head>
<body>
    <h1>你好,我是jinghong</h1>
    <h2>另外,我还很帅……</h2>
    <img src="./img/03.jpg" alt="野生脆脆.jpg">
</body>
<script src="./public/h.js"></script>
</html>
server.on('request', function(request, response) {
    // url 属性返回请求的URL字符串
    var urls = request.url;
    if( urls =='/'){
        fs.readFile('./2.2.2.html','utf8',function(error,html_data){
            // 设置响应头
            response.setHeader('Content-Type', 'text/html;charset=utf-8');
            // 将html中的内容响应回客户端,结束响应
            response.end(html_data);
        })
    }else{
        fs.readFile('.'+urls,function(error,html_data){
            response.end(html_data);
        })
    }
});

2.3 服务器遍历文件及文件夹-案例

模仿Apache服务器,遍历文件及文件,显示时间及大小;

右键另存为,下载页面当作静态页面模板使用;

使用node载入静态页面:

使用ajax技术在页面中发送请求到后台,apache.html

<script>
    var xhr = new XMLHttpRequest();
    xhr.onreadystatechange=function(){
      if(this.readyState == 4){
        console.log(this.responseText);
      }
    }
    xhr.open('get','/file_list');
    xhr.send();
</script>

node:

server.on('request', function(request, response) {
    // url 属性返回请求的URL字符串
    var urls = request.url;
    if( urls =='/'){
        fs.readFile('./apache.html','utf8',function(error,html_data){
            // 设置响应头
            response.setHeader('Content-Type', 'text/html;charset=utf-8');
            // 将html中的内容响应回客户端,结束响应
            response.end(html_data);
        })
    }else if(urls == '/file_list'){
        fs.readdir('./','utf8',function(err,files){
            response.end(JSON.stringify(files));
        });
    }else{
        fs.readFile('.'+urls,function(error,html_data){
            response.end(html_data);
        })
    }
});

apache.html –> ajax

xhr.onreadystatechange=function(){
    if(this.readyState == 4){
        var data = JSON.parse(this.responseText);
        var htmls = '';
        for(var i = 0;i<data.length;i++){
            htmls+='<tr><td valign="top">';
            htmls+= '<img src="./img/layout.gif" alt="[   ]"></td>';
            htmls+='<td><a href="http://localhost/%e7%ac%94%e8%ae%b0-01.pdf">';
            htmls+= data[i]+'</a> </td>';
            htmls+= '<td align="right">2018-04-26 10:31 </td>';
            htmls+= '<td align="right">3.2M</td><td>&nbsp;</td></tr>';
        }
        var tb = document.getElementsByTagName('tbody')[0];
        tb.innerHTML+=htmls;
    }
}

2.4 动态展示文件的其他属性

获取文件的其他属性:

var fs = require('fs');
fs.readdir('./','utf8',function(err,files){
    fs.stat(files[0],function(er,st){
        console.log(st.mtime);
        console.log(st.size);
        console.log(st.isFile());
    })
});

修改node代码

server.on('request', function (request, response) {
    // url 属性返回请求的URL字符串
    var urls = request.url;
    if (urls == '/') {
        fs.readFile('./apache.html', 'utf8', function (error, html_data) {
            // 设置响应头
            response.setHeader('Content-Type', 'text/html;charset=utf-8');
            // 将html中的内容响应回客户端,结束响应
            response.end(html_data);
        })
    } else if (urls == '/file_list') {
        fs.readdir('./', 'utf8', function (err, files) {
            // response.end(JSON.stringify(files));
            var file_obj = [];
            //  判断条件:声明一个变量,这个变量用来记录两个数据的中数据的长度
            var count = 0;
            for (var i = 0; i < files.length; i++) {
                file_obj[i] = {};
                // 利用自调用匿名函数,保留i的变量值
                (function (i) {
                    fs.stat(files[i], function (er, st) {
                        count ++;
                        file_obj[i].name = files[i];
                        if(st.isFile()){
                            file_obj[i].type = 'file';
                        }else{
                            file_obj[i].type = 'dir';
                        }
                        file_obj[i].mtime = st.mtime;
                        file_obj[i].size = st.size;
                        // 当读取的文件个数与所有文件个数相等时
                        if(count == files.length){
                            response.end(JSON.stringify(file_obj));
                        }
                    })
                    // console.log(file_obj);
                })(i);

                // console.log(files[i]);
            }
        });
    } else {
        fs.readFile('.' + urls, function (error, html_data) {
            response.end(html_data);
        })
    }
});

修改 ajax代码

var xhr = new XMLHttpRequest();
xhr.onreadystatechange=function(){
    if(this.readyState == 4){
        var data = JSON.parse(this.responseText);
        var htmls = '';
        for(var i = 0;i<data.length;i++){
            htmls+='<tr><td valign="top">';
            if(data[i].type == 'file'){
                htmls+= '<img src="./img/layout.gif" alt="[   ]"></td>';
            }else{
                htmls+= '<img src="./img/folder.gif" alt="[   ]"></td>';
            }
            htmls+='<td><a href="">';
            htmls+= data[i].name+'</a> </td>';
            htmls+= '<td align="right">'+ data[i].mtime +'</td>';
            htmls+= '<td align="right">'+ data[i].size +'</td><td>&nbsp;</td></tr>';
        }
        var tb = document.getElementsByTagName('tbody')[0];
        tb.innerHTML+=htmls;
    }
}
xhr.open('get','/file_list');
xhr.send();

循环后 i 丢失的问题:


// var arr = ['a', 'b', 'c'];
// for (var i = 0; i < arr.length; i++) {
//     // 模拟延迟
//     setTimeout(function () {
//         console.log(arr[i]);
//     }, 1000);
// }

/*
 * *******************************************
 * 上面的代码 全部输出 undefined
 * *******************************************
 */ 

var arr = ['a','b','c'];
for(var i = 0; i < arr.length; i ++) {
    (function(i){
        // 模拟延迟
        setTimeout(function() {
            console.log(arr[i]);
        }, 1000);
   })(i);
}

path模块

const path=require('path');

let str='/root/a/b/1.txt';

console.log(path.dirname(str)); 当前文件的上层目录
console.log(path.basename(str)); 返回给定路径的最后一段
console.log(path.extname('index.html')) 返回当前文件扩展名,没则空

//console.log(path.resolve('/root/a/b', '../c', 'build', '..', 'strict'));
//console.log(path.resolve(__dirname, 'build || 文件名'));
给定的路径序列从右到左进行处理,每个后续的 path 前置,直到构造出一个绝对路径。 例如,给定的路径片段序列:/foo、 /bar、 baz,调用 path.resolve('/foo', '/bar', 'baz') 将返回 /bar/baz

process模块

const process=require('process'); //process能获取到本机的信息,方便于自动切换开发环境

let mode=(process.env.OS=='Windows_NT'?'dev':'prod');

module.exports={
  mode,
  ...(mode=='dev'?require('./config.dev'):require('./config.prod'))
};

第3章 包管理器npm

3.1 使用moment

使用第三方包格式化时间

3.2 npm 命令的使用

上面的代码,我们使用npm安装了moment来进行格式化时间的处理,这就是使用第三方模块;

而我们使用的npm就是node中自带的包(模块)管理工具;

借助NPM可以帮助我们快速安装和管理依赖包,使Node与第三方模块之间形成了一个良好的生态系统;

我们也可以直接输入npm,查看帮助引导:

PS C:\xamp\htdocs\ceshi\09> npm

Usage: npm 

where  is one of:
    access, adduser, audit, bin, bugs, c, cache, ci, cit,
    completion, config, create, ddp, dedupe, deprecate,
    dist-tag, docs, doctor, edit, explore, get, help,
    help-search, hook, i, init, install, install-test, it, link,
    list, ln, login, logout, ls, outdated, owner, pack, ping,
    prefix, profile, prune, publish, rb, rebuild, repo, restart,
    root, run, run-script, s, se, search, set, shrinkwrap, star,
    stars, start, stop, t, team, test, token, tst, un,
    uninstall, unpublish, unstar, up, update, v, version, view,
    whoami

npm  -h  quick help on 
npm -l            display full usage info
npm help    search for help on 
npm help npm      involved overview

Specify configs in the ini-formatted file:
    C:\Users\Administrator\.npmrc
or on the command line via: npm  --key value
Config info can be viewed via: npm help config

[email protected] C:\Program Files\nodejs\node_modules\npm

3.3 使用npm初始化项目

一个项目,不可能只是使用一个第三方包,而包越多,管理起来就越麻烦,

而 npm init 给我们提供了项目初始化的功能,也解决了多个包的管理问题:

"name": "usenpm", // 项目名
"version": "1.0.0", // 版本号
"description": "这是我们第一次使用npm",  // 描述信息
"main": "index.js", // 入口文件
"scripts": { // npm 设置的一些指令
    "test": "echo \"Error: no test specified\" && exit 1"
},
"keywords": [ // 关键字
    "第一次"
],
"author": "itheima6期", // 作者
"license": "ISC" // 当前项目的协议

补充 npm install 几种方式 及 区别 -s 和 -d

“dependencies” : 依赖模块 -s
“devDependncies”: 开发环境 -d
npm install axios -s 表示安装到依赖模块

3.4 解决 npm 被墙问题

npm 存储包文件的服务器在国外,有时候会被墙,速度很慢,所以我们需要解决这个问题。

http://npm.taobao.org/淘宝的开发团队把 npm 在国内做了一个备份。

安装淘宝的 cnpm:

# 在任意目录执行都可以
# --global 表示安装到全局,而非当前目录
# --global 不能省略,否则不管用
npm install --global cnpm

接下来你安装包的时候把之前的npm替换成cnpm

举个例子:

# 这里还是走国外的 npm 服务器,速度比较慢
npm install jquery

# 使用 cnpm 就会通过淘宝的服务器来下载 jquery
cnpm install jquery

如果不想安装cnpm又想使用淘宝的服务器来下载:

npm install jquery --registry=https://registry.npm.taobao.org

但是每一次手动这样加参数很麻烦,所我们可以把这个选项加入配置文件中:

# 配置到淘宝服务器
npm config set registry https://registry.npm.taobao.org

# 查看 npm 配置信息
npm config list

只要经过了上面命令的配置,则你以后所有的npm install都会默认通过淘宝的服务器来下载。

3.5 package.json 与 package-lock.json 文件

如果后期开发过程中,需要项目迁移,我们只需要将package.json文件迁移即可,在新项目下执行

npm install,所有第三方包会自动安装;

package.json的作用就是用来记录当前项目及包的使用情况;不能在package.json中添加注释

package-lock.json 保存第三方包的版本和下载路径等详细信息;

当我们使用npm管理包时,package.json 及package-lock.json 的内容都会自动更新

3.6 服务端页面渲染

之前的案例中,我们时通过前端浏览器发送ajax请求获取服务器数据的,前端获取数据后进行遍历展示;

缺点就是发送多次请求、不利于搜索引擎查找;我们修改改为后端渲染数据;

art-template:https://www.npmjs.com/package/art-template

var art = require('art-template');
art.defaults.root = './';
var html = art('./art-test.html',{data:[{name:123,age:345},{a:678,b:987}]});
console.log(html);
<body>
    <h1>nihoa</h1>
    <h2>{{data[0].name}}</h2>
</body>

1:重新创建目录,并初始化项目:npm init

2:将之前写好的后台文件 http.js 和 前台模板页面 apache.html 复制到新项目目录中;

3:安装时间处理模块:npm install moment

4:安装模板引擎模块:npm install art-template

5: 修改 后台文件 http.js 和 前台模板页面 apache.html 文件

http.js :

apache.html :

那么我们在项目中应该使用 客户端渲染还是服务端渲染:

答:两者都用,根据数据的不同作用而定;

推举一个node开发时使用的小工具 nodemon

npm install nodemon -g

安装成功后,使用 nodemon 运行代码,

代码一旦被保存,nodemon便会自动重新运行新代码

第4章 Node模块化及CommonJS规范

通过前面几个章节的学习, 我们基本掌握了NodeJS编程的基础知识, 但是我们也直观的发现了一个问题,和我们之前学习浏览器编程时JS, 差异还是很大的; 都是JavaScript编程, 为何有这种差异? 前面写过的防Apache服务器的案例中, 使用过内置fs模块, 使用过 moment 模块, 而这些模块都不是我们写的, 都是直接拿过来使用, 那么我们能不能自己写一个模块, 应该怎么写, 有哪些规矩, 如果我们自己写了一个模块, 能不能提供给其他编程人员直接使用, 应该怎么用?

Electron 跨平台的桌面应用框架:https://electronjs.org/

4.1 CommonJS规范的由来

JS 的表现的表现能力取决于宿主环境提供的API, 在web1.0 时代, W3C 组织提供了浏览器的规范支持, 在web2.0 时代, 随着HTML5的发展, 更多的标准API 出现在了浏览器中, 但是, 在后端 JS 中标准的制定纹丝不动 ;

由 Mozilla 工程师Kevin Dangoor于2009年1月提出名为ServerJS的规范; 2009年8月,更名为CommonJS,以显示 API 的更广泛适用性。

What I’m describing here is not a technical problem. It’s a matter of people getting together and making a decision to step forward and start building up something bigger and cooler together.

我在这里描述的不是一个技术问题。这是一个人们聚在一起,决定向前一步,开始一起建立更大更酷的东西的问题。

–Kevin Dangoor

深入浅出Node.js 图2-2

4.2 CommonJS 的模块规范

CommonJS对模块的定义十分简单,主要分为:

1、模块引用:

使用require()方法引入一个模块API ;

2、模块定义:

在模块中使用 exports 对象导出当前模块数据或方法;

在模块中还存在一个module对象,它代表模块自身,module对象有一个exports 属性,用于数据导出;

其实exports 对象就是module.exports 的引用;exports === module.exports

3、模块标识:

其实就是模块的文件名,必须符合小驼峰法命名规则,使用require()引入时使用. 或 ..开头的相对路径或绝对路径,引入时可以不写文件后缀名;

重点注意: 模块中的方法和变量的作用于尽在模块内部,每个模块具有独立的空间,互不干扰;

CommonJS 构建的模块机制中的引入与导出是我们完全不用考虑变量污染或者替换的问题,相比与命名空间的机制,简直就是天才和菜鸟的区别;

4.3 Node对CommonJS的实现 (Node模块化)

以上代码就是自定义模块的基本规则 这是重点

4.4 模块加载的顺序和规则

在 CommonJS 规范中,使用require()加载(引入) 模块时,模块标识必须使用相对路径或绝对路径指明模块位置,但是在node的实现中,我们可以不指明模块路径;如:require('fs')、require('moment');

如果没有指明路径,那就是加载核心模块或第三方模块,指明加载路径一般就是加载自定义模块;

不管加载什么模块,都是优先从缓存中加载:

Node 加载模块时,如果这个模块已经被加载过了,则会直接缓存起来,将来再次引用时不会再次加加载这个模块(即:如果一个模块被加载两次,则模块中的代码只会被执行一次)

而核心模块和第三方模块的的加载顺序就是:

先加载核心模块,核心模块的内容都是在安装node时已经编译好的可执行的二进制代码,加载执行的速度,仅次于缓存加载,如果核心模块中没有,则加载第三方模块

第三方模块的加载规则:

  • 先在当前文件的模块所属目录去找 node_modules目录
  • 如果找到,则去该目录中找 模块名的目录 如 : moment
  • 如果找到 moment 目录, 则找该目录中的 package.json文件
  • 如果找到 package.json 文件,则找该文件中的 main属性
  • 如果找到main 属性,则拿到该属性对应的文件
  • 如果找到 moment 目录之后,
    • 没有package.json
    • 或者有 package.json 没有 main 属性
    • 或者有 main 属性,但是指向的路径不存在
    • 则 node 会默认去看一下 moment 目录中有没有 index.js –> index.json–> index.node 文件
  • 如果找不到index 或者 找不到 moment 或者找不到 node_modules
  • 则进入上一级目录找 node_moudles 查找(规则同上)
  • 如果上一级还找不到,继续向上,一直到当前文件所属磁盘的根目录
  • 如果到磁盘概目录还没有找到,直接报错

4.5 模块化封装案例

修改 http.js — 服务器模块

var http = require('http');
var server = http.createServer();
var luyou = require('./luyou');

luyou.server(server);

server.listen(8000, function () {
    console.log('请访问 127.0.0.1:8000');
})

添加自定义模块 luyou.js – 路由模块

var contrllor = require('./contrllor');
var fs = require('fs');
function server(server) {
    server.on('request', function (request, response) {
        var urls = request.url;
        if (urls == '/') {
            var html = contrllor.html;
            response.setHeader('Content-Type', 'text/html;charset=utf-8');
            response.end(html);
        }
        else {
            fs.readFile('.' + urls, function (error, data) {
                response.setHeader('Content-Type', 'text/html;charset=utf-8');
                response.end(data);
            })
        }

        // response.end('123');
    });
}
exports.server = server;

contrllor.js — 业务模块

var fs = require('fs');
var moment = require('moment');
var template = require('art-template');
template.defaults.root = './';

fs.readdir('./', 'utf8', function (err, files) {
    var file_arr = [];
    var cont = 0;
    for (var i = 0; i < files.length; i++) {
        file_arr[i] = {};
        // console.log(files[i]);
        (function (i) {
            fs.stat(files[i], function (err, data) {
                cont++;
                // 闭包  
                if (data.isFile()) {
                    file_arr[i].type = 'file';
                } else {
                    file_arr[i].type = 'dir';
                }
                file_arr[i].name = files[i];
                file_arr[i].size = data.size;
                file_arr[i].mtime = moment(data.mtime).format('YYYY-MM-DD hh:mm');
                if (cont == files.length) {
                    var html = template('./apache.html',{data:file_arr});
                    exports.html = html;
                }
            })
        })(i);
    }
})

第5章 用户管理系统–项目

5.1 连接MySQL数据库

5.1.1 mysql基本CURD

SELECT `id`, `name` FROM `users` WHERE 1 

INSERT INTO `users`(`id`, `name`) VALUES ([value-1],[value-2])

UPDATE `users` SET `name`='value' WHERE id=1

DELETE FROM `users` WHERE id=value

5.1.2 安装连接MySQL

npm install mysql

mysql.js :

var mysql = require('mysql');
var connection = mysql.createConnection({
    host: 'localhost',
    user: 'root',
    password: '',
    database: 'onepiece'
});
// 查找操作
var select = 'select * from users';
connection.query(select, function (error, results, fields) {
    for(var i=0;i<results.length;i++){
        console.log(results[i].name);
    }
});

// 添加操作
var ins = "INSERT INTO users (`name`, `nengli`, `jituan`, `img`) VALUES ('娜美', '雷电', '草帽海贼团', '')";
connection.query(ins,function(err,results,field){
    console.log(results.insertId)
})

connection.end();

mysql 连接池

//连接池相比于上面的单次连接,更快速和更好管理,连接池可以设置并发连接数,一旦达到这个数,后续的连接只能等前面的连接执行完才能进行
var Pool = mysql.createPool({
    host: 'localhost',
    user: 'root',
    password: '',
    database: 'onepiece'
    })
//池选项
//connectionLimit要同时创建的最大连接数。(默认:10)
//queueLimit:  排队最大数  在返回错误之前,池将排队的最大连接请求数getConnection..如果设置为0,对排队的连接请求的数量没有限制。(0不做限制)

co-mysql 异步执行

//把所有mysql语句变为异步执行 可以使用 async\await  返回Promise对象


const mysql=require('mysql');
const co=require('co-mysql');
const {DB_HOST, DB_PORT, DB_USER, DB_PASS, DB_NAME}=require('../config');

let conn=mysql.createPool({
  host: DB_HOST,
  port: DB_PORT,
  user: DB_USER,
  password: DB_PASS,
  database: DB_NAME
});

module.exports=co(conn);

// promise
p.query('SELECT 1').then(...).catch(...);

//如:await db.query(`SELECT ID FROM user_table WHERE username='${username}'`);

5.2 项目初始化及安装第三方模块

新建目录haizei, 打开命令行执行npm init初始化项目;

一次性安装项目所需的所有模块;

npm install art-template mysql bootstrap jquery

5.3 启动项目

5.3.1 创建http服务器并加载静态页面

http.js

var http=require('http');
// 加载路由模块
var luyou = require('./luyou');
var server = http.createServer();

luyou.bind(server);

server.listen('8080',function(){
    console.log('请打开浏览器访问 http://127.0.0.1:8080');
});

luyou.js

var fs = require('fs');
// 引入业务模块使用模板引擎加载页面
var yewu = require('./yewu');
exports.bind = function (server) {
    server.on('request', function (request, response) {
        var urls = request.url;
        if (urls == '/') {
            var data = yewu.html_data;
            response.end(data);

        } else {
            fs.readFile('.' + urls, function (error, data) {
                response.end(data);
            })
        }
    })
}

yewu.js

var template = require('art-template');
template.defaults.root = './';
var html_data = template('./index.html',{data:123});

exports.html_data = html_data;

5.3.2 动态获取数据

yewu.js

var linkdb = require('./linkdb');
var template = require('art-template');
template.defaults.root = './';
console.log(linkdb.data);
var html_data = template('./index.html',{data:linkdb.data});

exports.html_data = html_data;

linkdb.js

var mysql = require('mysql');

var mysql = require('mysql');
var connection = mysql.createConnection({
    host: 'localhost',
    user: 'root',
    password: '',
    database: 'onepiece'
});

connection.connect();

var sql = "select * from users ";

connection.query(sql,function(error,data,res){
    console.log(data);
    exports.data = data
});

connection.end();

通过连接数据查找到的数据,对外导出时,导不出去,引入linkdb的业务模块,接不到数据;

关键:这个问题出现的原因很重;

5.3.3 解决问题

yewu.js

var linkdb = require('./linkdb');
var template = require('art-template');
template.defaults.root = './';

// 使用linkdb模块导出的方法
linkdb.query(function(data){
    // 利用回调函数获取数据
    var html_data = template('./index.html',{data:data});
    exports.html_data = html_data;
});

linkdb.js

var mysql = require('mysql');

var connection = mysql.createConnection({
    host: 'localhost',
    user: 'root',
    password: '',
    database: 'onepiece'
});
connection.connect();
// 将原来导出数据的方式改为导出方法,供模块加载者调用
exports.query = function(callback){
    var sql = "select * from users ";
    connection.query(sql,function(error,data,res){
        // console.log(data);
        // 数据是通过回调函数的方式返回
        callback(data)
    });
    connection.end();
}

根据模板引擎语法 修改静态页面

<tbody id="tbody">
    {{each data}}
    <tr>
        <td>{{$value.id}}</td>
        <td>{{$value.name}}</td>
        <td>{{$value.nengli}}</td>
        <td>{{$value.jituan}}</td>
        <td>
            <a href="#">查看</a>
            <a href="#">修改</a>
            <a href="#">删除</a>
        </td>
    </tr>
    {{/each}}
</tbody>

5.4 获取单个用户信息

5.4.1 接受前台请求

修改 luyou.js 路由模块,获取单个用户信息

server.on('request', function (request, response) {
    var urls = request.url;
    console.log(urls);
    if (urls == '/') {
        var data = yewu.html_data;
        response.end(data);

    }else if(urls == '/getuser'){
        response.end('getsssss');
    }else {
        fs.readFile('.' + urls, function (error, data) {
            response.end(data);
        })
    }
})

但是,luyou模块,无法处理前台不同类型的请求, 需要我们在服务器端接受并处理客户端发送的get 及 post请求;

5.4.2 获取请求类型及参数

GET 请求把所有的内容编码到访问路径中,POST 请求的内容全部都在请求体中。
http.ServerRequest 并没有一个属性内容为请求体,原因是等待请求体传输可能是一件
耗时的工作,譬如上传文件。而很多时候我们可能并不需要理会请求体的内容,恶意的 POST
请求会大大消耗服务器的资源。所以 Node.js 默认是不会解析请求体的,当我们需要的时候,
只能手动来做

网络调试工具Postman,可以帮助我们发送各种HTTP请求,并接受服务器返回的数据;

https://www.getpostman.com/

获取请求类型

var http = require('http');
var server = http.createServer();

server.on('request', function(request, response) {
    // 获取请求类型
    var method = request.method;
    console.log(method);
    response.end();
});

server.listen(8000, function() {
    console.log(' 请访问http://localhost:8000');
});

获取 GET 的请求参数

获取Post请求参数

else if (method == "POST") {
        // url_obj = url.parse(request.url,true);
        // console.log(url_obj.query); 

        //以上代码 无内容,失败
        // POST请求的内容全部都在请求体中 
}

手册中明确说明:

为了支持各种可能的 HTTP 应用,Node.js 的 HTTP API 是非常底层的。 它只涉及流处理与消息解析。 它把一个消息解析成消息头和消息主体,但不解析具体的消息头或消息主体。

因此我们需要查找更底层的网络实现,node中的基础网络模块net模块:http://nodejs.cn/api/net.html:

else if (method == "POST") {
    // url_obj = url.parse(request.url,true);
    // console.log(url_obj.query); 

    //以上代码 无内容,失败
    // POST请求的内容全部都在请求体中 

    var data = '';
    // net 模块中的 net.Socket 提供的data及end事件 
    // 绑定data事件获取数据
    request.on('data', function (che) {
        data += che;
    })
    // 绑定end事件,监听数据接受完成
    request.on('end', function () {
        // console.log(data);
        console.log(require('querystring').parse(data));
    })
}

5.5 获取单个用户信息

1: 修改路由逻辑,优先判断请求类型:

var fs = require('fs');
var url = require('url');
// 引入业务模块使用模板引擎加载页面
var yewu = require('./yewu');

exports.bind = function (server) {
    server.on('request', function (request, response) {
        // 优先判断请求方式
        var method = request.method;
        // 解析URL参数
        var url_obj = url.parse(request.url, true);
        if(method == 'GET'){
            // console.log(url_obj.query);//参数

            if (url_obj.pathname == '/') {
                var data = yewu.html_data;
                response.end(data);

            // 判断 获取单个用户信息路由 
            }else if(url_obj.pathname == '/getuser'){

            }else {
                fs.readFile('.' + url_obj.pathname, function (error, data) {
                    response.end(data);
                })
            }
        }else if(method == 'POST'){

        }
    })
}

2: 修改静态模板,添加查看连接

 <td>
     <a href="/getuser?id={{$value.id}}">查看</a>
     <a href="#">修改</a>
     <a href="#">删除</a>
</td>

3:修改路由,获取get请求参数,并将id参数传入业务模块

// 判断 获取单个用户信息路由 
else if(url_obj.pathname == '/getuser'){
    // 获取id参数
    var id = url_obj.query.id;
    // 在yewu模块中封装方法,传递id参数。
    // 使用回掉函数获取数据
    yewu.gets(url_obj.query.id,function(data){
        response.end(data);
    });
}

4:修改业务模块,添加gets方法

// 封装gets方法并导出,接受id参数及回调函数
exports.gets = function(id,callback){
    // 调用数据库模块的getone方法,并传入id参数
    linkdb.getone(id,function(data){
        // 利用回调函数获取数据
        var user_data = template('./users.html',{data:data});
        callback(user_data);
    });
}

5:添加users.html静态模板

<body>
    {{data[0].name}}
    {{data[0].nengli}}
    {{data[0].jituan}}
    {{data[0].name}}
    {{data[0].name}}
</body>

6:修改数据库模块,添加getone方法

exports.getone = function(id,callback){
    var sql = "select * from users where id="+id;
    // console.log(sql);
    connection.query(sql,function(error,data,res){
        console.log(data);
        // 数据是通过回调函数的方式返回
        callback(data)
    });
    // connection.end();
}

注意:将数据库模块中的所有 connection.end(); 删除,因为我们有多个方法,不能在方法调用中停止数据库的连接,否则,其他方法在后续调用中无法连接数据;

5.6 链式操作原理解析

链式操作的核心原理:

test.js

var c = require('./chained1');
// c.select();
c.where('id=2').select();

chained.js

module.exports = {
    where: function (wh) {
        this.whe = wh;
        // 链式操作的核心就是保存数据并返回本对象
        return this;
    },
    select: function () {
        if (this.whe == undefined) {
            var sql = "select * from users ";
        } else {
            var sql = "select * from users where " + this.whe;
        }
        console.log(sql);
        this.whe = undefined;
    },
    update:function(){
        if (this.whe == undefined) {
            console.log('更新数据时,请填写where条件');
            return;
        } else {
            var sql = "update xx from  set () where " + this.whe;
        }
        console.log(sql);
        this.whe = undefined;
    }
}

5.7 使用链式操作灵活操作数据库

1:新建数据操作模块 db.js

var mysql = require('mysql');
var connection = mysql.createConnection({
    host: 'localhost',
    user: 'root',
    password: '',
    database: 'onepiece'
});

module.exports = {
    that: this,
    where: function (wh) {
        this.whe = wh;
        return this;
    },
    select: function (callback) {
        if (this.whe == undefined) {
            var sql = "select * from users ";
        } else {
            var sql = "select * from users where " + this.whe;
        }
        // 用完后重置where条件,以免后续操作的数据重复
        this.whe = undefined;
        connection.query(sql, function (error, results, fields) {
            if (error) {
                callback(error);
                return;
            }
            callback(results);
        });
    }
}

2:修改业务模块(yewu.js)的调用

// var linkdb = require('./linkdb');
var db = require('./db');
var template = require('art-template');
template.defaults.root = './';

module.exports = {
    getall: function (callback) {
        db.select(function (data) {
            // 利用回调函数获取数据
            var html_data = template('./index.html', { data: data });
            callback(html_data);
        });
    },
    getone:function(id,callback){
        db.where('id='+id).select(function(data){
            var user_data = template('./users.html',{data:data});
            callback(user_data);
        });
    }

}

3:修改路由模块(luyou.js)的调用

var fs = require('fs');
var url = require('url');
// 引入业务模块使用模板引擎加载页面
var yewu = require('./yewu');

exports.bind = function (server) {
    server.on('request', function (request, response) {
        // 优先判断请求方式
        var method = request.method;
        // 解析URL参数
        var url_obj = url.parse(request.url, true);
        if(method == 'GET'){
            // console.log(url_obj.query);//参数
            if (url_obj.pathname == '/') {
                yewu.getall(function(data){
                    response.end(data);
                })

            // 判断 获取单个用户信息路由 
            }else if(url_obj.pathname == '/getuser'){
                // 获取id参数
                var id = url_obj.query.id;
                // 在yewu模块中封装方法,传递id参数。
                // 使用回掉函数获取数据
                yewu.getone(url_obj.query.id,function(data){
                    response.end(data);
                });
            }else {
                fs.readFile('.' + url_obj.pathname, function (error, data) {
                    response.end(data);
                })
            }
        }else if(method == 'POST'){

        }
    })
}

5.8 修改用户信息

5.8.1 修改模板代码,添加链接

<tbody id="tbody">
    {{each data}}
    <tr>
        <td>{{$value.id}}</td>
        <td>{{$value.name}}</td>
        <td>{{$value.nengli}}</td>
        <td>{{$value.jituan}}</td>
        <td>
            <a href="/getuser?id={{$value.id}}">查看</a>
            <a href="/upuser?id={{$value.id}}">修改</a>
            <a href="#">删除</a>
        </td>
    </tr>
    {{/each}}
</tbody>

5.8.2 获取用户信息并填入表单

1:添加路由判断(luyou.js)

// 修改用户信息,先获取用户信息    
}else if(url_obj.pathname == '/upuser'){
    // 获取id参数
    var id = url_obj.query.id;
    // 在yewu模块中封装方法,传递id参数。
    // 使用回掉函数获取数据
    yewu.upuser_get(url_obj.query.id,function(data){
        response.end(data);
    });
}else {

2:添加业务逻辑(yewu.js),获取用户信息

upuser_get:function(id,callback){
    db.where('id='+id).select(function(data){
        var user_data = template('./upuser.html',{data:data});
        callback(user_data);
    });
}

3:添加表单模板(upuser.html)

<style>
    table {
        margin: 0 auto;
        border-collapse: collapse;
        width: 800px;
        height: 500px;
    }

    td {
        border: 1px solid #ccc;
    }
</style>

<body>
    <form action="/upuser?id={{data[0].id}}" method="post">
        <table>
            <tr>
                <td>姓名</td>
                <td><input name="name" type="text" value="{{data[0].name}}"></td>
            </tr>
            <tr>
                <td>能力</td>
                <td><input name="nengli" type="text" value="{{data[0].nengli}}"></td>
            </tr>
            <tr>
                    <td>团体</td>
                    <td><input name="tuanti" type="text" value="{{data[0].jituan}}"></td>
                </tr>
            <tr>
                <td>上传图片</td>
                <td><input type="file"></td>
            </tr>
            <tr>
                <td></td>
                <td><input type="submit" value="修改"></td>
            </tr>
        </table>
    </form>
</body>

5.8.3 获取并处理post提交数据,修改数据库

路由模块(luyou.js)

 else if (method == 'POST') {
     // 只要是POST请求,则优先获取数据
     // 后处理路由逻辑
     var data = '';
     request.on('data', function (che) {
         data += che;
     })
     // 绑定end事件,监听数据接受完成
     request.on('end', function () {
         // console.log(data);
         // 获取Post传输的数据
         var post_data = querystring.parse(data);
         // 路由判断
         if (url_obj.pathname == '/upuser') {
             // 调用业务层方法
             yewu.upuser_post(url_obj.query.id, post_data, function (data) {
                 response.end(data);
             });
         }
     })

 }

业务模块(yewu.js)

upuser_post:function(id,data,callback){
    // 调用数据模块修改用户信息
    db.where('id='+id).update(data,function(changedRows){
        // http服务器相应要求必须是字符串
        callback(changedRows.toString());
    })
}

数据库模块(db.js)

update:function(data,callback){
    // 没where条件,则停止所有操作
    if(this.where == undefined){
        callback('error');
        return;
    }
    // 拼接sql语句的set部分
    var set = '';
    for(v in data){
        set += v +"='"+ data[v]+"',";
    }
    var sets = set.slice(0,set.length-1);
    var sql = "UPDATE `users` SET "+sets + ' where '+ this.whe;
    // console.log(sql);
    this.whe = undefined;
    connection.query(sql,function(error,res,fields){
        // console.log(error)
        // 返回受影响行数
        callback(res.changedRows);
    })
}

作业: 自己完成添加及删除操作

第7章 express 框架

7.1 简介

Express 是基于 Node.js 平台,快速、开放、极简的 Web 开发框架, 提供一系列强大特性帮助你创建各种Web应用。Express 不对 node.js 已有的特性进行二次抽象,我们只是在它之上扩展了Web应用所需的功能。丰富的HTTP工具以及来自Connect框架的中间件随取随用,创建强健、友好的API变得快速又简单

7.2 安装使用

就像一个普通的第三方模块一样安装即可;

npm initnpm install express

var express = require('express');
var app = express();

app.get('/',(req,res)=>{
    res.send('hello world !');
})

app.listen('8000',()=>{
    console.log('127.0.0.1:8000')
})

其中Request、Response – API我们需要重点关注

第8章 项目的重构

将我们之前的海贼王项目使用express框架进行重写,重写过程中,学习框架提供的各种API,并完善项目功能;

8.1 启动服务器

创建http.js

var express = require('express');
var app = express();

app.listen('8000',()=>{
    console.log('127.0.0.1:8000')
})

8.2 重写路由模块

之前我们写了一个独立的模块(luyou.js)来处理请求,而在 express 中已经帮我们写好了路由的请求处理规则,不需要我们进行判断;

路由是指确定应用程序如何响应对特定端点的客户端请求,该请求是URI(或路径)和特定HTTP请求方法(GET,POST等)。

每个路由都可以有一个或多个处理函数,这些函数在路由匹配时执行。

8.2.1 express 中的基本路由

路径定义采用以下结构:

app.method(path, handler)

以下示例定义了简单路由。

Hello World!在主页上回复:

app.get('/', function (req, res) {
  res.send('Hello World!')
})

在根路由(/),应用程序的主页上响应POST请求:

app.post('/', function (req, res) {
  res.send('Got a POST request')
})

响应对/user路由的PUT请求:

app.put('/user', function (req, res) {
  res.send('Got a PUT request at /user')
})

响应对/user路由的DELETE请求:

app.delete('/user', function (req, res) {
  res.send('Got a DELETE request at /user')
})

8.2.2. 外置路由

设置 外置路由 rout.js

var express = require('express');
var router = express.Router();

router.get('/',(req,res)=>{
    res.send('123');
})
router.get('/user',(req,res)=>{
    res.send('user');
})
router.post('/edit',(req,res)=>{
    res.send('post_edit');
})

// 导出 router
module.exports = router;

写好路由规则,一定要记得将 路由对象(router) 导出

var express = require('express');
var app = express();

// 引入外置路由
var rout = require('./rout');
app.use(rout); // 使用引入外置的路由

app.listen('8000',()=>{
    console.log('127.0.0.1:8000')
})

将外置路由引入后,使用 app.use() 进行加载使用;

8.2.3 使用外置路由修改项目

在 luyou.js 中,注释以前的代码,添加新代码

var express = require('express');
var yewu = require('./yewu');
var router = express.Router();

router.get('/', (req, res) => {
    yewu.getall(function (data) {
        res.end(data);
    })
})

module.exports = router;

在 http.js 中,使用 express 启动服务,并引入使用新修改的 luyou.js 模块

var express = require('express');
var app = express();

var luyou = require('./luyou');
app.use(luyou);

app.listen('8080',()=>{
    console.log('127.0.0.1:8080')
})

8.2.4 使用链式操作添加路由

luyou.js

var express = require('express');
var yewu = require('./yewu');
var router = express.Router();

// express的路由支持链式操作
router
.get('/', (req, res) => {
    yewu.getall(function (data) {
        res.end(data);
    })
})
.get('/getuser', (req, res) => {
    // req 提供了query属性获取请求参数
    var id = req.query.id;
    // 在yewu模块中封装方法,传递id参数。
    // 使用回掉函数获取数据
    yewu.getone(id, function (data) {
        res.end(data);
    });
})

module.exports = router;

8.2.5 优化路由模块

路由模块 (luyou.js) 中只负责调用,调用时,将req res 传入业务模块

router
.get('/', (req, res) => {
    yewu.getall(req,res);
    // yewu.getall(function (data) {
    //     res.end(data);
    // })
})
.get('/getuser', (req, res) => {
    yewu.getone(req,res);
    // req 提供了query属性获取请求参数
    // var id = req.query.id;
    // // 在yewu模块中封装方法,传递id参数。
    // // 使用回掉函数获取数据
    // yewu.getone(id, function (data) {
    //     res.end(data);
    // });
})

业务模块接受 req res 负责处理请求并响应数据

getall: function (req,res) {
    db.select(function (data) {
        // 利用回调函数获取数据
        var html_data = template('./index.html', { data: data });
        // console.log(html_data);
        res.end(html_data);
    });
},
getone:function(req,res){
    db.where('id='+req.query.id).select(function(data){
        var user_data = template('./users.html',{data:data});
        res.end(user_data);
    });
},

继续简化路由模块

router
.get('/',yewu.getall)
.get('/getuser',yewu.getone)

原理:

function fn(callback){
    var a = 1;
    var b = 2;
    callback(a,b);
}

var pros = function(a,b){
    console.log(a+b);
}

fn(function(a,b){
    pros(a,b);
})

// fn(pros);

8.3 重写模板引擎

[email protected] 新特性

express-art-template

安装:

npm install --save art-template
npm install --save express-art-template

官方示例:

var express = require('express');
var app = express();
app.engine('art', require('express-art-template'));
app.set('view options', {
    debug: process.env.NODE_ENV !== 'production'
});

app.get('/', function (req, res) {
    res.render('index.art', {
        user: {
            name: 'aui',
            tags: ['art', 'template', 'nodejs']
        }
    });
});

修改 http.js 将 express-art-template 注册为express框架的模板引擎,并设置模板后缀为 html

在项目中新建views目录,将所有静态页面放入views目录

8.4 利用 Express 托管静态文件

http://www.expressjs.com.cn/starter/static-files.html

在项目中新建 public 文件夹并将bootstrap.css移入, 修改 index.html 加载 css 静态文件 ,在http.js中引入并设置静态资源加载路径:

如果要使用多个静态资源目录,请多次调用express.static函数:

app.use(express.static('public'))
app.use(express.static('files'))

访问静态资源文件时,express.static函数会根据目录的添加顺序查找所需的文件。

8.5 完成项目重构

修改所有路由及业务模块代码

luyou.js

var express = require('express');
var yewu = require('./yewu');
var router = express.Router();

router
.get('/',yewu.getall)
.get('/getuser',yewu.getone)
.get('/upuser',yewu.upuser_get)
.post('/upuser',yewu.upuser_post);

module.exports = router;

yewu.js

// var linkdb = require('./linkdb');
var db = require('./db');
var querystring = require('querystring');
// template.defaults.root = './';

module.exports = {
    getall: function (req, res) {
        db.select(function (data) {
            res.render('index.html', { data: data });
        });
    },
    getone: function (req, res) {
        db.where('id=' + req.query.id).select(function (data) {
            res.render('./users.html', { data: data });
        });
    },
    upuser_get: function (req, res) {
        db.where('id=' + req.query.id).select(function (data) {
            res.render('./upuser.html', { data: data });
        });
    },
    upuser_post: function (req, res) {
        var data = '';
        req.on('data', function (che) {
            data += che;
        })
        // 绑定end事件,监听数据接受完成
        req.on('end', function () {
            // console.log(data);
            // 获取Post传输的数据
            var post_data = querystring.parse(data);

            // 调用数据模块修改用户信息
            db.where('id=' + req.query.id).update(post_data, function (changedRows) {
                // http服务器相应要求必须是字符串
                res.json(changedRows);
            })
        })
    }
}

8.6 展示用户头像

8.7 修改用户头像

8.7.1 测试文件上传

创建服务器:

var express = require('express');
var app = express();

app.post('/upfile',(req,res)=>{
    var data = '';
    req.on('data', function (che) {
        data += che;
    });
    req.on('end',()=>{
        // 直接打印post传过来的数据
        console.log(data);
    })
})

app.listen('8000',()=>{
    console.log('127.0.0.1:8000')
})

使用postman工具上传文件:

8.7.2 借助第三方插件处理文件上传

https://www.npmjs.com/package/formidable

安装模块npm install formidable

var express = require('express');
var formidable = require('formidable');
var fs = require('fs');
var app = express();

app.post('/upfile', (req, res) => {
    var form = new formidable.IncomingForm();

    // form.uploadDir = './img'; // 设置上传路径
    // form.keepExtensions = true; // 保留文件扩展名

    form.parse(req, function (err, fields, files) {
        var times = new Date().getTime();
        // 组装上传路径
        var file_path = './img/'+times+files.imgs.name;
        // 将缓存文件移动至制定目录
        fs.rename(files.imgs.path,file_path,(err)=>{
            console.log(file_path);
        });
        res.end();
    });
})

app.listen('8000', () => {
    console.log('127.0.0.1:8000')
})

8.7.3 在项目中实现文件上传

修改 upuser.html

修改业务模块 yewu.js 使用 formidable 获取 post 数据,实现文件上传

upuser_post: function (req, res) {
    var form = new formidable.IncomingForm();

    form.parse(req, function (err, fields, files) {
        var times = new Date().getTime();
        // 组装上传路径
        var file_path = './public/img/' + times + files.imgs.name;
        // 将缓存文件移动至指定的public目录
        fs.rename(files.imgs.path, file_path, (err) => {
            if(!err){
                // 因为设置静态资源时,已经时public文件夹,写入数据库时,不要加public
                fields.img = './img/' + times + files.imgs.name;
                db.where('id=' + req.query.id).update(fields, function (changedRows) {
                    // http服务器相应要求必须是字符串
                    res.json(changedRows);
                })
            }else{
                console.log('文件上传失败'+err);
            }
        });
    });
}

8.8 用户登陆

8.8.1 登陆逻辑及cookie-session 的使用

express官方资源中,为我们提供了一个中间件,cookie-session

npm install cookie-session

测试代码:

var express = require('express');
var cookieSession = require('cookie-session')
var app = express();

// 注册中间件 
app.use(cookieSession({
    name: 'session', // 客户端cookie的名称
    keys: ['ss'] // 用于加密的关键字
}))

app.get('/', (req, res) => {
    // 获取并判断session
    if(req.session.sess_data){
        res.send('已经登陆')
    }else{
        // 如果没有session,跳转到登陆页面
        res.send('<script>alert("没登陆");window.location.href="/up"</script>');
    }
})
app.get('/up', (req, res) => {
    // 展示登陆页面,获取用户数据并写入session 
    req.session.sess_data = {name:12,age:89};
    res.send('已写入session');
});

8.8.2 完成项目登陆功能

修改http模块,注册 cookie-session 中间件;

var express = require('express');
var app = express();
var cookieSession = require('cookie-session');

// 注册中间件 
app.use(cookieSession({
    name: 'session', // 客户端cookie的名称
    keys: ['xilingzuishuai'] // 用于加密的关键字
}))

在业务模块 (yewu.js) 中 添加逻辑判断,只有登陆后才能展示首页:

getall: function (req, res) {
    // 获取并判断session
    if (req.session.sess_data) {
        db.select(function (data) {
            res.render('index.html', { data: data });
        });
    } else {
        // 如果没有session,跳转到登陆页面
        res.send('<script>alert("没登陆");window.location.href="/upload"</script>');
        // res.send('没登陆');
    }
},

在路由模块(luyou.js) 中添加以下两个路由,get 展示静态登陆页面,post 获取用户提交的数据并写入 session ,写入成功后,跳转到首页;在业务模块(yewu.js)中添加响应的方法

.get('/upload',yewu.upload_get).post('/upload',yewu.upload_post)

upload_get: (req, res) => {
    // 展示登陆页面
    res.render('./upload.html', {});
},

upload_post: (req, res) => {
    var form = new formidable.IncomingForm();
    form.parse(req, function (err, fields, files) {
        // console.log(fields);
        // 获取用户提交数据,判断用户名密码是否正确
        if (fields.userName == "admin" && fields.pwd == "123") {
            // 数据正确,写入session
            req.session.sess_data = fields;
            res.send('<script>alert("登陆成功");window.location.href="/"</script>');
        }else{
            // 数据错误,重新跳回登陆页面
            res.send('<script>alert("登陆失败");window.location.href="/upload"</script>');
        }
    })
}

express另外附加使用技巧

  1. 处理get数据,使用express自带的req.query()
  2. 处理post数据,除了原生自带的,还可以借助第三方模块body-parser(在第三方模块中有介绍),作为中间加工步骤来处理post请求
  3. 处理文件请求,原生处理起来比较乏力,借助第三方可以快速便捷,如:formidable 在以上案例(用户头像中有介绍使用,文件请求和字段都可以一并处理)、multiparty(在第三方模块中有介绍,同样文件请求和字段都可以一并处理)、multer(在第三方模块中有介绍)只能处理文件,可以做为中间件使用
  4. cookie和seesion,可以使用第三方模块cookie-session(在用户登录中有介绍)、cookie-parser(在第三方模块中有介绍) 专门用来处理cookie,可以用来加密cookie,以上都可以作为中间件使用

第9章 Express的中间件

9.1 什么是中间件

在一个整体的流程中的某个环节,因为某些原因加入了额外的处理环节;

9.2 中间件的使用

9.2.1 应用中间件

语法:

  • app.use()

    • app.use(function(){})

      无论发送任何请求都会执行的中间件

    • app.use(‘/path’, function(){})

      只要在请求path路由时才会执行的中间件(无论GET/POST)

  • app.method()

    • app.get()

      在get请求时会执行的中间件

    • app.post()

      在post请求时会执行的中间件

app.use() 的用法

var express = require('express');
var app = express();

// 在中间件之前,不受中间件影响
app.get('/',function(req,res){
    console.log(123);
})

// 应用中间件
// 请求 '/user' 时,会先调用中间件
app.use(function (req, res, next) {
    console.log(req);
    next();
});

// 调用之前先调用中间件
app.get('/user',function(req,res){
    console.log('user');
})

app.listen('8000', () => {
    console.log('127.0.0.1:8000')
})

app.method() 的用法

var express = require('express');
var app = express();

// 在中间件之前,不受中间件影响
app.get('/',function(req,res){
    console.log(123);
})

// 应用中间件
// 只有在 post 请求user 时才起作用
app.post('/user',function (req, res, next) {
    console.log(req);
    next();
});

// 调用之前先调用中间件
// 接受所有请求方式请求user
app.all('/user',function(req,res){
    console.log('user');
})

app.listen('8000', () => {
    console.log('127.0.0.1:8000')
})

9.2.2 路由中间件

路由器层中间件的工作方式与应用层中间件基本相同,差异之处在于它绑定到express.Router()的实例。

使用router.use()router.METHOD()函数装入路由器层中间件;

我们之前项目的代码,就是在使用路由中间件:

var router = express.Router();

router
.get('/',yewu.getall)
.get('/getuser',yewu.getone)
.get('/upuser',yewu.upuser_get)
.post('/upuser',yewu.upuser_post)

.get('/upload',yewu.upload_get)
.post('/upload',yewu.upload_post)
;

9.2.3 内置中间件

express.static外,先前 Express 随附的所有中间件函数现在以单独模块的形式提供:中间件函数的列表

Express 中唯一内置的中间件函数是express.static。此函数基于serve-static,负责提供 Express 应用程序的静态资源。

对于每个应用程序,可以有多个静态目录:

app.use(express.static('public'));
app.use(express.static('uploads'));
app.use(express.static('files'));

9.2.4 第三方中间件

使用第三方中间件向 Express 应用程序添加功能。

安装具有所需功能的 Node.js 模块,然后在应用层或路由器层的应用程序中将其加装入。

var cookieSession = require('cookie-session');

// 注册中间件 
app.use(cookieSession({
    name: 'session', // 客户端cookie的名称
    keys: ['xilingzuishuai'] // 用于加密的关键字
}))

第10章 Koa 框架

Koa框架是express原帮人马打造,小且精干,express有的Koa都有,甚至比express还更有优势,如:在express中回调是必不可少的,语法大多停止在ES5版本,而Koa则使用generator、async等新特性解决了回调套回调,语法上也紧跟ECMAScript版本,是当下流行的框架

安装使用

1.安装 koa框架

npm init

npm install koa

提示:在express中路由自带,koa中没有自带路由,需自行下载(koa-router)

嵌套路由:

const koa=require('koa');   //加载koa
const Router=require('koa-router'); //加载koa路由框架

let server=new koa();
server.listen(8080);

//在koa中可以嵌套路由
let router = new Router(), //最顶级
UserRouter = new Router(), //用户路由

    //用户路由中子路由
 HomeRouter = new Router(),
 AdminRouter = new Router();
//路由函数有两个参数 一个是ctx上下文(什么都有) next中间函数(可以省略)
//函数前都加async,使用next也同样要加await
HomeRouter.get('/a',async(ctx)=>{
    console.log('前台用户')
    ctx.body = '前台用户';
})
AdminRouter.get('/a',async(ctx)=>{
    console.log('后台用户');
    ctx.body = '后台用户';
})

UserRouter.get('/',async(ctx,next)=>{
    console.log('用户模块')
    ctx.body = 'User';
})
//用户路由添加两个子路由
UserRouter.use('/home',HomeRouter.routes())
UserRouter.use('/admin',AdminRouter.routes())
//最顶级的路由添加用户路由作为子路由
router.use('/user',UserRouter.routes());
//最后添加到服务器上
server.use(router.routes());

/*
当前嵌套路由层级
router=> 
        UserRouter => HomeRouter
                     AdminRouter
*/
//上面是把路由写在一个文件,正常情况是分开写然后引入

嵌套路由(分开版)图片展示:

1.服务器页面(总页面)

2.路由文件:router/user/index.js

3.路由目录

4.子路由页面

4.1 admin

4.2 company

提示:可以在总页面上添加和user同级的多个路由

路由传参:

koa路由传参新方式(也可以使用旧方式)

const Koa=require('koa');
const Router=require('koa-router');

let server=new Koa();
server.listen(8080);

let router=new Router();

//路由传参   :字段 想传多少传多少 
//也可以  ?字段=值方式传值,只不过获取方式不同
router.get('/news/:id/', async (ctx, next)=>{

  let {id}=ctx.params;  // :字段方式传参的数据在ctx.params中

//启动后,访问/news/34 id就是34
  ctx.body='新闻id='+id;
  await next();
});
router.get('/news/1/', async ctx=>{
  let {id}=ctx.params;

  ctx.body+='aaa';
});

//当定义的函数像以上情况有点混淆的时候,谁在前,谁先执行,和express一样从上往下执行
server.use(router.routes());


/*
用?id=xxx传参和/:id有什么区别?倾向于用/:id吗?

urlencoded      http://aaa.com/user?a=12&b=5
params          http://aaa.com/user/12/5

    urlencoded      params
    顺序灵活         死的
    可省略           死的
    不利于SEO        利于SEO
*/

ctx对象属性介绍


ctx.params  //获取路由 :字段方式的数据
ctx.query    //获取路由 ?字段 = 值 方式的数据
ctx.method //请求方式
ctx.url//请求地址url

--------------------------------------------------------------------------------

server.context:// 相当于ctx的prototype

server.coconst Koa=require('koa');
const Router=require('koa-router');
let server=new Koa();
server.listen(8080);
server.context.a=12;  //在ctx原型添加属性
let router=new Router();
router.get('/news/', async ctx=>{
  let {id}=ctx.query;
  console.log(ctx.query);
  ctx.body='bbb'+ctx.a; //实例里获取
});
server.use(router.routes());

--------------------------------------------------------------------------------

ctx.request //请求
ctx.response//响应

--------------------------------------------------------------------------------

ctx.path        //路径
ctx.ip         // 客户端的IP

ctx.headers     //请求头

--------------------------------------------------------------------------------

ctx.throw(code, msg)   
ctx.assert(条件, code, msg)

ctx.throw(400, 'username is required');
 ctx.assert(ctx.query.pass, 400, 'password is required');
//一旦触发,会把错误信息响应到客户端
--------------------------------------------------------------------------------

ctx.state=305;  //状态码
ctx.redirect(); //重定向
ctx.attachment(); //下载文件

koa-static管理静态文件

koa-static 需自行下载,不自带

const Koa=require('koa');
const Router=require('koa-router');
const static=require('koa-static');
let server=new Koa();
server.listen(8080);
let router=new Router();
router.get('/user', async ctx=>{
});
server.use(router.routes());
//可以配合koa-router使用
let staticRouter=new Router();
staticRouter.all(/(\.jpg|\.png|\.gif)$/i, static('./static', {
  maxage: 60*86400*1000 //静态文件缓存时间  这里2个月
}));
staticRouter.all(/(\.css)$/i, static('./static', {
  maxage: 1*86400*1000
}));
staticRouter.all(/(\.html|\.htm|\.shtml)$/i, static('./static', {
  maxage: 20*86400*1000
}));
staticRouter.all('', static('./static', {
  maxage: 30*86400*1000
}));
//加载static中间件
server.use(staticRouter.routes());

//类似express的方式
server.use(static('public'))

//指定文件
server.use(static({
    index:'index.html'
}))

koa-better-body处理post请求和文件请求(可以一并处理,需自行下载)

const Koa=require('koa');
const Router=require('koa-router');
const body=require('koa-better-body');

let server=new Koa();
server.listen(8080);

server.use(body({
  uploadDir: './static/upload'
}));

server.use(async ctx=>{
  //文件和post数据
  console.log(ctx.request.fields);

  ctx.body='aaa';
});

koa中的 cookie、session

//在koa中cookie自带,session则没有,需要下载一个叫:koa-session模块

//----------cookie  注意:如果在设置cookie的时候设置了签名,获取的时候也要开签名  (signed) 否则被串改,服务器获取的数据将会是被串改的
server.keys = ['sadasdasdasdasdvvvvvvvvvsadasdasd'];//设置cookie签名的循环密匙
//设置和获取cookie在ctx上下文对象中
server.use( async (ctx)=>
ctx.cookies.set('user','xuyuxin',{
    signed:true  //开启签名 
    //cookie中可以设置的属性,在这就可以设置如:domain、maxAge
}
})

 console.log(ctx.cookies.get('user', {signed: true}));
//---------------------session
const Koa=require('koa');
const Router=require('koa-router');
const session=require('koa-session');
let server=new Koa();
server.listen(8080);
server.keys=[    //跟cookie相同
  'asdfasdfasdfasdfasdf',
  'hghjfgjghjkyggfytyurt',
  'hjghjkfguig8ygyi8t78i8',
];
server.use(session({
  maxAge: 20*60*1000,   //有效期
  renew: true           //自动续期
}, server));
server.use(async ctx=>{
  if(!ctx.session['view']){
    ctx.session['view']=0;
  }
  ctx.session['view']++;
  ctx.body=`欢迎你第${ctx.session.view}次来访`;
});

koa中的mysql

//一般在koa中,会把常用的模块或中间件放在 ctx.prototype(server.context)中,以便在任何地方使用
---------database.js
const mysql=require('mysql');
const co=require('co-mysql');

let conn=mysql.createPool({
  host: 'localhost',
  user: 'root',
  password: '',
  database: '20181101'
});
let db=co(conn);

module.exports=db;

----------引入
const Koa=require('koa');
const Router=require('koa-router');

let server=new Koa();
server.listen(8080);
server.context.db=require('./libs/database');
//引入后,就可在ctx中使用
server.get('/',async ctx=>{
   let data =  await ctx.db.query('SELECT * FROM tbale');
   ctx.body = JSON.stringify(data);
})

在koa中如何处理错误

const Koa=require('koa');
const Router=require('koa-router');

let server=new Koa();
server.listen(8080);

//以中间件方式处理
server.use(async (ctx, next)=>{
  try{
    await next();
  }catch(e){
    ctx.body='错了';
  }
});

//也可以用Router方式处理  * 代表全部,只要请求就会触发
let router=new Router();

router.all('*', async ctx=>{
  try{
    await next();
  }catch(e){
    ctx.body='错了-router';
  }
});

router.get('/a', async ctx=>{
  ctx.body=div.title;
});

server.use(router.routes());

koa-ejs模板引擎

ejs 文档

<% if (user) { %>
 <%= user.name %>
<% } %>

用法
var template = ejs.compile(str, options);
template(data);
// => 输出绘制后的 HTML 字符串

ejs.render(str, data, options);
// => 输出绘制后的 HTML 字符串

ejs.renderFile(filename, data, options, function(err, str){
    // str => 输出绘制后的 HTML 字符串
});


你可能需要能够输出原始内容的标签 (<%-) 用于 include 指令,避免对输出的 HTML 代码做转义处理。

<ul>
  <% users.forEach(function(user){ %>
    <%- include('user/show', {user: user}); %>
  <% }); %>
</ul>

自定义分隔符
可针对单个模板或全局使用自定义分隔符:

var ejs = require('ejs'),
users = ['geddy', 'neil', 'alex'];

// 单个模板文件
ejs.render('<?- users.join(" | "); ?>', {users: users},{delimiter: '?'});
// => 'geddy | neil | alex'

// 全局
ejs.delimiter = '?';
ejs.render('<?- users.join(" | ");?>', {users: users});
// => 'geddy | neil | alex'

//语法大多跟js一样
  • 参数
    cache 缓存编译后的函数,需要提供 filename
    filename 被 cache 参数用做键值,同时也用于 include 语句
    context 函数执行时的上下文环境
    compileDebug 当为 false 时不编译调试语句
    client 返回独立的编译后的函数
    delimiter 放在角括号中的字符,用于标记标签的开与闭
    debug 将生成的函数体输出
    _with 是否使用 with() {} 结构。如果为 false,所有局部数据将存储在 locals 对象上。
    localsName 如果不使用 with ,localsName 将作为存储局部变量的对象的名称。默认名称是 locals
    rmWhitespace 删除所有可安全删除的空白字符,包括开始与结尾处的空格。对于所有标签来说,它提供了一个更安全版本的 -%> (在一行的中间并不会剔除标签后面的换行符)。
    escape 为 <%= 结构设置对应的转义(escape)函数。它被用于输出结果以及在生成的客户端函数中通过 .toString() 输出。(默认转义 XML)。

  • 标签含义
    <% ‘脚本’ 标签,用于流程控制,无输出。
    <%_ 删除其前面的空格符
    <%= 输出数据到模板(输出是转义 HTML 标签)
    <%- 输出非转义的数据到模板
    <%# 注释标签,不执行、不输出内容
    <%% 输出字符串 ‘<%’
    %> 一般结束标签
    -%> 删除紧随其后的换行符
    _%> 将结束标签后面的空格符删除
    包含(include)
    通过 include 指令将相对于模板路径中的模板片段包含进来。(需要提供 ‘filename’ 参数。) 例如,如果存在 “./views/users.ejs” 和 “./views/user/show.ejs” 两个模板文件,你可以通过 <%- include(‘user/show’); %> 代码包含后者。

在koa中使用

const Koa=require('koa');
const ejs=require('koa-ejs');
const path=require('path');

let server=new Koa();
server.listen(8080);
//语法
ejs(server, {
  root: path.resolve(__dirname, 'template'), //根路径  文件夹名称
  layout: false, //模板在根目录中是否要再封装一个文件夹, 是的话写对应的文件名,否则放在根目录指定位置 默认是 true 自动在根目录加上一层文件夹 
  viewExt: 'ejs', //模板后缀
  cache: false, //是否缓存
  debug: false // 是否将渲染好文件展示
});

附加章 第三方模块

forever:node启动器 ,发生错误时也会重启,挂在后台

npm i forever -g  npm安装

//语法
forever start xxx.js   开启
forever restart xxx.js 重启
forever stop xxx.js  停止
forever stopall  停止全部

forever start xxx.js -l c:/a.log -e c:/err.log -a 

body-parser:用来处理post数据的模块之一

const express=require('express');
const body=require('body-parser');

let server=express();
server.listen(8080);
//在express中作为中间件使用
server.use(body.urlencoded({
  extended: false
}));
//最后在req的body中
server.post('/reg', (req, res)=>{
  console.log(req.body);
});
//注意,此模块不能处理上传文件请求

//用原生node封装一个类似body-parser的简易版
const express=require('express');

let server=express();
server.listen(8080);

const querystring=require('querystring');
//跟以上相同
server.use((req, res, next)=>{
  let arr=[];
  req.on('data', buffer=>{
    arr.push(buffer);
  });
  req.on('end', ()=>{
    let post=querystring.parse(Buffer.concat(arr).toString());

    req.body=post;
    next();
  });
});

server.post('/reg', (req, res)=>{
  console.log(req.body);
});
//把以上的中间件函数封装
const express=require('express');
const body=require('./libs/body-parser');

let server=express();
server.listen(8080);

server.use(body.urlencoded());

server.post('/reg', (req, res)=>{
  console.log(req.body);
});
//即可自己实现一个简易的中间件

multiparty:处理post中文件上传请求和字段请求(可以一并处理也可单一)

const http=require('http');
const multiparty=require('multiparty');

http.createServer((req, res)=>{
  let form=new multiparty.Form({
    uploadDir: './upload'
  });

  form.parse(req);

  form.on('field', (name, value)=>{
    console.log('字段:', name, value);
  });
  form.on('file', (name, file)=>{
    console.log('文件:', name, file);
  });

  form.on('close', ()=>{
    console.log('表单解析完成');
  });
}).listen(8080);
//请求到响应,中间如果出错,就算文件已经上传成功也会被删除

multer:专门用来处理文件的模块,其他不行

const express=require('express');
const multer=require('multer');

let server=express();
server.listen(8080);
//作为中间件使用
let obj=multer({dest: './static/upload'});
server.use(obj.any());

//最后处理完的数据存在req的files中
server.post('/reg', (req, res)=>{
  console.log(req.files);

  res.send('upload successed');
});
const express=require('express');
const cookieParser=require('cookie-parser');

let server=express();
server.listen(8080);
//签名加密
server.use(cookieParser(
  'fasdgfhsrtyredfbfd56te5645sdter76tytutyi456ythgfgerrhdfghfdg'
));

server.get('/a', (req, res)=>{
  console.log('cookie:', req.cookies);         //未签名的
  console.log('signed:', req.signedCookies);   //签名的


  res.cookie('amount', 99.8, {
    //httpOnly: true,   是否只能在服务端使用
    maxAge: 14*86400*1000,
    //secure: true,         只有https
    signed: true     //是否启用签名
  });


  res.send('ok');
});

uuid生成唯一标识符

//注意:此模块不能使用require('uuid')直接引入,uuid有很多个版本,因此直接引入会出错,需要指定对应版本引入 require('uuid/[v1|v3|v4|v5]')

版本1(时间戳):

const  uuidv1  =  require(' uuid / v1 ';
uuidv1(); // ⇨'2c5ea4c0-4067-11e9-8bad-9b1deb4d3b7d'
版本3(命名空间):

const  uuidv3  =  require(' uuid / v3 ';

// ...使用预定义的DNS命名空间(域名)
uuidv3( ' hello.example.com ', uuidv3。 DNS); // ⇨'9125a8dc-52ee-365b-a5aa-81b0b3681cf6'

// ...使用预定义的URL命名空间(,好了,URL)的
uuidv3( ' http://example.com/hello ', uuidv3。网址); // ⇨'c6235813-3ba4-3801-ae84-e0a6ebb7d138'

// ...使用自定义名称空间
// // 
//注意:自定义名称空间应为特定于您的应用程序的UUID字符串!
//例如,这里的一个是使用uuid CLI模块生成的。
const  MY_NAMESPACE  =  ' 1b671a64-40d5-491e-99b0-da01ff1f3341 ' ;
uuidv3( ' Hello,World!', MY_NAMESPACE); // ⇨'e8b5a51d-11c8-3310-a6ab-367563f20686'
版本4(随机):

const  uuidv4  =  require(' uuid / v4 ';
uuidv4(); // ⇨'1b9d6bcd-bbfd-4b2d-9b5d-ab8dfbbd4bed'
版本5(命名空间):

const  uuidv5  =  require(' uuid / v5 ';

// ...使用预定义的DNS命名空间(域名)
uuidv5( ' hello.example.com ', uuidv5。 DNS); // ⇨'fdda765f-fc57-5604-a269-52a7df8164ec'

// ...使用预定义的URL命名空间(,好了,URL)的
uuidv5( ' http://example.com/hello ', uuidv5。网址); // ⇨'3bbcee75-cecc-5b56-8031-b6641c1ed1f1'

// ...使用自定义名称空间
// // 
//注意:自定义名称空间应为特定于您的应用程序的UUID字符串!
//例如,这里的一个是使用uuid CLI模块生成的。
const  MY_NAMESPACE  =  ' 1b671a64-40d5-491e-99b0-da01ff1f3341 ' ;
uuidv5( ' Hello,World!', MY_NAMESPACE); // ⇨'630eb68f-e0fa-5ecc-887a-7c7a62614681'

jsonwebtoken 检验/生成token

const jwt = require('jsonwebtoken');
const Token = {
  encrypt:function(data,time){ //data加密数据,time过期时间
    return jwt.sign(data, 'token', {expiresIn:time})
  },
  decrypt:function(token){
    try {
      let data = jwt.verify(token, 'token');
      return {
        token:true,
        id:data.id
      };
    } catch (e) {
      return {
        token:false,
        data:e
      }
    }
  }
}
module.exports = Token;

//1. 首先安装jsonwebtoken  

npm install jsonwebtoken

//2. 引入jsonwebtoken

const jwt = require('jsonwebtoken');

//3. encrypt加密函数

//jsonwebtoken提供了一个函数sign用于加密生成jwt,格式jwt.sign(data,str,options)

//参数data 表示要加密的数据

//参数str 自定义字符串,这个字符串在解密时需要用到,在这里我随便写了一个‘token’。这相当于一个密钥secret,服务器端需要妥善保管。

//参数options 其他内容,可以设置令牌有效时间{expiresIn:time}。time的取值,'15d'表示15天,'2h'表示2小时,……

//4.decrypt解密函数

//jsonwebtoken提供了一个函数verify用于解密jwt,格式jwt.verify(token,str)

//参数token 表示需要解密的令牌 

//参数str 表示加密时用到的自定义字符串,即密钥

//5.用法

const Token = require('../utils/token')

//code……

const token = Token.encrypt({id:user.id},'15d');  //将user.id加密,设置有效期15天,返回token

//code……


//解密
let data = Token.decrypt(ctx.header.authorization);  //将请求头的token取出解密
if (data.token) {
    //有效token
}else{
    //无效token
}

参考资源相关列表:

https://nodejs.org/zh-cn/node.js官网

http://nodejs.cn/node.js中文网

《深入浅出Node.js》 朴灵著 ,人民邮电出版社

https://en.wikipedia.org/wiki/CommonJS维基百科

《ECMAScript 6 入门》(第三版)阮一峰著 ,电子工业出版社

《你不知道的JavaScript》(上、中、下卷) [美] Kyle Simpson 著 ,人民邮电出版社

http://www.expressjs.com.cn/express中文网


文章作者:Jing Hong
版权声明:本博客所有文章除特別声明外,均采用CC BY 4.0许可协议。转载请注明来源Jing Hong!
评论
 上一篇
VueVue
Vue.js第 0 章 Vue 介绍0.0 开发工程发展历史 通过前面的介绍,我们对目前的项目工程化有了大体了了解,那么其中,在第二阶段的工程化演进中,有一个重要的工程设计理念诞生,他就是著名的 MVC 设计模式,简单点,MVC 其实就是为
2019-11-06
下一篇 
CanvasCanvas
Canvas canvas 是HTML5新出的标签,可以用来做小游戏,特效,作图等,自己并没有作画能力,只能通过Javascript脚本来操控 Canvas标准http://www.w3c.org/TR/2dcontext/https:/
2019-09-17
  目录