by 雪隐 from juejin.cn/user/143341...
本文欢迎分享与聚合,全文转载就不必了,尊重版权,圈子就这么大,若急用可联系授权
概要
在开始本章之前,希望大家先看下官方文档Mongo,此外我还将对接口进行保护,请阅读之前的博客文章 接口防护对前置知识进行了解。
MongoDB介绍以及优缺点
MongoDB是一种开源的、高性能、面向文档的NoSQL数据库,其名称"Mongo"来自于"humongous"(庞大的)一词,它旨在处理大规模的、非结构化或半结构化数据。MongoDB是目前最流行的NoSQL数据库之一,它具有一些独特的特点和优势,同时也有一些限制和缺点。
优点:
- 灵活的文档模型:MongoDB使用文档(BSON格式)来存储数据,文档可以包含不同类型的字段和嵌套文档,这种灵活性使其适用于多种数据结构和应用场景。
- 水平扩展:MongoDB支持水平扩展,可以轻松地将新节点添加到集群中以处理更多的负载,这使得它适用于大规模和高吞吐量的应用。
- 高性能:MongoDB的内部存储引擎具有出色的性能,特别适用于读取密集型和写入密集型应用。它还提供了自动负载均衡和分片来优化性能。
- 丰富的查询功能:MongoDB支持复杂的查询,包括范围查询、全文搜索和地理空间查询。它还具有强大的聚合框架,可以进行数据分析和转换。
- 自动故障转移:MongoDB具备自动故障转移功能,能够在主节点故障时自动选举新的主节点,保证了系统的高可用性。
- 社区和生态系统:MongoDB拥有庞大的社区支持和丰富的生态系统,有大量的第三方工具和驱动程序可供选择,使开发更加便捷。
- 容易学习和使用:MongoDB的查询语言和操作方式相对简单,开发人员可以很快上手。
缺点:
- 不支持事务:MongoDB在某些情况下不支持事务,虽然在较新的版本中引入了部分事务支持,但仍不适用于所有场景,这使得它在需要强一致性和事务支持的应用中有限制。
- 较大的存储空间需求:由于MongoDB的文档存储方式,它在某些情况下可能需要更多的存储空间,尤其是在包含大量小文档的集合中。
- 复杂的查询性能:复杂查询可能会导致性能下降,特别是在没有正确建立索引的情况下。
- 缺乏严格的模式:虽然文档模型的灵活性是MongoDB的优点之一,但也可能导致数据的不一致性,因为没有严格的模式来限制数据的结构。
- 不适合关系型数据:如果应用程序需要复杂的关系型数据操作(如多表连接),MongoDB可能不是最佳选择。
总的来说,MongoDB是一款强大的NoSQL数据库,适用于众多应用场景,特别是需要处理大规模和半结构化数据的应用。但开发人员需要根据具体需求和应用场景来权衡其优势和限制,以确定是否是最适合的数据库解决方案。在一些情况下,MongoDB也可以与关系型数据库结合使用,以充分利用其各自的优势。
Mongo的Docker环境准备
-
先根据官网安装docker和docker-compose环境
-
docker-compose.yml编写
yml
version: '3'
services:
mongodb-container:
image: mongo:6.0
container_name: mongodb-container
environment:
TZ: Asia/Shanghai
MONGO_INITDB_ROOT_USERNAME: root
MONGO_INITDB_ROOT_PASSWORD: 123456
volumes:
- "./data2/mongo_data:/data/db"
ports:
- 27017:27017
然后在当前文件夹路径下,执行下面的命令,运行容器。
shall
docker-compose up -d
- 连接环境测试
我用的图形化界面是MongoDB Compass,大家可以根据自己的喜好来选择图形化界面。效果如下:
导入Mongoose
好了废话不多说了,直接导入Mongoose
- 安装依赖
css
pnpm i @nestjs/mongoose mongoose
- app.module.ts
ts
import { Module } from '@nestjs/common';
import { MongooseModule } from '@nestjs/mongoose';
import { ConfigModule, ConfigService } from '@nestjs/config';
@Module({
imports: [
// ...
MongooseModule.forRootAsync({
imports: [ConfigModule],
useFactory: async (configService: ConfigService) => {
const uri = `mongodb://${configService.get('MONGO_CONFIG').HOST}:27017`;
return {
uri,
dbName: configService.get('MONGO_CONFIG').DB_NAME,
auth: {
username: configService.get('MONGO_CONFIG').USER,
password: configService.get('MONGO_CONFIG').PASSWORD,
},
};
},
inject: [ConfigService],
}),
// ...
],
controllers: [],
providers: [
// ...
],
})
export class AppModule {}
-
MongooseModule.forRootAsync
:这是用于配置MongoDB连接的关键部分。imports: [ConfigModule]
:首先,从ConfigModule
中导入配置服务,以便在后续配置中使用。useFactory
:这个选项定义了MongoDB连接的配置工厂函数。它接受configService
参数,用于从配置文件中获取连接参数。inject: [ConfigService]
:这里将ConfigService
注入到工厂函数中,以便在工厂函数中访问配置服务。uri
:这里通过configService
获取了MongoDB连接URI,包括主机名、端口和认证信息。dbName
:从配置中获取了MongoDB的数据库名称。auth
:通过配置获取了MongoDB的认证信息,包括用户名和密码。
- 配置文件 .dev.yml
注意用户名和密码必须和docker-compose.yml
里面的一致。
yml
MONGO_CONFIG:
HOST: 'localhost'
USER: 'root'
PASSWORD: '123456'
DB_NAME: 'diary(你想要设定的数据库名字)'
配置文件位置
别忘了修改nest-cli.json
(不多做解释了,看过我之前文章的应该都知道)
json
{
"$schema": "https://json.schemastore.org/nest-cli",
"collection": "@nestjs/schematics",
"sourceRoot": "src",
"compilerOptions": {
"deleteOutDir": true,
// 加入下面这句
"assets": [{ "include": "./config/*.yaml", "outDir": "./dist" }]
}
}
schema或者entity
ts
import { Prop, Schema, SchemaFactory } from '@nestjs/mongoose';
import { Document } from 'mongoose';
export type UserDocument = User & Document;
enum UserStatus {
ACTIVE = 1,
INACTIVE = 2,
// ...其他状态
}
@Schema({
// 使用timestamps自动生成createdAt和updatedAt字段
timestamps: true,
// toJSON要把密码字段隐藏
toJSON: {
transform: (_, ret) => {
delete ret.__v;
delete ret.password;
},
},
})
export class User {
@Prop({ description: '用户名' })
username: string;
@Prop({ description: '加密的密码' })
password: string;
@Prop({ enum: UserStatus, description: '用户状态' })
status: UserStatus;
}
export const UserSchema = SchemaFactory.createForClass(User);
在提供的代码示例中,使用了NestJS中的@nestjs/mongoose
库来创建MongoDB的模式(Schema)以及mongoose
库中的Document
类。下面是对代码示例中的关键部分的解释:
-
导入依赖:
Prop
:这是来自@nestjs/mongoose
库的装饰器,用于将属性标记为MongoDB模式的字段。Schema
:同样来自@nestjs/mongoose
库的装饰器,用于定义MongoDB模式的元数据。SchemaFactory
:用于创建MongoDB模式的工厂类。Document
:来自mongoose
库,表示MongoDB文档的基本类。
-
UserDocument类型:
UserDocument
是一个类型别名,用于表示MongoDB中用户文档的类型。它是User
类和Document
类的交集,表示用户文档的结构。
-
@Schema装饰器:
-
@Schema
装饰器用于将元数据附加到User
类,以定义MongoDB模式的行为。 -
timestamps: true
:这个选项会自动在文档中创建createdAt
和updatedAt
字段,用于记录文档的创建和更新时间。 -
toJSON
:这个选项定义了在将文档转换为JSON格式时要执行的操作。transform
函数:在这里,它删除了__v
字段和password
字段,以确保密码不会在JSON表示中暴露。
-
-
User类:
-
User
类是用于定义MongoDB文档模式的类。它包含了文档的各个字段和它们的描述。 -
@Prop
装饰器用于定义每个字段的属性。- 例如,
@Prop({ description: '用户名' })
定义了username
字段,并提供了一个描述。
- 例如,
-
-
UserSchema:
UserSchema
是通过SchemaFactory.createForClass(User)
创建的MongoDB模式,它将User
类转化为MongoDB的模式对象。这个模式可以用于在MongoDB数据库中创建和操作文档。
总结:上述代码示例演示了如何使用NestJS和@nestjs/mongoose
库来定义MongoDB文档的模式,包括字段的定义、枚举类型的使用、自动生成时间戳、字段的隐藏等操作。这种方式使得开发人员可以轻松地与MongoDB数据库交互,并在NestJS应用程序中使用这些模式来操作数据库文档。
mongo语法介绍
网上找了搜索下Mongoose基础入门相关的内容,这方面的资料有很多。
如果你不想细看,以下是一些MongoDB查询语法的简单示例,以及在代码中如何使用它们:
-
查询所有用户:
tsconst allUsers = await this.userModel.find().lean();
-
根据条件查询用户:
-
查询具有特定用户名的用户:
tsconst user = await this.userModel.findOne({ username }).lean();
-
查询具有特定属性值的用户(例如,查询所有年龄大于30的用户):
tsconst users = await this.userModel.find({ age: { $gt: 30 } }).lean();
-
使用逻辑操作符进行复杂查询(例如,查询用户名为 "John" 且年龄大于25的用户):
tsconst users = await this.userModel.find({ username: "John", age: { $gt: 25 } }).lean();
-
-
根据ID查询用户:
tsconst user = await this.userModel.findById(id).lean();
-
对查询结果进行排序:
-
按照特定字段升序排序(例如,按照用户名升序排序):
tsconst users = await this.userModel.find().sort({ username: 1 }).lean();
-
按照特定字段降序排序(例如,按照年龄降序排序):
tsconst users = await this.userModel.find().sort({ age: -1 }).lean();
-
-
限制查询结果数量:
-
查询前n个用户(例如,查询前5个用户):
tsconst users = await this.userModel.find().limit(5).lean();
-
-
跳过查询结果的前几项:
-
跳过前n个用户,返回剩余的结果(例如,跳过前10个用户,返回从第11个用户开始的结果):
tsconst users = await this.userModel.find().skip(10).lean();
-
这些只是MongoDB查询语法的一些基本示例。你可以根据具体的业务需求和条件来构建更复杂的查询。在上述代码中,this.userModel
是Mongoose的模型实例,你可以使用模型的方法来执行这些查询。lean()
方法用于将查询结果转换为普通JavaScript对象,而不是Mongoose文档对象。
user模块以及Auth模块做成
- user.controller.ts
ts
import { Controller, Post, Body } from '@nestjs/common';
import { UserService } from './user.service';
import { CreateUserDto } from './dto/create-user.dto';
import { SwaggerDocumentation } from '../decorator/swagger.decorator';
// import { Public } from '../guards';
@Controller('user')
export class UserController {
constructor(private readonly userService: UserService) {}
@Post('create')
// 这个接口我不想暴露出去,这里偷个懒,想要调用这个接口且不想带上token的情况下,把这个注释打开
// @Public()
@SwaggerDocumentation('创建用户', '返回创建结果', 'bad request例子', String)
create(@Body() createUserDto: CreateUserDto) {
return this.userService.create(createUserDto);
}
}
- user.module.tss
ts
import { Module } from '@nestjs/common';
import { UserService } from './user.service';
import { UserController } from './user.controller';
import { MongooseModule } from '@nestjs/mongoose';
import { User, UserSchema } from './entities/user.entity';
@Module({
// 导入Mongoose
imports: [
MongooseModule.forFeature([{ name: User.name, schema: UserSchema }]),
],
controllers: [UserController],
providers: [UserService],
exports: [UserService],
})
export class UserModule {}
- user.service.ts
ts
import { Injectable } from '@nestjs/common';
import { CreateUserDto } from './dto/create-user.dto';
import { BusinessException } from '../exceptions/business.exception';
import { InjectModel } from '@nestjs/mongoose';
import { User } from './entities/user.entity';
import { Model } from 'mongoose';
import * as bcrypt from 'bcryptjs';
@Injectable()
export class UserService {
constructor(@InjectModel(User.name) private userModel: Model<User>) {}
/**
* 创建用户
* @param createUserDto
* @returns
*/
async create(createUserDto: CreateUserDto) {
let createdUser;
const encryptedPassword = this.encodePassword(createUserDto.password);
const userToCreate = {
...createUserDto,
password: encryptedPassword,
};
createdUser = await this.userModel.create(userToCreate);
if (!createdUser) {
throw new BusinessException('用户创建失败');
}
return createdUser;
}
// 对用户密码进行编码
public encodePassword(password: string): string {
const salt = bcrypt.genSaltSync(10);
return bcrypt.hashSync(password, salt);
}
findOne(username: string): Promise<User | null> {
return this.userModel.findOne({ username }).lean();
}
findById(id: string): Promise<User | null> {
return this.userModel.findById(id).lean();
}
}
提供的代码示例是用于处理用户相关操作的UserService
服务。以下是与MongoDB语法相关的部分:
-
创建用户(create方法) :
- 在
create
方法中,通过this.userModel.create(userToCreate)
使用MongoDB模型来创建一个新用户文档。userToCreate
是一个包含用户信息的对象。
- 在
-
对用户密码进行编码(encodePassword方法) :
encodePassword
方法使用了bcrypt
库来对用户密码进行哈希加密,以确保密码的安全性。这不仅是一种安全最佳实践,还是与MongoDB中存储敏感数据相关的操作。
-
findOne方法:
findOne
方法使用了MongoDB的查询语法,通过this.userModel.findOne({ username })
来查找具有指定用户名的用户文档。这是一个基本的MongoDB查询操作,通过提供查询条件来查找文档。
-
findById方法:
findById
方法类似于findOne
,但是它使用文档的唯一标识符(通常是文档的ID)来查找文档。它使用this.userModel.findById(id)
来执行查询。
总结:虽然上述代码示例的主要重点是NestJS框架的用法,但它还包含了与MongoDB集成相关的操作,包括创建、查询用户文档以及对用户密码进行哈希加密。这些MongoDB操作是基本的CRUD(创建、读取、更新、删除)操作,用于在应用程序中管理用户数据。
一些补充
-
关于加密我这里用的是
bcryptjs
库而不是bcrypt
,纯js的库对性能会有损耗,bcrypt
与环境有关联,所以必须通过pnpm install
或者npm install
来安装。我发布的时候希望把自己本地依赖压缩一下直接放上去,所以就直接bcryptjs
了,因为和环境的关联性不强,并且我的项目并没有性能上的要求。 -
auth
模块和guard
,麻烦大家看下接口防护这篇文章,里面内容基本差不多。重复内容我就不写了。 -
最后用
Postman
来测试下接口,user如果没有用户的话,大家可以把@Public()
这个注释打开,然后调用接口加一个用户进去,我这里现在还没创建用户,所以验证不通过
加一个用户进去以后
总结
这一章向大家简单介绍了下mongo的接入方法,mongo的一些基础语法。随着后面的文章继续深入,还会介绍更多的语法。接着,介绍了user模块和Auth模块来完成接口防护的工作。谢谢大家的支持,如果这篇文章对您有启发,请多多点赞加评论🙏!