feat(chat): 新增会话管理支持多轮对话
- 引入 KeyboardAiChatSession 实体及对应 Mapper、Service - 为 KeyboardAiChatMessage 增加 session_id 字段 - ChatServiceImpl 保存消息时绑定会话,支持按用户+角色获取或创建活跃会话 - 保证同一用户同一角色的连续对话归属同一会话,实现多轮上下文管理
This commit is contained in:
@@ -0,0 +1,12 @@
|
|||||||
|
package com.yolo.keyborad.mapper;
|
||||||
|
|
||||||
|
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
|
||||||
|
import com.yolo.keyborad.model.entity.KeyboardAiChatSession;
|
||||||
|
|
||||||
|
/*
|
||||||
|
* @author: ziin
|
||||||
|
* @date: 2026/1/28 16:20
|
||||||
|
*/
|
||||||
|
|
||||||
|
public interface KeyboardAiChatSessionMapper extends BaseMapper<KeyboardAiChatSession> {
|
||||||
|
}
|
||||||
@@ -75,4 +75,8 @@ public class KeyboardAiChatMessage {
|
|||||||
@TableField(value = "created_at")
|
@TableField(value = "created_at")
|
||||||
@Schema(description="消息创建时间")
|
@Schema(description="消息创建时间")
|
||||||
private Date createdAt;
|
private Date createdAt;
|
||||||
|
|
||||||
|
@TableField(value = "session_id")
|
||||||
|
@Schema(description = "会话Id")
|
||||||
|
private Long sessionId;
|
||||||
}
|
}
|
||||||
@@ -0,0 +1,71 @@
|
|||||||
|
package com.yolo.keyborad.model.entity;
|
||||||
|
|
||||||
|
import com.baomidou.mybatisplus.annotation.IdType;
|
||||||
|
import com.baomidou.mybatisplus.annotation.TableField;
|
||||||
|
import com.baomidou.mybatisplus.annotation.TableId;
|
||||||
|
import com.baomidou.mybatisplus.annotation.TableName;
|
||||||
|
import io.swagger.v3.oas.annotations.media.Schema;
|
||||||
|
import java.util.Date;
|
||||||
|
import lombok.Data;
|
||||||
|
|
||||||
|
/*
|
||||||
|
* @author: ziin
|
||||||
|
* @date: 2026/1/28 16:20
|
||||||
|
*/
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 用户与AI陪伴角色的聊天会话表,用于支持聊天重置与关系重启
|
||||||
|
*/
|
||||||
|
@Schema(description="用户与AI陪伴角色的聊天会话表,用于支持聊天重置与关系重启")
|
||||||
|
@Data
|
||||||
|
@TableName(value = "keyboard_ai_chat_session")
|
||||||
|
public class KeyboardAiChatSession {
|
||||||
|
/**
|
||||||
|
* 聊天会话唯一ID
|
||||||
|
*/
|
||||||
|
@TableId(value = "id", type = IdType.AUTO)
|
||||||
|
@Schema(description="聊天会话唯一ID")
|
||||||
|
private Long id;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 用户ID
|
||||||
|
*/
|
||||||
|
@TableField(value = "user_id")
|
||||||
|
@Schema(description="用户ID")
|
||||||
|
private Long userId;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 陪伴角色ID
|
||||||
|
*/
|
||||||
|
@TableField(value = "companion_id")
|
||||||
|
@Schema(description="陪伴角色ID")
|
||||||
|
private Long companionId;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 会话重置版本号,用于标识第几次重新开始陪伴关系
|
||||||
|
*/
|
||||||
|
@TableField(value = "reset_version")
|
||||||
|
@Schema(description="会话重置版本号,用于标识第几次重新开始陪伴关系")
|
||||||
|
private Integer resetVersion;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 是否为当前活跃会话(true=当前使用中)
|
||||||
|
*/
|
||||||
|
@TableField(value = "is_active")
|
||||||
|
@Schema(description="是否为当前活跃会话(true=当前使用中)")
|
||||||
|
private Boolean isActive;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 会话创建时间
|
||||||
|
*/
|
||||||
|
@TableField(value = "created_at")
|
||||||
|
@Schema(description="会话创建时间")
|
||||||
|
private Date createdAt;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 会话结束时间(用户重置或系统关闭会话时记录)
|
||||||
|
*/
|
||||||
|
@TableField(value = "ended_at")
|
||||||
|
@Schema(description="会话结束时间(用户重置或系统关闭会话时记录)")
|
||||||
|
private Date endedAt;
|
||||||
|
}
|
||||||
@@ -0,0 +1,29 @@
|
|||||||
|
package com.yolo.keyborad.service;
|
||||||
|
|
||||||
|
import com.yolo.keyborad.model.entity.KeyboardAiChatSession;
|
||||||
|
import com.baomidou.mybatisplus.extension.service.IService;
|
||||||
|
|
||||||
|
/*
|
||||||
|
* @author: ziin
|
||||||
|
* @date: 2026/1/28 16:20
|
||||||
|
*/
|
||||||
|
public interface KeyboardAiChatSessionService extends IService<KeyboardAiChatSession> {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取用户与AI角色的活跃会话,如果不存在则创建新会话
|
||||||
|
*
|
||||||
|
* @param userId 用户ID
|
||||||
|
* @param companionId AI角色ID
|
||||||
|
* @return 活跃会话
|
||||||
|
*/
|
||||||
|
KeyboardAiChatSession getOrCreateActiveSession(Long userId, Long companionId);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取用户与AI角色的活跃会话ID,如果不存在则创建新会话
|
||||||
|
*
|
||||||
|
* @param userId 用户ID
|
||||||
|
* @param companionId AI角色ID
|
||||||
|
* @return 活跃会话ID
|
||||||
|
*/
|
||||||
|
Long getOrCreateActiveSessionId(Long userId, Long companionId);
|
||||||
|
}
|
||||||
@@ -89,6 +89,9 @@ public class ChatServiceImpl implements ChatService {
|
|||||||
@Resource
|
@Resource
|
||||||
private KeyboardAiChatMessageService aiChatMessageService;
|
private KeyboardAiChatMessageService aiChatMessageService;
|
||||||
|
|
||||||
|
@Resource
|
||||||
|
private KeyboardAiChatSessionService aiChatSessionService;
|
||||||
|
|
||||||
@Resource
|
@Resource
|
||||||
private ElevenLabsService elevenLabsService;
|
private ElevenLabsService elevenLabsService;
|
||||||
|
|
||||||
@@ -457,10 +460,14 @@ public class ChatServiceImpl implements ChatService {
|
|||||||
private void saveChatMessages(Long userId, Long companionId, String userContent, String aiResponse) {
|
private void saveChatMessages(Long userId, Long companionId, String userContent, String aiResponse) {
|
||||||
Date now = new Date();
|
Date now = new Date();
|
||||||
|
|
||||||
|
// 获取或创建活跃会话
|
||||||
|
Long sessionId = aiChatSessionService.getOrCreateActiveSessionId(userId, companionId);
|
||||||
|
|
||||||
// 保存用户消息 (sender=1)
|
// 保存用户消息 (sender=1)
|
||||||
KeyboardAiChatMessage userMessage = new KeyboardAiChatMessage();
|
KeyboardAiChatMessage userMessage = new KeyboardAiChatMessage();
|
||||||
userMessage.setUserId(userId);
|
userMessage.setUserId(userId);
|
||||||
userMessage.setCompanionId(companionId);
|
userMessage.setCompanionId(companionId);
|
||||||
|
userMessage.setSessionId(sessionId);
|
||||||
userMessage.setSender((short) 1);
|
userMessage.setSender((short) 1);
|
||||||
userMessage.setContent(userContent);
|
userMessage.setContent(userContent);
|
||||||
userMessage.setCreatedAt(now);
|
userMessage.setCreatedAt(now);
|
||||||
@@ -469,13 +476,14 @@ public class ChatServiceImpl implements ChatService {
|
|||||||
KeyboardAiChatMessage aiMessage = new KeyboardAiChatMessage();
|
KeyboardAiChatMessage aiMessage = new KeyboardAiChatMessage();
|
||||||
aiMessage.setUserId(userId);
|
aiMessage.setUserId(userId);
|
||||||
aiMessage.setCompanionId(companionId);
|
aiMessage.setCompanionId(companionId);
|
||||||
|
aiMessage.setSessionId(sessionId);
|
||||||
aiMessage.setSender((short) 2);
|
aiMessage.setSender((short) 2);
|
||||||
aiMessage.setContent(aiResponse);
|
aiMessage.setContent(aiResponse);
|
||||||
aiMessage.setCreatedAt(now);
|
aiMessage.setCreatedAt(now);
|
||||||
|
|
||||||
// 批量保存
|
// 批量保存
|
||||||
aiChatMessageService.saveBatch(List.of(userMessage, aiMessage));
|
aiChatMessageService.saveBatch(List.of(userMessage, aiMessage));
|
||||||
log.info("聊天记录保存成功, userId: {}, companionId: {}", userId, companionId);
|
log.info("聊天记录保存成功, userId: {}, companionId: {}, sessionId: {}", userId, companionId, sessionId);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
@@ -0,0 +1,58 @@
|
|||||||
|
package com.yolo.keyborad.service.impl;
|
||||||
|
|
||||||
|
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
|
||||||
|
import org.springframework.stereotype.Service;
|
||||||
|
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
|
||||||
|
import com.yolo.keyborad.mapper.KeyboardAiChatSessionMapper;
|
||||||
|
import com.yolo.keyborad.model.entity.KeyboardAiChatSession;
|
||||||
|
import com.yolo.keyborad.service.KeyboardAiChatSessionService;
|
||||||
|
|
||||||
|
import java.util.Date;
|
||||||
|
|
||||||
|
/*
|
||||||
|
* @author: ziin
|
||||||
|
* @date: 2026/1/28 16:20
|
||||||
|
*/
|
||||||
|
@Service
|
||||||
|
public class KeyboardAiChatSessionServiceImpl extends ServiceImpl<KeyboardAiChatSessionMapper, KeyboardAiChatSession> implements KeyboardAiChatSessionService {
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public KeyboardAiChatSession getOrCreateActiveSession(Long userId, Long companionId) {
|
||||||
|
// 查询当前活跃会话
|
||||||
|
LambdaQueryWrapper<KeyboardAiChatSession> queryWrapper = new LambdaQueryWrapper<>();
|
||||||
|
queryWrapper.eq(KeyboardAiChatSession::getUserId, userId)
|
||||||
|
.eq(KeyboardAiChatSession::getCompanionId, companionId)
|
||||||
|
.eq(KeyboardAiChatSession::getIsActive, true);
|
||||||
|
KeyboardAiChatSession activeSession = this.getOne(queryWrapper);
|
||||||
|
|
||||||
|
if (activeSession != null) {
|
||||||
|
return activeSession;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 不存在活跃会话,创建新会话
|
||||||
|
// 先查询该用户与该角色的最大版本号
|
||||||
|
LambdaQueryWrapper<KeyboardAiChatSession> maxVersionWrapper = new LambdaQueryWrapper<>();
|
||||||
|
maxVersionWrapper.eq(KeyboardAiChatSession::getUserId, userId)
|
||||||
|
.eq(KeyboardAiChatSession::getCompanionId, companionId)
|
||||||
|
.orderByDesc(KeyboardAiChatSession::getResetVersion)
|
||||||
|
.last("LIMIT 1");
|
||||||
|
KeyboardAiChatSession lastSession = this.getOne(maxVersionWrapper);
|
||||||
|
int newVersion = lastSession != null ? lastSession.getResetVersion() + 1 : 1;
|
||||||
|
|
||||||
|
// 创建新会话
|
||||||
|
KeyboardAiChatSession newSession = new KeyboardAiChatSession();
|
||||||
|
newSession.setUserId(userId);
|
||||||
|
newSession.setCompanionId(companionId);
|
||||||
|
newSession.setResetVersion(newVersion);
|
||||||
|
newSession.setIsActive(true);
|
||||||
|
newSession.setCreatedAt(new Date());
|
||||||
|
this.save(newSession);
|
||||||
|
|
||||||
|
return newSession;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Long getOrCreateActiveSessionId(Long userId, Long companionId) {
|
||||||
|
return getOrCreateActiveSession(userId, companionId).getId();
|
||||||
|
}
|
||||||
|
}
|
||||||
19
src/main/resources/mapper/KeyboardAiChatSessionMapper.xml
Normal file
19
src/main/resources/mapper/KeyboardAiChatSessionMapper.xml
Normal file
@@ -0,0 +1,19 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
|
||||||
|
<mapper namespace="com.yolo.keyborad.mapper.KeyboardAiChatSessionMapper">
|
||||||
|
<resultMap id="BaseResultMap" type="com.yolo.keyborad.model.entity.KeyboardAiChatSession">
|
||||||
|
<!--@mbg.generated-->
|
||||||
|
<!--@Table keyboard_ai_chat_session-->
|
||||||
|
<id column="id" jdbcType="BIGINT" property="id" />
|
||||||
|
<result column="user_id" jdbcType="BIGINT" property="userId" />
|
||||||
|
<result column="companion_id" jdbcType="BIGINT" property="companionId" />
|
||||||
|
<result column="reset_version" jdbcType="INTEGER" property="resetVersion" />
|
||||||
|
<result column="is_active" jdbcType="BOOLEAN" property="isActive" />
|
||||||
|
<result column="created_at" jdbcType="TIMESTAMP" property="createdAt" />
|
||||||
|
<result column="ended_at" jdbcType="TIMESTAMP" property="endedAt" />
|
||||||
|
</resultMap>
|
||||||
|
<sql id="Base_Column_List">
|
||||||
|
<!--@mbg.generated-->
|
||||||
|
id, user_id, companion_id, reset_version, is_active, created_at, ended_at
|
||||||
|
</sql>
|
||||||
|
</mapper>
|
||||||
Reference in New Issue
Block a user