node.js express koa 学习笔记

node.js 课程

第二章 node.js 介绍

2.1 下载和安装

nvm(Node.js version manager) 在github下载

几个指令

+   nvm list
+   nvm install v10.1.0
+   nvm use --delete-prefix 切换版本

2.2 nodejs 和 JavaScript 区别

ECMAScript  是语法基础 定义了语法规范

JavaScript 是  ECMAScript + web API

nodejs 是 ECMAScript + nodeAPI

2.3 commonjs 演示

实现引入导出
module.exports = function add(){}
const add = require("path")
1. 建立两个文件通过b文件引入a文件中的函数 实现 求和
2. 导出多个函数 分别求和 和 求乘(利用解构的语法)
3. 引入npm 包
安装lodash
1. npm init -y //npm 初始化
2. npm i lodash
3. 引入 lodash
4. 合并数组 concat([1,2],3)

2.4 debugger

const http=require('http')
const server = http.createServer((req,res)=>{
    res.writeHead(200,{"contetnt-type":"text/html"})
    res.end("<h1>Hello World</h1>")
})
server.listen(3000,()=>{
    console.log("listening on 3000 prot")
})

第三章 项目介绍

3.1 项目需求分析

一、目标

开发一个博客系统,具有博客的基本功能

只开发server端,不关心前端

二、需求 
  1. 首页,作者主页,博客详情页
  2. 登录页
  3. 管理中心,新建页,编辑页

3.2 技术方案

1、数据如何存储

2、如何与前端对接,接口设计

第四章 开发博客项目接口

4.1 http 概述

  1. 从输入url到显示页面的整个过程

    1. DNS解析,建立TCP链接,发送http请求
    2. server接收到请求 ,处理, 返回
    3. 客户端接收到返回数据,处理数据(如执行页面渲染,执行js)

    几个单词

    1. request 请求

    2. response 相应

4.2 nodejs 处理get请求

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]);
    console.log("req.query", req.query);
    res.end(JSON.stringify(req.query));
});

server.listen(8000, () => {
    console.log("OK");
});

4.3 nodejs处理post请求

  1. 安装postman 发送数据请求
const http = require("http");

const server = http.createServer((req, res) => {
    console.log("获取到请求");
    if (req.method === "POST") {
        console.log("log content-type:", req.headers["content-type"]);
        let dataString = "";
        req.on("data", (chunk) => {
            dataString += chunk.toString();
        });
        req.on("end", () => {
            console.log("dataString:", dataString);
            res.end("hello world");
        });
    }
});

server.listen(3000, () => {
    console.log("ok");
});

4.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 path = url.split("?")[0];
    const query = querystring.parse(url.split("?")[1]);

    res.setHeader("Content-type", "application/json");

    const resData = {
        method,
        url,
        path,
        query,
    };

    if (method === "GET") {
        res.end(JSON.stringify(resData));
    }

    if (method === "POST") {
        let postData = "";
        req.on("data", (chunk) => {
            postData += chunk.toString();
        });
        req.on("end", () => {
            resData.postData = JSON.parse(postData);
            res.end(JSON.stringify(resData));
        });
    }
});

server.listen(3000, () => {
    console.log("ok");
});

4.5 搭建开发环境

  1. 新建项目路径 并搭建基础代码

    • 项目目录

      |-- blog-1
        |-- bin
            |-- www.js  // 项目入口 服务器创建
        |-- res
            |-- router // 处理路由
                |-- blog.js 
                |-- user.js
            |-- model   // 定义数据格式
                |-- resModel.js
            |-- controller // 业务逻辑
                |-- blog.js
        |-- app.js  // 服务器配置等
        |-- package.json
      
    • www.js

      const http = require("http");
      const PORT = 3000;
      const serverHandle = require("../app");
      
      const server = http.createServer(serverHandle);
      
      server.listen(PORT, () => {
          console.log("node server ok");
      });
      
      
      
    • app.js

      const serverHandle = (req, res) => {
          res.setHeader("Content-type", "application/json");
      
          const resData = {
              name: "黑兔",
              age: "23",
          };
      
          res.end(JSON.stringify(resData));
      };
      
      module.exports = serverHandle;
      
      
  1. 使用nodemon 监听文件变化,自动重启node

  2. 使用 cross-env 设置环境变量,兼容 mac linux 和 windows

    1. 安装依赖 npm install nodemon cross-env --save-dev
    2. 配置启动命令 
    "dev": "cross-env NODE_ENV=dev nodemon ./bin/www.js"
    3. 使用配置的node_env 
    process.env.NODE_ENV
    
  1. 单词

    • Handle

