# Mongoose 连接数据库,Schema 规范,实现增删查改

TIP

本节正式开学 MongoDB 连接 NodeJS,实现 MongoDB 数据库的增删查改操作

  • NodeJS 连接 MongoDB ,NodeJS 原生实现增、删、查、改操作
  • Mongoose 连接数据库,Schema 数据类型、验证规范
  • 配置 MongoDB 数据库的环境变量
  • Mongoose 数据库的增、删、查、改
  • Mongoose 条件控制

我们前面学习了通过 MongoDB Shell 客户端 mongosh 命令行、MongoDB GUI 图形界面 Compass 客户端连接 MongoDB 服务端,接下来我们开始使用 NodeJS 连接

# 一、NodeJS 连接 MongoDB

TIP

体验如何使用 NodeJS 连接 MongoDB,即:通过 NodeJS 程序来操作数据库,未来不会真正在项目中应用。

Mongoose 是项目中用来连接数据库的工具,但小本节内容是学习 Mongoose 的核心基础

# 1、项目前后端设计

再次回顾下图 “评论系统 - 前后端设计”

image-20240304234002558

注:

  • 左边是前端,右边是后端(目前图中是将 NodeJS 和 数据库放在一起了)
  • 实际上数据库是独立于 web server 存在的,拆开来看 “创建新评论” 和 “获取评论列表” 是两个接口(路由),它们是通过 NodeJS 来实现的(具体的请求响应过程之前有讲过,只是模拟数据库实现的)
  • 接下来需要完成 NodeJS 与 数据库的交互(即:让 NodeJS 操作数据库,先不涉及路由),最后在打通路由的层级

# 2、NodeJS 连接 MongoDB 数据库

新建项目 node-mongodb

# 初始化 package.json
npm init -y

安装 nodemon

npm i nodemon -D

package.json 的 script 节点中添加 默认的启动命令

  "scripts": {
    "dev": "nodemon ./src/index.js"
  }

在根目录中新建 src 文件夹,同时新建 src/index.js

node-mongodb
├─ package-lock.json
├─ package.json
└─ src
   └─ index.js

# 2.1、npm 安装 MongoDB 包

npm 官方链接地址:https://www.npmjs.com/package/mongodb (opens new window)

npm i mongodb@^5.0.0 --save

注:

mongodb npm 包的版本 要与 NodeJS 的版本对应起来,否则会有版本不兼容的问题,无法启动运行连接数据库的程序

具体查询 NodeJS 与 mongodb 包版本兼容性文档 (opens new window)

# 2.2、NodeJS 连接 MongoDB 服务

TIP

src/index.js 编写 NodeJS 连接 MongoDB 的代码,不涉及路由(项目会使用 Mongoose 对接路由的功能)

// NodeJS 连接 MongoDB

// 对数据库来说:Compass、命令行、NodeJS 都是数据库的客户端
const { MongoClient } = require("mongodb");

// Connection URL
const url = "mongodb://localhost:27017"; // 本地启动的 MongoDB 服务

// Database Name(评论系统项目的数据库)
const dbName = "comment-system";

// 实例化 client
const client = new MongoClient(url);

async function main() {
  // 使用 connect 方法,连接到服务器
  await client.connect();
  console.log("已成功连接到 MongoDB 服务器 !");

  // 切换数据库(到指定的 comment-system 数据库)
  const db = client.db(dbName);

  // 下面的代码示例可以粘贴到这里 ...

  return "结束";
}

// 执行 main() 函数,它返回一个 Promise
main()
  .then(console.log) // 如果 main() 成功完成,打印其返回值
  .catch(console.error) // 如果 main() 发生错误,打印错误信息
  .finally(() => {
    client.close();
  }); // 无论成功还是失败,都执行 client.close() 以清理资源

在命令行终端启动,使用 nodemon 启动

npm run dev

image-20240306215011061

注:

以上打印输出 “结束” 后,控制台并没有退出,是因为 nodemon 一直在监听程序代码的变化

# 2.3、查看是否关闭数据库连接

TIP

想要观察是否退出 或 关闭 MongoDB 数据库连接,可使用 node 命令启动

node .\src\index.js

image-20240306220858072

注:

使用 node 命令启动运行程序后,立马就退出了该程序(即:数据库连接关闭成功了)

# 3、查询数据

TIP

src/index.js 中,实现 MongoDB 的数据查询

// NodeJS 连接 MongoDB

// 对数据库来说:Compass、命令行、NodeJS 都是数据库的客户端
const { MongoClient } = require("mongodb");

// Connection URL
const url = "mongodb://localhost:27017"; // 本地启动的 MongoDB 服务

// Database Name(评论系统项目的数据库)
const dbName = "comment-system";

// 实例化 client
const client = new MongoClient(url);

