📊 核心流程架构图
识别群聊/私聊类型
user-{用户ID} / group-{群ID}
获取 claude_session_id
获取 project_path
工作目录:project_path
捕获 session_id
累积文本缓冲
3秒或2000字符发送
更新会话活跃时间
保存 claude_session_id
💡 关键设计亮点
🔒 完全隔离
每个飞书会话拥有:
- 独立的 Claude 子进程
- 独立的工作目录
- 独立的会话上下文
- 独立的临时文件目录
💾 持久化存储
关键信息保存在 SQLite:
- conversation_id(会话标识)
- claude_session_id(子进程会话)
- project_path(工作目录)
- last_activity(最后活跃时间)
🔄 无状态恢复
服务重启后自动恢复:
- 从数据库读取 session_id
- 使用 --resume 恢复上下文
- 清理 24 小时过期会话
- 检测进程是否仍然存活
⚡ 流式优化
平衡实时性与性能:
- 累积缓冲区:2000 字符
- 最大延迟:3 秒
- JSON 流式解析
- 自动文件附件检测
🛡️ 安全防护
多重保护机制:
- 禁用 cd 命令(防止目录错乱)
- 工作目录只读原则
- 防并发竞争(预注册机制)
- 自动清理临时文件
🎯 智能路由
根据消息类型智能处理:
- 文件命令:直接发送附件
- Linux 命令:直接执行
- /clear:清空会话上下文
- 普通消息:调用 Claude 子进程
⏱️ 完整对话时序
第 1 步:消息接收
用户在飞书群聊中发送"帮我写个函数",飞书 WebSocket 客户端接收到事件,类型为 im.message.receive_v1
第 2 步:会话识别
会话管理器提取 chat_id 和 chat_type,生成会话标识:group-oc_8623156bb41f217a3822aca12362b068
第 3 步:数据库查询
查询 feishu_sessions 表,发现是新会话,返回:claude_session_id=null, project_path=/home/event
第 4 步:创建子进程
调用 spawn('claude', ['-p', '--output-format', 'stream-json', '帮我写个函数'], { cwd: '/home/event' })
第 5 步:捕获会话标识
子进程输出 {"type":"system","session_id":"session-abc123"},写入器捕获并保存到数据库
第 6 步:流式响应
子进程持续输出 JSON,解析 text 字段累积到缓冲区,每 3 秒或 2000 字符发送到飞书
第 7 步:完成并清理
子进程退出,发送剩余缓冲区内容,更新 last_activity,从进程池移除,清理临时文件
第 8 步:后续对话
用户再次发送"改一下这个函数",使用 --resume=session-abc123 恢复上下文,Claude 自动记住之前的对话
🔄 会话恢复机制详解
🎯 首次对话
创建新会话的完整流程:
- 步骤 1:spawn Claude 子进程,不带 --resume 参数
- 步骤 2:捕获系统输出的 session_id(如:session-abc123)
- 步骤 3:保存到数据库:UPDATE feishu_sessions SET claude_session_id='session-abc123'
- 步骤 4:注册到进程池:activeClaudeProcesses.set('session-abc123', process)
💬 后续对话
在同一会话中继续对话:
- 步骤 1:从数据库读取 claude_session_id='session-abc123'
- 步骤 2:spawn 时添加 --resume=session-abc123
- 步骤 3:Claude 自动恢复上下文(对话历史 + 文件操作)
- 步骤 4:进程退出后不清除数据库的 session_id
🔧 服务重启
重启后的智能恢复逻辑:
- 启动时:批量清理 24 小时未活跃的 session_id
- 首次请求:检测 session_id 是否在进程池中
- 若不存在:清空 session_id,下次创建新会话
- 若存在:继续使用(但重启后通常不存在)
🧹 自动清理
防止会话泄漏的策略:
- 定时清理:启动时清理 24 小时未活跃会话
- 进程退出:从 activeClaudeProcesses 移除
- 临时文件:清理 ~/.claude-logs/ 下的 cwd 文件
- /clear 命令:手动清空 session_id 重新开始
🛡️ 工作目录保护机制
❌ 问题场景
用户执行以下操作序列:
- cd /home/event/subdir(内存中 project_path 被修改)
- 创建文件(文件被创建到 subdir)
- 服务重启(内存状态丢失)
- 继续对话(project_path 恢复为 /home/event)
- 后续文件被创建到错误位置
✅ 解决方案
完全禁用 cd 命令:
- 检测到 cd 命令时返回提示信息
- 告知工作目录固定为 project_path
- 建议使用相对路径访问子目录
- 示例:ls subdir/ 或 cat subdir/file.txt
🔒 只读原则
工作目录管理规则:
- project_path 在会话创建时由系统确定
- 运行时任何代码都不允许修改
- 数据库中无 updateProjectPath 函数(有意为之)
- 需要修改必须手动操作数据库 + 重启服务
🗺️ 群聊目录映射
当前绑定关系:
- 1-市场活动:/home/event
- 2-案例库:/home/case
- 3-WebX:/home/webx
- 私聊用户:./feicc/user-{open_id}
⚙️ 并发控制与竞争防护
竞争场景
用户在 1 秒内发送 3 条消息,每条消息触发 spawn 调用,但 session_id 的捕获是异步的,可能导致进程池中出现重复或丢失
预注册机制
在 spawn 之前,先用 processKey(临时标识)在 activeClaudeProcesses 中占位,值为 'pending',防止并发请求通过"会话繁忙"检查
进程替换
spawn 成功后,将 'pending' 替换为实际进程对象。捕获到 session_id 后,如果 processKey 不同,则更新映射关系
繁忙检测
isSessionBusy() 检查 activeClaudeProcesses 中是否存在该 session_id,存在则返回"正在处理中,请稍候"
💾 数据库结构设计
📋 feishu_sessions 表
核心会话信息:
- id:自增主键
- conversation_id:唯一会话标识(user-xxx / group-xxx)
- claude_session_id:Claude 子进程会话 ID
- project_path:工作目录路径
- session_type:会话类型(private / group)
- last_activity:最后活跃时间
📝 feishu_message_log 表
消息历史记录:
- session_id:关联会话 ID
- direction:消息方向(incoming / outgoing)
- content_type:内容类型(text / file / command)
- content:消息内容
- created_at:创建时间
🔑 查询示例
常用数据库操作:
- 查询会话:SELECT * FROM feishu_sessions WHERE conversation_id='group-oc_xxx'
- 更新活跃时间:UPDATE feishu_sessions SET last_activity=datetime('now') WHERE id=1
- 清理过期会话:UPDATE feishu_sessions SET claude_session_id=NULL WHERE last_activity < datetime('now', '-24 hours')
🔍 调试命令
故障排查工具:
- 查看活跃会话:sqlite3 server/database/auth.db "SELECT * FROM feishu_sessions WHERE claude_session_id IS NOT NULL;"
- 检查子进程:ps aux | grep "claude.*resume"
- 查看消息历史:SELECT * FROM feishu_message_log ORDER BY created_at DESC LIMIT 20;
🎓 架构总结
✨ 优势
- 完全隔离:不同用户/群聊互不干扰
- 状态持久:服务重启自动恢复
- 扩展性强:可部署多个实例共享数据库
- 成本低:基于官方 Claude CLI,无需重复造轮子
⚠️ 注意事项
- Claude CLI 需要授权(gaccode token)
- 会话上下文有容量限制(200K tokens)
- 长时间未使用的会话会过期
- 子进程内存占用需要监控
🚀 性能优化
- 流式响应:3秒缓冲降低消息频率
- 预注册机制:防止并发竞争
- 自动清理:定期清理过期会话
- 临时文件隔离:避免磁盘占用
🔮 未来优化
- 支持会话导出/导入
- 实现会话共享机制
- 增加使用量统计
- 支持多租户隔离