Node 学习

439 阅读8分钟

Node 搭建HTTP服务器

原生搭建HTTP服务器

const http = require("http");

//同一个url下,不同的请求
const app = http.createServer((req, res) => {
    if (req.method === "POST") {
        if (req.url === "/user") {
            res.end(JSON.stringify({"message": "对user接口发起了POST请求"}));
        }
    } else if (req.method === "GET") {
        if (req.url === "/user") {
            res.end(JSON.stringify({"message": "对user接口发起了GET请求"}));
        }
    }
}).listen(3000);

koa 搭建HTTP服务器

const Koa = require("koa");
const Router = require("koa-router");
const router = new Router();
const app = new Koa();

router.get("/", (ctx) => {
   ctx.body = "这是主页"
});

app.use(router.routes());

app.listen(3000);

express 搭建HTTP服务器

const express = require('express');
const app = express();

app.get('/', function(req, res){ //响应对/的请求
  res.send('Hello');  //发送“Hello” 作为响应文本
});

app.listen(3000);  //监听端口3000

express 基础学习

express 处理POST请求

app.js 文件 解析POST数据

app.use(express.json());

路由 处理POST请求

const express = require('express');
const router = express.Router();

router.post('/', function (req, res, next) {
    const {username, password} = req.body;
    res.json({
        err: 0,
        data: []
    })
});

module.exports = router;

方法二

router.post("/regist", function (req, res, next) {
    var data = {
        username: req.body.username,
        password: req.body.password,
        password2: req.body.password2
    };
    res.send(data);
});

Koa 基础学习

需要安装的依赖

"dependencies": {
   "koa": "^2.11.0",
   "koa-bodyparser": "^4.2.1",
   "koa-router": "^8.0.8",
   "mongoose": "^5.9.3"
 }

koa-router 的基本使用

  • 路由拆分之后的基本文件
const Router = require("koa-router");
const router = new Router();

// 路由匹配
router.prefix("/api/user");

router.get("/", (ctx) => {
   ctx.body = "用户主页"
});

// module.exports = {
//     router 报错
// };
module.exports = router;
  • 路由拆分之后,首页的配置
const Koa = require("koa");
const app = new Koa();

const users = require("./routes/user");

// 官方推荐,丰富响应头
app.use(users.routes()).use(users.allowedMethods());

app.listen(3000);

koa 处理POST请求

routes 路由下的 user.js文件

const Router = require("koa-router");
const router = new Router();

// 路由匹配
router.prefix("/api/user");

router.post("/", async (ctx) => {
    let {username, password} = ctx.request.body;
    console.log(username, password)
});

module.exports = router;

app.js 文件

const Koa = require("koa");
const app = new Koa();
const body = require("koa-bodyparser")

// 引入路由
const users = require("./routes/user");

// 解析POST请求
app.use(body());
// 官方推荐,丰富响应头
app.use(users.routes()).use(users.allowedMethods());

app.listen(3000);

错误处理(自定义中间件)

const error = async (ctx, next) => {
    try {
        await next();
    } catch (e) {
        ctx.body = {
            message: "服务器出错",
            error: error.message
        }
    }
};

module.exports = error;

方法的使用

先引入,再注册
const error = require("../midleware/error.js");

app.use(error);

服务器

中间机制

什么是中间件

// 模拟登陆
function loginCkeck(req, res, next) {
    console.log("模拟登陆成功");
    setTimeout(() => {
        next();
    })
}
// loginCkeck是个中间件,后面的函数也是个中间件
app.get("/api/get-cookie", loginCkeck, (req, res, next) => {
    //假设在处理cookie
    req.cookie = {
        userid: "abc123"
    }
})

配置Express和你的程序

程序的需求取决于它所运行的环境。比如说,当你的产品处于开发环境中时,你可能想要详尽的日志,但在生产环境中,你可能想要精简的日志和gzip压缩。Express有一个极简的环境驱动配置系统,由5个方法组成,全部由环境变量NODE_ENV 驱动:

  • app.configure()
  • app.set()
  • app.get()
  • app.enable()
  • app.disable()

用app.configure()设定特定环境的选项

app.configure(function(){
  app.set('views', __dirname + '/views'); //所有环境
  app.set('view engine', 'ejs');
  ...
});
app.configure('development', function(){
  app.use(express.errorHandler());  //仅开发环境
});

用条件判断设定特定环境的选项

var env = process.env.NODE_ENV || 'development';  //默认为“development”
app.set('views', __dirname + '/views');
app.set('view engine', 'ejs');  //所有环境
...
if ('development' == env) {  //仅开发环境,用if语句代替app.configure
   app.use(express.errorHandler());
}