async function main() {
  // 使用 connect 方法,连接到服务器
  await client.connect();
  console.log("已成功连接到 MongoDB 服务器 !");

  // 切换数据库 database(指定的 comment-system 数据库)
  const db = client.db(dbName);

  // 切换到指定的结合 collection(users 集合)
  const userCollection = db.collection("users");

  // 查询所有用户数据
  // const userResult = await userCollection.find().toArray()
  // 按条件查询
  // const userResult = await userCollection.find({ city: 'beijing', username: 'icoding' }).toArray()
  // 排序(根据年龄逆序排列),大部分时候我们根据更新时间排序,目前数据库还没有时间,也可根据 id 来查询,将条件换成 { _id: -1 }
  const userResult = await userCollection.find().sort({ age: -1 }).toArray();
  console.log("查询结果:", userResult);

  return "结束";
}

// 执行 main() 函数,它返回一个 Promise
main()
  .then(console.log) // 如果 main() 成功完成,打印其返回值
  .catch(console.error) // 如果 main() 发生错误,打印错误信息
  .finally(() => {
    client.close();
  }); // 无论成功还是失败,都执行 client.close() 以清理资源

在命令行终端启动

npm run dev

image-20240307013719027

# 4、新增数据

TIP

新增一条用户文档数据,使用 insertOne方法

src/index.js 中,实现 MongoDB 新增一条用户文档数据

// 新增一条用户文档数据
const insertUserResult = await userCollection.insertOne({
  username: "美美",
  password: "123",
  age: 25,
  city: "shenzhen",
});

console.log("新增结果:", insertUserResult);

注:新增信息时,对象中的字段多少没有限制。这也是 MongoDB 的优势非常灵活

image-20240307102852068

注:

当你看到返回的是new ObjectId("65e92b5d6e5bc6df6633e145")这样的结果时,这通常意味着你正在查看一个对象表示,而不是一个纯字符串。在 JavaScript 中,new ObjectId(...)是创建一个新的ObjectId对象的语法。

如果你想要获取_id的字符串表示形式,你可以调用ObjectId对象的str方法,或者使用toString()方法。

// 新增一条用户文档数据
const insertUserResult = await userCollection.insertOne({
  username: "静静",
  password: "123",
  age: 23,
  city: "xian",
});

console.log(
  "新增结果:",
  insertUserResult.acknowledged,
  insertUserResult.insertedId.toString()
);

image-20240307193018047

# 5、修改数据

TIP

修改一条用户文档数据,使用 updateOne方法

src/index.js 中,实现 MongoDB 修改一条用户文档数据

// 修改数据
// 更新第一个用户名为 icoding 的用户信息,将 年龄 改为 36、城市改为 hangzhou
const updateUserResult = await userCollection.updateOne(
  { username: "icoding" }, // 修改的条件
  { $set: { age: 36, city: "hangzhou" } } // 修改的内容
);

console.log("修改结果:", updateUserResult);
// modifiedCount 表示 已修改了几行(命中了几行)
console.log("命中的行数:", updateUserResult.modifiedCount);

image-20240307192818152

# 6、删除数据

TIP

删除一条用户文档数据,使用 deleteOne方法

src/index.js 中,实现 MongoDB 删除一条用户文档数据

// 删除数据
// 删除用户名为 icoding 的用户信息
const deleteUserResult = await userCollection.deleteOne({
  username: "icoding",
});

console.log("删除结果:", deleteUserResult);

image-20240307195701963

# 7、总结

TIP

以上的学习中,我们完成了

  • 安装 MongoDB npm 插件,即:Mongoose
  • 连接数据库,连接集合
  • 操作文档,增删查改

# 二、Mongoose 连接数据库,数据类型

TIP

深入浅出 Mongoose 连接 和 操作 MongoDB 数据库

  • Mongoose 是什么
  • Schema 和 Model
  • 基于 Model 操作数据
  • 完善评论系统项目路由的功能

# 1、Mongoose 是什么

TIP

Mongoose 是一个对象文档模型(ODM)库,用于 Node.js 中操作 MongoDB 数据库。

它将数据库中的数据转换为 JavaScript 对象,封装了 MongoDB 对文档的增删改查等常用方法,使得在 Node.js 中操作 MongoDB 数据库变得更加灵活简单。Mongoose 提供了 Schema、Model 和 Document 三个主要对象,分别用于定义数据库中的文档结构、表示集合中的所有文档以及表示集合中的具体文档。

此外,Mongoose 还提供了验证和类型转换等功能,以及更多的高级特性和用法。总的来说,Mongoose 是一个强大的工具,使得在 Node.js 中操作 MongoDB 数据库变得更加简单和高效。

image-20240304165812757

注:

  • 使用 MongoDB Shell 客户端 mongosh 在命令行中与 MongoDB 服务端进行手动交互。即:在命令行中输入命令 -> “回车” 后,将请求发送给 MongoDB 服务端 -> 服务端再将结果返回给 “命令行”。此方式学习阶段没问题,但项目在正式运行中是不行的。
  • Compass 图形界面 与 MongoDB 服务端交互,也是同理。
  • 使用 代码程序 与 MongoDB 服务端建立连接 并 发送请求,将结果返回给代码程序,最终将结果做处理后返回给用户即可

