关于 Node.js Web应用开发

关于 Node.js Web应用开发

Node.js Web 框架演进

《狼书(卷2):Node.js Web应用开发》 第一章

Node.js http模块
最早的Connect
// 使用connect框架
var connect = require('connect')
var http = require('http')

var app = connect()

// 将开发中的可变部分抽象出来,通过app.use函数挂载到app对象上
// 中间件有顺序
// 中间价可分类:全局/局部

app.use('/', function foo(req, res, next){
    res.end('Hello from connect3!\n');
})

app.use('/2', function foo(req, res, next){
    res.end('Hello form connect2!\n');
})

// app.use(function(req, res){
//     res.end('Hello from connect!\n');
// })

http.createServer(app).listen(3011)

// 打开localhost:3011
曾经扛鼎的Express

内置了路由,将路由方法和中间件结合在一起,最终绑定到了app对象

var express = require('express');
var app = express();
var http = require('http');

app.get('/', function(req, res){
    res.send('hello world');
})

app.get('/2', function(req, res){
    res.send('hello world2');
})

// 通过内置模块启用(简单易用,但定制功能上略差)
// app.listen(4001)

// 使用http模块的http.createServer(app):可以完成更多定制功能
var server = http.createServer(app);
server.listen(4002)
新兴的Koa

Koa和Express中间件写法不一样之处在于http.creatteServer()函数的参数不同,其他的部分几乎一模一样

路由中间件

  • koa-generator koa-router
  • koa-trie-router
const http = require('http')
const Koa = require('koa')
const app = new Koa()

// 响应所有请求
app.use(async (ctx, next) => {
    // ctx.body = 'hello koa 2';

    // 多URL处理
    if (ctx.path == '/') {
        ctx.body = 'hello koa'
    }
    else if (ctx.path == '/2') {
        ctx.body = 'hello koa2'
    }
    else{
        ctx.body = 'hello koa else'
    }
});

app.listen(3005)

// 多URL不适用
// const server = http.createServer(app.callback())
// server.listen(3000)
const Koa = require('koa')
const Router = require('koa-trie-router')

let app = new Koa()
let router = new Router()

router
    .use(function (ctx, next) {
        console.log('* requests')
        return next()
    })
    .get(function (ctx, next) {
        console.log('GET requests')
        return next()
    })
    .put('/foo', function (ctx) {
        ctx.body = 'PUT /foo requests'
    })
    .put('/bar', function (ctx) {
        ctx.body = 'POST /bar requests'
    })

app.use(router.middleware())
app.listen(3333)

中间件的对比

中间件是Web框架的核心,无论是Express还是Koa的学习过程中,都应该重视它。写法和特性是相辅相成的,尤其是应用中常见的各种模块,绝大多数都是对中间件的封装。从http模块到中间件,这是一个非常好的演进路线。同样,从Express中间件进化到Koa中间价,也是非常重要的变革。

// Express中间件的经典写法
function (req, res, next) {
    res.send('hello world')
}

// Koa中间件写法
async function (ctx, next) {
    const start = new Date();
    await nect();
    const ms = new Date - start;
    ctx.set('X-Response-Time', '$(ms)ms');
}

Express和Koa大比拼

Express的哲学是为HTTP服务器提供小的、健壮的工具,使之成为开箱即用的Web框架

Koa只提供中间件内核,不绑定任何中间件

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-3CefB6SO-1646576971871)(C:\Users\BZL\AppData\Roaming\Typora\typora-user-images\image-20210719144247522.png)]

const bros = require('./build/Release/broscpp.node')
const Koa = require('koa')
const Router = require("koa-router")
const os = require('os')
const port = 8088

let app = new Koa()
let router = new Router()

// 获取IP地址
function getIPv4() {
    //同一接口可能有不止一个IP4v地址,所以用数组存
    let ipv4s = [];
    //获取网络接口列表对象
    let interfaces = os.networkInterfaces();
    Object.keys(interfaces).forEach(function(key) {
        interfaces[key].forEach(function(item) {
            //跳过IPv6 和 '127.0.0.1'
            if ('IPv4' !== item.family || item.internal !== false) return;
            //可用的ipv4s加入数组
            ipv4s.push(item.address); 
        })
    })
    return ipv4s[0]; //返回一个可用的即可
}

let host = getIPv4();

router.get('/msg_get', async (ctx, next) => {
    console.log("url value: " + ctx.request.querystring);
    var querys_tring = ctx.request.query.msg;
    bros.bros_talker(querys_tring);
    ctx.body = { querys_tring };
});

app.use(router.routes())
    .use(router.allowedMethods());

