Node.js从零开发web server博客项目


Node.js从零开发web server博客项目

篇幅过长, 仅展示目录和部分代码

点击去GitHub查看完整版

开发接口(不用框架)

http请求概述

  • DNS 解析,建立 TCP 连接,发送 http 请求
  • server 接收到 http 请求,处理并返回数据
  • 客户端接收到返回数据,处理数据(例如渲染、执行JS)

Node.js 处理http 请求

  • get 请求和 querystring
  • post 请求和 postdata
  • 路由(接口、地址)
const http = require('http');
const server = http.createServer((req,res) => {
    res.end('hello world!');
});
server.listen(8000);
//浏览器访问 http://localhost:8000/

Node.js 处理 get 请求

  • get 请求,客户端向 server 端获取数据,如查询博客列表
  • 通过 querystring 来传递数据,如 a.html?a=100&b=200
  • 浏览器直接访问,发送 get 请求
const http = require('http');
const querystring = require('querystring');
const server = http.createServer((req,res) => {
    console.log(req.method) //GET
    const url = req.url //获取请求的完整 URL
    //关键解析[0]是'?'前的内容, [1]是'?'后内容
    req.query = querystring.parse(url.split('?')[1]) 
    res.end(JSON.stringify(req.query)); //将 querystring 返回
});
server.listen(8000);
//浏览器访问 http://localhost:8000/

Node.js 处理 post 请求

  • post 请求,即客户端要像服务端传递数据,如新建博客
  • 通过 post data 传递数据,后面解释
  • 浏览器无法直接模拟,需要手写JS,或者使用 postman app
const http = require('http')
const server = http.createServer((req, res) => {
    if (req.method === 'POST'){ 
        // POST 必须大写
        //数据格式
        console.log('content-type: ', req.headers['content-type'])
        //接收数据
        let postData = ''
        //开始接收数据
        req.on('data', chunk => {
			postData += chunk.toString()
        })
        //结束数据接收
        req.on('end' () => {
            console.log('postData:', postData)
            res.end('hello world!') //在这里返回,因为是异步
        })
    }
})
server.listen(300)

Node.js 处理路由

  • https://github.com/username/xxx 每个斜线后面的唯一标识就是路由

Node.js 综合应用

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

const server = http.createServer((req, res) => {
    const method = req.method
    const url = req.url
    const path = url.split('?')[0] //重点:split('?'[0])语法弄清楚
    const query = querystring.parse(url.split('?')[1])

    //设置返回值格式为 JSON
    res.setHeader('Content-type', 'application/json')
    
    //返回的数据
    const resData = {
        method,
        url,
        path,
        query
    }
    
    //返回
    if (method === 'GET') {
        res.end(
            JSON.stringify(req.query)
        )
    }
    
    if (req.method === 'POST'){
        let postData = ''
        //res.on('data')指每次发送的数据
        //chunk 逐步接收数据 req绑定一个data方法 chunk是变量
        req.on('data', chunk => {
            postData += chunk.toString()
        })
        //req.on(end)数据发送完成;
        req.on('end', () => {
            console.log('postData:', postData)
            console.log('resData:', resData)
            resData.postData = postData
            //返回
            res.end(
                JSON.stringify(resData)
            )
        })
    }
})

server.listen(300)
console.log('OK')

搭建开发环境

  • 从零搭建,不使用框架
  • 使用 nodemon 监测文件变化,自动重启 node
  • 使用 cross-env 设置环境变量,兼容Mac Linux 和 Windows
  • 配置完后使用 $ npm run dev 命令启动项目

开始搭建

使用npm安装上述插件,设置npm镜像源

  1. 查看npm源地址
    npm config list

  2. 结果:
    metrics-registry = "http://registry.npm.taobao.org/"

  3. 修改registry地址,比如修改为淘宝镜像源。
    npm set registry https://registry.npm.taobao.org/
    如果有一天你肉身FQ到国外,用不上了,用rm命令删掉它
    npm config rm registry

  4. npm install -g nodemon

  5. npm install -g cross-env

