NodeJS--03
JS-NodeJSb站课程尚硅谷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格式
-
将压缩包解压,并移动到
C:\Program Files
下 -
创建
C:\data\db
目录,MongoDB会将数据默认保存在这个文件夹 -
在mongodb-win32-x86_64-windows-5.0.14中bin文件夹下启动命令行,使用cmd(在文件路径那个输入框中直接输入cmd后回车)运行命令
mongod
(如果是powershell需要先配环境变量)当看到”Waiting for connections”(id为23016的那条信息)时,就说明数据库服务已经启动
-
再在此处打开另一个cmd,运行命令
mongo
此时这个终端就与刚才启动的服务建立了连接,可以看到此时命令提示符由路径变成了
>
,说明进入了数据库操作状态 -
在
mongo
的那个终端中接着输入show dbs
检测连接是否成功实际上,当输入这个命令时,终端会向数据库服务端发送一个请求,数据库服务端执行查询命令后,将结果返回给终端
-
为方便后续启动服务,将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(查询条件)
删除文档
注:新创建的空的数据库不显示,需要添加一些数据
补充:
-
在创建集合时,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);
补充:
-
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);
插入文档
后续所有的功能性代码都在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(); //项目运行时,不会在这里关闭连接
});
});
字段类型及验证
类型 | 描述 |
---|---|
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
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();
});
查询文档
-
查询一条(查询第一条满足条件的数据):
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();
});
条件对象
在条件对象中还可以使用大于小于等于这些判断逻辑,但不能直接写>
<
,需使用替代符号
基本运算符 | 替代 |
---|---|
> |
$gt |
< |
$lt |
>= |
$gte |
<= |
$lte |
!== |
$ne |
\|\| |
$or |
&& |
$and |
使用方法:
-
找
id>3
的记录:{id: {$gt:3}}
-
找
id>3
且id<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();
});
个性化读取
字段筛选(只读取文档对象中某一部分属性):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);
});
数据排序(将查询结果排序后输出):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));
});
数据截断:
-
跳过前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);
});
代码模块化
因为只有连接成功mongoose.connection.once("open", () => {})
的回调函数这部分需要频繁改动,可以将其它的代码封装
思路:将除了连接成功回调函数的所有代码(连接数据库、数据连接成功/失败/关闭的函数)都封装到一个文件db.js
中,将这部分代码封装为一个函数,函数中可以传入回调函数作为参数,在连接成功/失败/关闭时执行回调
同时,创建文档对象的代码也是重复的,可以封装到另一个文件BookModel.js
中,它将文档对象暴露
再次改进:连接数据库的地址/端口号/数据库名称可能发生变化,将这些配置信息封装到另一个文件config.js
中,在db.js
中进行拼接
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即可
当我们想要使用另一个集合和文档对象时,只需在models文件夹中新建一个js文件,里面封装新的集合和文档对象,index.js直接调用它暴露的文档对象即可;更改配置信息时直接更改config.js,不用到db.js中去找连接的那行代码
图形化工具
主要有
之后双击左侧的local
,相当于与数据库建立连接
双击就是查看详细信息,右键就是进行新建/删除等操作
将光标置于文档的最后一格,按tab,就能自动创建一行新的文档
双击单元格就可以修改值,右键可以进行删除等操作,按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);
}
});
创建模型文件:修改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.js
中get('/')
路由部分
/* 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.js
中get('/: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
只获取某个id的歌曲信息:http://localhost:3000/song/歌曲id
接口测试工具
常用的几个:
apipost
安装,启动后点击新建Http接口
,输入接口信息,以上面json-server中创建的接口为例,先json-server --watch xxx.json
启动监听:
修改接口名称、请求方式、url后点击发送
按CTRL+s可以保存创建的测试链接
点击右上角+号可以新建接口:
为请求url设置默认前缀:
这样请求url就只写后面的部分就行了
测试post请求:一般将数据写在请求体中
观察中间的页面:
-
Header
请求头 -
Query
查询字符串(在这里面写参数,软件会自动将其转为查询字符串写到上面的url中) -
Body
请求体-
none
没有请求体 -
form-data
表单形式 -
x-www-form-urlencoded
查询字符串形式 -
raw
原生请求体(json格式)
-
这里我们使用json格式请求体:
写好请求体后点击发送
,返回新增的歌曲信息
注:json-server和mongodb一样,会自动维护id,所以这里我们不用添加id值
此时我们创建的test.json文件内容也会同步改变
测试delete请求:删除id为2的歌曲
测试patch请求:更新id为3的歌曲的singer属性
公共参数:让所有请求都携带某个参数
点击新建目录
其中设置公共参数
拖动左侧的接口文件进入该目录内
创建完成后可以进行更改公共参数设置
发送请求后,就可以看见公共参数
快速生成说明文档:给接口生成说明文档
例如:想要让批量获取歌曲
这个接口的路径中带有指定的查询字符串
-
search
——string类型、必填、含义是“关键字” -
page
——string类型、必填、含义是“页码数”
设置完后,保存,点击分享
按钮
在浏览器中打开这个链接:
包括url、参数描述等等信息
在说明文档中创建响应示例:
先把要展示的响应内容复制一份,在新建响应示例
创建完毕后,保存,刷新刚才的分享网页,就可以自动更新
在整个项目做完后,还可以直接分享项目
postman
点开下载的文件Postman-win64-Setup.exe
,开始会提示create an account or sign in
,按alt+F4关闭,打开它在桌面上创建的快捷方式,这时就不会提示登录了
使用方法基本与apipost相同
一些区别:
-
新增接口按钮在上边栏
-
Params是查询字符串,Headers是请求头,Body是请求体
-
设置json格式的请求体:
-
保存时需要存储在一个目录中,如果没有目录,会让你先创建目录再保存
综合案例
为记账本添加接口,在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);
为什么不用写create.js
:因为它只有一个负责渲染添加账单页面的路由,接口中不负责渲染页面
在网页中使用http://127.0.0.1:3000/api/xxx
来访问api
此时访问http://127.0.0.1:3000/api/account
可以看到记账本页面:
修改思路:因为这里我们做的是接口,接口不返回页面,而是返回数据,因此将res.render
(直接返回一个HTML页面)改成res.json
(返回一个json数据),其内容为
-
code
响应编号-
响应成功:取值
20000
或0000
或000000
,常用0000
-
响应失败:取值
1001
、1002
、…(以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进行测试:
如何测试失败的情况:停止mongodb服务即可
添加账单:更改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
});
如何测试创建失败:让某个必填项空着就可以
注:这里测试时既可以用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
});
});
});
获取单个账单:这个功能在原路由中没有,需要新增
/* 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
});
});
});
更新账单:这个功能在原路由中没有,需要新增
/* 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
});
});
});
});
注:这部分案例只能在接口测试工具中验证,因为不返回页面,因此无法在网页中查看