一路繁花似锦绣前程
失败的越多,成功才越有价值

导航

 

一、nodejs基础

1、认识nodejs
* nodejs是一个javascript运行环境,它让javascript可以开发后端程序,实现几乎其他后端
  语言实现的所有功能,可以与php、java、python、.net、ruby等后端语言平起平坐
* nodejs是基于v8引擎,v8是google发布的开源javascript引擎,本身就是用于chrome浏览器
  的js解释部分,但是ryan dahl这哥们,鬼才般的,把这个v8搬到了服务器上,用于做服务器的软件
2、nodejs的特性
* nodejs语法完全是js语法,只要你懂js基础就可以学会nodejs后端开发
* nodejs超强的高并发能力,实现高性能服务器
* 开发周期短、开发成本低、学习成本低
3、使用nodejs需要了解多少javascript
* http://nodejs.cn/learn/how-much-javascript-do-you-need-to-know-to-use-nodejs
4、浏览器环境vs node环境
* chrome浏览器
    - html/css - javascript
    - blink - v8
    - 中间层
    - 操作系统(网卡/硬盘/显卡...)
* node
    - javascript
    - v8
    - 中间层(libuv)
    - 操作系统(网卡/硬盘/显卡...)
* nodejs可以解析js代码(没有浏览器安全级别的限制)提供很多系统级别的api,如:
    - 文件的读写(file system)
########
const fs = require("fs")

fs.readFile("./ajax.png", "utf-8", (err, content) => {
  console.log(content)
})
########
    - 进程的管理(process)
########
function main(argv) {
  console.log(argv)
}

main(process.argv.slice(2))
########
    - 网络通信(http/https)
########
const http = require("http")
http.createServer((req, res) => {
  res.writeHead(200, {
    "content-type": "text/plain"
  })
  res.write("hello nodejs")
  res.end()
}).listen(3000)
########
5、开发环境搭建
* http://nodejs.cn/download/

二、模块、包、commonjs

1、为什么要有模块化开发
* 依赖关系 - 命名空间 - 代码组织
* 纠结的编程体验
* 定义 - 暴露接口 - 引用
2、commonjs规范
* modules、binary、packages、console、system、encodings、filesystems、sockets
* nodejs/webpack(部分实现规范)
3、modules模块化规范写法
* 我们可以把公共的功能抽离成为一个单独的js文件作为一个模块,默认情况下面这个模块里面的方法或者属性,
  外面是没法访问的。如果要让外部可以访问模块里面的方法或者属性,就必须在模块里面通过exports或者
  module.exports暴露属性或者方法。
########
// m1.js
const name = "gp19"

const sayName = () => {
  console.log(name)
}

console.log("module 1")

// 接口暴露方法一:
module.exports = {
  say: sayName
}

// 接口暴露方法二:
exports.say = sayName

/*
// 错误!
exports = {
  say: sayName
}
*/
########
########
// main.js
const m1 = require("./m1")
m1.say()
########

三、npm&yarn

1、npm的使用
npm init
npm install 包名 -g(uninstall、update)
npm install 包名 --save-dev(uninstall、update)
npm list -g(不加-g,列举当前目录下的安装包)
npm info 包名(详细信息) npm info 包名 version(获取最新版本)
npm install md5@1(安装指定版本)
npm outdated(检查包是否已经过时)
########
"dependencies": {"md5": "^2.1.0"}:^表示如果直接npm install将会安md5 2.*.*最新版本
"dependencies": {"md5": "~2.1.0"}:~表示如果直接npm install将会安装md5 2.1.*最新版本
"dependencies": {"md5": "*"}:*表示如果直接npm install将会安装md5最新版本
########
2、全局安装nrm
* nrm(npm registry manager)是npm的镜像源管理工具,有时候国外资源太慢,使用这个就可以快速地在
  npm源间切换
* 手动切换方法:npm config set registry https://registry.npm.taobao.org
* 安装nrm:在命令行执行命令,npm install -g nrm,全局安装nrm
* 使用nrm:执行命令nrm ls查看可选的源。其中,带*的是当前使用的源,上面的输出表明当前源是官方源
* 切换nrm:如果要切换到taobao源,执行命令nrm use taobao
* 测试速度:你还可以通过nrm test测试相应源的响应时间
    - nrm test
* 扩展:中国npm镜像,这是一个完整npmjs.org镜像,你可以用此代替官方版本(只读),同步频率目前为10分钟
  一次以保证尽量与官方服务同步。
    - npm install -g cnpm --registry=https://registry.npmmirror.com
