nodejs与mongodb结合运用

627 阅读8分钟

引言

教程源于:


该文章用于笔记整理

gfX7rt.png


准备

node版本管理:nvm——github.com/creationix/… Node.js 版本,也可以安装新版本用以尝试并且当出现问题时轻松地回滚

一 线程模型

  1. 接收请求(request)
  2. 开出线程(thread)处理,用户等待
  3. 请求处理完成,线程(thread)释放

线程处理模式重复以上三个步骤,来处理来自客户端的各种请求。当有大量客户端请求来袭时,服务器消耗的资源也会随之增加。

Node.js 应用程序运行于单个进程中,无需为每个请求创建新的线程。 Node.js 在其标准库中提供了一组异步的 I/O 原生功能(用以防止 JavaScript 代码被阻塞),并且 Node.js 中的库通常是使用非阻塞的范式编写的(从而使阻塞行为成为例外而不是规范)。当 Node.js 执行 I/O 操作时(例如从网络读取、访问数据库或文件系统),Node.js 会在响应返回时恢复操作,而不是阻塞线程并浪费 CPU 循环等待。

二 node中的事件循环

  1. 开一个事件等待循环(event-loop)
  2. 接收请求
  3. 放入事件处理队列中,然后继续接收新的请求
  4. 请求完成后,调用I/O,结束请求(非阻塞调用)

事件循环处理模式中,线程不用等待req处理完,而是将所有请求放入队列中,然后采用非同步的方式,等待请求处理完成后再调用I/O资源,然后结束请求。

gW7UdH.jpg

三 node中的非阻塞处理

  • 阻塞处理(Java,Ruby,PHP,Asp,Net)
  • 非阻塞处理(Node.js)

阻塞处理:后边的语句无法执行,除非前面的执行完毕

function updb1(){
    let start =new Date().getTime()
    while (new Date().getTime()<start+3000);
}
updb1()
console.log('数据库更新成功!')
console.log('其他语句')

非阻塞处理:继续执行后面的。(必须提供回调函数)

function updb2(done){
    setTimeOut(()=>{
        done()
    },3000)
}
updb2(function(){
    console.log('数据库更新成功!')
})
console.log('其他语句')

四 搭建http服务器


const http=require('http');
const hostname='127.0.0.1';
const port=3000;

const server=http.createServer((req,res)=>{
    res.statusCode=200;//设置状态码
    res.setHeader('Content-Type','text/plain')//设置响应头
    res.end('Hello World')
})

server.listen(port,hostname,()=>{
    console.log(`Server running at http://${hostname}:${port}/`);
})

一般情况下,每次更代码都需重新启动服务器,否则页面将没有变化。supervisor工具可以在不用重复启动服务器的情况下就能看到页面变化:

npm install -g supervisor	//全局安装
supervisor app.js	//使用supervisor替代node命令来运行程序

五 引入模块

默认情况下js文件是私有的。要让外部访问,需通过exportsmodule.exports暴露:

  • require:引用外部文件
  • exports、module.exports:公开文件

例子:hostname和port属于公共部分,可以抽出来作为模块

  • config.js

    const config={
        hostname:'127.0.0.1',
        port:3000
    }
    exports.config=config
    
  • server.js

    const http=require('http');
    const config=require('./config').config //引用模块
    
    const server=http.createServer((req,res)=>{
        res.statusCode=200;
        res.setHeader('Content-Type','text/plain')
        res.end('Hello World')
    })
    
    server.listen(config.port,config.hostname,()=>{
        console.log(`Server running at http://${config.hostname}:${config.port}/`);
    })
    

另一种写法:

  • config.js

    const config={
        hostname:'127.0.0.1',
        port:3000
    }
    module.exports=config
    
  • server.js

    const config=require('./config')
    

六 路由

const http=require('http');
const hostname='127.0.0.1';
const port=3000;

const server=http.createServer((req,res)=>{
    res.statusCode=200;
    res.setHeader('Content-Type','text/plain')
    
    switch(req.url){
        case '/':
            res.end('Hello World');
            break;
        case '/about':
            res.end('This is about page')
            break;
        case '/home':
            res.end('Welcome to myhomepage')
            break;
        default:
            res.end('404 NOT FOUND!')
    }
})

server.listen(port,hostname,()=>{
    console.log(`Server running at http://${hostname}:${port}/`);
})

