加载页面中...
NodeJS–03 | lwstkhyl

NodeJS--03

b站课程尚硅谷Node.js零基础视频教程P130-P167笔记——MongoDB、接口

写在前面:此笔记来自b站课程尚硅谷Node.js零基础视频教程 P130-P167 / 资料下载 提取码:s3wj / 课上案例

MongoDB

简介与安装

MongoDB是一个基于分布式文件存储的、非关系型的数据库,与使用文件管理数据相比,数据库的速度更快、扩展性更强、安全性更高

与其它的数据库相比,MongoDB的操作语法与js更相似

MongoDB的三个核心概念

  • 数据库(database):MongoDB中可以创建多个数据库,数据库中可以存放多个集合

  • 集合(collection):类似于js的数组,集合中可以存放多个文档,相当于关系型数据库中的一张表

  • 文档(document):是数据库中的最小单位,类似于js中的对象,相当于表中的一行;对象中的属性有时也被称为“字段”

例如对于下面的json结构:

{
  "accounts": [
    {
      "id": "3-YLju5f3",
      "title": "买电脑"
    },
    {
      "id": "3-YLju5f4",
      "title": "请女朋友吃饭"
    }
  ],
  "users":[
    {
      "id": 1
    },
    {
      "id": 2
    }
  ]
}

整个json看成一个数据库,则"accounts""users"就是集合,它们中的对象{"id": "3-YLju5f3","title": "买电脑"}{"id": 1}就是文档,"id""title"就是字段

注意,一般情况下

  • 一个项目使用一个数据库

  • 一个集合存储同一类的数据


官网下载,下载5.0.14,建议zip格式

5.0.14版本下载

  • 将压缩包解压,并移动到C:\Program Files

  • 创建C:\data\db目录,MongoDB会将数据默认保存在这个文件夹

  • 在mongodb-win32-x86_64-windows-5.0.14中bin文件夹下启动命令行,使用cmd(在文件路径那个输入框中直接输入cmd后回车)运行命令mongod(如果是powershell需要先配环境变量)

    简介与安装1

    当看到”Waiting for connections”(id为23016的那条信息)时,就说明数据库服务已经启动

    简介与安装2

  • 再在此处打开另一个cmd,运行命令mongo

    简介与安装3

    此时这个终端就与刚才启动的服务建立了连接,可以看到此时命令提示符由路径变成了>,说明进入了数据库操作状态

  • mongo的那个终端中接着输入show dbs检测连接是否成功

    简介与安装4

    实际上,当输入这个命令时,终端会向数据库服务端发送一个请求,数据库服务端执行查询命令后,将结果返回给终端

  • 为方便后续启动服务,将bin目录添加到环境变量中

注意:不要选中mongod那个终端(服务端)窗口中的内容,选中时数据库服务会暂停,如果不小心选中,可以按回车键取消选中,取消选中后服务自动恢复

常用命令(命令行)

使用较少,了解即可

数据库命令

  • show dbs显示所有的数据库

  • use 数据库切换到指定的数据库,若数据库不存在会自动创建

  • db显示当前所在的数据库

  • db.dropDatabase()删除当前所在数据库

集合命令

  • db.createCollection('集合名')创建集合

  • show collections显示当前数据库中的集合

  • db.集合名.drop()删除指定集合

  • db.集合名.renameCollection('新集合名')重命名指定集合

文档命令

  • db.集合名.insert(文档对象)插入文档

    注:插入时mongodb会自动生成一个键值对_id: xxx,它用于唯一标识文档

  • db.集合名.find(查询条件)根据查询条件查询文档,若不写查询条件则展示全部文档

  • db.集合名.update(查询条件, 新文档对象)更新文档

    db.集合名.update(查询条件, {$set:新文档对象})只更新新文档对象中的属性,其它属性保留

  • db.集合名.remove(查询条件)删除文档

常用命令(命令行)1

注:新创建的空的数据库不显示,需要添加一些数据

常用命令(命令行)2

常用命令(命令行)3

常用命令(命令行)4

补充:

  • 在创建集合时,mongodb会用集合名称的复数创建集合(如果集合名称结尾不是s,就加s)

  • 在某些项目中,当想“删除”某些数据时,不会真正将数据从数据库中删除,而是作“伪删除”,即给该条文档加一个标记,比如加一个属性is_deleted,为true时该文档就被标记删除

