Node.js学习笔记

本文详细介绍了Node.js的基础知识,包括其与浏览器的区别、模块系统、npm管理和服务器端开发要点。通过实战项目,阐述了如何使用Node.js开发博客系统,涉及接口设计、数据存储(选用MySQL而非MongoDB)、用户登录(使用cookie和session,存储session用到redis)以及安全防范措施,如SQL注入和XSS攻击的防御策略。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >


引用参考2021了,真的不要再说 Node.js 是一门编程语言了

一、node.js概述

Node.js 不是一门编程语言,它是一个执行 JavaScript 代码的运行环境

1、node.js和浏览器

  • 因为浏览器和 Node.js 都内置了 JavaScript V8 Engine。所以他们都可以运行 JavaScript

  • 浏览器的JavaScript可以操作HTML和浏览器窗口(在JavaScript V8引擎中添加了控制他们的API,即BOM和DOM)

  • Node.js 中没有 DOM 和 BOM,但是添加了很多系统级别的 API,如对系统文件和文件夹的操作。获取操作系统信息,比如系统内存总量是多少,系统临时目录在哪

  • JavaScript 运行在浏览器中(即客户端 JavaScript),用于创建前端工程化工具。

  • JavaScript 运行在 Node.js 中(即服务器端 JavaScript),用于构建服务器端应用。

2、ES6,JavaScript,node.js区别

  • ECMAScript 6.0(简称 ES6)是 JavaScript 语言的标准(即语法规范),写javascript和nodejs都必须遵守。如:变量定义,循环、判断、函数、原型和原型链、作用域和闭包、异步。
    不能操作DOM,不能监听click事件,不能发送ajax请求,不能处理http请求,不能操作文件。即,只有ECMAScript ,几乎做不了任何实际的项目

  • JavaScript:ECMAScript语法规范++Web API。可以进行DOM操作,BOM操作,事件绑定,Ajax等(浏览器端的任何操作)

  • node.js: ECMAScript语法规范++nodejs API。如:处理http ,处理文件等(完成server端的任何操作)

3、模块系统

在 Node.js 环境中,默认就支持模块系统,该模块系统遵循 CommonJS 规范。

  • __filename:当前模块文件名称。

  • __dirname:当前文件所在路径。

3.1、Node.js 内置模块
  • Path:模块内提供了一些和路径操作相关的方法。
const path = require("path")
console.log(path.parse(__filename))

{
  root: '/',
  dir: '/Users/administrators/Desktop/node_test',
  base: 'app.js',
  ext: '.js',
  name: 'app'
}
  • File system:文件操作系统,提供了和操作文件相关的方法。
const fs = require("fs")

const files = fs.readdirSync("./")
console.log(files)  [ 'app.js', 'logger.js' ]

fs.readdir("./", function (error, files) {
  console.log(error) // null | Error {}
  console.log(files) // [ 'app.js', 'logger.js' ] | undefined
})
  • 在引入内置模块时, 使用的是模块的名字,前面不需要加任何路径。

4、npm

Node.js 软件包都是基于Node.js平台开发的,这些软件包都被托管在 www.npmjs.com 中,通过npm来管理

npm随node.js一起安装,和node是两个独立的应用程序(可以通过版本号证明)

4.1、package.json
  • Node.js 规定在每一个软件包(或工程项目)都必须包含一个叫做 package.json 的文件

  • 它是应用程序的描述文件,包含和应用程序相关的信息,比如应用名称,应用版本,应用作者等等。

  • 通过 package.json 文件可以方便管理应用和发布应用。

  • 创建 package.json 文件: npm init

  • 快速创建 package.json 文件: npm init --yes

{
  "name": "project-name",
  "version": "1.0.0",
  "description": "",
  "main": "index.js",
  "scripts": {
    "test": "echo \"Error: no test specified\" && exit 1"
  },
  "keywords": [],
  "author": "",
  "license": "ISC"
}
4.2、下载 Node.js 软件包

在应用程序的根目录执行命令:npm install < pkg > 或者 npm i < pkg >