七 搭建静态服务器


7.1 读取html

./index.html

<html>
    <body>
        <h1>Hello world!</h1>
    </body>
</html>

./server.js

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

//__dirname:当前文件所在目录
const server=http.createServer((req,res)=>{
    fs.readFile(__dirname+'/index.html','utf-8',(err,data)=>{
        if(err){
            res.setHeader('Content-Type','text/plain');//纯文本
            res.statusCode=404;
            res.end('404 Not Founded!')
        }else{
            res.setHeader('Content-Type','text/html');//html
            res.statusCode=200;
            res.end(data)
        }
    })
})

const hostname='127.0.0.1';
const port=3000;
server.listen(port,hostname,()=>{
    console.log(`Server running at http://${hostname}:${port}/`);
})

7.2 读取其他

由于不同文件的Content-Type不一样,所以这里封装一个根据后缀名获取文件Content-Type的方法:

./modules.js

const fs=require('fs')
exports.getMimeType=function (extname){
    let data=fs.readFileSync('./mime.json') 
    let mimeObj=JSON.parse(data.toString())//转为对象
    return mimeObj[extname]
};
//mime.json可在网上找:格式{".html": "text/html"}

./server.js

const http=require('http');
const fs=require('fs')
const path=require('path');
const common=require('./modules');

const server=http.createServer((req,res)=>{
    let pathname=req.url
    pathname=pathname=='/'?'/index.html':pathname
	let extname=path.extname(pathname)//获取文件后缀名
    fs.readFile('./static'+pathname,(err,data)=>{
        if(err){
        	res.setHeader('Content-Type','text/plain');
            res.statusCode=404;
            res.end('404 Not Founded!')
        }else{
            let mime=common.getMimeType(extname)
            res.setHeader('Content-Type', mime+";charset='utf-8'");
            res.statusCode=200;
            res.end(data)
        }
    })
})

const hostname='127.0.0.1';
const port=3000;
server.listen(port,hostname,()=>{
    console.log(`Server running at http://${hostname}:${port}/`);
})

7.3 封装静态服务器

./module/route.js

const fs=require('fs')
const path=require('path');
const url=require('url');

function getMimeType(extname){
    let data=fs.readFileSync('./mime.json')
    let mimeObj=JSON.parse(data.toString())//转为对象
    return mimeObj[extname]
};

exports.static=function(req,res,staticPath){
    let pathname=url.parse(req.url,true).pathname
    pathname=pathname=='/'?'/index.html':pathname
    let extname=path.extname(pathname)
    fs.readFile('./'+staticPath+pathname,(err,data)=>{
        if(err){
            res.setHeader('Content-Type','text/plain');
            res.statusCode=404;
            res.end('404 Not Founded!')
        }else{
            let mime=getMimeType(extname)
            res.setHeader('Content-Type', mime+";charset='utf-8'");
            res.statusCode=200;
            res.end(data)
        }
    })
}

./server.js

const http = require('http');
const route= require('./module/route');

http.createServer(function (req, res) {
    route.static(req, res,'static')
}).listen(3000);

八 使用第三方包ejs

  • ejs(effective JavaScript template)(第三方模块)——指路-官方文档
  • querystring(node内置模块)——查询字符串,指路-官方文档
  • npm:Nodejs附带的第三方软件包管理器。是世界上最大的开放源代码的生态系统,可通过它下载各种各样的包,为Nodejs提供更多的功能支持——指路-npm与依赖包
  • fs

8.1 基本使用

安装

npm install ejs

./helo.ejs

<html>
    <title><%= title %></title>
    <body>
        <%- content %>
    </body>
</html>

./server.js

const http=require('http');
const fs=require('fs')
const ejs=require('ejs')
var template=fs.readFileSync(__dirname+'/helo.ejs','utf-8'); 

const server=http.createServer((req,res)=>{
    //模板渲染-传值
    var data=ejs.render(template,{
        title:'helo ejs',
        content:'<strong>big helo ejs.</strong>'
    })
    res.setHeader('Content-Type','text/html');
    res.statusCode=200;
    res.end(data)
})

const hostname='127.0.0.1';
const port=3000;
server.listen(port,hostname,()=>{
    console.log(`Server running at http://${hostname}:${port}/`);
})

8.2 get/post

./list.ejs