3、yarn使用
* npm install -g yarn
* 对比npm:
    - 速度超快:yarn缓存了每个下载过的包,所以每次使用时无需重复下载。同时利用并行下载以最大
      化资源利用率,因此安装速度更快。
    - 超级安全:在执行代码之前,yarn会通过算法校验每个安装包的完整性
* 开始新项目
    - yarn init
*添加依赖包
    - yarn add [package]
    - yarn add [package]@[version]
    - yarn add [package] --dev
* 升级依赖包
    - yarn upgrade [package]@[version]
* 移除依赖包
    - yarn remove [package]
* 安装项目的全部依赖
    - yarn install

四、内置模块

1、http模块
// 要使用http服务器和客户端,则必须require("http")
const http = require("http")

// 创建本地服务器来从其接收数据
const server = http.createServer((req, res) => {
  res.writeHead(200, {"Content-Type": "application/json"})
  res.end(JSON.stringify({
    data: "Hello World!"
  }))
})

server.listen(8000)
const http = require("http")

// 创建本地服务器来从其接收数据
const server = http.createServer()

// 监听请求事件
server.on("request", (req, res) => {
  res.writeHead(200, {"Content-Type": "application/json"})
  res.end(JSON.stringify({
    data: "你好世界!"
  }))
})

server.listen(8000)
2、url模块
  • parse
const url = require("url")
const urlString = "https://www.baidu.com:443/ad/index.html?id=8&name=mouse#tag=110"
const parsedStr = url.parse(urlString)
console.log(parsedStr)
  • format
const url = require("url")
const urlobject = {
  protocol: 'https:',
  slashes: true,
  auth: null,
  host: 'www.baidu.com:443',
  port: '443',
  hostname: 'www.baidu.com',
  hash: '#tag=110',
  search: '?id=8&name=mouse',
  query: {id: "8", name: "mouse"},
  pathname: '/ad/index.html',
  path: '/ad/index.html?id=8&name=mouse'
}
const parsedObj = url.format(urlobject)
console.log(parsedObj)
  • resolve
const url = require("url")
var a = url.resolve("/one/two/three", "four") // 注意最后加/,不加/的区别
var b = url.resolve("http://example.com/", "/one")
var c = url.resolve("http://example.com/one", "/two")
console.log(a + "," + b + "," + c)
3、querystring模块
  • parse
const querystring = require("querystring")
var qs = "x=3&y=4"
var parsed = querystring.parse(qs);
console.log(parsed)
  • stringify
const querystring = require("querystring")
var qo = {x: '3', y: '4'}
var parsed = querystring.stringify(qo);
console.log(parsed)
  • excape/unescape
'use strict';
const mysql = require("mysql")

let param = "ns"
let pool = mysql.createPool({
  user: "root",
  password: "root",
  database: "nlp_dict"
})
pool.getConnection(function (err, conn) {
  let sql = 'select * from tb_nature where nature = "' + param + '" and del_status=1'
  conn.query(sql, function (err, result) {
    console.log(result)
  })
})
/**
 * 1、这时正常情况下能查询到一条数据,如果将param修改成
 *   let param = 'ns"-- ';
 *   sql语句就会变成
 *   select * from tb_nature where nature = "ns"-- " and del_status=1
 *   后面的del_status就会被参数中的 -- 注释掉,失去作用,能查询到多条数据
 * 2、如果对param使用escape包装下,就能将参数中的特殊字符进行转义,防止sql的注入
 *   let sql = 'select * from tb_nature where nature = ' + mysql.escape(param) + ' and del_status=1'
 */
const querystring = require("querystring")
var str = "id=3&city=北京&url=https://www.baidu.com"
var escaped = querystring.escape(str)
console.log(escaped)
const querystring = require("querystring")
var str = "id%3D3%26city%3D%E5%8C%97%E4%BA%AC%26url%3Dhttps%3A%2F%2Fwww.baidu.com"
var unescaped = querystring.unescape(str)
console.log(unescaped)
4、http模块补充
  • 接口:jsonp
const http = require("http")
const url = require("url")

const app = http.createServer((req, res) => {
  let urlObj = url.parse(req.url, true)

  switch (urlObj.pathname) {
    case "/api/user":
      // 请求:/api/user?cb=callback
      // 浏览器:window.callback=function (param) {}
      res.end(`${urlObj.query.cb}({"name": "gp145"})`)
      break;
    default:
      res.end("404.")
      break;
  }
})

