node.js基础

官网传送门

 

 一、初识 Nodejs

1.初识 Nodejs

Node.js® is a JavaScript runtime built on Chrome's V8 JavaScript engine

Node.js® 是一个基于 Chrome V8 引擎 的 JavaScript 运行时环境

 基于 Express 框架

 nodejs官网:https://nodejs.org/en/

 2.初识Node-使用Node运行JS代码

node -v 查看node版本

在地址栏输入powershell可以弹出最新版的Windows终端

在终端中的命令

上箭头 可以快速定位到上一次执行的命令

tab自动补全地址

esc能够快速清空当前已输入的命令

cls命令,可以清空终端

3.fs文件系统模块

3.1.读取文件

# 简单文件读取

语法格式:

fs.readFile(path[, options], callback)
  • path:文件路径
  • options:配置选项,若是字符串则指定编码格式
    • encoding:编码格式
    • flag:打开方式
  • callback:回调函数
    • err:错误信息
    • data:读取的数据,如果未指定编码格式则返回一个 Buffer
//1.导入fs模块来操作文件
const fs = require('fs');
//2.调用fs.readFile() 方法读取文件
//参数1:读取文件的存放路径
//参数2:读取文件时候采用的编码格式,一般默认指定utf8
//参数3:回调函数,拿到读取失败和成功的结果 err dataStr
fs.readFile('./files/1.txt','utf8',function(err,dataStr){
    if(err){ return  console.log('读取文件失败',err.message);}
    console.log('读取文件成功',dataStr);//读取文件成功 111
})

3.2.写入文件

案例:将一个文件的内容,复制到另一个文件中,并修改文件的内容

//1.导入fs模块
const fs = require('fs');
//2.调用 fs.readFile()读取文件的内容
fs.readFile('./files/成绩.txt','utf8',function(err,dataStr){
    //3.判断是否读取成功
    if(err){
        return console.log(err.message);
    }
    //4.先把成绩的数据 按照空格进行分割
    const arrOld = dataStr.split(' ');
    //5.循环分割后 的数组,对每一项数据,进行字符串的替换操作
    const arrNew = [];
    arrOld.forEach(function(value){
        arrNew.push(value.replace('=',':'));
    })
    //6.把新数组中的每一项,进行合并,得到一个新的字符串
    const newStr = arrNew.join('\r\n');
    console.log(newStr);
    //7.调用fs.writeFile()方法,把处理完毕的成绩,写入到新文件中
    fs.writeFile('./files/成绩-ok',newStr,function(err){
        if(err){
            return console.log(err.message);
        }
        console.log('成绩写入成功');
    })
})

3.3.文件路径的问题

__dirname 表示当前文件所处的目录

出现路径拼接错误的问题,是因为提供了./或../开头的绝对路径

如果要解决这个问题,可以直接提供一个完整的文件存放路径就行

或者写__dirname(表示当前文件所处的目录)再拼接要读取的文件路径即可

const fs = require('fs');
//出现路径拼接错误的问题,是因为提供了./或../开头的绝对路径
//如果要解决这个问题,可以直接提供一个完整的文件存放路径就行
// fs.readFile('./files/1.txt','utf8',function(err,dataStr){
//     if(err){
//         return console.log('读取文件失败'+err.message);
//     }
//     console.log('读取文件成功'+dataStr);
// })

//移植性非常差,不利于维护
fs.readFile(__dirname+'/files/1.txt','utf8',function(err,dataStr){
    if(err){
        return console.log('读取文件失败'+err.message);
    }
    console.log('读取文件成功'+dataStr);
})

//__dirname 表示当前文件所处的目录
// console.log(__dirname);

其它操作

验证路径是否存在:

  • fs.exists(path, callback)
  • fs.existsSync(path)

获取文件信息:

  • fs.stat(path, callback)
  • fs.stat(path)

删除文件:

  • fs.unlink(path, callback)
  • fs.unlinkSync(path)

列出文件:

  • fs.readdir(path[,options], callback)
  • fs.readdirSync(path[, options])

截断文件:

  • fs.truncate(path, len, callback)
  • fs.truncateSync(path, len)

建立目录:

  • fs.mkdir(path[, mode], callback)
  • fs.mkdirSync(path[, mode])

删除目录:

  • fs.rmdir(path, callback)
  • fs.rmdirSync(path)

重命名文件和目录:

  • fs.rename(oldPath, newPath, callback)
  • fs.renameSync(oldPath, newPath)

监视文件更改:

  • fs.watchFile(filename[, options], listener)

 4.path路径模块

path 模块是 Node.js 官方提供的、用来处理路径的模块。它提供了一系列的方法和属性,用来满足用户对路径的处理需求。

4.1 路径拼接 path.join()

const path = require('path');
const fs = require('fs');
// //注意 ../会抵消前面的路径
// const pathStr= path.join('/a','/b/c','../../','./d','e');
// console.log(pathStr);// \a\d\e
fs.readFile(path.join(__dirname,'/files/2.txt'),'utf8',function(err,dataStr){
    if(err)
    {
        return console.log('失败咯',err);
    }
    console.log('成功',dataStr);
})

4.2获取路径中文件名 path.basename()

使用 path.basename() 方法,可以获取路径中的最后一部分,常通过该方法获取路径中的文件名

path.basename(path[, ext])

  • path: 文件路径
  • ext: 文件扩展名
const path  = require('path');
//定义文件存放路径
const fpath = 'a/b/c/index.html';
const fullName = path.basename(fpath);
console.log(fullName);//index.html
//获取除了文件后缀的其他部分
const nameWithoutExt = path.basename(fpath,'.html');
console.log(nameWithoutExt);//index

4.3 获取路径中文件扩展名 path.extname()

const path = require('path');
//这是文件的存放路径
const fpath = '/a/b/c/index.html';
//获取路径中扩展名部分
const text = path.extname(fpath);
console.log(text);//.html

案例;时钟案例

//1.导入fs模块
const fs = require('fs');
//2.导入path模块
const path = require('path');
//正则表达式 分别匹配<style></style> 和 <script></script>标签
const regStyle = /<style>[\s\S]*<\/style>/
const regScript = /<script>[\s\S]*<\/script>/
fs.readFile(path.join(__dirname,'./素材/index.html'),'utf8',function(err,dataStr){
    if(err){
        return console.log('读取HTML文件失败'+err.message);
    }
    //读取文件成功后,调用对应的三个方法,分别拆解出css,js,html文件
    resolveCss(dataStr);
    resolveJs(dataStr);
    resolveHTML(dataStr);
})

//处理css样式
function resolveCss(htmlStr){
//使用正则提取页面中的<style></style>标签
const r1 = regStyle.exec(htmlStr);
const newCss = r1[0].replace('<style>','').replace('</style>','');
//调用fs。writeFile()方法,将提取的样式,写入到clock目录中index.css文件里面
    fs.writeFile(path.join(__dirname,'./clock/index.css'),newCss,err=>{
        if(err){return console.log('写入CSS样式失败',err.message);}
        console.log('写入CSS样式成功');
    })
}

//处理JS脚本
function resolveJs(htmlStr){
//使用正则提取页面中的<script></script>标签
const r2 = regScript.exec(htmlStr);
//将提取出来的内容做进一步的处理
const newJs = r2[0].replace('<script>','').replace('</script>','');
    fs.writeFile(path.join(__dirname,'/clock/index.js'),newJs,err=>{
        if(err){return console.log('写入CSS脚本失败',err.message);}
        console.log('写入CSS脚本成功');
    })
}

//处理html文件
function resolveHTML(htmlStr){
    const newHTML = htmlStr.replace(regStyle,'<link rel="stylesheet" href="./index.css" />')
    .replace(regScript,'<script src="./index.js"></script>');
    fs.writeFile(path.join(__dirname,'/clock/index.html'),newHTML,err=>{
        if(err){
            return console.log('输出html失败',err.message);
        }
        console.log('输出HTML成功');
    })
}

5.http 模块

5.1.创建基本 Web 服务器

ctrl+c停止终端

req.url是客户端请求的URL地址

req.method是客户端请求的method类型

res.end()是向客户端发送指定的内容,并结束这次请求的处理过程

res.setHeader('Content-Type','text/html; charset=utf-8'):设置 Content-Type 响应头,解决中文乱码的问题

//1.导入http模块
const http = require('http');
//2.创建web服务器实例
const server = http.createServer();
//3.为服务器实例绑定request事件,监听客户端的请求
//req是请求对象,包含了与客户端相关的数据和属性 
server.on('request',(req,res)=>{
    //req.url是客户端请求的URL地址
    const url = req.url;
    //req.method是客户端请求的method类型
    const method = req.method;
    const str = `你访问的Url地址为${url},请求的method类型是${method}`;
    // 设置 Content-Type 响应头,解决中文乱码的问题
    res.setHeader('Content-Type','text/html; charset=utf-8')
    //res.end()是向客户端发送指定的内容,并结束这次请求的处理过程
    res.end(str);
})
//4.启动服务器
server.listen('80',function(){
    console.log('server running at http://127.0.0.1');
})