4.6 初始化路由

  1. blog.js

    const handleBlogRouter = (req, res) => {
        const method = req.method;
        const url = req.url;
        const path = url.split("?")[0];
    
        console.log("获取到的路径:", path);
        // 获取博客列表
        if (method == "GET" && path == "/api/blog/list") {
            return {
                msg: "调用博客列表",
            };
        }
    
        // 获取博客详情
        if (method == "GET" && path == "/api/blog/detailed") {
            return {
                msg: "获取博客详情",
            };
        }
        // 创建博客
        if (method == "POST" && path == "/api/blog/create") {
            return {
                msg: "创建博客",
            };
        }
        // 删除博客
        if (method == "POST" && path == "/api/blog/delete") {
            return {
                msg: "删除博客",
            };
        }
        // 修改博客
        if (method == "POST" && path == "/api/blog/update") {
            return {
                msg: "修改博客",
            };
        }
    };
    
    module.exports = handleBlogRouter;
    
    
  1. user.js

    const handleUserRouer = (req, res) => {
        const method = req.method;
        const url = req.url;
        const path = url.split("?")[0];
    
        // 用户登录
        if (method == "POST" && path == "/api/user/login") {
            return {
                msg: "调用用户登录aip",
            };
        }
    };
    
    module.exports = handleUserRouer;
    
    
  1. app.js

    const handleBlogRouter = require("./src/router/blog");
    const handleUserRouer = require("./src/router/user");
    
    const serverHandle = (req, res) => {
        res.setHeader("Content-type", "application/json");
    
        const blogData = handleBlogRouter(req, res);
        if (blogData) {
            res.end(JSON.stringify(blogData));
            return;
        }
    
        const userData = handleUserRouer(req, res);
        if (userData) {
            res.end(JSON.stringify(userData));
            return;
        }
    
        res.writeHeader(404, { "Content-type": "text/plan" });
        res.write("404 Not");
        res.end();
    };
    
    module.exports = serverHandle;
    
    

4.7 开发路由(博客列表路由)

  1. 定义数据格式

    class BaseModel {
       constructor(data, message) {
           if (typeof data == "string") {
               this.message = data;
               data = null;
               message = null;
           }
           if (data) {
               this.data = data;
           }
           if (message) {
               this.data = data;
           }
       }
    }
    
    class SuccessModel extends BaseModel {
       constructor(data, message) {
           super(data, message);
           this.errno = 0;
       }
    }
    
    class ErrorModel extends BaseModel {
       constructor(data, message) {
           super(data, message);
           this.erron = -1;
       }
    }
    
    module.exports = {
       SuccessModel,
       ErrorModel,
    };
    
    
  2. 创建controller(控制器)文件夹

    const getList = (author, keyword) => {
        return [
            {
                id: 1,
                title: "标题A",
                contentText: "内容eAAAnei",
                createTime: "1635941708713",
                author: "张三",
            },
            {
                id: 1,
                title: "标题A",
                contentText: "内容eAAAnei",
                createTime: "1635941708713",
                author: "张三",
            },
        ];
    };
    
    module.exports = {
        getList
    };
    
  1. 返回数据

           ~~~javascript
        if (method == "GET" && req.path == "/api/blog/list") {
            const author = req.query.author || "";
            const keyword = req.query.keyword || "";
            const listData = getList(author, keyword);
            return new SuccessModel(listData);
        }
        ~~~
    

4.8博客详情页开发

  1. Promise 简单讲解callback 方式

    const fs = require("fs");
    const path = require("path");
    
    // callback 方式获取一个文件内容
    
    function getFileContent(fileName, callback) {
        const fullFileName = path.resolve(__dirname, "files", fileName);
        fs.readFile(fullFileName, (err, data) => {
            if (err) {
                console.log(err);
                return;
            }
            callback(JSON.parse(data.toString()));
        });
    }
    
    getFileContent("a.json", (adata) => {
        console.log("a data:", adata);
        getFileContent(adata.next, (bdata) => {
            console.log("b data:", bdata);
            getFileContent(bdata.next, (cdata) => {
                console.log("c data:", cdata);
            });
        });
    });
    
  2. promise 方式

~~~javascript
function getFileContent(fileName) {
    const promise = new Promise((resolve, reject) => {
        const filePath = path.resolve(__dirname, "files", fileName);
        fs.readFile(filePath, (err, data) => {
            if (err) {
                reject(err);
                return;
            }
            return resolve(JSON.parse(data.toString()));
        });
    });
    return promise;
}

getFileContent("a.json")
    .then((data) => {
    console.log("a data:", data);
    return getFileContent(data.next);
})
    .then((bdata) => {
    console.log("b data:", bdata);
    return getFileContent(bdata.next);
})
    .then((cdata) => {
    console.log("cdata", cdata);
});

~~~