// app.listen(8088)
app.listen(port, () => {
    console.log("server started. listen at: ", host, port);
})

Koa 的使用

Context

Koa将Node.js的Request和Response对象封装到Context对象中,所以称为一次对话的上下文,通过加工Context对象,就可以控制返回给用户的内容

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-RIUvIQMY-1646576971872)(C:\Users\BZL\AppData\Roaming\Typora\typora-user-images\image-20210719174224060.png)]

Context对象内置了一些常用属性:

  • context.state
  • context.app
  • context.cookies
  • context.throw
常用属性和方法
ctx.request
  • ctx.request是Koa的Request对象,Koa的Request对象是在Node.js的请求对象之上的抽象,提供了很多对HTTP服务器开发有用的功能

  • ctx.request.url //获取请求URL

  • ctx.request.query //获取解析的查询字符串

  • ctx.request.querystring //获取原始查询字符串

  • 主要功能是获取GET请求中的参数

  • POST请求的参数获取方式和GET请求不同。Koa没有封装获取POST请求参数的方法,因此需要解析Context中的原生Node.js请求对象req

    • 可以通过koa-bodyparser等中间件来获取POST请求的参数
  • const Koa = require('koa');
    const app = new Koa();
    app.use(async (ctx) => {
        let postdata = '';
        ctx.req.on('data', (data) => {
            postdata += data;
        });
        ctx.req.on('end', () => {
            console.log(postdata);
        });
    });
    app.listen(3000);
    
    // curl 命令可以模拟POST请求
    // curl -d "param1=value&param2=value2" http://localhost:3000/
    
    // 处理路由
    const Koa = require('koa');
    const app = new Koa();
    app.use(async (ctx) => {
        if (ctx.request.method === 'POST'){
            
        }else if (ctx.request.method === 'GET'){
            if (ctx.request.path !== '/'){
                ctx.response.type = 'html';
                ctx.response.body = '<a href="/">Go To Index</a>'
            }else{
                ctx.response.body = 'hello world';
            }
        }
    });
    app.listen(3000);
    
ctx.response
  • ctx.response.body // 用于设置返回给用户的响应主题

  • ctx.response.status // 设置请求状态,如200、404、500等

  • ctx.response.type // 设置响应的Content-Type(HTML、图片…)

  • const Koa = require('koa');
    const app = new Koa();
    app.use(async (ctx) => {
        ctx.response.status = 200;
        if(ctx.response.accepts('json')){
            ctx.response.type = 'json';
            ctx.response.body = {data: 'hello world'};
        }else if(ctx.response.accepts('html')){
            ctx.response.type = 'html';
            ctx.response.body = '<p>hello world</p>';
        }else{
            ctx.response.type = 'text';
            ctx.response.body = 'hello world';
        }
    });
    
    app.listen(3000);
    
    // ctx.response.is(types...)
    // ctx.response.redirect(url, [alt]),这个方法用于将状态码302重定向到URL,例如用户登录后自动重定向到网站的首页
    
ctx.state
  • ctx.state是推荐的命名空间,用于通过中间件传递信息和前端视图。类似koa-views这些渲染View层的中间件也会默认把ctx.state里面的属性作为View的上下文传入ctx.state.user = yield User.find(id);,该代码是把user属性存放到ctx.state对象里,以便能够被另一个中间件读取
ctx.cookies
  • ctx.cookies用于获取和设置Cookie

  • ctx.cookies.get(name, [options]);
    ctx.cookies.set(name, value, [options]);
    
ctx.throw
  • ctx.throw用于抛出错误,把错误信息返回给用户

  • app.use(async (ctx) => {
        ctx.throw(500);
    })
    

把打印日志的部分单独抽象成一个logger函数

const logger = async function(ctx, next){
    console.log(ctx.method, ctx.host + ctx.url)
    // next() 会返回一个Promise对象,当前中间件位于next(0之后的代码会暂停执行,直到最后一个中间件执行完毕后,再自下而上一次执行每个中间件中next()之后的代码)
    await next();
}

app.use(logger)

app.use(async function (ctx, next){
    ctx.body = 'hello world'
})

如果想将多个中间件组合成一个单一的中间件,便于重用或导出,可以使用koa-compose,如下:

const compose = require('koa-compose');
async function middleware1(ctx, next){
    // do sth.
    await next();
};
async function middleware2(ctx, next){
    // do sth.
    await next();
};
async function middleware3(ctx, next){
    // do sth.
    await next();
};