app.listen(8000, () => {
  console.log("localhost:8000")
})
  • 跨域:cors
const http = require('http')
const url = require('url')
const querystring = require('querystring')

const app = http.createServer((req, res) => {
  let data = ''
  let urlObj = url.parse(req.url, true)

  res.writeHead(200, {
    'content-type': 'application/json;charset=utf-8',
    'Access-Control-Allow-Origin': '*'
  })

  req.on('data', (chunk) => {
    data += chunk
  })

  req.on('end', () => {
    responseResult(querystring.parse(data))
  })

  function responseResult(data) {
    switch (urlObj.pathname) {
      case '/api/login':
        res.end(JSON.stringify({
          message: data
        }))
        break
      default:
        res.end('404.')
        break
    }
  }
})

app.listen(8000, () => {
  console.log('localhost:8000')
})
  • 模拟get
var http = require('http')
var https = require('https')

// 1、接口 2、跨域
const server = http.createServer((request, response) => {
  var url = request.url.substr(1)

  var data = ''

  response.writeHeader(200, {
    'content-type': 'application/json;charset=utf-8',
    'Access-Control-Allow-Origin': '*'
  })

  https.get(`https://m.lagou.com/listmore.json${url}`, (res) => {

    res.on('data', (chunk) => {
      data += chunk
    })

    res.on('end', () => {
      response.end(JSON.stringify({
        ret: true,
        data
      }))
    })
  })

})

server.listen(8080, () => {
  console.log('localhost:8080')
})
  • 模拟post:服务器提交(攻击)
const https = require('https')
const querystring = require('querystring')

const postData = querystring.stringify({
  province: '上海',
  city: '上海',
  district: '宝山区',
  address: '同济支路199号智慧七立方3号楼2-4层',
  latitude: 43.0,
  longitude: 160.0,
  message: '求购一条小鱼',
  contact: '13666666',
  type: 'sell',
  time: 1571217561
})

const options = {
  protocol: 'https:',
  hostname: 'ik9hkddr.qcloud.la',
  method: 'POST',
  port: 443,
  path: '/index.php/trade/add_item',
  headers: {
    'Content-Type': 'application/x-www-form-urlencoded',
    'Content-Length': Buffer.byteLength(postData)
  }
}

function doPost() {
  let data

  let req = https.request(options, (res) => {
    res.on('data', chunk => data += chunk)
    res.on('end', () => {
      console.log(data)
    })
  })

  req.write(postData)
  req.end()
}

// setInterval(() => {
//   doPost()
// }, 1000)
  • 爬虫
const https = require('https')
const http = require('http')
const cheerio = require('cheerio')

http.createServer((request, response) => {
  response.writeHead(200, {
    'content-type': 'application/json;charset=utf-8'
  })

  const options = {
    // protocol: 'https:',
    hostname: 'i.maoyan.com',
    port: 443,
    path: '/',
    method: 'GET'
  }

  const req = https.request(options, (res) => {
    let data = ''
    res.on('data', (chunk) => {
      data += chunk
    })

    res.on('end', () => {
      filterData(data)
    })
  })

  function filterData(data) {
    //   console.log(data)
    let $ = cheerio.load(data)
    let $movieList = $('.column.content')
    console.log($movieList)
    let movies = []
    $movieList.each((index, value) => {
      movies.push({
        title: $(value).find('.movie-title .title').text(),
        detail: $(value).find('.detail .actor').text(),
      })
    })

    response.end(JSON.stringify(movies))
  }

  req.end()
}).listen(3000)
5、event模块
const {EventEmitter} = require("events")

class MyEventEmitter extends EventEmitter {
}

const event = new MyEventEmitter()

const onplay = (movie) => {
  console.log(movie)
}
event.on("play", onplay)
event.once('play', (movie) => {
  console.log('once', movie)
})

event.emit("play", "我和我的祖国")
// event.removeAllListeners()
event.removeListener('play', onplay)
event.emit("play", "中国机长")
6、fs文件操作模块
const fs = require('fs')

// 创建文件夹
fs.mkdir('./logs', (err) => {
  console.log('done.')
})

// 文件夹改名
fs.rename('./logs', './log', () => {
  console.log('done')
})

// 删除文件夹
fs.rmdir('./log', () => {
  console.log('done.')
})