5.2.根据不同的url响应不同的html内容

const http = require('http');
const server = http.createServer();
server.on('request',(req,res)=>{
    //1.获取请求的url地址
    const url = req.url;
    //2.设置默认的响应内容为404 Not found
    let content = '<h1>404 Not found!</h1>';
    //3.判断用户请求是否为/或index.html首页
    if(url ==='/' || url ==="/index.html")
    {
        content = '<h1>首页</h1>';

    }
    //4.判断用户请求的是否为/about.html关于页面
    else if(url ==='/about.html')
    {
         content = '<h1>关于页面</h1>';
    }
    //5.设置Content-Type响应头,防止中文乱码
    res.setHeader('Content-Type','text/html; charset=utf-8');
    //6.使用res.end()把内容响应给客户端
    res.end(content);
})
server.listen('81',function(){
    console.log('server running at http://127.0.0.1:81');
})

案例 时钟web

//1.导入http模块
const http = require('http');
//2.导入fs模块
const fs = require('fs');
//3.导入path模块
const path = require('path');
//创建web服务器
const server = http.createServer();
//监听web服务器的request事件
server.on('request',(req,res)=>{
    //获取到客户端请求的URL地址
    const url = req.url;
    //把请求的URL地址映射为具体文件的存放路径
    // const fPath = path.join(__dirname,url);
    let fpath = '';
    if(url ==='/')
    {
        fpath = path.join(__dirname,'/clock/index.html');
    }
    else{
        fpath = path.join(__dirname,'/clock',url);
    }
    //根据映射过来的文件路径读取文件
    fs.readFile(fpath,'utf8',(err,dataStr)=>{
        if(err) return res.end('404 No Found');
        res.end(dataStr);
    })
})
server.listen('80',()=>{
    console.log('server running at http://127.0.0.1');
}) 

6.模块化

6.1.模块化概念

  • 模块化是指解决一个复杂问题时,自顶向下逐层把系统划分为若干模块的过程,模块是可组合、分解和更换的单元。
  • 模块化可提高代码的复用性和可维护性,实现按需加载。
  • 模块化规范是对代码进行模块化拆分和组合时需要遵守的规则,如使用何种语法格式引用模块和向外暴露成员。

6.2.Node.js 中模块的分类 

使用require(),加载模块

  • 内置模块
  • 自定义模块
  • 第三方模块

 

在使用require加载用户自定义模块期间,可以省略.js的后缀名

 const m1 = require('./15.m1');

6. 3.Node.js 中的模块作用域

  • 和函数作用域类似,在自定义模块中定义的变量、方法等成员,只能在当前模块内被访问,这种模块级别的访问限制,叫做模块作用域
  • 防止全局变量污染

 自定义模块.js

const username='张三';
function sayHello(username){
    console.log(`我是${username}`);
}

 使用自定义模块的js

const custom = require('./17.模块作用域');
console.log(custom);//{}  不能访问别的模块内容

6. 4.模块作用域的成员

  • 自定义模块中都有一个 module 对象,存储了和当前模块有关的信息
  • 在自定义模块中,可以使用 module.exports 对象,将模块内的成员共享出去,供外界使用。导入自定义模块时,得到的就是 module.exports 指向的对象。
  • 默认情况下,exportsmodule.exports 指向同一个对象。最终共享的结果,以 module.exports 指向的对象为准。
console.log(module);

 

6.4.1module.exports对象

 在一个自定义模块中,默认情况下,model.exports ={}

在外界使用require导入一个自定义模块的时候,得到的成员,就是那个模块中,通过module.export指向的那个对象

自定义模块.js

//在一个自定义模块中,默认情况下,model.exports ={}
const age = 20;
//向module.exports对象挂载username属性
module.exports.username = 'zs';
//向module.exports对象挂载sayHello方法
module.exports.sayHello = function(){
    console.log('Hello!');
}
module.exports.age = age;
//{ username: 'zs', sayHello: [Function], age: 20 }

//让module.export 指向一个全新的对象,原先的对象被覆盖
module.exports = {
    nickname:'小粉',
    sayHi(){
        console.log('Hi');
    }
}
//{ nickname: '小粉', sayHi: [Function: sayHi] }

 使用自定义模块的js

//在外界使用require导入一个自定义模块的时候,得到的成员,就是那个模块中,通过module.export指向的那个对象
const m = require('./19.自定义模块');
console.log(m);

6.4.2exports对象

exports对象.js

const username = 'lgx';
exports.username = username;
exports.age = 20;
exports.sayHi = function(){
    console.log('一曲离歌笑江湖');
}
//最终 向外共享的对象,永远都是module.exports所指向的对象

 使用自定义模块的js

const m = require('./20.exports对象');
console.log(m);//{ username: 'lgx', age: 20, sayHi: [Function] }

6.4.3module.exports与exports

默认情况下,exportsmodule.exports 指向同一个对象最终共享的结果,以 module.exports 指向的对象为准。

console.log(exports);//{}
console.log(module.exports);//{}
console.log(exports === module.exports);//true

6.5CommonJS 模块化规范

  • 每个模块内部,module 变量代表当前模块
  • module 变量是一个对象,module.exports 是对外的接口
  • 加载某个模块即加载该模块的 module.exports 属性,require()方法用于加载模块

7.包与npm-包

(1)搜索包共享平台:https://www.npmjs.com/

(2)下载包:https://registry.npmjs.org

终端中输入npm -v 查询npm包版本

7.1.格式化时间传统做法

//1.定义格式化时间的方法
function dateFormat(dtStr){
    const dt = new Date();
    const y = dt.getFullYear();
    const m = padZero(dt.getMonth() +1);
    const d = padZero(dt.getDate());
    const hh = padZero(dt.getHours());
    const mm = padZero(dt.getMinutes());
    const ss = padZero(dt.getSeconds());
    // return 'YYYY-MM-DD HH:mm:ss';
    return `${y}-${m}-${d} ${hh}:${mm}:${ss}`;
}

//定义补零的函数
function padZero(n){
    return n>9?n:'0'+n;
}

module.exports ={
    dateFormat
}
//导入自定义的格式化时间的模块
const TIME = require('./21.dateFormat');
//调用方法 进行日期格式化
const dt = new Date();

const newDT = TIME.dateFormat(dt)
console.log(newDT);//2022-03-13 21:41:14

安装包命令:npm install 包的完整名称 简写为 npm i  包的完整名称

 核心依赖包,记录在dependencies节点中

7.2.引入第三方包后做法

//1.导入需要的包
//注意导入的名称,就是装包时候的名称
const moment = require('moment');
const dt = moment().format('YYYY-MM-DD HH:mm:ss');
console.log(dt);

npm i moment@2.29.1 (改变指定的版本号)

2(大版本).29(功能).1(Bug)

7.3.包管理配置文件

7.3.1.快速创建package.json

在项目根目录中创建一个叫做package.json的配置文件,即可用来记录项目中安装了哪些包。从而方便剔除node_modules目录之后,在团队成员之间共享项目的源代码。

npm init -y

 package.json中的dependencies属性:包含安装所有包及其版本

7.3.2.安装所有包

npm i / npm install

 7.3.3.卸载包

npm uninstall

 7.3.4.devDependencies节点

开发依赖包

只会在项目开发阶段用到,在项目上线之后不会用到,把这些包记录在devDependencies节点中

开发项目上线之后都需要用到,则建议把这些包记录到dependencies节点中

npm i 包名 -D   简写为  npm install 包名 --save-dev

 7.3.5解决下包速度慢的问题

切换成淘宝镜像

查看当前的下包镜像源

npm config get registry

将下包的镜像源切换为淘宝镜像源

npm config set registry=http://registry.npm.taobao.org/

检查镜像源是否下载成功

npm config get registry

 7.3.6nrm

切换包的镜像源

通过npm包管理器,将nrm安装为全局可用的工具

npm  i  nrm -g

查看所有可用的镜像源

nrm ls

将下包的镜像源切换为taobao镜像

nrm use taobao

卸载全局安装的包

npm uninstall 包名 -g

7.3.7i5ting_toc 把md文档转为html页面的小工具

将i5ting-toc 安装为全局包

npm install -g i5ting_toc

调用i5ting_toc,轻松实现md转html的功能

i5ting_toc -f 要转换的md文件路径 -o

 7.3.8开发属于自己的包