这样,就可以实现全部的自动化了,而 Mongoose 就是这样一个工具。有了 Mongoose 后,我们就可以在代码中连接 MongoDB 的数据库 并 做一些交互(文档的增删查改)等场景都可以通过代码来实现。

# 2、MongoDB 的数据类型特点

TIP

由于 MongoDB 的数据格式过于灵活

  • 可以插入任何数据,不受限制
  • 实际项目开发时,要有数据格式的规范(重要)。如:插入数据时一定要规定好具体有哪些字段,不能少也不能多。
  • 特别是多人协作开发时

Mongoose 就提供了数据的规范,就可以用来做约束,按照既定数据规范来实现业务功能即可,不需要数据格式灵活性和创新

# 3、Mongoose 的作用

TIP

Mongoose 是一个用于在 Node.js 中操作 MongoDB 数据库的模块,它是一个对象文档模型(ODM)库,对 Node 原生的 MongoDB 模块进行了优化封装,并提供了更多的功能。Mongoose 的主要作用体现在以下几个方面:

  • 连接 MongoDB 数据库:Mongoose 提供了一个简单的方法来连接 MongoDB 数据库,使得在 Node.js 中访问数据库变得轻松。

  • 模型定义与数据建模:Mongoose 允许你使用模式(Schema)来定义数据库中文档的结构,类似于传统数据库中的表。通过定义模型,你可以描述数据结构和数据之间的关系,确保数据库中存储的文档满足特定的格式规范。这种模型在 MongoDB 中更为灵活,可以动态地调整数据结构。

  • 数据验证:Mongoose 提供了内置的验证器,可以自动对文档中的数据进行验证,确保数据的一致性和有效性。你可以在模型中定义规则,例如字段的最小长度、最大长度、必填字段等,以保证数据的完整性。

  • 数据处理:Mongoose 提供了一组强大的 API,可以用来对数据库中的数据进行增删改查操作,从而方便地处理数据。

  • 中间件:Mongoose 允许你使用中间件来扩展模型的功能,例如添加自定义的业务逻辑。

Mongoose 在 Node.js 中操作 MongoDB 数据库时,提供了连接、建模、验证、处理数据和扩展功能等一系列便利的功能,使得开发者能够更加高效、灵活地与 MongoDB 数据库进行交互

# 4、Mongoose 数据规范

TIP

  • Schema 定义数据格式的规范
  • 以 Model 规范 Collection
  • 规范数据操作的 API

# 5、使用 Mongoose 连接数据库

新建 icoding-mongoose 项目,完成 Mongoose 连接数据库

# 初始化 package.json
npm init -y

安装 nodemon

npm i nodemon -D

package.json 的 script 节点中添加 默认的启动命令

  "scripts": {
    "dev": "nodemon ./src/index.js"
  }

安装 Mongoose

npm i mongoose --save

在根目录中新建 src/db 文件夹,同时新建 src/db//db.js

icoding-mongoose
├─ package-lock.json
├─ package.json
└─ src
   └─ db
      └─ db.js

src/db//db.js 中完成 Mongoose 连接数据库的操作

// 使用 Mongoose 连接数据库 MongoDB 的服务端
// 导入 mongoose
const mongoose = require("mongoose");

// Connection URL
const url = "mongodb://localhost:27017"; // 本地启动的 MongoDB 服务
// Database Name(评论系统项目的数据库)如果该数据库不存在,会自动创建
const dbName = "icoding-comment-system";

// 开始连接数据库
mongoose.connect(`${url}/${dbName}`);

// 获取数据库的连接对象
const conn = mongoose.connection;

// 设置连接成功的回调
conn.on("open", () => {
  console.log("数据库连接成功 !");
});

// 连接异常(将端口改错来测试),设置连接错误的回调
conn.on("error", (err) => {
  console.log("数据库连接失败 !" + err);
});

// 设置连接关闭的回调(下边关闭连接完成后,就会执行该回调函数)
conn.on("close", () => {
  console.log("数据库连接关闭 !");
});

// 关闭 MongoDB 的连接,设置一个延时操作,等待 2s 后关闭
setTimeout(() => {
  mongoose.disconnect();
}, 2000);

在 VSCode 命令行终端中运行

node .\src\db\db.js

image-20240311210522644

# 6、新增文档

TIP

  • 创建文档的结构对象 Schema
  • 创建模型对象,对文档操作的封装对象
  • 完成文档的新增

/src/base/create.js

// 使用 Mongoose 连接数据库 MongoDB 的服务端
const mongoose = require("mongoose");

// Connection URL
const url = "mongodb://localhost:27017"; // 本地启动的 MongoDB 服务
// Database Name(评论系统项目的数据库)如果该数据库不存在,会自动创建
const dbName = "icoding-comment-system";