// 写内容到文件里
fs.writeFile(
  './logs/log1.txt',
  'hello',
  // 错误优先的回调函数
  (err) => {
    if (err) {
      console.log(err.message)
    } else {
      console.log('文件创建成功')
    }
  }
)

// 给文件追加内容
fs.appendFile('./logs/log1.txt', '\nworld', () => {
  console.log('done.')
})

// 读取文件内容
fs.readFile('./logs/log1.txt', 'utf-8', (err, data) => {
  console.log(data)
})

// 删除文件
fs.unlink('./logs/log1.txt', (err) => {
  console.log('done.')
})

// 批量写文件
for (var i = 0; i < 10; i++) {
  fs.writeFile(`./logs/log-${i}.txt`, `log-${i}`, (err) => {
    console.log('done.')
  })
}

// 读取文件/目录信息
fs.readdir('./', (err, data) => {
  data.forEach((value, index) => {
    fs.stat(`./${value}`, (err, stats) => {
      // console.log(value + ':' + stats.size)
      console.log(value + ' is ' + (stats.isDirectory() ? 'directory' : 'file'))
    })
  })
})

// 同步读取文件
try {
  const content = fs.readFileSync('./logs/log-1.txt', 'utf-8')
  console.log(content)
  console.log(0)
} catch (e) {
  console.log(e.message)
}

// 异步读取文件:方法一
fs.readFile('./logs/log-0.txt', 'utf-8', (err, content) => {
  console.log(content)
  console.log(0)
})
console.log(1)

// 异步读取文件:方法二
const fs = require("fs").promises
fs.readFile('./logs/log-0.txt', 'utf-8').then(result => {
  console.log(result)
})
/**
 * 1、在fs模块中,提供同步方法是为了方便使用。那我们到底是应该用异步方法还是同步方法呢?
 * 2、由于Node环境执行的JavaScript代码是服务器端代码,
 *   所以,绝大部分需要在服务器运行期反复执行业务逻辑的代码,必须使用异步代码,
 *   否则,同步代码在执行时期,服务器将停止响应,因为JavaScript只有一个执行线程。
 * 3、服务器启动时如果需要读取配置文件,或者结束时需要写入到状态文件时,
 *   可以使用同步代码,因为这些代码只在启动和结束时执行一次,
 *   不影响服务器正常运行时的异步执行。
 */
7、stream流模块
var fs = require('fs');

// 打开一个流:
var rs = fs.createReadStream('sample.txt', {
  // 流是基于事件的,每读一次64kb(触发data事件)。不像readFile一次性读到内存,文件过大内存就会过载
  highWaterMark: 64 * 1024,
  // encoding: 'utf-8' // 默认是Buffer
});

rs.on('data', function (chunk) {
  console.log('DATA:')
  console.log(chunk);
  // rs.pause() // 暂停读
  // rs.resume() // 继续读
});

rs.on('end', function () {
  console.log('END');
});

rs.on('error', function (err) {
  console.log('ERROR: ' + err);
});
/**
 * 1、stream是Node.js提供的又一个仅在服务区端可用的模块,目的是支持“流”这种数据结构。
 * 2、什么是流?流是一种抽象的数据结构。想象水流,当在水管中流动时,就可以从某个地方
 *   (例如自来水厂)源源不断地到达另一个地方(比如你家的洗手池)。
 *   我们也可以把数据看成是数据流,比如你敲键盘的时候,就可以把每个字符依次连起来,看成字符流。
 *   这个流是从键盘输入到应用程序,实际上它还对应着一个名字:标准输入流(stdin)。
 * 3、如果应用程序把字符一个一个输出到显示器上,这也可以看成是一个流,这个流也有名字:
 *   标准输出流(stdout)。流的特点是数据是有序的,而且必须依次读取,或者依次写入,
 *   不能像Array那样随机定位。
 * 4、有些流用来读取数据,比如从文件读取数据时,可以打开一个文件流,然后从文件流中不断地读取数据。
 *   有些流用来写入数据,比如向文件写入数据时,只需要把数据不断地往文件流中写进去就可以了。
 * 5、在Node.js中,流也是一个对象,我们只需要响应流的事件就可以了:data事件表示流的数据已经可以读取了,
 *   end事件表示这个流已经到末尾了,没有数据可以读取了,error事件表示出错了。
 * 6、要注意,data事件可能会有多次,每次传递的chunk是流的一部分数据。
 */
const fs = require('fs')