7.3.8.1开发包

{
    "name":"itheima-tools-lgx",
    "version": "1.0.0",
    "main":"index.js",
    "description": "提供了格式化时间、HTML Escape相关的功能-离歌笑",
    "keywords": [
        "itheima",
        "dateFoemat",
        "escape"
    ],
    "license":"ISC"
}

1.新建 itheima-tools-lgx(自定义名称),作为包的根目录

2.在包根目录下新建三个文件

package.json (包管理文件)

index.js (包的入口文件)

README.md (包的说明文档)

3.在包共享平台上检测包是否重复

package.json

{
    "name":"itheima-tools-lgx",
    "version": "1.0.0",
    "main":"index.js",
    "description": "提供了格式化时间、HTML Escape相关的功能-离歌笑",
    "keywords": [
        "itheima",
        "dateFoemat",
        "escape"
    ],
    "license":"ISC"
}

index.js

//这是包的入口文件
const date = require('./src/dateFormat');
const escape = require('./src/htmlEscape');

//向外暴露需要的成员
module.exports = {
   ...date,
   ...escape
}

./src/dateFormat.js

//格式化时间的方法
function dateFormat(dataStr){
    const dt = new Date();
    const y = padZero(dt.getFullYear());
    const m = padZero(dt.getMonth()+1);
    const d = padZero(dt.getDate());

    const hh = padZero(dt.getHours());
    const mm = padZero(dt.getMinutes());
    const ss = padZero(dt.getSeconds());
    return `${y}-${m}-${d} ${hh}:${mm}:${ss}`;
}

//补零的方法
function padZero(n)
{
   return n > 9 ? n: '0'+n;
}
module.exports = {
    dateFormat
}

./src/htmlEscape.js

//定义转义HTM字符的函数
function htmlEscape(htmlStr){
    return htmlStr.replace(/<|>|"|&/g,(match)=>{
        switch(match){
            case '<':
                return '&lt;';
            case '>':
                return '&gt;';
            case '"':
                return '&quot;';
            case '&':
                return '&amp;';
        }
    })
}

//定义还原HTML字符串的函数
function htmlUnEscape(str){
    return str.replace(/&lt;|&gt;|&quot;|&amp;/g,(match)=>{
        switch(match){
            case '&lt;':
                return '<';
            case '&gt;':
                return '>';   
            case '&quot;':
                return '"';
            case '&amp;':
                return '&';
        }
    })
}

module.exports = {
    htmlEscape,
    htmlUnEscape
}

README.md

## 安装
```
npm install itheima-tools-lgx
```

## 导入
```js
const itheima = require('itheima-tools-lgx')
```

## 格式化时间
```js
// 调用 dateFormat 对时间进行格式化
const dtStr = itheima.dateFormat(new Date())
// 结果  2020-04-03 17:20:58
console.log(dtStr)
```

## 转义 HTML 中的特殊字符
```js
// 带转换的 HTML 字符串
const htmlStr = '<h1 title="abc">这是h1标签<span>123&nbsp;</span></h1>'
// 调用 htmlEscape 方法进行转换
const str = itheima.htmlEscape(htmlStr)
// 转换的结果 &lt;h1 title=&quot;abc&quot;&gt;这是h1标签&lt;span&gt;123&amp;nbsp;&lt;/span&gt;&lt;/h1&gt;
console.log(str)
```

## 还原 HTML 中的特殊字符 
```js
// 待还原的 HTML 字符串
const str1 = itheima.htmlUnEscape(str)
// 输出的结果 <h1 title="abc">这是h1标签<span>123&nbsp;</span></h1>
console.log(str1)
```

## 开源协议
ISC

7.3.8.2发布包

1.npm login 先登录,然后根据要求输入账号密码 lgx-lxy

2. 切换到要发布的包根目录下输入npm publish 发布包

 7.3.8.3删除已发布的包

npm unpublish 包名 --force (删除72小时以内发布的包,在24小时内不允许重复发布)

8.模块加载机制

模块第一次加载后会被缓存,即多次调用 require() 不会导致模块的代码被执行多次,提高模块加载效率

 8.1内置模块加载

内置模块加载优先级最高。

8.2自定义模块加载

加载自定义模块时,路径要以 ./../ 开头,否则会作为内置模块或第三方模块加载。

导入自定义模块时,若省略文件扩展名,则 Node.js 会按顺序尝试加载文件:

  • 按确切的文件名加载
  • 补全 .js 扩展名加载
  • 补全 .json 扩展名加载
  • 补全 .node 扩展名加载
  • 报错

8.3 第三方模块加载

  • 若导入第三方模块, Node.js 会从当前模块的父目录开始,尝试从 /node_modules 文件夹中加载第三方模块。
  • 如果没有找到对应的第三方模块,则移动到再上一层父目录中,进行加载,直到文件系统的根目录

例如,假设在 C:\Users\bruce\project\foo.js 文件里调用了 require('tools'),则 Node.js 会按以下顺序查找:

  • C:\Users\bruce\project\node_modules\tools
  • C:\Users\bruce\node_modules\tools
  • C:\Users\node_modules\tools
  • C:\node_modules\tools

8.4目录作为模块加载

当把目录作为模块标识符进行加载的时候,有三种加载方式:

  • 在被加载的目录下查找 package.json 的文件,并寻找 main 属性,作为 require() 加载的入口
  • 如果没有 package.json 文件,或者 main 入口不存在或无法解析,则 Node.js 将会试图加载目录下的 index.js 文件。
  • 若失败则报错

二、Express

官网传送门

基于 Node.js 平台,快速、开放、极简的 Web 开发框架

Express 是用于快速创建服务器的第三方模块。

1.Express 初体验

1.1基本使用

安装 Express:

npm install express

app.get()   app.post()

监听客户端 GET和POST 请求,并向客户端响应具体的内容

 res.send()

调用express提供的res.send()方法,向客户端响应一个JSON对象

req.query

通过req.query 可以获取到客户端发送过来的 查询参数 ?a=1&b=2 ,默认是一个空对象

http://127.0.0.1?a=1&b=2

req.params  

/user/:id  (冒号后面的名称,如 id 是可以改的)

req.params 是动态匹配到的URL参数,默认也是一个空对象

http://127.0.0.1/user/387

结果:{"id": "387"}

创建服务器,监听客户端请求,并返回内容:

//1.导入express
const express = require('express');
//2.创建web服务器
const app = express();

//4.监听客户端 GET和POST 请求,并向客户端响应具体的内容
app.get('/user',(req,res)=>{
    //调用express提供的res.send()方法,向客户端响应一个JSON对象
    res.send({name:'lgx',age:'20',gender:'boy'});
})
app.post('/user',(req,res)=>{
    res.send('请求成功');
})
app.get('/',(req,res)=>{
    //通过req.query 可以获取到客户端发送过来的 查询参数 ?a=1&b=2
    //注意:默认情况下,req.query是一个空对象 {}
    console.log(req.query);
    res.send(req.query);
})

//注意:这里的:id是一个动态参数
app.get('/user/:lgx/:name',(req,res)=>{
    //req.params 是动态匹配到的URL参数,默认也是一个空对象
    console.log(req.params);
    res.send(req.params);
})
//http://127.0.0.1/user/387
//结果:{"id": "387"}

//3.启动web服务器
app.listen(80,()=>{
    console.log('express server running at http://127.0.0.1');
})

1.2托管静态资源

  • 通过 express.static() 方法可创建静态资源服务器,向外开放访问静态资源。
  • Express 在指定的静态目录中查找文件,并对外提供资源的访问路径,存放静态文件的目录名不会出现在 URL 中
  • 访问静态资源时,会根据托管顺序查找文件
  • 可为静态资源访问路径添加前缀

  访问静态资源时,会根据托管顺序查找文件

app.use(express.static('./clock'));
app.use(express.static('./files'));

  可为静态资源访问路径添加前缀

app.use('/files',express.static('./files'));

 完整代码

const express = require('express');
const app = express();
//在这里,调用express.static()方法,快速的对外提供静态资源
app.use('/files',express.static('./files'));
app.use(express.static('./clock'));
app.listen(80,(req,res)=>{
    console.log('express server running at http://127.0.0.1');
})

/*
可直接访问 public, files 目录下的静态资源
http://localhost:3000/index.html
http://localhost:3000/index.css
http://localhost:3000/index.js

通过带有 /files前缀的地址访问 files 目录下的文件
http://localhost:8080/files/index.html
*/

 安装nodemon能够监听文件的变动,自动重启项目

npm install -g nodemon

2.Express 路由

2.1创建路由模块

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

//挂载路由
app.get('/',(req,res)=>{
    res.send('Hello World!');
})

app.post('/user',(req,res)=>{
    res.send('Hello POST');
})

