搭建流程(按新建顺序)层层嵌套引入
系统架构设计的四层抽象:
- 第一层: www.js [开启 Server]
- 第二层:app.js [通信设置层]
- 第三层:router.js [业务逻辑层]
- 第四层:controller.js [数据层]
总体结构:
- bin/www.js(第一层):node构建服务的基本流程配置,和业务无关。package里面配置好的入口文件
- app.js(第二层)
- src
- router(第三层:根据不同路由返回不同数据)
- blog.js:博客crud接口
- user.js:用户相关的接口
- control(第四层:返回数据给到路由这边,只关心数据及其处理,一般是和数据库这边交互)
- model(数据模型:成功、失败返回的数据格式等)
- conf:配置
- db.js:获取环境参数对数据库进行配置,根据开发环境下的和线上环境使用不同的数据库
- db
- mysql.js:对数据库的基本操作(连接创建关闭,不涉及业务逻辑)
- redis.js: 封装redis的set、get操作
- utils:工具
- log.js:专门用来写日志的工具
- router(第三层:根据不同路由返回不同数据)
- logs:日志
- access.log:存储访问日志
- event.log:存储一些自定义内容
- error.log:存储错误内容
记录
session和Redis
一、session不足:
- session是存储在nodeJS进程的内存中的
- 进程内存有限,session过大,会把进程挤爆
- 进程与进程之间内存相互独立,无法共享,因此session也无法在多进程之间共享
解决:将session存储在Redis中
- Redis 是另外一个服务,和web server的nodeJS 进程没有关系了
- Redis类似于数据库,也是一个数据仓库,用来存储数据的(可以叫做内存数据库吧)
- 但是普通数据库的数据是存储在硬盘上的,访问速度慢
- Redis上的数据是存储在内存中的,访问速度快,但昂贵
二、为何session适合用Redis存储
- session访问频繁,对性能要求极高
- session可不考虑断点丢失数据问题(内存的硬伤),因为大不了重新登录
- session数据量不会太大,一般只是存储用户的个人信息(相比于 MySQL 中 存储的数据)
三、开启redis服务:
打开一个 cmd 窗口 使用 cd 命令切换目录到redis目录下 运行
redis-server.exe redis.windows.conf
接口和前端联调
- 登陆功能依赖cookie,必须用浏览器来联调(postman无法做到)
- cookie跨域不共享,前端和server端必须同域
- 因此需要用到Nginx 做代理,让前后端同域
Nginx介绍
- 高性能的 web 服务器,开源免费
- 一般用于做静态服务(CDN)、负载均衡
- 反向代理
如何配置反向代理:
- 要同时开启对应的服务(占用不同端口):nodeJS server服务8000端口(后端接口)、http-server服务8001端口(前端页面)
- 监听8080端口
- 反向代理配置
nginx.conf
配置文件:
location / {
# 如果是根目录(即 http://localhost:8080)则代理到 8001 端口
proxy_pass http://localhost:8001;
}
location /api/ {
# 如果是访问接口,则代理到 8000 端口
proxy_pass http://localhost:8000;
proxy_set_header Host $host;
}
- 通过访问
http://localhost:8080/index.html
即可
ps:
- 开启nginx 服务:
start nginx
- 开启http-server服务:
http-server -p8001
- 开启nodeJs server服务:
npm run dev
Nginx命令
- start nginx:启动nginx服务
- nginx -t:测试配置文件nginx.conf 语法是否正确
express搭建
构建流程
npm install -g express -generator
全局安装express命令安装工具express 项目名
npm install
安装组件npm start
启动项目(服务器)npm i nodemon cross-env
写自己的业务逻辑:
- 新建路由文件,针对不同路由进行业务逻辑处理
- 将新建的路由文件(比如
blog.js
)引入app.js
文件中,并使用app.use
注册我们的路由
相对于原生nodejs:
- 直接通过
req.query
获取get传过来的参数,通过req.body
获取 post传过来的参数 - 通过
res.json
直接返回json数据给客户端 - 使用
express-session
、connect-redis
、登陆中间件 - 使用
morgan
来记录日志,根据配置决定将日志输出到控制台还是文件中
koa2
构建流程
npm install koa-generator -g
全局安装express命令安装工具Koa2 项目名
npm install
安装组件npm i cross-env
npm run dev
启动项目(服务器)
相对于express和原生nodejs:
- 直接通过
ctx.query
获取get传过来的参数,通过ctx.request.body
获取 post传过来的参数 - 通过
ctx.body
直接返回json数据给客户端 - 通过
async/await
来实现中间件 - 通过
await next()
来执行下一个中间件 - 相比
express
,koa2在记录日志时要手动安装koa-morgan
插件
中间件
一、express中间件
- 其实中间件就是一个这样格式的函数,三个参数
req, res, next
function loginCheck(req, res, next) {
console.log('模拟登陆成功')
next()
}
- 可以通过
app.use()
、app.get()
、app.post()
注册中间件 - 可以注册多个中间件,依次执行
- 通过
next()
的执行一个一个的往下串联下一个中间件
实现原理思路:
app.use
用来注册中间件,先收集起来- 遇到
http
请求,根据path
、method
判断触发哪些中间件 - 实现
next()
机制,即上一个通过next()
触发下一个
// 实现类似 express 的中间件
const http = require('http')
const slice = Array.prototype.slice
class LikeExpress {
constructor() {
// 收集存放中间件的列表
this.routes = {
all: [], // app.use(...)
get: [], // app.get(...)
post: [] // app.post(...)
}
}
register(path) {
const info = {}
if (typeof path === 'string') {
info.path = path
// 从第二个参数开始,转换为数组,存入 stack
info.stack = slice.call(arguments, 1)
} else {
info.path = '/'
// 从第一个参数开始,转换为数组,存入 stack
info.stack = slice.call(arguments, 0)
}
return info
}
// 中间件注册和收集
use() {
const info = this.register.apply(this, arguments)
this.routes.all.push(info)
}
get() {
const info = this.register.apply(this, arguments)
this.routes.get.push(info)
}
post() {
const info = this.register.apply(this, arguments)
this.routes.post.push(info)
}
// 通过当前 method 和 url 来匹配当前路由可执行的中间件
match(method, url) {
let stack = []
if (url === '/favicon.ico') {
return stack
}
// 获取 routes
let curRoutes = []
curRoutes = curRoutes.concat(this.routes.all)
curRoutes = curRoutes.concat(this.routes[method])
curRoutes.forEach(routeInfo => {
if (url.indexOf(routeInfo.path) === 0) {
// url === '/api/get-cookie' 且 routeInfo.path === '/'
// url === '/api/get-cookie' 且 routeInfo.path === '/api'
// url === '/api/get-cookie' 且 routeInfo.path === '/api/get-cookie'
stack = stack.concat(routeInfo.stack)
}
})
return stack
}
// 核心的 next 机制
handle(req, res, stack) {
const next = () => {
// 拿到第一个匹配的中间件
const middleware = stack.shift()
if (middleware) {
// 执行中间件函数
middleware(req, res, next)
}
}
next()
}
callback() {
return (req, res) => {
res.json = (data) => {
res.setHeader('Content-type', 'application/json')
res.end(
JSON.stringify(data)
)
}
const url = req.url
const method = req.method.toLowerCase()
const resultList = this.match(method, url)
this.handle(req, res, resultList)
}
}
listen(...args) {
const server = http.createServer(this.callback())
server.listen(...args)
}
}
// 工厂函数
module.exports = () => {
return new LikeExpress()
}
二、koa2中间件
- koa2中间件其实就是一个
async
函数,参数为(ctx, next)
app.use(async (ctx, next) => {
await next();
ctx.body = 'Hello World';
});
实现思路:
- 也是使用
app.use
来注册中间件,先收集起来 - 实现
next
机制,即上一个通过await next()
触发下一个中间件 - 不涉及
method
和path
的判断
<!--实现类似 靠中间件-->
const http = require('http')
// 组合中间件
function compose(middlewareList) {
return function (ctx) {
function dispatch(i) {
const fn = middlewareList[i]
try {
return Promise.resolve(
fn(ctx, dispatch.bind(null, i + 1)) // promise
)
} catch (err) {
return Promise.reject(err)
}
}
return dispatch(0)
}
}
class LikeKoa2 {
constructor() {
this.middlewareList = []
}
// 收集中间件列表
use(fn) {
this.middlewareList.push(fn)
return this
}
createContext(req, res) {
const ctx = {
req,
res
}
ctx.query = req.query
return ctx
}
handleRequest(ctx, fn) {
return fn(ctx)
}
callback() {
const fn = compose(this.middlewareList)
return (req, res) => {
const ctx = this.createContext(req, res)
return this.handleRequest(ctx, fn)
}
}
listen(...args) {
const server = http.createServer(this.callback())
server.listen(...args)
}
}
module.exports = LikeKoa2
node线上环境
PM2
- 进程守护,系统奔溃自动重启
restart
,而不是说系统出错之后其他用户就无法使用了 - 启动多进程,充分利用cpu 和内存
- 自带日志记录功能
下载安装
cnpm i pm2 -g
配置命令
"prd": "cross-env NODE_ENV=production pm2 start app.js"
启动
npm run prd
常用命令
- pm2 start … 启动进程
- pm2 list: 可以查看pm2进程列表
- pm2 restart appNme/id : 重启进程
- pm2 stop appName/id : 停止
- pm2 delete appName/id : 删除
- pm2 info appName/id :查看基本信息
- pm2 log appName/id :查看进程日志
- pm2 monit appName/id:监控进程的cpu和内存信息
pm2配置文件
- 包括进程数量、日志文件目录等
- 修改pm2 启动命令,重启
- 访问 server,检查日志文件的内容(日志记录是否生效)
{
"apps": {
"name": "pm2-test-server", // 进程名
"script": "app.js", //用框架就是'bin/www'
"watch": true, // 监听文件变化,是否自动重启
"ignore_watch": [ // 哪些文件不需要重启
"node_modules",
"logs"
],
"instances": 4, // 进程个数,这里
"error_file": "logs/err.log", // 错误日志存放位置
"out_file": "logs/out.log", // 本来打印在控制台的console自定义存放在文件里
"log_date_format": "YYYY-MM-DD HH:mm:ss" // 日志的时间戳
}
}
多进程
为何使用多进程
- 操作系统会限制一个进程的最大可用内存。因为按照软件设计拆分这种模式来说,如果一个进程做很多很多的事情,该进程万一奔溃,这可是天灾后果。因此需要分为多个进程,进程之间相互独立,提高了服务器稳定性
- 因此多进程可以充分利用机器的内存,充分发挥多核CPU的优势(同时处理多个进程)
多进程和redis
- 多进程之间的session无法共享
- 采用共享redis 来解决