使用Node开发网站 中级篇

写在学习之前

学完基础的node知识以后,通过实践来进一步的加深对nodejs的理解 加深对一个东西的理解,最好的方法就是动手去做一个真实的项目,踩过很多坑之后,才算是对一个东西的真正掌握。

进击的Nodejs

nodejs也好,javascript也好,之所以可以火起来的原因,个人认为是因为有了插件这个概念。自己在上大学的时候,也学习过javascript的东西,但是当时只是一个脚本语言,并没有太强大的功能,这次重新学习之后,发现本质上其实是一样的,但是一些很漂亮的效果都是通过插件这种形式,使得JS变得非常强大。包括自己现在在用的ATOM这个编辑器一样,虽然核心的功能就是一个编辑器,但是通过不断的安装各种package,当然这也是一个插件的概念,使得这个编辑器几乎变成了一个万能的东西,任何语言,任何脚本,都可以在这个核心的基础上来得到增加,更多的插件,更强大的功能。这或许可以给自己提供一个新的思路。

常见的web服务框架 Sail, Express, Hapi

Koa2 实现微信公众号前后端开发

课程内容 开发一个可以实时更新的预告片网站 未来给自己的作业,开发一个可以自动更新的奇奇的图片和视频的网站,算是给孩子的礼物

Koa2 + MongoDB + Mongoose + Pug + Bootstrap + 微信 JS-SDK 同时学习Nodejs,MongoDB,Puppeteer,AntDesign,Bootstrap和Parcel

需要这套视频教程的朋友,请联系我

Koa2是Nodejs的上层Web框架
第1章 2018 年的编程姿势
第2章 必会 ES6-7 语法特性与规范
第3章 层层学习 Koa 框架的
第4章 Koa2 与 Koa1 、Express 框架对比
第5章 从 0 开发一个电影预告片网站
第6章 利用爬虫搞定网站基础数据
第7章 彩蛋篇 - [高难度拔高干货] 深度理解 Node.js 异步 IO 模型
第8章 实战篇 - 在 Koa 中向 MongoDB 建立数据模型
第10章 实战篇 - 集成 AntDesign 与 Parcel 打通前后端与构建
第11章 实战篇 - 实现网站前端路由与页面功能
第12章 实战篇 - 实现后台登录权限与管理功能
第13章 服务器部署与发布
第14章 课程总结与展望
自己之前做的是操作层面的东西,不过对于前端的技术也很感兴趣,现在趁着有时间,要把前后端整个打通,为以后做好准备。

学习笔记

nodejs的下载

  • 更新到最新的长期支持版本,保持跟进

nvm

  • node版本管理工具

npm

  • node中使用的package管理工具 Node Package Manager

使用apt安装nodejs

$ sudo apt install nodejs
$ node -v
v10.8.0

先安装nvm,然后用nvm安装node

$ curl -o- https://raw.githubusercontent.com/creationix/nvm/v0.31.4/install.sh | bash
$ nvm ls
->       system
node -> stable (-> N/A) (default)
iojs -> N/A (default)

nvm常用命令

  • 使用nvm –help查看是否安装成功。
  • 使用nvm ls查看已经安装的版本。
  • 使用nvm ls-remote查看所有远端版本。
  • 使用nvm install安装某个版本,如nvm install v5.3.0。
  • 使用nvm use切换到某个版本,如nvm use v5.3.0使用5.3.0,nvm use system使用系统版本。
nvm ls
nvm install v8.9.2
nvm use v8.9.2
nvm alias default v8.9.2
node -v

查看最新稳定版之后,安装v8.11.3

$ nvm install v8.11.3
Downloading https://nodejs.org/dist/v8.11.3/node-v8.11.3-linux-x64.tar.xz...
######################################################################## 100.0%
Now using node v8.11.3 (npm v5.6.0)
Creating default alias: default -> v8.11.3

安装npm并查看npm的版本

$ sudo apt install npm
$ npm -v
5.6.0

执行npm list之后,报下面的错误

npm ERR! extraneous: http-errors@1.6.3 /home/CORPUSERS/xp024975/work/Execrise/NodeJS/node_modules/body-parser/node_modules/http-errors

原因是在当前目录下没有package.json文件,需要初始化一个node项目来管理所有的包,然后重新安装报错的package 再次安装时,需要加上-save参数,将包安装在当前项目里