app.listen(80,(req,res)=>{
    console.log('express server running at http://127.0.0.1');
})

2.2注册路由模块

模块化路由.js

const express = require('express');
const app = express();
//1.导入路由模块
const router = require('./03.router');
//2.注册路由模块
// app.use(router);
 
//为路由模块添加前缀
app.use('/api',router);

//注意 app.use()的作用,就是来注册全局中间件

app.listen(80,()=>{
    console.log('http:/127.0.0.1');
})

router.js

//这是路由模块
//1.导入Express
const express = require('express');
//2.创建路由对象
const router = express.Router();
//3.挂载具体的路由
router.get('/user/list',(req,res)=>{
    res.send('GET USER LIST');
})
router.post('/user/add',(req,res)=>{
    res.send('GET USER ADD');
})
//4.向外导出路由对象
module.exports = router;

3.Express 中间件

  • 中间件是指流程的中间处理环节
  • 服务器收到请求后,可先调用中间件进行预处理
  • 中间件是一个函数,包含 req, res, next 三个参数,next() 参数把流转关系交给下一个中间件或路由

中间件注意事项;

  • 在注册路由之前注册中间件(错误级别中间件除外)
  • 中间件可连续调用多个
  • 别忘记调用 next() 函数
  • next() 函数后别写代码
  • 多个中间件共享 reqres对象

 opp.get('/',function(req,res,next){})

带有next()参数的才是中间件,不带的是路由

next函数是实现多个中间件连续调用的关键,它表示把流转关系转交给下一个中间件或路由

3.1最简单的中间件函数

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

//定义一个最简单的中间件函数
const nw = function(req,res,next){
    console.log('这是最简单的中间件函数');
    //把流转关系,转交给下一个中间件或路由
    next();
}

app.listen(80,(req,res)=>{
    console.log('http://127.0.0.1');
})

3.2.全局中间件

客户端发起的任何请求,到达服务器之后,都会触发的中间件,叫做全局生效的中间件

通过 app.use() 定义的中间件为全局中间件

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

//定义一个最简单的中间件函数
// const nw = function(req,res,next){
//     console.log('这是最简单的中间件函数');
//     //把流转关系,转交给下一个中间件或路由
//     next();
// }

// //将nw注册为全局生效的中间件
// app.use(nw);

// 全局中间件简写
app.use(function(req,res,next){
    console.log('这是最简单的中间件函数2');
    next();
});

app.get('/',(req,res)=>{
    console.log('调用了/这个路由');
    res.send("Home Page.");
})

app.get('/user',(req,res)=>{
    console.log('调用了/user这个路由');
    res.send("Home Page~");
})


app.listen(80,(req,res)=>{
    console.log('http://127.0.0.1');
})

 全局中间件简写

app.use(function(req,res,next){
    console.log('这是最简单的中间件函数2');
    next();
});

3.2.1中间件的作用

多个中间件之间, 共享同一份 req res 。基于这样的特性,我们可以在 上游 的中间件中, 统一 为 req 或 res 对象添 自定义 属性 方法 ,供 下游 的中间件或路由进行使用。
const express = require('express');
const app = express();
// 全局中间件简写
app.use(function(req,res,next){
    //获取请求到达服务器的时间
    const time = Date.now();
    //为req对象,挂载自定义属性,从而把时间共享给后面所有路由
    req.startTime = time;
    next();
});

app.get('/',(req,res)=>{
    res.send("Home Page."+req.startTime);
})

app.get('/user',(req,res)=>{
    res.send("Home Page~"+req.startTime);
})


app.listen(80,(req,res)=>{
    console.log('http://127.0.0.1');
})

3.2.2定义多个全局中间件

const express = require('express');
const app = express();
//定义第一个中间件函数
app.use(function(req,res,next){
    console.log('全局中间件1');
    next();
})
//定义第二个中间件函数
app.use(function(req,res,next){
    console.log('全局中间件2');
    next();
})
//定义一个路由
app.get('/lgx',(req,res)=>{
    console.log('一曲离歌笑江湖');
    res.send('一曲离歌笑江湖');
})
app.listen(80,()=>{
    console.log('http://127.0.0.1');
})

3.3局部中间件

3.3.1局部中间件

//导入express模块
const express = require('express');
//创建express的服务器实例
const app = express();
//1.定义中间件函数
const nw1 = function(req,res,next){
    console.log('调用了局部生效的中间件');
    next();
}
//2.创建路由
app.get('/',nw1,(req,res)=>{
    res.send('一曲离歌笑江湖1');
})

app.get('/lgx',(req,res)=>{
    res.send('一曲离歌笑江湖2');
})

app.listen(80,()=>{
    console.log('http://127.0.0.1');
})

3.3.2定义多个局部中间件

app.get('/',nw1,nw2,(req,res)=>{
    res.send('一曲离歌笑江湖1');
})
//或者
app.get('/',[nw1,nw2],(req,res)=>{
    res.send('一曲离歌笑江湖1');
})

3.4了解中间件的5个使用注意事项

一定要在路由之前注册中间件
客户端发送过来的请求,可以连续调用多个中间件进行处理
执行完中间件的业务代码之后,不要忘记调用 next() 函数
为了防止代码逻辑混乱,调用 next() 函数后不要再写额外的代码
连续调用多个中间件时,多个中间件之间,共享 req 和 res 对象

3.5 中间件分类

3.5.1.应用级别的中间件

  • 通过 app.use()app.get()app.post() ,绑定到 app 实例上的中间件
  • express()

3.5.2.路由级别的中间件 

  • 绑定到 express.Router() 实例上的中间件,叫做路由级别的中间件。用法和应用级别中间件没有区别。应用级别中间件是绑定到 app 实例上,路由级别中间件绑定到 router 实例上。(全局,局部中间件都可以设置)
const express = require('express');
const app = express();
const router = express.Router();
//路由级别的中间件
// router.use(function(req,res,next){
//     console.log('TIME:'+Date.now());
//     next();
// })

const nw = function(req,res,next){
    console.log('TIME666:'+Date.now());
    next();
}

app.use('/',router);
app.get('/',nw,(req,res)=>{
    res.send('一曲离歌笑江湖');
})
app.listen(80,()=>{
    console.log('http://127.0.0.1');
})

 3.5.3.错误级别的中间件

  • 用来捕获整个项目中发生的异常错误,从而防止项目异常崩溃的问题
  • 错误级别中间件的处理函数中,必须有 4 个形参,形参顺序从前到后分别是 (err, req, res, next)
  • 错误级别的中间件必须注册在所有路由之后
const express = require('express');
const app = express();
//1.定义路由
app.get('/',(req,res)=>{
    //人为的制造错误
    throw new Error('服务器内部发送错误');
    res.send('一曲离歌笑江湖');
})

//2.定义错误级别的中间件,捕获整个项目的异常错误,从而防止程序的崩溃
app.use(function(err,req,res,next){
    console.log('发生了错误!'+err.message);
    res.send('Error:'+err.message);
})

app.listen(80,()=>{
    console.log('http://127.0.0.1');
})

3.5.4Express 内置中间件

自 Express 4.16.0 版本开始,Express 内置了 3 个常用的中间件,极大的提高了 Express 项目的开发效率和体验:

  • express.static 快速托管静态资源的内置中间件,例如: HTML 文件、图片、CSS 样式等(无兼容性)
  • express.json 解析 JSON 格式的请求体数据(有兼容性,仅在 4.16.0+ 版本中可用)
  • express.urlencoded 解析 URL-encoded 格式的请求体数据(有兼容性,仅在 4.16.0+ 版本中可用)
1.通过express.json()这个中间件,解析表单中的JSON格式的数据
app.use(express.json());
2.通过express.urlencodeed()这个中间件,来解析表单中url-encodeed格式的数据
app.use(express.urlencoded({extended:false}));
3.在服务器 可以使用req.body这个属性,来获取json格式的表单数据和url-encodeed格式的数据
console.log(req.body)

 完整代码

const express = require('express');
const app = express();
//注意:除了错误级别的中间件,其他的中间件,必须在路由之前进行配置
//通过express.json()这个中间件,解析表单中的JSON格式的数据
app.use(express.json());
//通过express.urlencodeed()这个中间件,来解析表单中url-encodeed格式的数据
app.use(express.urlencoded({extended:false}));
app.post('/user',(req,res)=>{
    //在服务器 可以使用req.body这个属性,来接收客户端发送过来的请求体数据
    //默认情况下,如果不配置解析表单数据的中间件,则req.body 默认等于 undefined
    console.log(req.body);
    res.send('ok');
})
app.post('/book',(req,res)=>{
     //在服务器 可以使用req.body这个属性,来获取json格式的表单数据和url-encodeed格式的数据
     console.log(req.body)
    res.send('ok2');
})
app.listen(80,()=>{
    console.log("Express server running at http://127.0.0.1");
})

