一.path路径模块
// 通过require引入
const path = require('path')
//路径拼接 path.resolve(__dirname,'目录1','目录2', '待处理文件名) __dirname: 当前目录
const filePath = path.resolve(__dirname,'./','./test1.txt')
二.file文件模块
// 使用前先引入文件模块
const fs = require('fs')
读取文件
(1)readFile(path, [option], callback)
异步方式,将要读取的文件内容完整读入缓存区, 再从缓存区读取文件的内容, 且没有返回值
(2) fs.readFileSync(path, [option])
同步读取数据,且读取的内容通过返回值获取, 若指定encoding选项,则该函数返回字符串, 否则返回buffer缓存
参数说明:
path: 文件路径 常使用path拼接路径
options: 对象, 包含encoding属性(字符编码)和flag文件系统标志, 未指定字符编码,返回原始buffer, 当为字符串时,只能表示字符编码
callback(err, data): 回调函数, err错误信息, data文件内容
(3)Promise实现readFile
// 引入模块
const fsPromise = require('fs').promises / require('fs/promises').
(4)FileHandel包装器
FileHandle对象是数字文件描述符的包装器,由fsPromises.open()方法在内部创建
filehandle.readFile(encoding);执行语句返回值就是读取的内容
/* 1. 读取文件
readFile(path, [option], callback) 异步方式,将要读取的文件内容完整读入缓存区, 再从缓存区读取文件的内容, 且没有返回值
fs.readFileSync(path, [option]) 同步读取数据,且读取的内容通过返回值获取, 若指定encoding选项,则该函数返回字符串, 否则返回buffer缓存
说明:
path: 文件路径 常使用path拼接路径 path.resolve(__dirname,'目录1','目录2', '待处理文件名) __dirname: 当前目录
options: 对象, 包含encoding属性(字符编码)和flag文件系统标志, 未指定字符编码,返回原始buffer, 当为字符串时,只能表示字符编码
callback(err, data): 回调函数, err错误信息, data文件内容
*/
const filePath = path.resolve(__dirname,'./','./test1.txt')
// (1) readFile 异步读取
const file1 = fs.readFile(filePath,'utf-8',(err,data)=>{
console.log(' 1 异步读取readFile:',' err1:',err,' data1:',data);
})
console.log(' 2 file1: ',file1);
// (2) readFileSync 同步读取 无返回值
const file2 = fs.readFileSync(filePath,'utf-8')
const file3 = fs.readFileSync(filePath) //未指定字符集
console.log(' 3 file2: ',file2); // i am okkk!!!
console.log(' 4 file3: ',file3); // <Buffer 69 20 61 6d 20 6f 6b 6b 6b 21 21 21>
console.log(' 5 file3: ',file3.toString()); // i am okkk!!!
// (3) Promise 实现readFile 可通过.then接收 需先引入模块require('fs').promises / require('fs/promises').
fsPromise.readFile(filePath).then(data=>{
console.log(' 6 promise实现读取:',data.toString());
})
// 也可通过封装函数,将异步变为同步
const print = async ()=>{
const data1 = await fsPromise.readFile(filePath)
console.log(' 7 promise封装函数实现读取:',data1.toString())
// return
// (4) FileHandel实现 是一个Promise
const fileHandel = await fsPromise.open(filePath)
const data2 = await fileHandel.readFile()
console.log(' 8 FileHandel实现读取:',data2.toString())
return
}
print()
/* 读取文件最终结果: (打印顺序与同/异步有关)
2 file1: undefined
3 file2: i am okkk!!!
4 file3: <Buffer 69 20 61 6d 20 6f 6b 6b 6b 21 21 21>
5 file3: i am okkk!!!
1 异步读取readFile: err1: null data1: i am okkk!!!
6 promise实现读取: i am okkk!!!
7 promise封装函数实现读取: i am okkk!!!
8 FileHandel实现读取: i am okkk!!!
*/
重写文件
writeFile(file, data, [options], callback); 写入文件 与读取相似, 默认新内容会覆盖原内容
/* writeFile(file, data, [options], callback);
参数1:必选参数,文件存放路径
参数2:必选参数,表示要写入的内容
参数3:可选参数,写入文件内容格式,默认值是 utf-8
参数4:必选参数,文件写入完成后的回调函数
*/
const filePath = path.resolve(__dirname,'../logs','access.log');
const content =`${moment().format()}-${req.method}-${req.headers['user-agent']}:URL-${pathname}\n`; // req.headers['user-agent'] 获取浏览器信息
await fsPromise.writeFile(filePath,content,{flag:'a'})
3.stream
文件IO性能慢, 使用stream流解决
(1)标准的输入输出
// (1) 标准的输入输出: pipe()管道 连接 process.stdin 获取数据 (键盘) process.stdout 输出数据 (显示器)
process.stdin.pipe(process.stdout) //控制台输入,会显示输入的内容
(2)实现文件备份
// (2) 实现文件备份
// source 源
const source = path.resolve(__dirname,'../logs','test2-source.log')
// read stream 读取流
const rs = fs.createReadStream(source)
// dest 目标
const dest = path.resolve(__dirname,'../logs',`test2-dest${moment().format()}.log`)
const ws = fs.createWriteStream(dest,'utf-8')
// 连接
rs.pipe(ws)
rs.on('close',()=>{
console.log('复制完成');
})
// 与日志, 定时任务结合可实现定时文件备份
(3)writeStream
const createWS = filename=>{
const filePath = path.resolve(__dirname, '../05-日志(LOG)', filename);
return fs.createWriteStream(filePath,{flags:'a',encoding:'utf-8'});
}
const content = `${moment().format()}-${req.method}-${req.headers['user-agent']}:URL-${pathname}\n`;
// 使用流的方式去写
createWS('test3.log').write(content);
(4)readline逐行读取
//需先安装并导入 npm i readline --save-dev
const readline = require('readline')
// (4)逐行读取文件 readline 先安装readline再导入
const source = path.resolve(__dirname, '../logs', 'access.log');
const rs = fs.createReadStream(source);
const rlInterface = readline.createInterface(rs);
let sum = 0;
let edgCount = 0;
rlInterface.on('line', data => {
if (data.trim().length === 0) {
return;
}
sum += 1;
if (data.indexOf('Edg') > -1) {
edgCount += 1;
}
});
rlInterface.on('close', () => {
console.log('文件读取完成', edgCount / sum);
});
return { code: 0, message: `统计完成` }
三.加密
保存在数据库中的密码不能使用明文,必须要进行加密。
使用crypto, nodejs自身提供的库,直接引用
// 加密工具
const crypto = require('crypto')
// 使用md5进行加密
const secretMDPsd = password =>{
// 创建hash对象并返回
/* crypto.createHash(algorithm) 创建并返回一个hash对象,它是一个指定算法的加密hash,用于生成hash摘要。 参数algorithm可选择系统上安装的OpenSSL版本所支持的算法。例如:'sha1', 'md5', 'sha256', 'sha512'等
hash.update(data) 更新hash的内容为指定的data。当使用流数据时可能会多次调用该方法。
hash.digest(encoding='binary') 计算所有传入数据的hash摘要。参数encoding(编码方式)可以为'hex', 'binary' 或者'base64' */
const hash = crypto.createHash('md5')
return hash.update(password).digest('hex')
}
// 使用Hmac加密 Hmac算法也是一种哈希算法,它可以利用MD5或SHA1等哈希算法。不同的是,Hmac还需要一个密钥
const secretHMPsd = password =>{
const secretKey = 'itkk--0521';
const hmac = crypto.createHmac('md5', secretKey);
return hmac.update(password).digest('hex')
}
module.exports = {
secretMDPsd,
secretHMPsd
}
四.cookie和session
实现登录功能时,与cookie和session相关
1.cookie和session
cookie 存储在浏览器端的字符串, 有大小限制, 4KB
特点: 域的限制(不能跨域), 时间限制, 数量限制, 每个域下最多不超过50个cookie
存储格式: k1=v1; k2=v2; k1=v1_afa(特殊格式)
每次发送http请求, 会将请求域中的cookie一起发送给serve端, serve端可以查看cookie和修改cookie, 浏览器只读不可修改cookie
使用cookie问题: 暴露用户名, 不安全
解决办法: 在cookie中存放uuid, 在server服务器端保存用户信息, 让uuid与用户信息进行对应
2.查看cookie
方式1: 控制台network中
方式2: 通过js查看cookie: document.cookie(不推荐) (通过浏览器控制台输入)
3.实现登录功能
客户端向浏览器每次发送http请求时,会携带cookie, 服务器端可以查看和修改cookie, 故用户登录操作可以将用户信息保存在cookie中, 下次登录时, 与浏览器中的cookie作对比, 如果有则不需要再登录, 如果没有则重新登录
// 实现登录一次,无需多次登录 cookie保存在浏览器,会暴露用户信息不安全,解决办法: 用户信息保存在服务器端session中 cookie中保存一个令牌(uuid,一串字符),在session中保存令牌对应的用户信息
// 服务器端处理cookie 客户端发送请求携带cookie, 将参数 服务器端判断是否有, 有则进行后续, 无则修改cookie并进行登录
/* SESSION_DATA用于保存用户信息 , 后续将用户信息保存在req.session中
登录操作时,
cookie中是否有令牌
有令牌,则session无需设置,
查看session中是否包含用户信息,
无用户信息, 返回空数据{ }给客户端, 有令牌有用户信息,
有用户信息, 则返回用户数据{,,,}
无令牌,需要设置cookie,随机生成令牌uuid,(由于首次登录,还没有用户信息,返回空用户信息)
生成后,将对应的用户信息保存在SESSION_DATA中返回给客户端
*/
// 1. 获取请求中的cookie字符串
const cookiesStr = req.headers.cookie //是个字符串
// 2. 解析cookie(转换为对象)
let cookies = {}
if(cookiesStr){
cookies = querystring.decode(cookiesStr,';','=') //返回对象, 键值对
}
// 3. 将cookie保存在req中 由于cookie会暴露用户信息,故将用户信息保存在session中, 去除此步
// req.cookies = cookies
// 4. 获取cookies对象中对应的内容 声明cookie令牌uuid
let uuid = cookies['uuid'] || cookies[' uuid'] //浏览器cookie中从第二个开始,键前有个空格
// 5. 声明一个变量 判断用户是否需要设置cookie, 用户登录信息不存在则需要设置
let isWriteCookie = false
// 判断session中是否包含cookie中令牌uuid对应的用户信息
if(uuid){ //令牌存在
if(!SESSION_DATA[uuid]){ //无用户信息
SESSION_DATA[uuid] = {} //存在令牌, 则不设置
}
}else{ //令牌不存在
// 请求中无cookie, 后续服务器端设置cookie
isWriteCookie = true
//首次登录不存在令牌,随机生成 uuid
uuid = `${new Date().getTime()}_${Math.random(10000)+1}`
SESSION_DATA[uuid] = {} // 因为还未登录,故无用户信息
}
//用户信息保存在服务器端的session中
req.session = SESSION_DATA[uuid]
// 对用户请求进行处理 ?作用: 匹配到,则进行后边.then, 否则不进行
userRouter(req)?.then(data=>{
// 6. 设置cookie
if(data === undefined){
return
}
// 判断是否需要设置cookie
if(isWriteCookie){ //true
// 设置cookie 'Set-Cookie','key=value;httpOnly;path=/;httpOnly;expiress=时间'
res.setHeader('Set-Cookie',`uuid=${uuid};httpOnly;path=/;httpOnly;expiress=${getExpiresTime()}`)
}
SESSION_DATA[uuid] = data.data
res.session = SESSION_DATA[uuid]
res.end(JSON.stringify(data))
return
})
PreviousNotes:
https://blog.csdn.net/qq_54379580/article/details/126781836?spm=1001.2014.3001.5501