新建文件夹blog_1,在里面新建bin文件夹和app.js,在bin里面新建www.js文件

// package.json 代码 注意: =不能有空格
{
  "name": "blog_1",
  "version": "1.0.0",
  "description": "",
  "main": "www.js",
  "scripts": {
    "test": "echo \"Error: no test specified\" && exit 1",
    "dev": "cross-env NODE_ENV=dev nodemon ./bin/www.js",
    "prd": "cross-env NODE_ENV=production nodemon ./bin/www.js"
  },
  "author": "",
  "license": "ISC"
}
// ./bin/www.js 代码
const http = require('http')

const PORT = 300
const serverHandle = require('../app')
const server = http.createServer(serverHandle)
server.listen(PORT)
// app.js 代码
const handleBlogRouter = require('./src/router/blog')
const handleUserRouter = require('./src/router/user')

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

	// const resData = {
	// 	name: 'zhang',
	// 	site: 'imooc',
	// 	env: process.env.NODE_ENV
	// }

	// res.end(
	// 	JSON.stringify(resData)
	// )
	
	//处理 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

开发接口

初始化路由

  • 初始化路由:根据之前设计方案,做出路由
  • 返回假数据:将路由和数据处理分离,以符合设计原则

接口设计方案

描述 接口 方法 url参数 备注
获取博客列表 /api/blog/list get author 作者,keyword 搜索关键字 参数为空则不进行查询过滤
获取一篇博客的内容 /api/blog/detail get id
新增一篇博客 /api/blog/new post post 中有新增的信息
更新一篇博客 /api/blog/update post id postData 中有更新信息
删除一篇博客 /api/blog/del post id
登录 /api/user/login post postData 中有用户名和密码

具体代码:

// ./src/router/user.js
const handleUserRouter = (req, res) => {
	const method = req.method //GET POST

	//登录
	if (method === 'POST' && req.path === '/api/user/login') {
		return {
			msg: '这是登录的接口'
		}
	}
}

module.exports = handleUserRouter

// ./src/router/blog.js
const handleUserRouter = (req, res) => {
	const method = req.method //GET POST

	//登录
	if (method === 'POST' && req.path === '/api/user/login') {
		return {
			msg: '这是登录的接口'
		}
	}
}

module.exports = handleUserRouter

开发路由 博客列表

  1. 业务分层 拆分业务

    • createServer 业务单独放在 ./bin/www.js
    • 系统基本设置、基本信息 app.js 放在根目录
    • 路由功能 ./src/router/xxx.js
    • 数据管理 ./src/contoller/xxx.js
    • 数据处理
  2. 博客列表代码

开发路由 博客详情

  • 博客代码同上一章

  • 使用 promise 读取文件,避免 callback-hell

开发路由 (处理POSTData)

开发路由 (新建和更新博客路由)

开发路由 (删除博客路由和登录博客路由)

  • 删除博客

  • 登录博客

总结

  • node.js 处理 http 请求的常用技能,postman 的使用

  • node.js 开发博客项目的接口(未连接数据库,未登录使用)

  • 为何要将 router 和 controller 分开?

  • 路由和 API 区别:

    • API :前后端、不同端(子系统)之间对接的通用术语
    • 路由:系统内部的接口定义,是 API 的一部分

使用MySQL数据库

MySQL安装

讲解步骤:

  1. MySQL 的介绍、安装和使用

  2. node.js 连接 MySQL

  3. API 连接 MySQL

为什么使用MySQL?

  • MySQL 最常用,有专人运维
  • MySQL 有问题可以随时查到
  • MySQL 本身是复杂的,本课只讲使用

MySQL 介绍:

  • web server 中最流行的关系型数据库
  • 免费下载学习
  • 轻量级,易学易用

MySQL 下载: https://dev.mysql.com/downloads/mysql/

MySQL 安装:

  • 解压,打开根目录初始化my.ini 文件, 自行创建在安装根目录下创建my.ini
