Node.js 学习笔记
小插件Template String Converter
当输入${}
时,自动为其加上 ``
反引号
一、node入门
-
node.js是什么
-
node的作用
-
开发服务器应用
-
开发工具类应用
-
开发桌面端应用
1.命令行工具
-
命令的结构
-
常用命令
切换到D盘——
D:
查看D盘目录——dir
切换工作目录——cd machine
输出文件夹内的所有内容——dir /s
- cd 是change direct的缩写
2.使用node运行js文件
js文件
命令行操作
直接在vscode的集成终端中操作
3.node注意事项
顶级对象global相当于浏览器环境的window
4.Buffer 缓冲区
概念
- 一个类似于Array的对象,用于表示固定长度的字节序列
- 本质是一段内存空间,专门用来处理二进制数据。
特点
- 大小固定,且无法调整(与数组不同,数组大小可以改变)
- 性能较好,可以直接对计算机内存进行操作
- 每个元素的大小为1字节
使用
let buf=Buffer.alloc(2)
console.log(buf)let buf_2 =Buffer.allocUnsafe(11112)
//使用这种方式不一定会把旧的数据清空
//速度更快,不用进行清零操作
console.log(buf_2)let buf_3 =Buffer.from('hello')
//把字符串的每个字符都转换成unicode
console.log(buf_3)//终端显示D:\web>node test.js
<Buffer 00 00>
<Buffer 80 00 df 3e 08 02 00 00 f0 fc 10 35 08 02 00 00 00 20 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ff ff ... 11062 more bytes>
<Buffer 68 65 6c 6c 6f>D:\web>node test.js
<Buffer 00 00>
<Buffer 80 00 3b f5 be 02 00 00 80 63 34 f5 be 02 00 00 00 20 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ff ff ... 11062 more bytes>
<Buffer 68 65 6c 6c 6f>
可以发现:使用allocUnsafe每次的都不样
4.1 与字符串的转换
let buf_4 =Buffer.from([105,108,111,118,101,121,111,117])
console.log(buf_4)
console.log(buf_4.toString())//
D:\web>node test.js
<Buffer 69 6c 6f 76 65 79 6f 75>
iloveyou
4.2 Buffer 的读写
Buffer 可以直接通过 []
的方式对数据进行处理。
//读取
console.log(buf_3[1]);
//修改
buf_3[1] = 97;
//查看字符串结果
console.log(buf_3.toString())
注意:
- 如果修改的数值超过 255 ,则超过 8 位数据会被舍弃
- 一个 utf-8 的字符 一般 占 3 个字节(如:中文)
5. 计算机的基本组成
6. 程序运行的基本流程
APP、数据——载入内存——CPU读取指令——显卡or 声卡——显示器or音箱。
7.进程、线程
- 进程打开任务管理器即可查看
- 线程是进程的组成部分。
查看QQ的线程
找到进程ID
…
二、fs模块
file system 文件系统
1. 写入文件
文件不存在会自动创建。
// 导入模块
const fs1 =require('fs');
// 写入文件
fs1.writeFile('./f_write.txt','abcdefg',err=>{if(err){console.log('写入失败')return;}
})
2.同步与异步
2.1 异步写入
// 导入模块
const fs1 =require('fs');
// 写入文件
fs1.writeFile('./f_write.txt','abcdefg',err=>{if(err){console.log('写入失败')return;}console.log('写入成功')
})
console.log('1')
上述代码有两个线程:
-
JS主线程——执行解析JS代码。
-
磁盘写入——输入/输出( I / O)线程
writeFile方法是异步的。
当上述I/O线程完成后,将回调函数(err)压入队列中,等主线程执行完后续代码后,再取出这个函数,执行。
效果展示
2.2 同步写入
// 导入模块
const fs1 =require('fs');
// 写入文件
fs1.writeFileSync('./test1','test1');
写入效果:
3.文件的追加写入
3.1 appendFile
// 导入模块
const fs2 =require('fs');
// 写入文件
fs2.appendFile('./append.txt','append!!',err=>{//写入成功则返回错误对象,失败则返回noneif(err){console.log('追加写入失败')return}console.log('追加写入成功')
})
3.2 appendFileSync
// 导入模块
const fs2 =require('fs');
// 写入文件
fs2.appendFileSync('./append.txt','\r\nappend!!')//进行换行操作
3.3 writeFile…{flag:‘a’}
// 导入模块
const fs1 =require('fs');
// 写入文件(第一遍直接写入的
// fs1.writeFile('./f_write.txt','ABC',err=>{//追加写法——添加配置项
fs1.writeFile('./f_write.txt','hhhh',{flag:'a'},err=>{if(err){console.log('写入失败')return;}console.log('写入成功')
})
console.log('1')
4.文件流式写入
// 导入模块
const fs1 =require('fs');
//创建写入对象
const ws=fs1.createWriteStream('fff.txt');
//write
ws.write('乘醉听萧鼓\r\n')
ws.write('吟赏烟霞\r\n')
ws.write('异日图将好景\r\n')
ws.write('归去凤池夸\r\n')
//关闭通道
ws.close()
5.文件读取
5.1 异步读取
// // 导入模块
const fs1 =require('fs');
// //创建写入对象
// const ws=fs1.createWriteStream('fff.txt');
// //write
// ws.write('乘醉听萧鼓\r\n')
// ws.write('吟赏烟霞\r\n')
// ws.write('异日图将好景\r\n')
// ws.write('归去凤池夸\r\n')
// //关闭通道
// ws.close()//读取上面写入的文档,并进行输出
fs1.readFile('./fff.txt',(err,data)=>{if(err){console.log('读取失败')return}console.log(data)console.log(data.toString())
})
5.2 同步读取
// // 导入模块
const fs1 =require('fs');
//读取
let data=fs1.readFileSync('./fff.txt')
console.log(data)
console.log(data.toString())
5.3 流式读取
// // 导入模块
const fs1 =require('fs');
//流式读取
let rs=fs1.createReadStream('./fff.txt')
rs.on('data',data=>{console.log(data)
})
//读取完毕后,执行end回调
rs.on('end',()=>{console.log('读取完成')
})
7.文件重命名、移动
7.1 异步
// // 导入模块
const fs1 =require('fs');
//重命名
fs1.rename('./fff.txt','./poem.txt',err=>{if(err) throw errconsole.log('移动完成')
})
也可以用它来修改路径
7.2 同步
// // 导入模块
const fs1 =require('fs');
//重命名
fs1.renameSync('./poem.txt','./poemAA.txt')
8.删除
以下两个方法均有同步方法
8.1 unlink方法
// // 导入模块
const fs1 =require('fs');
//删除
fs1.unlink('./poemAA.txt',err=>{if(err){console.log('删除失败')return}console.log('删除成功')
})
8.2 rm方法
// // 导入模块
const fs1 =require('fs');
//删除
fs1.rm('./f_write.txt',err=>{if(err){console.log('删除失败')return}console.log('删除成功')
})
9.文件夹操作
9.1 文件夹创建mkdir
make directory
// // 导入模块
const fs1 =require('fs');
//删除
fs1.mkdir('./html',err=>{if(err){console.log('创建失败');return}console.log('创建成功')
})
递归创建
// // 导入模块
const fs1 =require('fs');
//删除
fs1.mkdir('./a/b/c',err=>{if(err){console.log('创建失败');return}console.log('创建成功')
})
//D:\web>node test.js
//创建失败
需要添加配置项
// // 导入模块
const fs1 =require('fs');
//删除
fs1.mkdir('./a/b/c',{recursive:true},err=>{if(err){console.log('创建失败');return}console.log('创建成功')
})
9.2 文件夹读取readdir
// // 导入模块
const fs1 =require('fs');
//删除
fs1.readdir('./vue3',(err,data)=>{if(err){console.log('创建失败');return}console.log('创建成功',data)
})
9.3 删除文件夹rmdir
rm——remove
系统建议使用rm来代替rmdir
(node:968) [DEP0147] DeprecationWarning: In future versions of Node.js, fs.rmdir(path, { recursive: true }) will be removed. Use fs.rm(path, { recursive: true }) instead
// // 导入模块
const fs1 =require('fs');
//删除
fs1.rmdir('./html',err=>{if(err){console.log('删除失败')return}console.log('删除成功')
})
若删除之后再次运行,则显示删除失败
递归删除
// // 导入模块
const fs1 =require('fs');
//想删除a文件夹下的所以目录
fs1.rmdir('./a',err=>{if(err){console.log('删除失败',err)return}console.log('删除成功')
})
// 导入模块
const fs1 =require('fs');
//想删除a文件夹下的所以目录
fs1.rmdir('./a',{recursive:true},err=>{if(err){console.log('删除失败')return}console.log('删除成功')
})
10.查看资源状态
// 导入模块
const fs1 =require('fs');
//stat方法 status缩写
fs1.stat('./2023_1/demo/动态表格视频.mp4',(err,data)=>{if(err){console.log('操作失败')return}console.log('操作成功',data)
})
- size 文件体积
- birthtime 创建时间
- mtime 最后修改时间
- atime 最后访问时间
- ctime 最后修改文件状态时间
- isFile 检测是否为文件
- isDirectory 检测是否为文件夹
11.路径补充说明
// // 导入模块
const fs1 =require('fs');
//使用绝对路径删除
fs1.rm('D:/web/test1',err=>{if(err){console.log('删除失败')return}console.log('删除成功')
})
D:/web/test1 最初的形式是D:\web\test1反斜杠,要改成 / 才可以进行操作
12.相对路径的bug解决
使用相对路径时的参照目录是命令行所在的目录,而不是当前文件的所在的所在目录。
__dirname 保存着 当前文件所在目录的绝对路径
13、path模块
1. path.resolve
拼接规范的逻辑路径
console.log(__dirname+'/index.html')
如下图所示 斜杠不一致
console.log(__dirname+'/index.html')
2.其他
console.log(path1.sep)
- 文件的绝对路径
console.log(__filename)
- 解析文件
let ts='D:\\web\\node\\test.js'
console.log(path1.parse(ts))
- 获取文件名
let ts='D:\\web\\node\\test.js'
console.log(path1.basename(ts))
- 获取路径的目录名、扩展名
let ts='D:\\web\\node\\test.js'
console.log(path1.dirname(ts))
console.log(path1.extname(ts))```
14.URL补充
网页中的 URL 主要分为两大类:相对路径与绝对路径
绝对路径可靠性强,而且相对容易理解,在项目中运用较多
相对路径
如果已经在最外层,再加…/还是不变,依旧是最外层
使用场景
三、http协议
hypertext Transfer Protocol
超文本传输协议
请求报文,响应报文
1.http报文
encode 编码 decode解码
1.1 请求报文结构
(1) 请求行
-
请求方法
-
url
用于定位服务器中资源
端口号有些情况下可以省略
以百度为例> 主机名=域名?
http版本号
(2)请求头
以键值对形式存在。
https提高交互的安全性。
(3)请求体
可以随意设置,与后端商量好
json形式的请求体
1.2 响应报文
(1)响应行
响应状态码
响应状态描述
(2)响应头
记录了与服务器相关的东西
(3)响应体
2. 网络基础概念
2.1 IP
就像发件写 收件地址
IP——寻找网络设备
将32位二进制转为10进制
作用
只要设备接入互联网,它都有自己的ip地址。
2.2 IP分类
(1)局域网IP
解决IP不够用的方法:共享IP
(2)广域网IP
连接互联网
局域网ip可以复用
(2)本地回环IP地址
2.1 端口
作用
主战区转换为后端:服务器
四、http 模块
1. 创建HTTP服务端
// 导入模块
const http=require('http');// 创建服务对象
// request,response是两个形参,分别是对请求,响应报文的封装
// 当服务接收到请求时,回调函数执行
const server=http.createServer((request,response)=>{// 设置响应体并结束响应体response.end('hello HTTP Server')
});// 监听端口,启动服务器
// 当服务启动成功以后执行此回调函数
m /
浏览器向服务器发送请求
注意事项
当响应体中有中文response.end('你好 HTTP')
时,该怎么解决。
// 导入模块
const http=require('http');// 创建服务对象
// request,response是两个形参,分别是对请求,响应报文的封装
// 当服务接收到请求时,回调函数执行
const server=http.createServer((request,response)=>{// 设置响应体并结束响应体response.setHeader('content-type','text/html;charset=utf-8')response.end('你好 HTTP')
});// 监听端口,启动服务器
// 当服务启动成功以后执行此回调函数
server.listen(9000,()=>{console.log('服务已经启动...')
});
若有端口被占用,则进入任务管理器或者资源监视器,结束任务。
2. 在浏览器中查看报文
请求头
提交表单后
<!DOCTYPE html>
<html lang="en">
<head><meta charset="UTF-8"><meta name="viewport" content="width=device-width, initial-scale=1.0"><title>Document</title>
</head>
<body><form action="http://127.0.0.1:9000" method="post"><input type="text" name="username"><input type="password" name="password"><input type="submit" name=""></form>
</body>
</html>
3. 提取请求报文的内容
(1)获取请求头
const http=require('http');// 当服务接收到请求时,回调函数执行
const server=http.createServer((request,response)=>{// 获取请求的方法console.log(request.method);// 获取请求的url(只包含路径与查询字符串)console.log(request.url)// 获取请求头console.log(request.headers)// 设置响应体并结束响应体response.setHeader('content-type','text/html;charset=utf-8')response.end('你好 HTTP')
});// 监听端口,启动服务器
// 当服务启动成功以后执行此回调函数
server.listen(9000,()=>{console.log('服务已经启动...')
});
(2) 获取请求体
提取路径和查询字符串
const http=require('http');const server=http.createServer((request,response)=>{let url=new URL(request.url,'http://127.0.0.1')console.log(url)response.setHeader('content-type','text/html;charset=utf-8')response.end('请求成功')
});server.listen(9000,()=>{console.log('服务已经启动...')
});//请求体的内容
URL {href: 'http://127.0.0.1/search?key=123',origin: 'http://127.0.0.1',protocol: 'http:',username: '',password: '',host: '127.0.0.1',hostname: '127.0.0.1',port: '',pathname: '/search',search: '?key=123',searchParams: URLSearchParams { 'key' => '123' },hash: ''
}
URL {href: 'http://127.0.0.1/favicon.ico',origin: 'http://127.0.0.1',protocol: 'http:',username: '',password: '',host: '127.0.0.1',hostname: '127.0.0.1',port: '',pathname: '/favicon.ico',search: '',searchParams: URLSearchParams {},hash: ''
}
查询路径和字符串
let url=new URL(request.url,'http://127.0.0.1')console.log('查询路径:',url.pathname)console.log('\n查询字符串:',url.searchParams)
取出字符串console.log(url.searchParams.get('key'))
(3)http请求练习
const http=require('http');// 创建服务
const server=http.createServer((request,response)=>{//获取请求的方法// let method =request.method// 简写:解构赋值let method =request.method//获取请求的路径let pathname =new URL(request.url,'http://127.0.0.1').pathname//设置响应头response.setHeader('content-type','text/html;charset=utf-8')//判断if(method === 'GET' && pathname === '/login'){response.end('登录页面')}else if(method === 'GET' && pathname === '/reg'){response.end('注册页面')}else{response.end('404')}//每次请求只能对应一次end方法
});server.listen(9000,()=>{console.log('服务已经启动...\n端口9000监听中...')
});
pathname 是一个 URL
对象
的属性,它并不是一个字符串。为了正确判断请求的路径,需要使用pathname.pathname
来获取路径字符串,然后进行比较。
注意路径别写成./login
4.设置响应报文
const http=require('http')
const server=http.createServer((request,response)=>{// 设置响应状态码response.statusCode=201;// 响应状态的描述,一般不用response.statusMessage='message'// 设置响应头,用来表示服务端的名字response.setHeader('ch','text/html;charset=utf-8')response.setHeader('Flavia','FFFFF')// 响应体,write方法与end方法相比,可多次调用response.write('love')response.write('\npatience')//每个请求必须有end方法,且只能有一个response.end()
})
server.listen(9000,()=>{console.log('服务已经启动...')
})
(1)http响应练习
搭建 HTTP 服务,响应一个 4 行 3 列的表格,并且要求表格有 隔行换色效果 ,且 点击 单元格能 高亮显示
const http=require('http')
const server=http.createServer((request,response)=>{response.end(`<!DOCTYPE html>
<html lang="en">
<head><meta charset="UTF-8"><meta name="viewport" content="width=device-width, initial-scale=1.0"><title>Document</title><style>td{padding:20px 40px;}table,td{border-collapse:collapse;}</style>
</head>
<body><table border=1><tr><td></td><td></td><td></td></tr><tr><td></td><td></td><td></td></tr><tr><td></td><td></td><td></td></tr><tr><td></td><td></td><td></td></tr></table>
</body>
</html>`)
})
server.listen(9000,()=>{console.log('服务已经启动...')
})
const http=require('http')
const server=http.createServer((request,response)=>{response.end(`<!DOCTYPE html>
<html lang="en">
<head><meta charset="UTF-8"><meta name="viewport" content="width=device-width, initial-scale=1.0"><title>Document</title><style>td{padding:10px 35px;}table,td{border-collapse:collapse;}table tr:nth-child(odd){background: #eee;}table tr:nth-child(even){background: #fff;}</style>
</head>
<body><table border=1><tr><td></td><td></td><td></td></tr><tr><td></td><td></td><td></td></tr><tr><td></td><td></td><td></td></tr><tr><td></td><td></td><td></td></tr></table><script>//获取所有tdlet tds=document.querySelectorAll('td')//遍历tds.forEach(item=>{item.οnmοuseοver=function(){this.style.background='#ccc'}item.οnmοuseοut=function(){this.style.background='#fff'}})</script>
</body>
</html>`)
})
server.listen(9000,()=>{console.log('服务已经启动...')
})
(2)优化
把html内容单独放一个文件
const http=require('http')
const fs=require('fs')
const server=http.createServer((request,response)=>{let html=fs.readFileSync(__dirname+'/index.html')response.end(html)
})
server.listen(9000,()=>{console.log('服务已经启动...')
})
<!DOCTYPE html>
<html lang="en">
<head><meta charset="UTF-8"><meta name="viewport" content="width=device-width, initial-scale=1.0"><title>Document</title><style>td{padding:10px 35px;}table,td{border-collapse:collapse;}table tr:nth-child(odd){background: #eee;}table tr:nth-child(even){background: #fff;}</style>
</head>
<body><table border=1><tr><td></td><td></td><td></td></tr><tr><td></td><td></td><td></td></tr><tr><td></td><td></td><td></td></tr><tr><td></td><td></td><td></td></tr></table><script>//获取所有tdlet tds=document.querySelectorAll('td')//遍历tds.forEach(item=>{item.onmouseover=function(){this.style.background='#ccc'}item.onmouseout=function(){this.style.background='#fff'}})</script>
</body>
</html>
(3)实现引入外部文件
想要独立css,js文件
根据请求路径区分请求结果,而不是每个文件都是html形式。尚硅谷第56集
5. 网页资源加载的基本过程
这两个请求关系不大
很多请求都是并行的。
执行着,遇到了,就去请求
6.静态资源,动态资源
静态资源:内容长时间不发生改变的资源
动态资源:内容经常更新的资源
7.搭建静态资源服务
需求
const http=require('http')
const fs=require('fs')
const server=http.createServer((request,response)=>{// 请求url路径let {pathname}=new URL(request.url,'http://127.0.0.1');// 拼接文件路径let filePath=__dirname+pathname;fs.readFile(filePath,(err,data)=>{if(err){response.statusCode=500;response.end('文件读取失败');return;}response.setHeader('content-type','text/html;charset=utf-8')response.end(data)})
})
server.listen(9000,()=>{console.log('服务已经启动...')
});
(1)静态资源目录(网站根目录)
HTTP 服务在哪个文件夹中寻找静态资源,那个文件夹就是 静态资源目录 ,也称之为 网站根目录
8.设置mime类型
媒体类型(通常称为 Multipurpose Internet Mail Extensions 或 MIME 类型 )是一种标准,用来表示文档、文件或字节流的性质和格式。
mime 类型结构: [type]/[subType]
例如: text/html text/css image/jpeg image/png application/json
HTTP 服务可以设置响应头 Content-Type 来表明响应体的 MIME 类型,浏览器会根据该类型决定如何处理资源.
//是常见文件对应的 mime 类型
html: 'text/html',
css: 'text/css',
js: 'text/javascript',
png: 'image/png',
jpg: 'image/jpeg',
gif: 'image/gif',
mp4: 'video/mp4',
mp3: 'audio/mpeg',
json: 'application/json'
对于未知的资源类型,可以选择
application/octet-stream
类型,浏览器在遇到该类型的响应时,会对响应体内容进行独立存储,也就是我们常见的 下载 效果
9.解决乱码问题
响应头的优先级更高
如果是html格式,则加上utf-8
10.完善错误处理
11.GET和POST请求场景
GET和POST请求的区别
- GET 和 POST 是 HTTP 协议请求的两种方式。
- GET 主要用来获取数据,POST 主要用来提交数据(上面两点两者都可以)
- GET 带参数请求是将参数缀到 URL 之后,在地址栏中输入 url 访问网站就是 GET 请求,
- POST 带参数请求是将参数放到请求体中
五、模块化
1.介绍
- 将一个复杂的程序文件依据一定规则(规范)拆分成多个文件的过程称之为 模块化。
- 其中拆分出的 每个文件就是一个模块 ,模块的内部数据是私有的,不过模块可以暴露内部数据以便其他模块使用。
好处:
- 防止命名冲突
- 高复用性
- 高维护性
2.暴露数据
D:\web\node\fn.jsfunction tie(){console.log('贴膜...')
}
//暴露数据
module.exports=tie
D:\web\node\index.js// 导入模块
const tie =require('./fn.js')
//调用函数
tie()
3.暴露方式
注意:
- module.exports 可以暴露 任意 数据
- 不能使用 exports = value 的形式暴露数据,模块内部 module 与 exports 的隐式关系
exports = module.exports = {} ,require 返回的是目标模块中 module.exports 的值
(1)module.exports = value
(2)exports.name = value
此处的代码改变则不生效
exports.tie=tie
exports.hi=hi
![在这里插入图片描述](https://img-blog.csdnimg.cn/50a1a2345eaf4fbfa7d47d227a7254aa.png)
console.log(module.exports===exports)
4.导入模块
使用require函数
- 对于自己创建的模块,导入时路径建议写 相对路径 ,且不能省略
./
和../
- js 和 json 文件导入时可以不用写后缀,如果js和json名字一样,则先检测js,c/c++编写的 node 扩展文件也可以不写后缀,但是一般用不到
- 如果导入其他类型的文件,会以 js 文件进行处理
- 如果导入的路径是个文件夹,则会 首先 检测该文件夹下 package.json 文件中 main 属性对应
的文件,如果存在则导入,反之如果文件不存在会报错。
如果 main 属性不存在,或者 package.json 不存在,则会尝试导入文件夹下的 index.js 和index.json ,
如果还是没找到,就会报错 - 导入 node.js 内置模块时,直接 require 模块的名字即可,无需加 ./ 和 …/
没有反应??
5.导入自定义模块的基本流程
- 将相对路径转为绝对路径,定位目标文件
- 缓存检测
- 读取目标文件代码
- 包裹为一个函数并执行(自执行函数)。通过 arguments.callee.toString() 查看自执行函数
- 缓存模块的值
- 返回 module.exports 的值
// 伪代码
function require(){// 转换为绝对路径let absolutePath=path.resolve(__dirname,file);// 缓存检测if(caches[absolutePath]){return caches[absolutePath]}//读取文件内容let code=fs.readFileSync(absolutePath).toString()// 包裹成函数,执行let module={}let exports=module.exports={}function (exports, require, module, __filename, __dirname) {const test={name:'尚硅谷'}module.exports=test;// 输出指向函数的代码体console.log(arguments.callee.toString())}(exports, require, module, __filename, __dirname)//缓存caches[absolutePath]=module.exports}
const m=require('./fn.js')
因为缓存,只执行一次
module.exports 、 exports 以及 require 这些都是 CommonJS 模块化规范中的内容。
而 Node.js 是实现了 CommonJS 模块化规范,二者关系有点像 JavaScript 与 ECMAScript
六、包管理工具
- 『包』英文单词是 package ,代表了一组特定功能的源码集合
- 包管理工具——管理『包』的应用软件,可以对「包」进行 下载安装 , 更新 , 删除 , 上传 等操作
- 借助包管理工具,可以快速开发项目,提升开发效率
- 包管理工具是一个通用的概念,很多编程语言都有包管理工具,所以 掌握好包管理工具非常重要
常用的包管理工具 npm yarn cnpm
1.npm
npm 全称 Node Package Manager ,翻译为中文意思是『Node 的包管理工具』
npm 是 node.js 官方内置的包管理工具,是 必须要掌握住的工具.
2. 初始化
创建一个空目录,然后以此目录作为工作目录 启动命令行工具 ,执行 npm init
终端显示如下
D:\web\node\files1>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 init` for definitive documentation on these fields
and exactly what they do.Use `npm install <pkg>` 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: (files1) npm1
version: (1.0.0)
description:
entry point: (index.js)
test command:
git repository:
keywords:
author: F
license: (ISC)
About to write to D:\web\node\files1\package.json:{"name": "npm1","version": "1.0.0","description": "","main": "index.js","scripts": {"test": "echo \"Error: no test specified\" && exit 1"},"author": "F","license": "ISC"
}
确认最后一步之后,自动创建一个json文件。
npm init 命令的作用是将文件夹初始化为一个『包』, 交互式创建 package.json 文件 package.json
是包的配置文件,每个包都必须要有 package.json
初始化过程中的注意事项:
package name ( 包名 )
不能使用中文、大写,默认值是 文件夹的名称 ,所以文件夹名称也不能使用中文和大写- version ( 版本号 )要求 x.x.x 的形式定义, x 必须是数字,默认值是 1.0.0
- ISC 证书与 MIT 证书功能上是相同的,关于开源证书扩展阅读http://www.ruanyifeng.com/blog/2011/05/how_to_choose_free_software_licenses.html
- package.json 可以手动创建与修改
- 使用
npm init -y
或者npm init --yes
极速创建 package.json
3.npm搜索,下载,使用包
(1)搜索(search)
- 命令行
npm s
- 网站搜索网址
https://www.npmjs.com/
(2)下载(install)
- 通过
npm install
和npm i
命令安装包
# 示例
npm install uniq
npm i uniq
安装后的目录如下
- 运行之后文件夹下会增加两个资源
node_modules 文件夹
存放下载的包 package-lock.json
包的锁文件 ,用来锁定包的版本
安装 uniq 之后, uniq 就是当前这个包的一个 依赖包 ,有时会简称为 依赖
比如我们创建一个包名字为 A,A 中安装了包名字是 B,我们就说 B 是 A 的一个依赖包 ,也会说A 依赖 B
(3)使用
// 导入uniq包
const uniq=require('uniq')
// 使用
let arr=[1,1,5,4,2,2,2,2,2,3,1]
const result =uniq(arr)
console.log(arr)
console.log(result)
由结果可见,此方法会改变原数组,并且结果经排序后输出。
4. require导入包
以下代码等价
但是第一种写法
const uniq=require('uniq')
更灵活就,因为:如果node_modules文件夹安装在了外层目录,根据如下规则:
- 在当前文件夹下 node_modules 中寻找同名的文件夹
- 在上级目录中下的 node_modules 中寻找同名的文件夹,直至找到磁盘根目录
还是能成功导入模块
5.开发与生产依赖
举个例子方便大家理解,比如说做蛋炒饭需要 大米 , 油 , 葱 , 鸡蛋 , 锅 , 煤气 , 铲子 等
其中 锅 , 煤气 , 铲子 属于开发依赖,只在制作阶段使用
而 大米 , 油 , 葱 , 鸡蛋 属于生产依赖,在制作与最终食用都会用到
所以 开发依赖 是只在开发阶段使用的依赖包,而 生产依赖 是开发阶段和最终上线运行阶段都用到的依赖包
(1) 生产环境与开发环境
- 开发环境是程序员 专门用来写代码 的环境,一般是指程序员的电脑,开发环境的项目一般 只能程序员自己访问
- 生产环境是项目 代码正式运行 的环境,一般是指正式的服务器电脑,生产环境的项目一般 每个客户都可以访问
(2)生产依赖
运行时也使用它
生产依赖 npm i -S
uniq或者npm i --save
uniq
-S 等效于 --save, -S 是默认选项
包信息保存在 package.json 中 dependencies 属性
(3)开发依赖
只在开发阶段使用
npm i -D
less
npm i --save-dev
less
-D 等效于 --save-dev
包信息保存在 package.json 中 devDependencies 属性
npm i --save uniq
npm i -D less
6.全局安装 -g
执行安装选项 -g 进行全局安装
全局安装完成之后就可以在命令行的任何位置运行 nodemon 命令
该命令的作用是 自动重启 node 应用程序
npm i -g nodemon
// 导入模块
const http=require('http');// 创建服务对象
const server=http.createServer((request,response)=>{response.setHeader('content-type','text/html;charset=utf-8')response.end('你好 nodemon')
});// 监听端口,启动服务器
// 当服务启动成功以后执行此回调函数
server.listen(9000,()=>{console.log('服务已经启动...')
});
当返回的内容改变时,不需要手动重启服务器,使用nodemon会自动重启,只需要在浏览器界面刷新即可
实时根据内容的改变,自动重启服务器
说明:
- 全局安装的命令不受工作目录位置影响
- 可以通过
npm root -g
可以查看全局安装包的位置 - 不是所有的包都适合全局安装 , 只有全局类的工具才适合,可以通过 查看包的官方文档来确定安装方式 ,这里先不必太纠结
7.修改window执行策略
目的:为解决 全局包无法使用的问题
如果可以使用nodemon命令,则不用管这些
- 方法一
- 方法二
修改vscode的终端窗口
8.环境变量Path
当输入QQ
命令时,会在当前目录查找以exe或者cmd结尾的文件,找不到则无法运行。
目的:不论在哪个目录都可以通过命令行找到QQ
步骤
- 把QQ所在目录复制
D:\Tencent\QQ\Bin
- 打开高级设置——环境变量
- 打开用户变量中的Path
4.重新打开一个命令行
Path 是操作系统的一个环境变量,可以设置一些文件夹的路径,在当前工作目录下找不到可执行文件时,就会在环境变量 Path 的目录中挨个的查找,如果找到则执行,如果没有找到就会报错。
9.安装包的所有依赖
在项目协作中有一个常用的命令就是 npm i
,通过该命令可以依据 package.json
和 packag-lock.json
的依赖声明安装项目依赖
node_modules 文件夹
。大多数情况都不会存入版本库
作用:下载 这个包(项目)安装的所有依赖。一般用于拉取远程仓库的代码,之后执行的。或者把文件发给别人,不用打包node_modules文件夹,人家npm i即可。
10.安装指定版本、删除包
项目中可能会遇到版本不匹配的情况,有时就需要安装指定版本的包
- 安装指定版本包
## 格式
npm i <包名@版本号>
## 示例
npm i jquery@1.11.2
2. 删除包
11.配置命令别名
- 点开package.json
- 找到script属性
- 配置别名
- 执行
//index.中的内容
console.log('hi')
start 别名比较特别,使用时可以省略 run
"scripts": {
"start": "node index.js"
},
npm start 是项目中常用的一个命令,一般用来启动项目 npm run 有自动向上级目录查找的特性,跟 require 函数也一样
对于陌生的项目,我们可以通过查看 scripts 属性来参考项目的一些操作
12.cnpm
- cnpm 是一个淘宝构建的 npmjs.com 的完整镜像,也称为『淘宝镜像』,网址
https://npmmirror.com/
- cnpm 服务部署在国内 阿里云服务器上 , 可以提高包的下载速度
- 官方也提供了一个全局工具包 cnpm ,操作命令与 npm 大体相同
(1)安装
npm install -g cnpm --registry=https://registry.npmmirror.com
(2)操作命令
初始化
功能 | 命令 |
---|---|
初始化 | cnpm init / cnpm init |
安装包 | cnpm i uniq |
安装包 | cnpm i -S uniq |
安装包 | cnpm i -D uniq |
安装包 | cnpm i -g nodemon |
安装项目依赖 | cnpm i |
删除 | cnpm r uniq |
(3)配置淘宝镜像
用 npm 也可以使用淘宝镜像,配置的方式有两种
- 直接配置
npm config set registry https://registry.npmmirror.com/
- 工具配置
使用 nrm 配置 npm 的镜像地址 npm registry manager(nrm作用:决定npm到哪去下载包)
- 安装 nrm
npm i -g nrm
- 修改镜像
nrm use taobao
- 检查是否配置成功(选做)
npm config list
- 检查 registry 地址是否为
https://registry.npmmirror.com/
, 如果 是 则表明成功
- 安装 nrm
补充说明:
建议使用第二种方式 进行镜像配置,因为后续修改起来会比较方便
还可以使用nrm use npm
切换回原来的
13.yarn
yarn 是由 Facebook 在 2016 年推出的新的 Javascript 包管理工具,官方网址:https://yarnpkg.com/
yarn 官方宣称的一些特点
- 速度超快:yarn 缓存了每个下载过的包,所以再次使用时无需重复下载。 同时利用并行下载以最大
- 化资源利用率,因此安装速度更快
- 超级安全:在执行代码之前,yarn 会通过算法校验每个安装包的完整性
- 超级可靠:使用详细、简洁的锁文件格式和明确的安装算法,yarn 能够保证在不同系统上无差异的
工作
(1)安装
使用 npm 安装 yarn
npm i -g yarn
(2)命令操作
功能 | 命令 |
---|---|
初始化 | yarn init 或 yarn init -y |
安装包 | yarn add uniq (生产依赖) |
安装包 | yarn add less --dev (开发依赖) |
安装包 | yarn global add nodemon (全局安装) |
删除包 | yarn remove uniq (删除项目依赖包) |
删除包 | yarn global remove nodemon (全局删除包) |
安装项目依赖 | yarn |
运行命令别名 | yarn <别名> (不需要添加 run) |
{"dependencies": {"uniq": "^1.0.1"},"name": "yarnf","version": "1.0.0","main": "index.js","license": "MIT","scripts": {"start": "node index.js"}
}
(3)全局安装中的注意点
因为没有配置环境变量,所以,通过yarn安装的-g等命令,没有作用,必须要配置环境变量。
- 找到yarn的位置
D:\nodejs\node_global\bin
- 手动配置环境变量
(4)配置淘宝镜像
可以通过如下命令配置淘宝镜像yarn config set registry https://registry.npmmirror.com/
可以通过 yarn config list 查看 yarn 的配置项
14. npm 和 yarn 选择
根据不同的场景进行选择
- 个人项目
如果是个人项目, 哪个工具都可以 ,可以根据自己的喜好来选择 - 公司项目
如果是公司要根据项目代码来选择,可以 通过锁文件判断 项目的包管理工具
npm 的锁文件为 package-lock.json
yarn 的锁文件为 yarn.lock
包管理工具 不要混着用,切记,切记,切记
15.发布包
(1) 创建与发布
我们可以将自己开发的工具包发布到 npm 服务上,方便自己和其他开发者使用,操作步骤如下:
-
创建文件夹,并创建文件 index.js, 在文件中声明函数,使用 module.exports 暴露
-
npm 初始化工具包,package.json 填写包的信息 (包的名字是唯一的)
-
注册账号 https://www.npmjs.com/signup
-
激活账号 ( 一定要激活账号 )
-
修改为官方的官方镜像 (命令行中运行
nrm use npm
) -
命令行下
npm login
填写相关用户信息 -
命令行下
npm publish
提交包
+表示发布成功
可以进行安装和使用了
(2) 更新包
后续可以对自己发布的包进行更新,操作步骤如下
-
更新包中的代码
-
测试代码是否可用
-
修改 package.json 中的版本号
-
运行
npm publish
发布更新的版本
(3)删除包
执行如下命令删除包npm unpublish --force
减号表示删除成功
删除包需要满足一定的条件,
https://docs.npmjs.com/policies/unpublish
- 你是包的作者
- 发布小于 24 小时
- 大于 24 小时后,没有其他包依赖,并且每周小于 300 下载量,并且只有一个维护者
16.包管理工具扩展介绍
在很多语言中都有包管理工具,比如:
语言 | 包管理工具 |
---|---|
PHP | composer |
Python | pip |
Java | maven |
Go | go mod |
JavaScript | npm/yarn/cnpm/other |
Ruby | rubyGems |
除了编程语言领域有包管理工具之外,操作系统层面也存在包管理工具,不过这个包指的是『 软件包 』
操作系统 | 包管理工具 | 网址 |
---|---|---|
Centos | yum | https://packages.debian.org/stable/ |
Ubuntu | apt | https://packages.ubuntu.com/ |
MacOS | homebrew | https://brew.sh/ |
Windows | chocolatey | https://chocolatey.org/ |
17.nvm的介绍与使用
全称:Node Version Manager ——用来管理node版本的工具,方便切换不同版本的node.js。
(1)下载安装
首先先下载 nvm,下载地址 https://github.com/coreybutler/nvm-windows/releases,
选择 nvm-setup.exe
下载即可(网络异常的小朋友可以在资料文件夹中获取)。
(2)常用命令
命令 | 说明 |
---|---|
nvm list available | 显示所有可以下载的 Node.js 版本 |
nvm list | 显示已安装的版本 |
nvm install 18.12.1 | 安装 18.12.1 版本的 Node.js |
nvm install latest | 安装最新版的 Node.js |
nvm uninstall 18.12.1 | 删除某个版本的 Node.js |
nvm use 18.12.1 | 切换 18.12.1 的 Node.js |
七、express框架
express 是一个基于 Node.js 平台的极简、灵活的 WEB 应用开发框架,官方网址:https://www.expressjs.com.cn/
简单来说,express 是一个封装好的工具包,封装了很多功能,便于我们开发 WEB 应用(HTTP 服务)
1.初体验
(1)express下载
express 本身是一个 npm 包,所以可以通过 npm 安装
npm init
npm i express
(2)创建代码
创建 JS 文件,键入如下代码
// 导入express
const express=require('express');
// 创建应用对象
const app=express();
// 创建路由
// 当浏览器发送请求的方法是get,且请求的路径是/home的话
// 就会执行后面的回调函数,为浏览器响应结果
app.get('/home',(req,res)=>{;// req是请求报文的封装对象,res是响应报文的封装对象。res.end('hi express');
})
// 监听端口,启动服务
app.listen(3000,()=>{console.log('服务已经启动,端口3000正在监听中...')
})
使用nodemon开启服务器。
当正确输入路径后
2.路由的使用
官方定义:路由确定了应用程序如何响应客户端对特定端点的请求
(1)路由的组成
一个路由的组成有 请求方法 , 路径 和 回调函数
组成
express 中提供了一系列方法,可以很方便的使用路由,使用格式:app.<method>(path,callback)
app.get('/',(req,res)=>{;// req是请求报文的封装对象,res是响应报文的封装对象。res.end('home');
})
(2)post请求
在地址栏回车键入的是get请求。所以无法获取login页面。
app.post('/login',(req,res)=>{res.end('login_post')
})
解决方法
<!DOCTYPE html>
<html lang="en">
<head><meta charset="UTF-8"><meta name="viewport" content="width=device-width, initial-scale=1.0"><title>Document</title>
</head>
<body><form method="post" action="http://127.0.0.1:3000/login"><button>发送</button></form>
</body>
</html>
点击“发送”按钮,即可跳转到login页面
(3)允许所有的请求方法
//js文件
const express=require('express');
const app=express();
app.all('/test',(req,res)=>{res.end('test_all')
})
app.listen(3000,()=>{console.log('服务已经启动,端口3000正在监听中...')
})
//html文件
<body><form method="post" action="http://127.0.0.1:3000/test"><button>发送</button></form>
</body>
使用app.all( )
这样不论是html文件的表单发送的get请求,还是通过网址栏发送的get请求,都能得到服务器返回的页面。
const express=require('express');
const app=express();
// 匹配所有方法
app.all('/test',(req,res)=>{res.end('test_all')
})
// 匹配404
app.all('*',(req,res)=>{// *表示匹配所有的路径res.end('404 Not Found')
})
app.listen(3000,()=>{console.log('服务已经启动,端口3000正在监听中...')
})
3.提取请求数据
(1)req属性
const express=require('express');
const app=express();
app.get('/request',(req,res)=>{// 原生操作console.log(req.method)console.log(req.url)console.log(req.httpVersion)console.log(req.headers)console.log('______________________________________')//express 操作console.log(req.path)console.log(req.query)console.log(req.ip)res.end('hello expression')
})app.listen(3000,()=>{console.log('服务已经启动,端口3000正在监听中...')
})
输出结果
GET
/request?a=100&b=200
1.1
{host: '127.0.0.1:3000',connection: 'keep-alive','cache-control': 'max-age=0','sec-ch-ua': '"Microsoft Edge";v="117", "Not;A=Brand";v="8", "Chromium";v="117"','sec-ch-ua-mobile': '?0','sec-ch-ua-platform': '"Windows"','upgrade-insecure-requests': '1','user-agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/117.0.0.0 Safari/537.36 Edg/117.0.2045.36',accept: 'text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.7','sec-fetch-site': 'none','sec-fetch-mode': 'navigate','sec-fetch-user': '?1','sec-fetch-dest': 'document','accept-encoding': 'gzip, deflate, br','accept-language': 'zh-CN,zh;q=0.9,en;q=0.8,en-GB;q=0.7,en-US;q=0.6',cookie: '_ga=GA1.1.1922259483.1687846523; _ga_R1FN4KJKJH=GS1.1.1688639930.11.1.1688640103.0.0.0'
}
______________________________________
/request
{ a: '100', b: '200' }
::ffff:127.0.0.1
(2)req方法
const express=require('express');
const app=express();
app.get('/request',(req,res)=>{//express 方法// 获取请求头// console.log(req.get())// 只获取host请求头console.log(req.get('host'))res.end('hello expression')
})app.listen(3000,()=>{console.log('服务已经启动,端口3000正在监听中...')
})输出结果
// 127.0.0.1:3000
4. 获取路由参数
路由参数指的是 URL 路径中的参数(数据
(1)占位符 :f
const express=require('express');
const app=express();
app.get('/:idf.html',(req,res)=>{res.setHeader('content-type','text/html;charset=utf-8')res.end('商品详情')
})app.listen(3000,()=>{console.log('服务已经启动,端口3000正在监听中...')
})
使用:id
占位符。
弹幕:商品页面就是固定的,然后去根据id请求数据库中的内容,渲染到当前页面
(2)获取路由参数req.params.f
通过req.params.idf
得到请求数据时的路由参数。
const express=require('express');
const app=express();
app.get('/:idf.html',(req,res)=>{res.setHeader('content-type','text/html;charset=utf-8')console.log(req.params.idf)res.end('商品详情')
})app.listen(3000,()=>{console.log('服务已经启动,端口3000正在监听中...')
})
(3)练习
为什么singers不能换成别的值
a.获取数据
下面两块代码只有占位符不同
const express=require('express');
//导入json文件
// 因为这个json文件里面有两层对象,所以使用解构赋值
const {singers}=require('./singers.json')
console.log(singers)
const app=express();
app.get('/single/:f.html',(req,res)=>{res.setHeader('content-type','text/html;charset=utf-8')let{f}=req.params;res.end('商品详情')
})app.listen(3000,()=>{console.log('服务已经启动,端口3000正在监听中...')
})
最后结果变成数组。
const express=require('express');
//导入json文件
// 因为这个json文件里面有两层对象,所以使用解构赋值
const {singers}=require('./singers.json')
// console.log(singers)
const app=express();
app.get('/singer/:id.html',(req,res)=>{res.setHeader('content-type','text/html;charset=utf-8')let{id}=req.params;// 在数组中寻找对应id的数据let result=singers.find(item=>{if(item.id===Number(id)){return true;}});console.log(result)res.end('result')
})app.listen(3000,()=>{console.log('服务已经启动,端口3000正在监听中...')
})
const express=require('express');
//导入json文件
// 因为这个json文件里面有两层对象,所以使用解构赋值
const {singers}=require('./singers.json')
// console.log(singers)
const app=express();
app.get('/singer/:f.html',(req,res)=>{res.setHeader('content-type','text/html;charset=utf-8')let{f}=req.params;// 在数组中寻找对应id的数据let result=singers.find(item=>{if(item.id===Number(f)){return true;}});console.log(result)res.end('result')
})app.listen(3000,()=>{console.log('服务已经启动,端口3000正在监听中...')
})
item就是数组中的某个对象
b.将数据展示到页面
const express=require('express');
//导入json文件
// 因为这个json文件里面有两层对象,所以使用解构赋值
const {singers}=require('./singers.json')
// console.log(singers)
const app=express();
app.get('/singer/:f.html',(req,res)=>{res.setHeader('content-type','text/html;charset=utf-8')let{f}=req.params;// 在数组中寻找对应id的数据let result=singers.find(item=>{if(item.id===Number(f)){return true;}});// console.log(result)//当请求的内容不在json数据里,就返回404if(!result){res.statusCode=404;res.end(`<h1>404 Not Found</h1>`)return;}res.end(`<!DOCTYPE html><html lang="en"><head><meta charset="UTF-8"><meta name="viewport" content="width=device-width, initial-scale=1.0"><title>Document</title></head><body><h2>${result.singer_name}</h2><img src='${result.singer_pic}' alt=""></body></html>`)
})app.listen(3000,()=>{console.log('服务已经启动,端口3000正在监听中...')
})
5.响应设置
(1)原生响应
const express=require('express');
const app=express();
app.get('/response',(req,res)=>{// 原生响应res.statusCode=404res.statusMessage='love ping'res.setHeader('xxx','yyy')res.write('hello')res.end('response')
})
app.listen(3000,()=>{console.log('服务已经启动,端口3000正在监听中...')
})
(2)内置响应
分开写
const express=require('express');
const app=express();
app.get('/response',(req,res)=>{res.status(500)res.set('chang','ping')res.send('你好')res.end('response')
})
app.listen(3000,()=>{console.log('服务已经启动,端口3000正在监听中...')
})
连着写
const express=require('express');
const app=express();
app.get('/response',(req,res)=>{res.status(500).set('haiying','changping').send('这很OK')
})
app.listen(3000,()=>{console.log('服务已经启动,端口3000正在监听中...')
})
res.send('你好')
方法会自动在响应头添加,防止中文展示乱码。
(3)其他响应
a.重定向
const express=require('express');
const app=express();
app.get('/other',(req,res)=>{//跳转响应res.redirect('https://mp.csdn.net/mp_blog/manage/article?spm=1000.2115.3001.5448')
})
app.listen(3000,()=>{console.log('服务已经启动,端口3000正在监听中...')
})
b.下载
在网址输入路径后,自动下载软件
const express=require('express');
const app=express();
app.get('/other',(req,res)=>{//下载响应res.download(__dirname+'/package.json')
})
app.listen(3000,()=>{console.log('服务已经启动,端口3000正在监听中...')
})
c.JSON响应
const express=require('express');
const app=express();
app.get('/other',(req,res)=>{//JSON响应res.json({'fang':'haiying','age':'19'})
})
app.listen(3000,()=>{console.log('服务已经启动,端口3000正在监听中...')
})
d.响应文件内容
const express=require('express');
const app=express();
app.get('/other',(req,res)=>{// 响应文件内容res.sendFile(__dirname+'/index.html')
})
app.listen(3000,()=>{console.log('服务已经启动,端口3000正在监听中...')
})
<!DOCTYPE html>
<html lang="en">
<head><meta charset="UTF-8"><meta name="viewport" content="width=device-width, initial-scale=1.0"><title>Document</title>
</head>
<body><div style="color: pink;">你好呀</div>
</body>
</html>
6.中间件
- 什么是中间件
- 中间件(Middleware)
本质是一个回调函数
- 中间件函数 可以像路由回调一样访问 请求对象(request) , 响应对象(response)
- 中间件(Middleware)
- 中间件的作用
中间件的作用 就是 使用函数封装公共操作,简化代码 - 中间件的类型
全局中间件
路由中间件
(1)全局中间件
需求:记录每个请求的url和IP地址
a. 原始方法
const express=require('express');
const fs=require('fs')
const path=require('path')
const app=express();
app.get('/home',(req,res)=>{ // 获取url和iplet {url,ip}=req;console.log(url+' '+ip)//将信息保存在文件中fs.appendFileSync(path.resolve(__dirname,'./fhy.log'),`${url}${ip}\r\n`)res.send('主页')
})
app.get('/admin',(req,res)=>{res.send('后台')
})
app.all('*',(req,res)=>{res.send(`404 Not Found`)
})
app.listen(3000,()=>{console.log('服务已经启动,端口3000正在监听中...')
})
但是目前只对主页一个路径起作用
b.中间件方法
整个效果就是先执行中间件函数,然后通过next执行路由函数。
const express = require('express');
const fs = require('fs')
const path = require('path')
const app = express();// 声明中间件函数
//next是内部函数,后续会指向路由或者中间件
function recordMiddleware(req, res, next) {// 获取url和iplet { url, ip } = req;console.log(url + ' ' + ip)//将信息保存在文件中fs.appendFileSync(path.resolve(__dirname, './fhy.log'), `${url}${ip}\r\n`)// 调用next函数next()
}//使用中间件函数
app.use(recordMiddleware)app.get('/home', (req, res) => {res.send('主页')
})
app.get('/admin', (req, res) => {res.send('后台')
})
app.all('*', (req, res) => {res.send(`404 Not Found`)
})
app.listen(3000, () => {console.log('服务已经启动,端口3000正在监听中...')
})
文件内容
(2)路由中间件——不是很懂
如果 只需要对某一些路由进行功能封装 ,则就需要路由中间件。
需求:
const express = require('express');
const fs = require('fs')
const path = require('path')
const app = express();// 声明中间件函数
//next是内部函数,后续会指向路由或者中间件
let checkCodeMid=(req, res, next)=> {
// 判断url的code参数是否等于521if(req.query.code==='521'){next()}else{res.send('暗号错误')}// 调用next函数next()
}//使用中间件函数
app.use(checkCodeMid)// 以下都是路由回调
app.get('/home', (req, res) => {res.send('主页')
})//把中间件函数放到受约束的函数中
app.get('/admin', checkCodeMid,(req, res) => {res.send('后台')
})
app.get('/set',checkCodeMid, (req, res) => {res.send('设置页面')
})
app.all('*', (req, res) => {res.send(`404 Not Found`)
})
app.listen(3000, () => {console.log('服务已经启动,端口3000正在监听中...')
})
(3)静态资源中间件
静态资源目录(网站根目录)
浏览器发送给服务端请求后,服务端到哪个目录下去找对应的文化,则那个文件的文件夹就是静态资源目录。
const express = require('express');
const app = express();
//静态资源中间件的设置,将当前文件夹下的public目录作为网站的根目录
//express.static的返回结果是中间件函数
app.use(express.static(__dirname + '/public')); // 这个参数就是静态资源文件夹的路径app.get('/home',(req,res)=>{
res.send('首页');
});app.listen(3000,()=>{
console.log('3000 端口启动....');
});
public要放在init文件夹下面
a.注意事项
-
index.html 文件为默认打开的资源(即不输入/index.html,界面默认显示此文件)
-
如果静态资源与路由规则同时匹配,谁先匹配谁就响应。
-
路由一般响应动态资源,静态资源中间件响应静态资源
b.练习
目的:使手机也可以通过局域网看到静态资源
接下来是手机显示
7.获取请求体数据
使用 body-parser
包处理请求体
需求如下:
通过按钮发送post请求数据,获取请求体
在npm搜索包,并选择路由中间件的使用方式
// create application/json parser
var jsonParser = bodyParser.json()// create application/x-www-form-urlencoded parser
var urlencodedParser = bodyParser.urlencoded({ extended: false })
当中间件函数执行完毕后,会向req对象上,添加一个body属性。
const express =require('express');
const app=express();
//导入解析请求体的包
const bodyParser=require('body-parser')
//解析queryString格式 请求体的中间件
const urlEncodeParser=bodyParser.urlencoded({extended:false})
//创建路由规则
app.get('/login',(req,res)=>{// 响应文件内容res.sendFile(__dirname+'/form.html')
})
app.post('/login',urlEncodeParser,(req,res)=>{if(req.body){res.send(req.body)}else{res.send('数据获取失败')}
})app.listen(3000,()=>{console.log('服务已经启动...')
})
<body><!-- <form action="http://127.0.0.1:3000/login" method="post"> --><!-- 代码可以简写,只写路径 --><form action="/login" method="post">用户名:<input type="text" name="username"><br>密码:<input type="password" name="password"><br><button>登录</button></form>
</body>
8.防盗链
(1)介绍
防止外部网站盗用网站资源。
禁止该域名之外的其他网站访问这个资源
实现防盗链效果
在请求头中有一个 referer
包含了发送请求的网站的协议、域名、端口。
如果referer不对,则返回404结果。
(2)练习
正常展示静态资源,使用localhost
和127.0.0.1
都可以访问
const express =require('express');
const app=express();
app.use(express.static(__dirname+'/public'))app.listen(3000,()=>{console.log('服务已经启动...')
})
添加防盗链效果:只有127.0.0.1域名可以访问。
const express = require('express');
const app = express();
//添加中间件,实现效果
app.use((req, res, next) => {// 获取refererlet referer = req.get('referer')// 判断一下是否有refererif (referer) {// 实例化let url = new URL(referer)// 获取hostname域名let hostname=url.hostnameconsole.log(hostname)if(hostname!=='127.0.0.1'){res.end('404')return}}next()
})
app.use(express.static(__dirname + '/public'))app.listen(3000, () => {console.log('服务已经启动...')
})
9.路由模块化
- express 中的 Router 是一个完整的中间件和路由系统,可以看做是一个小型的 app 对象。
- 对路由进行模块化,更好的管理路由
- router就相当于小型的app对象,就是把原来的
app.get( )、app.post( )
变成router.get( ) 、router.post()
等。
(1)未导入时
没有路由组件时
//main.js文件
const express = require('express');
const app = express();
//引入子路由文件
// const homeRouter=require('./routers/homeRouter.js')
//设置和使用中间件
// app.use(homeRouter)app.all('*',(req,res)=>{res.send('<h1>404</h1>')
})
app.listen(3000,()=>{
console.log('3000 端口启动....');
})
(2)导入一个后
创建的路由文件的路径D:\web\node\init\routers\homeRouter.js
homeRouter.js
//1. 导入 express
const express = require('express');
//2. 创建路由器对象
const router = express.Router();
//3. 在 router 对象身上添加路由
router.get('/', (req, res) => {
res.send('首页');
})
router.get('/search', (req, res) => {
res.send('内容搜索');
});
//4. 暴露
module.exports = router;
(3) 两个都导入后
//1. 导入 express
const express = require('express');
//2. 创建路由器对象
const router = express.Router();
//3. 在 router 对象身上添加路由
router.get('/admin', (req, res) => {
res.send('管理页面');
})//4. 暴露
module.exports = router;
(4)设置路由前缀
修改后的user.js路由模块。
var express = require('express');
var router = express.Router();/* GET users listing. */
router.get('/', function(req, res, next) {res.send('respond with a resource');
});router.get('/test', function(req, res, next) {res.send('Flavia测试');
});module.exports = router;
因为在app.js中设置了路由前缀app.use('/users', usersRouter);
,所以得输入 /users/test
成功显示!
10.模版引擎
- 模板引擎是分离
用户界面和业务数据
的一种技术。 - EJS 是一个高效的 Javascript 的模板引擎。
- 官网: https://ejs.co/
- 中文站:https://ejs.bootcss.com/
(1)理解
也就是分离服务器端的js和html
如下面的代码
const express=require('express');
//导入json文件
// 因为这个json文件里面有两层对象,所以使用解构赋值
const {singers}=require('./singers.json')
// console.log(singers)
const app=express();
app.get('/singer/:f.html',(req,res)=>{res.setHeader('content-type','text/html;charset=utf-8')let{f}=req.params;// 在数组中寻找对应id的数据let result=singers.find(item=>{if(item.id===Number(f)){return true;}});// console.log(result)//当请求的内容不在json数据里,就返回404if(!result){res.statusCode=404;res.end(`<h1>404 Not Found</h1>`)return;}res.end(`<!DOCTYPE html><html lang="en"><head><meta charset="UTF-8"><meta name="viewport" content="width=device-width, initial-scale=1.0"><title>Document</title></head><body><h2>${result.singer_name}</h2><img src='${result.singer_pic}' alt=""></body></html>`)
})app.listen(3000,()=>{console.log('服务已经启动,端口3000正在监听中...')
})
我们要做的就是分离这部分
(2)初体验
注意 nrm use taobao 使用淘宝镜像。
下载安装EJS npm i ejs
在ejs文件夹下装的,但是node_modules文件夹在ejs文件夹外部。
目的:我们要达到拼接效果,像下面代码所示,但是不想让str2变量和其他混在一起,这样不利于后续处理
const ejs=require('ejs')
let str1 ='你好'
let str2=`ejs${str1}`
console.log(str2)
<%= %>
是ejs解析内容的标记,作用是输出当前表达式的执行结果。
ejs会从后面传入的数据中找<%= %>
内的内容,并利用对应属性的值替换掉<%= %>
内的内容。
下面不同的写法,效果相同
const ejs=require('ejs')
let result=ejs.render('你好<%= str %>',{str:'ejs'})
console.log(result)
const ejs=require('ejs')
h='你好<%= str %>'
str='ejs'let result=ejs.render(h,{str:str})
console.log(result)
const fs=require('fs')
const ejs=require('ejs')
hi=fs.readFileSync('./str.html').toString()
let result=ejs.render(hi,{str:'ejs'})
console.log(result)
str.html的内容
有点晕了
const fs=require('fs')
const ejs=require('ejs')let ejsHtml=fs.readFileSync('./ejs.html').toString()
let str='ejs'
weather='今天天气不错'
let result=ejs.render(ejsHtml,{str,weather,ejsHtml})
console.log(result)
<!DOCTYPE html>
<html lang="en">
<head><meta charset="UTF-8"><meta name="viewport" content="width=device-width, initial-scale=1.0"><title>Document</title>
</head>
<body><h2>你好<%= str %></h2><%= weather %>
</body>
</html>
(3)列表渲染
原生js实现
const palyer=['喜羊羊','美羊羊','懒羊羊','灰太狼']
let str='<ul>'
palyer.forEach(item=>{str+=`<li>${item}</li>`
})
str+='</ul>'
console.log(str)
使用ejs
(4)条件渲染
11.express-generator
安装命令npm install -g express-generator
创建命令express -e 名字
所创建的文件夹目录
-
安装依赖
-
装好后的目录
-
通过
npm start
运行文件 -
进入
错误处理的两种方式
12.文件上传
(1)上传效果实现
在D:\web\node\init\ejs\generator\routes\index.js
下的内容
var express = require('express');
var router = express.Router();/* GET home page. */
router.get('/', function(req, res, next) {res.render('index', { title: 'Express' });
});
//显示网页的表单
router.get('/upload',(req,res)=>{res.render('upload')
})
//处理文件上传
router.post('/upload',(req,res)=>{res.send('成功')
})module.exports = router;
在D:\web\node\init\ejs\generator\views\upload.ejs
下的内容
<!DOCTYPE html>
<html lang="en"><head><meta charset="UTF-8"><meta name="viewport" content="width=device-width, initial-scale=1.0"><title>文件上传</title>
</head><body><h2>文件上传</h2><hr><!-- 文件上传时,必不可少的设置enctype --><form action="/upload" method="post" enctype="multipart/form-data">用户名:<input type="text" name="username"><br>头像:<input type="file" name="upload"><br><button>点击提交</button></form></body></html>
点击提交后的内容
(2)对上传文件进行处理
下载formidable
包
npm i formidable
有bug
已经解决!!!!!!!!!!
看到弹幕说换个版本
然后试了一下npm i formidable@2
没问题了
var express = require('express');
// const formidable =require('formidable')var router = express.Router();
const formidable = require('formidable');
/* GET home page. */
router.get('/', function(req, res, next) {res.render('index', { title: 'Express' });
});
//显示网页的表单
router.get('/upload',(req,res)=>{res.render('upload')
})
//处理文件上传
router.post('/upload',(req,res)=>{// 创建form表单const form = formidable({multiples:true});
// 解析请求报文form.parse(req, (err, fields, files) => {if (err) {next(err);return;}console.log('00')console.log(fields)console.log(files)res.send('ok')});
})module.exports = router;
<!DOCTYPE html>
<html lang="en"><head><meta charset="UTF-8"><meta name="viewport" content="width=device-width, initial-scale=1.0"><title>文件上传</title>
</head><body><h2>文件上传</h2><hr><!-- 文件上传时,必不可少的设置enctype --><form action="/upload" method="post" enctype="multipart/form-data">用户名:<input type="text" name="username"><br>头像:<input type="file" name="upload"><br><button>点击提交</button></form></body></html>
(3)保存文件
router.post('/upload',(req,res)=>{// 创建form表单const form = formidable({multiples:true,//设置文件的保存路径uploadDir:__dirname+'/../public/images',// 保存文件后缀名keepExtensions:true});
// 解析请求报文form.parse(req, (err, fields, files) => {if (err) {next(err);return;}console.log(fields)console.log(files)res.send('ok')});
})
让用户获得图像
let url='/images/'+files.upload.newFilename// 学完数据库,将此数据保存在数据库中res.send(url)
注意大小写newFilename
是对的
八、接口
1.简介
- 接口是
前后端通信的桥梁
- 简单理解:一个接口就是 服务中的一个路由规则 ,
根据请求 响应结果
。(可以接收客户端发来的请求,并且给客户端响应结果,一般是json格式) - 接口的英文单词是 API (Application Program Interface),所以有时也称之为
API 接口
- 这里的接口指的是『数据接口』, 与编程语言(Java,Go 等)中的接口语法不同。
2.开发与调用
- 大多数接口都是由 后端工程师 开发的, 开发语言不限
- 一般情况下接口都是由 前端工程师 调用的,但有时 后端工程师也会调用接口 ,比如短信接口,支付接口等。
3.接口的组成
请求方法
接口地址(URL)
请求参数
响应结果
4. RESTful API
RESTful API 是一种特殊风格的接口,主要特点有如下几个:
- URL 中的路径表示 资源 ,路径中不能有 动词 ,例如 create , delete , update 等这些都不能有
- 操作资源要与 HTTP 请求方法 对应
- 操作结果要与 HTTP 响应状态码 对应
规则示例:
操作 | 请求类型 | URL | 返回 |
---|---|---|---|
新增歌曲 | POST | /song | 返回新生成的歌曲信息 |
删除歌曲 | DELETE | /song/10 | 返回一个空文档 |
修改歌曲 | PUT | /song/10 | 返回更新后的歌曲信息 |
修改歌曲 | PATCH | /song/10 | 返回更新后的歌曲信息 |
获取所有歌曲 | GET | /song | 返回歌曲列表数组 |
获取单个歌曲 | GET | /song/10 | 返回单个歌曲信息 |
扩展阅读:https://www.ruanyifeng.com/blog/2014/05/restful_api.html
5.json-server
json-server 本身是一个 JS 编写的工具包,可以快速搭建 RESTful API 服务
官方地址: https://github.com/typicode/json-server
- 全局安装 json-server
npm i -g json-server
- 创建 JSON 文件(db.json),编写基本结构
{
"song": [
{ "id": 1, "name": "干杯", "singer": "五月天" },
{ "id": 2, "name": "当", "singer": "动力火车" },
{ "id": 3, "name": "不能说的秘密", "singer": "周杰伦" }
]
}
- 以 JSON 文件
所在文件夹
作为工作目录 ,执行如下命令
json-server --watch db.json
默认监听端口为 3000
6.接口测试工具
- apipost https://www.apipost.cn/ (中文)
- apifox https://www.apifox.cn/ (中文)
- postman https://www.postman.com/ (英文)
7.apiPost
(1)前缀
- 设置前缀
(2)新增歌曲
会自动添加id,我们不用写
(3)删除歌曲
需要传入对应id
(4)更新数据
(5)传入公共参数
新建一个目录,把相关接口都放进去。
8.postman
九、会话控制
- HTTP 是一种无状态的协议,它
没有办法区分多次的请求是否来自于同一个客户端, 无法区分用户。
- 而产品中又大量存在的这样的需求,所以我们需要通过 会话控制 来解决该问题
- 常见的会话控制技术有三种:
- cookie
- session
- token
1.cookie
- cookie 是 HTTP 服务器发 送到用户浏览器 并保存在
本地
的一小块数据 - cookie 是按照域名划分保存的
简单示例:
域名 | cookie |
---|---|
www.baidu.com | a=100; b=200 |
www.bilibili.com | xid=1020abce121; hm=112411213 |
jd.com | x=100; ocw=12414cce |
(1)特点
- 浏览器向服务器发送请求时,会自动将
当前域名下
可用的 cookie 设置在请求头中,然后传递给服务器。 - 请求头的名字也叫 cookie ,所以将 cookie 理解为一个 HTTP 的请求头也是可以的
(2)运行流程
填写账号和密码校验身份,校验通过后下发 cookie
有了 cookie 之后,后续向服务器发送请求时,就会自动携带 cookie
(3)浏览器操作 cookie
浏览器操作 cookie 的操作,使用相对较少,大家了解即可
-
禁用所有 cookie
-
删除 cookie
-
查看 cookie
(4)cookie 的代码操作
express 中可以使用 cookie-parser 进行处理
const express =require('express');
//1. 安装 cookie-parser npm i cookie-parser
//2. 引入 cookieParser 包
const cookieParser = require('cookie-parser');
const app = express();
//3. 设置 cookieParser 中间件
app.use(cookieParser());//4-1 设置 cookie
app.get('/set-cookie', (request, response) => {
// 不带时效性
response.cookie('username','wangwu');
// 带时效性
//maxAge是生命周期
//只要生命周期没到时间,就算浏览器关闭,也依旧存在
response.cookie('email','23123456@qq.com', {maxAge: 5*60*1000 });
//响应
response.send('Cookie的设置');
});//4-2 读取 cookie
app.get('/get-cookie', (request, response) => {
//读取 cookie
console.log(request.cookies);
//响应体
response.send('Cookie的读取');
});//4-3 删除cookie
app.get('/delete-cookie', (request, response) => {
//删除
response.clearCookie('username');
//响应
response.send('cookie 的清除');
});
//4. 启动服务
app.listen(3000, () => {
console.log('服务已经启动....');
});
不同浏览器中的 cookie 是相互独立的,不共享
2. session
代码操作
const express = require('express');
//1. 安装包 npm i express-session connect-mongo
//2. 引入 express-session connect-mongo
const session = require("express-session");
const MongoStore = require('connect-mongo');
const app = express();
//3. 设置 session 的中间件
app.use(session({
name: 'sid', //设置cookie的name,默认值是:connect.sid
secret: 'atguigu', //参与加密的字符串(又称签名)
saveUninitialized: false, //是否为每次请求都设置一个cookie用来存储session的id
resave: true, //是否在每次请求时重新保存session
store: MongoStore.create({
mongoUrl: 'mongodb://127.0.0.1:27017/project' //数据库的连接配置
}),
cookie: {
httpOnly: true, // 开启后前端无法通过 JS 操作
maxAge: 1000 * 300 // 这一条 是控制 sessionID 的过期时间的!!!
},
}))
//创建 session
app.get('/login', (req, res) => {
//设置session
req.session.username = 'zhangsan';
req.session.email = 'zhangsan@qq.com'
res.send('登录成功');
})
//获取 session
app.get('/home', (req, res) => {
console.log('session的信息');
console.log(req.session.username);
if (req.session.username) {
res.send(`你好 ${req.session.username}`);
}else{
res.send('登录 注册');
}
})
//销毁 session
app.get('/logout', (req, res) => {
//销毁session
// res.send('设置session');
req.session.destroy(() => {
res.send('成功退出');
});
});
app.listen(3000, () => {
console.log('服务已经启动, 端口 ' + 3000 + ' 监听中...');
});
有这个标识后,前端js便不能对cookie进行操作。否则,不安全,如使用document.cookie
。
session 和 cookie 的区别
- 存在的位置 :cookie——浏览器端 session——服务端
- 安全性: cookie 是以明文的方式存放在客户端的,安全性
相对
较低 session 存放于服务器中,所以安全性 相对 较好- 网络传输量 :cookie 设置内容过多会增大报文体积, 会影响传输效率 。session 数据存储在服务器,只是通过 cookie 传递 id,所以不影响传输效率
- 存储限制 浏览器限制单个 cookie 保存的数据不能超过 4K ,且单个域名下的存储数量也有限制session 数据存储在服务器中,所以没有这些限制.
3. token
- token 是服务端生成并返回给 HTTP 客户端的一串
加密字符串
, token 中保存着 用户信息 - 作用:实现会话控制,可以识别用户的身份,主要用于移动端 APP。
- token 的工作流程
- 填写账号和密码校验身份,校验通过后响应 token,token 一般是在响应体中返回给客户端的
特点
- 服务端压力更小
数据存储在客户端 - 相对更安全
数据加密
可以避免 CSRF(跨站请求伪造) - 扩展性更强
服务间可以共享
增加服务节点更简单
JWT(JSON Web Token )是目前最流行的跨域认证解决方案,可用于基于 token 的身份验证
JWT 使 token 的生成与校验更规范
我们可以使用 jsonwebtoken 包 来操作 token
//导入 jsonwebtokan
const jwt = require('jsonwebtoken');
//创建 token
// jwt.sign(数据, 加密字符串, 配置对象)
let token = jwt.sign({
username: 'zhangsan'
}, 'atguigu', {
expiresIn: 60 //单位是 秒
})
//解析 token
jwt.verify(token, 'atguigu', (err, data) => {
if(err){
console.log('校验失败~~');
return
}
console.log(data);
})
4. 本地域名
所谓本地域名就是 只能在本机使用的域名 ,一般在开发阶段使用
十、项目上线
创建.gitignore
,并写入
HTTPS
尚硅谷
相关文章:
Node.js 学习笔记
小插件Template String Converter 当输入${}时,自动为其加上 反引号 一、node入门 node.js是什么 node的作用 开发服务器应用 开发工具类应用 开发桌面端应用 1.命令行工具 命令的结构 常用命令 切换到D盘——D: 查看D盘目录——dir 切换工作目录——c…...
RabbitMQ之发布确认高级
RabbitMQ之发布确认高级 一、发布确认 SpringBoot 版本1.1 确认机制方案1.2 代码架构图1.3 配置文件1.4 添加配置类1.5 消息生产者1.6 回调接口1.7 消息消费者1.8 结果分析 二、回退消息2.1 Mandatory 参数2.2 消息生产者代码2.3 回调接口2.4 结果分析 三、备份交换机3.1 代码架…...
lv5 嵌入式开发-10 信号机制(下)
目录 1 信号集、信号的阻塞 2 信号集操作函数 2.1 自定义信号集 2.2 清空信号集 2.3 全部置1 2.4 将一个信号添加到集合中 2.5 将一个信号从集合中移除 2.6 判断一个信号是否在集合中 2.7 设定对信号集内的信号的处理方式(阻塞或不阻塞) 2.8 使进程挂起(…...
【postgresql】 ERROR: multiple assignments to same column “XXX“
Cause: org.postgresql.util.PSQLException: ERROR: multiple assignments to same column "XXX"; bad SQL grammar []; nested exception is org.postgresql.util.PSQLException: ERROR: multiple assignments to same column "XXX"; 原因:or…...
一文读懂Llama 2(从原理到实战)
简介 Llama 2,是Meta AI正式发布的最新一代开源大模型。 Llama 2训练所用的token翻了一倍至2万亿,同时对于使用大模型最重要的上下文长度限制,Llama 2也翻了一倍。Llama 2包含了70亿、130亿和700亿参数的模型。Meta宣布将与微软Azure进行合…...
完整指南:如何使用 Node.js 复制文件
文件拷贝指的是将一个文件的数据复制到另一个文件中,使目标文件与源文件内容一致。Node.js 提供了文件系统模块 fs,通过该模块可以访问文件系统,实现文件操作,包括拷贝文件。 Node.js 中文件拷贝方法 在 Node.js 中,有…...
ElementUI - 主页面--动态树右侧内容管理
一.左侧动态树 1.定义组件 ①样式&数据处理 <template><el-menu class"el-menu-vertical-demo" background-color"#334157"text-color"#fff" active-text-color"#ffd04b" :collapse"collapsed" router :def…...
全国排名前三的直播公司无锋科技入驻天府蜂巢成都直播产业基地
最近,全国排名前三的直播公司——无锋科技,正式宣布入驻位于成都的天府蜂巢直播产业基地,这一消息引起了业内人士的高度关注。成都直播产业基地一直是中国直播产业的重要地标之一,其强大的技术和资源优势为众多直播公司提供了广阔…...
机器人中的数值优化|【五】BFGS算法非凸/非光滑处理
机器人中的数值优化|【五】BFGS算法的非凸/非光滑处理 往期内容回顾 机器人中的数值优化|【一】数值优化基础 机器人中的数值优化|【二】最速下降法,可行牛顿法的python实现,以Rosenbrock function为例 机器人中的数值优化|【三】无约束优化࿰…...
ESP32S3的MPU-6050组件移植教程
前言 (1)实习公司要搞ESP32BOX的驱动移植,所有资料自己找还是比较折磨人的现在我分享几个官方的组件移植资料: <1>Find the most exciting ESP-IDF components(ESP32的官方组件都可以在里面查,按照他…...
excel筛选后求和
需要对excel先筛选,后对“完成数量”进行求和。初始表格如下: 一、选中表内任意单元格,按ctrlshiftL,开启筛选 二、根据“部门”筛选,比如选择“一班” 筛选完毕后,选中上图单元格,然后按alt后&…...
pyspark 检测任务输出目录是否空,避免读取报错
前言 在跑调度任务时候,有时候子任务需要依赖前置任务的输出,但类似读取 Parquet 或者 Orc 文件时,如果不判断目录是否为空,在输出为空时会报错,所以需要 check 一下,此外Hadoop通常在写入数据时会在目录中…...
「网页开发|前端开发|Vue」10 vuex模块化:将数据划分成不同modules分别管理
本文主要介绍如何使用vuex的modules将状态数据根据不同模块进行划分并分别管理以及如何使用mapGetters快速将状态管理中的数据导入成local变量。 文章目录 本系列前文传送门一、场景说明二、使用modules划分不同模块三、使用Getters获取状态管理数据Getter传参mapGetters 辅助…...
苹果CMS插件-苹果CMS全套插件免费
网站内容的生成和管理对于网站所有者和内容创作者来说是一个挑战。有一些强大的工具可以帮助您轻松地解决这些问题。苹果CMS插件自动采集插件、采集发布插件以及采集伪原创发布插件,是这些工具之一。它们不仅可以极大地节省您的时间和精力,还可以提高您网…...
域环境介绍
一、概述 内网也指局域网,指的是某个区域由多台计算机互连而成的计算机组,范围通常在数千米以内,在局域网中,可以实现文件管理,应用软件共享,打印机共享、工作组内的日程安排、电子邮件和传真通信服务等&a…...
地球同步静止轨道上的中国卫星
3万6千公里地球同步静止轨道上的中国控制的卫星(包括香港属非国产平台卫星、外国属中国平台卫星),共80颗;截止到2023年8月3日,共有563颗在轨卫星。 号定位名称发射时间用途重量1141.1W中星1C(FH2C)2015.12.10DFH4平台…...
HAProxy代理TCP(使用HAProxy 为TiDB-Server 做负载均衡)
目录 一、使用HAProxy 为TiDB-Server 做负载均衡环境1、创建文件夹2、配置haproxy.cfg3、创建 docker-compose.yaml 文件haproxy.cfg 配置说明[参照官方文档](https://pingcap.com/docs-cn/v3.0/reference/best-practices/haproxy/ "参照官方文档") 一、使用HAProxy …...
全新自适应导航网模板 导航网系统源码 网址导航系统源码 网址目录网系统源码
高价值目录网导航网整站源码 | 2999元价值,最新版本源码下载推荐 1、导航网一键获取目标站SEO信息,7.5版本增加会员中心一键获取网站信息网站权重,增加小程序提交发布,全新自适应模板; 2、可设置游客提交、游客提交人工审核,会员免审提交,会员提交人工审核,VIP会员免…...
无人直播间
失败!! 采用 ffmpeg 技术进行推流 推流代码: 【需要将rtmp替换为你的推流地址】 ffmpeg -re -stream_loop -1 -i "rain.mp4" -c copy -f flv ""推流地址获取 以哔哩哔哩为例 点击下方链接 开播设置 - 个人中心 - …...
Linux 服务器防止 ssh 暴力密码登录破解之使用 fail2ban
前言,网络安全越来越重要,如何保证网站安全至关重要,在使用 Linux 服务器时,如果未设置有效安全登录屏障,每日将会有数百甚至数万次的密码暴力尝试登录,本篇章将介绍两种 Linux 登录安全防护 一࿱…...
第十四届蓝桥杯大赛软件赛决赛 C/C++ 大学 B 组 试题 D: 合并数列
[蓝桥杯 2023 国 B] 合并数列 【问题描述】 小明发现有很多方案可以把一个很大的正整数拆成若干正整数的和。他采取了其中两种方案,分别将他们列为两个数组 { a 1 , a 2 , ⋯ a n } \{a_1, a_2, \cdots a_n\} {a1,a2,⋯an} 和 { b 1 , b 2 , ⋯ b m } \{b…...
ChatGPT必应联网功能正式上线
今日凌晨发现,ChatGPT又支持必应联网了!虽然有人使用过newbing这个阉割版的联网GPT4,但官方版本确实更加便捷好用啊! 尽管 ChatGPT 此前已经展现出了其他人工智能模型无可比拟的智能,但由于其训练数据的限制ÿ…...
DETR中的问题汇总(代码)
一、讲述一下torch.tensor()和torch.as_tensor()的区别 torch.tensor() 和 torch.as_tensor() 都是 PyTorch 中用于创建张量(Tensor)的函数,但它们有一些区别,主要涉及到张量的内存管理方式和数据拷贝。以下是它们的主要区别&…...
华为云云耀云服务器L实例评测|使用华为云耀云服务器L实例的CentOS部署Docker并运行Tomcat应用
目录 前言 步骤1:登录到华为云耀云服务器L实例 步骤2:安装Docker 并验证Docker安装 步骤3:拉取Tomcat镜像并运行Tomcat容器 步骤4:放行8080端口 步骤5:访问tomcat 步骤6:管理Tomcat容器 小结 前言 …...
Java基础---第八篇
系列文章目录 文章目录 系列文章目录一、a=a+b与a+=b有什么区别吗?二、try catch finally,try里有return,finally还执行么?三、Excption与Error包结构一、a=a+b与a+=b有什么区别吗? += 操作符会进行隐式自动类型转换,此处a+=b隐式的将加操作的结果类型强制转换为持有结果…...
(附源码)springboot体检预约APP 计算机毕设16370
目 录 摘要 1 绪论 1.1开发背景 1.2研究现状 1.3springboot框架介绍 1.4论文结构与章节安排 2 Springboot体检预约APP系统分析 2.1 可行性分析 2.1.1 技术可行性分析 2.1.2 经济可行性分析 2.1.3 操作可行性分析 2.2 系统流程分析 2.2.1 数据添加流程 2.2.2 数据…...
Spring的注解开发-@Component的三个衍生注解
由于JavaEE开发是分层的(三层架构体系,控制层、服务层、持久层),为了每层Bean标识的注解语义化更加明确,Component又衍生出以下三个注解 注解用途Repository(仓库)标识持久层(DAO&am…...
无线WIFI工业路由器可用于楼宇自动化
钡铼4G工业路由器支持BACnet MS/TP协议。BACnet MS/TP协议是一种用于工业自动化的开放式通信协议,被广泛应用于楼宇自动化、照明控制、能源管理等领域。通过钡铼4G工业路由器的支持,可以使设备间实现高速、可靠的数据传输,提高自动化水平。 钡…...
基于长短期神经网络铜期货价格预测,基于LSTM的铜期货价格预测,LSTM的详细原理
目录 背影 摘要 代码和数据下载:基于长短期神经网络的铜期货开盘价格预测,基于长短期神经网络的铝价格期货开盘价预测(代码完整,数据齐全)资源-CSDN文库 https://download.csdn.net/download/abc991835105/88230626 LSTM的基本定义 LSTM实现的步骤 基于长短期神经网络LSTM…...
300元开放式耳机推荐哪个、最值得入手的开放式耳机推荐
开放式耳机成为今年耳机界的主流了,如果你还不曾体验过开放式耳机,那真的是太OUT了!相对于传统的入耳式耳机对听力的损伤,开放式耳机有着很长远的益处,能够很好的保护听力。随着技术的成熟,开放式耳机也在音…...
法律顾问 网站 源码/网站提交入口大全
分享一下我老师大神的人工智能教程!零基础,通俗易懂!http://blog.csdn.net/jiangjunshow也欢迎大家转载本篇文章。分享知识,造福人民,实现我们中华民族伟大复兴!无论是开发GPS设备硬件还是开发应用软件&…...
涞水住房和城乡建设厅网站/网页设计模板
Python实战社群Java实战社群长按识别下方二维码,按需求添加扫码关注添加客服进Python社群▲扫码关注添加客服进Java社群▲作者丨艺艺子来源丨TechWeb(ID:TechWeb)1月13日消息,据国外媒体报道,美国当地时间周…...
wordpress超简洁主题/跨境电商哪个平台比较好
在《【Hibernate】Hibernate的聚类查询、分组查询、排序与时间之差》(点击打开链接)一文中已经讲述过如何利用HQL语句取代SQL语句,进行聚类查询、分组查询、排序与时间之差的查询,同时指出hql能代替sql语句做任何事情。我原本以为…...
网站建设费用摊销会计分录/计算机培训机构哪个最好
实际上配置文件application.properties还有application.yaml(application.yml) 我自己实际使用中,就是感觉用yaml能省不少,但是要注意换行空格符号,举个例子有两个属性:server.port 和server.name propert…...
网站怎么做图片转换/seo的中文含义是什么意思
目录一、MySQL进阶查询1.1 常用查询1.1.1 按关键字排序1.1.2 对结果进行分组1.1.3 限制结果条目1.1.4 设置别名1.1.5 通配符1.1.6 子查询1.2 NULL值1.3 正则表达式1.3.1 以特定字符串开头的记录1.3.2 以特定字符串结尾的记录1.3.3 包含指定字符串的记录1.3.4 以“.”代替字符串…...
网站开发设计软件/百度推广账号登陆入口
如同智能机一夜之间颠覆了诺基亚,Serverless 的出现也带来了一种全新的、颠覆式的云开发架构模式。在 Serverless 出现前,开发者们根本无法想象几分钟就能快速部署一个 Web 应用上线。 2012 年,Iron 公司首次提出 Serverless 的概念。2014 年…...