feat(session_core): 新增会话核心模块
范围:src/core/session_core/ 涉及文件: - src/core/session_core/index.ts - src/core/session_core/session_core.interfaces.ts - src/core/session_core/session_core.module.ts - src/core/session_core/session_core.module.spec.ts - src/core/session_core/README.md 主要内容: - 定义会话管理抽象接口(ISessionQueryService, ISessionManagerService) - 实现动态模块配置(SessionCoreModule.forFeature) - 添加完整的单元测试覆盖 - 创建功能文档README.md
This commit is contained in:
100
src/core/session_core/README.md
Normal file
100
src/core/session_core/README.md
Normal 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 层
|
||||
25
src/core/session_core/index.ts
Normal file
25
src/core/session_core/index.ts
Normal 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';
|
||||
140
src/core/session_core/session_core.interfaces.ts
Normal file
140
src/core/session_core/session_core.interfaces.ts
Normal 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';
|
||||
164
src/core/session_core/session_core.module.spec.ts
Normal file
164
src/core/session_core/session_core.module.spec.ts
Normal 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);
|
||||
});
|
||||
});
|
||||
});
|
||||
99
src/core/session_core/session_core.module.ts
Normal file
99
src/core/session_core/session_core.module.ts
Normal 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,
|
||||
};
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user