// 读的速率大于写的速率,浪费空间;写的速率大于读的速率,浪费时间
const readstream = fs.createReadStream('./1.txt', {
  highWaterMark: 1,
  // encoding: 'utf-8'
})
const writestream = fs.createWriteStream('./2.txt', {
  highWaterMark: 2,
  // encoding: 'utf-8'
})

readstream.on('data', data => {
  // 该返回值为false表示:内存中需写的流,未能全部写完
  const flag = writestream.write(data, () => {
    // 该回调函数表示此次写操作已完成
  })
  if (!flag) {
    // 暂停读操作
    readstream.pause()
  }
})
// 内存中需写的流,已全部写完
writestream.on('drain', () => {
  // 继续读操作
  readstream.resume()
})

readstream.on('end', () => {
  writestream.end()
})
/**
 * 1、要以流的形式写入文件,只需要不断调用write()方法,最后以end()结束:
 */
const fs = require('fs')

const readstream = fs.createReadStream('./1.txt')
const writestream = fs.createWriteStream('./2.txt')

readstream.pipe(writestream)
/**
 * 1、pipe 就像可以把两个水管串成一个更长的水管一样,两个流也可以串起来。
 *   一个Readable流和一个Writable流串起来后,所有的数据自动从Readable流进入Writable流,
 *   这种操作叫pipe。
 * 2、在Node.js中,Readable流有一个pipe()方法,就是用来干这件事的。
 * 3、让我们用pipe()把一个文件流和另一个文件流串起来,这样源文件的所有数据就自动写入到目标文件里了,
 *   所以,这实际上是一个复制文件的程序:
 */
8、zlib
const fs = require('fs')
const zlib = require('zlib')

const gzip = zlib.createGzip()

const readstream = fs.createReadStream('./note.txt')
const writestream = fs.createWriteStream('./note2.txt')

readstream
  .pipe(gzip)
  .pipe(writestream)
9、crypto
const crypto = require('crypto');

const hash = crypto.createHash('md5');

// 可任意多次调用update():
hash.update('Hello, world!');
hash.update('Hello, nodejs!');

console.log(hash.digest('hex'));
/**
 * 1、crypto模块的目的是为了提供通用的加密和哈希算法。用纯JavaScript代
 *   码实现这些功能不是不可能,但速度会非常慢。Nodejs用C/C++实现这些算法
 *   后,通过cypto这个模块暴露为JavaScript接口,这样用起来方便,运行速
 *   度也快。
 * 2、MD5是一种常用的哈希算法,用于给任意数据一个“签名”。这个签名通常用一
 *   个十六进制的字符串表示
 * 3、update()方法默认字符串编码为UTF-8,也可以传入Buffer。
 * 4、如果要计算SHA1,只需要把'md5'改成'sha1',就可以得到SHA1的结果
 *   1f32b9c9932c02227819a4151feed43e131aca40。
 */
const crypto = require('crypto');

const hmac = crypto.createHmac('sha256', 'secret-key');

hmac.update('Hello, world!');
hmac.update('Hello, nodejs!');

console.log(hmac.digest('hex')); // 80f7e22570...
/**
 * 1、Hmac算法也是一种哈希算法,它可以利用MD5或SHA1等哈希算法。不同的是,
 *   Hmac还需要一个密钥
 * 2、只要密钥发生了变化,那么同样的输入数据也会得到不同的签名,因此,可以
 *   把Hmac理解为用随机数“增强”的哈希算法。
 */
const crypto = require("crypto");

function encrypt(key, iv, data) {
  let decipher = crypto.createCipheriv('aes-128-cbc', key, iv);
  // decipher.setAutoPadding(true);
  return decipher.update(data, 'binary', 'hex') + decipher.final('hex');
}

// key,iv必须是16个字节
function decrypt(key, iv, crypted) {
  crypted = Buffer.from(crypted, 'hex').toString('binary');
  let decipher = crypto.createDecipheriv('aes-128-cbc', key, iv);
  return decipher.update(crypted, 'binary', 'utf8') + decipher.final('utf8');
}

/**
 * 1、AES是一种常用的对称加密算法,加解密都用同一个密钥。crypto模块提供了AES支持,
 *   但是需要自己封装好函数,便于使用
 * 2、可以看出,加密后的字符串通过解密又得到了原始内容。
 */

五、路由

1、基础
var fs = require("fs")
var path = require("path")

function render(res, path) {
  res.writeHead(200, {"Content-Type": "text/html;charset=utf8"})
  res.write(fs.readFileSync(path, "utf8"))
  res.end()
}

