模块化开发
CommonJS导入导出语法
不同于ES6的import和export语法
const yyy = require("zzz") 引入一个zzz外部模块赋值给yyy变量
module.exports = xxx 暴露一个xxx模块接口
外部模块
分类:
核心模块:node 自带的模块,可以使用require 直接引用模块名
自定义模块:我们自己编写的,引入自定义模块需要引用完整路径
第三方模块:使用npm下载的模块;下载完成后可以使用require 直接引用模块名
node-dev的使用node-dev server.js 8888
可以预览页面效果并实时更新页面
常用API
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 是文件的内容。
fs.readFile('./views/index.html', (err, data) => {
if (err) {
throw err//或者 console.log(err);res.end() 必须res.end(),否则死循环
}
else{
res.write(data.toString()) //返回给客户端的内容
//data是一个二进制的Buffer类型,需要转化为字符串
res.end()
}
});
如果未指定字符编码,则返回原始的 buffer。
如果 options 是一个字符串,则它指定了字符编码。 例子:fs.readFile('/etc/passwd', 'utf8', callback);
//针对req.url做判断机制 + 声明一个path变量赋值给fs.readFile()第一个实参
let path = './views/'
switch(req.url){
case '/':
path += 'index.html';
res.statusCode = 200
break;
case: '/about':
path += 'about.html';
res.statusCode = 200
break;
case: '/about-other': //重定向
res.statusCode = 301;
res.setHeader('Location','/about') //设置header
res.end()
default:
path += '404.html';
res.statusCode = 404
break
}
fs.readFile(path, (err, data) => {
if (err) {
throw err//或者 console.log(err);res.end() 必须res.end(),否则死循环
}
else{
res.write(data) //data是一个二进制的Buffer类型,需要转化为字符串
res.end()
}
});
path
path模块提供了一些用于 处理文件或目录的工具函数const path = require('path');
如:path.join([…paths])
path.join('/foo', 'bar', 'baz/asdf', 'quux', '..');
// 返回: '/foo/bar/baz/asdf'
//也可以将路径赋值变量然后直接作为形参
http
http.createServer([requestListener])
返回一个新建的 http.Server 实例,即在本地创建一个服务器。server.listen(options[, callback])
options中可以传入需要开启的端口port以及服务器启动成功后的回调函数
const http = require('http')
const server = http.createServer((req,res) => { //创建本地服务器,回调可 接收请求和定义响应
console.log(req.url,req.method) //常用的req属性
res.setHeader('Content-Type','text/plain')//常用res之设置header,说明返回的数据类型
res.write("hello npc") //返回给客户端的内容,可能有多个res.write()
res.end('hello npc') //若res.write()参数的data只有一个,则可直接写在end方法中
})
server.listen(3000,()=> { //服务器监听端口,成功开启服务器并设定监听端口,则会触发函数
console.log('Server is running')
})
nodemon 第三方模块:使用nodemon启动服务器,修改node文件后自动更新页面
数据流&pipe管道
读写数据是怎样的流程?
使用数据之前确保数据已经加载完成
硬盘 → 内存 → 客户端
若传输数据庞大,客户端拿到数据之前需要等待内存加载完成数据
使用管道传输,数据不间断地、小批量地传输,占用内存时间长,但占内存空间小
const fs = require('fs')
//创建 读取流 的对象
const readStream = fs.createReadStream('./docs/data.txt',{encoding:'utf8'})
//创建 写数据流 的对象
const writeStream = fs.createWriteStream('./docs/data2.txt')
//先读再写,读取一小段一小段,再写入一小段一小段
readStream.on('data',chunk => {
// console.log('======读取一小段一小段=====')
// console.log(chunck.toString()) //toString()可被encoding属性替换 ↑
writeStream.write('====写入一小段=====')
writeStream.write(chunk)
}
) // .on添加data事件,可传回调
//优化版:直接管道传输
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
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服务器能够接受并操作前端发送过来的请求数据
- 常见项目结构:
```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);
<a name="nH8th"></a>
## ctx
每个请求都将创建一个 Context,并在中间件中作为接收器引用,或者 ctx 标识符,<br />封装了request 和 response 对象<br />如以下代码片段所示:
```javascript
app.use(async ctx => {
ctx; // 这是 Context
ctx.request; // 这是 koa Request
ctx.response; // 这是 koa Response
});
ctx.redirect
执行 [302] 重定向到 url.
ctx.redirect('/login');
ctx.redirect('http://google.com');
要更改 “302” 的默认状态,只需在该调用之前或之后给 status 赋值。要变更主体请在此调用之后:
ctx.status = 301;
ctx.redirect('/cart');
ctx.body = 'Redirecting to shopping cart';
示例:服务端处理请求
router.post('/login',async ctx => {
let username = ctx.request.body.username
let password = ctx.request.body.password
if(username === 'npc' && password === '123456') {
ctx.redirect('/content') //重定向做跳转
}else {
ctx.redirect('/')
}
})
示例:只有登录成功后才能访问:
请求访问content页面时,服务端检验:只有请求体携带登录过的用户信息后才能访问
router.get('/content',async ctx = {
if(ctx.session.user) {
await ctx.render('content.html')
}else{
ctx.redirect('/')
}
})
app.use(function)
将给定的中间件方法添加到此应用程序。app.use() 返回 this, 因此可以链式表达.
app.use(someMiddleware)
.use(someOtherMiddleware)
.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,跳转不同页面内容
const Koa = require('koa');
const Router = require('koa-router');
const app = new Koa();
const router = new Router();
router.get('/',async (ctx) => { //前端发送get请求,后端定义API接口
ctx.body = "hello koa"
})
router.get('/news',async (ctx) => {
ctx.type = 'html';
ctx.body = '<h2>新闻列表</h2>';
})
// 调用router.routes()来 组装匹配好的路由,返回一个合并好的中间件
app.use(router.routes());
/*
调用router.allowedMethods()获得一个中间件,当发送了不符合的请求时,
会返回 `405 Method Not Allowed` 或 `501 Not Implemented`
*/
app.use(router.allowedMethods({
// throw: true, // 抛出错误,代替设置响应头状态
// notImplemented: () => '不支持当前请求所需要的功能',
// methodNotAllowed: () => '不支持的请求方式'
}));
app.listen(3000,()=>{
console.log('应用已经启动,http://localhost:3000');
});
理解:
响应体中返回图片的话如何设置?
在网页中插入图片,需要在img标签中添加src属性写图片的地址,src中只能写静态服务器对应的目录,这样才能得到地址,即web应用的服务器,只有静态文件目录的文件才可以被html页面直接访问
即不能直接将图片等页面内容文件拷贝到server.js文件的同级,即不能向以上路由示例那样写
为什么?
比如,我们发布一个网站,所有的客户端的用户要访问这个网站的图片等内容,用户不可以直接通过浏览器访问到服务器的任何文件,否则文件不安全,需要在中间创建一个静态文件目录,而这个目录需要用Koa来设置,即需要用到koa-static静态资源中间件
打通前端和后端
前端项目请求后端接口并拿到对应数据:
使用vue.config.js代理,proxy会代理转发前端的请求,将请求转发至指定的后端服务器接口所在url
module.exports = {
devServer: {
proxy: 'http://localhost:4000' //写入后端服务器接口所在url
}
}
此外,在前端发axios请求的时候,url只需二级域名开始写,前面不再需要接上后端服务器接口url,否则依然报跨域错
asycn getData() {
const result = await axios.get("/user") //url只需二级开始
}
koa-static静态资源中间件
dirname 同 path.dirname() ,返回当前文件的目录的绝对路径
filename ,返回当前文件的绝对路径名
//示例:在 /Users/mjr/example.js 运行 node
console.log(__dirname); // 打印: /Users/mjr
console.log(path.dirname(__filename)); // 打印: /Users/mjr/example.js
console.log(__filename); // 打印: /Users/mjr/example.js
static函数设置静态目录:
cosnt Koa = require("koa")
const static = require("koa-static")
const app = new Koa()
app.use(static(__dirname + "/public")) //static函数,可以设置一个名为public的静态目录
app.use(async (ctx) => {
ctx.body = `
<h1>这是一个图片网页<h1>
<img src='/imgs/logo.jpg'>
` //public静态目录中可设置imgs、css、js文件夹
})
app.listen(3000,() => {console.log("server is running")})
补充:
静态文件服务器(静态网页)
定义:自动查找对应文件,即静态文件服务器(没有请求数据库)
举例:如 http-server
实操示例:改造nodejs-test文件
动态文件服务器(动态网页)
定义:请求了数据库,就是动态服务器
实操示例:使用json文件当做数据库
模板引擎nunjucks
使用模板引擎可以动态设置响应的html文件,如可以把后台数据库的数据绑定到模板中,然后发送给客户端,也可以针对用户个性化交互操作返回对应数据,而不是只能返回整个html文件或字符串
注意:
- 需要把配置模板引擎的代码移动到所有与路由相关的代码之前,模板引擎才能正常渲染,即将
app.use(router.routes())
移动到app.listen
之前即可 -
基本使用
安装:在koa框架下使用nunjucks需要两个第三方模块
npm install --save koa-views //负责配置koa的模板引擎用哪个模板引擎
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:”视频”}) })
<a name="BOnCr"></a>
### 模板语法
<a name="FTioV"></a>
#### 循环语句
```javascript
<ul>
{% for item in items %}
<li>{{ item.title }}</li>
{% else %}
<li>This would display if the 'item' collection were empty</li>
{% endfor %}
</ul>
if语句
{% if hungry %}
I am hungry
{% elif tired %}
I am tired
{% else %}
I am good!
{% endif %}
模板继承(大模板)
可以达到模板复用的效果,当写一个模板的时候可以定义 “blocks”,子模板可以覆盖他,同时支持多层继承。
可理解为:Vue中的插槽 / JS中的原型
使用 extends 和 block 标签
- 原型模板上写 block 标签
- 实例模板上写 extends 标签 ```javascript //—————————-原型模板上: {% block header %} This is the default 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
可以将原型模板设为一个变量,这样就可以动态指定继承的模板。
```javascript
{% extends parentTemplate %}
include(小模板)
- 可以在多模板之间共享一些小模板,如果某个模板已使用了继承那么 include 将会非常有用
- 某些页面会包含相同的组件,可以将其抽离为共有的模板,但模板一般用于设定原型,即设定一个大的框架,然后小的实例去继承它,但是在这里,组件是模板中很小的一部分,使用模板继承的话小题大做且容易产生耦合,故这里使用新的方式include
例如轮播图,这样的内容可以通过include引入到网页中
{% include "item.html" %} //include 后接需要插入的小模板
可在循环中引入模板
{% for item in items %}
{% include "item.html" %}
{% endfor %}
处理表单数据
首先思考两个问题:
如何通过表单向后台发送数据?
接收表单发来的请求后,如何将得到的表单数据作为部分响应内容 响应给浏览器(重点)
理解:koa是web服务器,相当中介,获取来自浏览器发出的请求,接收到数据后响应给浏览器
form标签的属性:
- action 属性:指定表单提交数据的路径
- method 属性:指定表单提交数据的请求方法
对表单空间进行设置:
input.name 属性:指定数据提交的数据字段,理解为标识
input.type = “submit” :指定提交按钮,点击后提交表单数据
获取参数方法
koa在接收请求时,获取表单get请求的参数,有两种方法:
query:返回的是格式化好的参数对象。
querystring:返回的是请求字符串。
router.get('/newsContent',async (ctx) => {
/**
* 从ctx中读取get传值
*/
console.log(ctx.query); // { id: '123', name: 'sport' } 推荐
console.log(ctx.querystring); // id=123&name=sport
koa在接收请求时,获取表单post请求的参数,需要引入第三方模块如koa-parser
:
app.use(parser()) //中间件方法先通过use引入
router.get('/newsContent',async (ctx) => {
/**
* 从ctx中读取post传值
*/
console.log(ctx.request.body); //获取请求体中的post参数
表单GET请求
//index.html
...
<form action="/login" > //默认不写method的话为get请求
<input type="text" name="username">
<input type="password" name="password">
<input type="submit" value="登录">
</form>
...
//server.js
router.get("/login",async (ctx) => { //当用户访问/login时候,异步返回相应
let username = ctx.query.username
let password = ctx.query.password //获取get请求的参数对象中的属性
await ctx.render("home",{
username //将get请求得到的参数对象 渲染到home文件中
password
})
})
表单POST请求
//index.html
...
<form action="/login" method="post"> //默认不写method的话为get请求
<input type="text" name="username">
<input type="password" name="password">
<input type="submit" value="提交数据">
</form>
...
若需要获取post请求体的数据,需要安装第三方模块koa-parser 来解析post请求npm install --save koa-parser
//server.js
const parser = require("koa-parser")
app.use(parser()) // 将parser方法添加在程序上以便后续调用
// api/user.js
const users require("../data/users.js")
router.post("/login",async (ctx) => { //当用户提交数据给/login时候,异步返回响应
const user = { //通过前端post来的数据 创建用户信息(之后需要保存至数据库,这里用本地js模拟)
id:Math.random()
username:ctx.request.body.username
password:ctx.request.body.password //获取post请求的参数对象中的属性
}
users.push(user) //保存用户信息至 本地js数据库
await ctx.render("home",{
username, //将get请求得到的参数对象 渲染到home文件中
password
})
})
创建用户信息后需要保存至数据库,这里使用js变量来模拟真实数据库:
const users =[]
module.export = users
cookies 与 session
以上学习node思路为:请求、响应、渲染页面三个步骤
目前:
- 请求是基于表单发起的
- 响应是基于node.js 的一个web开发框架koa
- 渲染是基于koa的API即ctx.render进行的
cookies
保存在浏览器端
示例:
服务端设置cookies:ctx.cookies.set(name, value, [options])
//server.js
router.get('/',async ctx => {
ctx.cookies.set("username","npc") //当用户访问 / 页面时,服务端响应cookie
})
//options常用:
//maxAge: 一个数字, 表示从 Date.now() 得到的毫秒数,用于设置cookies过期时间段
ctx.cookies.set("count",count,{
maxAge:2000 //设置cookie过期时间为2秒
})
服务端获取cookies:ctx.cookies.get(name, [options])
//server.js
router.get('/',async ctx => {
ctx.cookies.get("username") //当用户访问 / 页面时,服务端获取其携带的cookie
})
浏览器调试根据查看network选项卡对应响应页面的cookie内容:
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 = ''
//server.js
const Session = require("koa-session")
const session = new Session()
session.keys = ['设置加密cookies的密钥']
session.use(session({
maxAge:2000,
},session)) //session(config,session)
router.get("/login",async ctx => {
...服务端设置或获取session
})
egg 框架
“约定大于配置”
前置:了解后端MVC模式
M数据
V视图
C控制
Control控制器一般职责比较单一,它负责从Service逻辑业务层 获取 数据并将数据嵌入模板
Service 就是在复杂业务场景下 用于做业务逻辑封装的一个抽象层,
View视图 使用后端模板引擎进行渲染
Express框架
const express = require('express')
const app = express()
app.get('/',(req,res) =>{
//res.send('hello npc')//代替res.write和res.end
res.sendFile('./views/index.html',{root:__dirname}) //直接响应单个文件
//必须使用绝对路径或加上{root:__dirname}根路径
})
app.get('/about-other',(req,res) =>{
res.redirect('about') //重定向
})
app.use((req,res) => { //404页面,使用use中间件
res.status(404).sendFile('./views/404.html',{root:__dirname})
})
app.listen(3001)
EJS模板引擎
常用模板引擎:EJS、pug、handlebars
出现意义:
html静态资源是不可变更的内容,直接在后台写好html文件后放置对应静态文件夹中,客户端访问资源时,后台只能固定不可变地响应给客户端
即html文件不能够动态地、实时地响应用户不同交互操作的 数据,只是完整地返回单个html文件给客户端
模板引擎的好处:形式上写html,配合特定的语法和api,实现动态地响应不同数据
基本使用
- 安装对应包
- 使用模板引擎的文件名后缀为 .ejs,内容同样用html格式编写或复杂的数据可用js写,即html+js = ejs
- 在后台程序中声明使用模板引擎:
app.set('viwe engine','ejs') 此处必须为views目录存放静态资源,目录变更则:app.set('views','xxx','ejs')
- 使用res.render渲染ejs模板引擎
app.get('/',(req,res)=>{
res.render('index',{foo: 'FOO'}) //第一个实参为对应模板文件名
//第二个参数指定数据(可以是对象或者文件)传递给模板(动态加载数据)
})
模板语法
在ejs文件中定义 js数据 并传值
首先在编辑器中安装两个ejs插件,方便使用:声明变量:<% const name = 'npc' %> (少用)
使用变量: <%= name%> 一般配合后台程序在render回调中传入第二个实参声明变量(传值)
ejs是怎样渲染到浏览器中的?
浏览器只认识html、css、js文件,故仍是通过ejs模板引擎将ejs文件转化为html返回页面
if语句
<% if (user) { %> //注意:需要在一行内有{
<h2><%= user.name %></h2>
<% } %>
模板继承
抽离公共小模块nav,可将其注入到各需要用到nav组件的页面中,只要在其需方页面写:<%- include('./partials/nav.ejs')>
样式
只需要在ejs文件中添加style标签,在style标签内正常书写css样式
中间件
中间件一定是在 req 和 res 之间
中间件 一定是有一定作用的