Node.js(三)– 开发web后台服务

一、Express —— Web开发框架

1、Express是什么?

Express 是一个简洁而灵活、目前最流行的基于Node.js的Web开发框架, 提供了一系列强大特性帮助你创建各种 Web 应用,和丰富的 HTTP 工具;

2、Express有什么用?

可以快速地搭建一个完整功能的网站;使用Node.js作为AngularJS开发Web服务器的最佳方式是使用Express模块;

3、Express官网: Express - Node.js web application framework

4、Express框架核心特性

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

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

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

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

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

5、安装Express

安装 Express 并将其保存到依赖列表中:npm install express --save(也可以不加后面的 --save)如图所示:

img

6、创建第一个Express框架实例

(1)首先创建 bin 目录;再创建一个app.js文件作为一个入口,用于中间件挂载;再创建一个www.js文件作为启动服务,如下所示:

app.js 文件代码如下:

//引入express模块
var express = require('express');
?
//创建一个app对象,作为一个express模块导出的入口函数,类似一个web 应用(网站)
var app = express();
?
//接受指定路径的请求,指定回调函数
app.get('/', function (req, res) {
    // 返回客户端的字符
    res.send('Hello Express!');
});
?
// 导出对象
module.exports=app;

www.js 文件代码如下:

// 导入自定义模块
const app=require("../app.js")
?
// 绑定并侦听指定主机和端口上的连接。
app.listen(8080,"127.0.0.1",()=>{
    console.log("web应用运行中...");
});

(2)在package.json文件中,添加一个 ‘ start ’ 的参数,来访问该文件

img

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

7.1、为什么需要中间件

一个请求发送到服务器后,它的生命周期是 先收到request(请求),然后服务端处理,处理完了以后发送response(响应)回去,而这个服务端处理的过程就有文章可做了,想象一下当业务逻辑复杂的时候,为了明确和便于维护,需要把处理的事情分一下,分配成几个部分来做,而每个部分就是一个中间件。简而言之就是:运行程序时,有时候会出现卡住的情况,此时我们使用中间件就可以解决运行卡,一直在转的情况了!!!

7.2、use方法

use 是express注册中间件的方法,它返回一个函数。app.use([path,], function [, function…]) 挂载中间件方法到路径上。如果路径未指定,那么默认为”/”

// 导入内置模块
var express = require('express'); 
?
// 是一个express模块导出的入口函数,类似一个web 应用(网站)
var app = express(); 
?
// 中间件
app.use(function (req, res, next) {
    console.log('当前时间:', Date.now())
    next()
})
?
// 接受指定路径的请求,指定回调函数
app.get('/', function(req, res){
  res.send('测试use中间件栗子!'); // 返回客户端的字符
});
?
// 绑定并侦听指定主机和端口上的连接。
module.exports=app; // 导出对象

中间件是一个函数,在响应发送之前对请求进行一些操作,这个函数有些不太一样,它还有一个next参数,而这个next也是一个函数,它表示函数数组中的下一个函数,如果当前中间件函数没有结束请求/响应循环,那么它必须调用 next(),以将控制权传递给下一个中间件函数。否则,请求将保持挂起状态。意思就是说:在这个函数体里面,写完逻辑之后,如果还想执行下面的方法,就必须在use函数体里面写上 next() 方法来继续执行下一行代码!中间件方法是顺序处理的。

7.3、路径匹配

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

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

栗子如下:

img

7.4、挂载静态资源

在Express程序中使用express.static中间件。 为程序托管位于程序目录下的public目录下的静态资源,栗子如下:

// 导入内置模块
var express = require('express'); 
var path = require('path'); // 表示路径的模块
?
var app = express(); 
?
// 中间件挂载静态资源
// __dirname:当前目录,连接public目录
// 完整的路径为:file:///E:/qd/11.%20node.js/%E6%A1%88%E4%BE%8B/ch03/public/index.html
app.use(express.static(path.join(__dirname,"public")))
?
app.get('/', function(req, res){
  res.send('测试中间件挂载静态资源的栗子!'); // 返回客户端的字符
});
?
// 导出对象
module.exports=app;
7.5、路由router(有点类似控制器或Servlet)

