package com.xly.service; import com.fasterxml.jackson.databind.ObjectMapper; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.data.redis.core.StringRedisTemplate; import org.springframework.stereotype.Service; import java.time.Duration; import java.util.ArrayList; import java.util.LinkedHashMap; import java.util.List; import java.util.Map; /** * 会话事件日志(append-only)—— 会话的**唯一事实源**:用户话、模型消息(含工具调用/结果的原样载荷)、 * 表单/提议/确认等按钮事件,全部按发生顺序落一条 Redis LIST。前端历史与 LLM 上下文都是它的投影 * ({@link EventProjectionService}),不再有 chat:mem 整包读改写与多处手工同步。 * *
键:{@code chat:ledger:{convId}},元素为事件 JSON {@code {t,type,...}},30 天 TTL; * rightPush 原子,任意并发写者(对话流、确认端点)互不覆盖。 * *
事件类型:
* {@code user}(text, internal?)/ {@code ai}(text)/
* {@code tool_call}(payload=原样序列化的模型消息, text, tools)/
* {@code tool_result}(tcId, name, text, digest)/
* {@code form_submit}(entity, fields)/ {@code proposal}(opId, summary)/
* {@code confirm}(opId, status, msg, description)/ {@code cancel}(opId, description)/
* {@code skill_active}(name, text)/ {@code skill_done}(name);
* 旧版遗留类型 {@code assistant/clarify/form/question/tool} 仍可读(兼容渲染,自然淘汰)。
*/
@Service
public class LedgerService {
private static final Logger log = LoggerFactory.getLogger(LedgerService.class);
private static final String PREFIX = "chat:ledger:";
private static final Duration TTL = Duration.ofDays(30);
private final StringRedisTemplate redis;
private final ObjectMapper mapper;
public LedgerService(StringRedisTemplate redis, ObjectMapper mapper) {
this.redis = redis;
this.mapper = mapper;
}
/** 追加一条事件(绝不抛异常——日志失败不能影响对话主流程)。 */
public void append(String convId, String type, Map