【手写OSS】第2章:数据库与缓存系统(MongoDB+Redis)

288 阅读7分钟

本章将详细介绍如何安装和配置MongoDB和Redis,并在Node.js项目中使用它们,以确保我们的对象存储系统能够高效、稳定地运行。

首先,我们将从MongoDB开始,介绍其安装步骤、配置用户和权限的方法,以及如何使用Mongoose在Node.js中连接和操作MongoDB。接着,我们会介绍Redis的安装与配置,包括持久化和安全设置,并展示如何在Node.js项目中使用Redis进行缓存操作。

通过本章的学习,读者将能够掌握数据库和缓存系统的基本安装与配置方法,并在项目中灵活应用这些技术,为后续功能模块的实现打下坚实的基础。

2.1 MongoDB的安装与配置

2.1.1 安装MongoDB并配置基本设置
  1. 安装MongoDB

在不同的操作系统上安装MongoDB的步骤略有不同,这里介绍在Windows和Linux上的安装方法。

  • Windows:

    1. 从MongoDB的官方网站下载MongoDB社区版:MongoDB Download Center
    2. 运行下载的安装程序,按照提示完成安装。
    3. 安装完成后,添加MongoDB的bin目录到系统的环境变量中,以便在命令行中直接使用mongomongod命令。
  • Linux (以Ubuntu为例):

    # 导入MongoDB公钥
    wget -qO - https://www.mongodb.org/static/pgp/server-4.4.asc | sudo apt-key add -
    
    # 创建MongoDB源列表文件
    echo "deb [ arch=amd64,arm64 ] https://repo.mongodb.org/apt/ubuntu focal/mongodb-org/4.4 multiverse" | sudo tee /etc/apt/sources.list.d/mongodb-org-4.4.list
    
    # 更新包数据库
    sudo apt-get update
    
    # 安装MongoDB
    sudo apt-get install -y mongodb-org
    
    # 启动MongoDB服务
    sudo systemctl start mongod
    
    # 设置开机自启动
    sudo systemctl enable mongod
    
  1. 配置用户和权限

安装完成后,为了安全性,需要为MongoDB设置用户和权限。

  • 启动MongoDB shell:

    mongo
    
  • 切换到admin数据库并创建管理员用户:

    use admin
    db.createUser({
      user: "admin",
      pwd: "password",
      roles: [ { role: "userAdminAnyDatabase", db: "admin" } ]
    })
    
  • 启用认证: 打开MongoDB配置文件(通常在/etc/mongod.conf),找到并修改如下内容:

    security:
      authorization: enabled
    
  • 重启MongoDB服务:

    sudo systemctl restart mongod
    
  • 使用管理员用户登录:

    mongo -u admin -p password --authenticationDatabase admin
    

注意!这搞起来太费劲,小编我啊直接用k8s+helm安装啦~~~

2.1.2 使用Mongoose连接MongoDB
  1. 安装Mongoose

在Node.js项目中安装Mongoose:

npm install mongoose @types/mongoose
  1. 定义基础Model类

创建一个基础Model类,包含通用字段和操作。在项目的models目录下创建BaseModel.ts文件:

/* eslint-disable @typescript-eslint/no-explicit-any */
import mongoose, {
  Schema,
  Document,
  Model,
  FilterQuery,
  QueryOptions,
} from "mongoose";

// 定义接口
interface IBaseDocument extends Document {
  createdAt: Date;
  updatedAt: Date;
  deletedAt: Date | null;
  createdBy: mongoose.Types.ObjectId | null;
  updatedBy: mongoose.Types.ObjectId | null;
  version: number;
}

// 定义基础Schema
const baseSchema = new Schema<IBaseDocument>({
  createdAt: {
    type: Date,
    default: Date.now,
  },
  updatedAt: {
    type: Date,
    default: Date.now,
  },
  deletedAt: {
    type: Date,
    default: null,
  },
  createdBy: {
    type: mongoose.Types.ObjectId,
    ref: "User",
  },
  updatedBy: {
    type: mongoose.Types.ObjectId,
    ref: "User",
  },
  version: {
    type: Number,
    default: 0,
  },
});