route():避免重复路线名称,栗子如下:

(1)首先创建一个router目录,在目录下面创建一个index.js文件,代码如下:

var express = require('express')
?
// 创建路由对象
var router = express.Router()
?
// 添加访问路径
router.get('/', function (req, res, next) {
    console.log("hello router!");
    next()
})
?
// 导出路由
module.exports=router;

(2)创建一个router.js 文件作为web服务器,挂载路由运行到客户端(浏览器)上,代码如下:

var express = require('express')
?
var app = express(); 
?
// 导入路由
var indexRouter = require("./router/index");
var carRouter = require("./router/car");
?
// 挂载路由
app.use("/",indexRouter);
app.use("/car",carRouter);
?
// 添加访问路径
app.get('/', function(req, res){
    res.send('测试路由的栗子!'); // 返回客户端的字符
});
  
// 导出对象
module.exports=app; 

请注意:如果有多个路由,路径会发生变化!!栗子如下:

router 目录下面创建一个 car.js 文件,代码如下:

const express = require('express')
?
// 创建路由对象
const router = express.Router()
?
// 添加访问路径
router.get('/', function (req, res, next) {
    res.send("hello car!");
    next()
})
?
// 导出路由
module.exports=router;

router.js 文件 代码如下:

var express = require('express')
?
var app = express(); 
?
// 导入路由
var indexRouter = require("./router/index");
var carRouter = require("./router/car");
?
// 挂载路由
app.use("/",indexRouter);
app.use("/car",carRouter);
?
// 添加访问路径
app.get('/', function(req, res){
    res.send('测试路由的栗子!'); // 返回客户端的字符
});
  
// 导出对象
module.exports=app; 
7.6、morgan日志

(1)首先导入依赖:npm i morgan

(2)导入第三方模块

(3)指定格式,挂载日志组件

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

具体操作代码如下:

//引入express模块
var express = require('express');
?
//创建一个app对象,作为一个express模块导出的入口函数,类似一个web 应用(网站)
var app = express();
var logger = require("morgan");
?
?
// 挂载日志组件
app.use(logger("dev"));
?
//接受指定路径的请求,指定回调函数
app.get('/', function (req, res) {
    // 返回客户端的字符
    res.send('测试morgan日志的栗子!!');
});
?
?
// 导出对象
module.exports=app;

8、通过Express 应用程序生成器,快速生成一个完整的项目

通过应用生成器工具 express-generator 可以快速创建一个应用的骨架。(包含bin、public、routes、views目录;json、js文件等等)

通过 npx 命令来运行 Express 应用程序生成器。

npx express-generator -e

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

$ npm install -g express-generator
$ express -e

所有可用的命令行参数:

$ 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         强制在非空目录下创建

具体操作如下:

(1)创建一个空目录

(2)在命令提示符切换盘符,进入该文件夹路径

img

(3)使用 npx express-generator 命令生成项目

img

img

(3)此时我们发现目录里面是没有 modules 依赖的,所以我们要使用说明提示我们安装依赖的命令 :npm install

img

(4)最后用上面提示我们运行服务的命令:SET DEBUG=ch04:* & npm start

img

(5)运行结果:

img

9、ejs基础

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

官网:EJS -- 嵌入式 JavaScript 模板引擎 | EJS 中文文档

添加一个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.js 文件,注册定义好的模块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);

运行结果:

img

使用 ejs 和 node.js 实现 crud 的栗子:

路由:

const express = require('express');
const _ = require("lodash")
?
const router = express.Router();
?
// 数据
let book = [
    {"id":1,"name":"Java高级编程","author":"大强","price":56.9},
    {"id":2,"name":"ES6编程","author":"刘静","price":20.0},
    {"id":3,"name":"MySQL数据库","author":"张三","price":85.9},
    {"id":4,"name":"Vue全家桶","author":"李四","price":72.9},
    {"id":5,"name":"SpingBoot","author":"旺旺","price":16.0}
]
?
// 中间件(查询)
router.get("/",(req,res,next)=>{
    // render():该方法将返回可能的错误和呈现的字符串,但不执行自动响应。 当发生错误时,该方法会在内部调用 next (err)。
    res.render("index",{book,msg:"",bookData:{id:"",name:"",author:"",price:""}});
});
?
// 中间件(删除)
router.get("/del/:id",(req,res,next)=>{
    // 查找客户端发送过来的id
    // _.findIndex方法:该方法返回第一个通过 predicate 判断为真值的元素的索引值(index)
    let index = _.findIndex(book,{id:parseInt(req.params.id)})
    // console.log(index); // 输出下标
    book.splice(index,1);
    res.render("index",{book,msg:"删除成功!",bookData:{id:"",name:"",author:"",price:""}});
});
?
// 中间件(添加)
router.post("/add",(req,res,next)=>{
    // req.body:获取客户端post请求过来的数据
    let inputText = req.body;
    
    // 自动添加id
    // 根据编号排序
    let ids = _.orderBy(book,["id"]);
    // 获取到book数组的最后一个元素的编号 + 1
    inputText.id = _.last(ids).id + 1;
?
    // 把获取到的json对象添加到book数组中
    book.push(inputText);
?
    // 返回给客户端
    res.render("index",{book,msg:"添加成功!",bookData:{id:"",name:"",author:"",price:""}});
});
?
// 中间件(点击编辑把该行的数据渲染到对应的文本框中)
router.get("/edit/:id",(req,res,next)=>{
    // _.find方法:遍历 collection(集合)元素,返回 predicate(断言函数)第一个返回真值的第一个元素。
    // id需要强制转换为int类型
    let bookData = _.find(book,{id:parseInt(req.params.id)});
    
    // 把查询出来的数据(bookData),返回给客户端
    res.render("index",{book,bookData,msg:""});
});
?
// 中间件(修改)
router.post("/update",(req,res,next)=>{
    // 获取提交过来的数据对象
    let inputText = req.body;
?
    // 在集合中查找要更新的元素,根据id
    let updateData = _.find(book,{id:parseInt(inputText.id)});
?
    // 更新数据
    updateData.name = inputText.name;
    updateData.author = inputText.author;
    updateData.price = inputText.price;
?
    console.log(book);
?
    // 重新渲染页面,更新列表的数据
    res.render("index",{book,msg:"修改成功!",bookData:{id:"",name:"",author:"",price:""}});
?
?
});
?
module.exports=router;

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>
    <style>
        h3 {
            text-align: center;
        }
?
        table {
            width: 80%;
            margin: auto;
            text-align: center;
            border-collapse: collapse;
        }
?
        table,
        tr,
        th,
        td {
            border: 1px solid #444;
        }
?
        th {
            background-color: darkblue;
            color: #fff;
        }
?
        form,h4 {
            margin-left: 135px;
        }
?
        a {
            text-decoration: none;
            color: blue;
        }
?
        h4{
            color: red;
        }
    </style>
</head>
?
<body>
    <h3>图书管理系统</h3>
    <table>
        <tr>
            <th>编号</th>
            <th>名称</th>
            <th>作者</th>
            <th>价格</th>
            <th>操作</th>
        </tr>
        <% for(let i=0;i<book.length;i++){ %>
            <tr>
                <td><%=book[i].id%></td>
                <td><%=book[i].name%></td>
                <td><%=book[i].author%></td>
                <td><%=book[i].price%></td>
                <td>
                    <!-- 占位参数的写法 -->
                    <a href="/book/del/<%=book[i].id%>" class="del">删除</a> | 
                    <a href="/book/edit/<%=book[i].id%>">修改</a>
                </td>
            </tr>
        <% } %>
?
    </table>
