feature/gateway-module-integration-20260115 #48

Merged
moyin merged 8 commits from feature/gateway-module-integration-20260115 into main 2026-01-15 11:13:51 +08:00
5 changed files with 528 additions and 0 deletions
Showing only changes of commit 3f3c29354e - Show all commits

View File

@@ -0,0 +1,100 @@
# SessionCore 会话核心模块
SessionCore 是为会话管理业务提供技术支撑的核心模块,定义了会话管理的抽象接口,实现 Business 层模块间的解耦。
## 对外提供的接口
### ISessionQueryService会话查询服务接口
提供只读的会话查询能力,用于跨模块查询会话信息。
#### getSession(socketId: string): Promise<IGameSession | null>
获取指定 WebSocket 连接的会话信息,不存在时返回 null。
#### getSocketsInMap(mapId: string): Promise<string[]>
获取指定地图中所有活跃的 Socket ID 列表。
### ISessionManagerService会话管理服务接口
提供完整的会话管理能力,包括创建、更新、删除操作,继承自 ISessionQueryService。
#### createSession(socketId, userId, zulipQueueId, username?, initialMap?, initialPosition?): Promise<IGameSession>
创建新的游戏会话,关联 WebSocket 连接、用户信息和 Zulip 事件队列。
#### injectContext(socketId: string, mapId?: string): Promise<IContextInfo>
根据玩家位置确定 Zulip Stream 和 Topic实现上下文注入。
#### updatePlayerPosition(socketId: string, mapId: string, x: number, y: number): Promise<boolean>
更新玩家在地图中的位置坐标。
#### destroySession(socketId: string): Promise<boolean>
销毁指定的会话,清理相关资源。
#### cleanupExpiredSessions(timeoutMinutes?: number): Promise<{ cleanedCount: number; zulipQueueIds: string[] }>
清理超时的过期会话,返回清理数量和对应的 Zulip 队列 ID 列表。
### SessionCoreModule会话核心模块
#### forFeature(options: SessionCoreModuleOptions): DynamicModule
注册会话服务提供者,支持动态配置会话查询和管理服务的实现。
## 使用的项目内部依赖
### IPosition (本模块)
位置信息接口,定义 x 和 y 坐标。
### IGameSession (本模块)
游戏会话接口,包含 socketId、userId、username、zulipQueueId、currentMap、position、lastActivity、createdAt 等字段。
### IContextInfo (本模块)
上下文信息接口,包含 stream 和可选的 topic 字段。
### SESSION_QUERY_SERVICE (本模块)
会话查询服务的依赖注入 Token。
### SESSION_MANAGER_SERVICE (本模块)
会话管理服务的依赖注入 Token。
## 核心特性
### 接口抽象设计
- 定义抽象接口,不包含具体实现
- 实现由 Business 层的 ChatModule 提供
- 支持依赖注入和模块解耦
### 动态模块配置
- 使用 forFeature 方法支持灵活配置
- 可单独注册查询服务或管理服务
- 支持同时注册多个服务提供者
### 分离查询和管理职责
- ISessionQueryService 提供只读查询能力
- ISessionManagerService 提供完整管理能力
- 清晰的职责分离,便于权限控制
### 跨模块会话查询
- 其他模块可通过 SESSION_QUERY_SERVICE 查询会话信息
- 不需要直接依赖 Business 层实现
- 实现模块间的松耦合
## 潜在风险
### 接口实现缺失风险
- Core 层只定义接口,不提供实现
- 如果 Business 层未正确注册实现,会导致运行时错误
- 缓解措施:在模块导入时验证服务提供者配置
### 依赖注入配置错误风险
- forFeature 配置不当可能导致服务无法注入
- Token 名称错误会导致依赖解析失败
- 缓解措施:使用 TypeScript 类型检查和单元测试验证
### 接口变更影响范围风险
- 接口定义变更会影响所有实现和使用方
- 可能导致多个模块需要同步修改
- 缓解措施:保持接口稳定,使用版本化管理
### 循环依赖风险
- 如果 Business 层实现反向依赖 Core 层其他模块
- 可能形成循环依赖导致模块加载失败
- 缓解措施严格遵守分层架构Core 层不依赖 Business 层

