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:
YHH
2025-12-31 16:26:53 +08:00
committed by GitHub
parent 87f71e2251
commit 71022abc99
41 changed files with 5226 additions and 186 deletions

View 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

View 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

View File

@@ -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:

View File

@@ -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

View 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/) - 依赖注入集成

View 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 })
```

View 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` | 发生错误 |

View 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/) - 查询条件语法

View 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$' } }
]
}
})
```

View 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
}
```

View 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[]>
}
}
```

View File

@@ -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 模式数据操作 |
## 安装
所有模块都可以独立安装:

View File

@@ -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();
```
### 特点