<!DOCTYPE html>
<html lang="zh-cmn-Hans">
<head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>列表</title>
</head>
<body>
    <form action="" method="post">
        <input type="text" name="content" id="content">
        <input type="submit" value="提交">
        <ul>
            <% for(let i=0 ; i < posts.length;i++){  %>
                <li><%= posts[i] %></li>
            <% } %>
        </ul>
    </form>
</body>
</html>

./server.js

const http=require('http');
const fs=require('fs')
const ejs=require('ejs')
const qs=require('querystring') //+ 处理用户提交的表单数据

var template=fs.readFileSync(__dirname+'/list.ejs','utf-8'); 
var posts=[] //+ 用户输入的内容

const server=http.createServer((req,res)=>{
    if(req.method==='POST'){
        req.data="";
        req.on("data",function(postDataChunk){
            req.data+=postDataChunk
        })        
        req.on("end",function(){
            //表单处理
            let query=qs.parse(req.data)//将数据进行内部解析,生成查询变量
            posts.push(query.content)//content就是取到表单name为content的内容
            showForm(posts,res)
        })
    }else{
        showForm(posts,res)
    }
})

//渲染页面
function showForm(posts,res){
    let data=ejs.render(template,{
        title:'列表',
        posts
    })
    res.setHeader('Content-Type','text/html');
    res.statusCode=200;
    res.end(data)
}

const hostname='127.0.0.1';
const port=3000;
server.listen(port,hostname,()=>{
    console.log(`Server running at http://${hostname}:${port}/`);
})

九 使用MongoDB


9.1 连接MongoDB

驱动安装

npm install mongodb --save

./connect.js

//mongodb客户端
const MongoClient=require('mongodb').MongoClient

// 判断某两值是否相等的库
const test=require('assert')

// mongodb的连接地址
const url='mongodb://127.0.0.1:27017'

// 数据库名称-指定数据库名称
const dbName='admin'

// 连接使用客户端
MongoClient.connect(url, function(err, client) {
    test.equal(null, err);//判断错误对象是否为空,为空说明成功
    const adminDb = client.db(dbName)// 打开数据库进行操作
    console.log('链接数据库成功')
    client.close();
});

9.2 插入数据

./insert.js

const MongoClient=require('mongodb').MongoClient
const test=require('assert')
const url='mongodb://127.0.0.1:27017'
const dbName='mydata'//这里的mydata数据库是提前创建好的,其下创建了posts集合。

MongoClient.connect(url, function(err, client) {
    test.equal(null, err);
    const db = client.db(dbName)
    console.log('链接数据库成功')
    db.collection("posts").insertMany(
        [
            {title:"今天不是很热",tag:"life"},
            {title:"外卖还没到,非常饿!",tag:"life"},
        ],
        (err,result)=>{
            test.equal(null,err)
            client.close();
        }
    )
});

9.3 查询数据

./find.js

const MongoClient=require('mongodb').MongoClient
const test=require('assert')
const url='mongodb://127.0.0.1:27017'
const dbName='mydata'

MongoClient.connect(url, function(err, client) {
    test.equal(null, err);
    const adminDb = client.db(dbName)
    console.log('链接数据库成功')
    adminDb.collection("posts").find({tag:"study"}).toArray((err,docs)=>{
        test.equal(null, err);
        console.log(docs)
        client.close()
    })
});

十 处理异步

引言-异步/非阻塞的处理方式

10.1 回调地狱

Node.js是非阻塞编程,那么在编码过程中会遇到很多的回调函数(Callback),如果多个处理的回调函数嵌套在一起的话,就会形成回调地狱,虽然对于程序的结果没有任何影响,但对于程序代码的可读性来说就是个地狱。

function dbupd(sql, done) {
    setTimeout(() => done(sql + " upd ok."), 800);
}
dbupd("1.sql1", result => {
    console.log(result);
    dbupd("2.sql2", result => {
        console.log(result);
        dbupd("3.sql3", result => {
            console.log(result);
        });
    });
});

10.2 Promise解决

// Promise函数嵌套解决方法
function dbupAsync(sql) {
    const p = new Promise((resolve, reject) => {
        setTimeout(() => {
            console.log(sql + " upd ok.");
            resolve(sql + ".ok");
        }, 800)
    });
    return p;
}

方法一:

dbupAsync("2.sql1")
    .then(() => dbupAsync("2.sql2"))
    .then(() => dbupAsync("3.sql3"));