3.5.5第三方中间件

1.运行npm install bod-parser安装中间件

2.使用require导入中间件

3.调用app.use()注册并使用中间件

//1.导入解析表单数据的中间件 body-parser
const parser = require('body-parser');
//2.使用app.use() 注册中间件
app.use(parser.urlencoded({extended:false}));

  完整代码

const express = require('express');
const app = express();
//1.导入解析表单数据的中间件 body-parser
const parser = require('body-parser');
//2.使用app.use() 注册中间件
app.use(parser.urlencoded({extended:false}));
app.post('/lgx',(req,res)=>{
    //如果没有配置任何解析表单数据的中间件,则req.body默认等于undefined
    console.log(req.body);
    res.send('ook');
})
app.listen(80,()=>{
    console.log("Express server running at http://127.0.0.1");
})

3.6自定义中间件

3.6.1监听 req 的 data 事件

在中间件中,需要监听 req 对象的 data 事件,来获取客户端发送到服务器的数据。
如果数据量比较大,无法一次性发送完毕,则客户端会 把数据切割后 分批发送到服务器 。所以 data 事件可能会触发多次,每一次触发 data 事件时, 获取到数据只是完整数据的一部分 ,需要手动对接收到的数据进行拼接。
req.on('data',(chunk)=>{str += chunk;})

 3.6.2监听 req 的 end 事件

当请求体数据 接收完毕 之后,会自动触发 req 的 end 事件。
因此,我们可以在 req 的 end 事件中, 拿到并处理完整的请求体数据
req.on('end',()=>{console.log(str);})

 3.6.3使用 querystring 模块解析请求体数据

Node.js 内置了一个 querystring 模块,专门用来处理查询字符串。通过这个模块提供的 parse() 函数,可以轻松把查询字符串,解析成对象的格式。

//导入Node.js内置的querystring模块

const qs = require('querystring');

//调用qs.parse()方法,把查询字符串解析为对象

console.log(qs.parse(str));

 3.6.4将解析出来的数据对象挂载为 req.body

上游 中间件 下游 中间件及路由 之间, 共享同一份 req 和 res 。因此,我们可以将解析
出来的数据,挂载为 req的自定义属性,命名为 req.body ,供下游使用。

3.6.5将自定义中间件封装为模块

为了优化代码的结构,我们可以把自定义的中间件函数,封装为独立的模块

 完整代码

custom-body-parser.js(自己封装的中间件模块)

//导入Node.js内置的querystring模块
const qs = require('querystring');

const bodyParser = function (req,res,next) {
    //定义中间件具体的业务逻辑
    //1.定义一个str字符串,专门用来存储客户端发送过来的请求体数据
    let str='';
    //2.监听req的data事件
    req.on('data',(chunk)=>{
        str += chunk;
    })
    //3.监听req的end事件
    req.on('end',()=>{
        //在str中存放的是完整的请求体数据
        // console.log(str);
        //1000:把字符串格式的请求体数据,解析成对象格式
        //调用qs.parse()方法,把查询字符串解析为对象
        const body = qs.parse(str);
        req.body = body;
        next();
    })
};

module.exports = bodyParser;

对自定义的中间件进行模块化的拆分.js

//导入express模块
const express = require("express");
//创建express的服务器实例
const app = express();

//1.导入自己封装的中间件模块
const customBodyParser = require('./14.custom-body-parser');
//2.将自定义的中间件函数注册为全局可用的中间件
app.use(customBodyParser);

app.post('/lgx',(req,res)=>{
    res.send(req.body);
})
app.listen(80,()=>{
    console.log('http://127.0.0.1');
})

4.使用Express写接口

4.1cors 中间件解决跨域

  • 安装中间件:npm install cors
  • 导入中间件:const cors = require('cors')
  • 配置中间件:app.use(cors)

 4.2.CORS跨域资源共享

  • CORS(Cross-Origin Resource Sharing,跨域资源共享)解决跨域,是通过 HTTP 响应头决定浏览器是否阻止前端 JS 代码跨域获取资源
  • 浏览器的同源安全策略默认会阻止网页“跨域”获取资源。但如果接口服务器配置了 CORS 相关的 HTTP 响应头,就可解除浏览器端的跨域访问限制
  • CORS 主要在服务器端进行配置。客户端浏览器无须做任何额外的配置,即可请求开启了 CORS 的接口。
  • CORS 在浏览器中有兼容性。只有支持 XMLHttpRequest Level2 的浏览器,才能正常访问开启了 CORS 的服务端接口(例如:IE10+、Chrome4+、FireFox3.5+)。

4.3CORS 常见响应头

4.3.1Access-Control-Allow-Origin:制定了允许访问资源的外域 URL

res.setHeader('Access-Control-Allow-Origin', 'http://lgx.com')
res.setHeader('Access-Control-Allow-Origin', '*')

 4.3.2Access-Control-Allow-Headers

默认情况下,CORS 仅支持客户端向服务器发送如下的 9 个请求头:Accept、Accept-Language、Content-Language、DPR、Downlink、Save-Data、Viewport-Width、Width 、Content-Type (值仅限于 text/plain、multipart/form-data、application/x-www-form-urlencoded 三者之一)
如果客户端向服务器发送了额外的请求头信息,则需要在服务器端,通过 Access-Control-Allow-Headers 对额外的请求头进行声明,否则这次请求会失败!

res.setHeader('Access-Control-Allow-Headers', 'Content-Type, X-Custom-Header')

 4.3.3Access-Control-Allow-Methods

默认情况下,CORS 仅支持客户端发起 GET、POST、HEAD 请求。如果客户端希望通过 PUT、DELETE 等方式请求服务器的资源,则需要在服务器端,通过 Access-Control-Alow-Methods 来指明实际请求所允许使用的 HTTP 方法

res.setHeader('Access-Control-Allow-Methods', 'POST, GET, DELETE, HEAD')
res.setHEader('Access-Control-Allow-Methods', '*')

4.4CORS 请求分类

4.4.1简单请求

  • 请求方式:GET、POST、HEAD 三者之一
  • HTTP 头部信息不超过以下几种字段:无自定义头部字段、Accept、Accept-Language、Content-Language、DPR、Downlink、Save-Data、Viewport-Width、Width 、Content-Type(只有三个值 application/x-www-formurlencoded、multipart/form-data、text/plain)

  4.4.2预检请求

  • 请求方式为 GET、POST、HEAD 之外的请求 Method 类型
  • 请求头中包含自定义头部字段
  • 向服务器发送了 application/json 格式的数据

在浏览器与服务器正式通信之前,浏览器会先发送 OPTION 请求进行预检,以获知服务器是否允许该实际请求,所以这一次的 OPTION 请求称为“预检请求”。服务器成功响应预检请求后,才会发送真正的请求,并且携带真实数据

4.5JSONP接口

概念:浏览器端通过 <script> 标签的 src 属性,请求服务器上的数据,同时,服务器返回一个函数的调用。这种请求数据的方式叫做 JSONP。
特点:
JSONP 不属于真正的 Ajax 请求,因为它没有使用 XMLHttpRequest 这个对象。
JSONP 仅支持 GET 请求,不支持 POST、PUT、DELETE 等请求。

如果项目中已经配置了 CORS 跨域资源共享,为了防止冲突,必须在配置 CORS 中间件之前声明 JSONP 的接口。否则 JSONP 接口会被处理成开启了 CORS 的接口。

完整案例

使用express写接口.js

//使用express
const express = require('express');
//创建服务器实例
const app = express();
//配置解析表单数据的中间件
app.use(express.urlencoded({extended:false}));

//必须在配置cors中间件之前,配置JSONP的接口
app.get('/api/jsonp',(req,res)=>{
    //TODO:定义 JSON接口具体的实现过程
    //1.得到函数的名称
    const funName = req.query.callback;
    //2.定义要发送到客户端的数据对象
    const data = {name:'lgx',age:22};
    //3.拼接出一个函数的调用
    const scriptStr = `${funName}(${JSON.stringify(data)})`
    //4.把拼接的字符串,响应给客户端
    res.send(scriptStr);
})

//一定要在路由之前,配置cors这个中间件,从而解决接口跨域的问题
const cors = require('cors');
app.use(cors());
//导入路由模块
const router = require('./16.apiRouter');
//把路由模块,注册到app上
app.use('/api',router);
//启动服务器
app.listen(80,()=>{
    console.log("http://127.0.0.1");
})

apiRouter.js