Mongoose

是一个对象文档模型库,方便我们使用代码操作MongoDB数据库

使用npm i mongoose@6.9.2安装6.9.2版本

连接数据库

先在终端中启动mongodb服务

const mongoose = require("mongoose");
mongoose.connect("mongodb://主机名:端口号/数据库名称"); //若指定的数据库不存在则自动创建
mongoose.connection.on("open",()=>{
  //连接成功的回调函数(通常是功能性代码)
});
mongoose.connection.on("error",()=>{
  //连接失败的回调函数
});
mongoose.connection.on("close",()=>{
  //连接关闭的回调函数
});
mongoose.disconnect(); //关闭连接

例:

const mongoose = require("mongoose");
mongoose.connect("mongodb://127.0.0.1:27017/test");
mongoose.connection.on("open", () => {
    console.log("连接成功");
});
mongoose.connection.on("error", () => {
    console.log("连接失败");
});
mongoose.connection.on("close", () => {
    console.log("连接关闭");
});
setTimeout(() => { //5s后关闭连接
    mongoose.disconnect();
}, 5000);

连接数据库1

补充:

  • MONGOOSE] DeprecationWarning: Mongoose: the strictQuery option警告解除方法:在导入包后添加mongoose.set('strictQuery', false)

  • 官方推荐绑定open(连接成功)回调函数时使用once而不是on,区别是once只执行一次回调函数。这是因为我们常常在其中写一些功能性代码,如启动HTTP监听,如果第一次连接成功后,数据库服务中断,重新连接成功后就不能再设置监听,因此我们想让open回调函数只执行一次

const mongoose = require("mongoose");
mongoose.set('strictQuery', false); //解除警告
mongoose.connect("mongodb://127.0.0.1:27017/test");
mongoose.connection.once("open", () => { //使用once
    console.log("连接成功");
});
mongoose.connection.on("error", () => {
    console.log("连接失败");
});
mongoose.connection.on("close", () => {
    console.log("连接关闭");
});
setTimeout(() => { //5s后关闭连接
    mongoose.disconnect();
}, 5000);

连接数据库2

插入文档

后续所有的功能性代码都在open回调函数中写

//创建文档的结构对象,用于约束文档值类型
let schema = new mongoose.Schema({
  字段名: 字段类型,
  ...
});
//创建模型对象
let model = mongoose.model(集合名称, schema);
model.create(要插入的数据对象, (err, data)=>{
  //err是错误对象
  //data是插入后的文档对象
});

例:

const mongoose = require("mongoose");
mongoose.set('strictQuery', false); //解除警告
mongoose.connect("mongodb://127.0.0.1:27017/test");
mongoose.connection.once("open", () => {
    const book_schema = new mongoose.Schema({ //文档结构对象
        name: String,
        author: String,
        price: Number
    });
    const model = mongoose.model("books", book_schema); //模型对象
    model.create({
        name: "book1",
        author: "abc",
        price: "100"
    }, (err, data) => {
        if (err) {
            console.log(err);
            return;
        }
        console.log(data);
        mongoose.disconnect(); //项目运行时,不会在这里关闭连接
    });
});

连接数据库3

字段类型及验证
类型 描述
String 字符串
Number 数字
Boolean 布尔值
Array 数组,也可用[]标识
Date 日期,使用new Date()类来赋值
Buffer Buffer对象,可以用于存图片视频,但使用较少(图片视频一般都是存url)
Mixed 任意类型,使用mongoose.Schema.Types.Mixed标识
ObjectId 对象id,使用mongoose.Schema.Types.ObjectId标识
Decimal128 高精度数字,使用mongoose.Schema.Types.Decimal128标识

注:

  • 如果指定的字段类型与实际赋值的字段类型不同,有可能不会自动类型转换,而是报错。如指定类型为Boolean,但实际给了一个字符串,就会报错

  • 如果指定的字段名与实际赋值的字段名不同,则该赋值的字段会被忽略,不会插入到文档对象中,其它相同的字段仍会正常插入

  • ObjectId的值必须是文档id格式(即mongodb自动给每个文档加的那个id),一般用于作外键连接多个表