渲染视图

渲染视图对几乎所有程序来说都至关重要。它的概念很简单:你把数据传给视图,然后数据会被转换,通常是变成Web程序中的HTML。视图的概念对你来说应该不算陌生,因为大多数框架都提供了类似的功能。Express中提供两种渲染视图的办法:在程序层面用app.render() ,在请求或响应层面用res.render() ,它在内部用的也是前者。本章只用res.render() 。

exports.index = function(req, res){
   res.render('index', { title: 'Express' });
};

视图系统配置 Express视图系统配置起来很简单。即便express(1) 帮你生成了配置,你还是应该知道它的底层机制,这样才能在需要时修改它。我们将重点介绍3个领域:

  • 调整视图的查找;
  • 配置默认的模板引擎;
  • 启用视图缓存,减少文件I/O。 改变查找目录
app.set('views', __dirname + '/views');

用文件扩展名指定模板引擎

app.set('view engine', 'jade');
app.get('/', function(){
  res.render('index'); //会假定为.jade,因为它被设定为view engine
});
app.get('/feed', function(){
  res.render('rss.ejs');  //因为提供了扩展名.ejs,所以会用模板引擎EJS
});

视图缓存

生产环境中会默认启用view cache 设定,并防止后续的render() 调用执行硬盘I/O。模板的内容保存在内存中,性能会得到显著提升。启用这个设定的副作用是只有重启服务器才能让模板文件的编辑生效,所以在开发时会禁用它。如图8-12所示,在view cache 被禁用时,每次请求都会从硬盘上读取模板。这样你无需重启程序就可以让模板的修改生效。当启用view cache 时,每个模板只会读取一次硬盘。

视图缓存

图8-12 view cache设定

把数据输出到视图中

RESTful架构的最佳实践

什么是RESTful

传统的URL风格

请求接口或者地址的时候都在描述,完全没有必要去做描述

http://127.0.0.1/user/query/1 查询 根据用户id去查询用户数据
http://127.0.0.1/user/save 保存 注册用户
http://127.0.0.1/user/update 更新 修改用户
http://127.0.0.1/user/delete/(id) 删除 删除用户

RESTFUL 风格

面向资源:对于同一个资源操作,都在同一个URL进行,通过判断HTTP的请求类型来决定做不同的事

http://127.0.0.1/user/1 GET
http://127.0.0.1/user POST
http://127.0.0.1/user PUT
http://127.0.0.1/user DELETE

原生实现 RESTFUL

需要安装的依赖

mysql co-mysql md5-node

操作数据库

const http = require("http");
const mysql = require("mysql"); //数据库操作是异步的
const co = require("co-mysql"); //同步的方法操作数据库
const md5 = require("md5-node");

// 数据库链接池
let db = mysql.createPool({
    host: "localhost",
    user: "root",
    password: "663067",
    database: "user"
});
let conn = co(db);

const app = http.createServer(async (req, res) => {
    if (req.method === "POST") {
        if (req.url === "/user") {
            let arr = [];
            req.on("data", async (data) => {
                arr.push(data);
            });

            req.on("end", async () => {
                let buffer = Buffer.concat(arr);

                let {username, password} = JSON.parse(buffer.toString());
                console.log(username, password)

                let data = await conn.query(`select user from admin where user="${username}"`);
                // 写入数据库
                password = md5(password);
                let sql = `INSERT INTO admin (user,password) VALUES ("${username}","${password}")`;
                await conn.query(sql);
                res.send(JSON.stringify({"status": 200, "message": "注册成功"}));

            });
        }
    } else if (req.method === "GET") {
        if (req.url === "/user") {
            res.end(JSON.stringify({"message": "对user接口发起了GET请求"}));
        }
    }
}).listen(3000);

框架实现 RESTFUL

Express 链接MySQL 数据库

const express = require("express");
const bodyparse = require("body-parser");
const mysql = require("mysql"); //数据库操作是异步的
const co = require("co-mysql"); //同步的方法操作数据库
const md5 = require("md5-node");
const app = express();

// 数据库链接池
let db = mysql.createPool({
    host: "localhost",
    user: "root",
    password: "663067",
    database: "user"
});
let conn = co(db);

app.use(bodyparse.urlencoded({
    extended: true
}));
app.use(bodyparse.json());

app.post("/user", async (req, res) => {
    let {username, password} = req.query;
    // 写入数据库
    password = md5(password);
    let sql = `INSERT INTO admin (user,password) VALUES ("${username}","${password}")`;
    await conn.query(sql);
    res.send(JSON.stringify({"status": 200, "message": "注册成功"}));
});