?
    <form  method="post">
        <!-- 接受后台(bookData)传过来的数据,把值渲染到文本框里面 -->
        <p>
            <label>编号:</label> <input type="text" name="id" value="<%=bookData.id%>" disabled="" />
        </p>
        <p>
            <label>姓名:</label> <input type="text" name="name" value="<%=bookData.name%>" />
        </p>
        <p>
            <label>作者:</label> <input type="text" name="author" value="<%=bookData.author%>" />
        </p>
        <p>
            <label>价格:</label> <input type="text" name="price" value="<%=bookData.price%>" />
        </p>
        <p>
            <!-- 隐藏域:用于隐藏id -->
            <input type="hidden" name="id" id="bookId" value="<%=bookData.id%>" />
            <!-- formaction 属性规定当表单提交时处理输入控件的文件的 URL。 -->
            <!-- 覆盖 <form> 元素的 action 属性。 -->
            <button formaction="/book/add">添加</button>
            <button formaction="/book/update">修改</button>
        </p>
    </form>
?
    <h4><%=msg%></h4>
?
</body>
<script>
    let dels=document.querySelectorAll(".del");
    for(let i=0;i<dels.length;i++){
         dels[i].οnclick=function(){
            return confirm("您确定要删除吗?");
         }
    }
 </script>
?
</html>

app.js:

//引入express模块
var express = require('express');
var path = require('path');
?
//创建一个app对象,作为一个express模块导出的入口函数,类似一个web 应用(网站)
var app = express();
?
//引入body依赖
const bodyParser = require('body-parser')
?
// 请求体解析body依赖
app.use(bodyParser.json());
app.use(bodyParser.urlencoded({ extended: true }));
?
//指定视图引擎为ejs
app.set('views', path.join(__dirname, 'views'));
app.set('view engine', 'ejs'); 
?
//导入
var bookRouter = require('./router/book');
?
// 路由
app.use("/book",bookRouter);
?
?
//接受指定路径的请求,指定回调函数
app.get('/', function (req, res) {
    // 返回客户端的字符
    res.send('Hello Express!');
});
?
// 导出对象
module.exports=app;

10、lodash 插件

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

官网:Lodash 简介 | Lodash中文文档 | Lodash中文网

(1)安装:安装时先用cd切换到当前项目下,命令如下:

npm i lodash

(2)依赖成功后会在 package.json 文件中自动添加引用

(3)后台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

11、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类型

12、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类型

13、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

14、 参数

14.1、URL中的参数占位

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);
});

运行结果:

img

14.2、URL中的QueryString

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);
});

运行结果:

img

14.3、HTTP正文中的参数( 在post请求中获得表单中的数据)

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

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:"添加成功"});
});

15、JSON

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

(1)将对象序列化成字符:

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

结果:

img

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

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

结果:

img

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

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

运行结果:

img

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

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的产品

(1)REST设计需要遵循的原则:

  • 网络上的所有事物都被抽象为资源(resource);

  • 每个资源对应一个唯一的资源标识符(resource identifier);

  • 通过通用的连接器接口(generic connector interface)对资源进行操作;

  • 对资源的各种操作不会改变资源标识符;

  • 所有的操作都是无状态的(stateless)

(2)谓词

GET:表示查询操作,相当于Retrieve、Select操作

POST:表示插入操作,相当于Create,Insert操作 PUT:表示修改操作,相当于Update操作** DELETE:**表示删除操作,相当于Delete操作

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

17、使用RESTful和node.js实现crud的栗子

路由 Api 接口:

// 此文件用于:提供服务接口,编写逻辑与方法
?
// 导入模块
var express = require('express');
var router = express.Router();
var _ = require("lodash");
?
// 用于文件上传
var path = require('path');
var multer = require('multer');
?
// 连接数据库
const {
    MongoClient
} = require("mongodb");
?
// // 数据
var book = [];
book.push({
    "id": 1,
    "name": "Java高级编程",
    "picture": "1.jpg",
    "author": "大强",
    "price": 56.9
});
book.push({
    "id": 2,
    "name": "ES6编程",
    "picture": "2.jpg",
    "author": "刘静",
    "price": 20.0
});
book.push({
    "id": 3,
    "name": "MySQL数据库",
    "picture": "3.jpg",
    "author": "张三",
    "price": 85.9
});
book.push({
    "id": 4,
    "name": "Vue全家桶",
    "picture": "4.jpg",
    "author": "李四",
    "price": 72.9
});
book.push({
    "id": 5,
    "name": "SpingBoot",
    "picture": "5.jpg",
    "author": "旺旺",
    "price": 16.0
});
?
?
// 查询所有数据
router.get("/", (req, res, next) => {
     // 返回客户端
     res.json({
        status: "查询成功!",
        data: book
    });
});
?
// 添加图书
router.post("/", (req, res, next) => {
    // 获取通过post请求传递过来的参数
    let inputText = req.body;
    // 获取编号最大的图书,实现编号自动增长
    let maxId = _.last(_.sortBy(book, ["id"]));
    inputText.id = maxId.id + 1;
?
    console.log(inputText);
?
    // 添加一条数据对象进数组
    book.push(inputText);
?
    // 传递客户端状态信息和最新的一条数据对象
    res.json({
        status: "添加成功啦!",
        data: inputText
    });
});
?
// 删除图书
router.delete("/:id", (req, res, next) => {
    let inputText = req.body;
?
    //查找编号为id的图书索引号
    let index = _.findIndex(book, {
        id: parseInt(req.params.id)
    });
?
    // 删除
    book.splice(index, 1);
?
    // 返回客户端
    res.json({
        status: "删除成功啦!",
        data: req.params.id
    });
?
});
?
// 渲染数据到对应的文本框
router.get("/:id", (req, res, next) => {
    let element = _.find(book, {
        id: parseInt(req.params.id)
    });
    res.json({
        status: "渲染成功啦!",
        data: element
    });
});
?
// 修改图书
router.put("/", (req, res, next) => {
    let inputText = req.body;
    let element = _.find(book, {
        id: parseInt(inputText.id)
    });
?
    // 修改
    element.name = inputText.name;
    element.picture = inputText.picture;
    element.author = inputText.author;
    element.price = inputText.price;
?
    // 返回客户端
    res.json({
        status: "修改成功啦!",
        data: book
    });
});
?
// 通过id,查询一条数据
router.get("/:id", (req, res, next) => {
    let element = _.filter(book, {
        id: parseInt(req.params.id)
    });
    res.json({
        status: "成功查询到一条数据",
        data: element
    });
});
?
// 排序
router.get("/book/sort", (req, res, next) => {
    let sort = _.orderBy(book, ['id'], ['desc']);
    res.json({
        status: "按id倒序成功!",
        data: sort
    });
});
?
router.get("/book/maxId", (req, res, next) => {
    // 获取编号最大的图书
    let maxId = _.last(_.sortBy(book, ["id"]));
    res.json({
        status: "获取编号最大的图书成功啦!",
        data: maxId
    })
})
?
// 图片上传
// 文件上传的内置方法
const storage = multer.diskStorage({
    // 文件存储路径
    destination(req, file, callback) {
        // 上传的图片放到本地的文件夹里面
        let imgUrl = "E:/qd/11. node.js/练习/test04_Rest/public/images/fileTestImage"
        // 回调函数
        callback(null, imgUrl)
    },
    filename(req, file, callback) {
        // 后缀名,到时候添加数据的时候,先把它设为空,然后进入图片上传页,然后做修改的操作
        let xx = _.random(0, 9999999999999) + path.extname(file.originalname);
        callback(null, xx);
?
        book[book.length - 1].picture = xx;
?
    }
});
?
// 也是内置函数
const addfild = multer({
    storage
});
?
// 在这里传内置函数过去
router.put("/file", addfild.single('file'), (req, res, next) => {
?
?
    // 返回客户端
    res.json({
        status: "修改封面图片成功啦!",
        data: book
    });
?
})
?
?
?
module.exports = router;

页面:

<!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>图书管理系统 —— REST框架版</title>
    <!-- 图标 -->
    <!-- ejs里的图片路径直接不用管public !!! -->
    <link rel="shortcut icon" href="/images/taozi.svg" type="image/x-icon">
    <!-- 样式 -->
    <link rel="stylesheet" href="/stylesheets/book.css">
    <link rel="stylesheet" href="/stylesheets/index.css">