4.9 处理post 请求

  1. 通过promise 解析post 数据

    const getPostData = (req) => {
        const promise = new Promise((resolve, reject) => {
            if (method !== "POST") {
                resolve({});
                return;
            }
    
            if (req.headers["Content-type"] !== "application/json") {
                resolve({});
                return;
            }
    
            let postData = "";
    
            req.on("data", (chunk) => {
                postData += chunk.toString();
            });
    
            req.on("end", () => {
                if (postData) {
                    resolve({});
                    return;
                } else {
                    resolve(JSON.parse(postData));
                }
            });
        });
        return promise;
    };
    
  2. 使用 处理数据

getPostData(req).then((postData) => {
    req.body = postData;

    // 创建博客
    if (method == "POST" && req.path == "/api/blog/create") {
        return {
            msg: "创建博客",
        };
    }
    // 删除博客
    if (method == "POST" && req.path == "/api/blog/delete") {
        return {
            msg: "删除博客",
        };
    }
    // 修改博客
    if (method == "POST" && req.path == "/api/blog/update") {
        return {
            msg: "修改博客",
        };
    }
});

4.12 补充路由和Api

路由 和 api
  • API: 前段和后端,不同端(子系统)直接对接的一个术语

  • 路由: API 的一部分 后端系统内部的一个定义

第五章 数据存储

5.1 MySql 介绍

1.  下载mysql
1.  下载workBench
1.  查看全部的表 show  databases

5.2 数据库操作

  • 数据库缩写
    1. PK primary key 主键
    2. NN not null 是否为空
    3. UQ unique 外键
    4. BIN binary 二进制
    5. UN unsigned 无符号
    6. ZF zero fill 补零
    7. AI auto increment 自动增加

简单的指令
1. show databases // 查看数据库
2. use // 数据名称
3. show tables // 显示数据库
4. SET SQL_SAFF_UPDATAS = 0 // 关闭数据库安全检查
5. <> // 设置不等于零

  • 指令:

      1. insert into 表名 (参数一,参数二,.....) values(参数一,参数二,..)
      1. delete from users where username="lisi"
      1. update users set realname = "测试"
      2. update users set realname = "李四" where username="lisi"
      1. select * from 表名; //查询全部
      2. select 列名,列名 from 表名 // 查询某几列
      3. select * from 表名 where 列名=值 // 具有筛选条件的
      4. select * from 表名 where 列名=值 and 列名=值 // 多重与查询
      5. select * from 表名 where 列名=值 or 列名=值 // 多重或查询
      6. select * from 表名 where 列名 like %值% // 模糊查询
      7. select * from 表名 order by 列名 // 排序 desc

5.4 nodejs 操作数据库

1. 新建一个项目 (初始化)
2. 安装mysql 依赖
- npm install mysql
3. 引入mysql
    1. const con = require('mysql')
4. 创建链接对象
+ con = mysql.createConnection({})
  +  host:"localhost" //域地址
  + user:"root"
  + password:"root"
  + port:"3306"
  + database:"myblog"
5. 开始链接数据库
    1. con.connect()
6. 执行sql语句
    1.  const sql = `'select * from users'`
    2.  con.query(sql,(err,result)=>{})
7. 关闭链接
    1.  con.end()

5.5 nodejs 链接mysql 做成工具

  1. 获取环境变量
  • 链接 const env = process.env.NODE_ENV
  1. 新建两个文件

  2. config

    const env = process.env.NODE_ENV; // 获取环境参数
    
    let MYSQL_CONF;
    
    if (env === "dev") {
        console.log("链接dev:");
        MYSQL_CONF = {
            host: "localhost",
            user: "root",
            password: "root",
            port: "3306",
            database: "myblog",
        };
    }
    
    if (env === "production") {
        console.log("链接production:");
        MYSQL_CONF = {
            host: "localhost",
            user: "root",
            password: "root",
            port: "3306",
            database: "myblog",
        };
    }
    
    module.exports = {
        MYSQL_CONF,
    };
    
    
  3. mysql.js

  ~~~ javascript
  const mysql = require("mysql");
  
  const { MYSQL_CONF } = require("../conf/db.js");
  
  // 创建链接对象
  const con = mysql.createConnection(MYSQL_CONF);
  
  // 开始链接
  con.connect();
  
  // 执行sql的函数
  function exec(sql) {
    const promise = new Promise((resolve, reject) => {
      con.query(sql, (err, result) => {
        console.log("数据库获取回来的数据:", sql, err, result);
        if (err) {
          reject(err);
          return;
        }
        resolve(result);
      });
    });
  
    return promise;
  }
  
  module.exports = {
    exec,
    escape: mysql.escape,
  };
  
  ~~~

第六章 登录