软件包下载完成后会发生三件事:

  • 软件包会被存储在 node_modules 文件夹中,如果在应用中不存在此文件夹,npm 会自动创建。

  • 软件包会被记录在 package.json 文件中. 包含软件包的名字以及版本号。

  • npm 会在应用中创建 package-lock.json 文件, 用于记录软件包及软件包的依赖包的下载地址及版本。

4.3、node_modules

node_modules 文件夹中的软件包不需要提交到 git 仓库中,因为文件夹较大,可达到几百兆。

  • 在 package.json 文件中都会记录项目都依赖了哪些包,在package-lock.json 查看对应包的版本

  • 可以通过 npm install 命令重新下载它们

将应用程序提交到版本库之前,切记将 node_modules 文件夹添加到 .gitignore 文件中,这样就不会连同node_modules一块上传。

git init
git status
echo "node_modules/" > .gitignore
git status
git add .
git commit -m "our first commit"
4.4、删除和更新软件包
npm uninstall <pkg>
npm un <pkg>

npm outdated ,可以查看哪些软件包已经过期,对应的新版本是什么。
npm update ,更新过期的软件包,更新操作遵循语义版本控制规则。
4.5、项目依赖和开发依赖

在 package.json 文件中, 项目依赖和开发依赖要分别记录

  • 项目依赖被记录在 dependencies 对象中
  • 开发依赖被记录在 devDependencies 中
  • 使开发者可以在不同的环境中下载不同的依赖软件包。
下载开发依赖,在命令后加上 --save-dev 或者 -D。如:npm i eslint -D
4.6、本地安装和全局安装

本地安装:将软件包下载到应用根目录下的 node_modules 文件夹中,软件包只能在当前应用中使用

全局安装:将软件包下载到操作系统的指定目录中,可以在任何应用中使用

  • 通过 -g 选项将软件包安装到全局:npm install < pkg > -g
4.7、设置或更改npm镜像地址

由于 npmjs.com 是国外的网站,大多数时候下载软件包的速度会比较慢

更改 npm 镜像地址

npm config set registry https://registry.npm.taobao.org
npm config set registry https://registry.npmjs.org/
cat .npmrc
4.8、npx命令

npx 是 npm 软件包提供的命令,它是 Node.js 平台下软件包执行器。主要用途有两个:

  • 第一个是临时安装软件包执行后删除它

有些提供命令的软件包使用的频率并不高,比如 create-react-app 脚手架工具,我能不能临时下载使用,然后再删掉它。

npx create-react-app react-test
  • 第二个是执行本地安装的提供命令的软件包

现在有两个项目都依赖了某个命令工具软件包,但是项目 A 依赖的是它的 1 版本,项目 B 依赖的是它的 2 版本,我在全局到底应该安装什么版本呢 ?

该软件包可以在本地进行安装,在 A 项目中安装它的 1 版本, 在 B 项目中安装它的 2 版本,在应用中可以通过 npx 调用 node_modules 文件夹中安装的命令工具。

  • 总结:将所有软件包安装到应用本地是现在最推荐的做法,一是可以防止软件包的版本冲突问题,二是其他开发者在恢复应用依赖时可以恢复全部依赖,因为软件包安装到本地后会被 package.json 文件记录,其他开发者在运行项目时不会因为缺少依赖而报错

二、server端介绍

1、服务稳定性

  • server端可能会遭受各种恶意攻击和误操作
  • 单个客户端可以意外挂掉,但是服务端不能
  • 使用PM2做进程守候(当服务一旦挂掉,会自动重启)

2、考虑CPU 和内存(优化、扩展)

  • 客户端独占一个浏览器,内存和CPU都不是问题
  • server端要承载很多请求,CPU和内存都是稀缺资源
  • 使用stream 写日志,使用redis存session

3、日志记录

  • 前端也会参与写日志,但只是日志的发起方,不关心后续
  • server端要记录日志、存储日志、分析日志,前端不关心
  • 多种日志记录方式,以及如何分析日志

4、安全

  • server端要随时准备接收各种恶意攻击,前端则少很多
  • 如︰越权操作,数据库攻击等
  • 登录验证,预防xss攻击和sql注入