View File

@@ -0,0 +1,25 @@
/**
* 会话核心模块导出
*
* 功能描述:
* - 统一导出会话核心模块的接口和模块定义
* - 提供会话管理相关的类型定义和依赖注入Token
* - 简化外部模块的导入路径
*
* 导出内容:
* - IPosition, IGameSession, IContextInfo - 数据接口
* - ISessionQueryService, ISessionManagerService - 服务接口
* - SESSION_QUERY_SERVICE, SESSION_MANAGER_SERVICE - 依赖注入Token
* - SessionCoreModule - 核心模块
*
* 最近修改:
* - 2026-01-14: 代码规范优化 - 完善文件头注释 (修改者: moyin)
*
* @author moyin
* @version 1.0.1
* @since 2026-01-14
* @lastModified 2026-01-14
*/
export * from './session_core.interfaces';
export * from './session_core.module';

View File

@@ -0,0 +1,140 @@
/**
* 会话管理核心接口定义
*
* 功能描述:
* - 定义会话管理的抽象接口
* - 供 Business 层实现Core 层依赖
* - 实现 Business 层模块间的解耦
*
* 架构层级Core Layer核心层
*
* 使用场景:
* - ZulipEventProcessorService 需要查询玩家会话信息
* - 其他需要会话信息的服务
*
* 最近修改:
* - 2026-01-14: 代码规范优化 - 完善方法注释,添加@param和@returns (修改者: moyin)
* - 2026-01-14: 代码规范优化 - 完善文件头注释替换AI标识 (修改者: moyin)
*
* @author moyin
* @version 1.0.2
* @since 2026-01-14
* @lastModified 2026-01-14
*/
/**
* 位置信息接口
*/
export interface IPosition {
x: number;
y: number;
}
/**
* 游戏会话接口
*/
export interface IGameSession {
socketId: string;
userId: string;
username: string;
zulipQueueId: string;
currentMap: string;
position: IPosition;
lastActivity: Date;
createdAt: Date;
}
/**
* 上下文信息接口
*/
export interface IContextInfo {
stream: string;
topic?: string;
}
/**
* 会话查询服务接口
*
* 提供只读的会话查询能力,用于跨模块查询会话信息
* 不包含会话的创建、更新、删除操作
*/
export interface ISessionQueryService {
/**
* 获取会话信息
* @param socketId WebSocket连接ID
* @returns 会话信息不存在返回null
*/
getSession(socketId: string): Promise<IGameSession | null>;
/**
* 获取指定地图的所有Socket ID
* @param mapId 地图ID
* @returns Socket ID列表
*/
getSocketsInMap(mapId: string): Promise<string[]>;
}
/**
* 会话管理服务接口
*
* 提供完整的会话管理能力,包括创建、更新、删除
* 继承自 ISessionQueryService
*/
export interface ISessionManagerService extends ISessionQueryService {
/**
* 创建会话
* @param socketId WebSocket连接ID
* @param userId 用户ID
* @param zulipQueueId Zulip事件队列ID
* @param username 用户名(可选)
* @param initialMap 初始地图ID可选
* @param initialPosition 初始位置(可选)
* @returns 创建的会话信息
*/
createSession(
socketId: string,
userId: string,
zulipQueueId: string,
username?: string,
initialMap?: string,
initialPosition?: IPosition,
): Promise<IGameSession>;
/**
* 上下文注入根据位置确定Stream/Topic
* @param socketId WebSocket连接ID
* @param mapId 地图ID可选
* @returns 上下文信息包含stream和topic
*/
injectContext(socketId: string, mapId?: string): Promise<IContextInfo>;
/**
* 更新玩家位置
* @param socketId WebSocket连接ID
* @param mapId 地图ID
* @param x X坐标
* @param y Y坐标
* @returns 更新是否成功
*/
updatePlayerPosition(socketId: string, mapId: string, x: number, y: number): Promise<boolean>;
/**
* 销毁会话
* @param socketId WebSocket连接ID
* @returns 销毁是否成功
*/
destroySession(socketId: string): Promise<boolean>;
/**
* 清理过期会话
* @param timeoutMinutes 超时时间(分钟),可选
* @returns 清理结果包含清理数量和对应的Zulip队列ID列表
*/
cleanupExpiredSessions(timeoutMinutes?: number): Promise<{ cleanedCount: number; zulipQueueIds: string[] }>;
}
/**
* 依赖注入 Token
*/
export const SESSION_QUERY_SERVICE = 'SESSION_QUERY_SERVICE';
export const SESSION_MANAGER_SERVICE = 'SESSION_MANAGER_SERVICE';