6.1 cook介绍

  1. 什么是cookie

    1. 存储在浏览器中的一段字符串(最大5kb)
    2. 跨越不共享
    3. 格式如 k1=v1;k2=v2; k3=v3
    4. 没次发送http请求,会将请求的cookie 一起发送给server
    5. server 可以修改cookie 并返回给浏览器
    6. 浏览器中也可以通过JavaScript修改cookie(有限制)
  2. JavaScript 操作 cookie 浏览器中查看cookie

    1. 查看cookie 的三种方式
    2. 直接在请求中查看
    3. 在application 中查看
    4. document.cookie
    5. 操作cookie的方式
    6. document.cookie = "k1=100"
    7. 只能以累加的方式修改
  3. server 端操作 cookie, 实现登录验证

    1. 查看cookie

      1. const cookieStr = req.headers.cookie
    2. 修改cookie

      1. res.serHeader("Set-Cookie",username = zhangshan; path=/; httpOnly)
      2. 注意设置 path
      3. 设置httpOnly 实现禁止前端修改
      4. 设置expires过期时间
        • expires = ${getCookieExpires()}
    3. 设置cookie过期时间

      const getCookieExpires = ()=>{
          const d = new Date()
          d.setTime(d.getTime() + (24 * 60 * 60 * 1000))
          return d.toGMTString()
      } 
      

6.2 session介绍学习

  1. 设置session的作用
    1. cookie不够安全 seesion 用于存储用户数据 前端存储虚拟的报文

    2. 加密数据 解决cooki不安全的问题

    3. 设置 session data 存储 数据

    4. session 的问题
      1. 进程内存有限 访问量过大 内存暴增
      2. 正式上线运行是多进程,进程之间内存无法共享

6.3 redis(内存数据库) 学习

web server 最常用的缓存数据库,数据放在内存中

优点读写特别快 缺点 内存很贵 存储空间小

  1. session 访问频繁 对性能要求极高
  2. session 可不考虑断电数据丢失的问题(内存的硬伤)也可以断电不丢失 要配置
  3. session 数据量不会太大(相比于MySQL中的数据)

安装redis

windows https://www.runoob.com/redis/redis-install.html 安装教程

redis 命令

启动命令 
  1. redis-server.exe redis.windows.conf
  2. redis-cli.exe -h 127.0.0.1 -p 6379
  3. set myKey abc
  4. get myKey
  5. keys *

6.4 nodejs 链接 redis 的demo

  1. 安装 redis
    1. npm i redis@3.1.2 --save

    2. const redis = require("redis");
      
      // 创建redis客户端
      const redisClient = redis.createClient(6379, "127.0.0.1");
      // 监听错误
      redisClient.on("err", (err) => {
        console.error(err);
      });
      
      // 测试
      redisClient.set("myname", "zhangshan1", redis.print);
      redisClient.get("myname", (err, val) => {
        if (err) {
          console.log("err:", err);
          return;
        }
        console.log("val:", val);
        redisClient.quit();
      });
      
      

6.5 redis封装成组件

const redis = require("redis");
const { REDIS_CONF } = require("../conf/db");

// 創建客戶端
const redisClient = redis.createClient(REDIS_CONF.port, REDIS_CONF.host);

redisClient.on("error", (err) => {
  console.log("error:", err);
});

// set value

function set(key, val) {
  if (typeof val === "object") {
    val = JSON.stringify(val);
  }
  redisClient.set(key, val, redis.print);
}

// get value

function get(key) {
  const promise = new Promise((resolve, reject) => {
    redisClient.get(key, (err, val) => {
      if (err) {
        reject(err);
        return;
      }
      if (val == null) {
        resolve(null);
        return;
      }
      // 兼容json 操作
      try {
        resolve(JSON.parse(val));
      } catch {
        resolve(val);
      }
    });
  });
  return promise;
}

module.exports = {
  set,
  get,
};

6.6 session+cookie 联调

  1. 判断用户是否通过cookie传入userid 如果没有传入则随机生成 并设置到用户浏览器和 redis中
  2. 如果传入cookie 中有userid 则去redis中查询userid 如何返回非空对象则登录 如果返回空对象 则未登录
  3. 添加登录验证 在调用接口前判断是否登录成功(有 username)

6.7 前端联调配置nginx

问题:

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

6.8 nginx 介绍

  1. 高性能的web服务器,开源免费
  2. 一般用于做静态服务,负载均衡
  3. 反向代理