const all = compose([middleware1, middleware2, middleware3]);
app.use(all);
几个常用的中间件
koa-bodyparser
  • 该中间件可以把POST请求的参数解析到ctx.request.body中
const Koa = require('koa')
const app = new Koa()
const bodyParser = require('koa-bodyparser')

// 加入了html
app.use(bodyParser())
app.use(async (ctx) => {
    if (ctx.url === '/' && ctx.method === 'GET') {
        ctx.type = 'html';
        let html = `
        <h1>登录</h1>
        <form method="POST" action="/">
            <p>用户名</p>
            <input name="userName" /><br/>
            <p>密码</p>
            <input name="password" type="password /><br/>
            <button type="submit">submit</button>
        </form>`
        ctx.body = html;
    }else if (ctx.url === '/' && ctx.method === 'POST'){
        let postData = ctx.request.body
        ctx.body = postData
    }
})

app.listen(3000)
koa-router
  • 简化了路由的写法
  • 用该中间件改造上面的程序
  const Koa = require('koa')
  const app = new Koa()
  const bodyParser = require('koa-bodyparser')
  const Router = require('koa-router')
  const router = new Router()
  router.get('/', (ctx, next) => {
      // 绘制登录页,省略
  })
  router.post('/', (ctx, next) => {
      // 解析formData,省略
  })
  
  app.use(bodyParser())
  .use(router.routes())  // 加载koa-router中间件
  .use(router.allowedNethods())  // 对异常状态码的处理
  
  app.listen(3000)
koa-static和koa-view
  • 实际开发中,把HTML模板写在单独的文件中,引用单独的CSS样式及JavaScript文件
  • koa-static是专门用于加载静态资源的中间件,通过它可以为页面请求加载CSS、JavaScript等静态资源
  • koa-view用于加载HTML模板文件
const Koa = require('koa')
const views = require('koa-views')
const path = require('path')
const bodyParser = require('koa-bodyparser')
const static = require('koa-static')
const Router = require('koa-router')
const app = new Koa()
const router = new Router()

app.use(views(__dirname + '/views'), {  // 加载模板引擎
    map: {html: 'ejs'}
})

app.use(static(						// 加载静态资源
    path.join(__dirname, '/static')
))

app.get('/', async(ctx, next) => 
    await ctx.render('index')			// 渲染模板
})

app.post('/', (ctx, next) => {
    let postData = ctx.request.body
    ctx.body = postData
})

app.use(bodyParser())
.use(router.routes())
.use(router.allowedMethods())

app.listen(3000)

REST(表现层状态转移)设计一般符合以下条件

  • 程序或应用的事物都应该抽象为资源
  • 每个资源对应唯一的URI
  • 使用统一的接口对资源进行操作
  • 对资源的各种操作不会改变资源标识
  • 所有操作都是无状态的

如果一条路由请求在all方法和其它方法中同时命中,只有执行了await next(),这条路由才会在all方法和其它方法中都起作用

  • 在项目中,all()方法一般用来设置请求头,如设置过期时间、CORS、跨域资源共享等

  • HTTP状态码主要包括1xx(消息)、2xx(成功)、3xx(重定向)、4xx(请求错误)、5xx和6xx(服务器错误)等6种不同类型。表4.1列举了常用的HTTP状态码

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-paS8SV1W-1646576971873)(C:\Users\BZL\AppData\Roaming\Typora\typora-user-images\image-20210720112300734.png)]

  • MVC模式在概念上强调Model、View和Controller的分离,模块间也遵循着由Controller进行消息处理、Model进行数据源处理、View进行数据显示的职责分离原则。因此,在实现上,MVC模式(如图5.1所示)的Framework通常会将MVC的3个部分分离实现。

Web应用开发

必然涉及两个方向

  • 前端界面
  • 后端服务
  • 需要后端服务提供数据信息给前端界面进行展示
    • 前端发起HTTP请求,以Ajax的方式调用后端提供的服务接口,后端接口接收到请求之后,进行相应的逻辑处理,并返回对应的数据给前端,然后由前端进行动态的HTML片段替换
模板引擎
  • 模板引擎是Web应用中用来生成动态HTML的工具,负责将数据模型与HTML模板结合(模板渲染),生成最终的HTML

  • 编写HTML模板的语法被称为模板语法,模板语法的表达能力和可扩展性决定了模板引擎的易用性

  • 模板引擎可以让程序实现界面与数据、业务代码与逻辑代码的分离,大大提升了开发效率,良好的设计也使代码重用变得更加容易

    • 置换型
      • 实现简单,但效率低下
    • 解释型
    • 编译型
  • Nunjucks

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-r1GMXlyq-1646576971874)(C:\Users\BZL\AppData\Roaming\Typora\typora-user-images\image-20210720171138548.png)]