方法二:async/await 更简洁

async function upAllDB() {
    const result1 = await dbupAsync("3.sql1");
    const result2 = await dbupAsync("3.sql2");
    const result3 = await dbupAsync("3.sql3");
    console.log(result1, result2, result3);
}
upAllDB();

十一 封装express路由

./moudle/route


const fs=require('fs')
const path=require('path');
const url=require('url');

//获取文件类型
function getMimeType(extname){
    let data=fs.readFileSync('./mime.json')
    let mimeObj=JSON.parse(data.toString())//转为对象
    return mimeObj[extname]
};

//扩展res
function changeRes(res){
    res.send=(data)=>{
        res.writeHead(200, { 'Content-Type': 'text/html;charset="utf-8"' });
        res.end(data);
    }
}

//静态web服务的方法
function initStatic(req, res, staticPath) {
    //1、获取地址
    let pathname = url.parse(req.url).pathname;
    pathname = pathname == '/' ? '/index.html' : pathname;
    let extname = path.extname(pathname);
    //2、通过fs模块读取文件
    try {
        let data = fs.readFileSync('./' + staticPath + pathname);
        if (data) {
            let mime=getMimeType(extname)
            res.setHeader('Content-Type', mime+";charset='utf-8'");
            res.statusCode=200;
            res.end(data)
        }
    } catch (error) {

    }

}


let server = () => {
    let G = {
        _get:{},
        _post:{},
        staticPath:'static' //默认静态web目录
    };

    let app = function (req, res) {
        //扩展res的方法
        changeRes(res);
        //配置静态web服务
        initStatic(req, res,G.staticPath);

        let pathname = url.parse(req.url).pathname;
        //获取请求类型
        let method=req.method.toLowerCase();
        
        if (G['_'+method][pathname]) {

            if(method=="get"){ 
                G['_'+method][pathname](req, res);  //执行方法
            }else{
                //post  获取post的数据 把它绑定到req.body
                let postData = '';
                req.on('data',(chunk)=>{
                    postData+=chunk;
                })
                req.on('end',()=>{                  
                   req.body=postData;
                   G['_'+method][pathname](req, res);  //执行方法
                })
               
            }

        } else {
            res.writeHead(404, { 'Content-Type': 'text/html;charset="utf-8"' });
            res.end('页面不存在');
        }
    }
    //get请求
    app.get = function (str, cb) {
        //注册方法
        G._get[str] = cb;     
    }
    //post请求
    app.post = function (str, cb) {
        //注册方法
        G._post[str] = cb;      
    }
    //配置静态web服务目录
    app.static=function(staticPath){
        G.staticPath=staticPath;
    } 

    return app;
}
module.exports = server();

十二 注册用户并存入数据库

  • ejs
  • mongodb
  • fs
  • querystring

.server.js

const http=require("http")
const app=require('./module/route')//上面封装的
const ejs=require('ejs')
const qs=require('querystring')

const MongoClient=require('mongodb').MongoClient
const test=require('assert')
const url='mongodb://127.0.0.1:27017'
const dbName='mydata'

http.createServer(app).listen(3000)

//首页
app.get('/',(req,res)=>{
    ejs.renderFile("./views/index.html",{},(err,data)=>{
        res.send(data)
    })
})

//注册页
app.get('/register',(req,res)=>{
    ejs.renderFile("./views/register.ejs",{},(err,data)=>{
        res.send(data)
    })
})

//获取注册用户列表
app.get('/userlist',(req,res)=>{
    MongoClient.connect(url, function(err, client) {
        test.equal(null, err);
        const db = client.db(dbName)
        console.log('链接数据库成功')
        db.collection("users").find().toArray((err,result)=>{
            test.equal(null, err);
            ejs.renderFile("./views/userlist.ejs",{
                users:result
            },(err,data)=>{
                res.send(data)
                console.log('获取成功')
                client.close()
            })  
        })
    });
    
})

//提交注册
app.post('/submit',(req,res)=>{
    let body=qs.parse(req.body)
    MongoClient.connect(url, function(err, client) {
        test.equal(null, err);
        const db = client.db(dbName)
        console.log('链接数据库成功')
        db.collection("users").insertOne(
            body,
            (err,result)=>{
                test.equal(null,err)
                console.log('注册成功!')
                res.send('注册成功!')
                client.close();
            }
        )
    });
})