[mysqld]
# 设置3306端口
port=3306
# 设置mysql的安装目录
basedir=C:\Program Files\MySQL
# 设置mysql数据库的数据的存放目录
datadir=C:\Program Files\MySQL\Data
# 允许最大连接数
max_connections=200
# 允许连接失败的次数。
max_connect_errors=10
# 服务端使用的字符集默认为utf8mb4
character-set-server=utf8mb4
# 创建新表时将使用的默认存储引擎
default-storage-engine=INNODB
# 默认使用“mysql_native_password”插件认证
#mysql_native_password
default_authentication_plugin=mysql_native_password
[mysql]
# 设置mysql客户端默认字符集
default-character-set=utf8mb4
[client]
# 设置mysql客户端连接服务端时默认使用的端口
port=3306
default-character-set=utf8mb4

配置文件中的路径要和实际存放的路径一致(要手动创建Data文件夹)

  • 打开系统设置,配置环境变量 ` Path = ‘解压目录’\bin

  • 初始化安装: mysqld --initialize --console

    注意输出信息: root @ localhost:后面是初始密码(不含首位空格),后续登录需要用到,复制密码先保存起来

  • 安装MySQL 服务: mysqld --install[服务名] 不填默认mysql

  • 启动MySQL: net start mysql

使用官方客户端管理mysql

  • Wrokbench 下载地址:https://dev.mysql.com/downloads/

  • 默认安装,打开后输入之前保存的默认密码登录

  • 弹出修改密码界面,修改密码再登录

MySQL基本使用

根据需求设计表

users:

id username password realname
1 zhangsan 123 张三
2 lisi 1234 李四

blogs:

id title content createtime author
1 标题A 内容A 1573989043149 zhangsan
2 标题B 内容B 1573989111301 lisi

MySQL语法和操作

右键表 Drop table 删除

右键表 Alter table 修改

总结

  • 如何建库、如何建表
  • 建表时常用数据类型( int bigint varchar longtext)
  • SQL 语句实现增删改查

Node.js 操作 MySQL

  1. 示例:用 demo 演示 Node.js 操作 MySQL

  2. 封装:将其封装为系统可用的工具

  3. 使用:让 API 直接操作 MySQL

Node.js 操作 MySQL demo

总结

  • Node.js 连接 MySQL,如何执行 sql 语句
  • 根据 NODE_ENV 区分设置
  • 封装 exec 函数,API 使用 exec 操作数据库

用户登录

  • 核心:登录校验 & 登录信息存储
  • 为何只讲登录,不讲注册?
    • 注册复杂程度低,涉及内容少
    • 登录有统一解决方案
  • 什么是 Cookie

Session

  • Cookie 存放信息非常危险
  • 如何解决:cookie 中存储 userId, server 端对应 username
  • 解决方案:session ,即 server 端储存用户信息

当前代码 session 代码的问题

  • session 是 JS 变量,放在 Node.js 进程内存中
  • 进程内存有限,访问量过大,内存暴增怎么办?
  • 正式上线是多进程,进程之间内存无法共享

Redis

Redis 特点

  • Web Server 最常用的缓存数据库,数据储存在内存中
  • 相比于 MySQL ,访问速度极快
  • 成本更高,储存空间小
  • 将 Web Server 和 Redis 拆分为两个单独服务
  • 双方独立,可扩展
  • 像 MySQL 一样

安装 Redis

打开系统设置,配置环境变量 Path = C:\Program Files\Redis

开发登录 前端联调

  • 登录依赖 Cookie,必须用浏览器
  • Cookie 跨域不共享,前端和 server 端必须同域
  • 需要用到 Nginx 做代理,让前后端共域

Stream

  • IO 操作的性能瓶颈
    • IO 包括 “网络 IO” 和 “文件 IO”
    • 相对于 CPU 计算和内存读写, IO 的突出特点就是:慢
    • 如何在有限的硬件资源下提高 IO 的操作效率

写日志

日志拆分

  • 日志内容会慢慢积累,放在一个文件中不好处理
  • 按时间划分日志文件,如 2019-02-10.access.log
  • 实现方式:Linux 的 crontab 命令,即定时任务

Crontab

  • 设置定时任务,格式: ***** command

    *分钟*小时*天*月*星期 command脚本命令
  
- 将 access.log 拷贝并重命名为 2019-02-10.access.log 
  
  - 清空 access.log 文件,继续积累日志

代码演示

总结

  • 日志对 server 的重要性
  • IO 性能瓶颈,使用 stream 提高性能, node.js 中如何操作
  • 使用 Crontab 拆分日志,使用 readline 分析日志内容

Web 安全

常见安全问题和解决方案

  • SQL 注入:窃取数据库内容
  • XSS攻击:窃取前端的 Cookie 内容
  • 密码加密:保障用户信息安全(重要)
  • Server 端攻击方式非常多,预防手段也非常多
  • 本科只讲常见的、能通过 Web Server ( Node.js ) 层面预防的
  • 有些攻击需要硬件和服务来支持(需要 OP 支持),如 DDOS

SQL 注入

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

不用框架开发博客总结

主要课程

  1. 处理 Http 接口
  2. 连接数据库
  3. 实现登录
  4. 安全
  5. 日志
  6. 上线(最后一起讲)

Server 和前端区别

  • 服务稳定性(zui后讲)
  • 内存 CPU (优化 扩展)
  • 日志记录
  • 安全(包括登录验证)
  • 集群和服务拆分

下一步要怎么做

  • 不使用框架开发,从零开始,关注底层 API
  • 很琐碎、复杂,没有标准,很容易写乱
  • 适合学习,但不适合应用,接下来开始 Express 和 Koa2

Express 框架

Express 介绍

  • Express 是 Node.js 最常用的 Web Server 框架
  • 什么是框架?
  • 不要以为 Express 框架过时了

目录

  • Express 下载、安装和使用,了解 Express 中间件机制
  • 开发接口、连接数据库、实现登录、记录日志
  • 分析 Express 中间件原理

介绍 Express

  • 安装(使用脚手架 Express-grnerator)
  • 初始化代码介绍,处理路由
  • 使用中间件

总结

  • 使用框架开发的好处(相比之前不使用框架)
  • express 的使用和路由处理,以及操作 session redis 日志等
  • express 中间件的使用和原理

下一步

  • JS 的异步回调带来了很多问题,Promise 也不能解决所有
  • Node.js 已经支持 async/await 语法,要用起来
  • Ko2 也已经原生支持 async/await 语法,接下来讲解

Koa2 框架

总结

  • 使用 async/await 的好处
  • koa2 的使用,以及如何操作 session redis 日志
  • koa2 中间件的使用和原理

下一步:

  • 一直处于开发环境,和前端联调过,但从未上线
  • 如何实现线上服务稳定性?PM2 是什么?
  • nginx 在线上环境扮演了什么重要作用?

线上环境

  • 服务器稳定性
  • 充分利用服务器硬件资源,以便提高性能
  • 线上日志记录

PM2 功能:

  • 进程守护,系统崩溃自动重启
  • 启动多进程,充分利用 CPU 和内存
  • 自带日志记录功能

多进程

  • 为何使用多进程?
    • 回顾之前讲 session 时说过, 操作系统限制一个进程的内存
    • 内存:无法充分利用机器全部内存
    • CPU:无法充分利用多核CPU
  • 多进程和 redis
    • 多进程之间,内存无法共享
    • 多进程访问一个 redis ,实现数据共享

关于服务器运维

  • 服务器运维,一般都由专业的 OP 人员和部门来处理
  • 大公司都有自己的运维团队
  • 中小型工期推荐使用一些云服务,如阿里云的 node 平台

总结

  • PM2 的核心价值
  • PM2 的常用命令和配置,日志记录
  • 多进程

课程总结


文章作者: Eish
版权声明: 本博客所有文章除特別声明外,均采用 CC BY 4.0 许可协议。转载请注明来源 Eish !
  目录