const express = require('express');
const router = express.Router();
//这里挂载对应的路由
//定义GET接口
router.get('/lgx',(req,res)=>{
    //通过req.query获取客户端通过查询字符串,发送到服务器的数据
    const query = req.query;
    //调用res.send() 方法,向客户端响应处理的结果
    res.send({
        status :0,//0表示处理成功 1表示失败
        msg:'GET请求成功!!',//状态的描述
        data:query//需要响应给客户端的数据
    });
})
//定义POST接口
router.post('/lxy',(req,res)=>{  
    // 通过 req.body 获取请求体中包含的 url-encoded 格式的数据
    const body = req.body;
    // 调用 res.send() 方法,向客户端响应结果
    res.send({
        status:0,//0表示处理成功 1表示失败
        msg:"POST请求成功!!!",//状态的描述
        data:body,//需要响应给客户端的数据
    });
    // res.setHeader('Access-Control-Allow-Headers','Content-Type','X-Custom-Header');
    // res.setHeader('Access-Control-Allow-Methods','POST,GET,DELETE,HEAD');
    // Response.setHeader('Access-Control-Allow-Methods','*');
})

//定义Delete接口
router.delete('/delete',(req,res)=>{
    res.send({
        status:0,
        msg:'DELETE请求成功'
    })
})

module.exports = router;

 测试接口跨域问题.html

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <meta http-equiv="X-UA-Compatible" content="ie=edge">
    <script src="https://cdn.bootcdn.net/ajax/libs/jquery/3.6.0/jquery.min.js"></script>
    <title>17.测试接口跨域问题</title>
</head>
<body>
    <button id="btnGet">GET</button>
    <button id="btnPost">POST</button>
    <button id="btnDelete">Delete</button>
    <button id="btnJSONP">JSONP</button>
    <script>
    $(function(){
        //1.测试GET接口
        $("#btnGet").on('click',function(){
            $.ajax({
                type:'GET',
                url:'http://127.0.0.1/api/lgx',
                data:{name:'lgx',age:18},
                success:function(res){
                    console.log(res);
                    console.log(res.msg);
                }
            })
        })
        //2.测试POST接口
        $('#btnPost').on('click',function(){
            $.ajax({
                type:'POST',
                url:'http://127.0.0.1/api/lxy',
                data:{name:'lxy',age:'10'},
                success:function(res){
                    console.log(res);
                    console.log(res.msg);
                }
            })
        })
        //3.为删除按钮绑定点击事件处理函数
        $("#btnDelete").on('click',function(){
            $.ajax({
                type:'delete',
                url:'http://127.0.0.1/api/delete',
                success:function(res){
                    console.log(res);
                }
            })
        })
        //4.btnJSONP
        $("#btnJSONP").click('on',function(){
            $.ajax({
                type:'GET',
                url:'http://127.0.0.1/api/jsonp',
                dataType:'jsonp',//表示要发起JSONP的请求
                success:function(res){
                    console.log(res);
                }
            })
        })
    })
    </script>
</body>
</html>

三、数据库和身份认证

1.Node 操作 mysql

1.1数据表查询

1.1.1数据表

DataType 数据类型:
int 整数
varchar ( len ) 字符串 tinyint ( 1 ) 布尔值
字段的特殊标识:
PK (Primary Key) 主键、唯一标识
NN (Not Null) 值不允许为空
UQ (Unique) 值唯一
AI (Auto Increment) 值自动增长
select* from users
select username,password from users

insert into users(username,password) values ('tony stark','098123')

update users set password = '888888' where id = 4
update users set password = 'admin123',status = 1 where id = 2

delete from users where id = 4

 1.1.2where子句

select * from users where status = 1 
select * from users where id > 2
select * from users where username <> 'lxy'
select * from users where username != 'lxy'

1.1.3And和Or运算符

select * from users  where id <3 and status = 0

select  * from  users where status = 1 or  username = 'lgx'

1.1.4order by  子句

(1)DESC降序,默认是升序(ASC)

select  * from users order by status
select  * from users order by status asc

select * from users order by id desc

(2) 多重排序

select  * from users order by status desc, username desc

 1.1.5COUNT(*)函数

select count(*) from users where status = 0

 1.1.6使用AS关键字为列设置别名

select count(*) AS total from users where status = 0 

select password as pw , username as uname from users

1.2配置 mysql 模块

(1)安装 mysql 模块

npm install mysql

(2)建立连接

//1.导入mysql模块
const mysql = require('mysql');
//2.建立与MYSQL数据库的连接关系
const db = mysql.createPool({
    host:'127.0.0.1',//数据库的IP地址
    user:'root',//登录数据库的账号
    password:'admin123',//登录数据库的密码
    database:'my_db_01'//指定要操作哪个数据库
})

(3)测试是否正常工作

//3.检测mysql 模块能否正常工作
db.query('SELECT 1',(err,results)=>{
    //mysql模块工作期间报错了
    if(err){return console.log(err.message);}
    //只要打印出[ RowDataPacket { '1': 1 } ]的结果,就证明数据库连接正常
    console.log(results);
})

 1.3操作 mysql 数据库

(1)查询数据

//查询users表中所有数据
db.query('select * from users',(err,results)=>{
    if(err){return console.log(err.message);}
    //注意:如果执行的是select查询语句,则执行的结果是数组
    console.log(results);
})
//[ RowDataPacket {id: 1,username: 'lgx',password: '123456',status: 0},
// RowDataPacket {id: 2,username: 'lxy',password: 'admin123',status: 1},]

(2)插入数据

//向users表插入数据
const user = {username:'cbd',password:'123ddd'};
//定义待执行的SQL语句
// ? 表示占位符
const sqlStr = 'insert into users (username,password) values (?,?)';
//执行SQL语句
db.query(sqlStr,[user.username,user.password],(err,results)=>{
    if(err){return console.log(err.message);}
    //注意:如果执行的是insert into插入语句,则result是一个对象
    //可以通过affectedRows属性,来判断是否插入数据成功
    if(results.affectedRows === 1)
    {
        console.log("插入数据成功");
    }
})

向表中新增数据时,如果数据对象的每个属性和数据表的字段一一对应,则可以通过如下方式快速插入数据:

const users = {username:'lqc',password:'123456'};
const sqlStr = 'insert into users set ?';
db.query(sqlStr,users,(err,results)=>{
    if(err){return console.log(err.message);}
    if(results.affectedRows === 1){console.log("插入数据成功");}
})

(3)更新数据

const  users = {id:10,username:'bjq',password:'0624700'};
const sqlStr = 'update users set username = ? , password = ? where id = ?';
db.query(sqlStr,[users.username,users.password,users.id],(err,results)=>{
    if(err){return console.log(err.message);}
    //注意:执行了update语句之后,执行的结果,也是一个对象,可以通过 affectedRows,判断是否更新成功
    if(results.affectedRows === 1)
    {
        console.log("更新数据成功");
    }
}) 

快捷方式:

const users = {id:13,username:"bzt",password:'30624700'};
const sqlStr = 'update users set ? where id = ?';
db.query(sqlStr,[users,users.id],(err,results)=>{
    if(err){return console.log(err.message);}
    if(results.affectedRows===1){console.log("更新数据成功");}
})

(4)删除数据

//删除数据
const sqlStr = 'delete from users where id = ?';
db.query(sqlStr,12,(err,results)=>{
    if(err){return console.log(err.message);}
    //注意:执行delete语句之后,结果也是一个对象,也会包含affectedRows属性
    if(results.affectedRows===1){console.log("删除成功");}
})

使用 delete 语句会真正删除数据,保险起见,使用标记删除的形式,模拟删除的动作。即在表中设置状态字段,标记当前的数据是否被删除。

//标记删除
const sqlStr = 'update users set status = ? where id = ?';
db.query(sqlStr,[1,5],(err,results)=>{
    if(err){return console.log(err.message);}
    if(results.affectedRows === 1){console.log("标记删除成功");}
})

 2.Web 开发模式

2.1服务端渲染

服务器发送给客户端的 HTML 页面,是在服务器通过字符串的拼接动态生成的。因此客户端不需要使用 Ajax 额外请求页面的数据。

app.get('/index.html', (req, res) => {
  const user = { name: 'Bruce', age: 29 }
  const html = `<h1>username:${user.name}, age:${user.age}</h1>`
  res.send(html)
})

优点:

  • 前端耗时短。浏览器只需直接渲染页面,无需额外请求数据。
  • 有利于 SEO。服务器响应的是完整的 HTML 页面内容,有利于爬虫爬取信息。

缺点:

  • 占用服务器资源。服务器需要完成页面内容的拼接,若请求比较多,会对服务器造成一定访问压力。
  • 不利于前后端分离,开发效率低。

2.2 前后端分离