ctx.set("Content-Type", "application/json")
ctx.body = JSON.stringify(json)

// 提取为中间件
module.exports = () => {
    function render(json) {
        this.set("Content-Type", "application/json");
        this.body = JSON.stringify(json);
    }
    return async (ctx, next) => {
     // send 函数的作用是将需要处理的json对象转化为字符串
        ctx.send = render.bind(ctx);
        await next();
    }
}

为了是项目便于维护,建议将所有的中间件独立出来进行管理

通过应用MVC模式结合Koa框架来开发Web应用,使项目的层次更加清晰;业务之间相互解耦,易于扩展,能够提高团队的开发效率。

需要安装的插件/中间件
#sudo npm install koa-router -g
sudo npm install node-addon-api -g
sudo npm i koa-router -g
sudo npm i koa-compose -g
sudo npm i koa-bodyparser -g
sudo npm i koa-static -g
sudo npm i koa-views -g

# 监测项目中的所有文件,一旦发现文件改动,自动重启
# 启动 nodemon app.js
sudo npm i nodemon -g
sudo npm i koa-nunjucks-2 -g
sudo npm i koa-multer -g

如何查看端口占用情况

ubuntu 查看端口被占用并处理

lsof -i:[端口]

bootstrap

Bootstrap,来自 Twitter,是目前最受欢迎的前端框架。Bootstrap 是基于 HTML、CSS、JAVASCRIPT 的,它简洁灵活,使得 Web 开发更加快捷。

布局参考:https://v3.bootcss.com/examples/dashboard/

JavaScript

  • 声明一个对象,当做命名空间来使用
  • 定义一个公共函数来获取指定id元素,减少代码量,提高代码复用率
  • 给元素设置 id
  • 正则是公认的难学但非常有用的工具
// 声明一个对象,当做命名空间来使用
var eg = {};
// 定义一个公共函数来获取指定id元素,减少代码量,提高代码复用率
eg.$ = function(id){
    return document.getElementById(id);
}

// 调用
eg.regCheck = function(){
    var uid = eg.$("userid");
    var upwd = eg.$("userpwd");
}

先写好html结构基础,再配合CSS做出大致效果,最后用JavaScript加上各种动作

将JavaScript引用放在元素中的页面内容后面

对于getElementById( )、getElementsByClassName( )和getElementsByTagName( )这3个方法来说,只有getElementsByTagName( )这一个方法能够操作动态DOM

function showLog(str) {
    // class id
    if (!log) log = $("#log");
    // 推数据
    log.append("<li class='"+className+"'>"+str+"</li>");
    if(log.children("li").length > 8) {
        log.get(0).removeChild(log.children("li")[0]);
    }
}

DOM

想要创建一个元素,需要以下4步:

① 创建元素节点:createElement()。

② 创建文本节点:createTextNode()。

③ 把文本节点插入元素节点:appendChild()。

④ 把组装好的元素插入到已有元素中:appendChild()。

插入元素 将创建的元素插入到HTML

JavaScript中,插入元素,有两种方法:

  • appendChild()

    • // 语法: 插入到末尾
      A.appendChild(B);
      
      // 为按钮添加点击事件
      var oBtn = document.getElementById("btn");
      oBtn.onclick = function()
      {
          // content
      }
      
  • insertBefore()

    • // 语法: 在ref之前插入B
      // A表示父元素,B表示新子元素,ref表示指定子元素
      A.insertBefore(B, ref);
      
      
删除元素
A.emoveChild(B);
复制元素
// obj: 被复制的元素
// 参数为true:表示复制元素本身及复制该元素下的所有子元素
// 参数为false: 表示不复制子元素
obj.cloneNode(bool);
替换元素
A.replaceChild(new, old);
获取HTML属性值

