forked from datawhale/whale-town-end
feat(zulip): 添加全面的 Zulip 集成系统
* **新增 Zulip 模块**:包含完整的集成服务,涵盖客户端池(client pool)、会话管理及事件处理。 * **新增 WebSocket 网关**:用于处理 Zulip 的实时事件监听与双向通信。 * **新增安全服务**:支持 API 密钥加密存储及凭据的安全管理。 * **新增配置管理服务**:支持配置热加载(hot-reload),实现动态配置更新。 * **新增错误处理与监控服务**:提升系统的可靠性与可观测性。 * **新增消息过滤服务**:用于内容校验及速率限制(流控)。 * **新增流初始化与会话清理服务**:优化资源管理与回收。 * **完善测试覆盖**:包含单元测试及端到端(e2e)集成测试。 * **完善详细文档**:包括 API 参考手册、配置指南及集成概述。 * **新增地图配置系统**:实现游戏地点与 Zulip Stream(频道)及 Topic(话题)的逻辑映射。 * **新增环境变量配置**:涵盖 Zulip 服务器地址、身份验证及监控相关设置。 * **更新 App 模块**:注册并启用新的 Zulip 集成模块。 * **更新 Redis 接口**:以支持增强型的会话管理功能。 * **实现 WebSocket 协议支持**:确保与 Zulip 之间的实时双向通信。
This commit is contained in:
@@ -201,4 +201,86 @@ export class FileRedisService implements IRedisService {
|
||||
await this.saveData();
|
||||
this.logger.log('清空所有Redis数据');
|
||||
}
|
||||
|
||||
async setex(key: string, ttl: number, value: string): Promise<void> {
|
||||
const item: { value: string; expireAt?: number } = {
|
||||
value,
|
||||
expireAt: Date.now() + ttl * 1000,
|
||||
};
|
||||
|
||||
this.data.set(key, item);
|
||||
await this.saveData();
|
||||
|
||||
this.logger.debug(`设置Redis键(setex): ${key}, TTL: ${ttl}秒`);
|
||||
}
|
||||
|
||||
async incr(key: string): Promise<number> {
|
||||
const item = this.data.get(key);
|
||||
let newValue: number;
|
||||
|
||||
if (!item) {
|
||||
newValue = 1;
|
||||
this.data.set(key, { value: '1' });
|
||||
} else {
|
||||
newValue = parseInt(item.value, 10) + 1;
|
||||
item.value = newValue.toString();
|
||||
}
|
||||
|
||||
await this.saveData();
|
||||
this.logger.debug(`自增Redis键: ${key}, 新值: ${newValue}`);
|
||||
return newValue;
|
||||
}
|
||||
|
||||
async sadd(key: string, member: string): Promise<void> {
|
||||
const item = this.data.get(key);
|
||||
let members: Set<string>;
|
||||
|
||||
if (!item) {
|
||||
members = new Set([member]);
|
||||
} else {
|
||||
members = new Set(JSON.parse(item.value));
|
||||
members.add(member);
|
||||
}
|
||||
|
||||
this.data.set(key, { value: JSON.stringify([...members]), expireAt: item?.expireAt });
|
||||
await this.saveData();
|
||||
this.logger.debug(`添加集合成员: ${key} -> ${member}`);
|
||||
}
|
||||
|
||||
async srem(key: string, member: string): Promise<void> {
|
||||
const item = this.data.get(key);
|
||||
|
||||
if (!item) {
|
||||
return;
|
||||
}
|
||||
|
||||
const members = new Set<string>(JSON.parse(item.value));
|
||||
members.delete(member);
|
||||
|
||||
if (members.size === 0) {
|
||||
this.data.delete(key);
|
||||
} else {
|
||||
item.value = JSON.stringify([...members]);
|
||||
}
|
||||
|
||||
await this.saveData();
|
||||
this.logger.debug(`移除集合成员: ${key} -> ${member}`);
|
||||
}
|
||||
|
||||
async smembers(key: string): Promise<string[]> {
|
||||
const item = this.data.get(key);
|
||||
|
||||
if (!item) {
|
||||
return [];
|
||||
}
|
||||
|
||||
// 检查是否过期
|
||||
if (item.expireAt && item.expireAt <= Date.now()) {
|
||||
this.data.delete(key);
|
||||
await this.saveData();
|
||||
return [];
|
||||
}
|
||||
|
||||
return JSON.parse(item.value);
|
||||
}
|
||||
}
|
||||
@@ -118,6 +118,56 @@ export class RealRedisService implements IRedisService, OnModuleDestroy {
|
||||
}
|
||||
}
|
||||
|
||||
async setex(key: string, ttl: number, value: string): Promise<void> {
|
||||
try {
|
||||
await this.redis.setex(key, ttl, value);
|
||||
this.logger.debug(`设置Redis键(setex): ${key}, TTL: ${ttl}秒`);
|
||||
} catch (error) {
|
||||
this.logger.error(`设置Redis键失败(setex): ${key}`, error);
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
|
||||
async incr(key: string): Promise<number> {
|
||||
try {
|
||||
const result = await this.redis.incr(key);
|
||||
this.logger.debug(`自增Redis键: ${key}, 新值: ${result}`);
|
||||
return result;
|
||||
} catch (error) {
|
||||
this.logger.error(`自增Redis键失败: ${key}`, error);
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
|
||||
async sadd(key: string, member: string): Promise<void> {
|
||||
try {
|
||||
await this.redis.sadd(key, member);
|
||||
this.logger.debug(`添加集合成员: ${key} -> ${member}`);
|
||||
} catch (error) {
|
||||
this.logger.error(`添加集合成员失败: ${key}`, error);
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
|
||||
async srem(key: string, member: string): Promise<void> {
|
||||
try {
|
||||
await this.redis.srem(key, member);
|
||||
this.logger.debug(`移除集合成员: ${key} -> ${member}`);
|
||||
} catch (error) {
|
||||
this.logger.error(`移除集合成员失败: ${key}`, error);
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
|
||||
async smembers(key: string): Promise<string[]> {
|
||||
try {
|
||||
return await this.redis.smembers(key);
|
||||
} catch (error) {
|
||||
this.logger.error(`获取集合成员失败: ${key}`, error);
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
|
||||
onModuleDestroy(): void {
|
||||
if (this.redis) {
|
||||
this.redis.disconnect();
|
||||
|
||||
@@ -11,6 +11,14 @@ export interface IRedisService {
|
||||
*/
|
||||
set(key: string, value: string, ttl?: number): Promise<void>;
|
||||
|
||||
/**
|
||||
* 设置键值对并指定过期时间
|
||||
* @param key 键
|
||||
* @param ttl 过期时间(秒)
|
||||
* @param value 值
|
||||
*/
|
||||
setex(key: string, ttl: number, value: string): Promise<void>;
|
||||
|
||||
/**
|
||||
* 获取值
|
||||
* @param key 键
|
||||
@@ -46,6 +54,34 @@ export interface IRedisService {
|
||||
*/
|
||||
ttl(key: string): Promise<number>;
|
||||
|
||||
/**
|
||||
* 自增
|
||||
* @param key 键
|
||||
* @returns 自增后的值
|
||||
*/
|
||||
incr(key: string): Promise<number>;
|
||||
|
||||
/**
|
||||
* 添加元素到集合
|
||||
* @param key 键
|
||||
* @param member 成员
|
||||
*/
|
||||
sadd(key: string, member: string): Promise<void>;
|
||||
|
||||
/**
|
||||
* 从集合移除元素
|
||||
* @param key 键
|
||||
* @param member 成员
|
||||
*/
|
||||
srem(key: string, member: string): Promise<void>;
|
||||
|
||||
/**
|
||||
* 获取集合所有成员
|
||||
* @param key 键
|
||||
* @returns 成员列表
|
||||
*/
|
||||
smembers(key: string): Promise<string[]>;
|
||||
|
||||
/**
|
||||
* 清空所有数据
|
||||
*/
|
||||
|
||||
Reference in New Issue
Block a user