npm init
npm install --save http-errors
npm list

使用node的常用架构是MEAN(mongodb + express + angular + node) 最大的优点是高并发,适合适合I/O密集型应用

nodejs的好处就是异步处理,以下是node异步处理的进化过程

  • 回调函数Callbacks
  • 异步JavaScript
  • Promise/a+
  • 生成器Generators/ yield
  • Async/ await

Promises

一个规范,提供Promises接口 在框架中有大规模的使用 Promises是链式写法 function().function().function() 链式写法的核心是:每个方法都返回this

Promise表示一个异步操作的最终结果。与Promise最主要的交互方法是通过将函数传入它的then方法从而获取得Promise最终的值或Promise最终最拒绝(reject)的原因。

promise/a+的四个要点

异步操作的最终结果,尽可能每一个异步操作都是独立操作单元
与Promise最主要的交互方法是通过将函数传入它的then方法(thenable)
捕获异常catch error
根据reject和resolve重塑流程

cd-promise.js

const fs = require('fs')
//回调
fs.readFile('./package.json',(err,data) => {
  if(err) return console.log(err)
  data = JSON.parse(data)

  console.log(data.name)
  })

解读:读取当前目录下的package.json文件,然后解析json文件,并读取文件中的name字段的值

cd-promise2.js

const fs = require('fs')
const Promise = require('bluebird')

function readFileAsync (path) {
  return new  Promise((resolve,reject) => {
    fs.readFile(path,(err,data) => {
      if(err) reject(err)
      else resolve(data)
      data = JSON.parse(data)
      console.log('Promise: '+data.name)
    })
    })
}

readFileAsync('./package.json')
  .then(data => {
    data = JSON.parse(data)

    console.log('readFileAysnc: '+data.name)
    })
    .catch(err => {
      console.log(err)
      })

输出结果

$ node cd-promise2
Promise: nodejs
readFileAysnc: nodejs

解读:使用了bluebird的包,首先执行回调函数,然后再执行本身函数readFileAsync的内容,实现了异步操作 Bluebird 是一个广泛使用的 Promise 库

cd-promise3.js

const fs = require('fs')
const util = require('util')
util.promisify(fs.readFile)('./package.json')
  .then(JSON.parse)
  .then(data => {
    console.log(data.name)
    })
    .catch(err => {
        console.log(err)
    })

解读:使用封装在util里面的promise,代码简洁,减少了80%的代码量

package.json

{
  "name": "nodejs",
  "version": "1.0.0",
  "description": "test",
  "main": "index.js",
  "dependencies": {
    "bluebird": "^3.5.1",
    "express": "^4.16.3",
    "fs": "0.0.1-security",
    "http-errors": "^1.7.0",
    "mongodb": "^3.1.1",
    "multer": "^1.3.1",
    "mysql": "^2.16.0",
    "qr-code-with-logo": "^1.0.16",
    "string-width": "^2.1.1",
    "util": "^0.11.0"
  },
  "devDependencies": {},
  "scripts": {
    "test": "echo \"Error: no test specified\" && exit 1",
    "start": "node server.js"
  },
  "author": "",
  "license": "ISC"
}

创建一个package.json,记录项目中使用到的package和包之前的依赖关系

npm init

执行程序

node cb-promise.js

使用async funciton 使用同步的代码,完成异步的操作

promise-async.js

const fs = require('fs')
const util = require('util')
const readAysnc = util.promisify(fs.readFile)

aysnc function init(){
  try{
    let data = await readAysnc('./package.json')
    data = JSON.parse(data)

    console.log(data.name)
  } catch (err) {
    console.log(err)
  }
}
init()

借助Bible支持最新的特性

迭代器

iterator.js

function makeIterator (arr) {
  let nextIndex = 0
  //返回一个迭代器对象
  return {
    next: () => {
       //next()方法返回的结果对象
        if(nextIndex < arr.length)
        return {value:arr[nextIndex++],done:false
        } else {
          return {done:true}
        }
    }
  }
}

const it = makeIterator(['吃饭','睡觉','打豆豆'])

console.log('首先',it.next().value)
console.log('其次',it.next().value)
console.log('然后',it.next().value)
console.log('最后',it.next().value)