View File

@@ -0,0 +1,164 @@
/**
* SessionCoreModule 单元测试
*
* 功能描述:
* - 测试 SessionCoreModule 的动态模块配置功能
* - 验证 forFeature 方法的正确行为
* - 确保依赖注入配置正确
*
* @author moyin
* @version 1.0.0
* @since 2026-01-14
* @lastModified 2026-01-14
*/
import { Test, TestingModule } from '@nestjs/testing';
import { SessionCoreModule } from './session_core.module';
import {
SESSION_QUERY_SERVICE,
SESSION_MANAGER_SERVICE,
ISessionQueryService,
ISessionManagerService,
IGameSession,
IContextInfo,
} from './session_core.interfaces';
class MockSessionQueryService implements ISessionQueryService {
async getSession(): Promise<IGameSession | null> {
return null;
}
async getSocketsInMap(): Promise<string[]> {
return [];
}
}
class MockSessionManagerService implements ISessionManagerService {
async getSession(): Promise<IGameSession | null> {
return null;
}
async getSocketsInMap(): Promise<string[]> {
return [];
}
async createSession(
socketId: string,
userId: string,
zulipQueueId: string,
): Promise<IGameSession> {
return {
socketId,
userId,
username: 'test',
zulipQueueId,
currentMap: 'default',
position: { x: 0, y: 0 },
lastActivity: new Date(),
createdAt: new Date(),
};
}
async injectContext(): Promise<IContextInfo> {
return { stream: 'test' };
}
async updatePlayerPosition(): Promise<boolean> {
return true;
}
async destroySession(): Promise<boolean> {
return true;
}
async cleanupExpiredSessions(): Promise<{
cleanedCount: number;
zulipQueueIds: string[];
}> {
return { cleanedCount: 0, zulipQueueIds: [] };
}
}
describe('SessionCoreModule', () => {
describe('forFeature', () => {
it('should return dynamic module with empty providers when no options', () => {
const result = SessionCoreModule.forFeature({});
expect(result.module).toBe(SessionCoreModule);
expect(result.providers).toEqual([]);
expect(result.exports).toEqual([]);
});
it('should register sessionQueryProvider when provided', () => {
const result = SessionCoreModule.forFeature({
sessionQueryProvider: {
provide: SESSION_QUERY_SERVICE,
useClass: MockSessionQueryService,
},
});
expect(result.providers).toHaveLength(1);
expect(result.exports).toContain(SESSION_QUERY_SERVICE);
});
it('should register sessionManagerProvider when provided', () => {
const result = SessionCoreModule.forFeature({
sessionManagerProvider: {
provide: SESSION_MANAGER_SERVICE,
useClass: MockSessionManagerService,
},
});
expect(result.providers).toHaveLength(1);
expect(result.exports).toContain(SESSION_MANAGER_SERVICE);
});
it('should register both providers when both provided', () => {
const result = SessionCoreModule.forFeature({
sessionQueryProvider: {
provide: SESSION_QUERY_SERVICE,
useClass: MockSessionQueryService,
},
sessionManagerProvider: {
provide: SESSION_MANAGER_SERVICE,
useClass: MockSessionManagerService,
},
});
expect(result.providers).toHaveLength(2);
expect(result.exports).toContain(SESSION_QUERY_SERVICE);
expect(result.exports).toContain(SESSION_MANAGER_SERVICE);
});
});
describe('Module Integration', () => {
let module: TestingModule;
afterEach(async () => {
if (module) {
await module.close();
}
});
it('should inject sessionQueryService correctly', async () => {
module = await Test.createTestingModule({
imports: [
SessionCoreModule.forFeature({
sessionQueryProvider: {
provide: SESSION_QUERY_SERVICE,
useClass: MockSessionQueryService,
},
}),
],
}).compile();
const service = module.get<ISessionQueryService>(SESSION_QUERY_SERVICE);
expect(service).toBeInstanceOf(MockSessionQueryService);
});
it('should inject sessionManagerService correctly', async () => {
module = await Test.createTestingModule({
imports: [
SessionCoreModule.forFeature({
sessionManagerProvider: {
provide: SESSION_MANAGER_SERVICE,
useClass: MockSessionManagerService,
},
}),
],
}).compile();
const service = module.get<ISessionManagerService>(SESSION_MANAGER_SERVICE);
expect(service).toBeInstanceOf(MockSessionManagerService);
});
});
});