[图片上传失败...(image-6e6580-1642776253504)]

  1. Nginx下载地址 https://nginx.org/en/download.html

    1. nginx 的几个命令

      1. 查看配置是否错误 nginx -t
      2. 启动nginx nginx
      3. 重启nginx nginx -s reload
      4. 停止nginx -s stop
      
      #user  nobody;
      # 启动几何;
      worker_processes  1;
      
      #error_log  logs/error.log;
      #error_log  logs/error.log  notice;
      #error_log  logs/error.log  info;
      
      #pid        logs/nginx.pid;
      
      
      events {
          worker_connections  1024;
      }
      
      
      http {
          include       mime.types;
          default_type  application/octet-stream;
      
          #log_format  main  '$remote_addr - $remote_user [$time_local] "$request" '
          #                  '$status $body_bytes_sent "$http_referer" '
          #                  '"$http_user_agent" "$http_x_forwarded_for"';
      
          #access_log  logs/access.log  main;
      
          sendfile        on;
          #tcp_nopush     on;
      
          #keepalive_timeout  0;
          keepalive_timeout  65;
      
          #gzip  on;
      
          server {
              listen       8080;
              server_name  localhost;
      
              #charset koi8-r;
      
              #access_log  logs/host.access.log  main;
      
              #        location / {
              #            root   html;
              #           index  index.html index.htm;
              #      }
        
        location / {
            proxy_pass http://localhost:8001;
        }
        
        location /api/ {
            proxy_pass http://localhost:8000;
            proxy_set_header Host $host;
        }
      
              #error_page  404              /404.html;
      
              # redirect server error pages to the static page /50x.html
              #
              error_page   500 502 503 504  /50x.html;
              location = /50x.html {
                  root   html;
              }
      
              # proxy the PHP scripts to Apache listening on 127.0.0.1:80
              #
              #location ~ \.php$ {
              #    proxy_pass   http://127.0.0.1;
              #}
      
              # pass the PHP scripts to FastCGI server listening on 127.0.0.1:9000
              #
              #location ~ \.php$ {
              #    root           html;
              #    fastcgi_pass   127.0.0.1:9000;
              #    fastcgi_index  index.php;
              #    fastcgi_param  SCRIPT_FILENAME  /scripts$fastcgi_script_name;
              #    include        fastcgi_params;
              #}
      
              # deny access to .htaccess files, if Apache's document root
              # concurs with nginx's one
              #
              #location ~ /\.ht {
              #    deny  all;
              #}
          }
      
      
          # another virtual host using mix of IP-, name-, and port-based configuration
          #
          #server {
          #    listen       8000;
          #    listen       somename:8080;
          #    server_name  somename  alias  another.alias;
      
          #    location / {
          #        root   html;
          #        index  index.html index.htm;
          #    }
          #}
      
      
          # HTTPS server
          #
          #server {
          #    listen       443 ssl;
          #    server_name  localhost;
      
          #    ssl_certificate      cert.pem;
          #    ssl_certificate_key  cert.key;
      
          #    ssl_session_cache    shared:SSL:1m;
          #    ssl_session_timeout  5m;
      
          #    ssl_ciphers  HIGH:!aNULL:!MD5;
          #    ssl_prefer_server_ciphers  on;
      
          #    location / {
          #        root   html;
          #        index  index.html index.htm;
          #    }
          #}
      
      }
      
      

第七章 博客项目日志

7.1 开始

1. 访问日志 access log(server 端最重要的日志)
1. 自定义日志(包括自定义事件,错误记录等)

nodejs 文件操作 nodejs stream

日志功能开发和使用

日志文件拆分,日志内容分析

7.2 nodeJs 文件操作

  1. 引入fs 和 path 模块

  2. path 模块用于统一文件目录 Linux mac windows

  3. 读取文件

    1. const fs = require("fs");
      const path = require("path");
      
      const filePath = path.relative(__dirname, "data.txt");
      
      fs.readFile(filePath, (err, data) => {
        if (err) {
          console.error(err);
          return;
        }
        // data 是二进制类型 需要转换为字符串
        console.log("data.toString", data.toString());
      });
      
      
      
  4. 写入文件

    1. const fs = require("fs");
      const path = require("path");
      
      const filePath = path.relative(__dirname, "data.txt");
      
      const content = "这里是要写入的内容\n";
      const opt = {
        flag: "a", // 追加内容 覆盖用w
      };
      fs.writeFile(filePath, content, opt, (err) => {
        if (err) {
          console.log("err", err);
        }
      });
      
      
  5. 判断文件是否存在

    1. fs.exists(filePath, (exists) => {
        console.log("exists", exists);
      });
      

7.3 stream 介绍

  1. IO操作的性能瓶颈
    1. IO 包括 网络IO 和文件 IO
    2. 相比于 CPU 计算和内存读写 IO 突出的特殊的就是:慢!
    3. 如何在有限的硬件资源下提供IO 的操作效率

7.4 stream 实例

  1. 复制文件

    1. const fs = require("fs");
      const path = require("path");
      
      // 复制文件
      let index = 0;
      const filePath1 = path.relative(__dirname, "test01.txt");
      const filePath2 = path.relative(__dirname, "text03.txt");
      
      const streamFile1 = fs.createReadStream(filePath1);
      const streamFile2 = fs.createWriteStream(filePath2);
      
      streamFile1.pipe(streamFile2);
      streamFile1.on("data", (chunk) => {
        console.log("index:", index++);
      });
      streamFile1.on("end", () => {
        console.log("file ok");
      });
      
      
  2. server 利用stream

  3. const fs = require("fs");
    const path = require("path");
    const http = require("http");
    
    const filePath = path.relative(__dirname, "test01.txt");
    const server = http.createServer((req, res) => {
      if (req.method == "GET") {
        const readStreamFile = fs.createReadStream(filePath);
        readStreamFile.pipe(res);
      }
    });
    
    server.listen(4401);
    
    