5、集群和服务拆分

  • 产品发展速度快,流量可能会迅速增加
  • 如何通过扩展机器和服务拆分来承载大流量?
  • 本课程在设计上支持服务拆分

三、node简单demo实战

一、项目介绍

1、需求

首页,作者主页,博客详情页
登录页
管理中心,新建页,编辑页
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

2、技术方案

数据如何存储

  • 博客数据
  • 用户数据

如何与前端对接,即接口设计
在这里插入图片描述

二、开发博客项目之接口

1、开发接口(不用express、koa)

  • nodejs处理http请求
  • 搭建开发环境
  • 开发接口(暂不连接数据库,暂不考虑登录)

2、处理get请求

  • 先在项目路径下运行npm init -y,初始化,生成一个package.json文件
  • 将package.json里,改为"main": “app.js”, 并新建一个app.js文件
const http = require('http');
const querystring = require('querystring');

const server = http.createServer((req,res) => {
    console.log('method',req.method);
    const url = req.url;
    console.log('url',req.url);
    req.query = querystring.parse(url.split('?')[1]);
    let routerPath = url.split('?')[0];//返回路由
    console.log('query', req.query);
    res.end(JSON.stringify(req.query))
})

server.listen(8000);
console.log('OK')
  • 在浏览器输入地址:http://localhost:8000/api/blog/list?userName=zhangsan&password=1234
    打印 {“userName”:“zhangsan”,“password”:“1234”}

3、处理post请求

const http = require('http');

const server = http.createServer((req,res) => {
    console.log('method:',req.method);
    if(req.method === "POST"){
        //req  数据格式
        console.log('request content-type:',req.headers['content-type']);
        let postData = '';
        req.on('data',chuck=>{
            postData += chuck.toString();
        })
        req.on('end',()=>{
            console.log('postData:',postData);
            res.end('hello world!');
        })
    }
})

server.listen(8000);
console.log('OK')

打印如下:
method: POST
request content-type: application/json
postData: {
“username”:“admin”,
“password”:“123456”
}

4、处理http请求综合案例

const http = require('http');
const querystring = require('querystring');

const server = http.createServer((req,res) => {
    const method = req.method;
    const url = req.url;
    const routerPath = url.split('?')[0];
    const query = querystring.parse(url.split('?')[1]);

    //设置返回格式为 JSON
    res.setHeader('content-type','application/json');

    //返回的数据
    const resData = {
        method,
        url,
        routerPath,
        query
    };

    //返回数据
    if(method === "GET"){
        res.end(JSON.stringify(resData));//输出的是JSON字符串
    }
    if (method === "POST"){
        let postData = '';
        req.on('data',chuck=>{
            postData += chuck.toString();
        })
        req.on('end',()=>{
            resData.postData = postData;
            res.end(JSON.stringify(resData));
        })
    }
})

server.listen(8000);
console.log('OK')

在这里插入图片描述

5、搭建开发环境

  • 从О开始搭建,不适用任何框架
  • 使用nodemon 监测文件变化,自动重启node
  • 使用cross-env设置环量,兼用linux和windows

项目文件夹blog1 => npm init -y => 安装依赖 => 新建文件
“cross-env”: “^5.2.0”,
“nodemon”: “^1.18.9”
在这里插入图片描述
www.js

const http = require('http');

const PORT = 8000;
const serverHandle = require('../app.js');

const server = http.createServer(serverHandle);
server.listen(PORT);

console.log('OK1')

app.js


const handleBlogRouter = require('./src/router/blog');
const handleUserRouter = require('./src/router/user');

const serverHandle = (req,res)=>{
    //设置返回格式
    res.setHeader('Content-type','application/json');

    //处理blog路由
    const blogData = handleBlogRouter(req,res);
    if(blogData){
        res.end(
            JSON.stringify(blogData)
        )
        return;
    }

    //处理user路由
    const userData = handleUserRouter(req,res);
    if(userData){
        res.end(
            JSON.stringify(userData)
        )
        return;
    }

    //未命中路由返回,404
    res.writeHead(404,{'Content-type':'text/plain'});
    res.write('404 Not Found\n');
    res.end();
}