// 开始连接数据库
mongoose.connect(`${url}/${dbName}`);

// 获取数据库的连接对象
const conn = mongoose.connection;

// 设置连接成功的回调
conn.on("open", () => {
  console.log("数据库连接成功 !");

  // 新增文档数据 ------------------
  // 1、创建文档的结构对象(设置集合中文档的属性 和 属性值的类型)
  const UserSchema = new mongoose.Schema(
    {
      username: {
        type: String,
        required: true, // 必填
        unique: true, // 唯一,不重复
      },
      password: String,
      age: Number,
      city: String,
      // 性别
      gender: {
        type: Number,
        // 默认值
        default: 0, // 0 - 保密,1 - 男,2 - 女
      },
    },
    {
      timestamps: true, // 时间戳,自动添加文档的创建时间和更新时间
    }
  );

  // 2、创建模型对象,对文档操作的封装对象(用户文档数据需要依赖于 UserSchema 数据格式的定义)
  const UserModel = mongoose.model("user", UserSchema);
  // 3、新增文档数据
  UserModel.create({
    username: "icoding",
    password: "123",
    age: 18,
    city: "beijing",
    gender: 1,
  })
    .then((res) => {
      console.log("新增文档数据成功:", res);
    })
    .catch((err) => {
      console.error("新增文档数据失败:", err);
    });
});

// 连接异常(将端口改错来测试),设置连接错误的回调
conn.on("error", (err) => {
  console.log("数据库连接失败 !" + err);
});

// 设置连接关闭的回调(下边关闭连接完成后,就会执行该回调函数)
conn.on("close", () => {
  console.log("数据库连接关闭 !");
});

// 关闭 MongoDB 的连接,设置一个延时操作,等待 2s 后关闭
// setTimeout(() => {
//     mongoose.disconnect()
// }, 2000)

在 VSCode 命令行终端中运行

node .\src\base\create.js

image-20240312175609427

在 Compass 中查看新增成功后的文档数据(数据库 和 文档会被自动创建)

image-20240312175746117

# 7、Schema 中的内置字段类型

TIP

这些数据类型可以在定义 Mongoose 的 Schema 时指定,以确保存储在 MongoDB 中的数据具有正确的格式和结构。

即:文档属性值的类型

数据类型 描述
String 用于存储文本字符串。
Number 用于存储数字,包括整数和浮点数。
Date 用于存储日期和时间。
Buffer Buffer 对象,用于存储二进制数据。
Boolean 用于存储布尔值,即truefalse
Mixed 用于存储任意类型的数据,相当于 JavaScript 中的any类型。
ObjectId 用于存储 MongoDB 文档的 ID,通常是自动生成的。
Array 用于存储数组,数组中的元素可以是任意类型。
也可用 [] 来标识
Decimal128 高精度数字
{
    // 商品标题,String 字符串类型
    product_title: String,
    // 商品价格,Number 数字类型
    price: Number,
    // 商品上市时间,Date 时间日期类型
    time_to_market: Date,
    // 用于存储文件的二进制数据(将图片 或 视频 文件等转换成二进制格式存储)
    // 实际开发中不会这么做,一般都只存储 文件的路径在数据库中,将文件存在静态资源服务器 或 文件夹中,这样效率更高(Buffer 了解即可,开发中几乎不用)
    file: Buffer,
    // 是否热门,Boolean 布尔值
    is_hot: Boolean,
    // 数组
    tags: Array,
    // 测试任意类型的数据
    test_mixed: mongoose.Schema.Types.Mixed,
    // 文档类型的 id
    // 一般用于做 “外键”,即:将另一个文档的 id 存储在该文档中去(让他们之间产生关联)
    // 用于联合查询使用,通过该 id 查询另一个文档中关联的数据
    test_id: mongoose.Schema.Types.ObjectId,
    // 高精度数字,对数字精度要求比较高时会用到
    test_decimal128: mongoose.Schema.Types.Decimal128
}

// 赋值
{
    product_title: '商品标题',
    price: 9.9,
    time_to_market: Date.now, // 使用当前时间作为默认值
    is_hot: true,
    tags: ['HTML', 'CSS', 'JavaScript', 'Python', 'Go'],
    test_mixed: 1,
    test_id: '65f023d197cdbbb9b24e8fff'
}

# 8、Schema 定义字段值验证规范

TIP

对 Schema 中定义的字段进行验证,防止某些操作对数据库的影响。如缺少未设置字段也可以保存成功等操作

注:作为后端工程师永远不要相信用户的输入

常见验证操作 含义
required 必须填写
default 默认值
validate 自定义匹配
min 最小值(只适用于数字)
max 最大值(只适用于数字)
match 正则匹配(只适用于字符串)
enum 枚举类型(只适用于字符串)
minlength 字符串最小长度
maxlength 字符串最大长度