app.listen(3000);

获取HTTP请求的参数

app.get("/user/:id", (req, res) => {
    res.send(req.params.id);
});

开发个人博客

nodejs 和 JS 的区别

ECMAScript

  • 定义了语法,写 javascript 和 nodejs 都必须遵守
  • 变量定义、循环、判断、函数
  • 原型和原型链、作用域和闭包、异步和单线程
  • 不能操作DOM,不能监听click事件,不能发生ajax请求
  • 不能处理http请求,不能操作文件
  • 即,只有ECMAScript,做不了任何实际项目

javascript

  • 使用ECMAScript语法规范,外加Web API 缺一不可
  • DOM操作、BOM操作,事件绑定、Ajax等
  • 两者结合,即可完成浏览器端的任何操作

nodejs

  • 使用ECMAScript语法规范,外加nodejs API 缺一不可
  • 处理http,处理文件等
  • 两者结合,即可完成Server端的任何操作

commonjs 演示

a.js 文件

function add(a, b) {
    return a + b;
};

function mul(a, b) {
    return a * b;
};

module.eports = {
    add,
    mul
}

b.js 文件

// const {add, mul} = require("./a");
// 等价于
const opts = require("./a");
const add = opts.add;
const mul = opts.mul;

const sum = add(10, 20);
const result = mul(100, 200);

console.log(sum);
console.log(result);

http--概述

http 请求概述

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

get 请求和 querystring

// http://localhost:8000/user?username=胡海军&password=2655548
const http = require("http");
const querystring = require("querystring");

const server = http.createServer((req, res) => {
    console.log(req.method); //GET
    const url = req.url; //获取请求的完整 url
    req.query = querystring.parse(url.split("?")[1]); //解析 querystring
    res.end(JSON.stringify(req.query)); //将 querystring 返回
});

server.listen(8000);

post 请求和 postdata

const http = require("http");

const server = http.createServer((req, res) => {
    if (req.method === "POST") {
        // 数据格式
        console.log("req content-type:", req.headers["content-type"]);
        // 接收数据
        let postData = "";
        req.on('data', chunk => {
            postData += chunk.toString(); //chunk是二进制格式,转为字符串格式
        });
        req.on("end", () => {
            console.log(postData);
            res.end("hello word"); //在这里返回,因为是异步
        });
    }
});

server.listen(8000);

处理 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]);

    // 设置返回格式为 json
    res.setHeader("Content-type", "application/json");

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

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

server.listen(8000);

开发接口

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

初始化路由

routes 下的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") { }
    // 新建一篇博客
    if (method === "POST" && path === "/api/blog/new") { }
    // 更新一篇博客
    if (method === "POST" && path === "/api/blog/update") { }
    // 刪除一篇博客
    if (method === "POST" && path === "/api/blog/del") { }
};

module.exports = handleBlogRouter;

routes 下的user.js文件

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

    // 获取博客列表
    if (method === "POST" && path === "/api/blog/login") {
        return {
            msg:
        }
    }
};

module.exports = handleUserRouter;

app.js文件

const handleBlogRouter = require("./routes/handleBlogRouter");
const handleUserRouter = require("./routes/handleUserRouter");

const serverHandle = (req, res) => {
    // 设置返回格式 json
    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.writeHeader(404, {"content-type": "text/plain"});
    res.write("404 Not Found");
    res.end();
};

module.exports = serverHandle;

建数据模型

model 下的resModel.js文件

// 新建一个基类(父类)
class BaseModel {
    constructor(data, message) {
        if (typeof data === "string") {
            this.message = data;
            data = null;
            message = null;
        }
        if (data) {
            this.data = data;
        }
        if (message) {
            this.message = message;
        }
    }
}

class SuccessModel extends BaseModel {
    constructor(data, message) {
        super(data, message);
        this.errno = 0;
    }
}

class ErrorModel extends BaseModel {
    constructor(data, message) {
        super(data, message);
        this.errno = -1;
    }
}

module.exports = {
    SuccessModel,
    ErrorModel
}

MySql 介绍

为什么使用 mysql 而不是 mongodb

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

Nodejs操作Mysql

cookie 介绍

什么是cookie

  • 存储在浏览器的一大段字符串(最大5kb)
  • 跨域不共享
  • 格式如 k1=v1;k2=v2;因此可以存储结构化数据
  • 每次发送请求,会将请求域的cookie 一起发送给server
  • server 可以修改cookie并返回给浏览器
  • 浏览器中也可以通过javascript 修改cookie(有限制)

server 端操作cookie,实现登录验证