模块化开发

CommonJS导入导出语法

不同于ES6的import和export语法

  1. const yyy = require("zzz") 引入一个zzz外部模块赋值给yyy变量
  2. module.exports = xxx 暴露一个xxx模块接口

外部模块

分类:
核心模块:node 自带的模块,可以使用require 直接引用模块名
自定义模块:我们自己编写的,引入自定义模块需要引用完整路径
第三方模块:使用npm下载的模块;下载完成后可以使用require 直接引用模块名
node-dev的使用
node-dev server.js 8888
可以预览页面效果并实时更新页面

常用API

文档查阅:https://devdocs.io/node/
image.png

curl命令构造请求

curl构造请求
GET 请求:curl -v url
POST 请求: curl -v -d 'name=npc' url
设置请求头:-H 'Content-Type:application/json'
设置动词:-X PUT
JSON请求:curl -d '{"name":"body"}' -H 'Content-Type:application/json' url

核心模块

fs

fs.readFile(path[, options], callback)异步地读取一个文件的全部内容
回调有两个参数 (err, data),其中 data 是文件的内容。

  1. fs.readFile('./views/index.html', (err, data) => {
  2. if (err) {
  3. throw err//或者 console.log(err);res.end() 必须res.end(),否则死循环
  4. }
  5. else{
  6. res.write(data.toString()) //返回给客户端的内容
  7. //data是一个二进制的Buffer类型,需要转化为字符串
  8. res.end()
  9. }
  10. });

如果未指定字符编码,则返回原始的 buffer。
如果 options 是一个字符串,则它指定了字符编码。 例子:
fs.readFile('/etc/passwd', 'utf8', callback);

  1. //针对req.url做判断机制 + 声明一个path变量赋值给fs.readFile()第一个实参
  2. let path = './views/'
  3. switch(req.url){
  4. case '/':
  5. path += 'index.html';
  6. res.statusCode = 200
  7. break;
  8. case: '/about':
  9. path += 'about.html';
  10. res.statusCode = 200
  11. break;
  12. case: '/about-other': //重定向
  13. res.statusCode = 301;
  14. res.setHeader('Location','/about') //设置header
  15. res.end()
  16. default:
  17. path += '404.html';
  18. res.statusCode = 404
  19. break
  20. }
  21. fs.readFile(path, (err, data) => {
  22. if (err) {
  23. throw err//或者 console.log(err);res.end() 必须res.end(),否则死循环
  24. }
  25. else{
  26. res.write(data) //data是一个二进制的Buffer类型,需要转化为字符串
  27. res.end()
  28. }
  29. });

path

path模块提供了一些用于 处理文件或目录的工具函数
const path = require('path');
如:path.join([…paths])

  1. path.join('/foo', 'bar', 'baz/asdf', 'quux', '..');
  2. // 返回: '/foo/bar/baz/asdf'
  3. //也可以将路径赋值变量然后直接作为形参

http

http.createServer([requestListener])返回一个新建的 http.Server 实例,即在本地创建一个服务器。
server.listen(options[, callback])options中可以传入需要开启的端口port以及服务器启动成功后的回调函数

  1. const http = require('http')
  2. const server = http.createServer((req,res) => { //创建本地服务器,回调可 接收请求和定义响应
  3. console.log(req.url,req.method) //常用的req属性
  4. res.setHeader('Content-Type','text/plain')//常用res之设置header,说明返回的数据类型
  5. res.write("hello npc") //返回给客户端的内容,可能有多个res.write()
  6. res.end('hello npc') //若res.write()参数的data只有一个,则可直接写在end方法中
  7. })
  8. server.listen(3000,()=> { //服务器监听端口,成功开启服务器并设定监听端口,则会触发函数
  9. console.log('Server is running')
  10. })

nodemon 第三方模块:使用nodemon启动服务器,修改node文件后自动更新页面

数据流&pipe管道