# 8、字段值验证

TIP

Mongoose 有一些内建验证器,可以对文档属性的字段值进行验证。

如:若检测通过,即:放行。就往数据库中插入数据。若检测不合法,就报错禁止插入数据库。

# 8.1、default 默认值

TIP

  • 说明:当字段在创建文档时没有被明确赋值时,它将采用default指定的默认值。
  • 用法:可以是一个固定的值,也可以是一个返回默认值的函数。
username: {
    type: String,
    default: 'ibc' // 当 username 字段没有赋值时,它将默认为 'ibc'
},
createdAt: {
    type: Date,
    default: Date.now // 使用当前时间作为默认值
}

# 8.2、required 必填值

TIP

  • 说明:标记字段为必需,即在创建或更新文档时,该字段必须存在且不能为nullundefined
  • 用法:通常是一个布尔值,true表示字段是必需的,false表示不是必需的。
username: {
    type: String,
    required: true, // 必填
    unique: true // 唯一,不重复
},
email: {
    type: String,
    required: true, // email字段在创建或更新文档时是必需的
    match: [/^\S+@\S+\.\S+$/, '请提供有效的电子邮件地址']
}

# 8.3、enum 枚举值

TIP

  • 说明:限制字段的值只能为枚举数组中的某个元素。如果尝试设置不在枚举数组中的值,将会抛出验证错误。
  • 用法enum的值应该是一个数组,包含所有允许的字段值。
// 定义一个枚举字段,只允许指定的值
color: {
    type: String, // 使用String类型作为基础类型
    // color 字段的值只能是这三个字符串中的一个
    enum: ['red', 'green', 'blue'], // 定义枚举值的集合
    default: 'green' // 默认值为'green'
}

# 8.4、validate 自定义匹配

TIP

  • 说明:自定义验证函数,用于执行更复杂的验证逻辑。
  • 用法:提供一个函数,该函数接受字段值作为参数,并返回一个布尔值或错误对象。
age: {
    type: Number,
    validate: {
      validator: function(value) {
        return value >= 0 && value <= 150;
      },
      message: props => `${props.value} 岁不是有效年龄`
    }
 }

# 8.5、min 最小值

TIP

  • 说明:指定字段值的最小值。
  • 用法:与数值或字符串长度相关的字段一起使用。
// 数值示例
score: {
    type: Number,
    min: [0, 'Score 必须大于或等于零']
}

// 字符串长度示例
username: {
    type: 'string',
    minLength: 3
}

# 8.6、max 最大值

TIP

  • 说明:指定字段值的最大值。
  • 用法:与min类似,但用于指定上限。
age: {
    type: Number,
    max: [150, '年龄必须小于或等于150']
}

# 8.7、match 正则匹配

TIP

  • 说明:使用正则表达式匹配字段值。
  • 用法:提供一个正则表达式对象或字符串。
email: {
    type: String,
    match: [/^\w+([\.-]?\w+)*@\w+([\.-]?\w+)*(\.\w{2,3})+$/, '请填写有效的电子邮件地址']
}

# 8.8、minlength 字符串最小长度

TIP

  • 说明:指定字符串字段的最小长度。
  • 用法:与字符串字段一起使用。
username: {
    type: 'string',
    minLength: 4
}

# 8.9、maxlength 字符串最大长度

TIP

  • 说明:指定字符串字段的最大长度。
  • 用法:与minlength类似,但用于指定上限。
description: {
    type: 'string',
    maxLength: 255
}

# 三、配置 MongoDB 数据库的环境变量

TIP

在 NodeJS 应用程序中连接 MongoDB 数据库时,通常推荐使用环境变量(env variables)来存储敏感信息,如数据库的连接字符串。

这样可以避免在代码库中硬编码这些敏感信息,并且便于在不同环境(如开发、测试、生产)之间切换配置。

# 1、连接 MongoDB 数据库

TIP

首先,我们需要使用 Mongoose 连接到 MongoDB 数据库。

src/db/db.js

// 使用 Mongoose 连接数据库 MongoDB 的服务端
const mongoose = require("mongoose");

// Connection URL
const url = "mongodb://localhost:27017"; // 本地启动的 MongoDB 服务
// Database Name(评论系统项目的数据库)如果该数据库不存在,会自动创建
const dbName = "icoding-comment-system";

// 开始连接数据库
mongoose
  .connect(`${url}/${dbName}`)
  .then(() => {
    console.log("数据库连接成功 !");
  })
  .catch((err) => {
    console.error("连接数据库失败 !", err);
  });

// 导出 mongoose
module.exports = mongoose;

测试数据库连接

node .\src\db\db.js

# 2、创建 .env 文件

TIP

在你的项目根目录下创建一个名为 .env 的文件(注意:在某些系统上,.env 文件可能默认是隐藏的)。在这个文件中,你可以定义环境变量。