字段验证:对文档属性值作校验,如果通过就插入到数据库中,如果检测不合法就不插入

  • 必填项:required: true,即插入的文档对象必须包含该字段,如果不包含则报错

    let schema = new mongoose.Schema({
      字段名: {
        type: 字段类型,
        required: true //设置为必填项
      }
    });
    
  • 默认值:default: 默认值,为某个字段设置默认值(如果插入时没有赋值就用默认值),使用方法同上

  • 枚举值:enum: [值1, 值2, ...],即该属性的值必须是数组中的值,使用方法同上

  • 唯一值:unique: true,即该文档的这个属性的值不能与其它文档的重复

    注:要使唯一值生效,必须重建集合

删除/更新文档

删除

  • 删除一条(删除第一条满足条件的数据):model.deleteOne(条件对象, (err, data)=>{})

    • err是错误对象

    • data是一个对象,标识是否成功删除,以及删除了几条数据

  • 删除多条(删除全部符合条件的数据):model.deleteMany(条件对象, (err, data)=>{}),回调函数参数同上

    注:如果条件对象为空对象{},就是删除集合中的全部数据

更新

  • 更新一条(更新第一条满足条件的数据):model.updateOne(条件对象, 新对象, (err, data)=>{})

    • err是错误对象

    • data是一个对象,标识是否成功更新,以及更新了几条数据

  • 更新多条(更新全部符合条件的数据):model.updateMany(条件对象, 新对象, (err, data)=>{}),回调函数参数同上

  • 注:Mongoose不用像命令行一样加$set,它只会更新新对象中提到的属性

:使用资料中的data.js创建实例数据

//文档结构
name: String,
author: String,
price: Number,
is_hot: Boolean

删除文档1

mongoose.connection.once("open", () => {
    let BookSchema = new mongoose.Schema({
        name: String,
        author: String,
        price: Number,
        is_hot: Boolean
    });
    let BookModel = mongoose.model('novel', BookSchema);
    BookModel.deleteOne({ _id: '66ea3e04aa9e07da19234ec1' }, (err, data) => {
        if (err) {
            console.log(err);
            return;
        }
        console.log("删除一条数据:");
        console.log(data);
    });
    BookModel.deleteMany({ is_hot: true }, (err, data) => {
        if (err) {
            console.log("删除失败");
            return;
        }
        console.log("删除多条数据:");
        console.log(data);
    });
    BookModel.updateOne({ name: "大魏能臣" }, { price: 19.9 }, (err, data) => {
        if (err) {
            console.log("更新失败");
            return;
        }
        console.log("更新一条数据:");
        console.log(data);
    });
    BookModel.updateMany({ author: "余华" }, { is_hot: true }, (err, data) => {
        if (err) {
            console.log("更新失败");
            return;
        }
        console.log("更新多条数据:");
        console.log(data);
    });
    // mongoose.disconnect();
});

删除文档2

查询文档
  • 查询一条(查询第一条满足条件的数据):model.findOne(条件对象, (err, data)=>{})

    • err是错误对象

    • data是查询到的文档。如果没有符合条件的,返回null

  • 根据id查询:model.findOne(id编号, (err, data)=>{}),回调函数参数同上

  • 查询多条(查询全部符合条件的数据):model.find(条件对象, (err, data)=>{}),使用方法同上

    • data是一个元素为文档对象的数组

    注:如果像查询全部数据,就不写条件对象参数

例:

mongoose.connection.once("open", () => {
    let BookSchema = new mongoose.Schema({
        name: String,
        author: String,
        price: Number,
        is_hot: Boolean
    });
    let BookModel = mongoose.model('novel', BookSchema);
    BookModel.findOne({author: "吴承恩"},(err, data) => {
        if (err) {
            console.log("查询失败");
            return;
        }
        console.log("查询一条数据:");
        console.log(data);
    });
    BookModel.findById("66ea57c16c51e4ee2fc3be25",(err, data) => {
        if (err) {
            console.log("查询失败");
            return;
        }
        console.log("根据id查询:");
        console.log(data);
    });
    BookModel.find({author: "余华"},(err, data) => {
        if (err) {
            console.log("查询失败");
            return;
        }
        console.log("查询多条数据:");
        console.log(data);
    });
    BookModel.find((err, data) => {
        if (err) {
            console.log("查询失败");
            return;
        }
        console.log("查询全部数据:");
        console.log(data);
    });
    // mongoose.disconnect();
});