7.5 stream封装组件

const fs = require("fs");
const path = require("path");

// 写入文件
function writeLog(writeStream, log) {
  writeStream.write(log + "\n");
}

// 创建读stream
function createWriteStream(fileName) {
  const falePath = path.join(__dirname, "../", "../", "logs", fileName);
  const streamFile = fs.createWriteStream(falePath, { flags: "a" });
  return streamFile;
}

const accessWriteStream = createWriteStream("access.log");
accessWriteStream.on("err", (err) => {
  console.log("err", err);
});
function access(log) {
  writeLog(accessWriteStream, log);
}

module.exports = {
  access,
};

7.6 日志拆分(运维处理)

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

     > **** command
    
     1. 分钟   
     2. 小时
     3. 日期
     4. 月份
     5. 星期
    
  2. 将 access.log 拷贝并重命名为 2019-02-10.access.log

  3. 清空access.log 文件 ,继续积累日志

  4. 配置

    #!/bin/sh
    
    cd 目录文件下面
    cp access.lgo $(date +%Y-%m-%d).access.log
    echo '' > access.log
    
    >> 运行 sh copy.sh 可以用于测试
    
    >> 编辑 crontab 
      | crontab -e
    >> 在文件中编辑
      | * 0 * * * sh 目录
    
    成功的话显示 installing new crontab
    查看任务 crontab -l
    

7.7 日志分析 readline

const fs = require("fs");
const path = require("path");
const readline = require("readline");

// 文件路径
const filePath = path.join(__dirname, "../", "../", "logs", "access.log");

// 创建 read stream
const readStreamFile = fs.createReadStream(filePath);

// 创建一个 readline 对象
const rl = readline.createInterface({ input: readStreamFile });

let chromeNum = 0;
let sum = 0;

rl.on("line", (lineData) => {
  if (lineData == "") {
    return;
  }
  sum++;
  const str = lineData.split("--")[2];
  if (str && str.indexOf("Chrome") > 0) {
    chromeNum++;
  }
});

rl.on("close", () => {
  console.log("chrome 占比:" + chromeNum / sum);
});

第八章 node安全

8.1 开始和sql注入

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

一 、 sql 注入

1. 最原始最简单的攻击
2. 攻击方式 输入一个sql片段,最终拼接成一段攻击代码
3.  使用mysql的 escape 函数处理输入内容即可
const mysql = require("mysql");

const login = (username, password) => {
  const username1 = mysql.escape(username);
  const password1 = mysql.escape(password);

8.2 XSS攻击

攻击范式:在页面展示内容中掺杂js代码,以获取网页信息

预防措施:转换生成js 的特殊字符

  1. 使用专业的工具 xss
    1. npm install xss --save
  2. 引入后直接使用即可
    1. const title = escape(xss(blogData.title));

8.3 密码加密

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

总结

1. 服务端稳定性
2. 内存CPU 优化 扩展
3. 日志记录
4. 安全
5. 集群 和 服务拆分
const crypto = require("crypto");

// 密匙
const SERET_KEY = "BLACKITRABBIT";

// md5 加密

function md5(content) {
  let md5 = crypto.createHash("md5");
  return md5.update(content).digest("hex");
}

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

console.log(genPassword(123));

第九章 express 重构项目

9.1 开始

  1. express 是最常用的 web server 框架
  2. express 下载、安装、和使用,express 中间件机制
  3. 开发接口,链接数据库,实现登录,日志记录
  4. 分解 express 中间件原理

9.2 express 安装

  1. 安装(使用脚手架 express-generator)

    npm install express-generator -g

  2. 利用工具生成项目

    express express-test

  3. npm install && npm start

9.3 express 中间件机制

  1. 有很多 app.use
  2. 代码中的 next 参数是什么

9.4 环境初始化

  1. 使用express-session
  2. connect-redis

9.5 链接mysql

"mysql": "^2.18.1",

"xss": "^1.0.10"

9.6 链接redis

  1. redis
  2. connect-redis
const RedisStore = require("connect-redis")(session);

const { redisClient } = require("./db/redis");
const sessionStore = new RedisStore({
  client: redisClient,
});

app.use(
  session({
    secret: "asdwerd_sad",
    cookie: {
      path: "/", // 默认配置
      httpOnly: true, // 默认配置
      maxAge: 24 * 60 * 60 * 1000,
    },
    store: sessionStore,
  })
);

9.7 登录中间件

const { ErrorModel } = require("../model/resModel");

module.exports = (req, res, next) => {
  if (req.session.username) {
    next();
    return;
  }
  res.json(new ErrorModel("未登录"));
};

9.8 介绍morgan日志

  1. 引入 > const logger = require("morgan");
  2. 配置 app.use(logger("dev"));
const filePath = path.join(__dirname, "logs", "access.log");

const writeStream = fs.createWriteStream(filePath, { flags: "a" });

 app.use(

  logger("combined", {

   stream: writeStream,

  })

 );

9.9express 中间件原理

  1. 回顾中间件使用
  2. 分析如何实现
  3. 代码演示
const http = require("http");
const slice = Array.prototype.slice;

class LikeExpress {
  constructor() {
    // 存放中间件的列表
    this.routes = {
      all: [],
      get: [],
      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);
  }

  // 核心想next 函数
  handle(req, res, stack) {
    const next = () => {
      // 拿到第一个匹配的中间件
      const middleware = stack.shift();
      if (middleware) {
        // 执行中间件函数
        middleware(req, res, next);
      } 
    };
    next();
  }

  match(method, url) {
    let stack = {};
    if (url == "/favicon.icon") {
      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) {
        stack = stack.concat(routeInfo.stack);
      }
    });
  }

  callback() {
    return (req, res) => {
      res.json = (data) => {
        res.setHeader("Content-tyoe", "application/json");
        res.end(JSON.stringify(data));
      };

      const url = req.url;
      const method = req.method.toLowerCase();

      const resulList = this.match(method, url);
      this.handle(req, res, resulList);
    };
  }

  listen(...args) {
    const server = http.createServer(this.callback());
  }
}