对于HTML元素的属性值的获取,一般可以通过属性名来找到该属性对应的值(对象属性

// 语法
// obj 是元素名,它是一个DOM对象(即使用getElementById( )、getElementsByTagName( )等方法获取的元素节点)
// attr是属性名,对于一个对象来说,可以通过点运算符(.)来获取它的属性值
obj.attr

对象方法

  • getAttribute()
  • setAttribute()
  • removeAttribute()
  • hasAttribute()
// 语法
obj.getAttribute("attr");
obj.setAttribute("attr", "值");
获取CSS属性值

JavaScript中,可以使用getComputedStyle()方法来获取一个CSS属性的取值

// 语法
// attr表示CSS属性名,这里的属性名使用的是“小驼峰型”
getComputedStyle(obj).attr
设置CSS属性值
  • style 对象
// 语法
obj.style.attr="值"

// 对于文本框
obj.style["attr"]
  • cssText属性

使用cssText属性来同时设置多个CSS属性

// 语法
obj.style.cssText="值"
innerHTML和innerText

事件

JavaScript中,一个事件包含3部分:
  1. 事件主角:是按钮?还是div元素?还是其他?
  2. 事件类型:是单击?还是移动?还是其他?
  3. 事件过程:这个事件都发生了些什么?
JavaScript常见的事件有五种

事件操作是JavaScript的核心

  1. 鼠标事件
  2. 键盘事件
  3. 表单事件
  4. 编辑事件
  5. 页面事件
鼠标事件
  • onclick 鼠标单击事件
  • onmouseover 鼠标移入事件
  • onmouseout 鼠标移出事件
  • onmousedown 鼠标按下事件
  • onmouseup 鼠标松开事件
  • onmousemove 鼠标移动事件
键盘事件
  • onKeydown 键盘按下
  • onKeyup 键盘松开
表单事件
  • onfoucus和onblur 获取焦点/失去焦点触发的事件
  • onselect
  • onchange
  • onsubmit
编辑事件
  • oncopy
  • onselectstart
  • oncontextmenu
页面事件
  • onload 表示文档加载完成后再执行的一个事件
  • onbeforeunload
事件监听器
JavaScript中,如果要给元素添加一个事件,可采用以下两种方式
  • 事件处理器
  • 事件监听器
事件监听器

指的是使用addEventListener()方法为一个元素添加事件,又称为“绑定事件”

// 语法
// obj是一个DOM对象
// type是一个字符串,指的是事件类型(如单击事件用click,这个事件类型不需要加“on”)
// fn是一个函数名或匿名函数
// false表示事件冒泡阶段调用
obj.addEventListener(type, fn, false)
// 使用事件监听器为同一个元素添加多个相同的事件
<!DOCTYPE html>
<html>
<head>
    <title></title>
    <meta charset="utf-8" />
    <script>
        window.onload = function () {
            var oBtn = document.getElementById("btn");

            oBtn.addEventListener("click", function () {
                alert("第1次");
            }, false);
            oBtn.addEventListener("click", function () {
                alert("第2次");
            }, false);
            oBtn.addEventListener("click", function () {
                alert("第3次");
            }, false);
        }
    </script>
</head>
<body>
    <input id="btn" type="button" value="按钮"/>
</body>
</html>
解绑事件

JavaScript在,可以用removeEventListener()方法为元素解绑某个事件,解绑事件与绑定事件是功能相反的操作

// 语法
// obj是一个DOM对象
// type是一个字符串,指的是事件类型(如单击事件用click,这个事件类型不需要加“on”)
// fn 必须是一个函数名,而不是一个函数
// false表示事件冒泡阶段调用
obj.addEventListener(type, fn, false)
event对象

当一个事件发生的时候,这个事件有关的详细信息都会临时保存到一个指定的地方,这个地方就是event对象。每一个事件,都有一个对应的event对象

event对象常用属性
属性说明
type事件类型
keyCode键码值
shiftKey是否按下 shift键
CtrlKey是否按下ctrl键
AltKey是否按下alt键
<!DOCTYPE html>
<html>
<head>
    <title></title>
    <meta charset="utf-8" />
    <script>
        window.onload = function () {
            document.onkeydown = function (e) {
                if (e.shiftKey || e.altKey || e.ctrlKey) {
                    alert("禁止使用shift、alt、ctrl键!")
                }
            }
        }
    </script>
</head>
<body>
    <div>绿叶,给你初恋般的感觉~</div>
</body>
</html>
this

在事件操作中,可以这样理解:哪个DOM对象(元素节点)调用了this所在的函数,那么this指向的就是哪个DOM对象。

对话框
alert

一般只用于提示文字

confirm

不仅提示文字,还提供确认(返回true和false)

prompt

不仅会提示文字,还会返回一个字符串

定时器

setTimeout和clearTimeout

使用前者来“一次性”的调用函数,使用后者来取消执行前者

// code 可以是一段代码,可以是一个函数,也可以是一个函数名
setTimeout(code, time);
setInterval和clearInterval

重复性调用

// code 可以是一段代码,可以是一个函数,也可以是一个函数名
setInterval(code, time);

参考文章

koa获取get参数

nodejs控制台打印彩色及使用koa实现端口和ip打印

npm install 、npm install --save 和 npm install --save-dev的区别

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值