const route = {
  "/login": (req, res) => {
    render(res, "./static/login.html")
  },
  "/home": (req, res) => {
    render(res, "./static/home.html")
  },
  "/404": (req, res) => {
    res.writeHead(404, {"Content-Type": "text/html;charset=utf8"})
    res.write(fs.readFileSync("./static/404.html", "utf8"))
  }
}
2、获取参数
  • get请求
const route = {
  "/api/login": (req, res) => {
    const myURL = new URL(req.url, 'http://127.0.0.1:3000');
    console.log(myURL.searchParams.get("username"))
    render(res, `{ok:1}`)
  }
}
  • post请求
const route = {
  "/api/login": (req, res) => {
    var post = '';
    // 通过req的data事件监听函数,每当接受到请求体的数据,就累加到post变量中
    req.on('data', function (chunk) {
      post += chunk;
    });
    
    // 在end事件触发后,通过querystring.parse将post解析为真正的POST请求格式,然后向客户端返回。
    req.on('end', function () {
      post = JSON.parse(post);
      render(res, `{ok:1}`)
    });
  }
}
3、静态资源处理
function readStaticFile(req, res) {
  const myURL = new URL(req.url, 'http://127.0.0.1:3000')
  var filePathname = path.join(__dirname, "/static", myURL.pathname);

  if (fs.existsSync(filePathname)) {
    // npm i mime
    res.writeHead(200, {"Content-Type": `${mime.getType(myURL.pathname.split(".")[1])};charset=utf8`})
    res.write(fs.readFileSync(filePathname, "utf8"))
    res.end()
    return true
  } else {
    return false
  }
}

六、express

1、特色
* https://www.expressjs.com.cn/
* 基于 Node.js 平台,快速、开放、极简的 web 开发框架。
* web应用:express是一个基于nodejs平台的极简、灵活的web应用开发框架,它提供一系列强大的特性,
  帮助你创建各种web和移动设备应用
* api:丰富的http快捷方法和任意排列组合的connect中间件,让你创建健壮、友好的api变得即快速又简单
* 性能:express不对nodejs已有的特性进行二次抽象,我们只是在它之上扩展了web应用所需的基本功能
2、安装
* npm install express --save
3、路由
var express = require('express');
var app = express();

// respond with "hello world" when a GET request is made to the homepage
app.get('/', function (req, res) {
  res.send('hello world');
});
app.listen(3000, function () {
  console.log("web服务启动")
});
/**
 * 1、路由是指如何定义应用的端点(URIs)以及如何响应客户端的请求。
 * 2、路由是由一个 URI、HTTP 请求(GET、POST等)和若干个句柄组成,
 *   它的结构如下: app.METHOD(path, [callback...], callback),
 *   app 是 express 对象的一个实例,
 *   METHOD 是一个 HTTP 请求方法,
 *   path 是服务器上的路径,
 *   callback 是当路由匹配时要执行的函数。
 * 3、下面是一个基本的路由示例
 */
// 匹配根路径的请求
app.get('/', function (req, res) {
  res.send('root');
});

// 匹配 /about 路径的请求
app.get('/about', function (req, res) {
  res.send('about');
});

// 匹配 /random.text 路径的请求
app.get('/random.text', function (req, res) {
  res.send('random.text');
});
/**
 * 1、路由路径和请求方法一起定义了请求的端点,它可以是字符串、字符串模式或者正则表达式。
 */
// 匹配 acd 和 abcd
app.get('/ab?cd', function (req, res) {
  res.send('ab?cd');
});

// 匹配 /ab/******
app.get('/ab/:id', function (req, res) {
  res.send('aaaaaaa');
});

// 匹配 abcd、abbcd、abbbcd等
app.get('/ab+cd', function (req, res) {
  res.send('ab+cd');
});

// 匹配 abcd、abxcd、abRABDOMcd、ab123cd等
app.get('/ab*cd', function (req, res) {
  res.send('ab*cd');
});

// 匹配 /abe 和 /abcde
app.get('/ab(cd)?e', function (req, res) {
  res.send('ab(cd)?e');
});
/**
 * 1、使用字符串模式的路由路径示例
 */
// 匹配任何路径中含有 a 的路径:
app.get(/a/, function (req, res) {
  res.send('/a/');
});

// 匹配 butterfly、dragonfly,不匹配 butterflyman、dragonfly man等
app.get(/.*fly$/, function (req, res) {
  res.send('/.*fly$/');
});
/**
 * 1、使用正则表达式的路由路径示例
 */
