• Node.js学习笔记(三)——Node.js开发Web后台服务


    一、Express

    Express 是一个简洁而灵活的 node.js Web应用框架, 提供了一系列强大特性帮助你创建各种 Web 应用,和丰富的 HTTP 工具。
    使用 Express 可以快速地搭建一个完整功能的网站。使用Node.js作为AngularJS开发Web服务器的最佳方式是使用Express模块。

    Express官网: http://expressjs.com/

    Express4.x API:http://expressjs.com/zh-cn/4x/api.html

    1.2、Express框架核心特性

    可以设置中间件来响应 HTTP 请求。

    定义了路由表用于执行不同的 HTTP 请求动作。

    可以通过向模板传递参数来动态渲染 HTML 页面。

    丰富的 HTTP 快捷方法和任意排列组合的 Connect 中间件,让你创建健壮、友好的 API 变得既快速又简单。

    Express 不对 Node.js 已有的特性进行二次抽象,我们只是在它之上扩展了 Web 应用所需的基本功能。

    1.3、安装 Express

    安装 Express 并将其保存到依赖列表中:

    npm install express --save

    以上命令全局安装express。也可安装时指定安装中间件。

    body-parser - node.js 中间件,用于处理 JSON, Raw, Text 和 URL 编码的数据。

    cookie-parser - 这就是一个解析Cookie的工具。通过req.cookies可以取到传过来的cookie,并把它们转成对象。

    multer - node.js 中间件,用于处理 enctype="multipart/form-data"(设置表单的MIME编码)的表单数据。

    $ npm install body-parser --save
    $ npm install cookie-parser --save
    $ npm install multer --save

    默认这些模块都已经添加。

    1.4、第一个Express框架实例

    接下来我们使用 Express 框架来输出 "Hello World"。
    以下实例中我们引入了 express 模块,并在客户端发起请求后,响应 "Hello World" 字符串。

    创建一个目录,如Project,进入命令行:

    使用npm install express 导入express模块。

    在目录下创建hello.js文件,如下所示:

    //引入express模块
    var express = require('express');
    //创建一个app对象,类似一个web 应用(网站)
    var app = express();
    //接受指定路径的请求,指定回调函数
    app.get('/', function (req, res){
    res.send('Hello World');
    });
    //创建一个web服务器,可以认为就是web服务器对象
    //监听8081端口,当监听成功时回调
    var server = app.listen(8081, function () {
       var host = server.address().address;  //地址
       var port = server.address().port;  //端口
        console.log("应用实例,访问地址为 http://%s:%s", host, port);
    });
    })

    使用node执行js:

    运行结果:

    1.5、Express中use挂载中间件的方法

    1.5.1、为什么需要中间件

    一个请求发送到服务器后,它的生命周期是 先收到request(请求),然后服务端处理,处理完了以后发送response(响应)回去,而这个服务端处理的过程就有文章可做了,想象一下当业务逻辑复杂的时候,为了明确和便于维护,需要把处理的事情分一下,分配成几个部分来做,而每个部分就是一个中间件。

    1.5.2、use方法

    1.app.use([path,], function [, function…]) 挂载中间件方法到路径上。如果路径未指定,那么默认为”/”

    app.use(function(req, res, next) {

    });

    中间件其是一个函数,在响应发送之前对请求进行一些操作,这个函数有些不太一样,它还有一个next参数,而这个next也是一个函数,它表示函数数组中的下一个函数,如果当前中间件函数没有结束请求/响应循环,那么它必须调用 next(),以将控制权传递给下一个中间件函数。否则,请求将保持挂起状态。

     1.5.3、路径匹配

    一个路由将匹配任何路径如果这个路径以这个路由设置路径后紧跟着”/”。比如:app.use(‘/apple’, …)将匹配”/apple”,”/apple/images”,”/apple/images/news”等

    在一个路径上挂载一个中间件之后,每当请求的路径的前缀部分匹配了这个路由路径,那么这个中间件就会被执行。 由于默认的路径为/,中间件挂载没有指定路径,那么对于每个请求,这个中间件都会被执行

    app.use(function (req, res, next) {
      console.log('Time: %d', Date.now())
      next()
    })

    中间件方法是顺序处理的,所以中间件包含的顺序是很重要的,第二个中间件将不会到执行到

    // this middleware will not allow the request to go beyond it
    app.use(function (req, res, next) {
      res.send('Hello World')
    })
    
    // requests will never reach this route
    app.get('/', function (req, res) {
      res.send('Welcome')
    })

    对于相同的挂载路径可以挂载多个中间件,因为路径的相同,调用next的时候会自动执行下一个匹配相同路径的中间件

    You can define and mount a middleware function locally.
    
    app.use(function (req, res, next) {
      next()
    })
    A router is valid middleware.
    
    var router = express.Router()
    router.get('/', function (req, res, next) {
      next()
    })
    app.use(router)
    An Express app is valid middleware.
    
    var subApp = express()
    subApp.get('/', function (req, res, next) {
      next()
    })
    app.use(subApp)

     1.5.4、挂载静态资源

    下面是一些例子,在Express程序中使用express.static中间件。 为程序托管位于程序目录下的public目录下的静态资源

    // GET /style.css etc
    app.use(express.static(path.join(__dirname, 'public')))

    在/static路径下挂载中间件来提供静态资源托管服务,只当请求是以/static为前缀的时候

    // GET /static/style.css etc.
    app.use('/static', express.static(path.join(__dirname, 'public')))

    通过在设置静态资源中间件之后加载日志中间件来关闭静态资源请求的日志

    app.use(express.static(path.join(__dirname, 'public')))
    app.use(logger())

    1.5.5、路由

    var router = express.Router()
    router.get('/', function (req, res, next) {
      next()
    })
    app.use(router)
    An Express app is valid middleware.

    1.5.6、morgan日志

    预定义格式

    morgan预定义了多种格式:

    combined

    Standard Apache combined log output. 标准的Apache结合日志输出格式

    :remote-addr - :remote-user [:date[clf]] ":method :url HTTP/:http-version" :status :res[content-length] ":referrer" ":user-agent"
    common

    Standard Apache common log output.标准的Apache通用日志输出格式

    :remote-addr - :remote-user [:date[clf]] ":method :url HTTP/:http-version" :status :res[content-length]
    dev

    Concise output colored by response status for development use. The :status token will be colored green for success codes, red for server error codes, yellow for client error codes, cyan for redirection codes, and uncolored for information codes.

    为开发者使用的彩色输出状态,如果成功则状态标记为绿色,红色为服务器端错误代码,黄色为客户端错误代码,青色为重定向代码,没有使用彩色的表示普通信息。

    :method :url :status :response-time ms - :res[content-length]
    short

    Shorter than default, also including response time. 比默认更短且包含响应时间的日志格式

    :remote-addr :remote-user :method :url HTTP/:http-version :status :res[content-length] - :response-time ms
    tiny

    The minimal output. 最小的日志输出格式

    :method :url :status :res[content-length] - :response-time ms

    1.6、生成项目

    1.6.1、Express 应用程序生成器

    通过应用生成器工具 express-generator 可以快速创建一个应用的骨架。

    你可以通过 npx (包含在 Node.js 8.2.0 及更高版本中)命令来运行 Express 应用程序生成器。

    $ npx express-generator
    

    对于较老的 Node 版本,请通过 npm 将 Express 应用程序生成器安装到全局环境中并使用:

    $ npm install -g express-generator
    $ express
    

    -h 参数可以列出所有可用的命令行参数:

    $ express -h
    
      Usage: express [options] [dir]
    
      Options:
    
        -h, --help          输出使用方法
            --version       输出版本号
        -e, --ejs           添加对 ejs 模板引擎的支持
            --hbs           添加对 handlebars 模板引擎的支持
            --pug           添加对 pug 模板引擎的支持
        -H, --hogan         添加对 hogan.js 模板引擎的支持
            --no-view       创建不带视图引擎的项目
        -v, --view <engine> 添加对视图引擎(view) <engine> 的支持 (ejs|hbs|hjs|jade|pug|twig|vash) (默认是 jade 模板引擎)
        -c, --css <engine>  添加样式表引擎 <engine> 的支持 (less|stylus|compass|sass) (默认是普通的 css 文件)
            --git           添加 .gitignore
        -f, --force         强制在非空目录下创建
    

    例如,如下命令创建了一个名称为 myapp 的 Express 应用。此应用将在当前目录下的 myapp 目录中创建,并且设置为使用 Pug 模板引擎(view engine):

    $ express --view=pug myapp
    
       create : myapp
       create : myapp/package.json
       create : myapp/app.js
       create : myapp/public
       create : myapp/public/javascripts
       create : myapp/public/images
       create : myapp/routes
       create : myapp/routes/index.js
       create : myapp/routes/users.js
       create : myapp/public/stylesheets
       create : myapp/public/stylesheets/style.css
       create : myapp/views
       create : myapp/views/index.pug
       create : myapp/views/layout.pug
       create : myapp/views/error.pug
       create : myapp/bin
       create : myapp/bin/www
    

    然后安装所有依赖包:

    $ cd myapp
    $ npm install
    

    在 MacOS 或 Linux 中,通过如下命令启动此应用:

    $ DEBUG=myapp:* npm start
    

    在 Windows 命令行中,使用如下命令:

    > set DEBUG=myapp:* & npm start
    

    在 Windows 的 PowerShell 中,使用如下命令:

    PS> $env:DEBUG='myapp:*'; npm start
    

    然后在浏览器中打开 http://localhost:3000/ 网址就可以看到这个应用了。

    通过生成器创建的应用一般都有如下目录结构:

    .
    ├── app.js
    ├── bin
    │   └── www
    ├── package.json
    ├── public
    │   ├── images
    │   ├── javascripts
    │   └── stylesheets
    │       └── style.css
    ├── routes
    │   ├── index.js
    │   └── users.js
    └── views
        ├── error.pug
        ├── index.pug
        └── layout.pug
    
    7 directories, 9 files
    

    通过 Express 应用生成器创建应用只是众多方法中的一种。你可以不使用它,也可以修改它让它符合你的需求。

    1.6.2、使用nodeclipse插件插件

    如果直接使用记事本效率会不高,nodeclipse插件可以方便的创建一个Express项目,步骤如下:

    创建好的项目如下:

    app.js是网站:

    var express = require('express');
    var path = require('path');
    var favicon = require('serve-favicon');
    var logger = require('morgan');
    var cookieParser = require('cookie-parser');
    var bodyParser = require('body-parser');
    
    var index = require('./routes/index');
    var users = require('./routes/users');
    
    var app = express();
    
    //指定视图引擎为ejs
    app.set('views', path.join(__dirname, 'views'));
    app.set('view engine', 'ejs');
    
    // uncomment after placing your favicon in /public
    //app.use(favicon(path.join(__dirname, 'public', 'favicon.ico')));
    app.use(logger('dev'));
    app.use(bodyParser.json());
    app.use(bodyParser.urlencoded({ extended: false }));
    app.use(cookieParser());
    app.use(express.static(path.join(__dirname, 'public')));
    
    app.use('/', index);
    app.use('/users', users);
    
    // catch 404 and forward to error handler
    app.use(function(req, res, next) {
      var err = new Error('Not Found');
      err.status = 404;
      next(err);
    });
    
    // error handler
    app.use(function(err, req, res, next) {
      // set locals, only providing error in development
      res.locals.message = err.message;
      res.locals.error = req.app.get('env') === 'development' ? err : {};
    
      // render the error page
      res.status(err.status || 500);
      res.render('error');
    });
    
    module.exports = app;

    bin\www是web服务器:

    #!/usr/bin/env node
    
    /**
     * 依赖模块,导入
     */
    
    var app = require('../app');
    var debug = require('debug')('nodejsexpress:server');
    var http = require('http');
    
    /**
     * 从上下文环境中获得监听端口,如果空则3000
     */
    
    var port = normalizePort(process.env.PORT || '3000');
    app.set('port', port);
    
    /**
     * 创建Web服务器
     */
    
    var server = http.createServer(app);
    
    /**
     * 开始监听
     */
    
    server.listen(port);
    server.on('error', onError);  //指定发生错误时的事件
    server.on('listening', onListening);  //当监听成功时的回调
    
    /**
     * 规范化端口
     */
    
    function normalizePort(val) {
      var port = parseInt(val, 10);
    
      if (isNaN(port)) {
        // named pipe
        return val;
      }
    
      if (port >= 0) {
        // port number
        return port;
      }
    
      return false;
    }
    
    /**
     *错误事件监听
     */
    
    function onError(error) {
      if (error.syscall !== 'listen') {
        throw error;
      }
    
      var bind = typeof port === 'string'
        ? 'Pipe ' + port
        : 'Port ' + port;
    
      //错误处理
      switch (error.code) {
        case 'EACCES':
          console.error(bind + ' requires elevated privileges');
          process.exit(1);  //结束程序
          break;
        case 'EADDRINUSE':
          console.error(bind + ' is already in use');
          process.exit(1);
          break;
        default:
          throw error;
      }
    }
    
    /**
     * 当用户访问服务器成功时的回调
     */
    
    function onListening() {
      var addr = server.address();
      var bind = typeof addr === 'string'
        ? 'pipe ' + addr
        : 'port ' + addr.port;
      debug('Listening on ' + bind);
    }

    routers/index.js路由,有点类似控制器或Servlet:

    var express = require('express');
    var router = express.Router();
    
    /* 获得首页 */
    router.get('/', function(req, res, next) {
      res.render('index', { title: 'Express' });
    });
    
    module.exports = router;

    views/index.ejs首页视图:

    <!DOCTYPE html>
    <html>
      <head>
        <title><%= title %></title>
        <link rel='stylesheet' href='/stylesheets/style.css' />
      </head>
      <body>
        <h1><%= title %></h1>
        <p>Welcome to <%= title %></p>
      </body>
    </html>

    在www上右键选择“运行方式”->“Node Application”运行结果:

    1.6.3、request对象

    Request 对象 - request 对象表示 HTTP 请求,包含了请求查询字符串,参数,内容,HTTP 头部等属性。常见属性有:

    req.app:当callback为外部文件时,用req.app访问express的实例
    req.baseUrl:获取路由当前安装的URL路径
    req.body / req.cookies:获得「请求主体」/ Cookies
    req.fresh / req.stale:判断请求是否还「新鲜」
    req.hostname / req.ip:获取主机名和IP地址
    req.originalUrl:获取原始请求URL
    req.params:获取路由的parameters
    req.path:获取请求路径
    req.protocol:获取协议类型
    req.query:获取URL的查询参数串
    req.route:获取当前匹配的路由
    req.subdomains:获取子域名
    req.accepts():检查可接受的请求的文档类型
    req.acceptsCharsets / req.acceptsEncodings / req.acceptsLanguages:返回指定字符集的第一个可接受字符编码
    req.get():获取指定的HTTP请求头
    req.is():判断请求头Content-Type的MIME类型

    1.6.4、response对象

    Response 对象 - response 对象表示 HTTP 响应,即在接收到请求时向客户端发送的 HTTP 响应数据。常见属性有:

    res.app:同req.app一样
    res.append():追加指定HTTP头
    res.set()在res.append()后将重置之前设置的头
    res.cookie(name,value [,option]):设置Cookie
    opition: domain / expires / httpOnly / maxAge / path / secure / signed
    res.clearCookie():清除Cookie
    res.download():传送指定路径的文件
    res.get():返回指定的HTTP头
    res.json():传送JSON响应
    res.jsonp():传送JSONP响应
    res.location():只设置响应的Location HTTP头,不设置状态码或者close response
    res.redirect():设置响应的Location HTTP头,并且设置状态码302
    res.render(view,[locals],callback):渲染一个view,同时向callback传递渲染后的字符串,如果在渲染过程中有错误发生next(err)将会被自动调用。callback将会被传入一个可能发生的错误以及渲染后的页面,这样就不会自动输出了。
    res.send():传送HTTP响应
    res.sendFile(path [,options] [,fn]):传送指定路径的文件 -会自动根据文件extension设定Content-Type
    res.set():设置HTTP头,传入object可以一次设置多个头
    res.status():设置HTTP状态码
    res.type():设置Content-Type的MIME类型

    1.6.5、express获取参数有三种方法

    req.query 适合 http://localhost:3000/form?num=8888
    req.body 适合http://localhost:3000/form,Post请求中的参数
    req.params 适合获取form后的num:http://localhost:3000/form/num

    (一)、GET

    var num = req.query.num;
    res.send("你获取的get数据为:" + num); 

    (二)、POST

    解析post数据需要用到body-parser

    npm install body-parser --save

    app.js

    var express = require('express');
    var app = express();
    //引入body-parser
    var bodyParser = require('body-parser');
    app.use(express.static('public'));
    
    //需要use的
    app.use(bodyParser.json()); // for parsing application/json
    app.use(bodyParser.urlencoded({
    extended: true
    })); // for parsing application/x-www-form-urlencoded
    
    //获取数据
    app.post('/form', function(req, res) {
    var num = req.body.num;
    res.send("你获取的post数据为:" + num);
    });
    
    //设置监听端口
    app.listen(3000);

    public/test.html

    <!DOCTYPE html>
    <html lang="en">
    <head>
    <meta charset="UTF-8">
    <title>Document</title>
    <script src="http://apps.bdimg.com/libs/jquery/2.1.4/jquery.min.js"></script>
    </head>
    <body>
    <div id="box"></div>
    <form action="/form" method="post">
    <input type="text" name="num" value="tinyphp">
    <input type="submit" value="提交">
    </form>
    
    </body>
    </html>

    (三)、获取路径参数

    app.js

    var express = require('express');
    var app = express();
    
    //获取数据
    app.get('/form/:num', function(req, res) {
    var num = req.params.num;
    res.send("你获取到form/后的参数:" + num);
    });
    
    //设置监听端口
    app.listen(3000);

    1.7、ejs基础

    ejs是一个Express Web应用的模板引擎,在NodeJS开发中可以选择的模板引擎可能是所有Web应用开发中范围最广的,如jade、ejs、htmljs、swig、hogan.js,但ejs是最容易上手的,与jsp,asp,php的原始模板引擎风格很像。

    官网:https://ejs.bootcss.com/

    添加一个product.js路由:

    var express = require('express');
    var router = express.Router();
    
    /* 产品 */
    router.get('/', function(req, res, next) {
        
      var products=[];
      products.push({name:"ZTE U880",price:899.8});
      products.push({name:"HuWei 荣耀8",price:1899.8});
      products.push({name:"iPhone 7 Plus 128G",price:5899.8});
      
      //将product视图与指定的对象渲染后输出到客户端
      res.render('product', { title: '天狗商城', pdts:products});
    });
    
    module.exports = router;

    在views目录下添加product.ejs视图,这里是一个简单的MVC:

    <!DOCTYPE html>
    <html>
    
        <head>
            <title>
                <%= title %>
            </title>
            <link rel='stylesheet' href='/stylesheets/style.css' />
        </head>
    
        <body>
            <h1><%= title %> - 产品列表</h1>
            <table border="1" width="80%">
                <tr>
                    <th>序号</th>
                    <th>名称</th>
                    <th>价格</th>
                </tr>
                <%pdts.forEach(function(pdt,index){%>
                <tr>
                    <td>
                        <%=index+1%>
                    </td>
                    <td>
                        <%=pdt.name%>
                    </td>
                    <td>
                        <%=pdt.price%>
                    </td>
                </tr>
                <%});%>
            </table>
    
            <ul>
                <% for(var i=0; i<pdts.length; i++) {%>
                <li>
                    <%=pdts[i].name%>
                </li>
                <% } %>
        </body>
    
    </html>

    修改app,注册定义好的模块product:

    var index = require('./routes/index');
    var users = require('./routes/users');
    var pdts = require('./routes/product');
    
    var app = express();
    
    //指定视图引擎为ejs
    app.set('views', path.join(__dirname, 'views'));
    app.set('view engine', 'ejs');
    
    // uncomment after placing your favicon in /public
    //app.use(favicon(path.join(__dirname, 'public', 'favicon.ico')));
    app.use(logger('dev'));
    app.use(bodyParser.json());
    app.use(bodyParser.urlencoded({ extended: false }));
    app.use(cookieParser());
    app.use(express.static(path.join(__dirname, 'public')));
    
    app.use('/', index);
    app.use('/users', users);
    app.use('/pdt', pdts);

    运行结果:

    1.8、lodash

    这是一个具有一致接口、模块化、高性能等特性的 JavaScript 工具库。可以非常方便的操作json。

    官网:http://lodashjs.com/

    安装:

    npm i -g npm

    npm i --save lodash

    安装时先用cd切换到当前项目下。

    如果浏览器使用可以直接引入:

    <script src="lodash.js"></script>

    添加lodash依赖:

    依赖成功后会在package.json中添加引用:

    后台Node.js使用,可以引入模块:

    //导入lodash模块
    var _= require('lodash');
    
    var products=[];
    products.push({name:"ZTE U880",price:899.8});
    products.push({name:"HuWei 荣耀8",price:1899.8});
    products.push({name:"iPhone 7 Plus 128G",price:5899.8});
    
    //1、取出第一个元素
    var obj1=_.first(products);
    console.log(obj1.name);  //ZTE U880
    
    //2、取出最后一个元素
    var obj2=_.last(products);
    console.log(obj2.name);  //iPhone 7 Plus 128G
    
    //3、指定查找条件返回符合条件的索引
    var obj3=_.findIndex(products,function(obj){
        return obj.price>=1000&&obj.name.indexOf("7")>0;
    });
    console.log(obj3);  //2
    
    //4、指定查找条件返回查找到的对象
    var obj4=_.find(products,function(obj){
        return obj.price>=1000&&obj.name.indexOf("7")>0;
    });
    console.log(obj4);  //{ name: 'iPhone 7 Plus 128G', price: 5899.8 }
    
    //5、排序
    var obj5=_.orderBy(products,["price","name"],["desc","asc"]);
    console.log(obj5); 
    
    //[ { name: 'iPhone 7 Plus 128G', price: 5899.8 },
    //{ name: 'HuWei 荣耀8', price: 1899.8 },
    //{ name: 'ZTE U880', price: 899.8 } ]
    
    //6、查找价格为1899.8的产品的key
    var obj6=_.findKey(products,{price:1899.8});
    console.log(obj6);   //1

    API的使用非常简单,但需要注意版本,可以现查现用,API地址:https://lodash.com/docs/4.17.2 

    1.9、参数

    1.9.1、URL中的参数占位

    Checks route params (req.params), ex: /user/:id

    127.0.0.1:3000/index,这种情况下,我们为了得到index,我们可以通过使用req.params得到,通过这种方法我们就可以很好的处理Node中的路由处理问题,同时利用这点可以非常方便的实现MVC模式;

    //获得产品根据Id
    router.get('/:id/:category',function(request,res,next){
        res.send(request.params.id+","+request.params.category);
    });

    运行结果:

    1.9.2、URL中的QueryString

    Checks query string params (req.query), ex: ?id=12

    127.0.0.1:3000/index?id=12,这种情况下,这种方式是获取客户端get方式传递过来的值,通过使用req.query.id就可以获得,类似于PHP的get方法;

    router.get('/:id',function(request,res,next){
        res.send("name:"+request.query.name);
    });

    运行结果:

    1.9.3、HTTP正文中的参数

     在post请求中获得表单中的数据。

    Checks urlencoded body params (req.body), ex: id=

    127.0.0.1:300/index,然后post了一个id=2的值,这种方式是获取客户端post过来的数据,可以通过req.body.id获取,类似于PHP的post方法;

    页面:

    <!DOCTYPE html>
    <html>
    
        <head>
            <title>
                <%= title %>
            </title>
            <link rel='stylesheet' href='/stylesheets/style.css' />
        </head>
    
        <body>
            <h1><%= title %> - 产品列表</h1>
            <table border="1" width="80%">
                <tr>
                    <th>序号</th>
                    <th>名称</th>
                    <th>价格</th>
                </tr>
                <%pdts.forEach(function(pdt,index){%>
                <tr>
                    <td>
                        <%=index+1%>
                    </td>
                    <td>
                        <%=pdt.name%>
                    </td>
                    <td>
                        <%=pdt.price%>
                    </td>
                </tr>
                <%});%>
            </table>
    
            <ul>
                <% for(var i=0; i<pdts.length; i++) {%>
                <li>
                    <%=pdts[i].name%>
                </li>
                <% } %>
            </ul>
    <p>
                <%if(typeof msg!="undefined"){%>
                    <%=msg%>
                <%}%>
    </p>
            <form action="pdt/add" method="post">
                <p>
                    名称:<input name="name" />
                </p>
                <p>
                    价格:<input name="price" />
                </p>
                <button>添加</button>
            </form>
            
            
            
            
        </body>
    
    </html>
    View Code

    代码:

    router.post('/add',function(request,res,next){
        var entity={name:request.body.name,price:request.body.price};
        products.push(entity);
          //将product视图与指定的对象渲染后输出到客户端
          res.render('product', { title: '天狗商城', pdts:products,msg:"添加成功"});
    });

    结果:

    1.10、JSON

    如果需要Node.js向外提供返回JSON的接口,Express也是非常方便的,可以使用原来在浏览器中使用到的JSON对象,这是一个浏览器内置对象在服务可以直接使用:

    将对象序列化成字符:

                //对象
                var rose={"name":"Rose","weight":"65"};
                //序列化成字符串
                var str=JSON.stringify(rose);
                alert(str);

    结果:

    反序列化,将字符转换成对象:

                //将字符串转换成JavaScript对象
                var markStr='{"name":"mark","weight":"188"}';
                var mark=JSON.parse(markStr);
                alert(mark.name+","+mark.weight);

    结果:

    Express已经封装了一个json方法,直接调用该方法就可以序列化对象:

    /* 产品 */
    router.get('/rest', function(req, res, next) {
      res.json(products);
    });

    运行结果:

     

    二、RESTful(表述性状态转移)

    REST是英文Representational State Transfer的缩写,中文称之为“表述性状态转移”
    基于HTTP协议
    是另一种服务架构
    传递是JSON、POX(Plain Old XML)而不是SOAP格式的数据
    充分利用HTTP谓词(Verb)
    侧重数据的传输,业务逻辑交给客户端自行处理

    REST是一种分布式服务架构的风格约束,像Java、.Net(WCF、WebAPI)都有对该约束的实现,使URL变得更加有意义,更加简洁明了,如:

    http://www.zhangguo.com/products/1 get请求 表示获得所有产品的第1个

    http://www.zhangguo.com/products/product post请求 表示添加一个产品

    http://www.zhangguo.com/products/1/price get请求 表示获得第1个产品的价格

    http://www.zhangguo.com/products/1 delete请求 删除编号为1的产品

    REST设计需要遵循的原则
    网络上的所有事物都被抽象为资源(resource);
    每个资源对应一个唯一的资源标识符(resource identifier);
    通过通用的连接器接口(generic connector interface)对资源进行操作;
    对资源的各种操作不会改变资源标识符;
    所有的操作都是无状态的(stateless)

    谓词
    GET
    表示查询操作,相当于Retrieve、Select操作
    POST
    表示插入操作,相当于Create,Insert操作
    PUT
    表示修改操作,相当于Update操作
    DELETE
    表示删除操作,相当于Delete操作

    其它还有:

    NodeJS+Express可以很容易的实现REST

    application/x-www-form-urlencoded

    multipart/form-data

    application/json

    res.setHeader('Content-Type', 'application/json;charset=utf-8');  

    示例代码cars.js:

    var express = require('express');
    var router = express.Router();
    var _= require('lodash');
    
    var cars=[];
    cars.push({id:201701,name:"BMW",price:190,speed:"210km/h",color:"白色"});
    cars.push({id:201702,name:"BYD",price:25,speed:"160km/h",color:"红色"});
    cars.push({id:201703,name:"Benz",price:300,speed:"215km/h",color:"蓝色"});
    cars.push({id:201704,name:"Honda",price:190,speed:"170km/h",color:"黑色"});
    cars.push({id:201705,name:"QQ",price:130,speed:"210km/h",color:"白色"});
    
    /* Get */
    /*获得所有汽车*/
    /*url /cars/*/
    router.get('/', function(req, res, next) {
        res.json(cars);
    });
    
    /*Get*/
    /*获得汽车通过id*/
    /*url:/cars/:id  */
    router.get('/:id', function(req, res, next) {
         //从路径中映射参数,转换成数字
          var id=parseInt(req.params.id);
          var car=_.find(cars,{id:id});
          res.json(car);
    });
    
    /*Post*/
    /*添加汽车*/
    /*url:/cars/car  */
    router.post('/car', function(req, res, next) {
          var car=req.body;  //从请求正文中获得json对象
          car.id=_.last(cars).id+1;  //将编号修改为最后一辆车的编号+1
          cars.push(car);  //将汽车对象添加到集合中
          res.json(car);  //将添加成功的车以json的形式返回
    });
    
    /*Put*/
    /*修改汽车*/
    /*url:/cars/car  */
    router.put('/car', function(req, res, next) {
          var car=req.body;  //从请求正文中获得json对象
          console.log(req.body);
          var index=_.findIndex(cars,{id:parseInt(car.id)});  //根据id获得车在集合中的下标
          
          cars[index]=car;  //替换原对象
          //res.json(car);  //将修改后的车以json的形式返回
          res.send({status:"success", message:"更新成功!"});  
    });
    
    /*Delete*/
    /*删除汽车*/
    /*url:/cars/:id  */
    router.delete('/id/:id', function(req, res, next) {
          //获得url中的编号参数
          var id=parseInt(req.params.id);
          var index=_.findIndex(cars,{id:id});  //根据id获得车在集合中的下标
          cars.splice(index,1);   //在cars数组中删除下标从index开始的1条数据
          res.send({status:"success", message:"删除成功!"});  
    });
    
    module.exports = router;

    示例代码app.js:

    var express = require('express');
    var path = require('path');
    var favicon = require('serve-favicon');
    var logger = require('morgan');
    var cookieParser = require('cookie-parser');
    var bodyParser = require('body-parser');
    
    var index = require('./routes/index');
    var users = require('./routes/users');
    var pdts = require('./routes/product');
    var task = require('./routes/task');
    var cars = require('./routes/cars');
    
    var app = express();
    
    //指定视图引擎为ejs
    app.set('views', path.join(__dirname, 'views'));
    app.set('view engine', 'ejs');
    
    // uncomment after placing your favicon in /public
    //app.use(favicon(path.join(__dirname, 'public', 'favicon.ico')));
    app.use(logger('dev'));
    app.use(bodyParser.json());
    app.use(bodyParser.urlencoded({ extended: false }));
    app.use(cookieParser());
    app.use(express.static(path.join(__dirname, 'public')));
    
    app.use('/', index);
    app.use('/users', users);
    app.use('/pdt', pdts);
    app.use("/task",task);
    app.use("/cars",cars);
    
    // catch 404 and forward to error handler
    app.use(function(req, res, next) {
      var err = new Error('Not Found');
      err.status = 404;
      next(err);
    });
    
    // error handler
    app.use(function(err, req, res, next) {
      // set locals, only providing error in development
      res.locals.message = err.message;
      res.locals.error = req.app.get('env') === 'development' ? err : {};
    
      // render the error page
      res.status(err.status || 500);
      res.render('error');
    });
    
    module.exports = app;

    /* Get */
    /*获得所有汽车*/
    /*url /cars/*/

    /*Get*/
    /*获得汽车通过id*/
    /*url:/cars/:id */

    /*Post*/
    /*添加汽车*/
    /*url:/cars/car */

     

    参数中的json格式一定要使用标准格式,注意引号,注意Content-Type,默认的Content-Type类型是:application/x-www-form-urlencoded

    /*Put*/
    /*修改汽车*/
    /*url:/cars/car */

    /*Delete*/
    /*删除汽车*/
    /*url:/cars/:id */

    node.js跨域

    修改app.js文件拦截所有的请求,修改头部

    app.use(logger('dev'));
    app.use(bodyParser.json());
    app.use(bodyParser.urlencoded({ extended: false }));
    app.use(cookieParser());
    app.use(express.static(path.join(__dirname, 'public')));
    app.all('*', function(req, res, next) {
        res.header("Access-Control-Allow-Origin", "*");
        res.header("Access-Control-Allow-Headers", "content-type");
        res.header("Access-Control-Allow-Methods", "PUT,POST,GET,DELETE,OPTIONS");
        res.header("X-Powered-By", ' 3.2.1');
        res.header("Content-Type", "application/json;charset=utf-8");
        if(req.method == "OPTIONS") {
            res.send("200");
        } else {
            next();
        }
    });

    结果:

    三、学生管理系统(ejs版)

    运行效果:

     routers/student.js

    var express = require('express');
    const { parseInt } = require('lodash');
    const _=require("lodash");
    var router = express.Router();
    
    //模拟数据库数据
    let stus=[
      {id:202201,name:"tom",age:18},
      {id:202202,name:"rose",age:16},
      {id:202203,name:"jack",age:20},
      {id:202204,name:"lili",age:15},
      {id:202205,name:"lucy",age:15}
    ];
    
    /* GET home page. */
    router.get('/', function(req, res, next) {
      res.render('student', {stus,msg:"",stu:{id:"",name:"",age:""}});
    });
    
    router.get('/delete/:id', function(req, res, next) {
      //在stus中查找编号为id的元素索引
      let index=_.findIndex(stus,{id:req.params.id});
      //删除索引为index的元素
      stus.splice(index,1);
      res.render('student', {stus,msg:"删除成功!",stu:{id:"",name:"",age:""}});
    });
    
    
    router.get('/edit/:id', function(req, res, next) {
      //在stus中查找编号为id的元素
      let stu=_.find(stus,{id:parseInt(req.params.id)});
      res.render('student', {stus,stu,msg:""});
    });
    
    //添加
    router.post('/add', function(req, res, next) {
      let stu=req.body;
    
      //根据编号排序
      let stus2=_.orderBy(stus,["id"]);
      stu.id=_.last(stus2).id+1;  //取最后一个元素的编号加1
    
      stus.push(stu);
    
      res.render('student', {stus,msg:"添加成功!",stu:{id:"",name:"",age:""}});
    });
    
    
    
    //更新
    router.post('/update', function(req, res, next) {
      //获取提交的数据对象
      let stuSubmit=req.body;
      //在集合中查找要更新的元素
      let stu=_.find(stus,{id:parseInt(stuSubmit.id)}); 
      //更新名字与年龄
      stu.name=stuSubmit.name;
      stu.age=stuSubmit.age;
      //重新渲染页面,指定学生列表数据,学生数据,与提示消息
      res.render('student', {stus,msg:"更新成功!",stu:{id:"",name:"",age:""}});
    });
    
    module.exports = router;

    views/student.ejs

    <!DOCTYPE html>
    <html lang="en">
      <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>
        <h2>学生管理系统</h2>
        <table border="1" width="100%">
            <tr>
                <th>编号</th>
                <th>姓名</th>
                <th>年龄</th>
                <th>操作</th>
            </tr>
            <% for(let i=0;i<stus.length;i++){ %>
            <tr>
                <td><%=stus[i].id%></td>
                <td><%=stus[i].name%></td>
                <td><%=stus[i].age%></td>
                <td>
                    <a href="/delete/<%=stus[i].id%>" class="del">删除</a> | 
                    <a href="/edit/<%=stus[i].id%>">编辑</a>
                </td>
            </tr>
            <% } %>
        </table>
    
        
        <fieldset>
            <legend>学生</legend>
            <form method="post">
                <p>
                    <label>姓名:</label>
                    <input type="text" name="name" value="<%=stu.name%>" />
                </p>
                <p>
                    <label>年龄:</label>
                    <input type="text" name="age" value="<%=stu.age%>" />
                </p>
                <input type="hidden" name="id" id="studentId" value="<%=stu.id%>" />
                <button formaction="/add">添加</button>
                <button  formaction="/update">更新</button>
            </form>
        </fieldset>
        
    
        <h4>
         <%=msg%>
        </h4>
        <script>
           let dels=document.querySelectorAll(".del");
           for(let i=0;i<dels.length;i++){
                dels[i].onclick=function(){
                    return confirm("您确定要删除吗?");
                }
           }
        </script>
      </body>
    </html>

    app.js

    var createError = require('http-errors');
    var express = require('express');
    var path = require('path');
    var cookieParser = require('cookie-parser');
    var logger = require('morgan');
    
    var indexRouter = require('./routes/index');
    var usersRouter = require('./routes/users');
    var studentRouter = require('./routes/student');
    
    var app = express();
    
    // view engine setup
    app.set('views', path.join(__dirname, 'views'));
    app.set('view engine', 'ejs');
    
    app.use(logger('dev'));
    app.use(express.json());
    app.use(express.urlencoded({ extended: false }));
    app.use(cookieParser());
    app.use(express.static(path.join(__dirname, 'public')));
    
    app.use('/index', indexRouter);
    app.use('/users', usersRouter);
    app.use('/', studentRouter);
    
    // catch 404 and forward to error handler
    app.use(function(req, res, next) {
      next(createError(404));
    });
    
    // error handler
    app.use(function(err, req, res, next) {
      // set locals, only providing error in development
      res.locals.message = err.message;
      res.locals.error = req.app.get('env') === 'development' ? err : {};
    
      // render the error page
      res.status(err.status || 500);
      res.render('error');
    });
    
    module.exports = app;

    四、学生管理系统(REST版)

     运行效果:

     routers/carsApi.js

    const express=require("express");
    const _=require("lodash");
    
    let router=express.Router();
    
    var cars=[];
    cars.push({id:201701,name:"BMW",price:190,speed:"210km/h",color:"白色"});
    cars.push({id:201702,name:"BYD",price:25,speed:"160km/h",color:"红色"});
    cars.push({id:201703,name:"Benz",price:300,speed:"215km/h",color:"蓝色"});
    cars.push({id:201704,name:"Honda",price:190,speed:"170km/h",color:"黑色"});
    cars.push({id:201705,name:"QQ",price:130,speed:"210km/h",color:"白色"});
    
    //Get请求,获取所有汽车
    router.get("/",(req,res,next)=>{
        res.json({status:"ok","data":cars});
    });
    //Get请求,获取汽车通过编号
    router.get("/:id",(req,res,next)=>{
        //查找编号为id的汽车
        let car=_.find(cars,{id:parseInt(req.params.id)});
        res.json({status:"ok","data":car});
    });
    
    //Post,添加汽车
    router.post("/",(req,res,next)=>{
        let car=req.body;
        //获取编号最大的汽车
        let obj=_.last(_.sortBy(cars,["id"]));
        //最大的编号加1
        car.id=obj.id+1;
        cars.push(car);
        res.json({status:"ok","data":car});
    });
    
    //Put请求,修改汽车
    router.put("/",(req,res,next)=>{
        let srcCar=req.body;
        //查找编号为id的汽车
        let car=_.find(cars,{id:parseInt(srcCar.id)});
        //修改
        car.name=srcCar.name;
        car.speed=srcCar.speed;
        car.price=srcCar.price;
        car.color=srcCar.color;
        res.json({status:"ok","data":cars});
    });
    
    //Delete请求,删除汽车
    router.delete("/:id",(req,res,next)=>{
        let srcCar=req.body;
        //查找编号为id的汽车索引号
        let index=_.findIndex(cars,{id:parseInt(req.params.id)});
        //从cars数组中删除指定编号的元素
        cars.splice(index,1);
        res.json({status:"ok","data":req.params.id});
    });
    
    
    module.exports=router;

    routers/cars.js

    var express = require('express');
    var router = express.Router();
    
    /* GET home page. */
    router.get('/', function(req, res, next) {
      res.render('cars', { });
    });
    
    module.exports = router;

    views/cars.ejs

    <!DOCTYPE html>
    <html lang="en">
      <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>
        <div id="app">
          <h2>汽车管理系统</h2>
          <table border="1" width="100%">
            <tr>
              <th>编号</th>
              <th>车名</th>
              <th>价格</th>
              <th>车速</th>
              <th>颜色</th>
              <th>操作</th>
            </tr>
            <tr v-for="(obj,index) in cars">
              <td>{{ obj.id }}</td>
              <td>{{ obj.name }}</td>
              <td>{{ obj.price }}</td>
              <td>{{ obj.speed }}</td>
              <td>{{ obj.color }}</td>
              <td>
                <a href="" @click.stop.prevent="del(obj,index)">删除</a> |
                <a href="" @click.stop.prevent="edit(obj,index)">编辑</a>
              </td>
            </tr>
          </table>
          <fieldset>
            <legend>汽车</legend>
            <p>
              <label> 名称: </label>
              <input type="text" v-model="car.name" />
            </p>
            <p>
              <label> 价格: </label>
              <input type="text" v-model="car.price" />
            </p>
            <p>
              <label> 速度: </label>
              <input type="text" v-model="car.speed" />
            </p>
            <p>
              <label> 颜色: </label>
              <input type="text" v-model="car.color" />
            </p>
            <p>
              <button @click="save" type="button">保存</button>
            </p>
          </fieldset>
        </div>
    
        <script src="javascripts/vue/vue.js"></script>
        <script src="javascripts/axios/axios.min.js"></script>
        <script>
          let app = new Vue({
            el: "#app",
            data: {
              cars: [],
              car: { id: 0, name: "", price: "", speed: "", color: "" },
            },
            created() {
              axios
                .get("http://127.0.0.1:3000/api/cars")
                .then((data) => {
                  this.cars = data.data.data;
                })
                .catch((err) => {
                  throw err;
                });
            },
            methods: {
              del(obj, index) {
                if (confirm("您确定要删除吗?")) {
                  let url = "http://127.0.0.1:3000/api/cars/" + obj.id;
                  axios
                    .delete(url)
                    .then((data) => {
                      if (data.data.status === "ok") {
                        alert("删除成功");
                        this.cars.splice(index, 1);
                      } else {
                        alert("删除失败");
                      }
                    })
                    .catch((err) => {
                      throw err;
                    });
                }
                return false;
              },
              edit(obj, index) {
                let url = "http://127.0.0.1:3000/api/cars/" + obj.id;
                axios
                  .get(url)
                  .then((data) => {
                    this.car = data.data.data;
                  })
                  .catch((err) => {
                    throw err;
                  });
                return false;
              },
              save() {
                //有id为修改
                if (this.car.id) {
                  axios
                    .put("http://127.0.0.1:3000/api/cars", this.car)
                    .then((data) => {
                      if (data.data.status === "ok") {
                        alert("修改成功!");
                        this.car = {
                          id: 0,
                          name: "",
                          price: "",
                          speed: "",
                          color: "",
                        };
                        this.cars = data.data.data;
                      }
                    });
                } else {
                  //没id为添加
                  axios
                    .post("http://127.0.0.1:3000/api/cars", this.car)
                    .then((data) => {
                      if (data.data.status === "ok") {
                        alert("新增成功!");
                        this.cars.push(data.data.data);
                        this.car.id = 0;
                      }
                    })
                    .catch((err) => {
                      throw err;
                    });
                }
              },
            },
          });
        </script>
      </body>
    </html>

    app.js

    var createError = require('http-errors');
    var express = require('express');
    var path = require('path');
    var cookieParser = require('cookie-parser');
    var logger = require('morgan');
    
    var indexRouter = require('./routes/index');
    var usersRouter = require('./routes/users');
    var carsApiRouter = require('./routes/carsApi');
    var carsRouter = require('./routes/cars');
    
    var app = express();
    
    // view engine setup
    app.set('views', path.join(__dirname, 'views'));
    app.set('view engine', 'ejs');
    
    app.use(logger('dev'));
    app.use(express.json());
    app.use(express.urlencoded({ extended: false }));
    app.use(cookieParser());
    app.use(express.static(path.join(__dirname, 'public')));
    
    app.use('/index', indexRouter);
    app.use('/users', usersRouter);
    app.use('/api/cars', carsApiRouter);  //服务接口
    app.use('/', carsRouter);  //汽车管理首页
    // catch 404 and forward to error handler
    app.use(function(req, res, next) {
      next(createError(404));
    });
    
    // error handler
    app.use(function(err, req, res, next) {
      // set locals, only providing error in development
      res.locals.message = err.message;
      res.locals.error = req.app.get('env') === 'development' ? err : {};
    
      // render the error page
      res.status(err.status || 500);
      res.render('error');
    });
    
    module.exports = app;

    五、作业

    6.1、请安装好node.js环境,测试版本,在控制台写一个方法用于计算1-100间的所有能被3整除的数,并调用。

    6.2、请将8.1中的方法单独存放到一个math.js文件中,同时在math.html页面与node的控制台中调用

    6.3、在开发工具IDE中集成node.js开发环境,创建一个node.js项目,向控制台输出“Hello World!”

    6.4、使用记事本在c:\根目录下写一个server.js文件实现一个最简单的web服务器,请求时响应当前系统时间。

    6.5、将6.4的功能在IDE中完成,请注意端口号不能被占用,如果提示占用错误可以修改端口号为1025-65535之间

    6.6、完成一个图书管理的功能,图书包含(编号,名称,作者,图片,价格),实现:

    a)、非AJAX的CRUD,使用Node.js+Express+ejs的动态技术。

    b)、使用Node.js+Express+Axios+Rest+Vue技术实现,要求增加搜索,排序与分页功能。

    6.7、在lodash的官方文档中找出5个未学习过的方法,对如下集合进行操作:

    let stus=[
        {id:202201,name:"tom",age:18},
        {id:202202,name:"rose",age:16},
        {id:202203,name:"jack",age:20},
        {id:202204,name:"lili",age:15},
        {id:202205,name:"lucy",age:15}
    ];

    六、视频

    https://www.bilibili.com/video/av17977069/

    七、示例代码

    https://gitee.com/zhangguo5/nodejs_demo11.git

    https://gitee.com/zhangguo5/nodejs_demo13.git

    https://gitee.com/zhangguo5/nodejs_demo15.git

  • 相关阅读:
    maven的安装步骤
    Sublime Text 3设置指南
    Eclipse 不同版本名称
    redis命令简单介绍
    redis简介与window安装:
    redis数据类型:String
    Mybatis-plus的使用步骤
    SpringgBoot父子工程的创建
    我的大学-易班
    IDEA:配置Tomcat并运行应用
  • 原文地址:https://www.cnblogs.com/best/p/16422402.html
Copyright © 2020-2023  润新知