module.exports = serverHandle;

router/blog.js


const handleBlogRouter = (req,res)=>{
    const method = req.method;
    const url = req.url;
    const path = url.split('?')[0];

    //获取博客列表
    if (method === 'GET' && path === '/api/blog/list'){
        return {
            msg:'这是获取博客列表的接口'
        }
    }
    //获取博客详情
    if (method === 'GET' && path === '/api/blog/detail'){
        return {
            msg:'这是获取博客详情的接口'
        }
    }
    //新建一篇博客
    if (method === 'POST' && path === '/api/blog/new'){
        return {
            msg:'这是新建一篇博客的接口'
        }
    }
    //更新一篇博客
    if (method === 'POST' && path === '/api/blog/update'){
        return {
            msg:'这是更新一篇博客的接口'
        }
    }
    //删除一篇博客
    if (method === 'POST' && path === '/api/blog/del'){
        return {
            msg:'这是删除一篇博客的接口'
        }
    }

}

module.exports = handleBlogRouter;

router/user.js


const handleUserRouter = (req,res)=>{
    const method = req.method;
    const url = req.url;
    const path = url.split('?')[0];

    //获取博客列表
    if (method === 'POST' && path === '/api/user/login'){
        return {
            msg:'这是登录的接口'
        }
    }
    
}

module.exports = handleUserRouter;