app.get('/example/a', function (req, res) {
  res.send('Hello from A!');
});
/**
 * 1、可以为请求处理提供多个回调函数,其行为类似 中间件。
 *   唯一的区别是这些回调函数有可能调用 next('route')
 *   方法而略过其他路由回调函数。
 *   可以利用该机制为路由定义前提条件,如果在现有路径上继续执行没有意义,
 *   则可将控制权交给剩下的路径。
 */
app.get('/example/b', function (req, res, next) {
  console.log('response will be sent by the next function ...');
  next();
}, function (req, res) {
  res.send('Hello from B!');
});
/**
 * 1、使用多个回调函数处理路由(记得指定 next 对象)
 */
var cb0 = function (req, res, next) {
  console.log('CB0')
  next()
}

var cb1 = function (req, res, next) {
  console.log('CB1')
  next()
}

var cb2 = function (req, res) {
  res.send('Hello from C!')
}

app.get('/example/c', [cb0, cb1, cb2])
/**
 * 1、使用回调函数数组处理路由
 */
var cb0 = function (req, res, next) {
  console.log('CB0')
  next()
}

var cb1 = function (req, res, next) {
  console.log('CB1')
  next()
}

app.get('/example/d', [cb0, cb1], function (req, res, next) {
  console.log('response will be sent by the next function ...')
  next()
}, function (req, res) {
  res.send('Hello from D!')
})
/**
 * 1、混合使用函数和函数数组处理路由
 */
4、中间件
* Express 是一个自身功能极简,完全是由路由和中间件构成一个的 web 开发框架:
  从本质上来说,一个 Express 应用就是在调用各种中间件。
* 中间件(Middleware) 是一个函数,它可以访问请求对象(request object (req)),
  响应对象(response object (res)), 和 web 应用中处于请求-响应循环流程中的中间件,
  一般被命名为 next 的变量。
* 中间件的功能包括:
    - 执行任何代码。
    - 修改请求和响应对象。
    - 终结请求-响应循环。
    - 调用堆栈中的下一个中间件。
* 如果当前中间件没有终结请求-响应循环,则必须调用 next() 方法将控制权交给下一个中间件,
  否则请求就会挂起。
* Express 应用可使用如下几种中间件:
    - 应用级中间件
    - 路由级中间件
    - 错误处理中间件
    - 内置中间件
    - 第三方中间件
* 使用可选则挂载路径,可在应用级别或路由级别装载中间件。另外,你还可以同时装在一系列中间件函数,
  从而在一个挂载点上创建一个子中间件栈。
  • 应用级中间件
var app = express()

// 没有挂载路径的中间件,应用的每个请求都会执行该中间件
app.use(function (req, res, next) {
  console.log('Time:', Date.now())
  next()
})
/**
 * 1、应用级中间件绑定到 app 对象 使用 app.use() 和 app.METHOD(),
 *   其中, METHOD 是需要处理的 HTTP 请求的方法,
 *   例如 GET, PUT, POST 等等,全部小写。例如
 */
  • 路由级中间件
var app = express()
var router = express.Router()

// 没有挂载路径的中间件,通过该路由的每个请求都会执行该中间件
router.use(function (req, res, next) {
  console.log('Time:', Date.now())
  next()
})

// 一个中间件栈,显示任何指向 /user/:id 的 HTTP 请求的信息
router.use('/user/:id', function (req, res, next) {
  console.log('Request URL:', req.originalUrl)
  next()
}, function (req, res, next) {
  console.log('Request Type:', req.method)
  next()
})

// 一个中间件栈,处理指向 /user/:id 的 GET 请求
router.get('/user/:id', function (req, res, next) {
  // 如果 user id 为 0, 跳到下一个路由
  // next如果传参则会跳到错误处理中间件,错误处理中间件第一个参数就是next的传参
  if (req.params.id == 0) next('route')
  // 负责将控制权交给栈中下一个中间件
  else next() //
}, function (req, res, next) {
  // 渲染常规页面
  res.render('regular')
})

// 处理 /user/:id, 渲染一个特殊页面
router.get('/user/:id', function (req, res, next) {
  console.log(req.params.id)
  res.render('special')
})

// 将路由挂载至应用
app.use('/', router)
/**
 * 1、路由级中间件和应用级中间件一样,只是它绑定的对象为 express.Router()。
 */
  • 错误处理中间件