// 工厂函数
module.exports = () => {
  return new LikeExpress();
};

第十章 Ko2 重构博客项目

10.1 开始

  1. express 中间件是异步回调,koa2 原生支持 async/await(异步函数)
  2. 新开发的框架和系统,都开始基于koa2, 例如 egg.js
  3. express 虽然未过时,但是koa2 肯定是未来的趋势

10.2 koa2 介绍

  1. 安装

    npm install koa-generator -g

  2. koa2 环境初始化

    Koa2 koa2-test

  3. 启动项目

> npm install & npm run dev

10.3 介绍koa2 中间件机制

  1. 有很多 app.use
  2. 代码中的next参数是什么

10.4 koa2实现session

  1. 基于 koa-generic-session 和 koa-redis

    npm i koa-generic-session koa-redis redis --save

  2. 引用

    const session = require('koa-generic-session')
    const reidsStore = require('koa-redis')
    
  3. 配置session 设置key

    app.keys = ["key1"]
    app.use(session({
     // 配置cookie
     cookie: {
         path: '/',
         httpOnlay: true,
         maxAge: 24 * 60 * 60 * 1000
     }
     // 配置 redis
     store: redisStore({
         all: '127.0.0.1:6379' // 写死本地的 redis server
     })
    }))
    
  4. 配置验证接口

    router.get('/session-test',async function (ctx, next) {
     if(ctx.session.viewCount == null){
            ctx.session.viewCount = 0;
        }
        ctx.session.viewCount ++;
        ctx.body = {
            errno: 0,
            viewCount:ctx.session.viewCount
        }
    })
    

10.5 开发路由-准备工作

  1. 安装mysql 和 xss

    npm i mysql xss --save

  2. 拷贝module、config 文件夹

  3. 配置 redis

  4. 拷贝db,controller 文件夹加

10.6 日志

access log 记录 ,使用 morgan

  1. 安装koa-morgan

    npm i koa-morgan --save

  2. 新建logs文件夹 创建access.log 文件

  3. 引入morgan

    const fs = require('fs')
    const path = require('path')
    const morgan = require('koa-morgan')
    
    const ENV = process.env.NODE_ENV
    if(ENV !== 'production'){
        app.use(morgan('dev'))
    }else{
        const logFilePath = path.join(__dirname,'logs','access.log')
        const writeStream = fs.createWriteStream(logFilePath,{flags:'a'})
        app.user(morgan("combined",{
            stream:writeStram
        }))
    }
    

10.7 Koa2 中间件原理分析

洋葱圈模型 从最外层依次进入 从内层向外走出

const Koa = require('koa');
const app = new Koa();

// logger

app.use(async (ctx, next) => {
  await next();
  const rt = ctx.response.get('X-Response-Time');
  console.log(`${ctx.method} ${ctx.url} - ${rt}`);
});

// x-response-time

app.use(async (ctx, next) => {
  const start = Date.now();
  await next();
  const ms = Date.now() - start;
  ctx.set('X-Response-Time', `${ms}ms`);
});

// response

app.use(async ctx => {
  ctx.body = 'Hello World';
});

app.listen(3000);

