背景痛点
在上一篇文章中,我们通过 NestJS+WebSocket 实现了骰子游戏的毫秒级状态同步,但遗留了一个致命问题:
❗ 「当服务器重启时,所有房间状态瞬间蒸发!」
- 房主开盅瞬间断电 → 本局游戏数据全丢失
- 突发流量导致服务崩溃 → 玩家被迫回到初始页面
- 运维更新版本 → 在线用户集体掉线且无法恢复
这暴露了纯内存方案的脆弱性------实时通信必须与持久化存储双剑合璧,才能真正支撑线上业务。
技术演进图
lua
[ 旧方案 ] ➜ [ 新架构 ]
+---------------------+ +---------------------+
| WebSocket服务 | | WebSocket服务 |
| (内存存储房间状态) | | (状态实时同步) |
+----------+----------+ +----------+----------+
| |
| ▼
+----------+----------+ +---------------------+
| 致命弱点 | | MongoDB集群 |
| ➤ 服务重启=数据清零 | | ➤ 持久化房间快照 |
| ➤ 无法追溯历史对局 | | ➤ 崩溃自动恢复 |
+---------------------+ +---------------------+
所以我们要实现数据的持久化存储,这样就不会影响现有用户的一些数据的丢失
实现
还是老样子 通过脚手架新建一个 Nest
项目
shell
nest new mongo-demo
安装对应依赖
shell
npm i @nestjs/mongoose mongoose
需要在 app.module.ts
配置如下来连接 MongoDB
js
import { Module } from '@nestjs/common';
import { AppController } from './app.controller';
import { AppService } from './app.service';
import { MongooseModule } from "@nestjs/mongoose";
@Module({
imports: [
MongooseModule.forRoot("mongodb://username:password@localhost:27017/databaseName"), // 主要是这里
],
controllers: [AppController],
providers: [AppService],
})
export class AppModule {}
千万要注意 forRoot
里的内容,就是需要 MongoDB
对应的 账号密码,域名及库名 ,否则运行会报错。
如果信息内容都填对了,却还是没连上,就是有可能你的 MongoDB
服务没开,又或者被防火墙拦了,需要开放对应的端口,默认是 '27017'
然后运行你的服务,若显示如下,说明连接成功了

在 NestJS 中,Schema(模式) 是用于定义数据模型结构和约束的核心工具,尤其在集成 MongoDB(通过 Mongoose 模块)时起到关键作用。 以用户为例 我们定义一下数据模型结构
js
// user.schema.ts
import { HydratedDocument } from "mongoose";
import { Prop, Schema, SchemaFactory } from "@nestjs/mongoose";
export type UserDocument = HydratedDocument<User>;
@Schema()
export class User {
@Prop()
_id: string;
@Prop()
username: string;
@Prop()
avatar_url: string;
}
export const UserSchema = SchemaFactory.createForClass(User);
再创建一个 Service
类,实现数据的增删查改操作
js
// user.service.ts
import { Injectable } from '@nestjs/common';
import { InjectModel } from '@nestjs/mongoose';
import { Model } from 'mongoose';
import { User, UserDocument } from './user.schema';
@Injectable()
export class UserService {
constructor(@InjectModel(User.name) private userModel: Model<UserDocument>) {}
async create(data: any): Promise<User> {
return this.userModel.create(data);
}
async findAll(data: object = {}): Promise<User[]> {
return this.userModel.find(data).exec();
}
async findOne(id: string): Promise<User> {
return this.userModel.findById(id).exec();
}
async update(id: string, data: any): Promise<User> {
return this.userModel
.findByIdAndUpdate(id, data, { new: true })
.exec();
}
async remove(id: string): Promise<User> {
return this.userModel.findByIdAndDelete(id).exec();
}
}
然后在 app.module.vue
添加对应 schema
和 service
,这样就可以在全局使用它们。
js
import { Module } from '@nestjs/common';
import { AppController } from './app.controller';
import { AppService } from './app.service';
import { MongooseModule } from "@nestjs/mongoose";
import { User, UserSchema } from './module/user/user.schema';
import { UserService } from './module/user/user.service';
@Module({
imports: [
MongooseModule.forRoot("mongodb://dice:[email protected]:27017/dice"),
MongooseModule.forFeature([{ name: User.name, schema: UserSchema }]), // 新增
],
controllers: [AppController],
providers: [AppService, UserService], // 新增
})
export class AppModule {}
接下来 我们把对应的操作通过接口实现,那就是 controller
和 service
的代码实现,一般情况下会创建对应的 module
,在里面写对应的 controller
和 service
,这里为了方便测试,我们直接在app的控制层和服务层写对应的代码。
js
// app.controller
import { Body, Controller, Get, Post, Query, Delete } from '@nestjs/common';
import { AppService } from './app.service';
@Controller()
export class AppController {
constructor(private readonly appService: AppService) {}
@Get('getUsers')
getUsers() {
return this.appService.getUsers();
}
@Post('createUser')
createUser(@Body() body) {
return this.appService.createUser(body);
}
@Post('updateUser')
updateUser(@Query('id') id, @Body() body) {
return this.appService.updateUser(id, body);
}
@Delete('deleteUser')
deleteUser(@Body('id') id) {
return this.appService.deleteUser(id);
}
}
控制层就是写对应的接口的类型,路径,括号里面对应的就是接口的路径,这里不赘述
💡实际开发优化:使用DTO进行参数校验
js
// app.service
import { Injectable } from '@nestjs/common';
import { UserService } from './module/user/user.service';
@Injectable()
export class AppService {
constructor(private readonly userService: UserService) {}
async getUsers() {
const res = await this.userService.findAll()
return { code: 200, msg: 'success', data: res }
}
createUser(data) {
this.userService.create(data)
return { code: 200, msg: 'success' }
}
async updateUser(id, data) {
const res = await this.userService.update(id, data)
return { code: 200, msg: 'success', data: res }
}
async deleteUser(id) {
const res = await this.userService.remove(id)
return { code: 200, msg: 'success', data: res }
}
}
💡实际开发优化 :通过try catch
添加错误处理与类型校验
Postman测试结果展示
我们打开postman
来试一下

创建用户成功
我们来查询一下

我们将用户名改成 "笨鸟先飞"

再把该用户删除掉

总结
作为一个前端,肯定有想过自己做后端,我的学习动力全是开发驱动学习,当你学习不下的时候,就可以找一些感兴趣的东西,比如我感兴趣 钱
,相信大家都感兴趣,然后看到好多人通过小程序的广告挣钱,于是就想着开发小程序。
某天刚好朋友教会了我摇骰子作为导火索,看起来好像挺多人玩,于是我就决定自己开发一款有自己设计的小程序,从最开始的第一步 在2d平面实现3d效果的骰子,再到实现 联机功能,最后成果出炉:小程序 骰友局
(可微信搜索体验)
。虽然现在做的小程序还不是那么完美,但当你回看走过的路和成果,即使现在小程序收益每天几分钱,但这个学习的过程才是巨大的收获。
前面说了一大堆题外话,这篇文章也是自己在开发中的学习记录的过程,很适合新手学习 Nest
进行开发,因为自己在开发的时候,踩过一些坑,很多文章没有提及到的点,自己都有写出来。后续有时间还会继续更新 Nest
相关的文章。