最开始为基础部分,可以忽略
可以从模块系统部分开始查看
PS F:\Nodejs\02\code> npm init -y
PS F:\Nodejs\02\code> npm init -y
Wrote to F:\Nodejs\02\code\package.json:
{
"name": "code",
"version": "1.0.0",
"description": "",
"main": "01-http-简易Apache.js",
"dependencies": {
"art-template": "^4.13.2"
},
"devDependencies": {},
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1"
},
"keywords": [],
"author": "",
"license": "ISC"
}
PS F:\Nodejs\02\code> node .\06-在Apache目录中加入模板引擎.js
npmjs.com 网站 是用来搜索npm包的
npm是一个命令行工具,只要安装了node就已经安装了npm。
npm也有版本概念,可以通过npm --version
来查看npm的版本
升级npm(自己升级自己):
npm install --global npm
npm存储包文件的服务器在国外,有时候会被墙,速度很慢,所以需要解决这个问题。
https://developer.aliyun.com/mirror/NPM?from=tnpm淘宝的开发团队把npm在国内做了一个镜像(也就是一个备份)。
安装淘宝的cnpm:
npm install -g cnpm --registry=https://registry.npm.taobao.org;
#在任意目录执行都可以
#--global表示安装到全局,而非当前目录
#--global不能省略,否则不管用
npm install --global cnpm
安装包的时候把以前的npm
替换成cnpm
。
#走国外的npm服务器下载jQuery包,速度比较慢
npm install jQuery;
#使用cnpm就会通过淘宝的服务器来下载jQuery
cnpm install jQuery;
如果不想安装cnpm
又想使用淘宝的服务器来下载:
npm install jquery --registry=https://npm.taobao.org;
但是每次手动加参数就很麻烦,所以我们可以把这个选项加入到配置文件中:
# 这个好像停止服务了
npm config set registry https://npm.taobao.org;
# 我使用的这种方法
npm config set registry https://registry.npm.taobao.org
#查看npm配置信息
npm config list;
只要经过上面的配置命令,则以后所有的npm install
都会通过淘宝的服务器来下载
每一个项目都要有一个package.json
文件(包描述文件,就像产品的说明书一样)
这个文件可以通过npm init
自动初始化出来
D:\code\node中的模块系统>npm init
This utility will walk you through creating a package.json file.
It only covers the most common items, and tries to guess sensible defaults.
See `npm help json` for definitive documentation on these fields
and exactly what they do.
Use `npm install ` afterwards to install a package and
save it as a dependency in the package.json file.
Press ^C at any time to quit.
package name: (node中的模块系统)
Sorry, name can only contain URL-friendly characters.
package name: (node中的模块系统) cls
version: (1.0.0)
description: 这是一个测试项目
entry point: (main.js)
test command:
git repository:
keywords:
author: demo
license: (ISC)
About to write to D:\code\node中的模块系统\package.json:
{
"name": "cls",
"version": "1.0.0",
"description": "这是一个测试项目",
"main": "main.js",
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1"
},
"author": "xiaochen",
"license": "ISC"
}
Is this OK? (yes) yes
对于目前来讲,最有用的是dependencies
选项,可以用来帮助我们保存第三方包的依赖信息。
如果node_modules
删除了也不用担心,只需要在控制面板中npm install
就会自动把package.json
中的dependencies
中所有的依赖项全部都下载回来。
package.json
文件npm install 包名
的时候都加上--save
选项,目的是用来保存依赖信息npm 5以前是不会有package-lock.json
这个文件
npm5以后才加入这个文件
当你安装包的时候,npm都会生成或者更新package-lock.json
这个文件
--save
参数,它会自动保存依赖信息package-lock.json
文件package-lock.json
这个文件会包含node_modules
中所有包的信息(版本,下载地址。。。)
npm install
的时候速度就可以提升lock
称之为锁
lock
使用来锁版本的1.1.1
版本1.1.1
package-lock.json
的另外一个作用就是锁定版本号,防止自动升级使用Node编写应用程序主要就是在使用:
EcmaScript语言
核心模块
第三方模块
自己写的模块
模块没有全局作用域,只有模块作用域,也就是说a模块加载了b模块,但是b模块里面有a模块相同的变量名的变量,b模块不会去污染a模块,所以a模块不会有任何影响(外部访问不了内部,内部也访问不了外部)
加载require(): 它的作用就是用来加载模块的
在 Node 中,模块有三种:
有时候,我们加载文件模块的目的不是为了简简单单的执行里面的代码,更重要是为了使用里面的某个成员,但是模块之间又不互相污染,所以我们需要使用exports来导出要使用的方法或者成员
导出exports
如果我们在a模块中导入了b模块,但是因为b模块和a模块互相不污染,所以我们想要在a模块中是不能使用b模块的,但是我们可以在b模块中设置要导出的变量、方法,然后在a模块中接收导入b模块的返回值,这样我们就可以在a模块中使用b模块的方法或者变量
a模块:
const b = require('./04-2模块的导入和导出');
// 使用b模块导出的add方法
b.add(20, 30);
b模块:
var add = (num1, num2) => {
console.log(num1 + num2);
}
// 导出
exports.add = add;
JavaScript本身是不支持模块化的
模块作用域
使用require方法来加载模块
使用exports接口对象来导出模板中的成员
语法:
var 自定义变量名 = require('模块')
作用:
exports
导出接口对象Node中是模块作用域,默认文件中所有的成员只在当前模块有效
对于希望可以被其他模块访问到的成员,我们需要把这些公开的成员都挂载到exports
接口对象中就可以了
导出多个成员(必须在对象中):
exports.a = 123;
exports.b = function(){
console.log('bbb')
};
exports.c = {
foo:"bar"
};
exports.d = 'hello'
通过以上的方法,我们想要使用模块中(假设载入模块的返回值为demoExports
)的方法或者字符串就要这样写
console.log(demoExports.a) // 123
demoExports.b() // bbb
这种方法加载到的函数或者字符串都属于挂载(要通过xxx.变量名),而我们想要直接使用则需要使用module.exports
来导出,但是注意是否被覆盖
导出单个成员(拿到的就是函数,字符串):
module.exports = 'hello'
以下情况会覆盖:
module.exports = 'hello'
//后者会覆盖前者
module.exports = function add(x,y) {
return x+y;
}
通过以上方法导出可以直接通过加载的返回值调用
var demoExports = require('demo.js')
console.log(demoExports(1, 3)) // 4,注意,原本的'hello'被function add覆盖了
也可以通过以下方法来导出多个成员(这样也是导出一个对象,想要使用里面的方法或者字符串则也需要通过挂载的写法来使用):
module.exports = {
foo = 'hello',
add:function(){
return x+y
}
};
在每个模块里面,都默认有一个module对象,而在模块的最后,都会默认return module.exports
exports和module.exports
的一个引用:
console.log(exports === module.exports) //true
exports.foo = 'bar'
//等价于
module.exports.foo = 'bar'
当给exports重新赋值后,exports!= module.exports
最终return的是module.exports,无论exports中的成员是什么都没用。
真正去使用的时候:
导出单个成员:exports.xxx = xxx
导出多个成员:module.exports 或者 modeule.exports = {}
优先从缓存加载
判断模块标识符
var 名称 = require('npm install【下载包】 的包名')
自定义模块
// 如果非路径形式的标识
require('fs')
// 路径形式的标识:
// ./ 当前目录 不可省略
// ../ 上一级目录 不可省略
// /xxx也就是D:/xxx 首位/表示的是当前文件模块所属磁盘根目录
// 带有绝对路径几乎不用(D:/a/foo.js)
require('./a')
核心模块
// 核心模块本质也是文件,核心模块文件已经被编译到了二进制文件中了,我们只需要按照名字来加载就可以了
require('fs')
第三方模块
// 凡是第三方模块都必须通过npm下载(npm i node_modules),使用的时候就可以通过require('包名')来加载才可以使用
// 第三方包的名字不可能和核心模块的名字是一样的
// 既不是核心模块,也不是路径形式的模块
// 规则1:
// 先找到当前文所述目录的node_modules
// 然后找node_modules/art-template目录
// node_modules/art-template/package.json
// node_modules/art-template/package.json中的main属性
// main属性记录了art-template的入口模块
// 然后加载使用这个第三方包
// 实际上最终加载的还是文件
//规则2:
// 如果package.json不存在或者mian指定的入口模块不存在
// 则node会自动找该目录下的index.js
// 也就是说index.js是一个备选项,如果main没有指定,则加载index.js文件
//
// 如果条件都不满足则会进入上一级目录进行查找
// 注意:一个项目只有一个node_modules,放在项目根目录中,子目录可以直接调用根目录的文件
var template = require('art-template')
/
和文件操作路径中的/
文件操作路径:
// 咱们所使用的所有文件操作的API都是异步的
// 就像ajax请求一样
// 读取文件
// 文件操作中 ./ 相当于当前模块所处磁盘根目录
// ./index.txt 相对于当前目录
// /index.txt 相对于当前目录
// /index.txt 绝对路径,当前文件模块所处根目录
// d:express/index.txt 绝对路径
fs.readFile('./index.txt',function(err,data){
if(err){
return console.log('读取失败')
}
console.log(data.toString())
})
模块操作路径:
// 在模块加载中,相对路径中的./不能省略
// 这里省略了.也是磁盘根目录
require('./index')('hello')
Node为JS提供了很多服务器级别的API,这些API绝大多数都被包装到了一个具名的核心模块中。
例如:文件操作的fs核心模块,http服务构建的http模块,path路径操作模块,os操作系统信息模块…
只要是核心模块,我们就需要使用require()方法来加载核心模块,常用的就几个
nodejsAPI参考中文网址
npm install moduleName # 安装模块到项目目录下
npm install moduleName -g # -g 的意思是将模块安装到全局,具体安装到磁盘哪个位置,要看 npm config prefix 的位置。
npm install moduleName -save # -save 的意思是将模块安装到项目目录下,并在package文件的dependencies节点写入依赖。
npm install moduleName -save-dev # -save-dev 的意思是将模块安装到项目目录下,并在package.json文件的devDependencies节点写入依赖。
在浏览器中js没有文件操作的能力,但是在nodejs中可以操作文件的读写
// 1.先使用require()方法将fs核心模块引入
const fs = require('fs');
读取文件
使用fs.readFile()
成功
data 数据
error null
失败
data undefined没有数据
error 错误对象
// 2.读取文件使用fs.readFile(); 两个参数:路径、回调参数(接受两个对象 error,data,成功error就是null,data就是数据对象,失败error就是错误对象,data就是null)
fs.readFile("./txt/a.txt", (error, data) => {
console.log(data); //
// 因为我们省略了第二个参数,所以data返回的是二进制数据, 我们可以使用toString()方法转换为常见的字符
console.log(data.toString()); // this is a
})
写入文件
使用fs.writeFile()
成功:
文件写入成功
error 是 null
失败:
文件写入失败
error 就是错误对象
// 3.写文件使用fs.writeFile();三个参数:文件路径,文件内容,回调函数(接受error对象,成功error是null,失败就是错误对象)
fs.writeFile("txt/c.txt", "this is c", error => {
if(error){
console.log("写入失败");
} else {
console.log("写入成功");
}
});
模板引擎是第三方模块 官网
让开发者以更加友好的方式拼接字符串,使项目代码更加清晰、更加易于维护
npm install art-template
命令进行下载,他会将art-template
下载到你运行代码的文件夹下const template = require('art-template')
引入模板node_modules/art-template/lib/template-web.js
,并且不能让浏览器将模板文本当成js来解析,所以要设置type="text/template"
<script src="node_modules/art-template/lib/template-web.js">
script>
<script type="text/template">
模板内容
script>
npm install art-template
该命令在哪执行就会把包下载到哪里。默认会下载到 node_modules 目录中
node_modules 不要改,也不支持改。
{{ 与 }} 符号包裹起来的语句则为模板的逻辑表达式。并且模板引擎只关心内容,就算是在字符串里面的{{}}也会被解析
输出表达式
对内容编码输出:
{{content}}
不编码输出:
{{#content}}
编码可以防止数据中含有 HTML 字符串,避免引起 XSS 攻击。
条件表达式
{{if admin}}
<p>adminp>
{{else if code > 0}}
<p>masterp>
{{else}}
<p>error!p>
{{/if}}
遍历表达式
无论数组或者对象都可以用 each 进行遍历。
{{each list as value index}}
<li>{{index}} - {{value.user}}li>
{{/each}}
亦可以被简写:
{{each list}}
<li>{{$index}} - {{$value.user}}li>
{{/each}}
模板包含表达式
用于嵌入子模板。
{{include 'template_name'}}
子模板默认共享当前数据,亦可以指定数据:
{{include 'template_name' news_list}}
辅助方法
使用template.helper(name, callback)注册公用辅助方法:
template.helper('dateFormat', function (date, format) {
// ..
return value;
});
模板中使用的方式:
{{time | dateFormat:'yyyy-MM-dd hh:mm:ss'}}
支持传入参数与嵌套使用:
{{time | say:'cd' | ubb | link}}
里template('script标签的id', {数据对象})
调用模板引擎,并返回模板值 <!-- 模板引擎文件 -->
<script src="node_modules/art-template/lib/template-web.js"></script>
<!-- 确定模板 -->
<script type='text/template' id="tpl">
<p>大家好,我叫:{{ name }}</p>
<p>性别:{{ sex }}</p>
<p>我喜欢:{{each list}} {{$value}} {{/each}}</p>
</script>
<!-- 使用模板引擎 -->
<script>
let res = template('tpl', {
name : "张三",
sex : '男',
list : ['c', 't', 'r', 'l']
})
console.log(res)
document.getElementsByTagName('body')[0].innerHTML = res;
</script>
核心方法
// 立即渲染模板
template.render(`模板字符串`, 替换对象)
// 引入模板引擎
const template = require('art-template')
const fs = require('fs')
// 最简单用法
let str = template.render(`hello {{name}}`, {
name: `node.js`
})
console.log(str); // hello node.js
// 结合fs综合案例,先创建一个html文件,里面放的是模板
fs.readFile('./tpl.html', (error, data) => {
if (error) {
return console.log('文件读取失败')
}
// 默认读取到的 data 是二进制数据
// 而模板引擎的 render 方法需要接收的是字符串
// 所以我们在这里需要把 data 二进制数据转为 字符串 才可以给模板引擎使用
let tplstr = template.render(data.toString(), {
name: 'jack',
sex: 'nan',
like: [
'c',
't',
'r',
'l'
]
})
console.log(tplstr);
})
当我们制作网页时,多张网页的头部和尾部都是一样的,只有中间不一样,我们就可以使用模板继承来实现
在模板引擎中也可以使用include语法,在模板一中加载模板二
语法
{{include './header.art'}}
{{include './header.art' data}}
DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>模板demo1title>
head>
<body>
{{ title }}
{{ include './demo2.html'}}
body>
html>
<div>
<h2>demo2里面的内容h2>
div>
继承
这时,我们创建一个layout.html文件(layout:布局),include和block语法搭配使用
DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Documenttitle>
head>
<body>
{{ include './header.html' }}
{{block 'name'}}
<h1>layout默认内容h1>
{{/block}}
{{ include './footer.html' }}
body>
html>
不填坑
{{extend './layout.html'}}
填坑
{{extend './layout.html'}}
{{ block 'name'}}
<h1>index中的填坑内容h1>
{{ /block }}
扩展:如果想要加载什么资源的话,我们只需要在layout中使用link/script标签来加载就可以了,并且可以留多个坑,而且可以没有默认内容
在Node中可以通过http模块非常轻松的创建一个服务器,注意不能关闭命令窗口,不然就会关闭服务器,如果想不关闭命令窗口的情况下关闭服务器按ctrl+c,可以开启多个服务,但是要保证端口号不同
// 核心模块http的职责就是用来创建服务器
const http = require('http')
// 使用http.createServer()方法返回一个Web服务器
const server = http.createServer();
// 注册request事件
// request 请求事件处理函数,需要接收两个参数:
// Request 请求对象
// 请求对象可以用来获取客户端的一些请求信息,例如请求路径
// Response 响应对象
// 响应对象可以用来给客户端发送响应消息
server.on("request", (request, response) => {
console.log("收到服务器请求,请求路径是:" + request.url);// request.url是当前的请求路径
// 设置请求头,以防止中文乱码
response.setHeader("Content-Type", "text/plain; charset=utf-8");
// response响应,可以多次响应,但是最后一定要使用end结束响应,不然客户端会一直等待
// response.write("this is my response");
// response.end();
// 上面的方式比较麻烦,推荐使用更简单的方式,直接 end 的同时发送响应数据
response.end('this is my response')
})
// 给服务器绑定端口号,启动服务器
server.listen(3000, function () {
console.log("服务器启动成功,可以通过localhost:3000来进行访问");
})
通过判断请求路径来返回不同的请求内容
const fs = require('fs');
const http = require('http');
// 创建server
const server = http.createServer();
server.on("request", function (req, resp) {
const url = req.url;
// 如果文件路径是/plain则返回文本内容
if(url === '/plain'){
resp.setHeader("content-type", "text/plain; charset=utf-8");
fs.readFile('./txt/plain.txt', function (error, data) {
if(error){
console.log("文件读取失败");
} else {
resp.end(data)
}
})
// 如果文件路径是/html则返回html内容
} else if(url === '/html'){
resp.setHeader("content-type", "text/html; charset=utf-8");
fs.readFile('./txt/html.html', function (error, data) {
if(error){
console.log("文件读取失败");
} else {
resp.end(data);
}
})
// 除了/plain和/html其他都返回404
} else {
resp.end("404");
}
})
// 启动服务器
server.listen(3000, function () {
console.log("服务器启动成功");
})
以前使用过 Apache 服务器软件,这个软件默认有一个 www 目录,所有存放在 www 目录中的资源都可以通过网址来浏览,默认访问index.html
简易逻辑仿写
const http = require('http')
const fs = require('fs')
// 创建server
const server = http.createServer()
// 监听server的request请求,并设置处理函数
// 创建服务器的根目录
const wwwDir = 'F:/Nodejs/02/videos'
server.on('request', (req, resp) => {
const url = req.url
// 当访问路径为'/'或者'/index.html',则都进入目录下index.html
if (url === '/' || url === '/index.html') {
fs.readFile(wwwDir + '/index.html', (error, data) => {
if (error) {
resp.end('404')
} else {
resp.end(data)
}
})
// 假设服务器只有index.html
} else {
resp.end('404')
}
})
//绑定端口号,启动服务器
server.listen('3000', () => {
console.log('服务器启动')
})
简易功能仿写
const http = require('http')
const fs = require('fs')
const server = http.createServer()
// 服务器根目录
const wwwDir = 'F:/Nodejs/02/videos'
server.on('request', (req, res) => {
const url = req.url
let filePath = '/index.html'
// 判断是否为/
if (url !== '/') {
// 如果是/则直接访问/index.html,不是则访问指定路径
filePath = url
}
fs.readFile(wwwDir + filePath, function (err, data) {
if (err) {
return res.end('404 Not Found.')
}
res.end(data)
})
})
// 3. 绑定端口号,启动服务
server.listen(3000, function () {
console.log('running...')
})
Apache目录列表
以下只能将所有文件识别成文件夹,就算是index.html
也会识别成index.html/
文件夹
// 简易实现Apache功能
const http = require('http')
const fs = require('fs')
const server = http.createServer()
// 服务器根目录
const wwwDir = 'F:/Nodejs/02/www'
server.on('request', (req, resp) => {
const url = req.url
// 首先读取查看目录的html
fs.readFile('./template.html', (error, data) => {
if (error) {
return resp.end('404 Not Found.')
}
// 使用fs.readdir读取目录
fs.readdir(wwwDir, (error, files) => {
// 如果报错则则直接 Can not find www dir
if (error) {
return resp.end('Can not find www dir.')
}
// 生成需要替换的内容
let content = ``
// 使用foreach和模板字符串生成需要拼接的内容
files.forEach(item => {
content += `
${item}/
2017/11/2 上午10:32:47
`
});
// 替换,先将二进制toString一下,^_^是替换标记
data = data.toString();
data = data.replace('^_^', content)
// 响应数据
resp.end(data)
})
})
})
// 3. 绑定端口号,启动服务
server.listen(3000, function () {
console.log('running...')
})
使用模板引擎替换字符串拼接
// 简易实现Apache功能
const http = require('http')
const fs = require('fs')
const template = require('art-template')
const server = http.createServer()
// 服务器根目录
const wwwDir = 'F:/Nodejs/02/www'
server.on('request', (req, resp) => {
const url = req.url
// 首先读取查看目录的html
fs.readFile('./template-tpl.html', (error, data) => {
if (error) {
return resp.end('404 Not Found.')
}
// 使用fs.readdir读取目录
fs.readdir(wwwDir, (error, files) => {
// 如果报错则则直接 Can not find www dir
if (error) {
return resp.end('Can not find www dir.')
}
// 使用模板引擎
data = template.render(data.toString(), {
title : wwwDir,
files : files
})
// 响应数据
resp.setHeader("content-type", "text/html; charset=utf-8")
resp.end(data)
})
})
})
// 3. 绑定端口号,启动服务
server.listen(3000, function () {
console.log('running...')
})
// http简写方式
http.createServer(function(req, resp){ // 默认会注册request请求
resp.end("fangwen")
}).listen(3000, function () {
console.log('running...');
})
浏览器收到 HTML 响应内容之后,就要开始从上到下依次解析,
当在解析的过程中,如果发现:
link
script
img
iframe
video
audio
等带有 src 或者 href(link) 属性标签(具有外链的资源)的时候,浏览器会自动对这些资源发起新的请求。
注意:在服务端中,文件中的路径就不要去写相对路径了。
因为这个时候所有的资源都是通过 url 标识来获取的
我的服务器开放了 /public/ 目录
所以这里的请求路径都写成:/public/xxx
/ 在这里就是 url 根路径的意思。
浏览器在真正发请求的时候会最终把 http://127.0.0.1:3000 拼上
不要再想文件路径了,把所有的路径都想象成 url 地址
现在假设index.html在views文件里,但是访问的资源在public文件夹里,不在服务器中则可以使用…/public/index.css来进行访问,但是在服务器中需要使用/public/index.css
来访问,浏览器在真正发请求的时候会最终把 http://127.0.0.1:3000
拼上
// 如果客户端发现收到服务器的响应的状态码是 302 就会自动去响应头中找 Location ,然后对该地址发起新的请求
// 所以你就能看到客户端自动跳转了
res.statusCode = 302
// 第二个参数是设置重定向去哪,/默认代表重定向到本服务器的/,如果想要重定向去百度则将'/'改为'https://www.baidu.com/'
res.setHeader('Location', '/')
res.end()
创建方法
new URL(input[, base])
通过相对于 base 解析 input 来创建新的 URL 对象。 如果 base 作为字符串传入,则其将被解析为等效于 new URL(base)。
const myURL = new URL('/foo', 'https://example.org/');
// https://example.org/foo
URL 构造函数可作为全局对象的属性访问。 也可以从内置的 url 模块中导入:
console.log(URL === require('url').URL); // 打印 'true'.
如果 input 或 base 不是有效的 URL,则将抛出 TypeError。 注意,会将给定的值强制转换为字符串。 例如:
const myURL = new URL({ toString: () => 'https://example.org/' });
// https://example.org/
出现在 input 的主机名中的 Unicode 字符将使用 Punycode 算法自动转换为 ASCII。
const myURL = new URL('https://測試');
// https://xn--g6w251d/
只有在启用 ICU 的情况下编译 node 可执行文件时,此功能才可用。 如果不是,则域名将原封不动地传入,如果事先不知道 input 是否是绝对 URL 并且提供了 base,则建议验证 URL 对象的 origin 是否符合预期。
let myURL = new URL('http://Example.com/', 'https://example.org/');
// http://example.com/
myURL = new URL('https://Example.com/', 'https://example.org/');
// https://example.com/
myURL = new URL('foo://Example.com/', 'https://example.org/');
// foo://Example.com/
myURL = new URL('http:Example.com/', 'https://example.org/');
// http://example.com/
myURL = new URL('https:Example.com/', 'https://example.org/');
// https://example.org/Example.com/
myURL = new URL('foo:Example.com/', 'https://example.org/');
// foo:Example.com/
实际情况使用
// new一个url实例,第一个参数是请求的url,第二个参数是服务器信息端口号
let urldata = new URL(req.url, 'http://localhost:3000/')
// 获取请求路径,就算后面带有参数也只获取请求路径
let pathname = urldata.pathname
let urldata = new URL('/foo?name=张三&age=18', 'http://localhost:3000/');
// 获取发送请求时url栏夹带的参数对象
console.log(urldata.searchParams);//URLSearchParams { 'name' => '张三', 'age' => '18' }
// 使用get()获取夹带的name值
console.log(urldata.searchParams.get('name')) // 张三
参考文档:https://nodejs.org/docs/latest-v13.x/api/path.html
在每个模块中,除了require
,exports
等模块相关的API之外,还有两个特殊的成员:
__dirname
,是一个成员,可以用来动态获取当前文件模块所属目录的绝对路径
__filename
,可以用来动态获取当前文件的绝对路径(包含文件名)
__dirname
和__filename
是不受执行node命令所属路径影响的
在文件操作中,使用相对路径是不可靠的,因为node中文件操作的路径被设计为相对于执行node命令所处的路径。
所以为了解决这个问题,只需要把相对路径变为绝对路径(绝对路径不受任何影响)就可以了。
就可以使用__dirname
或者__filename
来帮助我们解决这个问题
在拼接路径的过程中,为了避免手动拼接带来的一些低级错误,推荐使用path.join()
来辅助拼接
假设有一个文件夹(demo)里有a.txt和main.js,main.js中书写了文件读取a.txt文件
var fs = require('fs');
fs.readFile('./a.txt','utf8',function(err,data){
if(err){
throw err
}
console.log(data);
});
此时我们再控制台中运行main.js
PS F:\\Nodejs\\06\\demo> node main.js
# 此时我们可以读取到a.txt里面的内容
但是我们使用以下方法就不可以读取到文件内容
PS F:\\Nodejs\\06\\> node demo/main.js
这就是node中文件操作的路径是被设计为相对于执行node命令所处的位置,这时候'./a.txt'
就不是06/demo/a.txt
了,而是06/a.txt
所以我们可以使用Node中的__dirname和path模块中的join方法搭配使用来避免这种错误
console.log(__dirname); // 不包含文件名的绝对路径 F:\Nodejs\06
console.log(__filename); // 包含文件名的绝对路径 F:\Nodejs\06\01-__dirname和__filename.js
var fs = require('fs');
var path = require('path');
// console.log(__dirname + 'a.txt');
// path.join方法会将文件操作中的相对路径都统一的转为动态的绝对路径,
fs.readFile(path.join(__dirname + '/a.txt'),'utf8',function(err,data){
if(err){
throw err
}
console.log(data);
});
补充:模块中的路径标识和这里的路径没关系,不受影响(就是相对于文件模块)
注意:
模块中的路径标识和文件操作中的相对路径标识不一致
模块中的路径标识就是相对于当前文件模块,不受node命令所处路径影响
原生的http模块在某些方面表现不足以应对我们的开发需求,所以就需要使用框架来加快我们的开发效率,框架的目的就是提高效率,让我们的代码高度统一。Express首页,主要封装http核心模块
安装
# 通过npm来安装express,保存到依赖文件中
npm i -S express
基础操作HelloWorld
// 1.导包
const express = require('express')
// 2.创建服务器
// 也就是原来的http.createServer()
const app = express()
// 公开指定目录,此时当访问/public/时,当前文件夹下的public文件夹就会被公开
app.use('/public/', express.static('./public/'))
// 3.设置请求
// 当服务器收到 get 请求的 / 的时候,执行回调处理函数
app.get('/' , (req , res)=>{
res.send('HelloWorld')
})
// 当服务器收到 get 请求的 /about 的时候,执行回调处理函数
app.get('/about' , (req , res)=>{
// 可以直接通过req.query获得参数对象
console.log(req.query)
res.send('关于我') // express已经处理好了字符编码的问题,但是要使用res.send(),使用原先的end()则还是需要自己设置
})
app.get('/cdx', (req, res) => {
// 使用express中的重定向至localhost:3000/about
res.redirect('/about')
})
// 4.注册端口号
// 相当于server.listen()
app.listen(3000, () => {
console.log('app running...');
})
get:
//当你以get方法请求/的时候,执行对应的处理函数
app.get('/',function(req,res){
res.send('hello world');
})
post:
//当你以post方法请求/的时候,执行对应的处理函数
app.post('/',function(req,res){
res.send('hello world');
})
send()和end()的区别
// 1, 参数类型的区别:
res.send() // 参数可以是 buffer, string, object , array
res.end() // 参数类型只能buffer对象或者字符串
// 2, res.send() 响应报文头不同
res.end() //只有Content-Type: text/html; charset=utf-8
res.send() //可以自动加响应头
// app.use不仅仅是用来处理静态资源的,还可以做很多工作(body-parser的配置)
app.use(express.static('public'));
app.use(express.static('files'));
app.use('/stataic',express.static('public'));
// 引入express
var express = require('express');
// 创建app
var app = express();
// 开放静态资源
// 1.当以/public/开头的时候,去./public/目录中找对应资源
// 访问:http://127.0.0.1:3000/public/login.html
app.use('/public/',express.static('./public/'));
// 2.当省略第一个参数的时候,可以通过省略/public的方式来访问
// 访问:http://127.0.0.1:3000/login.html
// app.use(express.static('./public/'));
// 3.访问:http://127.0.0.1:3000/a/login.html
// a相当于public的别名
// app.use('/a/',express.static('./public/'));
//
app.get('/',function(req,res){
res.end('hello world');
});
app.listen(3000,function(){
console.log('express app is runing...');
});
art-template官网中介绍Express的用法
安装
先要安装好express
,然后再安装以下两个包
npm install --save art-template
npm install --save express-art-template
# 也可以简写成
npm i art-template express-art-template
配置
注意,如果第一个参数是'art'
,这时候模板的后缀一定得是.art
,不能使用.html
;要是不想使用以art结尾的文件的话,就需要修改engine()的第一个参数了
// 配置使用art-template模板引擎
// 第一个参数,表示,当渲染以 .art 结尾的文件的时候,使用 art-template 模板引擎
// express-art-template 是专门用来在 Express 中把 art-template 整合到 Express 中
// 虽然外面这里不需要引入 art-template 但是也必须安装,原因就在于 express-art-template 依赖了 art-template
app.engine('art', require('express-art-template'))
// 如果想要修改默认的 views 目录,则可以使用以下方法,使用此方法需要导入path核心模板
// 参数1:代表将要修改views,第二个参数表示将原本指:项目/views/资源,换成其他的文件夹
// app.set('views', path.join(__dirname, render函数的默认路径))
app.set('views', path.join(__dirname, 'mobanwenjian'))// 将原本从项目目录下的views目录找资源换成了从项目目录下的mobanwenjian找资源
使用
注意:render方法的第一个参数是不能写路径的,默认会去项目中的views目录查找以.art结尾的模板文件,也就是说使用Express有一个约定就是开发人员要将所有的视图文件都放在views目录中
// 使用
app.get('/', (req, res) => {
// Express 为 Response 相应对象提供了一个方法:render
// render 方法默认是不可以使用,但是如果配置了模板引擎就可以使用了
// res.render('html模板名', {模板数据})
// 第一个参数不能写路径,默认会去项目中的 views 目录查找该模板文件
// 也就是说 Express 有一个约定:开发人员把所有的视图文件都放到 views 目录中
res.render('./moban.art', {// 由于一开始就是从views文件夹下查找,所以./moban.art就代表是views/moban.art文件
name: 'demoName',
sex: 'nan',
age: '10',
likes: ['HTML', 'css', 'javascript']
})
})
get
get的请求体数据可以直接通过req.query获取
app.get('/' , (req , res)=>{
// get方法可以直接使用req.query获取请求数据对象
console.log(req.query)
})
post
在Express中没有内置获取表单post请求体的api,这里我们需要使用一个第三方包body-parser
来获取数据。
安装:
npm install --save body-parser
配置:
// 配置解析表单 POST 请求体插件(注意:一定要在 app.use(router) 之前 )
var express = require('express')
// 引包
var bodyParser = require('body-parser')
var app = express()
// 配置body-parser
// 只要加入这个配置,则在req请求对象上会多出来一个属性:body
// 也就是说可以直接通过req.body来获取表单post请求数据
app.use(bodyParser.urlencoded({ extended: false }))
app.use(bodyParser.json())
使用:
app.use(function (req, res) {
res.setHeader('Content-Type', 'text/plain')
res.write('you posted:\n')
// 可以通过req.body来获取表单请求数据
res.end(JSON.stringify(req.body, null, 2))
})
案例
const express = require('express')
const bodyParser = require('body-parser')
const app = express()
// 配置body-parser
// 只要加入这个配置,则在req请求对象上会多出来一个属性:body
// 也就是说可以直接通过req.body来获取表单post请求数据
app.use(bodyParser.urlencoded({ extended: false }))
app.use(bodyParser.json())
app.use('/public/', express.static('./public/'))
// 获取post请求体数据
app.post('/demo' , (req , res)=>{
// req.body获取对象,req.body.属性名,获取具体属性值
console.log(req.body)
res.send()
})
// 获取get请求体数据
app.get('/demo' , (req , res)=>{
// get方法可以直接使用req.query获取请求数据对象
console.log(req.query)
res.send()
})
app.get('/' , (req , res)=>{
res.redirect('/public/qingqiu.html')
})
app.listen(3000, () => console.log('服务器运行'))
我们通过一个案例来学习如何设计路由
在这里我们通过JSON来模拟查询数据库文件
读取数据
const express = require('express')
const fs = require('fs')
const app = express()
app.engine('html', require('express-art-template'))
app.use('/public/', express.static('./public/'))
app.use('/node_modules/', express.static('../node_modules/'))
app.get('/', (req, res) => {
fs.readFile('./db.json', (err, data) => {
if (err) {
res.send('没有数据')
}
// 使用parse将String转换成Object
let jsondata = JSON.parse(data.toString())
let students = jsondata.students
res.render('./index.html', {
shuiguo: ['苹果', '香蕉'],
students: students
})
})
})
app.listen(3000, () => console.log('服务器启动'))
路由设计
请求方法 | 请求路径 | get 参数 | post 参数 | 备注 |
---|---|---|---|---|
GET | /students | 渲染首页 | ||
GET | /students/new | 渲染添加学生页面 | ||
POST | /students/new | name、age、gender、hobbies | 处理添加学生请求 | |
GET | /students/edit | id | 渲染编辑页面 | |
POST | /students/edit | id、name、age、gender、hobbies | 处理编辑请求 | |
GET | /students/delete | id | 处理删除请求 |
// 在不使用router模块的情况下,用以下方法写路由
module.exports = (app) => {
// /students 渲染首页
app.get('/students', (req, res) => {
})
}
当我们将路由设计写成一个核心模块的时候,我们想要将router模块加载到入口模块的时候就比较麻烦,所以express给我们提供了第三方模块router
// router.js写法
// 0. 加载核心模块express
var express = require('express')
// 1. 创建一个路由容器
var router = express.Router()
// 2.将所有的路由都挂载到router下
// /students 渲染首页
router.get('/students', (req, res) => {
})
// 3.把router导出
module.exports = router
// app.js写法
const router = require('./router')
// 将router挂载到app服务器上
app.use(router)
由于每次添加学生或者修改学生会经常使用到readFile和writeFile方法,所以我们可以将这些方法封装成一个核心模块students.js
// student.js 对db.json进行增删改查
const fs = require('fs')
// 获取学生列表
exports.find = () => {
}
// 增加学生
exports.add = () => {
}
// 更新学生列表
exports.updata = () => {
}
// 删除学生
exports.delete = () => {
}
由于在函数外部是无法获取里面的值的,所以我们需要使用回调函数来进行处理
注意:
const express = require('express')
const bodyParser = require('body-parser')
const router = require('./router')
const app = express()
// 配置模板引擎
app.engine('html', require('express-art-template'))
// 配置bodyParser
app.use(bodyParser.urlencoded({ extended: false }))
app.use(bodyParser.json())
// 公开资源
app.use('/public/', express.static('./public/'))
app.use('/node_modules/', express.static('../node_modules/'))
// 将router挂载到app服务器上
app.use(router)
app.listen(3000, () => console.log('服务器启动'))
路由代码
// 路由处理模块
// Express 提供了一种更好的方式
// 专门用来包装路由的
const express = require('express')
const students = require('./students.js')
// 1. 创建一个路由容器
const router = express.Router()
// 2.将所有的路由都挂载到router下
router.get('/', (req, res) => {
res.redirect('/students')
})
// /students 渲染首页
router.get('/students', (req, res) => {
students.find((err, data) => {
res.render('./index.html', {
students: data
})
})
})
// /students/new 渲染添加学生页面
router.get('/students/new', (req, res) => {
res.render('./new.html')
})
// /students/new 处理添加学生请求
router.post('/students/new', (req, res) => {
// 获取post数据
let student = req.body
// 处理数据:将数据追加到db.json中,先读取文件,将文件转换成对象,往对象中push数据,在将对象写入文件
students.add(student, (err) => {
if (err) {
return res.status(500).send('Server error')
}
res.redirect('/students')
})
})
// /students/edit 处理编辑请求
router.post('/students/edit', (req, res) => {
students.updata(req.body, (err) => {
if (err) {
return res.status(500).send('Server error')
}
res.redirect('/students')
})
})
// /students/edit 渲染编辑页面
router.get('/students/edit', (req, res) => {
students.findById(parseInt(req.query.id), (err, data) => {
if (err) {
return res.status(500).send('Server error')
}
res.render('./edit.html', {
student : data
})
})
})
// /students/delete 处理删除请求
router.get('/students/delete', (req, res) => {
students.deleteById(parseInt(req.query.id), (err) => {
if (err) {
return res.status(500).send('Server error')
}
res.redirect('/students')
})
})
// 3.把router导出
module.exports = router
操作文件代码
// 对db.json进行增删改查
const fs = require('fs')
const dbPath = './db.json'
/**
* 获取学生列表
* @param {Function}} callback(err, data) 等到调用的时候我们就需要往里传递一个函数,这个传进去的函数里面再定义处理数据的方法,并且由于是异步请求,如果想在函数体外获得函数体内部的数据,就需要使用回调函数,如果直接将数据return出来,便会是undefined
*/
exports.find = (callback) => {
fs.readFile(dbPath, 'utf-8', (err, data) => {
if (err) {
return callback(err)
}
callback(null, JSON.parse(data).students)
})
}
/**
* 通过id查找对应的学生
* @param {Number} id 学生id
* @param {Function} callback(err, data) 回调函数
*/
exports.findById = (id, callback) => {
fs.readFile(dbPath, 'utf-8', (err, data) => {
if (err) {
return callback(err)
}
const students = JSON.parse(data).students
const stu = students.find((item) => {
return parseInt(item.id) === id
})
callback(null, stu)
})
}
/**
* 保存增加学生
* @param {Object} student
* @param {Function} callback(err) 回调函数
*/
exports.add = (student, callback) => {
fs.readFile(dbPath, 'utf-8', (err, data) => {
if (err) {
return callback(err)
}
// 将JSON字符串转换成对象
let students = JSON.parse(data).students
// 处理id
student.id = parseInt(students[students.length - 1].id) + 1
students.push(student)
// 将数据转换成json格式
const fileData = JSON.stringify({
students: students
})
// 写入数据
fs.writeFile(dbPath, fileData, (err) => {
if (err) {
return callback(err)
}
callback(null)
})
})
}
/**
* 更新学生列表
* @param {Object} student 需要修改的学生对象
* @param {Function} callback 回调函数
*/
exports.updata = (student, callback) => {
fs.readFile(dbPath, 'utf-8', (err, data) => {
if (err) {
return callback(err)
}
let students = JSON.parse(data).students
// 使用ES6里面的find方法
let stu = students.find((item) => {
return parseInt(item.id) === parseInt(student.id)
})
// 更新数据
for (const key in stu) {
stu[key] = student[key]
}
// 将数据转换成json格式
const fileData = JSON.stringify({
students: students
})
// 写入数据
fs.writeFile(dbPath, fileData, (err) => {
if (err) {
return callback(err)
}
callback(null)
})
})
}
/**
* 删除学生
* @param {Number} id 学生id
* @param {Function} callback(err) 回调函数
*/
exports.deleteById = (id, callback) => {
fs.readFile(dbPath, 'utf-8', (err, data) => {
if (err) {
return callback(err)
}
const students = JSON.parse(data).students
let index = students.findIndex((item) => {
return parseInt(item.id) === parseInt(id)
})
// 使用splice删除
students.splice(index, 1)
// 将数据转换成json格式
const fileData = JSON.stringify({
students: students
})
// 写入数据
fs.writeFile(dbPath, fileData, (err) => {
if (err) {
return callback(err)
}
callback(null)
})
})
}
在浏览器默认情况下,表单的提交是默认行为(设置method、action),浏览器会锁死(转圈),等待服务器端响应,但是通过ajax可以使表单的的提交行为为异步请求。同步表单提交的请求浏览器会将服务器返回的内容直接渲染到页面上
普通的get/post请求为同步请求,刷新整个页面,而Ajax是异步请求,可以局部刷新页面,在此处随便书写两个示范
DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>异步请求title>
head>
<body>
<form id="demoform">
<input type="text" name="name" id="name">
<input type="text" name="password" id="password">
<input type="submit" value="提交">
form>
<script src="/node_modules/jquery/dist/jquery.js">script>
<script>
$("#demoform").on('submit', (e) => {
// 禁止浏览器默认的表单同步提交方式
e.preventDefault()
// 提取表单数据
const name = $('#name').val()
const password = $('#password').val()
// 像服务器发送url为/ajaxdemo的post请求,并且传递数据data
$.ajax({
type: "post",
url: "/ajaxdemo",
// 将表单数据封装为一个对象,传递给服务器
data: {
name: name,
passowrd: password
},
// 接受json数据
dataType: "JSON",
success: (response) => {
// 再此处只是随意做了一下返回值的处理,不符合逻辑
if (response.success === true) {
alert(response.message)
}
if (response.success === false) {
alert(response.message)
}
// 异步请求中服务端的重定向无效,需要使用客户端重定向
// window.location.href = '/'
}
})
})
script>
body>
html>
const express = require('express')
const path = require('path')
const bodyParser = require('body-parser')
const app = new express()
// 开放资源
app.use('/node_modules/', express.static(path.join(__dirname, '../node_modules/')))
// 配置art-template
app.engine('html', require('express-art-template'))
app.set('views', path.join(__dirname, './views/'))
// 设置post
// 配置解析表单 POST 请求体插件(注意:一定要在 app.use(router) 之前 )
// parse application/x-www-form-urlencoded
app.use(bodyParser.urlencoded({
extended: false
}))
// parse application/json
app.use(bodyParser.json())
app.get('/', (req, res) => {
res.render('demo.html')
})
app.post('/ajaxdemo', (req, res) => {
if (req.body.name === 'admin') {
// 响应请求,先将状态码设置200,因为设置接受的是json数据,所以我们要将数据改为json
return res.status(200).send(JSON.stringify({
success: true,
message: '您输入的name为admin'
}))
}
if (req.body.password === '123456') {
// res.status(200).send(JSON.stringify({对象}))还是比较麻烦的,所以express提供了json的方法
return res.status(200).json({
success: true,
message: '您输入的password为123456'
})
}
res.status(200).json({
success: false,
message: '没有符合要求数据'
})
})
app.listen(3000, console.log('服务器运行中...'))
在Express中默认是没有Session这个功能的,需要使用第三方插件来完成
注意:Session的配置文件要放在路由之前,我们可以使用template来搭配session从而进行页面的内容修改
默认Session数据时内存储数据,服务器一旦重启,真正的生产环境会把Session进行持久化存储。
下载
npm i express-session
配置
const session = require('express-session')
const express = require('express')
const app = new express()
// 配置session:
app.use(session({
secret: 'keyboard cat',
resave: false,
saveUninitialized: true
}))
之前学习JSP中,服务器会默认在浏览器中添加SESSIONID,在nodejs中也是这样
可以配置的值:
操作值
// 设置值
req.session.username = "张三";
// 获取值
console.log(req.session.username)
//删除值
req.session.username = null;
delete req.session.username
注销
app.get("/out",function(req,res){
//注销session
req.session.destroy()
res.redirect("/") //重定向定位到指定内容
})
将session存入mysql
网址
const express = require('express')
const path = require('path')
const bodyParser = require('body-parser')
const session = require('express-session')
const app = new express()
app.use('/node_modules/', express.static(path.join(__dirname, '../node_modules/')))
app.engine('html', require('express-art-template'))
app.set('views', path.join(__dirname, './views/')) // 默认就是 ./views 目录
// parse application/x-www-form-urlencoded
app.use(bodyParser.urlencoded({ extended: false }))
// parse application/json
app.use(bodyParser.json())
app.use(session({
// 配置加密字符串,它会在原有加密基础之上和这个字符串拼起来去加密
// 目的是为了增加安全性,防止客户端恶意伪造
secret: 'SessionDemo',
resave: false,
saveUninitialized: false // 无论你是否使用 Session ,我都默认直接给你分配一把钥匙
}))
app.get('/', (req, res) => {
// 如果没有则自动跳转登录页面
if (!req.session.username) {
return res.redirect('/login')
}
// 有则跳转至主页,并且显示用户名
res.render('./index.html', {
username: req.session.username
})
})
app.get('/login', (req, res) => {
res.render('./login.html')
})
app.post('/login', (req, res) => {
// 直接将对象创建到session中
req.session.username = req.body.username
// 跳转至主页
res.redirect('/')
})
app.listen(3000, console.log('running...'))
参考文档:http://expressjs.com/en/guide/using-middleware.html
中间件:把很复杂的事情分割成单个,然后依次有条理的执行。就是一个中间处理环节,有输入,有输出。
说的通俗易懂点儿,中间件就是一个(从请求到响应调用的方法)方法。
把数据从请求到响应分步骤来处理,每一个步骤都是一个中间处理环节。
var http = require('http');
var url = require('url');
var cookie = require('./expressPtoject/cookie');
var query = require('./expressPtoject/query');
var postBody = require('./expressPtoject/post-body');
var server = http.createServer(function(){
// 解析请求地址中的get参数
// var obj = url.parse(req.url,true);
// req.query = obj.query;
query(req,res); //中间件
// 解析请求地址中的post参数
req.body = {
foo:'bar'
}
});
if(req.url === 'xxx'){
// 处理请求
...
}
server.listen(3000,function(){
console.log('3000 runing...');
});
同一个请求对象所经过的中间件都是同一个请求对象和响应对象。
var express = require('express');
var app = express();
app.get('/abc',function(req,res,next){
// 同一个请求的req和res是一样的,
// 可以前面存储下面调用
console.log('/abc');
// req.foo = 'bar';
req.body = {
name:'xiaoxiao',
age:18
}
next();
});
app.get('/abc',function(req,res,next){
// console.log(req.foo);
console.log(req.body);
console.log('/abc');
});
app.listen(3000, function() {
console.log('app is running at port 3000.');
});
万能匹配(不关心任何请求路径和请求方法的中间件):
// 中间件本身是一个方法,该方法接收三个参数:
// Request 请求对象
// Response 响应对象
// next 匹配的下一个中间件
app.use(function(req,res,next){
console.log('Time',Date.now());
// 默认情况下不会自动添加next()
// 不添加next()就会导致多个中间件只会加载第一个中间件,也就是说有三个中间件a、b、c,如果a加了next()则可以执行b,但是b不加next()就不会执行c
next();
});
关心请求路径和请求方法的中间件:
app.use('/a',function(req,res,next){
console.log('Time',Date.now());
next();
});
注意: next()是指匹配的下一个中间件,在万能匹配中代表的是紧挨的中间件,因为万能匹配所有都会匹配,但是关心请求路径和请求方法中的中间件就需要匹配路径了
app.use(function(req,res,next){
console.log('万能中间件')
next()
})
app.use('/a',function(req,res,next){
console.log('请求路径为/a的中间件')
})
app.use('/b',function(req,res,next){
console.log('请求路径为/b的中间件')
})
假设现在有一个请求路径为/b/ac的请求,由于第一个中间件是万能中间件,所以肯定会打印万能中间件
,并且万能中间件还加入了next(),因为next()是下一个匹配的中间件,但是又由于第二个中间件/a
开头不匹配,所以这里的next()是指/b
,所以最后打印的内容为万能中间件 请求路径为/b的中间件
严格匹配请求路径和请求方法的中间件
严格匹配:请求路径不仅要完全等于设置的路径,方法也要符合,而上面的方法只要是请求路径的开头包含就可以了
get:
app.get('/',function(req,res){
res.send('get');
});
post:
app.post('/a',function(req,res){
res.send('post');
});
put:
app.put('/user',function(req,res){
res.send('put');
});
delete:
app.delete('/delete',function(req,res){
res.send('delete');
});
var express = require('express');
var app = express();
// 中间件:处理请求,本质就是个函数
// 在express中,对中间件有几种分类
// 1 不关心任何请求路径和请求方法的中间件
// 也就是说任何请求都会进入这个中间件
// 中间件本身是一个方法,该方法接收三个参数
// Request 请求对象
// Response 响应对象
// next 下一个中间件
// // 全局匹配中间件
// app.use(function(req, res, next) {
// console.log('1');
// // 当一个请求进入中间件后
// // 如果需要请求另外一个方法则需要使用next()方法
// next();
// // next是一个方法,用来调用下一个中间件
// // 注意:next()方法调用下一个方法的时候,也会匹配(不是调用紧挨着的哪一个)
// });
// app.use(function(req, res, next) {
// console.log('2');
// });
// // 2 关心请求路径的中间件
// // 以/xxx开头的中间件
// app.use('/a',function(req, res, next) {
// console.log(req.url);
// });
// 3 严格匹配请求方法和请求路径的中间件
app.get('/',function(){
console.log('/');
});
app.post('/a',function(){
console.log('/a');
});
// 同一个请求所经过的中间件都是同一个请求对象和响应对象
// 注意是同一个请求所经过的中间件,万能匹配也可以有以下操作
app.get('/abc', function (req, res, next) {
console.log('abc')
req.body = {
foo: 'bar'
}
next()
})
app.get('/abc', function (req, res, next) {
console.log(req.body.foo) // bar
console.log('abc 2')
})
app.listen(3000, function() {
console.log('app is running at port 3000.');
});
app.use(function(err,req,res,next){
console.error(err,stack);
res.status(500).send('Something broke');
});
配置使用404中间件:注意要放在路由之后,因为是全局没有任何一个能匹配到的路由就会匹配到这个
app.use(function(req,res){
res.render('404.html');
});
配置全局错误处理中间件:处理错误正确写法
app.get('/a', function(req, res, next) {
fs.readFile('.a/bc', funtion() {
if (err) {
// 当调用next()并且传参后,!!则直接进入到全局错误处理中间件方法中!!
// 当发生全局错误的时候,我们可以调用next传递错误对象
// 然后被全局错误处理中间件匹配到并进行处理
next(err); // 这里会直接调转到全局处理中间件中
}
})
});
//全局错误处理中间件
app.use(function(err,req,res,next){
res.status(500).json({
err_code:500,
message:err.message
});
});
参考文档:http://expressjs.com/en/resources/middleware.html
MongoDB参考手册
(表就是关系,或者说表与表之间存在关系)。
sql
语言来操作一个数据库中可以有多个数据库,一个数据库中可以有多个集合(数组),一个集合中可以有多个文档(表记录)
{
qq:{
user:[
{},{},{}...
]
}
}
下载数据库
mongoDB下载官网
注意:选择community版本
安装教程
下载好并安装,还需要配置环境变量,需要配置到bin目录下(如:D:\MongoDB\5.0\bin),此时cmd输入mongod --version便可以查看是否安装成功
node中安装
npm i mongoose
启动:
# mongodb 默认使用执行mongod 命令所处盼复根目录下的/data/db作为自己的数据存储目录
# 所以在第一次执行该命令之前先自己手动在C盘新建一个文件夹data,data下要有文件夹db /data/db
mongod
如果想要修改默认的数据存储目录,可以(不推荐,我没有修改成功):
mongod --dbpath = 数据存储目录路径
停止:
在开启服务的控制台,直接Ctrl+C;
或者直接关闭开启服务的控制台。
先开启数据库,然后重新打开一个cmd窗口
连接:
# 该命令默认连接本机的 MongoDB 服务
mongo
退出:
# 在连接状态输入 exit 退出连接
exit
show dbs
db
use 数据库名称
show collections
db.表名.find()
http://mongodb.github.io/node-mongodb-native/
第三方包:mongoose
基于MongoDB官方的mongodb
包再一次做了封装,名字叫mongoose
,是WordPress项目团队开发的。
https://mongoosejs.com/
官方学习文档:https://mongoosejs.com/docs/index.html
具体可以查看:CSDN中mongoose应用
基本概念
{
qq: {
users: [
{name: '张三', age: 15},
{name: '李四', age: 15},
{name: '王五', age: 15}
],
products: [
],
...
},
taobao: {},
baidu: {}
}
这里的qq、taobao、baidu就是数据库,users、products是表,表里面的数据是文档
基本操作
var mongoose = require('mongoose');
// 连接 MongoDB 数据库,mongodb5.0之后就不支持useMongoClient,需要将useMongoClient都改为useNewUrlParser,也可以不用加
// mongoose.connect('mongodb://localhost/test', { useNewUrlParser: true });
mongoose.connect('mongodb://localhost/test');
mongoose.Promise = global.Promise;
// 创建一个模型
// 就是在设计数据库
// MongoDB 是动态的,非常灵活,只需要在代码中设计你的数据库就可以了
// mongoose 这个包就可以让你的设计编写过程变的非常的简单
var Cat = mongoose.model('Cat', { name: String });
for (var i = 0; i < 100; i++) {
// 实例化一个 Cat
var kitty = new Cat({ name: '喵喵' + i });
// 持久化保存 kitty 实例
kitty.save(function (err) {
if (err) {
console.log(err);
} else {
console.log('meow');
}
});
}
此时可以将数据库切换到test,便可以看到cats表,然后使用db.cats.find()查看数据,一次只会显示25条数据,输入it可以显示更多
const mongoose = require('mongoose')
// 拿到设计结构
const Schema = mongoose.Schema
// 连接test数据库
mongoose.connect('mongodb://localhost/test')
// 设计集合结构(表结构)
// 字段名称就是表结构中的属性名称,值为字段要求为什么属性
// 约束的目的就是为了保证数据的完整性,不要有脏数据
var userSchema = new Schema({
username: {
type: String,
required: true // 非空的
},
password: {
type: String,
required: true
},
email: {
type: String
}
})
// 将文档结构发布为模型
// mongoose.model 方法就是用来将一个架构发布为 model
// 第一个参数:传入一个大写名词单数字符串用来表示你的数据库名称
// mongoose 会自动将大写名词的字符串生成 小写复数 的集合名称
// 例如这里的 User 最终会变为 users 集合名称
// 第二个参数:架构 Schema
//
// 返回值:模型构造函数
const User = mongoose.model('User', userSchema)
到了这时候我们就已经完成了数据结构的设计,我们就可以操作数据库进行增删改查
// 增加一条数据,里面传递的对象需要严格按照Schema标准来
const admin = new User({
username: 'admin',
password: '123456',
email: '[email protected]'
})
// 使用User.save(callback)方法存储数据
// callback有两个参数,err为错误信息,ret为返回值,要是存储成功err为null
admin.save((err, ret) => {
if (err) {
console.log('存储失败')
} else {
console.log('保存成功')
console.log(ret)
}
})
// 使用Schema.remove(Obj, callback),删除所有符合条件的数据
User.remove({
username: 'admin'
}, (err, ret) => {
if (err) {
console.log('删除失败')
} else {
console.log('删除成功')
console.log(ret)
}
})
根据条件删除一个:
Model.findOneAndRemove(conditions,[options],[callback]);
根据id删除一个:
User.findByIdAndRemove(id,[options],[callback]);
更新所有:
User.remove(conditions,doc,[options],[callback]);
根据指定条件更新一个:
User.FindOneAndUpdate([conditions],[update],[options],[callback]);
通过id修改
// 使用Schema.findByIdAndUpdate(id, updateObj, callback)通过id来修改
User.findByIdAndUpdate('60f52262ff45f649805b707b', {
username: '张三' // 将原先的username改为现在的‘张三’
}, (err, ret) => {
if (err) {
console.log('更新失败')
} else {
console.log('更新成功')
console.log(ret)
}
})
// 使用Schema.find(callback)查询所有数据
// callback两个参数,err为错误信息,ret为返回值,要是查询成功,ret为一个数组
User.find((err, ret) => {
if (err) {
console.log('查询全部失败')
} else {
console.log('查询全部成功')
console.log(ret)
}
})
// 使用Schema.find(Obj, callback)条件查询所有
// 此处查询username为admin的所有数据
User.find({
username: 'admin'
}, (err, ret) => {
if (err) {
console.log('条件查询失败')
} else {
console.log('条件查询成功')
console.log(ret)
}
})
// 使用Schema.findOne(Obj, callback)条件查询单个,返回一个对象,没有条件则返回一个查询到的数据
User.findOne({
username: 'admin',
password: '123456'
}, (err, ret) => {
if (err) {
console.log('条件查询失败')
} else {
console.log('条件查询成功')
console.log(ret)
}
})
const mongoose = require('mongoose')
// 拿到设计结构
const Schema = mongoose.Schema
// 连接test数据库
mongoose.connect('mongodb://localhost/test')
// 设计集合结构(表结构)
// 字段名称就是表结构中的属性名称,值为字段要求为什么属性
// 约束的目的就是为了保证数据的完整性,不要有脏数据
var userSchema = new Schema({
username: {
type: String,
required: true // 非空的
},
password: {
type: String,
required: true
},
email: {
type: String
}
})
// 将文档结构发布为模型
// mongoose.model 方法就是用来将一个架构发布为 model
// 第一个参数:传入一个大写名词单数字符串用来表示你的数据库名称
// mongoose 会自动将大写名词的字符串生成 小写复数 的集合名称
// 例如这里的 User 最终会变为 users 集合名称
// 第二个参数:架构 Schema
//
// 返回值:模型构造函数
const User = mongoose.model('User', userSchema)
// 增加一条数据,里面传递的对象需要严格按照Schema标准来
const admin = new User({
username: 'zs',
password: '123456',
email: '[email protected]'
})
// 使用User.save(callback)方法存储数据
// callback有两个参数,err为错误信息,ret为返回值,要是存储成功err为null
admin.save((err, ret) => {
if (err) {
console.log('存储失败')
} else {
console.log('保存成功')
console.log(ret)
}
})
// 查询
// 使用Schema.find(callback)查询所有数据
// callback两个参数,err为错误信息,ret为返回值,要是查询成功,ret为一个数组
User.find((err, ret) => {
if (err) {
console.log('查询全部失败')
} else {
console.log('查询全部成功')
console.log(ret)
}
})
// 使用Schema.find(Obj, callback)条件查询所有
// 此处查询username为admin的所有数据
// User.find({
// username: 'admin'
// }, (err, ret) => {
// if (err) {
// console.log('条件查询失败')
// } else {
// console.log('条件查询成功')
// console.log(ret)
// }
// })
// 使用Schema.findOne(Obj, callback)条件查询单个,返回一个对象,没有条件则返回一个查询到的数据
// User.findOne({
// username: 'admin'
// }, (err, ret) => {
// if (err) {
// console.log('条件查询失败')
// } else {
// console.log('条件查询成功')
// console.log(ret)
// }
// })
// 删除
// 使用Schema.remove(Obj, callback),删除所有符合条件的数据
User.remove({
username: 'admin'
}, (err, ret) => {
if (err) {
console.log('删除失败')
} else {
console.log('删除成功')
console.log(ret)
}
})
// 更新数据
// 使用Schema.findByIdAndUpdate(id, updateObj, callback)通过id来修改
User.findByIdAndUpdate('60f52262ff45f649805b707b', {
username: '张三' // 将原先的username改为现在的‘张三’
}, (err, ret) => {
if (err) {
console.log('更新失败')
} else {
console.log('更新成功')
console.log(ret)
}
})
Node中有专门的模块(mysql)来操作Mysql
文档:https://www.npmjs.com/package/mysql
可以参考CSDN文档使用Node操作Mysql
安装
npm i mysql
// 导入核心模块
var mysql = require('mysql');
// 1. 创建连接,需要传递一个对象,对象有host、user、password、database
// host:连接数据库地址、user:用户名、password:密码、database:选择数据库
var connection = mysql.createConnection({
host: 'localhost',
user: 'root',
password: 'root',
database: 'nodejs'
});
// 2. 使用connect()方法连接数据库
connection.connect();
// 3. 执行数据操作(增删改查都是使用query方法),两个参数,一个执行语句,一个回调函数
// 回调函数两个参数:error表示错误信息,成功为null,失败为错误对象;result表示返回结果
// 创建表
let createTable = `CREATE TABLE USER(
user_id INT NOT NULL AUTO_INCREMENT PRIMARY KEY,
user_name VARCHAR(20) NOT NULL,
user_password VARCHAR(20) NOT NULL
)`
connection.query(createTable, function(err, result){
if(err){
console.error('create failed and err is ', err)
}
console.log('create success and the result is ', result)
})
// 插入数据
connection.query('INSERT INTO user VALUES(NULL, "admin", "123456")', function (error, results) {
if (error) {
return console.log(error);
}
console.log('The solution is: ', results);
});
// 查询
connection.query('SELECT * FROM `user`', function (error, results) {
if (error) {
return console.log(error);
}
console.log('The solution is: ', results);
});
// 4. 关闭连接
connection.end();
可以使用一个第三方命名行工具:nodemon
来帮助我们解决频繁修改代码重启服务器的问题。
nodemon
是一个基于Node.js开发的一个第三方命令行工具,我们使用的时候需要独立安装:
#在任意目录执行该命令都可以
#也就是说,所有需要 --global安装的包都可以在任意目录执行
npm install --global nodemon
npm install -g nodemon
#如果安装不成功的话,可以使用cnpm安装
cnpm install -g nodemon
安装完毕之后使用:
// 不使用nodemon就和原先一样使用node
node ./app.js
// 使用nodemon就是将原先的node换成nodemon
nodemon ./app.js
只要是通过nodemon
启动的服务,则他会监视你的文件变化,当文件发生变化的时候,会自动帮你重启服务器。
具体浏览器的事件循环可以参考浏览器事件循环
只要是涉及到异步请求的都需要使用回调函数
不成立的情况下:
function add(x,y){
console.log(1);
setTimeout(function(){
console.log(2);
var ret = x + y;
return ret;
},1000);
console.log(3);
//到这里执行就结束了,不会i等到前面的定时器,所以直接返回了默认值 undefined
}
console.log(add(2,2));
// 结果是 1 3 undefined 4
使用回调函数解决:
回调函数:通过一个函数,获取函数内部的操作。(根据输入得到输出结果)
var ret;
function add(x,y,callback){
// callback就是回调函数
// var x = 10;
// var y = 20;
// var callback = function(ret){console.log(ret);}
console.log(1);
setTimeout(function(){
var ret = x + y;
callback(ret);
},1000);
console.log(3);
}
add(10,20,function(ret){
console.log(ret);
});
ajax:
基于原生XMLHttpRequest封装get方法:
var oReq = new XMLHttpRequest();
// 当请求加载成功要调用指定的函数
oReq.onload = function(){
console.log(oReq.responseText);
}
oReq.open("GET", "请求路径",true);
oReq.send();
function get(url,callback){
var oReq = new XMLHttpRequest();
// 当请求加载成功要调用指定的函数
oReq.onload = function(){
//console.log(oReq.responseText);
callback(oReq.responseText);
}
oReq.open("GET", url,true);
oReq.send();
}
get('data.json',function(data){
console.log(data);
});
callback hell(回调地狱):一个异步请求依赖于另外一个异步请求的结果
文件的读取无法判断执行顺序(文件的执行顺序是依据文件的大小来决定的)(异步api无法保证文件的执行顺序)
// 无法保证读取顺序,a->b->c、a->c->b、c->b->a都有可能
fs.readFile('./data/a.txt', 'utf-8', (err, data) => {
if (err) {
// 抛出异常
throw err
}
console.log(data)
})
fs.readFile('./data/b.txt', 'utf-8', (err, data) => {
if (err) {
// 抛出异常
throw err
}
console.log(data)
})
fs.readFile('./data/c.txt', 'utf-8', (err, data) => {
if (err) {
// 抛出异常
throw err
}
console.log(data)
})
通过回调嵌套的方式来保证顺序:
// 通过回调嵌套保证读取顺序,这样读取就一定是a->b->c
// 这也就是callback hell,回调地狱
fs.readFile('./data/a.txt', 'utf-8', (err, data) => {
if (err) {
// 抛出异常
throw err
}
console.log(data)
fs.readFile('./data/b.txt', 'utf-8', (err, data) => {
if (err) {
// 抛出异常
throw err
}
console.log(data)
fs.readFile('./data/c.txt', 'utf-8', (err, data) => {
if (err) {
// 抛出异常
throw err
}
console.log(data)
})
})
})
为了解决以上编码方式带来的问题(回调地狱嵌套),所以在EcmaScript6新增了一个API:Promise
。
基本语法:
console.log(1)
// 创建Promise容器,容器当中一般存放一个异步任务,并且返回一个实例,Promise容器一旦创建,就会开始执行里面的方法
// 容器接受两个参数,resolve:解决,就是成功、reject:驳回,就是失败
const p1 = new Promise((resolve, reject) => {
console.log(2)
// 异步任务
fs.readFile('./data/a.txt', 'utf-8', (err, data) => {
if (err) {
// 失败了,Promise容器中的任务失败了
// console.log(err)
// 将容器的Pending状态改为reject
reject(err)
} else {
console.log(3)
// Promise容器中的任务成功了
// console.log(data)
// 将容器的Pending状态改为resolve
resolve(data)
}
})
})
console.log(4)
// 打印顺序为1243,因为Promise本身不是异步的,但是里面会有异步的任务
// 使用p1.then()方法来做指定的操作,方法两个回调函数,一个是resolve情况下的处理方法,一个是reject情况下的处理方法
p1
.then((data) => {
console.log('文件读取成功:' + data)
}, (err) => {
console.log('文件读取失败:' + err)
})
链式循环:
封装Promise的readFile
:
const fs = require('fs')
let pReadFile = (filePath) => {
return new Promise((resolve, reject) => {
fs.readFile(filePath, 'utf-8', (err, data) => {
if (err) {
reject(err)
} else {
resolve(data)
}
})
})
}
pReadFile('./data/a.txt')
.then((data) => {
console.log(data)
// 在返回一个pReadFile方法并且设置读取b.txt文件,这样就从pReadFile('./data/a.txt')方法变成了pReadFile('./data/b.txt')
return pReadFile('./data/b.txt')
})
.then((data) => {
console.log(data)
// 在返回一个pReadFile方法并且设置读取c.txt文件
return pReadFile('./data/c.txt')
})
.then((data) => {
console.log(data)
})
jQuery的ajax方法也支持Promise
var data = {}
// path是请求路径
$.get(path1)
.then((userData) => {
data.userData = userData
return $.get(path2)
})
.then((jobData) => {
// 在这里是不能拿到userData的数据的,只能在外面再声明一个变量
data.jobData = jobData
})
mongoose所有的API都支持Promise:
// 查询所有
User.find()
.then(function(data){
console.log(data)
})
注册:
User.findOne({username:'admin'},function(user){
if(user){
console.log('用户已存在')
} else {
new User({
username:'aaa',
password:'123',
email:'fffff'
}).save(function(){
console.log('注册成功');
})
}
})
User.findOne({
username:'admin'
})
.then(function(user){
if(user){
// 用户已经存在不能注册
console.log('用户已存在');
}
else{
// 用户不存在可以注册
return new User({
username:'aaa',
password:'123',
email:'fffff'
}).save();
}
})
.then(funciton(ret){
console.log('注册成功');
})
async函数
md5是一种常见的加密方式,不可被反编译
常见的还有:
在node中使用:
下载
cnpm install md5
# 也有的地方使用blueimp-md5插件,其实结果是一样的
引入
const md5 = require('md5')
使用
const passWord = md5("加密的字符"); //将加密过后的字符赋值给 passWord
案例
const md5 = require('md5')
const password = '123456'
// 对密码进行污染,增加难度
const password1 = md5(password + '#@')
// 除了对密码污染之外还可以对密码多层加密
const password2 = md5(md5(password))
想要验证密码,就将输入的密码使用同样的规则加密,然后和数据库中查询的做对比