?
</head>
?
<body>
    <div id="app">
        <h3>图书管理系统</h3>
        <div id="addbtn">
            <div id="sort">
                <button id="sortId" @click="sortId">按id倒序</button>
            </div>
            <div id="search">
                <input type="text" name="" id="searchText">
                <button id="searchbtn" @click="search">搜索</button>
            </div>
            <button id="appBook">新增图书&nbsp;+&nbsp;</button>
        </div>
        <table>
            <tr>
                <th>编号</th>
                <th>书名</th>
                <th>封面</th>
                <th>作者</th>
                <th>价格</th>
                <th>操作</th>
            </tr>
            <tr v-for="(b, i) in book" :key="b.id">
                <td>{{ b.id }}</td>
                <td>{{ b.name }}</td>
                <td>
                    <div id="picture" :style="{backgroundImage: 'url(/images/fileTestImage/' + b.picture + ')'}"></div>
                </td>
                <td>{{ b.author }}</td>
                <td>{{ b.price }}</td>
                <td>
                    <button id="update" @click="edit(b,i)">编辑</button>
                    <button id="delete" @click="del(b,i)">删除</button>
                </td>
            </tr>
        </table>
?
        <!-- 模态框 -->
        <div id="modal_box">
            <!--修改or添加  -->
            <div id="mdinfo">
                <table>
                    <tr>
                        <td colspan="2" id="td">新增图书信息</td>
                    </tr>
                    <tr>
                        <td>书名:</td>
                        <td>
                            <input type="text" id="name" class="text" v-model="bookObj.name" />
                        </td>
                    </tr>
                    <tr>
                        <td>作者:</td>
                        <td>
                            <input type="text" id="author" class="text" v-model="bookObj.author" />
                        </td>
                    </tr>
                    <tr>
                        <td>价格:</td>
                        <td>
                            <input type="text" id="price" class="text" v-model="bookObj.price" />
                        </td>
                    </tr>
                    <tr>
                        <td colspan="2">
                            <button id="btn1" @click="save" class="btn">提交</button>
                            <button id="btn2" class="btn">取消</button>
                        </td>
                    </tr>
                </table>
            </div>
            <!-- 图片上传 -->
            <div id="file">
                <h3>添加书本封面图片</h3>
                <el-upload action="#" list-type="picture-card" :auto-upload="false" :on-change="handleChange" style="margin-left: 50px;">
                    <i slot="default" class="el-icon-plus"></i>
                    <div slot="file" slot-scope="{file}">
                        <img class="el-upload-list__item-thumbnail" :src="file.url" alt="">
                        <span class="el-upload-list__item-actions">
                            <span class="el-upload-list__item-preview" @click="handlePictureCardPreview(file)">
                                <i class="el-icon-zoom-in"></i>
                            </span>
                            <span v-if="!disabled" class="el-upload-list__item-delete" @click="handleDownload(file)">
                                <i class="el-icon-download"></i>
                            </span>
                            <span v-if="!disabled" class="el-upload-list__item-delete" @click="handleRemove(file)">
                                <i class="el-icon-delete"></i>
                            </span>
                        </span>
                    </div>
                </el-upload>
                <el-dialog :visible.sync="dialogVisible">
                    <img width="100%" :src="dialogImageUrl" alt="">
                </el-dialog>
                <button id="filebtn" @click="hold">保存</button>
            </div>
?
        </div>
    </div>
</body>
<script src="javascripts/jquery-1.12.4.js"></script>
<script src="javascripts/axios.js"></script>
<script src="javascripts/vue.js"></script>
<script src="javascripts/index.js"></script>
<script>
    // 接口(api)的路径
    var path = "http://127.0.0.1:8082/bookApi/";
