Files
SuperBizAgent-java/mvp/architecture/session-management.md
T
zhuyongxin 60be51f4a5 docs: 重构文档结构,分离学习笔记和 MVP 架构设计
**变更概述:**
- 将 MVP 架构设计文档独立到项目根目录 `mvp/`
- 整理 `docs/` 为纯学习和分析文档目录
- 按类型分类:learning(学习)、analysis(分析)、reports(报告)、guides(指南)

**目录结构:**
```
mvp/                          # MVP 架构设计(独立)
├── README.md                 # 数据库设计总览
├── architecture/             # 架构文档
│   ├── agent-architecture-mvp.md
│   ├── implementation-plan.md
│   └── ...
└── tables/                   # 数据表设计

docs/                         # 学习和分析文档
├── learning/                 # 学习笔记(00-08 编号)
├── analysis/                 # 分析笔记 + 重构计划
├── reports/                  # 临时报告
└── guides/                   # 指南文档
```

**详细变更:**
- docs/README.md → mvp/README.md(数据库设计入口)
- docs/architecture/ → mvp/architecture/(架构设计)
- docs/tables/ → mvp/tables/(数据表设计)
- docs/学习笔记-*.md → docs/learning/07-*.md, 08-*.md
- docs/项目学习路径.md → docs/learning/00-*.md
- docs/功能分析报告.md → docs/analysis/
- docs/修复报告-*.md → docs/reports/
- docs/日志配置*.md → docs/guides/ 或 docs/reports/
- docs/design/ → docs/analysis/(问题分析和重构计划)
2026-06-23 14:14:51 +08:00

4.4 KiB
Raw Blame History

会话管理设计

会话存储策略

Redis(主)

数据结构:

key: session:{session_id}
value: {
  "sessionId": "sess-abc",
  "userId": "user-123",
  "currentDiagnosisId": "diag-001",
  "messages": [
    {"role": "user", "content": "诊断订单 A"},
    {"role": "assistant", "content": "完整报告..."}
  ],
  "context": {
    "province": "广东",
    "apiName": "社保查询",
    "errorCode": "40003"
  },
  "createdAt": "2024-06-15T14:30:00Z",
  "lastActiveAt": "2024-06-15T14:35:00Z"
}
ttl: 1800秒(30分钟)

优势:

  • ✅ 快速读写
  • ✅ 自动过期
  • ✅ 支持追问(保存上下文)

MySQL(辅助,可选)

同步策略:

  1. 重要会话同步

    • 有用户反馈的会话
    • 诊断失败的会话(BadCase)
    • 多轮对话 > 3 轮的会话
  2. 同步时机

    • 会话结束时(30分钟过期)
    • 用户反馈时(实时)
    • 定时任务(每小时,可选)
  3. 同步目标

    • conversation_history 表
    • 用于长期分析和审计

数据流设计

场景1:单次诊断(主流 80%)

1. 用户发起诊断
   POST /api/diagnosis/start
   {
     "orderId": "202406150001"
   }

2. 创建会话(Redis)
   key: session:sess-abc
   ttl: 1800秒

3. 创建诊断记录(MySQL)
   INSERT INTO diagnosis_record
   - diagnosis_id: diag-001
   - session_id: sess-abc
   - status: RUNNING

4. Agent 执行诊断
   - 调用工具(queryOrder, queryLogs, searchDoc...)
   - 生成报告

5. 更新诊断记录(MySQL)
   UPDATE diagnosis_record
   - status: SUCCESS
   - root_cause: "idCard字段缺失"
   - report_markdown: "完整报告..."

6. 返回报告
   → 大部分用户到此结束

场景2:追问(少数 20%)

1. 用户追问
   POST /api/chat
   {
     "sessionId": "sess-abc",
     "message": "为什么会缺失字段?"
   }

2. 从 Redis 获取上下文
   GET session:sess-abc
   - 有之前的诊断结果
   - 有对话历史

3. Agent 基于上下文回答
   - 不创建新的 diagnosis_record
   - 只是普通对话

4. 更新 Redis 会话
   - 追加对话历史
   - 刷新 TTL(重新计时30分钟)

5. 可选:保存到 conversation_history(MySQL)
   - 如果需要长期分析
   - 异步存储

场景3:同一会话多次诊断

1. 用户第一次诊断
   "诊断订单 A"
   → diagnosis_record(diag-001, session_id=sess-abc)

2. 用户第二次诊断
   "再诊断订单 B"
   → diagnosis_record(diag-002, session_id=sess-abc)

3. 会话关联
   - 同一个 session_id
   - 两条 diagnosis_record
   - Redis 中保存完整对话历史

会话生命周期

创建
  ↓
活跃(每次交互刷新TTL)
  ↓
30分钟无活动
  ↓
自动过期
  ↓
可选:同步到 MySQL(重要会话)

实现示例

Java 代码

@Service
public class SessionService {
    
    @Autowired
    private RedisTemplate<String, String> redisTemplate;
    
    private static final String SESSION_PREFIX = "session:";
    private static final Duration SESSION_TTL = Duration.ofMinutes(30);
    
    // 创建会话
    public String createSession(String userId) {
        String sessionId = UUID.randomUUID().toString();
        
        SessionData session = SessionData.builder()
            .sessionId(sessionId)
            .userId(userId)
            .messages(new ArrayList<>())
            .context(new HashMap<>())
            .createdAt(LocalDateTime.now())
            .lastActiveAt(LocalDateTime.now())
            .build();
        
        String key = SESSION_PREFIX + sessionId;
        redisTemplate.opsForValue().set(key, toJson(session), SESSION_TTL);
        
        return sessionId;
    }
    
    // 获取会话
    public SessionData getSession(String sessionId) {
        String key = SESSION_PREFIX + sessionId;
        String json = redisTemplate.opsForValue().get(key);
        return json != null ? fromJson(json) : null;
    }
    
    // 更新会话(刷新TTL)
    public void updateSession(SessionData session) {
        session.setLastActiveAt(LocalDateTime.now());
        String key = SESSION_PREFIX + session.getSessionId();
        redisTemplate.opsForValue().set(key, toJson(session), SESSION_TTL);
    }
    
    // 删除会话
    public void deleteSession(String sessionId) {
        String key = SESSION_PREFIX + sessionId;
        redisTemplate.delete(key);
    }
}