生成器 generator.js

function *makeIterator (arr){
  for(let i = 0; i < arr.length; i++){
    yield arr[i]
  }
}

const gen = makeIterator(['吃饭','睡觉','打豆豆'])

console.log('首先',gen.next().value)
console.log('其次',gen.next().value)
console.log('然后',gen.next().value)
console.log('最后',gen.next().value)

输出

$ node generator
首先 吃饭
其次 睡觉
然后 打豆豆
最后 undefined

生成器简化了迭代器构造对象的繁琐过程,同时保证了逻辑清晰

co库

tj大神 https://github.com/tj
贡献了很多高质量的框架和模块
$ npm i co
$ npm i node-fetch

作用:把所有传入的参数都转换为promise

co.js

const co = require('co')
const fetch = require('node-fetch')

co(function *() {
  const res = yield fetch('https://api.douban.com/v2/movie/1291843')
  const movie = yield res.json()
  const summary = movie.summary

  console.log('summary',summary)
  })

yield 实现了一个generator的自动执行

使用run函数来执行co的过程

const co = require('co')
const fetch = require('node-fetch')

<!-- co(function *() {
  const res = yeild fetch('https://api.douban.com/v2/movie/1291843')
  const movie = yield res.json()
  const summary = movie.summary

  console.log('summary',summary)
  }) -->

function run (generator) {
  const iterator = generator()
  const it = iterator.netxt()
  const promise = it.value

  promise.then(data => {
    const it2 = iterator.next(data)
    const promise2  =it2.value

    promise2.then(data2 => {
      iterator.next(data2)
      })
    })
}

run(function *() {
  const res = yeild fetch('https://api.douban.com/v2/movie/1291843')
  const movie = yield res.json()
  const summary = movie.summary

  console.log('summary',summary)
  })

co会把生成的过程自动化

箭头函数

传统函数的写法

function (data){

}

箭头函数的写法

data => {

}

error.js

//const arrow = function (param) {}

//const arrow = (param) => {}

//const arrow = param => {}

//const arrow = param => console.log(param)

//const arrow = param = ({param: param})

//const arrow = (param1, param2) => {}

const arrow = ({id, movie}) => {
    console.log(id,movie)
}

const luke = {
  id:2,
  say: function(){
    setTimeout(function (){
      console.log('id:', this.id)
      },50)
  },
  sayWithThis: function (){
    let that = this //self me  _this
    setTimeout(function (){
      console.log('this id:', that.id)
      },500)
  },
  sayWithArrow: function(){
    setTimeout(() => {
      console.log('arrow id:', this.id)
      },1500)
  },
  sayWithGlobalArrow: () => {
    setTimeout(() => {
      console.log('global arrow id:', this.id)
      },2000)
  }
}

luke.say()
luke.sayWithThis()
luke.sayWithArrow()
luke.sayWithGlobalArrow()

输出结果

$ node error
id: undefined
this id: 2
arrow id: 2
global arrow id: undefined

异步函数

第一阶段,一般通过回调函数来实现异步函数

const fs = require('fs')
//const Promise = require('bluebird')

function readFile (cb) {
    fs.readFile('./package.json',(err,data) => {
      if(err) return cb(err)
      data = JSON.parse(data)
      cb && cb(null.data)
    })
}

readFile((err,data) => {
  if(!err){
    data = JSON.parse(data)
    console.log(data.name)
  }
  })

第二阶段.使用promise来实现

const fs = require('fs')
//const Promise = require('bluebird')

function readFileAsync (path) {
  return new Promise((resolve, reject) => {
    fs.readFile(path,(err,data) => {
        if(err) reject(err)
        else resolve(data)
    })
    })
}