读写数据是怎样的流程?
使用数据之前确保数据已经加载完成
硬盘 → 内存 → 客户端
若传输数据庞大,客户端拿到数据之前需要等待内存加载完成数据
image.png
使用管道传输,数据不间断地、小批量地传输,占用内存时间长,但占内存空间小
image.png

  1. const fs = require('fs')
  2. //创建 读取流 的对象
  3. const readStream = fs.createReadStream('./docs/data.txt',{encoding:'utf8'})
  4. //创建 写数据流 的对象
  5. const writeStream = fs.createWriteStream('./docs/data2.txt')
  6. //先读再写,读取一小段一小段,再写入一小段一小段
  7. readStream.on('data',chunk => {
  8. // console.log('======读取一小段一小段=====')
  9. // console.log(chunck.toString()) //toString()可被encoding属性替换 ↑
  10. writeStream.write('====写入一小段=====')
  11. writeStream.write(chunk)
  12. }
  13. ) // .on添加data事件,可传回调
  14. //优化版:直接管道传输
  15. readStream.pipe(writeStream) //将data.txt的数据全部传输到data2.txt中

结合ts文件写node

在js文件中可以自然地写node,因为node就是js语言构成,但在ts文件中写node的话,ts无法辨别node的模块,故需要:
安装所有node的所有模块的声明文件:yarn add --dev @types/node,出现node_modules 》types》node
启动node服务器:ts-node-dev index.ts

现在可以开始正常地在本地开发nodejs的ts应用了

Koa框架

Koa是一个基于Node 的web 服务器开发框架
注意安装的npm包名均是小写koa
简易搭建server项目:

  • 当前目录下npm init -y,生成package.json,并在script脚本中使用:"scripts":{"dev":"nodemon app.js"}
  • 根目录/app.js创建实例
  • yarn dev开启web服务器
  • 可在页面输入listen后的端口查看web服务器数据
  • 下面需要做的就是让koa服务器能够接受并操作前端发送过来的请求数据
  • 常见项目结构:image.png ```javascript const Koa = require(‘koa’); const app = new Koa(); //创建koa应用

app.use(async ctx => { //use函数是一个中间件,里面可放异步函数 ctx.body = ‘Hello World’; //ctx 是上下文,保存request对象和response对象 });

app.listen(3000);

  1. <a name="nH8th"></a>
  2. ## ctx
  3. 每个请求都将创建一个 Context,并在中间件中作为接收器引用,或者 ctx 标识符,<br />封装了request 和 response 对象<br />如以下代码片段所示:
  4. ```javascript
  5. app.use(async ctx => {
  6. ctx; // 这是 Context
  7. ctx.request; // 这是 koa Request
  8. ctx.response; // 这是 koa Response
  9. });

ctx.redirect

执行 [302] 重定向到 url.

  1. ctx.redirect('/login');
  2. ctx.redirect('http://google.com');

要更改 “302” 的默认状态,只需在该调用之前或之后给 status 赋值。要变更主体请在此调用之后:

  1. ctx.status = 301;
  2. ctx.redirect('/cart');
  3. ctx.body = 'Redirecting to shopping cart';

示例:服务端处理请求

  1. router.post('/login',async ctx => {
  2. let username = ctx.request.body.username
  3. let password = ctx.request.body.password
  4. if(username === 'npc' && password === '123456') {
  5. ctx.redirect('/content') //重定向做跳转
  6. }else {
  7. ctx.redirect('/')
  8. }
  9. })

示例:只有登录成功后才能访问:
请求访问content页面时,服务端检验:只有请求体携带登录过的用户信息后才能访问

  1. router.get('/content',async ctx = {
  2. if(ctx.session.user) {
  3. await ctx.render('content.html')
  4. }else{
  5. ctx.redirect('/')
  6. }
  7. })

app.use(function)

将给定的中间件方法添加到此应用程序。app.use() 返回 this, 因此可以链式表达.

  1. app.use(someMiddleware)
  2. .use(someOtherMiddleware)
  3. .listen(3000)

问:中间件在什么时候运行?
答:在请求后,响应前运行

koa常用第三方模块(中间件):