?
    var app = new Vue({
        el: "#app",
        data: {
            book: [],
            bookObj: { id: 0, name: "", picture: "", author: "", price: "" },
            // 文件上传
            dialogImageUrl: '',
            dialogVisible: false,
            disabled: false,
            // 获取编号最大的图书。即最新添加的编号
            maxId:""
        },
        created() {
            axios
                .get(path)
                .then((data) => {
                    this.book = data.data.data
                    // console.log(data.data.data);
                })
                .catch((err) => {
                    throw err;
                });
        },
        methods: {
            // 传该行对应的对象,和第几行的索引
            del(b, i) {
                if (confirm("您确定要删除这数据吗?")) {
                    axios
                        .delete(path + b.id)
                        .then((data) => {
                            if (data.data.status === "删除成功啦!") {
                                this.book.splice(i, 1);
                            } else {
                                alert("删除失败!");
                            }
                        })
                }
            },
            save() {
                if (this.bookObj.id) {
                    // 修改
                    axios
                        .put(path, this.bookObj)
                        .then((data) => {
                            if (data.data.status === "修改成功啦!") {
                                alert("修改成功!");
                                this.book = data.data.data;
                                this.bookObj = { id: 0, name: "", picture: "", author: "", price: "" };
                                $("#modal_box").css("display", "none");
                                console.log(this.book);
                            }
                        })
?
                } else {
                    console.log(this.bookObj);
                    // 添加
                    axios
                        .post(path, this.bookObj)
                        .then((data) => {
?
                            if (data.data.status === "添加成功啦!") {
                                this.book.push(data.data.data);
                                this.book.id = 0; // 因为是添加,所以id要重置为0
                                this.bookObj = { id: 0, name: "", picture: "", author: "", price: "" };
                                $("#mdinfo").css("display", "none");
                                $("#file").css("display", "block");
                            }
                        })
                }
            },
            // 显示数据到模态框
            edit(b, i) {
                $("#td").html("修改图书信息");
                $("#modal_box").slideToggle();
                axios
                    .get(path + b.id)
                    .then((data) => {
                        this.bookObj = data.data.data;
                    })
                    .catch((err) => {
                        throw err;
                    })
            },
            // 搜索
            search() {
                console.log($("#searchText").val());
                axios
                    .get(path + ($("#searchText").val()))
                    .then((data) => {
                        this.book = [data.data.data];
                        // console.log(this.book.id);
                        console.log(this.book);
                    })
                    .catch((err) => {
                        throw err;
                    })
            },
            // 按id倒序
            sortId() {
                axios
                    .get(path + "book/sort")
                    .then((data) => {
                        this.book = data.data.data;
                    })
                    .catch((err) => {
                        throw err;
                    })
            },
            // 文件上传的样式
            // 删除
            handleRemove(file) {
                console.log(file);
            },
            // 放大
            handlePictureCardPreview(file) {
                this.dialogImageUrl = file.url;
                this.dialogVisible = true;
                console.log(this.dialogImageUrl);
            },
            // 下载
            handleDownload(file) {
                console.log(file);
            },
            // 获取文件file对象:File {uid: 1663401378587, name: '300.png',....}
            handleChange(e) {
                // 首先获取最新添加的id
                axios
                    .get(path+"book/maxId")
                    .then((data)=>{
                        console.log(data.data.data);
                        this.maxId = data.data.data.id;
                        console.log(data.data.data.id);
                    })
?
                console.log(e.raw);
                let id = this.maxId
                let formDate = new FormData();
                formDate.append('file', e.raw);
                axios
                    .put(path+"file", formDate)
                    .then((data) => {
                        console.log(data);
                        if (data.data.status === "修改封面图片成功啦!") {
                            console.log("成功!");
                            this.book = data.data.data;
                        }
                    })
            },
            hold(){
                $("#modal_box").css("display", "none");
            }
        },
    });
?
    // 模态框显示
    $("#appBook").click(function () {
        $("#modal_box").slideToggle();
        $("#td").html("新增图书信息");
    });
    $("#btn2").click(function () {
        $("#modal_box").css("display", "none");
        $(".text").val("");
    });
?
?
</script>
?
</html>

运行结果:

img