View File

@@ -0,0 +1,99 @@
/**
* 会话核心模块
*
* 功能描述:
* - 提供会话管理接口的依赖注入配置
* - 作为 Core 层模块,不包含具体实现
* - 实现由 Business 层的 ChatModule 提供
*
* 架构层级Core Layer核心层
*
* 最近修改:
* - 2026-01-14: 代码规范优化 - 清理未使用的导入 (修改者: moyin)
* - 2026-01-14: 代码规范优化 - 添加SessionCoreModule类注释 (修改者: moyin)
* - 2026-01-14: 代码规范优化 - 完善文件头注释替换AI标识 (修改者: moyin)
*
* @author moyin
* @version 1.0.3
* @since 2026-01-14
* @lastModified 2026-01-14
*/
import { Module, DynamicModule, Provider } from '@nestjs/common';
import {
SESSION_QUERY_SERVICE,
SESSION_MANAGER_SERVICE,
} from './session_core.interfaces';
/**
* 会话核心模块配置选项
*/
export interface SessionCoreModuleOptions {
/**
* 会话查询服务提供者
*/
sessionQueryProvider?: Provider;
/**
* 会话管理服务提供者
*/
sessionManagerProvider?: Provider;
}
/**
* 会话核心模块类
*
* 职责:
* - 提供会话服务的依赖注入配置
* - 支持动态注册会话查询和管理服务
* - 作为Core层与Business层的桥梁
*
* 主要方法:
* - forFeature() - 注册会话服务提供者
*
* 使用场景:
* - Business层模块注册会话服务实现
* - 其他模块导入以获取会话查询能力
*/
@Module({})
export class SessionCoreModule {
/**
* 注册会话服务提供者
*
* @param options 模块配置选项
* @returns 动态模块配置
*
* @example
* // 在 ChatModule 中注册实现
* SessionCoreModule.forFeature({
* sessionQueryProvider: {
* provide: SESSION_QUERY_SERVICE,
* useExisting: ChatSessionService,
* },
* sessionManagerProvider: {
* provide: SESSION_MANAGER_SERVICE,
* useExisting: ChatSessionService,
* },
* })
*/
static forFeature(options: SessionCoreModuleOptions): DynamicModule {
const providers: Provider[] = [];
const exports: string[] = [];
if (options.sessionQueryProvider) {
providers.push(options.sessionQueryProvider);
exports.push(SESSION_QUERY_SERVICE);
}
if (options.sessionManagerProvider) {
providers.push(options.sessionManagerProvider);
exports.push(SESSION_MANAGER_SERVICE);
}
return {
module: SessionCoreModule,
providers,
exports,
};
}
}