Nodejs
主要配置:
1、环境配置:
npm init -y
2、配置淘宝镜像:
npm install -g cnpm --registry=https://registry.npm.taobao.org
3、配置运行命令:
运行程序:
npm run dev
4、监听代码:
cnpm install nodemon --save-dev
5、运行跨平台设置和使用环境变量的脚本:
cnpm install cross-env --save-dev
在package.json配置nodemon和cross-env
6、nodejs连接数据库
npm i mongodb --save-dev
7、全部的配置下载
8、配置
创建步骤:
1、创建服务
// 创建服务
// 引入http
const http = require('http')
const {serverHandler} = require('../src/handler/serverHandler')
// 新建服务
const server = http.createServer(
// 处理请求
serverHandler
);
// 监听服务
server.listen(8090,() => {
console.log('--服务已启动--');
})
2、处理用户的请求并解析参数,给前台做出响应
// 处理用户的请求,并给前台做出响应
// 参数处理 --- POST和GET
const { parse } = require('url')
const { usersRouter } = require('../router/usersRouter')
const { booksRouter } = require('../router/booksRouter')
const querystring = require('querystring')
const { decode } = require('punycode')
const SESSON_DATA = {}; // 保存用户信息
// 添加cookie中expires中
// 给当前时间添加1分钟
const getExpiresTime = () => {
const date = new Date();
date.setMinutes(date.getMinutes() + 1);
// date.toUTCString 将时间对象转为字符串
return date.toUTCString();
}
// 解析POST参数方法
const getPOST = (req) => {
// 解决异步问题 使用Promise构造函数
return new Promise((resolve, reject) => {
// 获取POST请求的参数,需要数据的监听--流的方式接收
let postParams = '';
req.on("data", chunk => {
postParams += chunk;
});
req.on("end", () => {
// JSON.parse(postParams) 不解析空 需要判断
if (postParams && postParams !== '') {
resolve(JSON.parse(postParams));
} else {
resolve({});
}
});
});
}
const serverHandler = (req, res) => {
// 解决跨域问题
res.setHeader('Access-Control-Allow-Origin', '*');
// 设置响应头
res.setHeader('content-type', 'application/json;charset=utf-8')
// cookie存储在浏览器端,用户登录一次,我们可以将用户信息存入cookie,下次用户访问时,不需要登录操作,就可以直接使用
// cookie
// 1、获取请求中的cookie 获取到的是String
const cookieStr = req.headers.cookie;
// 2、解析cookie--querystring
// 判断cookie是否存在
let cookies = {};
if (cookieStr) {
cookies = querystring.decode(cookieStr, ';', '=');
}
// 3、将cookies保存在req中
req.cookies = cookies;
// 因为cookie在浏览器端是可以查看的,所以为了增加我们信息的安全性,增加session 在cookie中设置一个令牌,将所有内容放入session中,
//前端发送请求,判断cookie是否含有令牌,含有令牌不需要登录操作,根据令牌内容,从服务端传入用户信息
//没有令牌,需要进行登录,并创建令牌
// 4、判断用户是否登录
// 增加 session 增加安全性---> uuid
// 解析时:中文字符转换 decodeURIComponent
// 判断cookie中是否含有uuid
let uuid = cookies['uuid'] || cookies[' uuid'];
// 设置一个标识,是否设置cookie
let isWriteCookie = false;
if (uuid) {
// uuid存在,判断SESSON_DATA[uuid]是否有内容
if (!SESSON_DATA[uuid]) {
// 没有内容,返回空对象
SESSON_DATA[uuid] = {}
}
} else {
// uuid不存在,设置cookie,增加uuid
isWriteCookie = true;
}
req.session = SESSON_DATA[uuid];
// 解析rul--使用parse,需要引用
// GET和POST解析方法不同
// GET参数
// <1>获取请求的url
const { url } = req;
// <2>解析url
const obj = parse(url, true); // 解析url
const { query, pathname } = obj;
// <3>将get请求的参数放置在req.query
req.pathname = pathname; // 将get请求的参数放置在req.query中
req.query = query
// POST参数 -- 封装
getPOST(req).then(data => {
console.log(isWriteCookie)
if (isWriteCookie) {
// res.end(JSON.stringify({
// message:'请登录'
// }))
req.body = data;
// req.session = SESSON_DATA[uuid];
usersRouter(req)?.then(data => {
// if(data === undefined){
// return;
// }
// cookie
if (data.data && data.data.length > 0) {
// 设置时:中文字符的转换 encodeURIComponent
uuid = `${new Date().getTime()}_${Math.random(10000000000) + 1}`;
res.setHeader("Set-Cookie", `uuid=${uuid}`);
}
SESSON_DATA[uuid] = data.data;
res.end(JSON.stringify(data))
return;
});
booksRouter(req)?.then(data => {
res.end(JSON.stringify(data))
return;
});
} else {
console.log("bbbb")
req.body = data;
usersRouter(req)?.then(data => {
console.log(data)
res.end(JSON.stringify(data))
return;
});
booksRouter(req)?.then(data => {
res.end(JSON.stringify(data))
return;
});
}
})
}
module.exports = {
serverHandler
}
3、dbutil(数据库连接工具)
发送请求,数据从数据库中获取,需要连接数据库
// 创建客户端
const mongodbClient = require('mongodb').MongoClient;
// 引入url dbname
const { MONGODBU_URL, DB_NAME } = require('../config/dconfig');
// 获取集合
// async 返回Promise对象
const getCollection = async collName => {
// 匹配环境信息
// dev环境
let url = MONGODBU_URL;
let dataName = DB_NAME;
const env = process.env.NODE_ENV;
if (env === "production") {
url = MONGODBU_URL_PRO;
dataName = DB_NAME_PRO;
}
// 连接数据库
const conn = await mongodbClient.connect(url)
// 使用数据库
const db = await conn.db(dataName);
// 获取集合
const collection = await db.collection(collName);
return collection;
}
// 查询数据
const searchData = async (collName, params = {}) => {
const collection = await getCollection(collName)
// 进行查询
const data = await collection.find(params).toArray()
return data;
}
// 增加数据
const addData = async (collName, params = {}) => {
// 判断增加内容是否为空
if (Object.keys(params).length === 0) {
return Promise.reject({
ok: false,
count: 0,
messagge: '失败,没有参数'
});
} else {
const collection = await getCollection(collName)
const result = await collection.insertOne(params)
if (result.acknowledged) {
return Promise.resolve({
ok: true,
count: 1,
message: '成功'
});
} else {
return Promise.resolve({
ok: false,
count: 0,
message: '失败'
});
}
}
}
// 修改数据
const upDate = async (collName,filterParam, params = {}) => {
const collection = await getCollection(collName)
// 进行查询
const result = await collection.updateOne(filterParam,{$set:params})
return result;
}
// 删除数据
const remDate = async (collName,params = {}) => {
const result = await getCollection(collName).deleteOne(params)
return result;
}
module.exports = {
searchData,
addData,
upDate,
remDate
}
4、router(处理请求的方法放入路由中)
请求分为对书的处理和对用户的处理,所以分为两个路由
从数据库中得到数据后,响应给前端
<1>booksRouter
const { parse } = require('url');
const { getBook, addBook, upBook ,remBook} = require('../data/books')
// 拿到请求需要判断请求的方式和pathname
const booksRouter = req => {
const { method, pathname } = req;
// req.query = obj.query;
// 获取pathName
// const pathName = pathname;
// 判断请求方式
if (method === "GET" && pathname === "/api/getBookList") {
// 获取数据库数据
return getBook(req.query);
} else if (method === 'POST' && pathname === '/api/addBookList') {
return addBook(req.body);
} else if (method === 'POST' && pathname === '/api/updateBookList') {
return upBook(req.body);
} else if (method === 'POST' && pathname === '/api/remdateBookList') {
return remBook(req.body);
}
// else if (method === 'GET'&& pathname === '/api/logs') {
// // 读取日志内容
// }
}
module.exports = {
booksRouter
}
<2>usersRouter
// 判断method
const { parse } = require('url');
const { getUser,upUser } = require('../data/users');
const usersRouter = req => {
const { method, pathname } = req;
// const { method, url } = req;
// const obj = parse(url, true);
// 获取pathName
// const pathName = obj.pathname;
// 判断请求方式
if (method === "POST" && pathname === "/api/login") {
// 获取数据库数据
const result = getUser(req.body);
return Promise.resolve(result);
} else if (method === "POST" && pathname === '/api/modifyPas') {
const result = upUser(req.body);
return Promise.resolve(result);
}
}
module.exports = {
usersRouter
}
5、为了增加安全性,我们也需要对密码进行加密操作
// 加密
// 引入
const crypto = require('crypto')
// 对密码进行加密
const secretPassword = password => {
// 创建并返回一个hash对象,它是一个指定算法的加密hash
/*
Hmac算法也是一种哈希算法,它可以利用MD5或SHA1等哈希算法
Hmac还需要一个密钥
*/
const hash = crypto.createHash('md5');
return hash.update(password).digest('hex')
}
module.exports = {
secretPassword
}
6、我们在生产模式下,是没有控制台的,所以需要用日志文件,将所用的访问信息,报错信息等都可以写入日志文件中
一、文件的基本操作
在nodejs中所有与文件相关的操作都在fs模块中,而读写操作又是我们会经常用到的操作。
nodejs的fs模块针对读操作为我们提供了readFile,read, createReadStream三个方法,针对写操作为我们提供了writeFile,write, createWriteStream三个方法。
<1>引入模块 fs, path
const fs = require('fs');
const path = require('path');
<2>readFile和readFileSync
readFile方法是通过异步的方式将要读取的文件内容完整读入缓存区,再从该缓存区中读取文件内容,且没有返回值,具体语法:readFile(path[, options], callback)。
path:表示文件路径,经常使用 path来拼接路径,path.resolve(__dirname,‘目录1’,‘目录2’,‘文件名’)
__dirname:表示当前路径
// 获取文件路径,__dirname表示当前路径
const filePath = path.resolve(__dirname,'../','logs','root.log');
options:是一个对象,包含两个属性encoding表示字符编码和flag表示文件系统标志。当没有指定字符编码时,则返回原始的 buffer,当为字符串时只能表示字符编码
fs.readFile(filePath,"utf-8",(err,data)=>{
if(!err){
console.log(data.toString());
}
});
callback:表示一个回调函数,会传入两个参数 (err, data),其中 data 是文件的内容,err是错误信息,没有则为null。
fs.readFile(filePath,(err,data)=>{
if(!err){
console.log(data.toString());
}
});
<3>readFileSync和readFile基本一致,唯一的区别是readFileSync使用同步的方式读取数据,且读取的内容通过返回值获取。具体语法:fs.readFileSync(path[, options])。在使用过程中,如果指定了 encoding 选项,则此函数返回字符串。 否则,返回 buffer。
const data = fs.readFileSync(filePath,'utf-8');
console.log(data.toString());
<4>通过Promise实现readFile
在js中通过require(‘fs’).promises或require(‘fs/promises’)调用。
const fsPromise = require('fs').promises;
具体语法:fsPromises.readFile(path[, options]);
fsPromise.readFile(filePath,'utf-8').then(data=>{
console.log('promise:',data);
});
将异步变成同步
// 封装打印函数
const print = async ()=>{
const filePath = path.resolve(__dirname,'../','logs','root.log');
const data = await fsPromise.readFile(filePath);
return data.toString();
}
//调用
console.log('promise:', await print());
<5> 通过Filehandle实现
FileHandle对象是数字文件描述符的包装器,由fsPromises.open()方法在内部创建。具体语法是:filehandle.readFile(encoding);执行语句返回值就是我们读取的内容。
// 使用fileHandle来实现
// 创建fileHandle
const fileHandle = await fsPromise.open(filePath); // Promise<FileHandle>
// 通过readFile去读取内容
const data = (await fileHandle.readFile()).toString();
<6>写的方法,与readFile很相似
// req.headers['user-agent'] 获取浏览器信息
const filePath = path.resolve(__dirname,'../logs','access.log');
const content = `${moment().format()}-${req.method}-${req.headers['user-agent']}:URL-${pathname}\n`;
await fsPromise.writeFile(filePath,content,{flag:'a'})
<7> 文件IO的性能瓶颈:慢。解决办法,nodejs/stream
-
标准的输入输出
process.stdin 获取数据 process.stdout 输出数据
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-KB8uPnch-1675514380250)(C:\Users\27306\AppData\Roaming\Typora\typora-user-images\image-20230202165129699.png)]
process.stdin.pipe(process.stdout)
-
备份文件
// source const source = path.resolve(__dirname, '../logs', 'access.log'); // read stream const rs = fs.createReadStream(source); // dest const dest = path.resolve(__dirname, '../logs/bak', `access${moment().format('YYYYMMDDHHmmss')}.log`); // write stream const ws = fs.createWriteStream(dest, { flags: 'a', encoding: 'utf-8' }); rs.pipe(ws); rs.on('end', () => { console.log("复制完成"); })
-
使用writeStream去写
const createWS = filename=>{ const filePath = path.resolve(__dirname, '../logs', filename); return fs.createWriteStream(filePath,{flags:'a',encoding:'utf-8'}); } const content = `${moment().format()}-${req.method}-${req.headers['user-agent']}:URL-${pathname}\n`; // 使用流的方式去写 createWS('access.log').write(content);
-
逐行读取文件 readline
<1> 先进行安装
npm install readline --save
<2>引用
const readline = require('readline');
<3>使用
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: `统计完成` }
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: `统计完成` }
``
注:路由获取数据库中的数据的方法,封装在获取数据的包中(获取书籍数据)
const { searchData,addData,upDate,remDate} = require('../dbutil/mongodbUtil')
// 查找
const getBook = params => {
const newParams = { ...params };
if (params.bookname && params.bookname !== '') {
newParams.bookname = { $regex: `.*${params.bookname}.*` }
}
return searchData("books",newParams);
}
// 增加
const addBook = params => {
return addData("books",params);
}
// 修改
const upBook = (params) => {
const {bookid,...newObj} = params
return upDate('books',{bookid},params);
}
// 删除
const remBook = params => {
// const par= JSON.stringify(params)
console.log(888888)
return remDate('books',params);
}
module.exports = {
getBook,
addBook,
upBook,
remBook
}