10.8

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)));
      } catch (err) {
        Promise.resolve(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;

第十一章 上线与配置

11.1 开始和PM2介绍

一、pm2做什么
  1. 进程守护,系统崩溃自动重启。
  2. 启动多进程,充分利用CPU和内存
  3. 自带日志记录功能
二 、pm2学什么
  1. pm2 介绍

  2. pm2 进程守护

  3. pm2 配置和日志记录

  4. pm2 多进程

  5. pm2 关于服务运维

11.2 pm2 下载和安装

  1. 下载和安装

    npm install pm2 -g

    pm2 -version

  2. 配置pm启动

    "prd": "cross-env NODE_ENV=production pm2 start app.js"

  3. 查看启动列表

    pm2 ls

11.3 pm2 常用命令

  1. pm2 start ...
  2. pm2 list // 获取当前运行的列表
  3. pm2 restart <appName>/<id> // 重启服务器
  4. pm2 stop <appName>/<id> // 停止
  5. pm2 delete <appName>/<id> // 删除
  6. pm2 info <appName>/<id> // 获取某一个进程的基本信息
  7. pm2 log <appName>/<id> // 打印log信息
  8. pm2 monit <appName>/<id> // 监听服务器状态

11.4 pm2 常用配置

新建pm2 配置文件(包括进程数量,日志文件目录等)

修改pm2 启动命令,重启

访问server,检查日志文件的内容(日志记录是否生效)

  1. 新建配置文件

    pm2.conf.json

  2. 配置内容

    {
      "apps": {
        "name": "pm2-test-server",
        "script": "app.js",
        "watch": true,
        "ignore_watch": ["node_modules", "logs"],
        "error_file": "logs/err.log",
        "out_file": "logs/out.log",
        "log_date_format": "YYYY-MM-DD HH:mm:ss"
      }
    }
    
    
  3. 配置启动项

    "prd": "cross-env NODE_ENV=production pm2 start pm2.conf.json"

11.5 pm2 多进程

  1. 为什么使用多进程

    1. 操作系统会限制进程的最大可用内存
    2. 无法充分利用多核cpu的优势
    3. 某一个进程程序崩溃 其他的进程不受影响
  2. 配置多进程

    "instances": 4

扩展项目上线发发布

CentOs nodejs搭建

  1. 安装nvm

    wget -qO- https://raw.githubusercontent.com/nvm-sh/nvm/v0.39.1/install.sh | bash

  2. 查看配置

    vim .bashrc

  3. 退出文件

> :w file 将修改另外保存到file中,不退出vi
>
> :w!  强制保存,不推出vi
>
> :wq 保存文件并退出vi
>
> :wq! 强制保存文件,并退出vi
>
> :q 不保存文件,退出vi
>
> :q! 不保存文件,强制退出vi
>
> :e! 放弃所有修改,从上次保存文件开始再编辑
  1. 执行source 使环境变量生效

    source .bashrc

CentOs Nginx环境搭建

  1. 安装nginx服务器需要配置 yum CentOs 服务自带

yum -y install pcre*

  1. 安装 openssl *

    yum -y install openssl*

  2. 新建文件夹

    mkdir nginx

  3. 切换到nginx中安装nginx

    wget http://nginx.org/download/nginx-1.12.2.tar.gz

  4. 解压

    tar -zxvf 解压的文件

  5. 进入到解压好的文件目录下 执行 configure

    ./configure 或者 sh configure --profix = 安装路径

  6. 利用make指令进行安装

    make install

  7. 查看是否安装成功

/user/local/nginx/sbin/nginx -t

  1. 路径进行软连接到 usr/bin 路径下 -snf 可以修改

ln -s /root/nginx/ninxtstart nginx

  1. 查看是否创建成功

    ll nginx

  2. 查看进程

    ps -ef|grep nginx

  3. 关闭nginx进程

    nginx -s stop

nginx配置

  1. 配置权限

  2. 配置引入其他配置

    include /root/nginx/*.conf;y

  3. 在其他文件中配置

    server{
       listen 80;
       server_name localhost;
       root /root/nginx/upload;
       autoindex on;
       add_header Cache-Control "no-cache,must-revalidate";
       location / {
           add_header Access-Control-Allow-Origin *;
       }
    }
    
  4. 配置 vim 显示行数

    :set nu

  5. 配置好对应展示的文件即可

CentOs git 部署+免密更新

  1. 安装git

    npm install -y git

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 212,222评论 6 493
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 90,455评论 3 385
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 157,720评论 0 348
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 56,568评论 1 284
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 65,696评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,879评论 1 290
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,028评论 3 409
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,773评论 0 268
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,220评论 1 303
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,550评论 2 327
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,697评论 1 341
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,360评论 4 332
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,002评论 3 315
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,782评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,010评论 1 266
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,433评论 2 360
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,587评论 2 350

推荐阅读更多精彩内容