readFileAsync(('./package.json')
  .then(data => {
    data = JSON.parse(data)
    console.log(data.name)
    })
    .catch (err => {
      console.log(err)
      })

第三阶段,使用co库+Genratorfunction+ Promise来实现

const co = require('co')
const util = require('util')

co(function *() {
  let data = yield util.promisify(fs.readFile)('./package.json')
  data = JSON.parse(data)
  console.log(data.name)
}

)

//第四个阶段 Async统一世界 async.js

const fs = require('fs')
const util = require('util')
const readAsync = util.promisify(fs.readFile)

async function init () {
  let data = await readAsync('./package.json')
    data = JSON.parse(data)
    console.log(data.name)
}
init ()

输出

$ node async.js 
nodejs

语言在不断的进化

Async在8.0以后就可以自由使用

在低版本中,可以使用Babel使用import和export

module.js

const fs = require('fs')

fs.writeFile

//运行时加载
const {writeFile} = require('fs')

静态加载

nopm i babel-cli babel-preset-env

新增配置文件 .babelrc

{
  "presents": [
    [
      "env",
      {
        "targets":{
          "node":"current"
        }

      }
    ]
  ]
}

运行babel


安装nodemon

npm -i nodemon

修改配置文件package.json

“dev”:”nodemon -w src –exec "babel-node src –presents env"”,


index.js

import fs from 'fs'

import {writeFile} from 'fs'

运行

run dev

index.js

import {promisify} from 'util'
import {resolve as r } from 'path'
import {readFile, writeFileSync as wfs} from 'fs'

import * as qs from 'querystring'

promisify(readFile)(r(__dirname,'./package.json'))
  .then(data => {
    data = JSON.parse(data)
    console.log(data.name)
    wfs(r(__dirname,'./name'),String(data.name),'utf8')
  })

查看更加详细的用法 关键字 mozila mdn import export

ex.js

export const name = 'Luke'
export const getName = () =>{
  return name
}

Test.js

import {name} from './ex'
import {getName} from './ex'

import age from './ex'

console.log(age)
console.log(name)
console.log(getName())

age.js

const age = 19
export default age

export的用法 单独导出一个方法或者对象 也可以批量导出对象 import {} from ‘’

修改配置文件package.json

"build": "rimraf babel src -s -D -d dist --presents env"

运行编译,将ES6.7转换为可以支持的特性

npm i rimraf
rpm run build
import {}

index.js

const util = require('util')
const readAsync = util.promisify(fs.readFile)

async function init () {
  let data = await promisify(readFile()(r(__dirname,'./package.json'))
    data = JSON.parse(data)
    console.log(data.name)
}
init ()

npm run dev
npm run build

配置插件

npm i -S babel-plugin-transform-runtime babel-runtime
"pluginins":[
  [
    "transform-runtime",{
      "polyfill":false,
      "regenrator":true
    }
  ]
]

层层学习 Koa 框架的 API

Koa 的最大特色,也是最重要的一个设计,就是中间件(middleware)Koa 应用程序是一个包含一组中间件函数的对象,它是按照类似堆栈的方式组织和执行的。Koa中使用app.use()用来加载中间件,基本上Koa 所有的功能都是通过中间件实现的。每个中间件默认接受两个参数,第一个参数是 Context 对象,第二个参数是next函数。只要调用next函数,就可以把执行权转交给下一个中间件。

接收,解析以及响应HTTP的请求

执行上下文:托管中间件 Application Context Request Response Middlewares Seesion Cookie

从源码的角度进行学习

源文件 server/index.js

consta Koa = require ('koa')
consta app = new Koa()

app.use(async (ctx.next) => {
  ctx.body = 'Hi Luke'
})
app.listen(2333)

运行

node server/index.js

在浏览器中执行,可以看到hello world字样

127.0.0.1:2333

解析:前两行和后一行是架设一个 HTTP 服务。中间的则是对用户访问的处理。ctx则是Koa所提供的Context对象(上下文),ctx.body=则是ctx.response.body=的alias(别名),这是响应体设置的API。

Koa Context 将 node 的 request 和 response 对象封装到单个对象中,为编写 Web 应用程序和 API 提供了许多有用的方法。上例的ctx.body = ‘‘即是发送给用户内容,它是ctx.response.body的简写。ctx.response代表 HTTP Response。ctx.request代表 HTTP Request。

可以根据ctx.request.url或者ctx.request.path获取用户请求的路径,来实现简单的路由

路由中间件koa-router

Koa 的最大特色,也是最重要的一个设计,就是中间件(middleware)Koa 应用程序是一个包含一组中间件函数的对象,它是按照类似堆栈的方式组织和执行的。Koa中使用app.use()用来加载中间件,基本上Koa 所有的功能都是通过中间件实现的。每个中间件默认接受两个参数,第一个参数是 Context 对象,第二个参数是next函数。只要调用next函数,就可以把执行权转交给下一个中间件。

查看源码 koa/package.json中的main

web服务类application 框架的使用和框架的实现原理

看框架的方法:

首先看ReadMe.md(十分钟),
然后选择性的看一下History.md看迭代的历史
然后看package.json中看main找到入口文件lib/application.js

依赖模块

  • compose中间件函数数组
  • context运行服务的上下文
  • request客户端请求
  • statuses状态
  • Cookies记录客户信息
  • accepts协议和资源的控制
  • Emitter事件循环
  • assert断言 Stream流 http only convert deprecate接口是否过期

不要上来就盯细节 删掉if,边界条件的处理 删掉异常处理,只考虑正常情况

constructor() 数据的进request,数据的出response 继承自Emitter,在构造器中声名了一些属性

listen() 创建一个服务器实例 然后让服务器实例去监听端口号

use 推进中间件

callback 调用handleRequest

handleRequest 向客户端访问数据 把请求的上下文对象交给中间件,然后把处理完的结果交给handlerespone

createContext

respond res.end向客户端返回数据

传入中间件,监听端口,生成服务器实例 拿到http实例,然后中间件处理,然后把数据返回给客户端

const app = new Koa() app.use(middleware) app.listen(2333)

上下文对象context

delegate代理,委托,将一些方法嫁接过来 创建一个实例,用来操作proto

画图工具mindnode

Request源码

request.js get,set:获取和修改属性值

  • get
  • set

console.log(ctx.href) ctx.path ctx.url ctx.method 通过context拿到一些信息

Response源码

response.js 定义了一些属性和方法

  • get,set:获取和修改属性值

  • socket套接字
  • get header 拿到头信息
  • get status 拿到服务端状态码
  • set status
  • vary() 验证客户端和服务端的内容
  • redirect 重定向
  • attachment 附件
  • type 文档类型
  • etag

中间件 多个中间件会形成一个栈结构(middle stack),以”先进后出”(first-in-last-out)的顺序执行。

    最外层的中间件首先执行。
    调用next函数,把执行权交给下一个中间件。
    ...
    最内层的中间件最后执行。
    执行结束后,把执行权交回上一层的中间件。
    ...
    最外层的中间件收回执行权之后,执行next函数后面的代码。

Server.js

const mid1 = async (ctx, next) => {
  ctx.type = 'text/html; charset = utf-8'
  await next()
  ctx.body = ctx.body + 'Hello'
}

const mid2 = async (ctx, next) => {
  ctx.body = 'Hi'
  await next()
}

const mid3 = async (ctx, next) => {
  ctx.body = ctx.body + 'Fan'
}
app.use(mid1)
app.use(mid2)
app.use(mid3)

app.listen(2333)

解释:app.use 加载用于处理http請求的middleware(中间件),当一个请求来的时候,会依次被这些 middlewares处理。use里面都是中间件,中间件按照顺序执行,是一个栈。中间件执行过程中允许中断。

使用官方中间件koa-logger来验证过程

npm i koa-logger
const logger = require('koa-logger')
app.use(logger)

纯函数 函数满足对于唯一的参数输入x,一定会输出y

function pure (x) {
  return x + 1
}

尾递归 自己调用自己

compose(数组,数组中的每一项是function) 返回Promise

一个函数的输出就是另一个函数的输入,就像一个多米诺骨牌一样联动起来。

使用Seesion

npm i koa-seesion

安装session以后,可以查看源代码 服务器端和客户端的会话

const session = require('koa-session')
app.Keys = ['Hi fan]
app.use(session(app))

app.use()
...
app.listen(2333)

路由 识别不同的页面

app.use(ctx => {
  if(ctx.path === '/'){
    let n = ctx.session.views || 0
    ctx.session.views = ++n
    ctx.body = n +'Times'
  } else if(ctx.path == '/hi'){
    ctx.body = 'Hi, fan'
  } else {
    ctx.body = '404'
  }
})

通过path实现路由识别,制定路由规则

总结 在koa中,一切流程都是中间件 都要流经中间件 顺序执行中间件,传递控制权 每一个中间件都会拿到上下文,可以访问属性和方法 context,request,response可以互相引用

Koa2 与 Koa1 、Express 框架对比

略过

模板引擎

在实际开发中,返回给用户的网页往往都写成模板文件。 Koa 先读取模板文件,然后将这个模板返回给用户,这事我们就需要使用模板引擎了,关于Koa的模版引擎,我们只需要安装koa模板使用中间件koa-views 然后在下载你喜欢的模板引擎便可以愉快的使用了。如安装使用ejs

从0开发一个电影网站

预告片的电影网站

后台管理页面

搭建一个远端的git仓库

github/douban-trailer Username: Scott Dong

提供私有仓库 gitee.com

初始化为nodejs项目

npm init

server/inde.js

const Koa = require('koa')
const app = new Koa()
app.use(async(ctx,next) =>{
  ctx.body = 'Hello Boy'
})
app.listen(4455)

安装最新版的koa

npm i koa@latest

配置package.json文件

"statrt": "node server/index.js"

运行

npm start

服务器返回一个静态HTML页面

网站一般都提供静态资源(图片、字体、样式表、脚本……),我们可以自己实现一个静态资源服务器,但这没必要,koa-static模块封装了这部分功能。

server/tpl/normal.js

module.exports = `
<!DOCTYPE html>
<html>
  <head>
    <meta charset="utf-8">
    <meta name="viewport" content="width=device-width, initial-scale=1">
    <title>Server</title>
    <link href="bootstrap" rel="stylesheet">
    <script src="bootstrap js"></script>
    <srcipt src="jquery js" ></script>
  </head>
  <body>
    <div class="container">
      <div class="row">
        <div class="col-md-8">
          <h1>Hi, Fan</h1>
          <p>This is fan</p>
        </div>
        <div class="col-md-4">
         <p>Test</p>
        </div>
      </div>
  </body>
</html>
`

server/inde.js

const Koa = require('koa')
const app = new Koa()
const {normal} = require('./tpl')

app.use(async(ctx,next) =>{
  ctx.type = 'text/html; charset=utf-8'
  ctx.body = normal
})
app.listen(4455)

server/tpl/index.js

const normalTpl = require('./normal')

module.exports = {
  normal:normalTpl
}

集成模板引擎koa 搭建初始模板目录

模板:快速搭建网站

server/tpl/index.js

module.exports = {
  normalTpl:require('./html')
  ejsTpl:require('./ejs')
  pugTpl:require('./pug')
}

在github上查找ejs server/tpl/ejs.js(拷贝client.html的内容)


npm i ejs --save

jade 带你学习Jade模板引擎

pug.js

module.exports = '
doctype html
html
  head
    meta(charset="utf-8")
    meta(name="viewport")
    title Hi,Server
    link()
    script()
    script()
  body
    .container
      .row
        .col-md-8
          h1 Hi #{you}
          p This is #{me}
        .col-md-4
          p Test Pug Page

server/index.js

const Koa = require('koa')
const app = new Koa()
const {normal, ejsTpl, pugTpl } = require('./tpl')
const pug = require('pug')

app.use(async(ctx,next) =>{
  ctx.type = 'text/html; charset=utf-8'
  ctx.body = pug.render(pugTpl,{
    you: 'Fan',
    me: 'xin'
  })
})
app.listen(4455)

koa-views template engine

通过pug引擎,制定模板文件位置为views 模板文件的后缀名为pug server/index.js

const Koa = require('koa')
const app = new Koa()
const views = require('koa-views')
const { resolve } = require('path')

app.use(views(resolve(__dirname,'./views'),{
  extension:'pug'
}))

app.use(async(ctx,next) =>{
  await ctx.render('index',{
    you:'fan',
    me: 'xin'
  })
  })
})
app.listen(4455)

views/index.pug,实现继承关系 先拿过default的内容,然后填入block块的内容

extends ./layouts/default

blcok title
  title First Page

block content
  .container
    .row
      .col-md-8
        h1 Hi #{you}
        p This is #{me}
      .col-md-4
        p Test Pug Page
module.exports = '
doctype html
html
  head
    meta(charset="utf-8")
    meta(name="viewport")
    title Hi,Server
    link()
    script()
    script()
  body
    .container
      .row
        .col-md-8
          h1 Hi #{you}
          p This is #{me}
        .col-md-4
          p Test Pug Page

一个网站的几个部分 title 从外面传进来

view/layouts/default.pug 使用include包含进样式和脚本

module.exports = '
doctype html
html
  head
    meta(charset="utf-8")
    meta(name="viewport")
    block title
    include ../includes/style
  body
    block content
    include ../incluede/script

view/includes/style.pug

<link href="bootstrap" rel="stylesheet">

view/include/script.pug

<script src="bootstrap js"></script>
<srcipt src="jquery js" ></script>

借助bootstrap 4-x搭建网站首页

server/index.pug

extends ./layouts/default

blcok title
  title First Page

block content
  
  include ./includes/header

  .container-fluid
    .sidebar
    .content
      .row
        .col-md-6
          .card
            img.card-img-top(src='',data-video='')
            .card-body
              h4.card-title title
              p.card-desc description
            .card-footer
              small.text-muted one day ago
        .col-md-6
                  .card
            img.card-img-top(src='',data-video='')
            .card-body
              h4.card-title title
              p.card-desc description
            .card-footer
              small.text-muted one day ago
      .row
        .col-md-6
          .card
            img.card-img-top(src='',data-video='')
            .card-body
              h4.card-title title
              p.card-desc description
            .card-footer
              small.text-muted one day ago
        .col-md-6
                  .card
            img.card-img-top(src='',data-video='')
            .card-body
              h4.card-title title
              p.card-desc description
            .card-footer
              small.text-muted one day ago
弹窗组件
#myModal.modal.fade.bd-example-modal-lg()
...


include ./includes/script

script.
  var player = null;
  判断播放器是否暂停
  ${document}.ready(function(){
    $('#myModal').on('hidden.bs.modal',function(e){
      if(player && player.pause) player.pause()
    })

    $('.card-img-top').click(function (e){
      var image = $(this).data('video')
      var image = $(this).attr('src')

      $('#myModal').modal('show')

      if(!player){
        player = new DPlayer({
          container: document.getElementById('videoModal')
          screenshot: true,
          video: {
            url: video,
            pic:image
            thumbnails: image
          }
        })
      } else{
        if(play.video.currentSrc !== video){
          player.switchVideo({
            url:video,
            pic: image,
            type:'auto'
          })
        }
      }
    })
  })

header.pug

header.navbar

播放器dplayer

style.pug

播放器的样式,直接从github上面拷贝过来

抓取网站数据

爬虫脚本 分析网页文本,提取文本,模拟浏览器 phantomJS, NightMare, pupeteer

Koa2 子父进程通信

利用pupeteer获取电影列表 豆瓣首页

server/crawler/trailer-list.js

const puppeteer = require('puppeteer')
const url = ''
const sleep = time => new Promise(resolve =>{
  setTimeout(resolve,time)
})

;(aysnc() =>{
  console.log('Start')
  const browser = await puppeteer.launch({
    args: ['--no-sandbox'],
    dumpio:false
  })

  const page = await brower.newPage()
  await page.goto(url,{
    waitUntil:'networkidle2'
  })

  await sleep(3000)
  await page.waitForSelector('.more')
  for(let i = 0 ; i < 1; i++){
    await sleep(3000)
    await page.click('.more')
  }
  const result = await page.evaluate(() => {
    var $ = window.$
    var items = $('.list-wp a')
    var links = []

    if(items.length >= 1){
      items.each((index, item) => {
        let it = $(item)
        let doubanId = it.find('div').data('id')
        let title = it.find('.title').text()
        let rate = Number(it.find('.rate').text())
        let poster = it.find('img').attr('src').replace('s_ratio','l_ratio')

        links.push({
          doubanId,
          title,
          rate,
          poster
        })
      })
    }
    return links
  })
  browser.close()
  console.log()
})()

安装

$npm i pupeteer

Child Process使用子进程来爬虫

进程模型

server/tasks/moive.js

const cp = require('child_process')
const { resolve } = require('path')

;(async () => {
  const script = resolve(__dirname, '../crawler/trailer-list')
  const child = cp.fork(script,[])
  
  let invoke = false

  child.on('error',err => {
    if (invoked) return

    involed = true
    console.log(err)
  })

  child.on('exit',code => {
    if(invoked) return

    involed = false
    let err = code === 0 ? null : new Error('exit code '+ code)

    console.log(err)
  } )

  child.on('message', data => {
    let result = data.result
    console.log(result)
  })

})

fork派生子进程 on的方式注册监听函数

server/crawler/trailer-list.js

const puppeteer = require('puppeteer')
const url = ''
const sleep = time => new Promise(resolve =>{
  setTimeout(resolve,time)
})

;(aysnc() =>{
  console.log('Start')
  const browser = await puppeteer.launch({
    args: ['--no-sandbox'],
    dumpio:false
  })

  const page = await brower.newPage()
  await page.goto(url,{
    waitUntil:'networkidle2'
  })

  await sleep(3000)
  await page.waitForSelector('.more')
  for(let i = 0 ; i < 1; i++){
    await sleep(3000)
    await page.click('.more')
  }
  const result = await page.evaluate(() => {
    var $ = window.$
    var items = $('.list-wp a')
    var links = []

    if(items.length >= 1){
      items.each((index, item) => {
        let it = $(item)
        let doubanId = it.find('div').data('id')
        let title = it.find('.title').text()
        let rate = Number(it.find('.rate').text())
        let poster = it.find('img').attr('src').replace('s_ratio','l_ratio')

        links.push({
          doubanId,
          title,
          rate,
          poster
        })
      })
    }
    return links
  })
  browser.close()

  process.send({result})
  process.exit(0)

  console.log()
})()

通过豆瓣API,获取详细数据

豆瓣API V2-> 电影API 拷贝示例 需要认证

tasks/api.js

//服务请求库
const rp = require('request-promise-native')

async function fetchMovie (item) {
  const url = 'http://:::'
  const res = await rp(url)

  return res
}

;(async () =>{
  let movies = [
    {
      doubanId:
      title:
    },{
      doubanId:
      title:
    }
  ]

  movies.map(async movie => {
    let movieData = await fetchMovie(movie)

    try {
      movieData = JSON.parse(movieData)
      console.log(movieData.summary)
    }catch (err){
      console.log(err)
    }

    console.log(movieData)
  })
})

进程的九个问题

  • 什么是同步异步

合租中只有一个卫生间 等着,就是阻塞 分别做不同的事情,定时查看就是非阻塞

查看就是从同步变为异步

异步阻塞

等待完成的事情,直到结束,就是同步 不需要被调用方响应,就是异步。实现方式是主动轮循或者是被调用方的主动通知(执行注册好的回调函数)

调用方在调用的过程中的状态叫阻塞/非阻塞 如果获取是一直等待的状态就是阻塞。反之,就是非阻塞。

通常同步会导致阻塞,而异步不会导致阻塞。

同步异步是过程,阻塞非阻塞是状态。

什么是进程同步

什么是事件驱动 就是有事件的时候,才有动作

阻塞

test/sync.js

const doSync = (sth, time) => new Promise (resolve => {
  setTimeout(() => {
    console.log(sth + 'use'+ time)
    resolve()
  },time)
})

const doAsync = (sth, time, cb) => {
  setTimeout(() => {
    console.log(sth + 'use' +time)
    cb && cb()
  },time)
}

const doElse = (sth) => {
  console.log(sth)
}

const Scott = { doSync, doAsync}
const Meizi = { doSync, doAsync, doElse}


;(async () => {
  console.log('case1: ')
  await Scott.doSync('brush ',1000)
  console.log('wait')
  await Meizi.doSync('wash',2000)
  Meizi.doElse('other')
  //console.log('other')

//注册了回调函数
  console.log('case2: ')
  Scott.doAsync('brush',1000, ()=>{
    console.log('inform meizi')
    Meizi.doAsync('wash',2000)
  })
  Meizi.doElse('other')
})()

参考链接:

更早的文章

使用Node开发网站 基础篇

Bootstrap 包含了一个响应式的、移动设备优先的、不固定的网格系统,可以随着设备或视口大小的增加而适当地扩展到 12 列。它包含了用于简单的布局选项的预定义类,也包含了用于生成更多语义布局的功能强大的混合类。现在 Bootstrap 在中型设备中,会查找带有 md 的类,并使用它们。在大型设备中,会查找带有 lg 的类,并使用它们。sm表示小型设备bootstrap有一个网格布局Bootstrap 使用 Helvetica Neue、 Helvetica、 Arial 和 sans-...…

继续阅读