前后端分离的开发模式,依赖于 Ajax 技术的广泛应用。后端只负责提供 API 接口,前端使用 Ajax 调用接口。

优点:

  • 开发体验好。前端专业页面开发,后端专注接口开发。
  • 用户体验好。页面局部刷新,无需重新请求页面。
  • 减轻服务器的渲染压力。页面最终在浏览器里生成。

缺点:

  • 不利于 SEO。完整的 HTML 页面在浏览器拼接完成,因此爬虫无法爬取页面的有效信息。Vue、React 等框架的 SSR(server side render)技术能解决 SEO 问题。

2.3如何选择

  • 企业级网站,主要功能是展示,没有复杂交互,且需要良好的 SEO,可考虑服务端渲染
  • 后台管理项目,交互性强,无需考虑 SEO,可使用前后端分离
  • 为同时兼顾首页渲染速度和前后端分离开发效率,可采用首屏服务器端渲染+其他页面前后端分离的开发模式

3.身份认证

3.1Session 认证机制

服务端渲染推荐使用 Session 认证机制

3.1.1Session 工作原理

 3.1.2Express 中使用 Session 认证

(1)安装 express-session 中间件

npm install express-session

(2)配置中间件

const session = require('express-session')
app.use(
  session({
    secret: 'Bruce', // secret 的值为任意字符串
    resave: false,
    saveUninitalized: true,
  })
)
(3)向 session 中存数据

中间件配置成功后,可通过 req.session 访问 session 对象,存储用户信息

app.post('/api/login', (req, res) => {
  req.session.user = req.body
  req.session.isLogin = true

  res.send({ status: 0, msg: 'login done' })
})

(4)从 session 取数据

app.get('/api/username', (req, res) => {
  if (!req.session.isLogin) {
    return res.send({ status: 1, msg: 'fail' })
  }
  res.send({ status: 0, msg: 'success', username: req.session.user.username })
})

(5)清空 session

app.post('/api/logout', (req, res) => {
  // 清空当前客户端的session信息
  req.session.destroy()
  res.send({ status: 0, msg: 'logout done' })
})

完整代码:

app.js

// 导入 express 模块
const express = require('express')
// 创建 express 的服务器实例
const app = express()

// TODO_01:请配置 Session 中间件
const session = require('express-session');
app.use(
  session({
    secret:'xjqxz',
    resave:false,//固定格式
    saveUninitialized:true//固定格式
  })
)
// 托管静态页面
app.use(express.static('./pages'))
// 解析 POST 提交过来的表单数据
app.use(express.urlencoded({ extended: false }))

// 登录的 API 接口
app.post('/api/login', (req, res) => {
  // 判断用户提交的登录信息是否正确
  if (req.body.username !== 'admin' || req.body.password !== '000000') {
    return res.send({ status: 1, msg: '登录失败' })
  }

  // TODO_02:请将登录成功后的用户信息,保存到 Session 中
  //注意:只有成功配置了express-session这个中间件之后,才能通过req点出来session这个属性
  req.session.user = req.body;//用户的信息
  req.session.islogin = true;//用户的登录状态

  res.send({ status: 0, msg: '登录成功' })
})

// 获取用户姓名的接口
app.get('/api/username', (req, res) => {
  // TODO_03:请从 Session 中获 取用户的名称,响应给客户端
  if(!req.session.islogin){return res.send({status:1,msg:'fail'})}
  res.send({
    status:0,
    msg:'success',
    username:req.session.user.username
  });
})

// 退出登录的接口
app.post('/api/logout', (req, res) => {
  // TODO_04:清空 Session 信息
  req.session.destroy();
  res.send({
    status:0,
    msg:'退出登录成功'
  })
})

// 调用 app.listen 方法,指定端口号并启动web服务器
app.listen(80, function () {
  console.log('Express server running at http://127.0.0.1:80')
})

index.html

<!DOCTYPE html>
<html lang="en">

<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>Document</title>
  <script src="./jquery.js"></script>
</head>

<body>
  <h1>首页</h1>

  <button id="btnLogout">退出登录</button>

  <script>
    $(function () {

      // 页面加载完成后,自动发起请求,获取用户姓名
      $.get('/api/username', function (res) {
        // status 为 0 表示获取用户名称成功;否则表示获取用户名称失败!
        if (res.status !== 0) {
          alert('您尚未登录,请登录后再执行此操作!')
          location.href = './login.html'
        } else {
          alert('欢迎您:' + res.username)
        }
      })

      // 点击按钮退出登录
      $('#btnLogout').on('click', function () {
        // 发起 POST 请求,退出登录
        $.post('/api/logout', function (res) {
          if (res.status === 0) {
            // 如果 status 为 0,则表示退出成功,重新跳转到登录页面
            location.href = './login.html'
          }
        })
      })
    })
  </script>
</body>

</html>

login.html

<!DOCTYPE html>
<html lang="en">

<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>Document</title>
  <script src="./jquery.js"></script>
</head>

<body>
  <!-- 登录表单 -->
  <form id="form1">
    <div>账号:<input type="text" name="username" /></div>
    <div>密码:<input type="password" name="password" /></div>
    <button>登录</button>
  </form>

  <script>
    $(function () {
      // 监听表单的提交事件
      $('#form1').on('submit', function (e) {
        // 阻止默认提交行为
        e.preventDefault()
        // 发起 POST 登录请求
        $.post('/api/login', $(this).serialize(), function (res) {
          // status 为 0 表示登录成功;否则表示登录失败!
          if (res.status === 0) {
            location.href = './index.html'
          } else {
            alert('登录失败!')
          }
        })
      })
    })
  </script>
</body>

</html>

3.2JWT 认证机制

前后端分离推荐使用 JWT(JSON Web Token)认证机制,是目前最流行的跨域认证解决方案

3.2.1JWT 工作原理

Session 认证的局限性:

  • Session 认证机制需要配合 Cookie 才能实现。由于 Cookie 默认不支持跨域访问,所以,当涉及到前端跨域请求后端接口的时候,需要做很多额外的配置,才能实现跨域 Session 认证。
  • 当前端请求后端接口不存在跨域问题的时候,推荐使用 Session 身份认证机制。
  • 当前端需要跨域请求后端接口的时候,不推荐使用 Session 身份认证机制,推荐使用 JWT 认证机制

3.2.2JWT 工作原理图

用户的信息通过 Token 字符串的形式,保存在客户端浏览器中。服务器通过还原 Token 字符串的形式来认证用户的身份。

JWT 组成部分:

  • Header、Payload、Signature
  • Payload 是真正的用户信息,加密后的字符串
  • Header 和 Signature 是安全性相关部分,保证 Token 安全性
  • 三者使用 . 分隔

3.2.3JWT 组成部分

  • Header、Payload、Signature
  • Payload 是真正的用户信息,加密后的字符串
  • Header 和 Signature 是安全性相关部分,保证 Token 安全性
  • 三者使用 . 分隔
Header.Payload.Signature

eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpZCI6MTcsInVzZXJuYW1lIjoiQnJ1Y2UiLCJwYXNzd29yZCI6IiIsIm5pY2tuYW1lIjoiaGVsbG8iLCJlbWFpbCI6InNjdXRAcXEuY29tIiwidXNlcl9waWMiOiIiLCJpYXQiOjE2NDE4NjU3MzEsImV4cCI6MTY0MTkwMTczMX0.bmqzAkNSZgD8IZxRGGyVlVwGl7EGMtWitvjGD-a5U5c

3.2.4JWT 使用方式:

  • 客户端会把 JWT 存储在 localStorage 或 sessionStorage 中
  • 此后客户端与服务端通信需要携带 JWT 进行身份认证,将 JWT 存在 HTTP 请求头 Authorization 字段中
  • 加上 Bearer 前缀
Authorization: Bearer <token>

3.3 Express 使用 JWT

3.3.1安装

  • jsonwebtoken 用于生成 JWT 字符串
  • express-jwt 用于将 JWT 字符串解析还原成 JSON 对象
npm install jsonwebtoken express-jwt

3.3.2定义 secret 密钥

  • 为保证 JWT 字符串的安全性,防止其在网络传输过程中被破解,需定义用于加密和解密的 secret 密钥
  • 生成 JWT 字符串时,使用密钥加密信息,得到加密好的 JWT 字符串
  • 把 JWT 字符串解析还原成 JSON 对象时,使用密钥解密
const jwt = require('jsonwebtoken')
const expressJWT = require('express-jwt')

// 密钥为任意字符串
const secretKey = 'lgxlxy = .='

3.3.3生成 JWT 字符串

调用 jwt.sign() 方法生成 JWT 字符串。