// 中间件:保存前更新updatedAt和版本号
baseSchema.pre<IBaseDocument>("save", function (next) {
  this.updatedAt = new Date();
  this.version += 1;
  next();
});

// 封装基础Model类
class BaseModel<T extends Document> {
  private model: Model<T>;

  constructor(
    modelName: string,
    schemaDef: mongoose.SchemaDefinition,
    options: mongoose.SchemaOptions = {}
  ) {
    // 合并基础Schema和自定义Schema
    const schema = new Schema(
      {
        ...schemaDef,
        ...baseSchema.obj,
      },
      options
    );

    this.model = mongoose.model<T>(modelName, schema);
  }

  // 创建文档
  async create(doc: Partial<T>, userId: mongoose.Types.ObjectId): Promise<T> {
    (doc as any).createdBy = userId;
    (doc as any).updatedBy = userId;
    return await this.model.create(doc);
  }

  // 更新文档
  async update(
    id: mongoose.Types.ObjectId,
    update: Partial<T>,
    userId: mongoose.Types.ObjectId
  ): Promise<T | null> {
    (update as any).updatedBy = userId;
    return await this.model.findByIdAndUpdate(id, update, { new: true }).exec();
  }

  // 删除文档
  async delete(
    id: mongoose.Types.ObjectId,
    userId: mongoose.Types.ObjectId
  ): Promise<T | null> {
    return await this.model
      .findByIdAndUpdate(
        id,
        {
          deletedAt: new Date(),
          updatedBy: userId,
        },
        { new: true }
      )
      .exec();
  }

  // 查询文档
  async find(query: FilterQuery<T>, options: QueryOptions = {}) {
    return await this.model.find(query, options).exec();
  }

  // 根据ID查询文档
  async findById(
    id: mongoose.Types.ObjectId,
    options: QueryOptions = {}
  ): Promise<T | null> {
    return await this.model.findById(id, options).exec();
  }
}

export default BaseModel;

  1. 定义具体的Model

在项目的models目录下创建具体的Model文件,例如UserModel.ts:

import BaseModel from './BaseModel';
import mongoose, { Document } from 'mongoose';

// 定义用户接口
interface IUser extends Document {
  username: string;
  password: string;
  email: string;
}

// 定义用户Schema
const userSchema = {
  username: { type: String, required: true, unique: true },
  password: { type: String, required: true },
  email: { type: String, required: true, unique: true },
};

// 创建用户Model
class UserModel extends BaseModel<IUser> {
  constructor() {
    super('User', userSchema);
  }
}

export default new UserModel();
  1. 连接MongoDB

新建src/dao/mongodb.ts,写入以下代码。在项目的主文件(例如app.jsserver.js)中,使用initializeMongoose连接MongoDB:

import mongoose, { ConnectOptions } from "mongoose";

const mongoUrl = "mongodb://admin:password@localhost:27017/mydatabase";

// 定义 MongoDB 连接选项
const options: ConnectOptions = {
  connectTimeoutMS: 10000, // 连接超时时间(毫秒)
  socketTimeoutMS: 45000, // 套接字超时时间(毫秒)
  maxPoolSize: 10, // 连接池最大连接数
  minPoolSize: 5, // 连接池最小连接数
  autoIndex: true, // 是否自动创建索引
  retryWrites: true, // 启用可重试写入
  w: "majority", // 写入确认级别
  readPreference: "primary", // 读取偏好设置
  authSource: "admin", // 鉴权数据库
  //   tls: true, // 启用 TLS/SSL 连接
  //   tlsAllowInvalidCertificates: false, // 是否允许无效的 TLS/SSL 证书
  //   tlsAllowInvalidHostnames: false, // 是否允许无效的 TLS/SSL 主机名
};