查询文档1

条件对象

在条件对象中还可以使用大于小于等于这些判断逻辑,但不能直接写> <,需使用替代符号

基本运算符 替代
> $gt
< $lt
>= $gte
<= $lte
!== $ne
\|\| $or
&& $and

使用方法:

  • id>3的记录:{id: {$gt:3}}

  • id>3id<5的记录:{$and: [{id: {$gt:3}}, {id: {$lt:5}}]}

还可以使用正则表达式:例如查找名称中带有“三”的文档

  • 直接用正则表达式字符串:{name: /三/}

  • RegExp对象:{name: new RegExp('三')},推荐这种方式,因为这种方式可以在正则表达式中使用变量

例:

mongoose.connection.once("open", () => {
    let BookSchema = new mongoose.Schema({
        name: String,
        author: String,
        price: Number,
        is_hot: Boolean
    });
    let BookModel = mongoose.model('novel', BookSchema);
    BookModel.find({ price: { $lt: 10 } }, (err, data) => {
        if (err) {
            console.log("查询失败");
            return;
        }
        console.log("价格小于10的书:");
        console.log(data);
    });
    BookModel.find({ $and: [{ price: { $gt: 20 } }, { price: { $lt: 25 } }] }, (err, data) => {
        if (err) {
            console.log("查询失败");
            return;
        }
        console.log("价格>20且<25的书:");
        console.log(data);
    });
    BookModel.find({ name: new RegExp("") }, (err, data) => {
        if (err) {
            console.log("查询失败");
            return;
        }
        console.log("书名中含“三”的书:");
        console.log(data);
    });
    // mongoose.disconnect();
});

条件对象1

个性化读取

字段筛选(只读取文档对象中某一部分属性):model.find(条件对象).select({属性名:1, ..., 属性名:0}).exec((err, data)=>{})

  • 想要读取哪个属性,就在select中设置哪个属性为1;不想读哪个属性,就设置为0

  • err是错误对象

  • data是读取到的文档对象(数组形式)

例:

BookModel.find({ price: { $lt: 10 } }).select({ name: 1, author: 1 }).exec((err, data) => {
    if (err) {
        console.log("查询失败");
        return;
    }
    console.log("价格小于10的书的书名、作者、id:");
    console.log(data);
});
BookModel.find({ price: { $lt: 10 } }).select({ name: 1, author: 1, _id: 0 }).exec((err, data) => {
    if (err) {
        console.log("查询失败");
        return;
    }
    console.log("价格小于10的书的书名和作者:");
    console.log(data);
});

个性化读取1

数据排序(将查询结果排序后输出):model.find(条件对象).sort({属性名:1, 属性名:-1, ...}).exec((err, data)=>{})

  • 1是从小到大,-1是从大到小

  • 注:可以与select方法联用,下同

例:

BookModel.find().select({ name: 1, price: 1, _id: 0 }).sort({ price: 1 }).exec((err, data) => {
    if (err) {
        console.log("查询失败");
        return;
    }
    console.log("按价格从小到大排序(前10条数据):");
    console.log(data.slice(0, 11));
});
BookModel.find().select({ name: 1, price: 1, _id: 0 }).sort({ price: 1, name: 1 }).exec((err, data) => {
    if (err) {
        console.log("查询失败");
        return;
    }
    console.log("按价格和书名从小到大排序(前10条数据):");
    console.log(data.slice(0, 11));
});

个性化读取2

数据截断

  • 跳过前n条:model.find(条件对象).skip(n).exec((err, data)=>{})

  • 共取n条:model.find(条件对象).limit(n).exec((err, data)=>{})

例:

BookModel.find().select({ name: 1, price: 1, _id: 0 }).sort({ price: -1, name: 1 }).limit(5).exec((err, data) => {
    if (err) {
        console.log("查询失败");
        return;
    }
    console.log("价格最高的5本书:");
    console.log(data);
});
BookModel.find().select({ name: 1, price: 1, _id: 0 }).sort({ price: -1, name: 1 }).skip(5).limit(5).exec((err, data) => {
    if (err) {
        console.log("查询失败");
        return;
    }
    console.log("价格最高的第6-10本书:");
    console.log(data);
});