app.use(function (err, req, res, next) {
  console.error(err.stack)
  res.status(500).send('Something broke!')
})
/**
 * 1、错误处理中间件和其他中间件定义类似,只是要使用 4 个参数,
 *   而不是 3 个,其签名如下: (err, req, res, next)。
 */
  • 内置的中间件
app.use(express.static('public'))
app.use(express.static('uploads'))
app.use(express.static('files'))
/**
 * 1、express.static 是 Express 唯一内置的中间件。
 *   它基于 serve-static,负责在 Express 应用中提托管静态资源。
 *   每个应用可有多个静态目录。
 */
  • 第三方中间件
var express = require('express')
var app = express()
// npm install cookie-parser
var cookieParser = require('cookie-parser')

// 加载用于解析 cookie 的中间件
app.use(cookieParser())
/**
 * 1、安装所需功能的 node 模块,并在应用中加载,可以在应用级加载,也可以在路由级加载。
 * 2、下面的例子安装并加载了一个解析 cookie 的中间件: cookie-parser
 */
5、获取请求参数
  • get
req.query
  • post
app.use(express.urlencoded({extended:false}))
app.use(express.json())
req.body
6、利用 Express 托管静态文件
app.use(express.static('public'))
/**
 * 1、通过 Express 内置的 express.static 可以方便地托管静态文件,
 *   例如图片、CSS、JavaScript 文件等。
 * 2、将静态资源文件所在的目录作为参数传递给 express.static
 *   中间件就可以提供静态资源文件的访问了。例如,假设在 public
 *   目录放置了图片、CSS 和 JavaScript 文件,你就可以
 * 3、现在,public 目录下面的文件就可以访问了
 *     - http://localhost:3000/images/kitten.jpg
 *     - http://localhost:3000/css/style.css
 *     - http://localhost:3000/js/app.js
 *     - http://localhost:3000/images/bg.png
 *     - http://localhost:3000/hello.html
 * 4、所有文件的路径都是相对于存放目录的,因此,存放静态文件的目录名不会出现在 URL 中。
 */
app.use(express.static('public'))
app.use(express.static('files'))
/**
 * 1、如果你的静态资源存放在多个目录下面,你可以多次调用 express.static 中间件
 * 2、访问静态资源文件时,express.static 中间件会根据目录添加的顺序查找所需的文件。
 */
app.use('/static', express.static('public'))
/**
 * 1、如果你希望所有通过 express.static 访问的文件都存放在一个
 *   “虚拟(virtual)”目录(即目录根本不存在)下面,可以通过为静态
 *   资源目录指定一个挂载路径的方式来实现,如下所示
 * 2、现在,你就可以通过带有 “/static” 前缀的地址来访问 public
 *   目录下面的文件了。
 *     - http://localhost:3000/static/images/kitten.jpg
 *     - http://localhost:3000/static/css/style.css
 *     - http://localhost:3000/static/js/app.js
 *     - http://localhost:3000/static/images/bg.png
 *     - http://localhost:3000/static/hello.html
 */
7、服务端渲染(模板引擎)
* 服务器渲染,后端嵌套模板,后端渲染模板,SSR(后端把页面组装)
    - 做好静态页面,动态效果
    - 把前端代码提供给后端,后端要把静态html以及里面的假数据给删掉,
      通过模板进行动态生成html的内容
* 前后端分离,BSR(前端中组装页面)
    - 做好静态页面,动态效果
    - json模拟,ajax,动态创建页面
    - 真实接口数据,前后联调
    - 把前端提供给后端静态资源文件夹
* npm i ejs
* 需要在应用中进行如下设置才能让 Express 渲染模板文件:
    - views, 放模板文件的目录,比如: app.set('views', './views')
    - view engine, 模板引擎,比如: app.set('view engine', 'ejs')
    - res.render("login"); // 找views文件夹下的login.ejs
    - res.redirect("/home"); // 重定向到home
########
// 等价
res.setHeader('Location', 'https://www.baidu.com')
res.statusCode = 302
res.end()
########
########
<% %>流程控制标签(写的是if else,for)
<%= %>输出标签(原文输出html标签)
<%- %>输出标签(html会被浏览器解析)
<%# %>注释标签
<%- include('user/show',{user:user}) %>导入公共的模板内容
########
* 支持直接渲染html文件
    - app.set('view engine', 'html')
    - app.engine("html", require("ejs").renderFile)
posted on 2023-02-20 23:05  一路繁花似锦绣前程  阅读(59)  评论(0编辑  收藏  举报