MONGODB_HOST = 127.0.0.1
MONGODB_PORT = 27017
MONGODB_USER = ''
MONGODB_PWD = ''
MONGODB_DB = icoding-comment-system

注:

  • MONGODB_HOST MongoDB 数据库服务器主机地址(IP 或 域名)
  • MONGODB_PORT MongoDB 数据库服务器监听的端口号
  • MONGODB_USER 连接 MongoDB 数据库的用户名
  • MONGODB_PWD 连接 MongoDB 数据库的密码
  • MONGODB_DB 要连接的 MongoDB 数据库的名称

出于安全考虑,不应该在代码库或版本控制系统中硬编码这些敏感信息(如用户名和密码)。使用环境变量是一个更好的做法,因为它允许你在不同的环境中(如开发、测试和生产)使用不同的值,而无需修改代码。

同时,确保这些环境变量在生产环境中是安全地存储和管理的。

# 3、安装 dotenv

TIP

安装 dotenv 包来读取 .env 文件中的环境变量

npm install dotenv --save

# 4、在代码中读取环境变量

TIP

在 NodeJS 代码中,使用 dotenv 包来加载 .env 文件中的环境变量,然后可以使用 process.env 对象来访问它们

创建 src/config/config.default.js 配置文件,用于读取环境变量的信息

// 导入 dotenv 包 ,将环境变量从文件加载到进程中
const dotenv = require("dotenv");
// 读取配置信息(加载 .env 文件中的环境变量)
dotenv.config();

// process 代表进程 ,env 代表环境变量
// console.log(process.env.MONGODB_PORT)

// 导出环境变量
module.exports = process.env;

# 5、改造数据库连接

TIP

将原来代码中的数据库相关信息,改成从环境变量中读取的信息

src/db/db.js

// 导入 env 环境变量
const {
  MONGODB_HOST,
  MONGODB_PORT,
  MONGODB_USER,
  MONGODB_PWD,
  MONGODB_DB,
} = require("../config/config.default");
// 使用 Mongoose 连接数据库 MongoDB 的服务端
const mongoose = require("mongoose");

// // Connection URL
// const url = 'mongodb://localhost:27017'; // 本地启动的 MongoDB 服务
// // Database Name(评论系统项目的数据库)如果该数据库不存在,会自动创建
// const dbName = 'icoding-comment-system';

// 适应环境变量参数
// 开始连接数据库
mongoose
  .connect(`mongodb://${MONGODB_HOST}:${MONGODB_PORT}/${MONGODB_DB}`)
  .then(() => {
    console.log("数据库连接成功 !");
  })
  .catch((err) => {
    console.error("连接数据库失败 !", err);
  });

// 导出 mongoose(commonJS 语法)
module.exports = mongoose;

在命令行终端,测试启动连接数据库

node .\src\db\db.js

# 6、确保 .env 文件不被提交到版本控制

TIP

通常,不希望将 .env 文件提交到版本控制(如 Git),因为这可能会泄露敏感信息。可以在 .gitignore 文件中添加 .env 来确保它不会被提交。

# .gitignore
.env

# 7、处理不同的环境

TIP

对于不同的环境(如开发、测试和生产),你可能需要不同的配置。一种常见的做法是为每个环境创建一个单独的 .env 文件(如 .env.development.env.test.env.production),并在运行应用程序时指定要加载哪个文件。

可以使用 dotenvdotenv.config({ path: 'path/to/.env' }) 方法来加载特定路径的 .env 文件。

# 8、在生产环境中保护环境变量

TIP

在生产环境中,最佳实践是将环境变量设置为操作系统级别的环境变量,而不是将它们存储在文件中。

这样,即使攻击者能够访问你的服务器文件,他们也无法直接读取这些环境变量。具体的设置方法取决于你使用的操作系统和部署方式。

# 四、Mongoose 数据库的增、删、查、改

TIP

使用 Mongoose 工具实现数据库的增(创建)、删(删除)、查(查询)、改(更新)

# 1、定义模型(Schema)

TIP

需要定义一个 Mongoose 模型,它基于 MongoDB 的集合和文档结构

创建 src/model/user.model.js

// 数据模型(规范数据格式)
const { Schema } = require("mongoose");
const mongoose = require("../db/db");

// 定义 Schema
const userSchema = new Schema(
  {
    username: {
      type: String,
      required: true, // 必填
      unique: true, // 唯一,不重复
    },
    password: String,
    age: Number,
    city: String,
    // 性别
    gender: {
      type: Number,
      // 默认值
      default: 0, // 0 - 保密,1 - 男,2 - 女
    },
  },
  {
    timestamps: true, // 时间戳,自动添加文档的创建时间和更新时间
  }
);

// 创建模型
const User = mongoose.model("User", userSchema);

// 导出 User 对象
module.exports = User;

# 2、新增文档数据

TIP

使用 Mongoose 模型创建一个新文档(即插入到数据库)

src/test/create.js