每个koa第三方模块都需要通过**koa.use**引入到koa应用中

  • koa-session:让无状态的http拥有状态,基于cookie实现的后台保存信息的session
  • koa-mysql:封装了需要用到的SQL语句
  • koa-mysql-session:当不想让session存储到内存,而想让session存储到mysql数据库中时使用
  • koa-router:后台会接受到各种请求的url,路由会根据不同的url来使用不同的处理逻辑。
  • koa-views:请求html页面时,后台会用模板引擎渲染数据到模板上,然后返回给后台
  • koa-static:请求img、js、css等文件时,不需要其他逻辑,只需要读取文件
  • koa-better-body:post上传文件时,解析请求体
  • koa-parser:解析post请求参数

koa路由中间件:

访问不同url,跳转不同页面内容

  1. const Koa = require('koa');
  2. const Router = require('koa-router');
  3. const app = new Koa();
  4. const router = new Router();
  5. router.get('/',async (ctx) => { //前端发送get请求,后端定义API接口
  6. ctx.body = "hello koa"
  7. })
  8. router.get('/news',async (ctx) => {
  9. ctx.type = 'html';
  10. ctx.body = '<h2>新闻列表</h2>';
  11. })
  12. // 调用router.routes()来 组装匹配好的路由,返回一个合并好的中间件
  13. app.use(router.routes());
  14. /*
  15. 调用router.allowedMethods()获得一个中间件,当发送了不符合的请求时,
  16. 会返回 `405 Method Not Allowed` 或 `501 Not Implemented`
  17. */
  18. app.use(router.allowedMethods({
  19. // throw: true, // 抛出错误,代替设置响应头状态
  20. // notImplemented: () => '不支持当前请求所需要的功能',
  21. // methodNotAllowed: () => '不支持的请求方式'
  22. }));
  23. app.listen(3000,()=>{
  24. console.log('应用已经启动,http://localhost:3000');
  25. });

理解:
响应体中返回图片的话如何设置?
在网页中插入图片,需要在img标签中添加src属性写图片的地址,src中只能写静态服务器对应的目录,这样才能得到地址,即web应用的服务器,只有静态文件目录的文件才可以被html页面直接访问
即不能直接将图片等页面内容文件拷贝到server.js文件的同级,即不能向以上路由示例那样写
为什么?
比如,我们发布一个网站,所有的客户端的用户要访问这个网站的图片等内容,用户不可以直接通过浏览器访问到服务器的任何文件,否则文件不安全,需要在中间创建一个静态文件目录,而这个目录需要用Koa来设置,即需要用到koa-static静态资源中间件

打通前端和后端

前端项目请求后端接口并拿到对应数据:
使用vue.config.js代理,proxy会代理转发前端的请求,将请求转发至指定的后端服务器接口所在url

  1. module.exports = {
  2. devServer: {
  3. proxy: 'http://localhost:4000' //写入后端服务器接口所在url
  4. }
  5. }

此外,在前端发axios请求的时候,url只需二级域名开始写,前面不再需要接上后端服务器接口url,否则依然报跨域错

  1. asycn getData() {
  2. const result = await axios.get("/user") //url只需二级开始
  3. }

koa-static静态资源中间件

dirname 同 path.dirname() ,返回当前文件的目录的绝对路径
filename ,返回当前文件的绝对路径名

  1. //示例:在 /Users/mjr/example.js 运行 node
  2. console.log(__dirname); // 打印: /Users/mjr
  3. console.log(path.dirname(__filename)); // 打印: /Users/mjr/example.js
  4. console.log(__filename); // 打印: /Users/mjr/example.js

static函数设置静态目录:

  1. cosnt Koa = require("koa")
  2. const static = require("koa-static")
  3. const app = new Koa()
  4. app.use(static(__dirname + "/public")) //static函数,可以设置一个名为public的静态目录
  5. app.use(async (ctx) => {
  6. ctx.body = `
  7. <h1>这是一个图片网页<h1>
  8. <img src='/imgs/logo.jpg'>
  9. ` //public静态目录中可设置imgs、css、js文件夹
  10. })
  11. app.listen(3000,() => {console.log("server is running")})

补充:
静态文件服务器(静态网页)
定义:自动查找对应文件,即静态文件服务器(没有请求数据库)
举例:如 http-server
实操示例:改造nodejs-test文件