个性化读取3

代码模块化

因为只有连接成功mongoose.connection.once("open", () => {})的回调函数这部分需要频繁改动,可以将其它的代码封装

思路:将除了连接成功回调函数的所有代码(连接数据库、数据连接成功/失败/关闭的函数)都封装到一个文件db.js中,将这部分代码封装为一个函数,函数中可以传入回调函数作为参数,在连接成功/失败/关闭时执行回调

同时,创建文档对象的代码也是重复的,可以封装到另一个文件BookModel.js中,它将文档对象暴露

再次改进:连接数据库的地址/端口号/数据库名称可能发生变化,将这些配置信息封装到另一个文件config.js中,在db.js中进行拼接

代码模块化1

db.js

/**
 * 
 * @param {*} success 数据库连接成功后执行的回调函数
 * @param {*} error 数据库连接失败后执行的回调函数
 */
module.exports = function (success, error) {
    //这样设置默认值比直接在参数中使用`=`指定更精准(防止error不是函数而报错)
    if (typeof error !== "function") error = () => console.log("连接失败");
    const { name } = require("ejs");
    const mongoose = require("mongoose");
    const { DBHOST, DBPORT, DBNAME } = require("../config/config"); //配置信息
    mongoose.set('strictQuery', false); //解除警告
    mongoose.connect(`mongodb://${DBHOST}:${DBPORT}/${DBNAME}`);
    mongoose.connection.once("open", () => success());
    mongoose.connection.on("error", () => error());
    mongoose.connection.on("close", () => console.log("连接关闭"));
}

快捷注释函数参数:打出/**后按tab

BookModel.js

const mongoose = require('mongoose');
let BookSchema = new mongoose.Schema({
    name: String,
    author: String,
    price: Number,
    is_hot: Boolean
});
let BookModel = mongoose.model('novel', BookSchema);
module.exports = BookModel;

config.js

module.exports = {
    DBHOST: '127.0.0.1',
    DBPORT: 27017,
    DBNAME: "test"
}

index.js

const db = require("./db/db"); //连接数据库
const BookModel = require('./models/BookModel'); //模型对象
const mongoose = require('mongoose');
function success() {
    BookModel.find().select({ name: 1, price: 1, _id: 0 }).sort({ price: -1, name: 1 }).limit(5).exec((err, data) => {
        if (err) {
            console.log("查询失败");
            return;
        }
        console.log("价格最高的5本书:");
        console.log(data);
    });
}
db(success);

执行index.js即可

代码模块化2

当我们想要使用另一个集合和文档对象时,只需在models文件夹中新建一个js文件,里面封装新的集合和文档对象,index.js直接调用它暴露的文档对象即可;更改配置信息时直接更改config.js,不用到db.js中去找连接的那行代码

图形化工具

主要有

图形化工具1

图形化工具2

之后双击左侧的local,相当于与数据库建立连接

图形化工具3

双击就是查看详细信息,右键就是进行新建/删除等操作

图形化工具4

将光标置于文档的最后一格,按tab,就能自动创建一行新的文档

图形化工具5

图形化工具6

双击单元格就可以修改值,右键可以进行删除等操作,按CTRL+s保存修改

综合案例

对之前的记账本案例进行改进,将存在json文件中的数据移到数据库中

安装mongoose:npm i mongoose@6.9.2

代码模块化中创建的config、db、models文件夹移入案例文件夹内

大体思路:当数据库连接成功时启动服务——将启动服务的/bin/www文件中的代码移入/db/db提供的数据库连接成功的回调函数中

/* www */
const db = require("../db/db"); //导入数据库连接成功函数
db(() => { //调用
  var app = require('../app');
  var debug = require('debug')('02-04:server');
  var http = require('http');
  ...
  ...
  ...
  function onListening() {
    var addr = server.address();
    var bind = typeof addr === 'string'
      ? 'pipe ' + addr
      : 'port ' + addr.port;
    debug('Listening on ' + bind);
  }
});

mongodb综合案例1

创建模型文件:修改AccountModel.js

