feat(database): add database layer architecture (#410)
- Add @esengine/database-drivers for MongoDB/Redis connection management - Add @esengine/database for Repository pattern with CRUD, pagination, soft delete - Refactor @esengine/transaction MongoStorage to use shared connection - Add comprehensive documentation in Chinese and English
This commit is contained in:
136
docs/src/content/docs/en/modules/database-drivers/index.md
Normal file
136
docs/src/content/docs/en/modules/database-drivers/index.md
Normal file
@@ -0,0 +1,136 @@
|
||||
---
|
||||
title: "Database Drivers"
|
||||
description: "MongoDB, Redis connection management and driver abstraction"
|
||||
---
|
||||
|
||||
`@esengine/database-drivers` is ESEngine's database connection management layer, providing unified connection management for MongoDB, Redis, and more.
|
||||
|
||||
## Features
|
||||
|
||||
- **Connection Pool** - Automatic connection pool management
|
||||
- **Auto Reconnect** - Automatic reconnection on disconnect
|
||||
- **Event Notification** - Connection state change events
|
||||
- **Type Decoupling** - Simplified interfaces, no dependency on native driver types
|
||||
- **Shared Connections** - Single connection shared across modules
|
||||
|
||||
## Installation
|
||||
|
||||
```bash
|
||||
npm install @esengine/database-drivers
|
||||
```
|
||||
|
||||
**Peer Dependencies:**
|
||||
```bash
|
||||
npm install mongodb # For MongoDB support
|
||||
npm install ioredis # For Redis support
|
||||
```
|
||||
|
||||
## Architecture
|
||||
|
||||
```
|
||||
┌─────────────────────────────────────────────────────────────────┐
|
||||
│ @esengine/database-drivers (Layer 1) │
|
||||
│ ┌─────────────────────┐ ┌─────────────────────┐ │
|
||||
│ │ MongoConnection │ │ RedisConnection │ │
|
||||
│ │ - Pool management │ │ - Auto-reconnect │ │
|
||||
│ │ - Auto-reconnect │ │ - Key prefix │ │
|
||||
│ │ - Event emitter │ │ - Event emitter │ │
|
||||
│ └──────────┬──────────┘ └─────────────────────┘ │
|
||||
│ │ │
|
||||
│ ┌──────────▼──────────┐ │
|
||||
│ │ IMongoCollection<T> │ ← Type-safe interface │
|
||||
│ │ (Adapter pattern) │ decoupled from mongodb types │
|
||||
│ └─────────────────────┘ │
|
||||
└─────────────────────────────────────────────────────────────────┘
|
||||
│
|
||||
▼
|
||||
┌───────────────────────┐ ┌───────────────────────┐
|
||||
│ @esengine/database │ │ @esengine/transaction │
|
||||
│ (Repository pattern) │ │ (Distributed tx) │
|
||||
└───────────────────────┘ └───────────────────────┘
|
||||
```
|
||||
|
||||
## Quick Start
|
||||
|
||||
### MongoDB Connection
|
||||
|
||||
```typescript
|
||||
import { createMongoConnection } from '@esengine/database-drivers'
|
||||
|
||||
// Create connection
|
||||
const mongo = createMongoConnection({
|
||||
uri: 'mongodb://localhost:27017',
|
||||
database: 'game',
|
||||
pool: {
|
||||
minSize: 5,
|
||||
maxSize: 20
|
||||
},
|
||||
autoReconnect: true
|
||||
})
|
||||
|
||||
// Listen to events
|
||||
mongo.on('connected', () => console.log('MongoDB connected'))
|
||||
mongo.on('disconnected', () => console.log('MongoDB disconnected'))
|
||||
mongo.on('error', (e) => console.error('Error:', e.error))
|
||||
|
||||
// Connect
|
||||
await mongo.connect()
|
||||
|
||||
// Use collections
|
||||
const users = mongo.collection<User>('users')
|
||||
await users.insertOne({ name: 'John', score: 100 })
|
||||
|
||||
const user = await users.findOne({ name: 'John' })
|
||||
|
||||
// Disconnect when done
|
||||
await mongo.disconnect()
|
||||
```
|
||||
|
||||
### Redis Connection
|
||||
|
||||
```typescript
|
||||
import { createRedisConnection } from '@esengine/database-drivers'
|
||||
|
||||
const redis = createRedisConnection({
|
||||
host: 'localhost',
|
||||
port: 6379,
|
||||
keyPrefix: 'game:',
|
||||
autoReconnect: true
|
||||
})
|
||||
|
||||
await redis.connect()
|
||||
|
||||
// Basic operations
|
||||
await redis.set('session:123', 'data', 3600) // With TTL
|
||||
const value = await redis.get('session:123')
|
||||
|
||||
await redis.disconnect()
|
||||
```
|
||||
|
||||
## Service Container Integration
|
||||
|
||||
```typescript
|
||||
import { ServiceContainer } from '@esengine/ecs-framework'
|
||||
import {
|
||||
createMongoConnection,
|
||||
MongoConnectionToken,
|
||||
RedisConnectionToken
|
||||
} from '@esengine/database-drivers'
|
||||
|
||||
const services = new ServiceContainer()
|
||||
|
||||
// Register connections
|
||||
const mongo = createMongoConnection({ uri: '...', database: 'game' })
|
||||
await mongo.connect()
|
||||
services.register(MongoConnectionToken, mongo)
|
||||
|
||||
// Retrieve in other modules
|
||||
const connection = services.get(MongoConnectionToken)
|
||||
const users = connection.collection('users')
|
||||
```
|
||||
|
||||
## Documentation
|
||||
|
||||
- [MongoDB Connection](/en/modules/database-drivers/mongo/) - MongoDB configuration details
|
||||
- [Redis Connection](/en/modules/database-drivers/redis/) - Redis configuration details
|
||||
- [Service Tokens](/en/modules/database-drivers/tokens/) - Dependency injection integration
|
||||
217
docs/src/content/docs/en/modules/database/index.md
Normal file
217
docs/src/content/docs/en/modules/database/index.md
Normal file
@@ -0,0 +1,217 @@
|
||||
---
|
||||
title: "Database Repository"
|
||||
description: "Repository pattern database layer with CRUD, pagination, and soft delete"
|
||||
---
|
||||
|
||||
`@esengine/database` is ESEngine's database operation layer, providing type-safe CRUD operations based on the Repository pattern.
|
||||
|
||||
## Features
|
||||
|
||||
- **Repository Pattern** - Generic CRUD operations with type safety
|
||||
- **Pagination** - Built-in pagination support
|
||||
- **Soft Delete** - Optional soft delete with restore
|
||||
- **User Management** - Ready-to-use UserRepository
|
||||
- **Password Security** - Secure password hashing with scrypt
|
||||
|
||||
## Installation
|
||||
|
||||
```bash
|
||||
npm install @esengine/database @esengine/database-drivers
|
||||
```
|
||||
|
||||
## Quick Start
|
||||
|
||||
### Basic Repository
|
||||
|
||||
```typescript
|
||||
import { createMongoConnection } from '@esengine/database-drivers'
|
||||
import { Repository, createRepository } from '@esengine/database'
|
||||
|
||||
// Define entity
|
||||
interface Player {
|
||||
id: string
|
||||
name: string
|
||||
score: number
|
||||
createdAt: Date
|
||||
updatedAt: Date
|
||||
}
|
||||
|
||||
// Create connection
|
||||
const mongo = createMongoConnection({
|
||||
uri: 'mongodb://localhost:27017',
|
||||
database: 'game'
|
||||
})
|
||||
await mongo.connect()
|
||||
|
||||
// Create repository
|
||||
const playerRepo = createRepository<Player>(mongo, 'players')
|
||||
|
||||
// CRUD operations
|
||||
const player = await playerRepo.create({
|
||||
name: 'John',
|
||||
score: 0
|
||||
})
|
||||
|
||||
const found = await playerRepo.findById(player.id)
|
||||
|
||||
await playerRepo.update(player.id, { score: 100 })
|
||||
|
||||
await playerRepo.delete(player.id)
|
||||
```
|
||||
|
||||
### Custom Repository
|
||||
|
||||
```typescript
|
||||
import { Repository, BaseEntity } from '@esengine/database'
|
||||
import type { IMongoConnection } from '@esengine/database-drivers'
|
||||
|
||||
interface Player extends BaseEntity {
|
||||
name: string
|
||||
score: number
|
||||
rank?: string
|
||||
}
|
||||
|
||||
class PlayerRepository extends Repository<Player> {
|
||||
constructor(connection: IMongoConnection) {
|
||||
super(connection, 'players')
|
||||
}
|
||||
|
||||
async findTopPlayers(limit: number = 10): Promise<Player[]> {
|
||||
return this.findMany({
|
||||
sort: { score: 'desc' },
|
||||
limit
|
||||
})
|
||||
}
|
||||
|
||||
async findByRank(rank: string): Promise<Player[]> {
|
||||
return this.findMany({
|
||||
where: { rank }
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
// Usage
|
||||
const playerRepo = new PlayerRepository(mongo)
|
||||
const topPlayers = await playerRepo.findTopPlayers(5)
|
||||
```
|
||||
|
||||
### User Repository
|
||||
|
||||
```typescript
|
||||
import { UserRepository } from '@esengine/database'
|
||||
|
||||
const userRepo = new UserRepository(mongo)
|
||||
|
||||
// Register new user
|
||||
const user = await userRepo.register({
|
||||
username: 'john',
|
||||
password: 'securePassword123',
|
||||
email: 'john@example.com'
|
||||
})
|
||||
|
||||
// Authenticate
|
||||
const authenticated = await userRepo.authenticate('john', 'securePassword123')
|
||||
if (authenticated) {
|
||||
console.log('Login successful:', authenticated.username)
|
||||
}
|
||||
|
||||
// Change password
|
||||
await userRepo.changePassword(user.id, 'securePassword123', 'newPassword456')
|
||||
|
||||
// Role management
|
||||
await userRepo.addRole(user.id, 'admin')
|
||||
await userRepo.removeRole(user.id, 'admin')
|
||||
|
||||
// Find users
|
||||
const admins = await userRepo.findByRole('admin')
|
||||
const john = await userRepo.findByUsername('john')
|
||||
```
|
||||
|
||||
### Pagination
|
||||
|
||||
```typescript
|
||||
const result = await playerRepo.findPaginated(
|
||||
{ page: 1, pageSize: 20 },
|
||||
{
|
||||
where: { rank: 'gold' },
|
||||
sort: { score: 'desc' }
|
||||
}
|
||||
)
|
||||
|
||||
console.log(result.data) // Player[]
|
||||
console.log(result.total) // Total count
|
||||
console.log(result.totalPages) // Total pages
|
||||
console.log(result.hasNext) // Has next page
|
||||
console.log(result.hasPrev) // Has previous page
|
||||
```
|
||||
|
||||
### Soft Delete
|
||||
|
||||
```typescript
|
||||
// Enable soft delete
|
||||
const playerRepo = createRepository<Player>(mongo, 'players', true)
|
||||
|
||||
// Delete (marks as deleted)
|
||||
await playerRepo.delete(playerId)
|
||||
|
||||
// Find excludes soft-deleted by default
|
||||
const players = await playerRepo.findMany()
|
||||
|
||||
// Include soft-deleted records
|
||||
const allPlayers = await playerRepo.findMany({
|
||||
includeSoftDeleted: true
|
||||
})
|
||||
|
||||
// Restore soft-deleted record
|
||||
await playerRepo.restore(playerId)
|
||||
```
|
||||
|
||||
### Query Options
|
||||
|
||||
```typescript
|
||||
// Complex queries
|
||||
const players = await playerRepo.findMany({
|
||||
where: {
|
||||
score: { $gte: 100 },
|
||||
rank: { $in: ['gold', 'platinum'] },
|
||||
name: { $like: 'John%' }
|
||||
},
|
||||
sort: {
|
||||
score: 'desc',
|
||||
name: 'asc'
|
||||
},
|
||||
limit: 10,
|
||||
offset: 0
|
||||
})
|
||||
|
||||
// OR conditions
|
||||
const players = await playerRepo.findMany({
|
||||
where: {
|
||||
$or: [
|
||||
{ score: { $gte: 1000 } },
|
||||
{ rank: 'legendary' }
|
||||
]
|
||||
}
|
||||
})
|
||||
```
|
||||
|
||||
## Query Operators
|
||||
|
||||
| Operator | Description | Example |
|
||||
|----------|-------------|---------|
|
||||
| `$eq` | Equal | `{ score: { $eq: 100 } }` |
|
||||
| `$ne` | Not equal | `{ status: { $ne: 'banned' } }` |
|
||||
| `$gt` | Greater than | `{ score: { $gt: 50 } }` |
|
||||
| `$gte` | Greater or equal | `{ level: { $gte: 10 } }` |
|
||||
| `$lt` | Less than | `{ age: { $lt: 18 } }` |
|
||||
| `$lte` | Less or equal | `{ price: { $lte: 100 } }` |
|
||||
| `$in` | In array | `{ rank: { $in: ['gold', 'platinum'] } }` |
|
||||
| `$nin` | Not in array | `{ status: { $nin: ['banned'] } }` |
|
||||
| `$like` | Pattern match | `{ name: { $like: '%john%' } }` |
|
||||
| `$regex` | Regex match | `{ email: { $regex: '@gmail.com$' } }` |
|
||||
|
||||
## Documentation
|
||||
|
||||
- [Repository API](/en/modules/database/repository/) - Repository detailed API
|
||||
- [User Management](/en/modules/database/user/) - UserRepository usage
|
||||
- [Query Syntax](/en/modules/database/query/) - Query condition syntax
|
||||
@@ -36,6 +36,13 @@ ESEngine provides a rich set of modules that can be imported as needed.
|
||||
| [Network](/en/modules/network/) | `@esengine/network` | Multiplayer game networking |
|
||||
| [Transaction](/en/modules/transaction/) | `@esengine/transaction` | Game transactions with distributed support |
|
||||
|
||||
### Database
|
||||
|
||||
| Module | Package | Description |
|
||||
|--------|---------|-------------|
|
||||
| [Database Drivers](/en/modules/database-drivers/) | `@esengine/database-drivers` | MongoDB, Redis connection management |
|
||||
| [Database Repository](/en/modules/database/) | `@esengine/database` | Repository pattern data operations |
|
||||
|
||||
## Installation
|
||||
|
||||
All modules can be installed independently:
|
||||
|
||||
@@ -125,23 +125,24 @@ tx:data:{key} - Business data
|
||||
|
||||
## MongoStorage
|
||||
|
||||
MongoDB storage, suitable for scenarios requiring persistence and complex queries. Uses factory pattern with lazy connection.
|
||||
MongoDB storage, suitable for scenarios requiring persistence and complex queries. Uses shared connection from `@esengine/database-drivers`.
|
||||
|
||||
```typescript
|
||||
import { MongoClient } from 'mongodb';
|
||||
import { MongoStorage } from '@esengine/transaction';
|
||||
import { createMongoConnection } from '@esengine/database-drivers';
|
||||
import { createMongoStorage, TransactionManager } from '@esengine/transaction';
|
||||
|
||||
// Factory pattern: lazy connection, connects on first operation
|
||||
const storage = new MongoStorage({
|
||||
factory: async () => {
|
||||
const client = new MongoClient('mongodb://localhost:27017');
|
||||
await client.connect();
|
||||
return client;
|
||||
},
|
||||
database: 'game',
|
||||
transactionCollection: 'transactions', // Transaction log collection
|
||||
dataCollection: 'transaction_data', // Business data collection
|
||||
lockCollection: 'transaction_locks', // Lock collection
|
||||
// Create shared connection
|
||||
const mongo = createMongoConnection({
|
||||
uri: 'mongodb://localhost:27017',
|
||||
database: 'game'
|
||||
});
|
||||
await mongo.connect();
|
||||
|
||||
// Create storage using shared connection
|
||||
const storage = createMongoStorage(mongo, {
|
||||
transactionCollection: 'transactions', // Transaction log collection (optional)
|
||||
dataCollection: 'transaction_data', // Business data collection (optional)
|
||||
lockCollection: 'transaction_locks', // Lock collection (optional)
|
||||
});
|
||||
|
||||
// Create indexes (run on first startup)
|
||||
@@ -149,11 +150,14 @@ await storage.ensureIndexes();
|
||||
|
||||
const manager = new TransactionManager({ storage });
|
||||
|
||||
// Close connection when done
|
||||
// Close storage (does not close shared connection)
|
||||
await storage.close();
|
||||
|
||||
// Or use await using for automatic cleanup (TypeScript 5.2+)
|
||||
await using storage = new MongoStorage({ ... });
|
||||
// Shared connection can continue to be used by other modules
|
||||
const userRepo = new UserRepository(mongo); // @esengine/database
|
||||
|
||||
// Finally close the shared connection
|
||||
await mongo.disconnect();
|
||||
```
|
||||
|
||||
### Characteristics
|
||||
|
||||
136
docs/src/content/docs/modules/database-drivers/index.md
Normal file
136
docs/src/content/docs/modules/database-drivers/index.md
Normal file
@@ -0,0 +1,136 @@
|
||||
---
|
||||
title: "数据库驱动"
|
||||
description: "MongoDB、Redis 等数据库的连接管理和驱动封装"
|
||||
---
|
||||
|
||||
`@esengine/database-drivers` 是 ESEngine 的数据库连接管理层,提供 MongoDB、Redis 等数据库的统一连接管理。
|
||||
|
||||
## 特性
|
||||
|
||||
- **连接池管理** - 自动管理连接池,优化资源使用
|
||||
- **自动重连** - 连接断开时自动重连
|
||||
- **事件通知** - 连接状态变化事件
|
||||
- **类型解耦** - 简化接口,不依赖原生驱动类型
|
||||
- **共享连接** - 单一连接可供多个模块共享
|
||||
|
||||
## 安装
|
||||
|
||||
```bash
|
||||
npm install @esengine/database-drivers
|
||||
```
|
||||
|
||||
**对等依赖:**
|
||||
```bash
|
||||
npm install mongodb # MongoDB 支持
|
||||
npm install ioredis # Redis 支持
|
||||
```
|
||||
|
||||
## 架构
|
||||
|
||||
```
|
||||
┌─────────────────────────────────────────────────────────────────┐
|
||||
│ @esengine/database-drivers (Layer 1) │
|
||||
│ ┌─────────────────────┐ ┌─────────────────────┐ │
|
||||
│ │ MongoConnection │ │ RedisConnection │ │
|
||||
│ │ - 连接池管理 │ │ - 自动重连 │ │
|
||||
│ │ - 自动重连 │ │ - Key 前缀 │ │
|
||||
│ │ - 事件发射器 │ │ - 事件发射器 │ │
|
||||
│ └──────────┬──────────┘ └─────────────────────┘ │
|
||||
│ │ │
|
||||
│ ┌──────────▼──────────┐ │
|
||||
│ │ IMongoCollection<T> │ ← 类型安全接口 │
|
||||
│ │ (适配器模式) │ 与 mongodb 类型解耦 │
|
||||
│ └─────────────────────┘ │
|
||||
└─────────────────────────────────────────────────────────────────┘
|
||||
│
|
||||
▼
|
||||
┌───────────────────────┐ ┌───────────────────────┐
|
||||
│ @esengine/database │ │ @esengine/transaction │
|
||||
│ (仓库模式) │ │ (分布式事务) │
|
||||
└───────────────────────┘ └───────────────────────┘
|
||||
```
|
||||
|
||||
## 快速开始
|
||||
|
||||
### MongoDB 连接
|
||||
|
||||
```typescript
|
||||
import { createMongoConnection } from '@esengine/database-drivers'
|
||||
|
||||
// 创建连接
|
||||
const mongo = createMongoConnection({
|
||||
uri: 'mongodb://localhost:27017',
|
||||
database: 'game',
|
||||
pool: {
|
||||
minSize: 5,
|
||||
maxSize: 20
|
||||
},
|
||||
autoReconnect: true
|
||||
})
|
||||
|
||||
// 监听事件
|
||||
mongo.on('connected', () => console.log('MongoDB 已连接'))
|
||||
mongo.on('disconnected', () => console.log('MongoDB 已断开'))
|
||||
mongo.on('error', (e) => console.error('错误:', e.error))
|
||||
|
||||
// 建立连接
|
||||
await mongo.connect()
|
||||
|
||||
// 使用集合
|
||||
const users = mongo.collection<User>('users')
|
||||
await users.insertOne({ name: 'John', score: 100 })
|
||||
|
||||
const user = await users.findOne({ name: 'John' })
|
||||
|
||||
// 完成后断开连接
|
||||
await mongo.disconnect()
|
||||
```
|
||||
|
||||
### Redis 连接
|
||||
|
||||
```typescript
|
||||
import { createRedisConnection } from '@esengine/database-drivers'
|
||||
|
||||
const redis = createRedisConnection({
|
||||
host: 'localhost',
|
||||
port: 6379,
|
||||
keyPrefix: 'game:',
|
||||
autoReconnect: true
|
||||
})
|
||||
|
||||
await redis.connect()
|
||||
|
||||
// 基本操作
|
||||
await redis.set('session:123', 'data', 3600) // 带 TTL
|
||||
const value = await redis.get('session:123')
|
||||
|
||||
await redis.disconnect()
|
||||
```
|
||||
|
||||
## 服务容器集成
|
||||
|
||||
```typescript
|
||||
import { ServiceContainer } from '@esengine/ecs-framework'
|
||||
import {
|
||||
createMongoConnection,
|
||||
MongoConnectionToken,
|
||||
RedisConnectionToken
|
||||
} from '@esengine/database-drivers'
|
||||
|
||||
const services = new ServiceContainer()
|
||||
|
||||
// 注册连接
|
||||
const mongo = createMongoConnection({ uri: '...', database: 'game' })
|
||||
await mongo.connect()
|
||||
services.register(MongoConnectionToken, mongo)
|
||||
|
||||
// 在其他模块中获取
|
||||
const connection = services.get(MongoConnectionToken)
|
||||
const users = connection.collection('users')
|
||||
```
|
||||
|
||||
## 文档
|
||||
|
||||
- [MongoDB 连接](/modules/database-drivers/mongo/) - MongoDB 连接详细配置
|
||||
- [Redis 连接](/modules/database-drivers/redis/) - Redis 连接详细配置
|
||||
- [服务令牌](/modules/database-drivers/tokens/) - 依赖注入集成
|
||||
265
docs/src/content/docs/modules/database-drivers/mongo.md
Normal file
265
docs/src/content/docs/modules/database-drivers/mongo.md
Normal file
@@ -0,0 +1,265 @@
|
||||
---
|
||||
title: "MongoDB 连接"
|
||||
description: "MongoDB 连接管理、连接池、自动重连"
|
||||
---
|
||||
|
||||
## 配置选项
|
||||
|
||||
```typescript
|
||||
interface MongoConnectionConfig {
|
||||
/** MongoDB 连接 URI */
|
||||
uri: string
|
||||
|
||||
/** 数据库名称 */
|
||||
database: string
|
||||
|
||||
/** 连接池配置 */
|
||||
pool?: {
|
||||
minSize?: number // 最小连接数
|
||||
maxSize?: number // 最大连接数
|
||||
acquireTimeout?: number // 获取连接超时(毫秒)
|
||||
maxLifetime?: number // 连接最大生命周期(毫秒)
|
||||
}
|
||||
|
||||
/** 是否自动重连(默认 true) */
|
||||
autoReconnect?: boolean
|
||||
|
||||
/** 重连间隔(毫秒,默认 5000) */
|
||||
reconnectInterval?: number
|
||||
|
||||
/** 最大重连次数(默认 10) */
|
||||
maxReconnectAttempts?: number
|
||||
}
|
||||
```
|
||||
|
||||
## 完整示例
|
||||
|
||||
```typescript
|
||||
import { createMongoConnection, MongoConnectionToken } from '@esengine/database-drivers'
|
||||
|
||||
const mongo = createMongoConnection({
|
||||
uri: 'mongodb://localhost:27017',
|
||||
database: 'game',
|
||||
pool: {
|
||||
minSize: 5,
|
||||
maxSize: 20,
|
||||
acquireTimeout: 5000,
|
||||
maxLifetime: 300000
|
||||
},
|
||||
autoReconnect: true,
|
||||
reconnectInterval: 5000,
|
||||
maxReconnectAttempts: 10
|
||||
})
|
||||
|
||||
// 事件监听
|
||||
mongo.on('connected', () => {
|
||||
console.log('MongoDB 已连接')
|
||||
})
|
||||
|
||||
mongo.on('disconnected', () => {
|
||||
console.log('MongoDB 已断开')
|
||||
})
|
||||
|
||||
mongo.on('reconnecting', () => {
|
||||
console.log('MongoDB 正在重连...')
|
||||
})
|
||||
|
||||
mongo.on('reconnected', () => {
|
||||
console.log('MongoDB 重连成功')
|
||||
})
|
||||
|
||||
mongo.on('error', (event) => {
|
||||
console.error('MongoDB 错误:', event.error)
|
||||
})
|
||||
|
||||
// 连接
|
||||
await mongo.connect()
|
||||
|
||||
// 检查状态
|
||||
console.log('已连接:', mongo.isConnected())
|
||||
console.log('Ping:', await mongo.ping())
|
||||
```
|
||||
|
||||
## IMongoConnection 接口
|
||||
|
||||
```typescript
|
||||
interface IMongoConnection {
|
||||
/** 连接 ID */
|
||||
readonly id: string
|
||||
|
||||
/** 连接状态 */
|
||||
readonly state: ConnectionState
|
||||
|
||||
/** 建立连接 */
|
||||
connect(): Promise<void>
|
||||
|
||||
/** 断开连接 */
|
||||
disconnect(): Promise<void>
|
||||
|
||||
/** 检查是否已连接 */
|
||||
isConnected(): boolean
|
||||
|
||||
/** 测试连接 */
|
||||
ping(): Promise<boolean>
|
||||
|
||||
/** 获取类型化集合 */
|
||||
collection<T extends object>(name: string): IMongoCollection<T>
|
||||
|
||||
/** 获取数据库接口 */
|
||||
getDatabase(): IMongoDatabase
|
||||
|
||||
/** 获取原生客户端(高级用法) */
|
||||
getNativeClient(): MongoClientType
|
||||
|
||||
/** 获取原生数据库(高级用法) */
|
||||
getNativeDatabase(): Db
|
||||
}
|
||||
```
|
||||
|
||||
## IMongoCollection 接口
|
||||
|
||||
类型安全的集合接口,与原生 MongoDB 类型解耦:
|
||||
|
||||
```typescript
|
||||
interface IMongoCollection<T extends object> {
|
||||
readonly name: string
|
||||
|
||||
// 查询
|
||||
findOne(filter: object, options?: FindOptions): Promise<T | null>
|
||||
find(filter: object, options?: FindOptions): Promise<T[]>
|
||||
countDocuments(filter?: object): Promise<number>
|
||||
|
||||
// 插入
|
||||
insertOne(doc: T): Promise<InsertOneResult>
|
||||
insertMany(docs: T[]): Promise<InsertManyResult>
|
||||
|
||||
// 更新
|
||||
updateOne(filter: object, update: object): Promise<UpdateResult>
|
||||
updateMany(filter: object, update: object): Promise<UpdateResult>
|
||||
findOneAndUpdate(
|
||||
filter: object,
|
||||
update: object,
|
||||
options?: FindOneAndUpdateOptions
|
||||
): Promise<T | null>
|
||||
|
||||
// 删除
|
||||
deleteOne(filter: object): Promise<DeleteResult>
|
||||
deleteMany(filter: object): Promise<DeleteResult>
|
||||
|
||||
// 索引
|
||||
createIndex(
|
||||
spec: Record<string, 1 | -1>,
|
||||
options?: IndexOptions
|
||||
): Promise<string>
|
||||
}
|
||||
```
|
||||
|
||||
## 使用示例
|
||||
|
||||
### 基本 CRUD
|
||||
|
||||
```typescript
|
||||
interface User {
|
||||
id: string
|
||||
name: string
|
||||
email: string
|
||||
score: number
|
||||
}
|
||||
|
||||
const users = mongo.collection<User>('users')
|
||||
|
||||
// 插入
|
||||
await users.insertOne({
|
||||
id: '1',
|
||||
name: 'John',
|
||||
email: 'john@example.com',
|
||||
score: 100
|
||||
})
|
||||
|
||||
// 查询
|
||||
const user = await users.findOne({ name: 'John' })
|
||||
|
||||
const topUsers = await users.find(
|
||||
{ score: { $gte: 100 } },
|
||||
{ sort: { score: -1 }, limit: 10 }
|
||||
)
|
||||
|
||||
// 更新
|
||||
await users.updateOne(
|
||||
{ id: '1' },
|
||||
{ $inc: { score: 10 } }
|
||||
)
|
||||
|
||||
// 删除
|
||||
await users.deleteOne({ id: '1' })
|
||||
```
|
||||
|
||||
### 批量操作
|
||||
|
||||
```typescript
|
||||
// 批量插入
|
||||
await users.insertMany([
|
||||
{ id: '1', name: 'Alice', email: 'alice@example.com', score: 100 },
|
||||
{ id: '2', name: 'Bob', email: 'bob@example.com', score: 200 },
|
||||
{ id: '3', name: 'Carol', email: 'carol@example.com', score: 150 }
|
||||
])
|
||||
|
||||
// 批量更新
|
||||
await users.updateMany(
|
||||
{ score: { $lt: 100 } },
|
||||
{ $set: { status: 'inactive' } }
|
||||
)
|
||||
|
||||
// 批量删除
|
||||
await users.deleteMany({ status: 'inactive' })
|
||||
```
|
||||
|
||||
### 索引管理
|
||||
|
||||
```typescript
|
||||
// 创建索引
|
||||
await users.createIndex({ email: 1 }, { unique: true })
|
||||
await users.createIndex({ score: -1 })
|
||||
await users.createIndex({ name: 1, score: -1 })
|
||||
```
|
||||
|
||||
## 与其他模块集成
|
||||
|
||||
### 与 @esengine/database 集成
|
||||
|
||||
```typescript
|
||||
import { createMongoConnection } from '@esengine/database-drivers'
|
||||
import { UserRepository, createRepository } from '@esengine/database'
|
||||
|
||||
const mongo = createMongoConnection({
|
||||
uri: 'mongodb://localhost:27017',
|
||||
database: 'game'
|
||||
})
|
||||
await mongo.connect()
|
||||
|
||||
// 使用 UserRepository
|
||||
const userRepo = new UserRepository(mongo)
|
||||
await userRepo.register({ username: 'john', password: '123456' })
|
||||
|
||||
// 使用通用仓库
|
||||
const playerRepo = createRepository<Player>(mongo, 'players')
|
||||
```
|
||||
|
||||
### 与 @esengine/transaction 集成
|
||||
|
||||
```typescript
|
||||
import { createMongoConnection } from '@esengine/database-drivers'
|
||||
import { createMongoStorage, TransactionManager } from '@esengine/transaction'
|
||||
|
||||
const mongo = createMongoConnection({
|
||||
uri: 'mongodb://localhost:27017',
|
||||
database: 'game'
|
||||
})
|
||||
await mongo.connect()
|
||||
|
||||
// 创建事务存储(共享连接)
|
||||
const storage = createMongoStorage(mongo)
|
||||
await storage.ensureIndexes()
|
||||
|
||||
const txManager = new TransactionManager({ storage })
|
||||
```
|
||||
228
docs/src/content/docs/modules/database-drivers/redis.md
Normal file
228
docs/src/content/docs/modules/database-drivers/redis.md
Normal file
@@ -0,0 +1,228 @@
|
||||
---
|
||||
title: "Redis 连接"
|
||||
description: "Redis 连接管理、自动重连、键前缀"
|
||||
---
|
||||
|
||||
## 配置选项
|
||||
|
||||
```typescript
|
||||
interface RedisConnectionConfig {
|
||||
/** Redis 主机 */
|
||||
host?: string
|
||||
|
||||
/** Redis 端口 */
|
||||
port?: number
|
||||
|
||||
/** 认证密码 */
|
||||
password?: string
|
||||
|
||||
/** 数据库编号 */
|
||||
db?: number
|
||||
|
||||
/** 键前缀 */
|
||||
keyPrefix?: string
|
||||
|
||||
/** 是否自动重连(默认 true) */
|
||||
autoReconnect?: boolean
|
||||
|
||||
/** 重连间隔(毫秒,默认 5000) */
|
||||
reconnectInterval?: number
|
||||
|
||||
/** 最大重连次数(默认 10) */
|
||||
maxReconnectAttempts?: number
|
||||
}
|
||||
```
|
||||
|
||||
## 完整示例
|
||||
|
||||
```typescript
|
||||
import { createRedisConnection, RedisConnectionToken } from '@esengine/database-drivers'
|
||||
|
||||
const redis = createRedisConnection({
|
||||
host: 'localhost',
|
||||
port: 6379,
|
||||
password: 'your-password',
|
||||
db: 0,
|
||||
keyPrefix: 'game:',
|
||||
autoReconnect: true,
|
||||
reconnectInterval: 5000,
|
||||
maxReconnectAttempts: 10
|
||||
})
|
||||
|
||||
// 事件监听
|
||||
redis.on('connected', () => {
|
||||
console.log('Redis 已连接')
|
||||
})
|
||||
|
||||
redis.on('disconnected', () => {
|
||||
console.log('Redis 已断开')
|
||||
})
|
||||
|
||||
redis.on('error', (event) => {
|
||||
console.error('Redis 错误:', event.error)
|
||||
})
|
||||
|
||||
// 连接
|
||||
await redis.connect()
|
||||
|
||||
// 检查状态
|
||||
console.log('已连接:', redis.isConnected())
|
||||
console.log('Ping:', await redis.ping())
|
||||
```
|
||||
|
||||
## IRedisConnection 接口
|
||||
|
||||
```typescript
|
||||
interface IRedisConnection {
|
||||
/** 连接 ID */
|
||||
readonly id: string
|
||||
|
||||
/** 连接状态 */
|
||||
readonly state: ConnectionState
|
||||
|
||||
/** 建立连接 */
|
||||
connect(): Promise<void>
|
||||
|
||||
/** 断开连接 */
|
||||
disconnect(): Promise<void>
|
||||
|
||||
/** 检查是否已连接 */
|
||||
isConnected(): boolean
|
||||
|
||||
/** 测试连接 */
|
||||
ping(): Promise<boolean>
|
||||
|
||||
/** 获取值 */
|
||||
get(key: string): Promise<string | null>
|
||||
|
||||
/** 设置值(可选 TTL,单位秒) */
|
||||
set(key: string, value: string, ttl?: number): Promise<void>
|
||||
|
||||
/** 删除键 */
|
||||
del(key: string): Promise<boolean>
|
||||
|
||||
/** 检查键是否存在 */
|
||||
exists(key: string): Promise<boolean>
|
||||
|
||||
/** 设置过期时间(秒) */
|
||||
expire(key: string, seconds: number): Promise<boolean>
|
||||
|
||||
/** 获取剩余过期时间(秒) */
|
||||
ttl(key: string): Promise<number>
|
||||
|
||||
/** 获取原生客户端(高级用法) */
|
||||
getNativeClient(): Redis
|
||||
}
|
||||
```
|
||||
|
||||
## 使用示例
|
||||
|
||||
### 基本操作
|
||||
|
||||
```typescript
|
||||
// 设置值
|
||||
await redis.set('user:1:name', 'John')
|
||||
|
||||
// 设置带过期时间的值(1 小时)
|
||||
await redis.set('session:abc123', 'user-data', 3600)
|
||||
|
||||
// 获取值
|
||||
const name = await redis.get('user:1:name')
|
||||
|
||||
// 检查键是否存在
|
||||
const exists = await redis.exists('user:1:name')
|
||||
|
||||
// 删除键
|
||||
await redis.del('user:1:name')
|
||||
|
||||
// 获取剩余过期时间
|
||||
const ttl = await redis.ttl('session:abc123')
|
||||
```
|
||||
|
||||
### 键前缀
|
||||
|
||||
配置 `keyPrefix` 后,所有操作自动添加前缀:
|
||||
|
||||
```typescript
|
||||
const redis = createRedisConnection({
|
||||
host: 'localhost',
|
||||
keyPrefix: 'game:'
|
||||
})
|
||||
|
||||
// 实际操作的键是 'game:user:1'
|
||||
await redis.set('user:1', 'data')
|
||||
|
||||
// 实际查询的键是 'game:user:1'
|
||||
const data = await redis.get('user:1')
|
||||
```
|
||||
|
||||
### 高级操作
|
||||
|
||||
使用原生客户端进行高级操作:
|
||||
|
||||
```typescript
|
||||
const client = redis.getNativeClient()
|
||||
|
||||
// 使用 Pipeline
|
||||
const pipeline = client.pipeline()
|
||||
pipeline.set('key1', 'value1')
|
||||
pipeline.set('key2', 'value2')
|
||||
pipeline.set('key3', 'value3')
|
||||
await pipeline.exec()
|
||||
|
||||
// 使用事务
|
||||
const multi = client.multi()
|
||||
multi.incr('counter')
|
||||
multi.get('counter')
|
||||
const results = await multi.exec()
|
||||
|
||||
// 使用 Lua 脚本
|
||||
const result = await client.eval(
|
||||
`return redis.call('get', KEYS[1])`,
|
||||
1,
|
||||
'mykey'
|
||||
)
|
||||
```
|
||||
|
||||
## 与事务系统集成
|
||||
|
||||
```typescript
|
||||
import { createRedisConnection } from '@esengine/database-drivers'
|
||||
import { RedisStorage, TransactionManager } from '@esengine/transaction'
|
||||
|
||||
const redis = createRedisConnection({
|
||||
host: 'localhost',
|
||||
port: 6379,
|
||||
keyPrefix: 'tx:'
|
||||
})
|
||||
await redis.connect()
|
||||
|
||||
// 创建事务存储
|
||||
const storage = new RedisStorage({
|
||||
factory: () => redis.getNativeClient(),
|
||||
prefix: 'tx:'
|
||||
})
|
||||
|
||||
const txManager = new TransactionManager({ storage })
|
||||
```
|
||||
|
||||
## 连接状态
|
||||
|
||||
```typescript
|
||||
type ConnectionState =
|
||||
| 'disconnected' // 未连接
|
||||
| 'connecting' // 连接中
|
||||
| 'connected' // 已连接
|
||||
| 'disconnecting' // 断开中
|
||||
| 'error' // 错误状态
|
||||
```
|
||||
|
||||
## 事件
|
||||
|
||||
| 事件 | 描述 |
|
||||
|------|------|
|
||||
| `connected` | 连接成功 |
|
||||
| `disconnected` | 连接断开 |
|
||||
| `reconnecting` | 正在重连 |
|
||||
| `reconnected` | 重连成功 |
|
||||
| `error` | 发生错误 |
|
||||
140
docs/src/content/docs/modules/database/index.md
Normal file
140
docs/src/content/docs/modules/database/index.md
Normal file
@@ -0,0 +1,140 @@
|
||||
---
|
||||
title: "数据库仓库"
|
||||
description: "Repository 模式的数据库操作层,支持 CRUD、分页、软删除"
|
||||
---
|
||||
|
||||
`@esengine/database` 是 ESEngine 的数据库操作层,基于 Repository 模式提供类型安全的 CRUD 操作。
|
||||
|
||||
## 特性
|
||||
|
||||
- **Repository 模式** - 泛型 CRUD 操作,类型安全
|
||||
- **分页查询** - 内置分页支持
|
||||
- **软删除** - 可选的软删除与恢复
|
||||
- **用户管理** - 开箱即用的 UserRepository
|
||||
- **密码安全** - 使用 scrypt 的密码哈希工具
|
||||
|
||||
## 安装
|
||||
|
||||
```bash
|
||||
npm install @esengine/database @esengine/database-drivers
|
||||
```
|
||||
|
||||
## 快速开始
|
||||
|
||||
### 基本仓库
|
||||
|
||||
```typescript
|
||||
import { createMongoConnection } from '@esengine/database-drivers'
|
||||
import { Repository, createRepository } from '@esengine/database'
|
||||
|
||||
// 定义实体
|
||||
interface Player {
|
||||
id: string
|
||||
name: string
|
||||
score: number
|
||||
createdAt: Date
|
||||
updatedAt: Date
|
||||
}
|
||||
|
||||
// 创建连接
|
||||
const mongo = createMongoConnection({
|
||||
uri: 'mongodb://localhost:27017',
|
||||
database: 'game'
|
||||
})
|
||||
await mongo.connect()
|
||||
|
||||
// 创建仓库
|
||||
const playerRepo = createRepository<Player>(mongo, 'players')
|
||||
|
||||
// CRUD 操作
|
||||
const player = await playerRepo.create({
|
||||
name: 'John',
|
||||
score: 0
|
||||
})
|
||||
|
||||
const found = await playerRepo.findById(player.id)
|
||||
|
||||
await playerRepo.update(player.id, { score: 100 })
|
||||
|
||||
await playerRepo.delete(player.id)
|
||||
```
|
||||
|
||||
### 自定义仓库
|
||||
|
||||
```typescript
|
||||
import { Repository, BaseEntity } from '@esengine/database'
|
||||
import type { IMongoConnection } from '@esengine/database-drivers'
|
||||
|
||||
interface Player extends BaseEntity {
|
||||
name: string
|
||||
score: number
|
||||
rank?: string
|
||||
}
|
||||
|
||||
class PlayerRepository extends Repository<Player> {
|
||||
constructor(connection: IMongoConnection) {
|
||||
super(connection, 'players')
|
||||
}
|
||||
|
||||
async findTopPlayers(limit: number = 10): Promise<Player[]> {
|
||||
return this.findMany({
|
||||
sort: { score: 'desc' },
|
||||
limit
|
||||
})
|
||||
}
|
||||
|
||||
async findByRank(rank: string): Promise<Player[]> {
|
||||
return this.findMany({
|
||||
where: { rank }
|
||||
})
|
||||
}
|
||||
|
||||
async incrementScore(playerId: string, amount: number): Promise<Player | null> {
|
||||
const player = await this.findById(playerId)
|
||||
if (!player) return null
|
||||
return this.update(playerId, { score: player.score + amount })
|
||||
}
|
||||
}
|
||||
|
||||
// 使用
|
||||
const playerRepo = new PlayerRepository(mongo)
|
||||
const topPlayers = await playerRepo.findTopPlayers(5)
|
||||
```
|
||||
|
||||
### 用户仓库
|
||||
|
||||
```typescript
|
||||
import { UserRepository } from '@esengine/database'
|
||||
|
||||
const userRepo = new UserRepository(mongo)
|
||||
|
||||
// 注册新用户
|
||||
const user = await userRepo.register({
|
||||
username: 'john',
|
||||
password: 'securePassword123',
|
||||
email: 'john@example.com'
|
||||
})
|
||||
|
||||
// 认证
|
||||
const authenticated = await userRepo.authenticate('john', 'securePassword123')
|
||||
if (authenticated) {
|
||||
console.log('登录成功:', authenticated.username)
|
||||
}
|
||||
|
||||
// 修改密码
|
||||
await userRepo.changePassword(user.id, 'securePassword123', 'newPassword456')
|
||||
|
||||
// 角色管理
|
||||
await userRepo.addRole(user.id, 'admin')
|
||||
await userRepo.removeRole(user.id, 'admin')
|
||||
|
||||
// 查询用户
|
||||
const admins = await userRepo.findByRole('admin')
|
||||
const john = await userRepo.findByUsername('john')
|
||||
```
|
||||
|
||||
## 文档
|
||||
|
||||
- [仓库 API](/modules/database/repository/) - Repository 详细 API
|
||||
- [用户管理](/modules/database/user/) - UserRepository 用法
|
||||
- [查询语法](/modules/database/query/) - 查询条件语法
|
||||
185
docs/src/content/docs/modules/database/query.md
Normal file
185
docs/src/content/docs/modules/database/query.md
Normal file
@@ -0,0 +1,185 @@
|
||||
---
|
||||
title: "查询语法"
|
||||
description: "查询条件操作符和语法"
|
||||
---
|
||||
|
||||
## 基本查询
|
||||
|
||||
### 精确匹配
|
||||
|
||||
```typescript
|
||||
await repo.findMany({
|
||||
where: {
|
||||
name: 'John',
|
||||
status: 'active'
|
||||
}
|
||||
})
|
||||
```
|
||||
|
||||
### 使用操作符
|
||||
|
||||
```typescript
|
||||
await repo.findMany({
|
||||
where: {
|
||||
score: { $gte: 100 },
|
||||
rank: { $in: ['gold', 'platinum'] }
|
||||
}
|
||||
})
|
||||
```
|
||||
|
||||
## 查询操作符
|
||||
|
||||
| 操作符 | 描述 | 示例 |
|
||||
|--------|------|------|
|
||||
| `$eq` | 等于 | `{ score: { $eq: 100 } }` |
|
||||
| `$ne` | 不等于 | `{ status: { $ne: 'banned' } }` |
|
||||
| `$gt` | 大于 | `{ score: { $gt: 50 } }` |
|
||||
| `$gte` | 大于等于 | `{ level: { $gte: 10 } }` |
|
||||
| `$lt` | 小于 | `{ age: { $lt: 18 } }` |
|
||||
| `$lte` | 小于等于 | `{ price: { $lte: 100 } }` |
|
||||
| `$in` | 在数组中 | `{ rank: { $in: ['gold', 'platinum'] } }` |
|
||||
| `$nin` | 不在数组中 | `{ status: { $nin: ['banned', 'suspended'] } }` |
|
||||
| `$like` | 模式匹配 | `{ name: { $like: '%john%' } }` |
|
||||
| `$regex` | 正则匹配 | `{ email: { $regex: '@gmail.com$' } }` |
|
||||
|
||||
## 逻辑操作符
|
||||
|
||||
### $or
|
||||
|
||||
```typescript
|
||||
await repo.findMany({
|
||||
where: {
|
||||
$or: [
|
||||
{ score: { $gte: 1000 } },
|
||||
{ rank: 'legendary' }
|
||||
]
|
||||
}
|
||||
})
|
||||
```
|
||||
|
||||
### $and
|
||||
|
||||
```typescript
|
||||
await repo.findMany({
|
||||
where: {
|
||||
$and: [
|
||||
{ score: { $gte: 100 } },
|
||||
{ score: { $lte: 500 } }
|
||||
]
|
||||
}
|
||||
})
|
||||
```
|
||||
|
||||
### 组合使用
|
||||
|
||||
```typescript
|
||||
await repo.findMany({
|
||||
where: {
|
||||
status: 'active',
|
||||
$or: [
|
||||
{ rank: 'gold' },
|
||||
{ score: { $gte: 1000 } }
|
||||
]
|
||||
}
|
||||
})
|
||||
```
|
||||
|
||||
## 模式匹配
|
||||
|
||||
### $like 语法
|
||||
|
||||
- `%` - 匹配任意字符序列
|
||||
- `_` - 匹配单个字符
|
||||
|
||||
```typescript
|
||||
// 以 'John' 开头
|
||||
{ name: { $like: 'John%' } }
|
||||
|
||||
// 以 'son' 结尾
|
||||
{ name: { $like: '%son' } }
|
||||
|
||||
// 包含 'oh'
|
||||
{ name: { $like: '%oh%' } }
|
||||
|
||||
// 第二个字符是 'o'
|
||||
{ name: { $like: '_o%' } }
|
||||
```
|
||||
|
||||
### $regex 语法
|
||||
|
||||
使用标准正则表达式:
|
||||
|
||||
```typescript
|
||||
// 以 'John' 开头(大小写不敏感)
|
||||
{ name: { $regex: '^john' } }
|
||||
|
||||
// Gmail 邮箱
|
||||
{ email: { $regex: '@gmail\\.com$' } }
|
||||
|
||||
// 包含数字
|
||||
{ username: { $regex: '\\d+' } }
|
||||
```
|
||||
|
||||
## 排序
|
||||
|
||||
```typescript
|
||||
await repo.findMany({
|
||||
sort: {
|
||||
score: 'desc', // 降序
|
||||
name: 'asc' // 升序
|
||||
}
|
||||
})
|
||||
```
|
||||
|
||||
## 分页
|
||||
|
||||
### 使用 limit/offset
|
||||
|
||||
```typescript
|
||||
// 第一页
|
||||
await repo.findMany({
|
||||
limit: 20,
|
||||
offset: 0
|
||||
})
|
||||
|
||||
// 第二页
|
||||
await repo.findMany({
|
||||
limit: 20,
|
||||
offset: 20
|
||||
})
|
||||
```
|
||||
|
||||
### 使用 findPaginated
|
||||
|
||||
```typescript
|
||||
const result = await repo.findPaginated(
|
||||
{ page: 2, pageSize: 20 },
|
||||
{ sort: { createdAt: 'desc' } }
|
||||
)
|
||||
```
|
||||
|
||||
## 完整示例
|
||||
|
||||
```typescript
|
||||
// 查找活跃的金牌玩家,分数在 100-1000 之间
|
||||
// 按分数降序排列,取前 10 个
|
||||
const players = await repo.findMany({
|
||||
where: {
|
||||
status: 'active',
|
||||
rank: 'gold',
|
||||
score: { $gte: 100, $lte: 1000 }
|
||||
},
|
||||
sort: { score: 'desc' },
|
||||
limit: 10
|
||||
})
|
||||
|
||||
// 搜索用户名包含 'john' 或邮箱是 gmail 的用户
|
||||
const users = await repo.findMany({
|
||||
where: {
|
||||
$or: [
|
||||
{ username: { $like: '%john%' } },
|
||||
{ email: { $regex: '@gmail\\.com$' } }
|
||||
]
|
||||
}
|
||||
})
|
||||
```
|
||||
244
docs/src/content/docs/modules/database/repository.md
Normal file
244
docs/src/content/docs/modules/database/repository.md
Normal file
@@ -0,0 +1,244 @@
|
||||
---
|
||||
title: "Repository API"
|
||||
description: "泛型仓库接口,CRUD 操作、分页、软删除"
|
||||
---
|
||||
|
||||
## 创建仓库
|
||||
|
||||
### 使用工厂函数
|
||||
|
||||
```typescript
|
||||
import { createRepository } from '@esengine/database'
|
||||
|
||||
const playerRepo = createRepository<Player>(mongo, 'players')
|
||||
|
||||
// 启用软删除
|
||||
const playerRepo = createRepository<Player>(mongo, 'players', true)
|
||||
```
|
||||
|
||||
### 继承 Repository
|
||||
|
||||
```typescript
|
||||
import { Repository, BaseEntity } from '@esengine/database'
|
||||
|
||||
interface Player extends BaseEntity {
|
||||
name: string
|
||||
score: number
|
||||
}
|
||||
|
||||
class PlayerRepository extends Repository<Player> {
|
||||
constructor(connection: IMongoConnection) {
|
||||
super(connection, 'players', false) // 第三个参数:启用软删除
|
||||
}
|
||||
|
||||
// 添加自定义方法
|
||||
async findTopPlayers(limit: number): Promise<Player[]> {
|
||||
return this.findMany({
|
||||
sort: { score: 'desc' },
|
||||
limit
|
||||
})
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## BaseEntity 接口
|
||||
|
||||
所有实体必须继承 `BaseEntity`:
|
||||
|
||||
```typescript
|
||||
interface BaseEntity {
|
||||
id: string
|
||||
createdAt: Date
|
||||
updatedAt: Date
|
||||
deletedAt?: Date // 软删除时使用
|
||||
}
|
||||
```
|
||||
|
||||
## 查询方法
|
||||
|
||||
### findById
|
||||
|
||||
```typescript
|
||||
const player = await repo.findById('player-123')
|
||||
```
|
||||
|
||||
### findOne
|
||||
|
||||
```typescript
|
||||
const player = await repo.findOne({
|
||||
where: { name: 'John' }
|
||||
})
|
||||
|
||||
const topPlayer = await repo.findOne({
|
||||
sort: { score: 'desc' }
|
||||
})
|
||||
```
|
||||
|
||||
### findMany
|
||||
|
||||
```typescript
|
||||
// 简单查询
|
||||
const players = await repo.findMany({
|
||||
where: { rank: 'gold' }
|
||||
})
|
||||
|
||||
// 复杂查询
|
||||
const players = await repo.findMany({
|
||||
where: {
|
||||
score: { $gte: 100 },
|
||||
rank: { $in: ['gold', 'platinum'] }
|
||||
},
|
||||
sort: { score: 'desc', name: 'asc' },
|
||||
limit: 10,
|
||||
offset: 0
|
||||
})
|
||||
```
|
||||
|
||||
### findPaginated
|
||||
|
||||
```typescript
|
||||
const result = await repo.findPaginated(
|
||||
{ page: 1, pageSize: 20 },
|
||||
{
|
||||
where: { rank: 'gold' },
|
||||
sort: { score: 'desc' }
|
||||
}
|
||||
)
|
||||
|
||||
console.log(result.data) // Player[]
|
||||
console.log(result.total) // 总数量
|
||||
console.log(result.totalPages) // 总页数
|
||||
console.log(result.hasNext) // 是否有下一页
|
||||
console.log(result.hasPrev) // 是否有上一页
|
||||
```
|
||||
|
||||
### count
|
||||
|
||||
```typescript
|
||||
const count = await repo.count({
|
||||
where: { rank: 'gold' }
|
||||
})
|
||||
```
|
||||
|
||||
### exists
|
||||
|
||||
```typescript
|
||||
const exists = await repo.exists({
|
||||
where: { email: 'john@example.com' }
|
||||
})
|
||||
```
|
||||
|
||||
## 创建方法
|
||||
|
||||
### create
|
||||
|
||||
```typescript
|
||||
const player = await repo.create({
|
||||
name: 'John',
|
||||
score: 0
|
||||
})
|
||||
// 自动生成 id, createdAt, updatedAt
|
||||
```
|
||||
|
||||
### createMany
|
||||
|
||||
```typescript
|
||||
const players = await repo.createMany([
|
||||
{ name: 'Alice', score: 100 },
|
||||
{ name: 'Bob', score: 200 },
|
||||
{ name: 'Carol', score: 150 }
|
||||
])
|
||||
```
|
||||
|
||||
## 更新方法
|
||||
|
||||
### update
|
||||
|
||||
```typescript
|
||||
const updated = await repo.update('player-123', {
|
||||
score: 200,
|
||||
rank: 'gold'
|
||||
})
|
||||
// 自动更新 updatedAt
|
||||
```
|
||||
|
||||
## 删除方法
|
||||
|
||||
### delete
|
||||
|
||||
```typescript
|
||||
// 普通删除
|
||||
await repo.delete('player-123')
|
||||
|
||||
// 软删除(如果启用)
|
||||
// 实际是设置 deletedAt 字段
|
||||
```
|
||||
|
||||
### deleteMany
|
||||
|
||||
```typescript
|
||||
const count = await repo.deleteMany({
|
||||
where: { score: { $lt: 10 } }
|
||||
})
|
||||
```
|
||||
|
||||
## 软删除
|
||||
|
||||
### 启用软删除
|
||||
|
||||
```typescript
|
||||
const repo = createRepository<Player>(mongo, 'players', true)
|
||||
```
|
||||
|
||||
### 查询行为
|
||||
|
||||
```typescript
|
||||
// 默认排除软删除记录
|
||||
const players = await repo.findMany()
|
||||
|
||||
// 包含软删除记录
|
||||
const allPlayers = await repo.findMany({
|
||||
includeSoftDeleted: true
|
||||
})
|
||||
```
|
||||
|
||||
### 恢复记录
|
||||
|
||||
```typescript
|
||||
await repo.restore('player-123')
|
||||
```
|
||||
|
||||
## QueryOptions
|
||||
|
||||
```typescript
|
||||
interface QueryOptions<T> {
|
||||
/** 查询条件 */
|
||||
where?: WhereCondition<T>
|
||||
|
||||
/** 排序 */
|
||||
sort?: Partial<Record<keyof T, 'asc' | 'desc'>>
|
||||
|
||||
/** 限制数量 */
|
||||
limit?: number
|
||||
|
||||
/** 偏移量 */
|
||||
offset?: number
|
||||
|
||||
/** 包含软删除记录(仅在启用软删除时有效) */
|
||||
includeSoftDeleted?: boolean
|
||||
}
|
||||
```
|
||||
|
||||
## PaginatedResult
|
||||
|
||||
```typescript
|
||||
interface PaginatedResult<T> {
|
||||
data: T[]
|
||||
total: number
|
||||
page: number
|
||||
pageSize: number
|
||||
totalPages: number
|
||||
hasNext: boolean
|
||||
hasPrev: boolean
|
||||
}
|
||||
```
|
||||
277
docs/src/content/docs/modules/database/user.md
Normal file
277
docs/src/content/docs/modules/database/user.md
Normal file
@@ -0,0 +1,277 @@
|
||||
---
|
||||
title: "用户管理"
|
||||
description: "UserRepository 用户注册、认证、角色管理"
|
||||
---
|
||||
|
||||
## 概述
|
||||
|
||||
`UserRepository` 提供开箱即用的用户管理功能:
|
||||
|
||||
- 用户注册与认证
|
||||
- 密码哈希(使用 scrypt)
|
||||
- 角色管理
|
||||
- 账户状态管理
|
||||
|
||||
## 快速开始
|
||||
|
||||
```typescript
|
||||
import { createMongoConnection } from '@esengine/database-drivers'
|
||||
import { UserRepository } from '@esengine/database'
|
||||
|
||||
const mongo = createMongoConnection({
|
||||
uri: 'mongodb://localhost:27017',
|
||||
database: 'game'
|
||||
})
|
||||
await mongo.connect()
|
||||
|
||||
const userRepo = new UserRepository(mongo)
|
||||
```
|
||||
|
||||
## 用户注册
|
||||
|
||||
```typescript
|
||||
const user = await userRepo.register({
|
||||
username: 'john',
|
||||
password: 'securePassword123',
|
||||
email: 'john@example.com', // 可选
|
||||
displayName: 'John Doe', // 可选
|
||||
roles: ['player'] // 可选,默认 []
|
||||
})
|
||||
|
||||
console.log(user)
|
||||
// {
|
||||
// id: 'uuid-...',
|
||||
// username: 'john',
|
||||
// email: 'john@example.com',
|
||||
// displayName: 'John Doe',
|
||||
// roles: ['player'],
|
||||
// status: 'active',
|
||||
// createdAt: Date,
|
||||
// updatedAt: Date
|
||||
// }
|
||||
```
|
||||
|
||||
**注意**:`register` 返回的 `SafeUser` 不包含密码哈希。
|
||||
|
||||
## 用户认证
|
||||
|
||||
```typescript
|
||||
const user = await userRepo.authenticate('john', 'securePassword123')
|
||||
|
||||
if (user) {
|
||||
console.log('登录成功:', user.username)
|
||||
} else {
|
||||
console.log('用户名或密码错误')
|
||||
}
|
||||
```
|
||||
|
||||
## 密码管理
|
||||
|
||||
### 修改密码
|
||||
|
||||
```typescript
|
||||
const success = await userRepo.changePassword(
|
||||
userId,
|
||||
'oldPassword123',
|
||||
'newPassword456'
|
||||
)
|
||||
|
||||
if (success) {
|
||||
console.log('密码修改成功')
|
||||
} else {
|
||||
console.log('原密码错误')
|
||||
}
|
||||
```
|
||||
|
||||
### 重置密码
|
||||
|
||||
```typescript
|
||||
// 管理员直接重置密码
|
||||
const success = await userRepo.resetPassword(userId, 'newPassword123')
|
||||
```
|
||||
|
||||
## 角色管理
|
||||
|
||||
### 添加角色
|
||||
|
||||
```typescript
|
||||
await userRepo.addRole(userId, 'admin')
|
||||
await userRepo.addRole(userId, 'moderator')
|
||||
```
|
||||
|
||||
### 移除角色
|
||||
|
||||
```typescript
|
||||
await userRepo.removeRole(userId, 'moderator')
|
||||
```
|
||||
|
||||
### 查询角色
|
||||
|
||||
```typescript
|
||||
// 查找所有管理员
|
||||
const admins = await userRepo.findByRole('admin')
|
||||
|
||||
// 检查用户是否有某角色
|
||||
const user = await userRepo.findById(userId)
|
||||
const isAdmin = user?.roles.includes('admin')
|
||||
```
|
||||
|
||||
## 查询用户
|
||||
|
||||
### 按用户名查找
|
||||
|
||||
```typescript
|
||||
const user = await userRepo.findByUsername('john')
|
||||
```
|
||||
|
||||
### 按邮箱查找
|
||||
|
||||
```typescript
|
||||
const user = await userRepo.findByEmail('john@example.com')
|
||||
```
|
||||
|
||||
### 按角色查找
|
||||
|
||||
```typescript
|
||||
const admins = await userRepo.findByRole('admin')
|
||||
```
|
||||
|
||||
### 使用继承的方法
|
||||
|
||||
```typescript
|
||||
// 分页查询
|
||||
const result = await userRepo.findPaginated(
|
||||
{ page: 1, pageSize: 20 },
|
||||
{
|
||||
where: { status: 'active' },
|
||||
sort: { createdAt: 'desc' }
|
||||
}
|
||||
)
|
||||
|
||||
// 复杂查询
|
||||
const users = await userRepo.findMany({
|
||||
where: {
|
||||
status: 'active',
|
||||
roles: { $in: ['admin', 'moderator'] }
|
||||
}
|
||||
})
|
||||
```
|
||||
|
||||
## 账户状态
|
||||
|
||||
```typescript
|
||||
type UserStatus = 'active' | 'inactive' | 'banned' | 'suspended'
|
||||
```
|
||||
|
||||
### 更新状态
|
||||
|
||||
```typescript
|
||||
await userRepo.update(userId, { status: 'banned' })
|
||||
```
|
||||
|
||||
### 查询特定状态
|
||||
|
||||
```typescript
|
||||
const activeUsers = await userRepo.findMany({
|
||||
where: { status: 'active' }
|
||||
})
|
||||
|
||||
const bannedUsers = await userRepo.findMany({
|
||||
where: { status: 'banned' }
|
||||
})
|
||||
```
|
||||
|
||||
## 类型定义
|
||||
|
||||
### UserEntity
|
||||
|
||||
```typescript
|
||||
interface UserEntity extends BaseEntity {
|
||||
username: string
|
||||
passwordHash: string
|
||||
email?: string
|
||||
displayName?: string
|
||||
roles: string[]
|
||||
status: UserStatus
|
||||
lastLoginAt?: Date
|
||||
}
|
||||
```
|
||||
|
||||
### SafeUser
|
||||
|
||||
```typescript
|
||||
type SafeUser = Omit<UserEntity, 'passwordHash'>
|
||||
```
|
||||
|
||||
### CreateUserParams
|
||||
|
||||
```typescript
|
||||
interface CreateUserParams {
|
||||
username: string
|
||||
password: string
|
||||
email?: string
|
||||
displayName?: string
|
||||
roles?: string[]
|
||||
}
|
||||
```
|
||||
|
||||
## 密码工具
|
||||
|
||||
独立的密码工具函数:
|
||||
|
||||
```typescript
|
||||
import { hashPassword, verifyPassword } from '@esengine/database'
|
||||
|
||||
// 哈希密码
|
||||
const hash = await hashPassword('myPassword123')
|
||||
|
||||
// 验证密码
|
||||
const isValid = await verifyPassword('myPassword123', hash)
|
||||
```
|
||||
|
||||
### 安全说明
|
||||
|
||||
- 使用 Node.js 内置的 `scrypt` 算法
|
||||
- 自动生成随机盐值
|
||||
- 默认使用安全的迭代参数
|
||||
- 哈希格式:`salt:hash`(均为 hex 编码)
|
||||
|
||||
## 扩展 UserRepository
|
||||
|
||||
```typescript
|
||||
import { UserRepository, UserEntity } from '@esengine/database'
|
||||
|
||||
interface GameUser extends UserEntity {
|
||||
level: number
|
||||
experience: number
|
||||
coins: number
|
||||
}
|
||||
|
||||
class GameUserRepository extends UserRepository {
|
||||
// 重写集合名
|
||||
constructor(connection: IMongoConnection) {
|
||||
super(connection, 'game_users')
|
||||
}
|
||||
|
||||
// 添加游戏相关方法
|
||||
async addExperience(userId: string, amount: number): Promise<GameUser | null> {
|
||||
const user = await this.findById(userId) as GameUser | null
|
||||
if (!user) return null
|
||||
|
||||
const newExp = user.experience + amount
|
||||
const newLevel = Math.floor(newExp / 1000) + 1
|
||||
|
||||
return this.update(userId, {
|
||||
experience: newExp,
|
||||
level: newLevel
|
||||
}) as Promise<GameUser | null>
|
||||
}
|
||||
|
||||
async findTopPlayers(limit: number = 10): Promise<GameUser[]> {
|
||||
return this.findMany({
|
||||
sort: { level: 'desc', experience: 'desc' },
|
||||
limit
|
||||
}) as Promise<GameUser[]>
|
||||
}
|
||||
}
|
||||
```
|
||||
@@ -37,6 +37,13 @@ ESEngine 提供了丰富的功能模块,可以按需引入到你的项目中
|
||||
| [网络同步](/modules/network/) | `@esengine/network` | 多人游戏网络同步 |
|
||||
| [事务系统](/modules/transaction/) | `@esengine/transaction` | 游戏事务处理,支持分布式事务 |
|
||||
|
||||
### 数据库模块
|
||||
|
||||
| 模块 | 包名 | 描述 |
|
||||
|------|------|------|
|
||||
| [数据库驱动](/modules/database-drivers/) | `@esengine/database-drivers` | MongoDB、Redis 连接管理 |
|
||||
| [数据库仓库](/modules/database/) | `@esengine/database` | Repository 模式数据操作 |
|
||||
|
||||
## 安装
|
||||
|
||||
所有模块都可以独立安装:
|
||||
|
||||
@@ -125,23 +125,24 @@ tx:data:{key} - 业务数据
|
||||
|
||||
## MongoStorage
|
||||
|
||||
MongoDB 存储,适用于需要持久化和复杂查询的场景。使用工厂模式实现惰性连接。
|
||||
MongoDB 存储,适用于需要持久化和复杂查询的场景。使用 `@esengine/database-drivers` 的共享连接。
|
||||
|
||||
```typescript
|
||||
import { MongoClient } from 'mongodb';
|
||||
import { MongoStorage } from '@esengine/transaction';
|
||||
import { createMongoConnection } from '@esengine/database-drivers';
|
||||
import { createMongoStorage, TransactionManager } from '@esengine/transaction';
|
||||
|
||||
// 工厂模式:惰性连接,首次操作时才创建连接
|
||||
const storage = new MongoStorage({
|
||||
factory: async () => {
|
||||
const client = new MongoClient('mongodb://localhost:27017');
|
||||
await client.connect();
|
||||
return client;
|
||||
},
|
||||
database: 'game',
|
||||
transactionCollection: 'transactions', // 事务日志集合
|
||||
dataCollection: 'transaction_data', // 业务数据集合
|
||||
lockCollection: 'transaction_locks', // 锁集合
|
||||
// 创建共享连接
|
||||
const mongo = createMongoConnection({
|
||||
uri: 'mongodb://localhost:27017',
|
||||
database: 'game'
|
||||
});
|
||||
await mongo.connect();
|
||||
|
||||
// 使用共享连接创建存储
|
||||
const storage = createMongoStorage(mongo, {
|
||||
transactionCollection: 'transactions', // 事务日志集合(可选)
|
||||
dataCollection: 'transaction_data', // 业务数据集合(可选)
|
||||
lockCollection: 'transaction_locks', // 锁集合(可选)
|
||||
});
|
||||
|
||||
// 创建索引(首次运行时执行)
|
||||
@@ -149,11 +150,14 @@ await storage.ensureIndexes();
|
||||
|
||||
const manager = new TransactionManager({ storage });
|
||||
|
||||
// 使用后关闭连接
|
||||
// 关闭存储(不会关闭共享连接)
|
||||
await storage.close();
|
||||
|
||||
// 或使用 await using 自动关闭 (TypeScript 5.2+)
|
||||
await using storage = new MongoStorage({ ... });
|
||||
// 共享连接可继续用于其他模块
|
||||
const userRepo = new UserRepository(mongo); // @esengine/database
|
||||
|
||||
// 最后关闭共享连接
|
||||
await mongo.disconnect();
|
||||
```
|
||||
|
||||
### 特点
|
||||
|
||||
Reference in New Issue
Block a user