动态文件服务器(动态网页)
定义:请求了数据库,就是动态服务器
实操示例:使用json文件当做数据库

模板引擎nunjucks

使用模板引擎可以动态设置响应的html文件,如可以把后台数据库的数据绑定到模板中,然后发送给客户端,也可以针对用户个性化交互操作返回对应数据,而不是只能返回整个html文件或字符串

注意:

  • 需要把配置模板引擎的代码移动到所有与路由相关的代码之前,模板引擎才能正常渲染,即将app.use(router.routes())移动到app.listen之前即可
  • 每次修改模板引擎后最好重启一下服务器

    基本使用

    安装:在koa框架下使用nunjucks需要两个第三方模块

    1. npm install --save koa-views //负责配置koa的模板引擎用哪个模板引擎
    2. npm install --save nunjucks //
  • views函数传递两个参数,第一个指定模板引擎渲染到哪个静态目录中;第二个指定哪个模板引擎渲染哪个文件

  • ctx.render是个异步函数,传递两个参数,第一个指定渲染哪个文件(views设置的静态目录中查找),第二个指定数据(可以是对象或者文件)传递给模板(动态加载数据) ```javascript const Koa = require(“koa”) const views = reuqire(“koa-views”) const Router = require(“koa-router”) const nunjucks = require(“nunjucks”)

const app = new Koa() const router = new Router()

//一般模板引擎 结合 路由使用: app.use(views(__dirname + “/views”,{map:{html:”nunjucks”}})) app.use(router.routes())

router.get(“/“,async (ctx) => { aswait ctx.render(“index”,{title:”首页”}) })

router.get(“/video”,async (ctx) => { await ctx.render(“index”,{title:”视频”}) })

  1. <a name="BOnCr"></a>
  2. ### 模板语法
  3. <a name="FTioV"></a>
  4. #### 循环语句
  5. ```javascript
  6. <ul>
  7. {% for item in items %}
  8. <li>{{ item.title }}</li>
  9. {% else %}
  10. <li>This would display if the 'item' collection were empty</li>
  11. {% endfor %}
  12. </ul>

if语句

  1. {% if hungry %}
  2. I am hungry
  3. {% elif tired %}
  4. I am tired
  5. {% else %}
  6. I am good!
  7. {% endif %}

模板继承(大模板)

可以达到模板复用的效果,当写一个模板的时候可以定义 “blocks”,子模板可以覆盖他,同时支持多层继承。
可理解为:Vue中的插槽 / JS中的原型
使用 extends 和 block 标签

  • 原型模板上写 block 标签
  • 实例模板上写 extends 标签 ```javascript //—————————-原型模板上: {% block header %} This is the default content {% endblock %}
{% block left %}{% endblock %}
{% block right %} This is more content {% endblock %}

//———————————实例模板上: {% extends “parent.html” %} //extends 后接 原型模板的文件路径

{% block left %} This is the left side! {% endblock %}

{% block right %} This is the right side! {% endblock %}

//合成结果: This is the default content

This is the left side!

This is the right side!

  1. 可以将原型模板设为一个变量,这样就可以动态指定继承的模板。
  2. ```javascript
  3. {% extends parentTemplate %}

include(小模板)

  • 可以在多模板之间共享一些小模板,如果某个模板已使用了继承那么 include 将会非常有用
  • 某些页面会包含相同的组件,可以将其抽离为共有的模板,但模板一般用于设定原型,即设定一个大的框架,然后小的实例去继承它,但是在这里,组件是模板中很小的一部分,使用模板继承的话小题大做且容易产生耦合,故这里使用新的方式include

例如轮播图,这样的内容可以通过include引入到网页中

  1. {% include "item.html" %} //include 后接需要插入的小模板

可在循环中引入模板

  1. {% for item in items %}
  2. {% include "item.html" %}
  3. {% endfor %}

处理表单数据

首先思考两个问题:
如何通过表单向后台发送数据?
接收表单发来的请求后,如何将得到的表单数据作为部分响应内容 响应给浏览器(重点)