// 导入 User 模型
const User = require("../model/user.model");

// 定义一个 async 的匿名函数,并执行。为了里面能用 await
!(async () => {
  // // 创建一个新用户 - 方式 1
  // const newUser = new User({
  //     username: 'allen',
  //     password: '123',
  //     age: 21,
  //     city: 'shanghai',
  //     gender: 2
  // })

  // // 保存新用户到数据库
  // newUser.save()
  //     .then(user => {
  //         console.log('用户创建成功 !', user)
  //     })
  //     .catch(err => {
  //         console.log('用户创建失败 !', err)
  //     })

  // 创建一个新用户 - 方式 2
  const newUser = await User.create({
    username: "ibc",
    password: "123",
    age: 25,
    city: "shenzhen",
    gender: 1,
  });

  console.log("创建用户成功 !", newUser);
})();

在命令行终端,测试启动新增文档数据

node .\src\test\create.js

image-20240315134900871

# 3、查询文档数据

TIP

查询数据库中的文档,查询所有数据、 查找特定用户(按条件查询)、查询单条数据、分页查询

src/test/find-findOne.js

// 导入 User 模型
const User = require("../model/user.model");

!(async () => {
  // // 查找所有用户(返回数组)
  // const userList = await User.find()
  // console.log('所有用户查询结果:', userList)

  // // 查找特定用户(按条件查询)
  // const userList = await User.find({ username: 'allen' })
  // console.log('按条件查询用户结果:', userList)

  // 查询单条数据
  // const user = await User.findOne({ city: 'beijing' })
  // console.log('查询单条数据:', user)

  // 分页查询
  // page 请求的页码,默认值 1
  // pageSize 每页显示的文档数量, 默认值 10
  const getPaginatedData = async (page = 1, pageSize = 10) => {
    // 计算需要跳过的文档数量
    const skip = (page - 1) * pageSize;
    // 执行查询并获取数据
    return await User.find().skip(skip).limit(pageSize).exec();
  };

  // 使用分页查询函数获取数据
  // 获取第2页的数据,每页2条记录
  getPaginatedData(2, 2)
    .then((data) => {
      console.log(data); // 输出查询结果
    })
    .catch((error) => {
      console.error("获取分页数据时出错:", error);
    });
})();

在命令行终端,测试启动查询文档数据

node .\src\test\find-findOne.js

image-20240316112527057

注:

以上代码中,分页查询的解读

getPaginatedData 函数接受两个参数:page(页码)和 pageSize(每页的记录数)。函数内部首先计算需要跳过的文档数量(skip),然后使用 User.find().skip(skip).limit(pageSize).exec() 执行分页查询。

最后,函数返回查询结果。你可以根据需要调整页码和每页的记录数。

# 4、分页查询详细解读

TIP

以上分页查询的代码中,是一个异步函数 getPaginatedData,它用于获取分页的数据。该函数接收两个参数:pagepageSize,分别代表请求的页码和每页显示的文档数量。

如果调用函数时没有提供这两个参数,它们将分别默认为 1 和 10。

# 4.1、参数定义

const getPaginatedData = async (page = 1, pageSize = 10) => {};

注:

这里定义了一个异步函数 getPaginatedData,它有两个参数:pagepageSize

这两个参数都有默认值,page 的默认值是 1,pageSize 的默认值是 10。

# 4.2、计算需要跳过的文档数量

const skip = (page - 1) * pageSize;

注:

为了获取特定页码的数据,我们需要知道从哪个位置开始查询。这里,我们使用 (page - 1) * pageSize 来计算需要跳过的文档数量。

例如,如果 page 是 2,pageSize 是 10,那么 skip 的值将是 10,意味着我们将跳过前 10 个文档,从而从第 11 个文档开始查询。

# 4.3、执行查询并获取数据

return await User.find().skip(skip).limit(pageSize).exec();

注:

  • User.find():这是一个基于 MongoDB 的查询操作,用于从 User 集合中查找文档
  • .skip(skip):这是 Mongoose 的一个方法,用于跳过指定数量的文档。在这里,我们跳过 skip 个文档
  • .limit(pageSize):这也是 Mongoose 的一个方法,用于限制返回的文档数量。在这里,我们限制返回的文档数量为 pageSize
  • .exec():这是 Mongoose 的一个方法,用于执行查询并返回结果

# 5、数据排序

// 按 年龄 age 升序排列
const userList = await User.find().sort({ age: 1 }).exec();

注:

  • sort({ age: 1 }) 表示按照 age 字段 升序 排列查询结果
  • sort({ age: -1 }) 表示按照 age 字段 降序 排列查询结果

# 6、更新文档数据

TIP

更新数据库中的现有文档

src/test/update.js

// 导入 User 模型
const User = require("../model/user.model");

