feat(transaction): 添加游戏事务系统 | add game transaction system (#381)

- TransactionManager/TransactionContext 事务管理
- MemoryStorage/RedisStorage/MongoStorage 存储实现
- CurrencyOperation/InventoryOperation/TradeOperation 内置操作
- SagaOrchestrator 分布式 Saga 编排
- withTransactions() Room 集成
- 完整中英文文档
This commit is contained in:
YHH
2025-12-29 10:54:00 +08:00
committed by GitHub
parent 2d46ccf896
commit d4cef828e1
38 changed files with 6631 additions and 16 deletions

View File

@@ -0,0 +1,286 @@
/**
* @zh 事务上下文实现
* @en Transaction context implementation
*/
import type {
ITransactionContext,
ITransactionOperation,
ITransactionStorage,
TransactionState,
TransactionResult,
TransactionOptions,
TransactionLog,
OperationLog,
OperationResult,
} from './types.js'
/**
* @zh 生成唯一 ID
* @en Generate unique ID
*/
function generateId(): string {
return `tx_${Date.now().toString(36)}_${Math.random().toString(36).substring(2, 11)}`
}
/**
* @zh 事务上下文
* @en Transaction context
*
* @zh 封装事务的状态、操作和执行逻辑
* @en Encapsulates transaction state, operations, and execution logic
*
* @example
* ```typescript
* const ctx = new TransactionContext({ timeout: 5000 })
* ctx.addOperation(new DeductCurrency({ playerId: '1', amount: 100 }))
* ctx.addOperation(new AddItem({ playerId: '1', itemId: 'sword' }))
* const result = await ctx.execute()
* ```
*/
export class TransactionContext implements ITransactionContext {
private _id: string
private _state: TransactionState = 'pending'
private _timeout: number
private _operations: ITransactionOperation[] = []
private _storage: ITransactionStorage | null
private _metadata: Record<string, unknown>
private _contextData: Map<string, unknown> = new Map()
private _startTime: number = 0
private _distributed: boolean
constructor(options: TransactionOptions & { storage?: ITransactionStorage } = {}) {
this._id = generateId()
this._timeout = options.timeout ?? 30000
this._storage = options.storage ?? null
this._metadata = options.metadata ?? {}
this._distributed = options.distributed ?? false
}
// =========================================================================
// 只读属性 | Readonly properties
// =========================================================================
get id(): string {
return this._id
}
get state(): TransactionState {
return this._state
}
get timeout(): number {
return this._timeout
}
get operations(): ReadonlyArray<ITransactionOperation> {
return this._operations
}
get storage(): ITransactionStorage | null {
return this._storage
}
get metadata(): Record<string, unknown> {
return this._metadata
}
// =========================================================================
// 公共方法 | Public methods
// =========================================================================
/**
* @zh 添加操作
* @en Add operation
*/
addOperation<T extends ITransactionOperation>(operation: T): this {
if (this._state !== 'pending') {
throw new Error(`Cannot add operation to transaction in state: ${this._state}`)
}
this._operations.push(operation)
return this
}
/**
* @zh 执行事务
* @en Execute transaction
*/
async execute<T = unknown>(): Promise<TransactionResult<T>> {
if (this._state !== 'pending') {
return {
success: false,
transactionId: this._id,
results: [],
error: `Transaction already in state: ${this._state}`,
duration: 0,
}
}
this._startTime = Date.now()
this._state = 'executing'
const results: OperationResult[] = []
let executedCount = 0
try {
await this._saveLog()
for (let i = 0; i < this._operations.length; i++) {
if (this._isTimedOut()) {
throw new Error('Transaction timed out')
}
const op = this._operations[i]
const isValid = await op.validate(this)
if (!isValid) {
throw new Error(`Validation failed for operation: ${op.name}`)
}
const result = await op.execute(this)
results.push(result)
executedCount++
await this._updateOperationLog(i, 'executed')
if (!result.success) {
throw new Error(result.error ?? `Operation ${op.name} failed`)
}
}
this._state = 'committed'
await this._updateTransactionState('committed')
return {
success: true,
transactionId: this._id,
results,
data: this._collectResultData(results) as T,
duration: Date.now() - this._startTime,
}
} catch (error) {
const errorMessage = error instanceof Error ? error.message : String(error)
await this._compensate(executedCount - 1)
return {
success: false,
transactionId: this._id,
results,
error: errorMessage,
duration: Date.now() - this._startTime,
}
}
}
/**
* @zh 手动回滚事务
* @en Manually rollback transaction
*/
async rollback(): Promise<void> {
if (this._state === 'committed' || this._state === 'rolledback') {
return
}
await this._compensate(this._operations.length - 1)
}
/**
* @zh 获取上下文数据
* @en Get context data
*/
get<T>(key: string): T | undefined {
return this._contextData.get(key) as T | undefined
}
/**
* @zh 设置上下文数据
* @en Set context data
*/
set<T>(key: string, value: T): void {
this._contextData.set(key, value)
}
// =========================================================================
// 私有方法 | Private methods
// =========================================================================
private _isTimedOut(): boolean {
return Date.now() - this._startTime > this._timeout
}
private async _compensate(fromIndex: number): Promise<void> {
this._state = 'rolledback'
for (let i = fromIndex; i >= 0; i--) {
const op = this._operations[i]
try {
await op.compensate(this)
await this._updateOperationLog(i, 'compensated')
} catch (error) {
const errorMessage = error instanceof Error ? error.message : String(error)
await this._updateOperationLog(i, 'failed', errorMessage)
}
}
await this._updateTransactionState('rolledback')
}
private async _saveLog(): Promise<void> {
if (!this._storage) return
const log: TransactionLog = {
id: this._id,
state: this._state,
createdAt: this._startTime,
updatedAt: this._startTime,
timeout: this._timeout,
operations: this._operations.map((op) => ({
name: op.name,
data: op.data,
state: 'pending' as const,
})),
metadata: this._metadata,
distributed: this._distributed,
}
await this._storage.saveTransaction(log)
}
private async _updateTransactionState(state: TransactionState): Promise<void> {
this._state = state
if (this._storage) {
await this._storage.updateTransactionState(this._id, state)
}
}
private async _updateOperationLog(
index: number,
state: OperationLog['state'],
error?: string
): Promise<void> {
if (this._storage) {
await this._storage.updateOperationState(this._id, index, state, error)
}
}
private _collectResultData(results: OperationResult[]): unknown {
const data: Record<string, unknown> = {}
for (const result of results) {
if (result.data !== undefined) {
Object.assign(data, result.data)
}
}
return Object.keys(data).length > 0 ? data : undefined
}
}
/**
* @zh 创建事务上下文
* @en Create transaction context
*/
export function createTransactionContext(
options: TransactionOptions & { storage?: ITransactionStorage } = {}
): ITransactionContext {
return new TransactionContext(options)
}

View File

@@ -0,0 +1,255 @@
/**
* @zh 事务管理器
* @en Transaction manager
*/
import type {
ITransactionContext,
ITransactionStorage,
TransactionManagerConfig,
TransactionOptions,
TransactionLog,
TransactionResult,
} from './types.js'
import { TransactionContext } from './TransactionContext.js'
/**
* @zh 事务管理器
* @en Transaction manager
*
* @zh 管理事务的创建、执行和恢复
* @en Manages transaction creation, execution, and recovery
*
* @example
* ```typescript
* const manager = new TransactionManager({
* storage: new RedisStorage({ url: 'redis://localhost:6379' }),
* defaultTimeout: 10000,
* })
*
* const tx = manager.begin({ timeout: 5000 })
* tx.addOperation(new DeductCurrency({ ... }))
* tx.addOperation(new AddItem({ ... }))
*
* const result = await tx.execute()
* ```
*/
export class TransactionManager {
private _storage: ITransactionStorage | null
private _defaultTimeout: number
private _serverId: string
private _autoRecover: boolean
private _activeTransactions: Map<string, ITransactionContext> = new Map()
constructor(config: TransactionManagerConfig = {}) {
this._storage = config.storage ?? null
this._defaultTimeout = config.defaultTimeout ?? 30000
this._serverId = config.serverId ?? this._generateServerId()
this._autoRecover = config.autoRecover ?? true
}
// =========================================================================
// 只读属性 | Readonly properties
// =========================================================================
/**
* @zh 服务器 ID
* @en Server ID
*/
get serverId(): string {
return this._serverId
}
/**
* @zh 存储实例
* @en Storage instance
*/
get storage(): ITransactionStorage | null {
return this._storage
}
/**
* @zh 活跃事务数量
* @en Active transaction count
*/
get activeCount(): number {
return this._activeTransactions.size
}
// =========================================================================
// 公共方法 | Public methods
// =========================================================================
/**
* @zh 开始新事务
* @en Begin new transaction
*
* @param options - @zh 事务选项 @en Transaction options
* @returns @zh 事务上下文 @en Transaction context
*/
begin(options: TransactionOptions = {}): ITransactionContext {
const ctx = new TransactionContext({
timeout: options.timeout ?? this._defaultTimeout,
storage: this._storage ?? undefined,
metadata: {
...options.metadata,
serverId: this._serverId,
},
distributed: options.distributed,
})
this._activeTransactions.set(ctx.id, ctx)
return ctx
}
/**
* @zh 执行事务(便捷方法)
* @en Execute transaction (convenience method)
*
* @param builder - @zh 事务构建函数 @en Transaction builder function
* @param options - @zh 事务选项 @en Transaction options
* @returns @zh 事务结果 @en Transaction result
*/
async run<T = unknown>(
builder: (ctx: ITransactionContext) => void | Promise<void>,
options: TransactionOptions = {}
): Promise<TransactionResult<T>> {
const ctx = this.begin(options)
try {
await builder(ctx)
const result = await ctx.execute<T>()
return result
} finally {
this._activeTransactions.delete(ctx.id)
}
}
/**
* @zh 获取活跃事务
* @en Get active transaction
*/
getTransaction(id: string): ITransactionContext | undefined {
return this._activeTransactions.get(id)
}
/**
* @zh 恢复未完成的事务
* @en Recover pending transactions
*/
async recover(): Promise<number> {
if (!this._storage) return 0
const pendingTransactions = await this._storage.getPendingTransactions(this._serverId)
let recoveredCount = 0
for (const log of pendingTransactions) {
try {
await this._recoverTransaction(log)
recoveredCount++
} catch (error) {
console.error(`Failed to recover transaction ${log.id}:`, error)
}
}
return recoveredCount
}
/**
* @zh 获取分布式锁
* @en Acquire distributed lock
*/
async acquireLock(key: string, ttl: number = 10000): Promise<string | null> {
if (!this._storage) return null
return this._storage.acquireLock(key, ttl)
}
/**
* @zh 释放分布式锁
* @en Release distributed lock
*/
async releaseLock(key: string, token: string): Promise<boolean> {
if (!this._storage) return false
return this._storage.releaseLock(key, token)
}
/**
* @zh 使用分布式锁执行
* @en Execute with distributed lock
*/
async withLock<T>(
key: string,
fn: () => Promise<T>,
ttl: number = 10000
): Promise<T> {
const token = await this.acquireLock(key, ttl)
if (!token) {
throw new Error(`Failed to acquire lock for key: ${key}`)
}
try {
return await fn()
} finally {
await this.releaseLock(key, token)
}
}
/**
* @zh 清理已完成的事务日志
* @en Clean up completed transaction logs
*/
async cleanup(beforeTimestamp?: number): Promise<number> {
if (!this._storage) return 0
const timestamp = beforeTimestamp ?? Date.now() - 24 * 60 * 60 * 1000 // 默认清理24小时前
const pendingTransactions = await this._storage.getPendingTransactions()
let cleanedCount = 0
for (const log of pendingTransactions) {
if (
log.createdAt < timestamp &&
(log.state === 'committed' || log.state === 'rolledback')
) {
await this._storage.deleteTransaction(log.id)
cleanedCount++
}
}
return cleanedCount
}
// =========================================================================
// 私有方法 | Private methods
// =========================================================================
private _generateServerId(): string {
return `server_${Date.now().toString(36)}_${Math.random().toString(36).substring(2, 8)}`
}
private async _recoverTransaction(log: TransactionLog): Promise<void> {
if (log.state === 'executing') {
const executedOps = log.operations.filter((op) => op.state === 'executed')
if (executedOps.length > 0 && this._storage) {
for (let i = executedOps.length - 1; i >= 0; i--) {
await this._storage.updateOperationState(log.id, i, 'compensated')
}
await this._storage.updateTransactionState(log.id, 'rolledback')
} else {
await this._storage?.updateTransactionState(log.id, 'failed')
}
}
}
}
/**
* @zh 创建事务管理器
* @en Create transaction manager
*/
export function createTransactionManager(
config: TransactionManagerConfig = {}
): TransactionManager {
return new TransactionManager(config)
}

View File

@@ -0,0 +1,20 @@
/**
* @zh 核心模块导出
* @en Core module exports
*/
export type {
TransactionState,
OperationResult,
TransactionResult,
OperationLog,
TransactionLog,
TransactionOptions,
TransactionManagerConfig,
ITransactionStorage,
ITransactionOperation,
ITransactionContext,
} from './types.js'
export { TransactionContext, createTransactionContext } from './TransactionContext.js'
export { TransactionManager, createTransactionManager } from './TransactionManager.js'

View File

@@ -0,0 +1,484 @@
/**
* @zh 事务系统核心类型定义
* @en Transaction system core type definitions
*/
// =============================================================================
// 事务状态 | Transaction State
// =============================================================================
/**
* @zh 事务状态
* @en Transaction state
*/
export type TransactionState =
| 'pending' // 等待执行 | Waiting to execute
| 'executing' // 执行中 | Executing
| 'committed' // 已提交 | Committed
| 'rolledback' // 已回滚 | Rolled back
| 'failed' // 失败 | Failed
// =============================================================================
// 操作结果 | Operation Result
// =============================================================================
/**
* @zh 操作结果
* @en Operation result
*/
export interface OperationResult<T = unknown> {
/**
* @zh 是否成功
* @en Whether succeeded
*/
success: boolean
/**
* @zh 返回数据
* @en Return data
*/
data?: T
/**
* @zh 错误信息
* @en Error message
*/
error?: string
/**
* @zh 错误代码
* @en Error code
*/
errorCode?: string
}
/**
* @zh 事务结果
* @en Transaction result
*/
export interface TransactionResult<T = unknown> {
/**
* @zh 是否成功
* @en Whether succeeded
*/
success: boolean
/**
* @zh 事务 ID
* @en Transaction ID
*/
transactionId: string
/**
* @zh 操作结果列表
* @en Operation results
*/
results: OperationResult[]
/**
* @zh 最终数据
* @en Final data
*/
data?: T
/**
* @zh 错误信息
* @en Error message
*/
error?: string
/**
* @zh 执行时间(毫秒)
* @en Execution time in milliseconds
*/
duration: number
}
// =============================================================================
// 事务日志 | Transaction Log
// =============================================================================
/**
* @zh 操作日志
* @en Operation log
*/
export interface OperationLog {
/**
* @zh 操作名称
* @en Operation name
*/
name: string
/**
* @zh 操作数据
* @en Operation data
*/
data: unknown
/**
* @zh 操作状态
* @en Operation state
*/
state: 'pending' | 'executed' | 'compensated' | 'failed'
/**
* @zh 执行时间
* @en Execution timestamp
*/
executedAt?: number
/**
* @zh 补偿时间
* @en Compensation timestamp
*/
compensatedAt?: number
/**
* @zh 错误信息
* @en Error message
*/
error?: string
}
/**
* @zh 事务日志
* @en Transaction log
*/
export interface TransactionLog {
/**
* @zh 事务 ID
* @en Transaction ID
*/
id: string
/**
* @zh 事务状态
* @en Transaction state
*/
state: TransactionState
/**
* @zh 创建时间
* @en Creation timestamp
*/
createdAt: number
/**
* @zh 更新时间
* @en Update timestamp
*/
updatedAt: number
/**
* @zh 超时时间(毫秒)
* @en Timeout in milliseconds
*/
timeout: number
/**
* @zh 操作日志列表
* @en Operation logs
*/
operations: OperationLog[]
/**
* @zh 元数据
* @en Metadata
*/
metadata?: Record<string, unknown>
/**
* @zh 是否分布式事务
* @en Whether distributed transaction
*/
distributed?: boolean
/**
* @zh 参与的服务器列表
* @en Participating servers
*/
participants?: string[]
}
// =============================================================================
// 事务配置 | Transaction Configuration
// =============================================================================
/**
* @zh 事务选项
* @en Transaction options
*/
export interface TransactionOptions {
/**
* @zh 超时时间(毫秒),默认 30000
* @en Timeout in milliseconds, default 30000
*/
timeout?: number
/**
* @zh 是否分布式事务
* @en Whether distributed transaction
*/
distributed?: boolean
/**
* @zh 元数据
* @en Metadata
*/
metadata?: Record<string, unknown>
/**
* @zh 重试次数,默认 0
* @en Retry count, default 0
*/
retryCount?: number
/**
* @zh 重试间隔(毫秒),默认 1000
* @en Retry interval in milliseconds, default 1000
*/
retryInterval?: number
}
/**
* @zh 事务管理器配置
* @en Transaction manager configuration
*/
export interface TransactionManagerConfig {
/**
* @zh 存储实例
* @en Storage instance
*/
storage?: ITransactionStorage
/**
* @zh 默认超时时间(毫秒)
* @en Default timeout in milliseconds
*/
defaultTimeout?: number
/**
* @zh 服务器 ID分布式用
* @en Server ID for distributed transactions
*/
serverId?: string
/**
* @zh 是否自动恢复未完成事务
* @en Whether to auto-recover pending transactions
*/
autoRecover?: boolean
}
// =============================================================================
// 存储接口 | Storage Interface
// =============================================================================
/**
* @zh 事务存储接口
* @en Transaction storage interface
*/
export interface ITransactionStorage {
/**
* @zh 获取分布式锁
* @en Acquire distributed lock
*
* @param key - @zh 锁的键 @en Lock key
* @param ttl - @zh 锁的生存时间(毫秒) @en Lock TTL in milliseconds
* @returns @zh 锁令牌,获取失败返回 null @en Lock token, null if failed
*/
acquireLock(key: string, ttl: number): Promise<string | null>
/**
* @zh 释放分布式锁
* @en Release distributed lock
*
* @param key - @zh 锁的键 @en Lock key
* @param token - @zh 锁令牌 @en Lock token
* @returns @zh 是否成功释放 @en Whether released successfully
*/
releaseLock(key: string, token: string): Promise<boolean>
/**
* @zh 保存事务日志
* @en Save transaction log
*/
saveTransaction(tx: TransactionLog): Promise<void>
/**
* @zh 获取事务日志
* @en Get transaction log
*/
getTransaction(id: string): Promise<TransactionLog | null>
/**
* @zh 更新事务状态
* @en Update transaction state
*/
updateTransactionState(id: string, state: TransactionState): Promise<void>
/**
* @zh 更新操作状态
* @en Update operation state
*/
updateOperationState(
transactionId: string,
operationIndex: number,
state: OperationLog['state'],
error?: string
): Promise<void>
/**
* @zh 获取待恢复的事务列表
* @en Get pending transactions for recovery
*/
getPendingTransactions(serverId?: string): Promise<TransactionLog[]>
/**
* @zh 删除事务日志
* @en Delete transaction log
*/
deleteTransaction(id: string): Promise<void>
/**
* @zh 获取数据
* @en Get data
*/
get<T>(key: string): Promise<T | null>
/**
* @zh 设置数据
* @en Set data
*/
set<T>(key: string, value: T, ttl?: number): Promise<void>
/**
* @zh 删除数据
* @en Delete data
*/
delete(key: string): Promise<boolean>
}
// =============================================================================
// 操作接口 | Operation Interface
// =============================================================================
/**
* @zh 事务操作接口
* @en Transaction operation interface
*/
export interface ITransactionOperation<TData = unknown, TResult = unknown> {
/**
* @zh 操作名称
* @en Operation name
*/
readonly name: string
/**
* @zh 操作数据
* @en Operation data
*/
readonly data: TData
/**
* @zh 验证前置条件
* @en Validate preconditions
*
* @param ctx - @zh 事务上下文 @en Transaction context
* @returns @zh 是否验证通过 @en Whether validation passed
*/
validate(ctx: ITransactionContext): Promise<boolean>
/**
* @zh 执行操作
* @en Execute operation
*
* @param ctx - @zh 事务上下文 @en Transaction context
* @returns @zh 操作结果 @en Operation result
*/
execute(ctx: ITransactionContext): Promise<OperationResult<TResult>>
/**
* @zh 补偿操作(回滚)
* @en Compensate operation (rollback)
*
* @param ctx - @zh 事务上下文 @en Transaction context
*/
compensate(ctx: ITransactionContext): Promise<void>
}
// =============================================================================
// 事务上下文接口 | Transaction Context Interface
// =============================================================================
/**
* @zh 事务上下文接口
* @en Transaction context interface
*/
export interface ITransactionContext {
/**
* @zh 事务 ID
* @en Transaction ID
*/
readonly id: string
/**
* @zh 事务状态
* @en Transaction state
*/
readonly state: TransactionState
/**
* @zh 超时时间(毫秒)
* @en Timeout in milliseconds
*/
readonly timeout: number
/**
* @zh 操作列表
* @en Operations
*/
readonly operations: ReadonlyArray<ITransactionOperation>
/**
* @zh 存储实例
* @en Storage instance
*/
readonly storage: ITransactionStorage | null
/**
* @zh 元数据
* @en Metadata
*/
readonly metadata: Record<string, unknown>
/**
* @zh 添加操作
* @en Add operation
*/
addOperation<T extends ITransactionOperation>(operation: T): this
/**
* @zh 执行事务
* @en Execute transaction
*/
execute<T = unknown>(): Promise<TransactionResult<T>>
/**
* @zh 回滚事务
* @en Rollback transaction
*/
rollback(): Promise<void>
/**
* @zh 获取上下文数据
* @en Get context data
*/
get<T>(key: string): T | undefined
/**
* @zh 设置上下文数据
* @en Set context data
*/
set<T>(key: string, value: T): void
}