6、路由和 API

  • API:
    前端和后端、不同端(子系统)之间对接的一个术语
    url(路由)‘/api/blog/list`,get,输入,输出
  • 路由:
    API的一部分
    后端系统内部的一个定义

三、开发博客项目之数据存储

  • node.js连接mysql
  • API连接mysql

1、为什么选择mysql,而不是mogondb

  • mysql是企业内、社区内最常用的存储工具,一般都有专人运维,有问题随时可查
  • 另: mysql本身是一个复杂的数据库软件,本课只讲基本使用

2、mysql服务启动

net start mysql

3、mysql

  • 安装:npm i mysql --registry=https://registry.npm.taobao.org 使用淘宝镜像
  • config/db.js
const env = process.env.NODE_ENV // 环境参数

//配置参数
let MYSQL_CONFIG = {};

if(env === 'dev'){
    MYSQL_CONFIG = {
        host:'localhost',
        user:'root',
        password:'123456',
        port:'3306',
        database:'hzy_blog'
    }
}
if(env === 'production'){
    MYSQL_CONFIG = {
        host:'localhost',
        user:'root',
        password:'123456',
        port:'3306',
        database:'hzy_blog'
    }
}

module.exports = MYSQL_CONFIG;
  • db/mysql.js
const mysql = require('mysql');
const MYSQL_CONFIG = require('../config/db');

// 创建链接对象
const connection = mysql.createConnection(MYSQL_CONFIG);

//开始连接
connection.connect();

//统一执行sql  函数
const exec = (sql) => {
    return new Promise((resolve,reject) => {
        connection.query(sql,(err,res) => {
            if(err){
                reject(err);
                return;
            }
            resolve(res);
        })
    })
}

module.exports = {exec};

//执行sql,增删改查
// const sql = `insert into blogs (title,content,createtime,author) values ('标题C','内容C','2021-06-26 20:32:58','cccc')`
// 删除操作  ---软删除,可恢复,借助‘改’操作
// const sql = `update users set state='1' where username='lisi'`
// const sql = `delete from users where username='lisi'`
// 修改操作
// const sql = `update users set state='1' where username='lisi'`
// 查询操作
// const sql = `select *from users where username='lisi'`

#为了兼容后面如果某些条件不满足的情况SQL报错
where 1=1 
# 用到的基本语句
USE myblog;
#插入语句
INSERT INTO `user` (username,`password`,realname) VALUES ('瓜瓜',12345678,'木瓜')
#查询语句  
SELECT * FROM `user`;
#查询某个字段
SELECT id,username FROM `user`;
#查询条件 WHERE
select * FROM `user` WHERE username='一咻';
#多个条件共存  
#AND
SELECT * FROM `user` WHERE username ='一咻' AND realname = 'wf';
#或者条件  
#OR
SELECT * FROM `user` WHERE username='一咻' OR username='瓜瓜';
#模糊查询  
#LIKE
SELECT * FROM `user` WHERE `password` LIKE '%1%';
#排序
#ORDER BY
#默认情况下是正序  DESC 倒序
SELECT * FROM `user` WHERE `password` LIKE '%1%' ORDER BY id DESC;
#更新,必须添加 WHERE 条件
UPDATE `user` SET realname='木瓜2' WHERE realname='木瓜';
#删除  物理删除
DELETE FROM `user` WHERE realname='木瓜2';
# 逻辑删除
UPDATE `user` SET state='0' WHERE realname='木瓜';
#插入数据
INSERT INTO blog (title,content,create_time,author) VALUES ('文','文章内容4','2020-11-04 19:49:55','一咻');
#修改文章
UPDATE blog SET title='修改文章',content='修改内容' WHERE id=5;
#根据关键字进行查询
SELECT * FROM blog WHERE 1=1 AND author='一咻' AND title LIKE '%文章%' ORDER BY create_time DESC;
#查询博客详情
SELECT * FROM blog WHERE id=1;
#删除博客
DELETE FROM blog WHERE id=5;
# 新增用户
INSERT INTO `user` (username,`password`,realname) VALUES ('一咻',123456,'天明');

四、开发博客项目之用户登录

已有用户登录通用设计经验

  • 核心∶登录校验&登录信息存储
  • redis是内存数据库,mysql是硬盘数据库,session要写入redis

1、cookie

(a)cookie特点

  • 存储在浏览器的一段字符串(最大5kb)
  • 跨域不共享(不同浏览器的cookie是不一样的,但同源窗口可共享数据)
  • cookie是key:value格式
  • cookie数据始终在同源的http请求中携带(在broswer和server间来回传递)
  • server可以修改cookie,并返回给broswer
  • broswer可通过js修改cookie(有限制)
  • 淘宝的网页请求淘宝的服务器,就将淘宝的cookie带过去

(b)浏览器查看cookie

  • 控制台network
  • 控制台,application,cookie。控制台删除cookie
  • document.cookie

(c)server端nodejs操作cookie

  • node查看cookie(在app.js)
//解析 cookie
    req.cookie = {};
    const cookieStr = req.headers.cookie || ''; // k1=y1;k2=y2....
    cookieStr.split(';').forEach(item => {
        if(!item){
            return;
        }
        const arr = item.split('=');
        const key = arr[0].trim();
        const val = arr[1].trim();
        req.cookie[key] = val;
    })
  • node修改cookie(在router.js\user.js)
const getCookieExpires = () => {
    const d = new Date();
    // console.log(d,d.getTime()) //时间格式,时间戳
    d.setTime(d.getTime() + (24*60*60*1000));
    // console.log(d.toGMTString())//转化成cookie的时间格式
    return d.toGMTString();
}

//操作cookie  path 必须为根路径:让这个cookie在这个网站前端的所有路由全部生效   设置httpOnly防止客户端对cookie操作
res.setHeader('Set-Cookie',`username='${data.username}'; path=/; httpOnly; expires='${getCookieExpires()}'`)

2、session

(a)session介绍

  • cookie缺点:会暴露username,很危险
  • 如何解决:cookie存储userid,server端对应username
  • 解决方案:session:即server端存储用户信息

3、redis

在这里插入图片描述
(a)session存在的问题

  • 目前,session直接是js 变量,放在nodejs进程内存中
  1. 进程内存有限,多个用户登录,访问量过大,内存暴增怎么办?
  2. 正式线上运行是多进程,进程之间内存无法共享,仍需重新登录

(b)解决方案:redis(内存数据库 )

  1. 是web server最常用的缓存数据库,数据存放在内存中(问题2)
  2. 相比于mysql ,访问速度快(内存和硬盘不是一个数量级的)
  3. 可扩展内存大小(问题1)

在这里插入图片描述

(c)为何session适合用redis ?

  • session访问频繁,对性能要求极高(数据存取快)
  • session 可不考虑断电丢失数据的问题(内存的硬伤)
  • session数据量不会太大(相比于mysql中存储的数据)

(d)为何网站数据不适合用redis ?

  • 操作频率不是太高(相比于session操作)
  • 断电不能丢失,必须保留
  • 数据量太大,内存成本太高

(e)redis安装

  • redis-server.exe redis.windows.conf ----- 服务启动方式
  • redis-cli ----- redis客户端(client)进行数据存取
    • set myname zhangsan ----- 存
    • get myname ----- 取
    • keys * 查看所有的key
    • del myname 删除myname的数据
    • flushdb 清空当前数据库中的所有 key
    • flushall 清空整个 Redis 服务器的数据(删除所有数据库的所有 key )

和前端联调

登录功能依赖cookie ,必须用浏览器来联调
cookie跨域不共享的,前端和server端必须同域
需要用到nignx做代理,让前后端端同域

正向代理:如,校园内网,我在家,通过正向代理的方式访问校园内网(客户端能控制得代理)
反向代理:(客户端控制不了,对于客户端是一个黑盒)

nginx命令
测试配置文件格式是否正确nginx -t
启动nginx ;重启nginx -s reload
停止nginx -s stop,

五、安全

  • sql注入∶窃取数据库内容
    XSS攻击:窃取前端的cookie内容
    密码加密∶保障用户信息安全(重要!)

  • server端攻击方式非常多,预防手段也非常多
    本课只讲解常见的、能迪wee
    有些攻击需要硬件和服务来支持(需要OP支持),如DDOS

  • 最原始、最简单的攻击,从有了web2.0就有了sql注入攻击
    攻击方式:输入一个sql片段,最终拼接成一段攻击代码
    预防措施:使用mysql的escape i函数处理输入内容即可

1、sql注入

改sql语句,使得有些语句部分注释

module.exports = {
    exec,
    escape:mysql.escape
};
const login = (username,password) => {
    // 防止sql注入,后果非常严重
    // select username, realname from users where username='zhangsan' -- ' and password='123456'
    // select username, realname from users where username='zhangsan';delete from users;-- ' and password='123456'
    username = escape(username);
    password = escape(password);
    // const sql = `select username, realname from users where username='${username}' and password='${password}'`;
    const sql = `select username, realname from users where username=${username} and password=${password}`;
    // console.log('sql is',sql);
    return exec(sql).then(rows => {
        // console.log(rows);
        return rows[0] || {};
    });
}

2、xss攻击

  • 攻击方式:在页面展示内容中掺杂js 代码,以获取网页信息
  • 预防措施∶转换生成js 的特殊字符

如:创建博客标题为

<script>alert(document.cookie)</script>

提交后,会在界面显示一个警告提示框,然后,发现新建的博客标题为空

  • 安装
npm i xss --save --registry=https://registry.npm.taobao.org
//防止xss攻击,将输入的内容的特殊字符转义
// const title = blogData.title;
const title = xss(blogData.title);
const content = xss(blogData.content);
const createtime = Date.now();
const author = blogData.author;
const sql = `insert into blogs (title,content,createtime,author) values ('${title}','${content}','${createtime}','${author}')`

3、密码加密

  • 万一数据库被用户攻破,最不应该泄漏的就是用户信息
  • 攻击方式:获取用户名和密码,再去尝试登录其他系统
  • 预防措施:将密码加密,即便拿到密码也不知道明文

src/utils/cryp.js

const crypto = require('crypto');

//密匙
const SECRET_KEY = 'Hzy_Hjy611'

//md5 加密
function  md5(content){
    let md5 = crypto.createHash('md5');
    return md5.update(content).digest('hex')
}

//加密函数
function genPassword(password){
    const str = `password=${password}&key=${SECRET_KEY}`
    return md5(str);
}

module.exports = {genPassword};
// const result = genPassword('1234');
// console.log(result);
//密码加密后放入数据库中
    password = genPassword(password);
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值