理解:koa是web服务器,相当中介,获取来自浏览器发出的请求,接收到数据后响应给浏览器

form标签的属性:

  • action 属性:指定表单提交数据的路径
  • method 属性:指定表单提交数据的请求方法

对表单空间进行设置:
input.name 属性:指定数据提交的数据字段,理解为标识
input.type = “submit” :指定提交按钮,点击后提交表单数据

获取参数方法

koa在接收请求时,获取表单get请求的参数,有两种方法:
query:返回的是格式化好的参数对象。
querystring:返回的是请求字符串。

  1. router.get('/newsContent',async (ctx) => {
  2. /**
  3. * 从ctx中读取get传值
  4. */
  5. console.log(ctx.query); // { id: '123', name: 'sport' } 推荐
  6. console.log(ctx.querystring); // id=123&name=sport

koa在接收请求时,获取表单post请求的参数,需要引入第三方模块如koa-parser:

  1. app.use(parser()) //中间件方法先通过use引入
  2. router.get('/newsContent',async (ctx) => {
  3. /**
  4. * 从ctx中读取post传值
  5. */
  6. console.log(ctx.request.body); //获取请求体中的post参数

表单GET请求

  1. //index.html
  2. ...
  3. <form action="/login" > //默认不写method的话为get请求
  4. <input type="text" name="username">
  5. <input type="password" name="password">
  6. <input type="submit" value="登录">
  7. </form>
  8. ...
  9. //server.js
  10. router.get("/login",async (ctx) => { //当用户访问/login时候,异步返回相应
  11. let username = ctx.query.username
  12. let password = ctx.query.password //获取get请求的参数对象中的属性
  13. await ctx.render("home",{
  14. username //将get请求得到的参数对象 渲染到home文件中
  15. password
  16. })
  17. })

表单POST请求

  1. //index.html
  2. ...
  3. <form action="/login" method="post"> //默认不写method的话为get请求
  4. <input type="text" name="username">
  5. <input type="password" name="password">
  6. <input type="submit" value="提交数据">
  7. </form>
  8. ...

若需要获取post请求体的数据,需要安装第三方模块koa-parser 来解析post请求
npm install --save koa-parser

  1. //server.js
  2. const parser = require("koa-parser")
  3. app.use(parser()) // 将parser方法添加在程序上以便后续调用
  4. // api/user.js
  5. const users require("../data/users.js")
  6. router.post("/login",async (ctx) => { //当用户提交数据给/login时候,异步返回响应
  7. const user = { //通过前端post来的数据 创建用户信息(之后需要保存至数据库,这里用本地js模拟)
  8. id:Math.random()
  9. username:ctx.request.body.username
  10. password:ctx.request.body.password //获取post请求的参数对象中的属性
  11. }
  12. users.push(user) //保存用户信息至 本地js数据库
  13. await ctx.render("home",{
  14. username, //将get请求得到的参数对象 渲染到home文件中
  15. password
  16. })
  17. })

创建用户信息后需要保存至数据库,这里使用js变量来模拟真实数据库:

  1. const users =[]
  2. module.export = users

cookies 与 session

以上学习node思路为:请求、响应、渲染页面三个步骤
目前:

  • 请求是基于表单发起的
  • 响应是基于node.js 的一个web开发框架koa
  • 渲染是基于koa的API即ctx.render进行的

理解为‘客户带会员卡去店铺消费的流程’

cookies

保存在浏览器端
示例:
服务端设置cookies:ctx.cookies.set(name, value, [options])

  1. //server.js
  2. router.get('/',async ctx => {
  3. ctx.cookies.set("username","npc") //当用户访问 / 页面时,服务端响应cookie
  4. })
  5. //options常用:
  6. //maxAge: 一个数字, 表示从 Date.now() 得到的毫秒数,用于设置cookies过期时间段
  7. ctx.cookies.set("count",count,{
  8. maxAge:2000 //设置cookie过期时间为2秒
  9. })

服务端获取cookies:ctx.cookies.get(name, [options])

  1. //server.js
  2. router.get('/',async ctx => {
  3. ctx.cookies.get("username") //当用户访问 / 页面时,服务端获取其携带的cookie
  4. })

浏览器调试根据查看network选项卡对应响应页面的cookie内容:
image.png
image.png

session

与cookies结合起来,保存在服务端
session其实就是基于cookies,也可以说cookies是客户端式的session
此处需要拿cookie保存的session id 让服务器查找对应的 session 信息
利用cookies在客户端存储数据是完全透明的,如果存储一些用户信息,会导致很严重的安全问题,所以为了记录用户的登录状态,需要使用cookie和session结合的方式
使用中间件koa-session
服务端设置session:ctx.session.count = 0
服务端获取session:let count = ctx.session.count
服务端清空session:ctx.session.user = ''

  1. //server.js
  2. const Session = require("koa-session")
  3. const session = new Session()
  4. session.keys = ['设置加密cookies的密钥']
  5. session.use(session({
  6. maxAge:2000,
  7. },session)) //session(config,session)
  8. router.get("/login",async ctx => {
  9. ...服务端设置或获取session
  10. })

egg 框架

“约定大于配置”
前置:了解后端MVC模式
M数据
V视图
C控制
image.png
Control控制器一般职责比较单一,它负责从Service逻辑业务层 获取 数据并将数据嵌入模板
Service 就是在复杂业务场景下 用于做业务逻辑封装的一个抽象层,
View视图 使用后端模板引擎进行渲染

Express框架

  1. const express = require('express')
  2. const app = express()
  3. app.get('/',(req,res) =>{
  4. //res.send('hello npc')//代替res.write和res.end
  5. res.sendFile('./views/index.html',{root:__dirname}) //直接响应单个文件
  6. //必须使用绝对路径或加上{root:__dirname}根路径
  7. })
  8. app.get('/about-other',(req,res) =>{
  9. res.redirect('about') //重定向
  10. })
  11. app.use((req,res) => { //404页面,使用use中间件
  12. res.status(404).sendFile('./views/404.html',{root:__dirname})
  13. })
  14. app.listen(3001)

EJS模板引擎

常用模板引擎:EJS、pug、handlebars
出现意义:
html静态资源是不可变更的内容,直接在后台写好html文件后放置对应静态文件夹中,客户端访问资源时,后台只能固定不可变地响应给客户端
即html文件不能够动态地、实时地响应用户不同交互操作的 数据,只是完整地返回单个html文件给客户端

模板引擎的好处:形式上写html,配合特定的语法和api,实现动态地响应不同数据

基本使用

  1. 安装对应包
  2. 使用模板引擎的文件名后缀为 .ejs,内容同样用html格式编写或复杂的数据可用js写,即html+js = ejs
  3. 在后台程序中声明使用模板引擎:app.set('viwe engine','ejs') 此处必须为views目录存放静态资源,目录变更则:app.set('views','xxx','ejs')
  4. 使用res.render渲染ejs模板引擎
    1. app.get('/',(req,res)=>{
    2. res.render('index',{foo: 'FOO'}) //第一个实参为对应模板文件名
    3. //第二个参数指定数据(可以是对象或者文件)传递给模板(动态加载数据)
    4. })

    模板语法

    在ejs文件中定义 js数据 并传值
    首先在编辑器中安装两个ejs插件,方便使用:
    image.png
    1. 声明变量:<% const name = 'npc' %> (少用)
    2. 使用变量: <%= name%> 一般配合后台程序在render回调中传入第二个实参声明变量(传值)

ejs是怎样渲染到浏览器中的?
浏览器只认识html、css、js文件,故仍是通过ejs模板引擎将ejs文件转化为html返回页面
image.png

if语句

  1. <% if (user) { %> //注意:需要在一行内有{
  2. <h2><%= user.name %></h2>
  3. <% } %>

模板继承

抽离公共小模块nav,可将其注入到各需要用到nav组件的页面中,只要在其需方页面写:
<%- include('./partials/nav.ejs')>

样式

只需要在ejs文件中添加style标签,在style标签内正常书写css样式

中间件

中间件一定是在 req 和 res 之间
中间件 一定是有一定作用的

中间件处理css样式