!(async () => {
  // 更新用户数据
  const updateUserOne = async (username, age) => {
    // updateOne命令用于更新集合中匹配到的第一个文档
    // 更新第一个用户名为 allen 的用户信息,将 年龄 改为 31
    const res = await User.updateOne(
      { username: username }, // 修改的条件
      { $set: { age: age } } // 修改的内容
    );
    // 返回值为代码改动记录数
    return res.modifiedCount > 0 ? true : false;
  };

  // 更新用户数据
  updateUserOne("allen", 36)
    .then((data) => {
      console.log("用户数据更新成功 !", data);
    })
    .catch((err) => {
      console.log("更新用户信息失败 !", err);
    });
})();

在命令行终端,测试启动更新文档数据

node .\src\test\update.js

image-20240316112805009

# 7、删除文档数据

TIP

从数据库中删除文档

src/test/delete.js

// 导入 User 模型
const User = require("../model/user.model");

!(async () => {
  // 删除用户数据
  const deleteUserOne = async (username) => {
    // deleteOne方法用于删除与指定查询条件匹配的第一个文档
    // 删除该用户名的第一个用户信息
    const res = await User.deleteOne({ username: username });
    // 返回值为代码改动记录数
    return res.deletedCount > 0 ? true : false;
  };

  // 根据用户名删除用户信息
  deleteUserOne("ibc")
    .then((data) => {
      console.log("删除成功!", data);
    })
    .catch((err) => {
      console.log("删除失败!", err);
    });
})();

在命令行终端,测试启动删除文档数据

node .\src\test\delete.js

image-20240316163609803

# 五、条件控制

TIP

在 Mongoose 中,可以使用多种条件控制语句、运算符、逻辑运算以及正则匹配来构建查询。

# 1、运算符

TIP

Mongoose 支持 MongoDB 的所有查询运算符。

以下是一些常见的运算符:

# 1.1、比较运算符

比较运算符 替代符号 描述
= $eq 等于
> $gt 大于
>= $gte 大于等于
< $lt 小于
<= $lte 小于等于
!== $ne 不等于
$in 在数组中
$nin 不在数组中

$gt 比较运算符,大于的情况

// 查找 age 大于 20 的所有数据(返回数组)
const userList = await User.find({ age: { $gt: 20 } });

$ne 比较运算符,不等于的情况

// 查找 不包含 age = 19 的所有数据(返回数组)
const userList = await User.find({ age: { $ne: 19 } });

# 1.2、元素运算符

元素运算符 描述
$exists 字段存在
$type 字段类型

# 1.3、逻辑运算符

逻辑运算符 描述
$and
$or
$not
$nor 非或

$or 逻辑运算符,或的情况

// 查找 age 等于 20 或 age 等于 22 的所有数据,满足其中一个条件即可(返回数组)
const userList = await User.find({ $or: [{ age: 19 }, { age: 22 }] });

$and 逻辑运算符,与的情况

// 查找 30 > age > 18 的所有数据(返回数组)
const userList = await User.find({
  $and: [{ age: { $lt: 30 } }, { age: { $gt: 18 } }],
});
console.log("查询结果:", userList);

$not 逻辑运算符,非的情况

// 查询所在城市 非北京 的用户数据(返回数组)
const userList = await User.find({ city: { $not: { $eq: "beijing" } } });
console.log("查询结果:", userList);

# 1.4、数组运算符

数组运算符 描述
$all 所有元素匹配
$elemMatch 至少一个元素匹配
$size 数组大小

# 1.5、地理空间运算符

地理空间运算符 描述
$near 近邻搜索
$nearSphere 球面上的近邻搜索
$within 在指定形状内

# 1.6、更新运算符

更新运算符 描述
$set 设置字段值
$inc 增加字段值
$mul 乘以字段值
$rename 重命名字段
$unset 删除字段

# 2、正则匹配

结合多个运算符 和 逻辑运算来构建复杂的查询

// 结合多个运算符 和 逻辑运算来构建复杂的查询
const userList = await User.find({
  $or: [
    { username: { $regex: new RegExp("new", "i") } },
    { age: { $gte: 18, $lte: 30 } },
  ],
  city: "beijing",
});

注:

这个查询会找到名字中包含 "new"(不区分大小写)或者年龄在 18 到 30 岁之间,并且 city 字段为 beijing 的所有用户。

  • new RegExp('new', 'i') 创建了一个正则表达式对象,用于模式匹配
  • 其中,'i' 是一个标志(flag),它表示“不区分大小写”(ignore case)

因此,当你使用 { username: { $regex: new RegExp('new', 'i') } } 作为查询条件时,它会匹配所有 username 字段中包含 "new"(不区分大小写)的文档。这意味着 "New"、"nEw"、"neW"、“NEW” 等都会被视为匹配项。

简而言之,'i' 标志允许你在进行正则表达式匹配时忽略大小写。

上次更新时间: 3/17/2024, 7:06:32 PM

大厂最新技术学习分享群

大厂最新技术学习分享群

微信扫一扫进群,获取资料

X