简单学习NODEJS
NODEJS介绍
NODEJS是前端工程化的基础
NODEJS无法调用DOM,BOM
NODE代码中建议使用绝对路径
常用框架express,electron,restify
运行node文件(项目目录下) node 文件名,其它需要添加路径支持
安装NODEJS
NODE版本管理工具NVM
1.下载安装NVM
安装NVM前一定要先卸载已安装的NODEJS,否则会发生冲突。
请使用管理员身份安装,避免出现乱码
NVM下载地址:https://github.com/coreybutler/nvm-windows/releases
2.配置NVM
在安装路径默认c:\用户\登录用户\AppData\Roaming\nvm目录打开setting.txt,添加如下代码:(使用国内镜像)
node_mirror:https://npm.taobao.org/mirrors/node/
npm_mirror:https://npm.taobao.org/mirrors/npm
3.使用NVM管理NODEJS
在控制台或CMD,终端下命令:
nvm on/off | 使用NVM管理node |
nvm -v | nvm的版本,查看nvm是否安装成功 |
nvm install | 安装nodejs的版本号 |
nvm uninstall | 卸载node版本 |
nvm ls(list) | 显示已安装的nodejs版本号 |
nvm list available | widnow显示远程可安装版本 |
nvm ls-remote | linux显示远程可安装版本 |
nvm use | 使用nodejs版本号 |
nvm install stable | 安装最稳定版本 |
nvm use node | 安装最新版 |
实时监控文件变化运行NODE文件
1.安装第三方包nodemon
npm i -g nodemon
2.使用nodemon
默认监控当前工作目录
nodemon app.js localhost 3697
其它nodemon使用方法请移步度娘搜索nodemon
创建工作环境
1.初始化项目(工作目录)
npm init -y
模块化
NODEJS中每个文件都是一个独立的模块,模块间使用特定语法导出,导入使用
默认使用commonJS标准,变更ECMA标准在package.json指定
{“type”:“module”}
使用require()方法加载其它模块时,会执行被加载模块中的代码
module.exports和exports
默认exports和module.exports 指向同一个对象
使用误区:
require模块时,指向的是module.exports指向的对象
建议不要在同一个模块中同时使用exports和module.exports
CommonJS标准
默认导出module.exports={}
按需导出exports.变量名
导入require('模块名‘)
自定义模块需要添加路径
ECMAScript标准
默认导出 export defalut{}
按需导出 export
导入import 变量名 from ‘模块名’
包的概念
将模块,代码,其他资料整合在一个文件夹下
包分类:
项目包:编写项目和业务逻辑
软件包:封装工具和方法进行使用
package.json文件记录软件包的名字,作者,入口文件等信息。
导入一个包文件夹,是根据package.json文件。
第三方包目录在node_modules
本地软件包:作用当前项目
全局软件包:作用所有项目
package.json文件
package-lock.json 锁定开发依赖清单
软件包版本:X,Y,Z:X大版本,Y功能版本,Z修复版本
~1.2.2,安装的是1.2.x不低于1.2.2版本,但不安装1.3.x
^1.2.2安装1.x.x的最新版本,不低于1.2.2,不装2.x.x
大版本X为0,^和~是表示相同的。
latest最新版
npm shrinkwrap 当前目录下生成npm-shrinkwrap.json,npm i 优先使用这个安装(package-lock.json)
dependencies 记录安装了哪些包
devDependencies 开发环境使用的包
开发自己的软件包
1.包的顶级目录存在package.json
2.包必须是单独的目录
3.package.json包含:name,version,main,description
发布包的方法:
1.官网注册账户,发布时切换到官网npm源
2.在终端登录npm login
3.发布 npm publish
4.删除已发布 npm unpublish 包名 --force
只能删除72小时内的包,删除后在24小时内不许重复发布,不要随意发布垃圾包
软件包管理器NPM
安装NODEJS内置了NPM包管理器
允许用户从NPM服务器下载第三方包到本地使用
允许用户从NPM服务器下载安装别人的命令程序到本地使用
允许用户上传到NPM给别人使用
npm 使用
npm i | 根据package.json补全项目文件清单 |
npm i -g | 全局安装软件包 |
npm uni | 卸载软件包 |
npm i -D | 安装开发环境使用的软件包,生产环境不用 |
软件包的安装方式:通过 https://www.npmjs.com/查看具体的建议安装方式
使用NRM管理NPM源,快速切换npm源
npm install -g nrm | 安装NRM |
nrm ls | 查看源列表 , *代表当前使用源 |
nrm current | 当前使用的源 |
nrm use | 切换源,源名称 |
nrm add | 添加源,源名称,地址 |
nrm del | 删除源 |
nrm test | 测试源 |
如果遇到禁止运行脚本提示,在power shell管理员运行如下命令:
set-ExecutionPolicy RemoteSigned
NODEJS内置模块–文件读写FS
FS模块封装了与本机文件系统交互的方法,属性
语法:
1.加载fs模块对象
2.写入文件内容
3.读取文件内容
const fs = require('fs')
fs.readFile(path[,options],callback)
fs.writeFile(path,data,[,options],callback)
判断是否读取成功
读取成功,err,为null,读取失败results为undefined
const fs = require('fs')
fs.readFile('11.txt','utf-8',(err,results)=>{
console.log( err ? err.message : '读取成功')
console.log(results? results : undefined)
//未完成,读取成功,文件为空的情况
})
判断写入成功
写入文件默认是utf8模式覆盖写入,写入成功err为null,
const fs = require('fs')
const str = '和您一起学习nodejs'
fs.writeFile('11.txt', str, 'utf-8', (err,results)=>{
console.log( err ? err.message : '写入成功')
})
NODEJS内置模块–路径path
语法:
1.引入path模块
2.使用path.join方法拼路径
const path = require('path')
path.join(__dirname,filename)
路径动态拼接
NODE代码中,相对路径是根据终端所在路径来查找的,可能无法找到文件,建议在NODE代码中使用绝对路径
__dirname(不包含文件)node内置获取当前模块目录绝对路径。
__filename(包含文件)node内置获取当前模块目录绝对路径。
path.basename(path,[options] 获取路径中的文件名,
options会省略文件扩展名
path.resolve()返回以程序为根目录,作为起点,根据参数解析出一个绝对路径。注意“ / ”的使用,代表根目录
path.join(…paths
<string>
) 多个路径片段拼接完整路径字符串
path.resolve(‘a’, ‘/b’) 返回路径是根目录下的b目录(c:\b)
path.extname(path)获取路径中的文件扩展名
const path = require(‘path’);
const pathstr = path.join([‘/a’,’/b’c’,’../’,’./d’,’e’])
console.log(pathstr) // 输出\a\b\d\\e
这里 ../ 拼接时抵消了它前面的字符串,抵消了 C
NODEJS内置模块–WEB服务http
语法:
1.引入http模块对象
2.实例化web服务器
3.绑定事件,监听request请求事件,设置响应
4.监听,启动web服务
const http = require('http')
const fs = require('fs')
const path = require('path')
const server = http.createServer();
server.on('request',(req,res)=>{
const url = req.url;
const filePath = path.join(__dirname, url);
fs.readFile(filePath,(err,results)=>{
if(err) return res.send('404 not found')
res.end(results)
})
})
server.listen(80,()=>console.log( 'server running at http://127.0.0.1'))
处理查询字符串
将查询字符串处理解析成对象格式
语法:
1.引入模块对象
2.使用parse()函数
const qs = require(‘querystring’)
const obj = qs.parse(str)
示例 压缩HTML
步骤
1.读取源html文件内容
2.正则替换字符串
3.写入到新的html文件中
Express基础
基于NODE内置的http模块封装,创建WEB服务器
1.安装
npm init -y
npm i express@4.17.2
2.使用
const express = require('express')
const app = express()
const port = 3000
app.get('/', (req, res) => {
res.send('Hello World!')
})
app.listen(port, () => {
console.log(`Example app listening on port ${port}`)
})
3.运行
nodemon app.js
Express路由
客户端请求与服务器处理函数之间的映射关系
路由3部分组成:
请求类型,请求的URL,处理函数
app.method(path,handler)
示例:app.get(‘/’,(req,res)=>res.send(‘hello world’)
app实例,get请求方法,path服务上的文件路径,handler路由匹配时执行的数据。
当请求到达服务器之后,需要先经过路由匹配成功,才调用对应的处理函数,路径匹配按路由定义的顺序进行匹配,当请求类型和请求URL同时匹配成功,则express会将这次请求转交给对应的函数进行处理
路由方法
get,post,put,delete,all
路由路径
Express 使用路径到正则表达式来匹配路由路径;
‘/ab?cd’ =‘acd abcd
’/ab+cd’=abcd abbcd abbbcd
‘ab*cd’=abcd abxcd abRANDOMcd ab123cd
‘/ab(cd)?e’= /abe/abcde
/a/=a
/.*fly$/=butterflydragonflybutterflymandragonflyman
路由参数
1.获取URL的动态参数
app.get(‘/user/:id’,(req,res)=>res.send(req.params)
path: /flights/:from-:to
URL: http://localhost:3000/flights/LAX-SFO
path: /plantae/:genus.:species
URL: http://localhost:3000/plantae/Prunus.persica
path: /user/:userId(\d+)
URL: http://localhost:3000/user/42
2.获取URL地址参数
path=/user?name=abc&age=18’,
app.get(‘/user’,(req,res)=>res.send(req.query)
3.获取表单数据
使用内置中间件app.use(express.urlencoded({extended:false})
req.body()
路由处理函数
app.get(‘/example/c’, [cb0, cb1, cb2])
app.get(‘/example/d’, [cb0, cb1], function (req, res, next) {})
路由处理结果函数
res.download() | 提示下载文件。 |
res.end() | 结束响应过程。 |
res.json() | 发送 JSON 响应 |
res.jsonp() | 发送支持 JSONP 的 JSON 响应。 |
res.redirect() | 重定向请求。 |
res.render() | 呈现视图样板 |
res.send() | 发送各种类型的响应。 |
res.sendFile() | 将文件作为八位字节流发送。 |
res.sendStatus() | 设置响应状态代码并将其字符串表示形式作为响应正文发送。 |
模块化路由
不建议将路由直接挂载到APP实例上,推荐将路由抽离为单独的模块。
1.创建单独的js文件
2.调用express.Router()创建路由对象
3.向路由对象挂载具体的路由
4.向外共享路由对象
5.使用app.use()在app实例注册路由模块
//router.js
const express = require('express')
const router = express.Router()
rout.get('/', (req,res)=>res.send('welcome'))
module.exports = router
//app.js
const express = require('express')
const app=express()
const router = require('./router')
app.use(router)
app.listen(80,()=>console.log('app running ')
路由前缀
app.use(‘/api’,userRouter)
中间件middleware
对当次请求进行预处理,中间件必须包next参数,路由只有req,res
next函数的作用
实现多个中间件连续调用的关键,表示把流转关系交给下一个中间件或路由
全局中间件
const mw = (req,res,next)=>{
console.log('xxx')
next();
}
//通过app.use(中间件函数)注册中间件
可以连续定义多个全局中间件,会按照定义的先后顺序依次进行调用。
局部中间件
不使用app.use定义的中间件
const express = require('express');
const app = express();
//局部中间件
const mw1 = (req, res, next) => {
console.log('调用了局部中间件');
next();
};
const mw2 = (req, res, next) => {
console.log('调用了局部中间件');
next();
};
app.get('/', mw1,mw2, (req, res) => {
res.send('Home Page');
});
app.listen(80, () => console.log('running'));
中间件作用
多个中间件共享同一份req,res,上下游中间件之间可以给req对象添加自定义属性、方法给下游中间件或路由使用
const express = require('express');
const app = express();
const mw = (req, res, next) => {
console.log('mw');
const time = Date.now();
req.startTime = time;
next();
};
//注册为全局
app.use(mw);
//路由处理函数
app.get('/', (req, res) => res.send('abc' + req.startTime));
app.listen(80, () => console.log('app1 running'));
中间件使用注意事项
1.必须放在路由前面,否则无效
2.可以连续调用多个中间件
3.不要忘记调用next
4.next后不要写代码,业务已经结束
5.连续调用多个中间件,中间件之间共享req,res对象
中间件分类
1.应用级别
app.use,app.get,app.post等绑定到app实例的中间件
2.路由级别
绑定到express.Router实例上的
router.use((req,res,next)=>next())
3.错误级别
捕获项目错误,放在所有路由的最后
const express = require('express');
const router = require('./router');
const app = express();
app.get('/', (req, res) => {
throw new Error('服务器内部发生了错误');
res.send('Home Page');
});
app.use((err, req, res, next) => {
console.log('发生了错误:' + err.message);
res.send('Error!' + err.message);
});
app.listen(80, () => console.log('app running at 127.0.0.1'));
内置中间件
1.express.static快速托管静态资源
2.express.json解析JSON格式的请求数据,有兼容性在4.16+可用
3.express.urlencoded解析url-encode格式的请求体数据 有兼容性在4.116+可用
app.use(express.json())
app.use(express.urlencoded({extended:false}))
第三方中间件
1.npm i
2.引入
3.挂载app.use
自定义中间件
1.定义中间件文件
2.监听req的data事件
3.监听req的end事件
4.使用querystring模块解析请求体数据
5.将解析的数据对象挂载为req.body
6.将自定义中间件封装为模块
客户端如果数据量比较大,无法一次发送完毕,会把数据切割后分批发送到服务器,所以data事件会触发多次,每次触发获取到数据的一部分,需要手动对接收的数据进行拼接。
拼接后使用node内置模块querystring对字符串转换
const express = require('express');
const customBodyParse = require('./middleware.js');
const app = express();
app.use(customBodyParse);
app.post('/', (req, res) => {
console.log(req.body);
res.send(req.body);
});
app.listen(80, () => console.log('app running at 127.0.0.1'));
//模块化自定义中间件
const qs = require('querystring');
const bodyParse = (req, res, next) => {
//1.监听req的data,完整数据
let str = '';
req.on('data', chunk => (str += chunk));
//监听req end
req.on('end', () => {
// console.log('str: ' + str);
const body = qs.parse(str);
req.body = body;
next();
});
};
module.exports = bodyParse;
托管静态文件
定义:express.static(path,[options])
发布:app.use(express.static(‘public’)
访问方法:http://localhost/
express在静态目录中查找文件,因此存放静态文件的目录不会出现在URL中
API 接口编程
1.创建api服务器
2.创建api路由模块
3.注册api路由模块
4.编写接口
const express = require('express');
const app = express();
app.listen(80, () => console.log('app running at 127.0.0.1'));
const express = require('express');
const router = express.Router();
//路由定义
module.exports = router;
const express = require('express');
const app = express();
const router = require('./apiRouter');
app.use('/api', router);
app.listen(80, () => console.log('app running at 127.0.0.1'));
router.get('/get', (req, res) => {
//通过req.query获取客户端数据
const query = req.query;
res.send({
status: 0,
msg: 'GET 请求成功!',
data: query,
});
});
const express = require('express');
const app = express();
const router = require('./apiRouter');
app.use(express.urlencoded({ extended: false }));
app.use('/api', router);
app.listen(80, () => console.log('app running at 127.0.0.1'));
router.post('/post', (req, res) => {
//通过req.body获取客户端url-encoded数据
const query = req.body;
console.log(query);
res.send({
status: 0,
msg: 'POST 请求成功!',
data: query,
});
});
跨域问题
方案
cors
jsonp只支持GET请求
使用cors中间件
1.安装npm i cors
2.导入 const cors = require(‘cors’)
3.配置app.use(cors())必须放置在路由之前
什么是cors
cross-origin resource sharing
浏览器的同源安全策略默认会阻止网页跨域,如果接口服务器配置了cors相关的http响应头就可以解除浏览器的跨域访问限制
服务器要允许访问
只有支持XMLHttpRequest LEVEL2的浏览器才能正常访问(ie10+)
cors响应头
Access-Control-Allow-Origin:|允许所有的url
res.setHeader(‘Access-Control-Allow-Origin’, ’’)
Access-Control-Allow-Headers:额外的请求信息
res.setHeader(‘Access-Control-Allow-Headers’, ‘content-type, X-Custom-Header’)
Access-Control-Allow-Methods 默认get,post,head
如果要使用put,delete,需要在服务端设置
res.setHeader(‘Access-Control-Allow-Methods’, ‘*’)
res.setHeader(‘Access-Control-Allow-Methods’, ‘put,delete’)
cors请求的分类
根据请求方式,请求头不同:
1.简单请求
请求方式:get,post,head
http头部不超过以下几种字段:
accept,accept-language,content-language,DPR,Downlink,save-data,viewport-width,width,content-type(application/x-www-form-urlencoded,multipart/form-data,text/plain)
2.预检请求
上面的请求之外的请求,请求头包含了自定义头部字段
向服务器发送了application/json格式数据
浏览器与服务器正式通信之前,浏览发送option请求进行预检,以获知服务器是否实际支持,成功响应后,才会发送真正的请求,并携带真实数据
简单请求只发生一次请求
预检请求发生二次请求
JSONP接口
浏览器通过《script》标签的src属性,请求服务器数据,同时服务器返回一个函数的调用。
JSONP不属于真正Ajax,没有使用XMLHttpRequest
仅支持Get
JSONP请求防止冲突,必须配置在cors之前
//jsonp
app.get(‘/api/jsonp’,(req,res)=>{
})
实现jsonp
1.获取client发送过来的回调函数名字
2.得到要通过JSONP形式发送给客户端的数据
3.拼一个函数调用的字符串
4.响应给client
$('#jsonp').on('click', () => {
$.ajax({
type: 'GET',
url: 'http://127.0.0.1/api/jsonp',
dataType: 'jsonp',
success: (res) => console.log(res)
})
})
//jsonp
app.get('/api/jsonp', (req, res) => {
const funname = req.query.callback;
const data = { name: 'zs', age: 20 };
const str = `${funname}(${JSON.stringify(data)})`;
res.send(str);
});
MySql数据库
数据库结构:库,表,行,字段
工具:mysql server, mysql workbench
1.安装mysql第三方模块
2.通过mysql模块连接MySQL数据库
3.通过MySQL模块执行sql语句
mysql8连接认证错误:修改连接认证协议为
ALTER USER ‘root’@‘localhost’ IDENTIFIED WITH mysql_native_password BY ‘123456’;
flush privileges;
npm i mysql
配置mysql模块
const mysql = require('mysql');
const db = mysql.createConnection({
host: '127.0.0.1',
user: 'root',
password: 'nethand88',
database: 'my_db_01',
});
执行slq语句
查询
db.connect();
const sqlstr = 'select * from users';
db.query(sqlstr, (err, results) => {
if (err) return console.log(err.message);
console.log(results);
});
db.end();
插入
db.connect();
const user = { username: 'splider2', password: 'pc1234' };
const sqlstr = 'insert into users(username,password)values(?,?)';
db.query(sqlstr, [user.username, user.password], (err, results) => {
if (err) return console.log(err.message);
console.log(results);
if (results.affectedRows === 1) return console.log('成功');
});
db.end();
快速插入
如果新增数据时,数据对象的每个属性和数据表的字段一一对应,则可以通过下面的方式快速插入
db.connect();
const user = { username: 'splider3', password: 'pc1234' };
const sqlstr = 'insert into users set ?';
db.query(sqlstr, user, (err, results) => {
if (err) return console.log(err.message);
console.log(results);
if (results.affectedRows === 1) return console.log('成功');
});
db.end();
更新数据
db.connect();
const user = { username: 'splider5', password: 'pc12345',id:10 };
const sqlstr = 'update users set username=?,password=? where id=?';
db.query(sqlstr, [user.username,user.password,user.id], (err, results) => {
if (err) return console.log(err.message);
console.log(results);
if (results.affectedRows === 1) return console.log('成功');
});
db.end();
快速更新
db.connect();
const user = { username: 'splider5', password: '0000',id:10 };
const sqlstr = 'update users set ? where id=?';
db.query(sqlstr, [user,user.id], (err, results) => {
if (err) return console.log(err.message);
console.log(results);
if (results.affectedRows === 1) return console.log('成功');
});
db.end();
删除数据
根据id删除
db.connect();
const sqlstr = 'delete from users where id=?';
db.query(sqlstr, 10, (err, results) => {
if (err) return console.log(err.message);
console.log(results);
if (results.affectedRows === 1) return console.log('成功');
});
db.end();
标记删除(软删除)
const sqlstr = 'update users set status=? where id=?';
db.query(sqlstr, [1,6], (err, results) => {
if (err) return console.log(err.message);
console.log(results);
if (results.affectedRows === 1) return console.log('成功');
});
db.end();
身份认证
session
express-session中间件
npm i express-session
const session = require('express-session');
app.use(
session({
secret: 'itheima',
resave: false,
saveUninitialized: true,
})
);
req.session来访问和使用session对象
router.post('/login', (req, res) => {
if (req.body.username !== 'admin' || req.body.password !== '000000') {
return res.send({ status: 1, msg: '登录失败' });
}
req.session.user = req.body;
req.session.islogin = true;
res.send({ status: 0, msg: '登录成功' });
});
router.get('/username', (req, res) => {
if (!req.session.islogin) {
return res.send({ status: 1, msg: 'fail' });
}
res.send({
status: 0,
msg: 'success',
username: req.session.user.username,
});
});
router.get('/logout', (req, res) => {
req.session.destroy();
res.send({ status: 0, msg: '退出登录成功' });
});
JWT
npm i jsonwebtoken express-jwt
jsonwebtoken用于生成JWT
express-jwt用于解析jwt还原
// 导入 express 模块
const express = require('express');
// 创建 express 的服务器实例
const app = express();
// TODO_01:安装并导入 JWT 相关的两个包,分别是 jsonwebtoken 和 express-jwt
const jwt = require('jsonwebtoken');
// 允许跨域资源共享
const cors = require('cors');
app.use(cors());
// 解析 post 表单数据的中间件
const bodyParser = require('body-parser');
const { expressjwt } = require('express-jwt');
app.use(bodyParser.urlencoded({ extended: false }));
// TODO_02:定义 secret 密钥,建议将密钥命名为 secretKey
const secretKey = 'abcbit&&';
// TODO_04:注册将 JWT 字符串解析还原成 JSON 对象的中间件
app.use(expressjwt({ secret: secretKey, algorithms: ['HS256'] }).unless({ path: [/^\/api\//] }));
// 登录接口
app.post('/api/login', function (req, res) {
// 将 req.body 请求体中的数据,转存为 userinfo 常量
const userinfo = req.body;
// 登录失败
if (userinfo.username !== 'admin' || userinfo.password !== '000000') {
return res.send({
status: 400,
message: '登录失败!',
});
}
// 登录成功
// TODO_03:在登录成功之后,调用 jwt.sign() 方法生成 JWT 字符串。并通过 token 属性发送给客户端
const token = jwt.sign({ username: userinfo.username }, secretKey, {
expiresIn: '300s',
});
res.send({
status: 200,
message: '登录成功!',
token: token, // 要发送给客户端的 token 字符串
});
});
// 这是一个有权限的 API 接口
app.get('/admin/getinfo', function (req, res) {
// TODO_05:使用 req.user 获取用户信息,并使用 data 属性将用户信息发送给客户端
console.log(req.auth);
res.send({
status: 200,
message: '获取用户信息成功!',
data: req.auth, // 要发送给客户端的用户信息
});
});
// TODO_06:使用全局错误处理中间件,捕获解析 JWT 失败后产生的错误
app.use((err, req, res, next) => {
if (err.name === 'UnauthorizedError') return res.send({ status: 401, msg: '无效的token' });
res.send({ status: 500, msg: '未知的错误' });
next();
});
// 调用 app.listen 方法,指定端口号并启动web服务器
app.listen(8888, function () {
console.log('Express server running at http://127.0.0.1:8888');
});
登录成功后获取用户信息,express-jwt@9,获取用户的方式req.auth
exports.getUserInfo = (req, res) => {
const sql = 'select id,username,nickname,email,user_pic from ev_users where id=?';
db.query(sql, req.auth.id, (err, results) => {
if (err) return res.cc(err);
if (results.length !== 1) return res.cc('获取用户信息 失败!');
res.send({
status: 0,
message: '获取用户基本信息 成功!',
data: results[0],
});
});
};