// 连接MongoDB
export const initializeMongoose = () => {
  mongoose
    .connect(mongoUrl, options)
    .then(() => {
      console.log("MongoDB 连接成功...");
    })
    .catch((err) => {
      console.error("MongoDB 连接错误:", err);
    });

  // 监听连接事件
  mongoose.connection.on("connected", () => {
    console.log("Mongoose 已连接到数据库");
  });

  mongoose.connection.on("error", (err) => {
    console.error("Mongoose 连接错误:", err);
  });

  mongoose.connection.on("disconnected", () => {
    console.log("Mongoose 已断开与数据库的连接");
  });

  // 处理应用程序终止事件
  process.on("SIGINT", async () => {
    await mongoose.connection.close();
    console.log("应用程序终止,Mongoose 已断开连接");
    process.exit(0);
  });
};

注意!注意!注意!

在 Mongoose 6.x 中,某些选项(如 useCreateIndexuseFindAndModify)已经被移除,因为它们的默认行为已经改变以更符合现代 MongoDB 驱动的标准。需要深入使用的话,建议先看看最新文档~

2.2 Redis的安装与配置

2.2.1 安装Redis并配置基本设置
  1. 安装Redis

在不同的操作系统上安装Redis的步骤略有不同,这里介绍在Windows和Linux上的安装方法。

  • Windows:

    1. Microsoft Open Tech Redis下载Redis安装包。
    2. 解压安装包,并运行redis-server.exe启动Redis服务器。
  • Linux (以Ubuntu为例):

    # 更新包数据库
    sudo apt-get update
    
    # 安装Redis
    sudo apt-get install -y redis-server
    
    # 启动Redis服务
    sudo systemctl start redis-server
    
    # 设置开机自启动
    sudo systemctl enable redis-server
    
  1. 配置持久化和安全设置
  • 打开Redis配置文件(通常在/etc/redis/redis.conf),找到并修改如下内容:

    # 启用持久化
    appendonly yes
    
    # 设置密码
    requirepass yourpassword
    
  • 重启Redis服务:

    sudo systemctl restart redis-server
    

没错,又是k8s+helm 两行就装上了~~~

2.2.2 使用Node.js连接Redis
  1. 安装ioredis模块

在Node.js项目中安装ioredis模块:

npm install ioredis
  1. 进行基本的缓存操作

在项目中创建一个文件,例如dao/redis.ts,配置Redis客户端并进行基本的缓存操作:

import Redis from "ioredis";

// 创建 Redis 客户端
const client = new Redis({
  host: "localhost",
  port: 6379,
  password: "yourpassword",
});

// 连接 Redis
client.on("connect", () => {
  console.log("已连接到 Redis...");
});

// 处理连接错误
client.on("error", (err) => {
  console.error("Redis 连接错误:", err);
});
// 导出 Redis 客户端
export default client;
  1. 在项目中使用Redis

src/cache/test.ts中使用Redis缓存的地方引入Redis客户端,例如在用户登录验证中缓存用户会话:

import { getCache, setCache } from "../dao/redis";

export const testCache = async () => {
  // 设置缓存
  await setCache("key", "value", 60 * 60);

  // 获取缓存
  const value = await getCache("key");
  if (value) {
    console.log("缓存值:", value);
  } else {
    console.log("缓存不存在或已过期");
  }
};

重新运行项目

/home/ORE$ npm run dev

> ore@0.0.1 dev
> ts-node-dev src/index.ts

[INFO] 12:33:13 ts-node-dev ver. 2.0.0 (using ts-node ver. 10.9.2, typescript ver. 5.5.3)
Server is running on http://localhost:3000
已连接到 Redis...
Mongoose 已连接到数据库
MongoDB 连接成功...
缓存设置成功: key
缓存值: value
缓存值: value

总结

通过本章的学习,我们成功地完成了MongoDB和Redis的安装与配置,并在Node.js项目中实现了它们的基本使用。MongoDB为我们的对象存储系统提供了强大的数据存储和管理功能,而Redis则通过高效的缓存机制显著提升了系统的性能与响应速度。

我们不仅详细讲解了如何在不同操作系统上安装和配置这些数据库与缓存系统,还介绍了如何使用Mongoose和ioredis在Node.js项目中进行数据操作和缓存管理。这些知识和技能将为我们后续开发项目中的其他功能模块打下坚实的基础。

接下来,我们将进入第3章,重点实现用户管理模块,包括用户注册、登录、认证与授权等功能,为我们的对象存储系统进一步完善用户交互和安全机制。