/* AccountModel.js */
const mongoose = require('mongoose');
let AccountSchema = new mongoose.Schema({
    //id不用写,因为mongodb会自动生成
    title: { //标题(必填)
        type: String,
        required: true
    },
    time: { //时间(必填)
        type: Date,
        required: true
    },
    type: { //收入还是支出(必填)
        type: Number, //收入是1,支出是-1
        default: -1,  //默认是支出
        required: true
    },
    account: { //金额(必填)
        type: Number,
        required: true
    },
    remarks: String //备注
});
let AccountModel = mongoose.model('accounts', AccountSchema);
module.exports = AccountModel;

插入数据库:修改account.js中post路由部分

使用moment包将2024-09-20这样的字符串转成Date对象:npm i moment@2.29.4,我们使用其中的两个方法

  • moment("2024-09-20").toDate()将字符串转为Date对象返回

  • moment(Date对象).format('YYYY-MM-DD')将Date对象转为字符串返回

注:如果只是想将将字符串转为Date对象,可以不下这个包,直接new Date("2024-09-20")

/* account.js */
router.post('/', function (req, res) {
  AccountModel.create({
    ...req.body, //先把全部属性放入
    time: moment(req.body.time).toDate() //再修改time属性(自动覆盖)
  }, (err, data) => {
    if (err) {
      res.render('error', { msg: "添加失败", url: "/create" }); //添加失败提醒
      res.status(500);
      return;
    }
  });
  res.render('success', { msg: "添加成功", url: "/account" }); //添加成功提醒
});
<!-- error.ejs -->
<body>
    <div class="container">
        <div class="h-50"></div>
        <div class="alert alert-danger" role="alert">
            <h1>:( <%= msg %>
            </h1>
            <p><a href="<%= url %>">点击跳转</a></p>
        </div>
    </div>
</body>

读取数据库,渲染页面:修改account.jsget('/')路由部分

/* account.js */
router.get('/', function (req, res) {
  AccountModel.find().sort({ time: -1 }).exec((err, data) => { //按时间倒序(新的在上面)
    if (err) {
      res.render('error', { msg: "读取失败", url: "/account" }); //读取失败提醒
      res.status(500);
      return;
    }
    res.render('list', { accounts: data, moment: moment }); //渲染页面
  });
});

更改list.ejs

  • 因为这里的type是数字,需要修改<% if(account.type==='-1' ){ %><% if(account.type===-1 ){ %>

  • 将Date对象转为字符串展示,修改<%= account.time %>moment(account.time).format('YYYY-MM-DD')

删除文档:修改account.jsget('/:id')路由部分

思路:使用mongodb生成的_id作为a标签链接

修改list.ejs中的<%= account.id %><%= account._id %>

注:其实不改也可以,mongodb会向文档中添加一个id属性,其值同_id

/* account.js */
router.get('/:id', function (req, res) {
  const id = req.params.id; //获取id
  AccountModel.deleteOne({ _id: id }, (err, data) => {
    if (err) {
      res.render('error', { msg: "删除失败", url: "/account" }); //删除失败提醒
      res.status(500);
      return;
    }
    res.render('success', { msg: "删除成功", url: "/account" }); //删除成功提醒
  });
});

添加删除确认功能:修改list.ejs,给a标签添加类名del_btn

<!-- list.ejs -->
<script>
    const del_btn = document.querySelectorAll(".del_btn"); //删除按钮
    del_btn.forEach(item =>{
        item.addEventListener("click", function(e){
            if(confirm("确认删除?")){
                return;
            }else{
                e.preventDefault(); //阻止默认行为(跳转->发送请求)
            }
        })
    })
</script>

在账单页加一个能跳转到添加页的按钮:修改list.ejs中的<h2>记账本</h2>标签为

<!-- list.ejs -->
<div class="row">
    <h2 class="col-xs-6">记账本</h2>
    <h2 class="col-xs-6 text-right"><a href="/create" class="btn btn-primary">添加账单</a></h2>
</div>

接口

接口(Application Program Interface, API),也称为API接口,是前后端通信的桥梁。一个接口就是服务中的一个路由规则,根据请求响应结果

这里的接口指“数据接口”,与编程语言中接口的语法不同

注:

  • 项目中接口给客户端返回结果一般是json格式数据

  • 一般是后端写接口,前端调用接口。有时如短信接口、支付接口是后端调用

组成:请求方法、接口地址(url)、请求参数、响应结果

RESTful API

是一种特殊风格的接口,主要特点:

  • url的路径表示资源,路径中不能有动词(例如create、delete、update等)

  • 操作资源要与HTTP请求方法对应

  • 操作结果要与HTTP响应状态码对应

例:

操作 请求类型 url 返回
新增歌曲 post /song 新生成的歌曲信息
删除歌曲 delete /song/歌曲id 一个空文档
修改歌曲 put/patch /song/歌曲id 更新后的歌曲信息
获取所有歌曲 get /song 歌曲列表数组
获取单个歌曲 get /song/歌曲id 单个歌曲信息

注:put是完整更新,patch是局部更新,区别类似于mongodb的更新操作

json-server

是一个工具包,可以快速搭建RESTful API服务

使用全局安装:npm i -g json-server

使用方法:

  • 创建json文件,编写基本结构

    {
        "song":[
            {"id":1, "name":"song1", "singer":"abc"},
            {"id":2, "name":"song2", "singer":"cde"},
            {"id":3, "name":"song3", "singer":"bcd"}
        ],
        "user":[]
    }
    
  • 在json文件所在文件夹下执行json-server --watch json文件名

  • 默认监听url为http://localhost:3000

jsonserver1

jsonserver2

只获取某个id的歌曲信息:http://localhost:3000/song/歌曲id

jsonserver3

接口测试工具

常用的几个:

apipost

安装,启动后点击新建Http接口,输入接口信息,以上面json-server中创建的接口为例,先json-server --watch xxx.json启动监听:

修改接口名称、请求方式、url后点击发送

apipost1

按CTRL+s可以保存创建的测试链接

点击右上角+号可以新建接口

apipost2

为请求url设置默认前缀

apipost3

这样请求url就只写后面的部分就行了

apipost4

测试post请求:一般将数据写在请求体中

观察中间的页面:

apipost5

  • Header请求头

  • Query查询字符串(在这里面写参数,软件会自动将其转为查询字符串写到上面的url中)

  • Body请求体

    • none没有请求体

    • form-data表单形式

    • x-www-form-urlencoded查询字符串形式

    • raw原生请求体(json格式)

这里我们使用json格式请求体:

apipost6

写好请求体后点击发送,返回新增的歌曲信息

注:json-server和mongodb一样,会自动维护id,所以这里我们不用添加id值

此时我们创建的test.json文件内容也会同步改变

测试delete请求:删除id为2的歌曲

apipost7

测试patch请求:更新id为3的歌曲的singer属性

apipost8


公共参数:让所有请求都携带某个参数

点击新建目录

apipost9

其中设置公共参数

apipost10

拖动左侧的接口文件进入该目录内

apipost11

创建完成后可以进行更改公共参数设置

apipost12

发送请求后,就可以看见公共参数

apipost13


快速生成说明文档:给接口生成说明文档

例如:想要让批量获取歌曲这个接口的路径中带有指定的查询字符串

  • search——string类型、必填、含义是“关键字”

  • page——string类型、必填、含义是“页码数”

设置完后,保存,点击分享按钮

apipost14

apipost15

在浏览器中打开这个链接:

apipost16

包括url、参数描述等等信息

在说明文档中创建响应示例

先把要展示的响应内容复制一份,在新建响应示例

apipost17

apipost18

创建完毕后,保存,刷新刚才的分享网页,就可以自动更新

apipost19


在整个项目做完后,还可以直接分享项目

apipost20

apipost21

postman

点开下载的文件Postman-win64-Setup.exe,开始会提示create an account or sign in,按alt+F4关闭,打开它在桌面上创建的快捷方式,这时就不会提示登录了

使用方法基本与apipost相同

postman1

一些区别:

  • 新增接口按钮在上边栏

  • Params是查询字符串,Headers是请求头,Body是请求体

  • 设置json格式的请求体:

    postman2

  • 保存时需要存储在一个目录中,如果没有目录,会让你先创建目录再保存

    postman3

综合案例

为记账本添加接口,在APP端可以使用这些接口获取数据

更改文件夹结构,在routes下新建文件夹web,存储网页端的路由,在vscode中将routes的两个js文件移入其中,此时提示“是否更新导入”,选择“是”。之后更改app.js中两个js文件的引入路径:

/* app.js */
var accountRouter = require('./routes/web/account');
var createRouter = require('./routes/web/create');

之后在routes下新建文件夹api,存储接口,其中新建文件account.js,将web中的account.js文件内容全部复制到其中,此后的接口都是在这个文件中写。在app.js中引入它:

/* app.js */
const accountRouterAPI = require('./routes/api/account');
app.use("/api/account", accountRouterAPI);

接口综合案例2

为什么不用写create.js:因为它只有一个负责渲染添加账单页面的路由,接口中不负责渲染页面

在网页中使用http://127.0.0.1:3000/api/xxx来访问api

此时访问http://127.0.0.1:3000/api/account可以看到记账本页面:

接口综合案例1

修改思路:因为这里我们做的是接口,接口不返回页面,而是返回数据,因此将res.render(直接返回一个HTML页面)改成res.json(返回一个json数据),其内容为

  • code响应编号

    • 响应成功:取值200000000000000,常用0000

    • 响应失败:取值10011002、…(以1开头,后面的值为具体错误类型,自行规定)

  • msg响应信息,如“读取成功”

  • data响应数据,这里是从数据库中查到的信息

获取账单:更改get('/')res.render('list', { accounts: data, moment: moment })(读取成功处理):

/* account.js */
res.json({
    code: '0000',
    msg: '读取成功',
    data: data
});

res.render('error', { msg: "读取失败", url: "/account" });res.status(500);(读取失败处理):

/* account.js */
res.json({
    code: "1001",
    msg: '读取失败',
    data: null
});

使用apipost进行测试:

接口综合案例3

如何测试失败的情况:停止mongodb服务即可

接口综合案例4

添加账单:更改post('/')res.render('success', { msg: "添加成功", url: "/account" })(读取成功处理):

/* account.js */
res.json({
    code: "0000",
    msg: '创建成功',
    data: data
});

更改res.render('error', { msg: "添加失败", url: "/create" });res.status(500);(读取失败处理):

/* account.js */
res.json({
    code: "1002",
    msg: '创建失败',
    data: null
});

接口综合案例5

如何测试创建失败:让某个必填项空着就可以

接口综合案例6

注:这里测试时既可以用raw(json格式)请求体也可以用form-data(表单–查询字符串形式),因为在app.js中它设置了:

/* app.js */
app.use(express.json());
app.use(express.urlencoded({ extended: false }));

这样req.body就能同上解析这两种格式

删除账单:更改get('/:id')路由

/* account.js */
router.delete('/:id', function (req, res) {
    const id = req.params.id; //获取id
    AccountModel.deleteOne({ _id: id }, (err, data) => {
        if (err) {
            res.json({
                code: "1003",
                msg: '删除失败',
                data: null
            });
            return;
        }
        res.json({
            code: "0000",
            msg: '删除成功',
            data: null
        });
    });
});

接口综合案例7

接口综合案例8

获取单个账单:这个功能在原路由中没有,需要新增

/* account.js */
router.get('/:id', function (req, res) {
    let { id } = req.params;
    AccountModel.findById(id, (err, data) => {
        if (err) {
            res.json({
                code: "1004",
                msg: '读取单个账单失败',
                data: null
            });
            return;
        }
        res.json({
            code: '0000',
            msg: '读取成功',
            data: data
        });
    });
});

接口综合案例9

接口综合案例10

更新账单:这个功能在原路由中没有,需要新增

/* account.js */
router.patch('/:id', function (req, res) {
    let { id } = req.params;
    AccountModel.updateOne({ _id: id }, req.body, (err, data) => {
        if (err) {
            res.json({
                code: "1005",
                msg: '更新账单失败',
                data: null
            });
            return;
        }
        //要求返回更新后的结果:再查询一次
        AccountModel.findById(id, (err, data) => {
            if (err) {
                res.json({
                    code: "1004",
                    msg: '读取单个账单失败',
                    data: null
                });
                return;
            }
            res.json({
                code: '0000',
                msg: '更新成功',
                data: data
            });
        });
    });
});

接口综合案例11

接口综合案例12

注:这部分案例只能在接口测试工具中验证,因为不返回页面,因此无法在网页中查看