app.post('/api/login', (req, res) => {
  ...
  res.send({
    status: 200,
    message: '登录成功',
    // jwt.sign() 生成 JWT 字符串
    //参数1:用户的信息对象
    //参数2:加密的秘钥
    //参数3:配置对象,可以配置当前token的有效期
    // 尽量不保存敏感信息,因此只有用户名,没有密码
    token: jwt.sign({username: userInfo.username}, secretKey, {expiresIn: '10h'})
    //expiresIn: '10h' 有效时间30秒钟
  })
})

3.3.4JWT 字符串还原为 JSON 对象

  • 客户端访问有权限的接口时,需通过请求头的 Authorization 字段,将 Token 字符串发送到服务器进行身份认证
  • 服务器可以通过 express-jwt 中间件将客户端发送过来的 Token 解析还原成 JSON 对象
// unless({ path: [/^\/api\//] }) 指定哪些接口无需访问权限
//注意:只要配置成功了 express-jwt 这个中间件,就可以把解析出来的用户信息,挂载到req.user属性上
app.use(expressJWT({ secret: secretKey, algorithms: ['HS256']}).unless({ path: [/^\/api\//] }))
const tokenStr = jwt.sign({ username: userinfo.username ,status:userinfo.status}, secretKey, { expiresIn: '30s' })
//记录多个参数
{ username: userinfo.username ,status:userinfo.status}

3.3.5获取用户信息

当 express-jwt 中间件配置成功后,即可在那些有权限的接口中,使用 req.user 对象,来访问从 JWT 字符串中解析出来的用户信息

请求头的 Authorization 字段 的值是Bearer + token(记得token前要加Bearer)

头信息Authorization: Bearer <token>

// 这是一个有权限的 API 接口
app.get('/admin/getinfo', function (req, res) {
  // TODO_05:使用 req.user 获取用户信息,并使用 data 属性将用户信息发送给客户端
  console.log(req.user)
  res.send({
    status: 200,
    message: '获取用户信息成功!',
    data: req.user, // 要发送给客户端的用户信息
  })
})

3.3.6捕获解析 JWT 失败后产生的错误

  • 当使用 express-jwt 解析 Token 字符串时,如果客户端发送过来的 Token 字符串过期或不合法,会产生一个解析失败的错误,影响项目的正常运行
  • 通过 Express 的错误中间件,捕获这个错误并进行相关的处理
// TODO_06:使用全局错误处理中间件,捕获解析 JWT 失败后产生的错误
app.use((err,req,res,next)=>{
  //这次错误是由token解析失败导致的
  if(err.name ==='UnauthorizedError'){
    return res.send({
      status:401,
      message:'无效的token'
    })
  }
  res.send({
    status:500,
    message:'未知的错误'
  })
})

完整代码:

// 导入 express 模块
const express = require('express')
// 创建 express 的服务器实例
const app = express()

// TODO_01:安装并导入 JWT 相关的两个包,分别是 jsonwebtoken 和 express-jwt
const jwt = require('jsonwebtoken')
const expressJWT = require('express-jwt')

// 允许跨域资源共享
const cors = require('cors')
app.use(cors())

// 解析 post 表单数据的中间件
const bodyParser = require('body-parser')
app.use(bodyParser.urlencoded({ extended: false }))

// TODO_02:定义 secret 密钥,建议将密钥命名为 secretKey
const secretKey = 'itheima No1 ^_^'

// TODO_04:注册将 JWT 字符串解析还原成 JSON 对象的中间件
// 注意:只要配置成功了 express-jwt 这个中间件,就可以把解析出来的用户信息,挂载到 req.user 属性上
app.use(expressJWT({ secret: secretKey, algorithms: ['HS256']}).unless({ path: [/^\/api\//] }))

// 登录接口
app.post('/api/login', function (req, res) {
  // 将 req.body 请求体中的数据,转存为 userinfo 常量
  const userinfo = req.body
  console.log(userinfo);
  // 登录失败
  if (userinfo.username !== 'admin' || userinfo.password !== '000000') {
    return res.send({
      status: 400,
      message: '登录失败!',
    })
  }
  // 登录成功
  // TODO_03:在登录成功之后,调用 jwt.sign() 方法生成 JWT 字符串。并通过 token 属性发送给客户端
  // 参数1:用户的信息对象
  // 参数2:加密的秘钥
  // 参数3:配置对象,可以配置当前 token 的有效期
  // 记住:千万不要把密码加密到 token 字符中
  const tokenStr = jwt.sign({ username: userinfo.username ,status:userinfo.status}, secretKey, { expiresIn: '30s' })
  res.send({
    status: 200,
    message: '登录成功!',
    token: tokenStr, // 要发送给客户端的 token 字符串
  })
})

// 这是一个有权限的 API 接口
app.get('/admin/getinfo', function (req, res) {
  // TODO_05:使用 req.user 获取用户信息,并使用 data 属性将用户信息发送给客户端
  res.send({
    status: 200,
    message: '获取用户信息成功!',
    data: req.user, // 要发送给客户端的用户信息
  })
})

// TODO_06:使用全局错误处理中间件,捕获解析 JWT 失败后产生的错误
app.use((err,req,res,next)=>{
  //这次错误是由token解析失败导致的
  if(err.name ==='UnauthorizedError'){
    return res.send({
      status:401,
      message:'无效的token'
    })
  }
  res.send({
    status:500,
    message:'未知的错误'
  })
})
// 调用 app.listen 方法,指定端口号并启动web服务器
app.listen(8888, function () {
  console.log('Express server running at http://127.0.0.1:8888')
})

四、大事件后台 API 项目

API接口文档

开发文档(如下)

另外的借鉴笔记代码

1. 项目初始化

1.1 创建项目

1.新建 api_server 文件夹作为项目根目录,并在项目根目录中运行如下的命令,初始化包管理配置文件:

npm init -y

2.运行如下的命令,安装特定版本的 express

npm i express@4.17.1

3.在项目根目录中新建 app.js 作为整个项目的入口文件,并初始化如下的代码:

const express = require('express')
// 创建 express 的服务器实例
const app = express()

// write your code here...

// 调用 app.listen 方法,指定端口号并启动web服务器
app.listen(3007, function () {
  console.log('api server running at http://127.0.0.1:3007')
})

1.2 配置 cors 跨域

1.运行如下的命令,安装 cors 中间件:

npm i cors@2.8.5

2.在 app.js 中导入并配置 cors 中间件:

// 导入 cors 中间件
const cors = require('cors')
// 将 cors 注册为全局中间件
app.use(cors())

1.3 配置解析表单数据的中间件

通过如下的代码,配置解析 application/x-www-form-urlencoded 格式的表单数据的中间件:

app.use(express.urlencoded({ extended: false }))

1.4 初始化路由相关的文件夹

1.在项目根目录中,新建 router 文件夹,用来存放所有的路由模块

路由模块中,只存放客户端的请求与处理函数之间的映射关系

2.在项目根目录中,新建 router_handler 文件夹,用来存放所有的 路由处理函数模块

路由处理函数模块中,专门负责存放每个路由对应的处理函数

 1.5 初始化用户路由模块

1.在 router 文件夹中,新建 user.js 文件,作为用户的路由模块,并初始化代码:

const express = require('express')
// 创建路由对象
const router = express.Router()

// 注册新用户
router.post('/reguser', (req, res) => {
  res.send('reguser OK')
})

// 登录
router.post('/login', (req, res) => {
  res.send('login OK')
})

module.exports = router

2.在 app.js 中,导入注册用户路由模块 :

const userRouter = require('./router/user')
app.use('/api', userRouter)

1.6 抽离用户路由模块中的处理函数

目的:为了保证 路由模块 的纯粹性,所有的 路由处理函数,必须抽离到对应的 路由处理函数模块

 1.在 /router_handler/user.js 中,使用 exports 对象,分别向外共享如下两个 路由处理函数

/**
 * 在这里定义和用户相关的路由处理函数,供 /router/user.js 模块进行调用
 */

// 注册用户的处理函数
exports.regUser = (req, res) => {
  res.send('reguser OK')
}

// 登录的处理函数
exports.login = (req, res) => {
  res.send('login OK')
}

 2.将 /router/user.js 中的代码修改为如下结构:

const express = require('express')
const router = express.Router()

// 导入用户路由处理函数模块
const userHandler = require('../router_handler/user')

// 注册新用户
router.post('/reguser', userHandler.regUser)
// 登录
router.post('/login', userHandler.login)

module.exports = router

2.登录注册

2.1 新建 ev_users 表

1.在 test 数据库中,新建 ev_users 表如下:

 2.2 安装并配置 mysql 模块

在 API 接口项目中,需要安装并配置 mysql 这个第三方模块,来连接和操作 MySQL 数据库

  1. 运行如下命令,安装 mysql 模块:
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值