办学质量监测教学评价系统
ageer
2025-05-03 c2f6a8321a0fa9296818ca89d0f2ab9a53cc93fe
feat: 会话管理
已修改6个文件
已添加6个文件
392 ■■■■■ 文件已修改
ruoyi-common/ruoyi-common-chat/src/main/java/org/ruoyi/common/chat/request/ChatRequest.java 6 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-modules-api/ruoyi-chat-api/src/main/java/org/ruoyi/domain/ChatMessage.java 5 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-modules-api/ruoyi-chat-api/src/main/java/org/ruoyi/domain/ChatSession.java 51 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-modules-api/ruoyi-chat-api/src/main/java/org/ruoyi/domain/bo/ChatMessageBo.java 5 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-modules-api/ruoyi-chat-api/src/main/java/org/ruoyi/domain/bo/ChatSessionBo.java 54 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-modules-api/ruoyi-chat-api/src/main/java/org/ruoyi/domain/vo/ChatMessageVo.java 5 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-modules-api/ruoyi-chat-api/src/main/java/org/ruoyi/domain/vo/ChatSessionVo.java 59 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-modules-api/ruoyi-chat-api/src/main/java/org/ruoyi/mapper/ChatSessionMapper.java 15 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-modules-api/ruoyi-chat-api/src/main/java/org/ruoyi/service/IChatSessionService.java 48 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-modules-api/ruoyi-chat-api/src/main/java/org/ruoyi/service/impl/ChatSessionServiceImpl.java 111 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-modules/ruoyi-chat/src/main/java/org/ruoyi/chat/service/chat/impl/ChatCostServiceImpl.java 2 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-modules/ruoyi-chat/src/main/java/org/ruoyi/chat/service/chat/impl/SseServiceImpl.java 31 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-common/ruoyi-common-chat/src/main/java/org/ruoyi/common/chat/request/ChatRequest.java
@@ -56,6 +56,12 @@
     */
    private Long userId;
    /**
     * ä¼šè¯id
     */
    private Long sessionId;
    /**
     * åº”用ID
     */
ruoyi-modules-api/ruoyi-chat-api/src/main/java/org/ruoyi/domain/ChatMessage.java
@@ -35,6 +35,11 @@
    private Long userId;
    /**
     * ä¼šè¯id
     */
    private Long sessionId;
    /**
     * æ¶ˆæ¯å†…容
     */
    private String content;
ruoyi-modules-api/ruoyi-chat-api/src/main/java/org/ruoyi/domain/ChatSession.java
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,51 @@
package org.ruoyi.domain;
import com.baomidou.mybatisplus.annotation.*;
import lombok.Data;
import lombok.EqualsAndHashCode;
import org.ruoyi.core.domain.BaseEntity;
import java.io.Serial;
/**
 * ä¼šè¯ç®¡ç†å¯¹è±¡ chat_session
 *
 * @author ageerle
 * @date 2025-05-03
 */
@Data
@EqualsAndHashCode(callSuper = true)
@TableName("chat_session")
public class ChatSession extends BaseEntity {
    @Serial
    private static final long serialVersionUID = 1L;
    /**
     * ä¸»é”®
     */
    @TableId(value = "id")
    private Long id;
    /**
     * ç”¨æˆ·id
     */
    private Long userId;
    /**
     * ä¼šè¯æ ‡é¢˜
     */
    private String sessionTitle;
    /**
     * ä¼šè¯å†…容
     */
    private String sessionContent;
    /**
     * å¤‡æ³¨
     */
    private String remark;
}
ruoyi-modules-api/ruoyi-chat-api/src/main/java/org/ruoyi/domain/bo/ChatMessageBo.java
@@ -41,6 +41,11 @@
    private String content;
    /**
     * ä¼šè¯id
     */
    private Long sessionId;
    /**
     * å¯¹è¯è§’色
     */
    @NotBlank(message = "对话角色不能为空", groups = { AddGroup.class, EditGroup.class })
ruoyi-modules-api/ruoyi-chat-api/src/main/java/org/ruoyi/domain/bo/ChatSessionBo.java
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,54 @@
package org.ruoyi.domain.bo;
import org.ruoyi.common.core.validate.AddGroup;
import org.ruoyi.common.core.validate.EditGroup;
import org.ruoyi.core.domain.BaseEntity;
import io.github.linpeilie.annotations.AutoMapper;
import lombok.Data;
import lombok.EqualsAndHashCode;
import jakarta.validation.constraints.*;
import org.ruoyi.domain.ChatSession;
/**
 * ä¼šè¯ç®¡ç†ä¸šåŠ¡å¯¹è±¡ chat_session
 *
 * @author ageerle
 * @date 2025-05-03
 */
@Data
@EqualsAndHashCode(callSuper = true)
@AutoMapper(target = ChatSession.class, reverseConvertGenerate = false)
public class ChatSessionBo extends BaseEntity {
    /**
     * ä¸»é”®
     */
    @NotNull(message = "主键不能为空", groups = { EditGroup.class })
    private Long id;
    /**
     * ç”¨æˆ·id
     */
    @NotNull(message = "用户id不能为空", groups = { AddGroup.class, EditGroup.class })
    private Long userId;
    /**
     * ä¼šè¯æ ‡é¢˜
     */
    @NotBlank(message = "会话标题不能为空", groups = { AddGroup.class, EditGroup.class })
    private String sessionTitle;
    /**
     * ä¼šè¯å†…容
     */
    @NotBlank(message = "会话内容不能为空", groups = { AddGroup.class, EditGroup.class })
    private String sessionContent;
    /**
     * å¤‡æ³¨
     */
    @NotBlank(message = "备注不能为空", groups = { AddGroup.class, EditGroup.class })
    private String remark;
}
ruoyi-modules-api/ruoyi-chat-api/src/main/java/org/ruoyi/domain/vo/ChatMessageVo.java
@@ -42,6 +42,11 @@
    private Long userId;
    /**
     * ä¼šè¯id
     */
    private Long sessionId;
    /**
     * æ¶ˆæ¯å†…容
     */
    @ExcelProperty(value = "消息内容")
ruoyi-modules-api/ruoyi-chat-api/src/main/java/org/ruoyi/domain/vo/ChatSessionVo.java
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,59 @@
package org.ruoyi.domain.vo;
import com.alibaba.excel.annotation.ExcelIgnoreUnannotated;
import com.alibaba.excel.annotation.ExcelProperty;
import io.github.linpeilie.annotations.AutoMapper;
import lombok.Data;
import org.ruoyi.domain.ChatSession;
import java.io.Serial;
import java.io.Serializable;
/**
 * ä¼šè¯ç®¡ç†è§†å›¾å¯¹è±¡ chat_session
 *
 * @author ageerle
 * @date 2025-05-03
 */
@Data
@ExcelIgnoreUnannotated
@AutoMapper(target = ChatSession.class)
public class ChatSessionVo implements Serializable {
    @Serial
    private static final long serialVersionUID = 1L;
    /**
     * ä¸»é”®
     */
    @ExcelProperty(value = "主键")
    private Long id;
    /**
     * ç”¨æˆ·id
     */
    @ExcelProperty(value = "用户id")
    private Long userId;
    /**
     * ä¼šè¯æ ‡é¢˜
     */
    @ExcelProperty(value = "会话标题")
    private String sessionTitle;
    /**
     * ä¼šè¯å†…容
     */
    @ExcelProperty(value = "会话内容")
    private String sessionContent;
    /**
     * å¤‡æ³¨
     */
    @ExcelProperty(value = "备注")
    private String remark;
}
ruoyi-modules-api/ruoyi-chat-api/src/main/java/org/ruoyi/mapper/ChatSessionMapper.java
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,15 @@
package org.ruoyi.mapper;
import org.ruoyi.core.mapper.BaseMapperPlus;
import org.ruoyi.domain.ChatSession;
import org.ruoyi.domain.vo.ChatSessionVo;
/**
 * ä¼šè¯ç®¡ç†Mapper接口
 *
 * @author ageerle
 * @date 2025-05-03
 */
public interface ChatSessionMapper extends BaseMapperPlus<ChatSession, ChatSessionVo> {
}
ruoyi-modules-api/ruoyi-chat-api/src/main/java/org/ruoyi/service/IChatSessionService.java
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,48 @@
package org.ruoyi.service;
import org.ruoyi.core.page.PageQuery;
import org.ruoyi.core.page.TableDataInfo;
import org.ruoyi.domain.bo.ChatSessionBo;
import org.ruoyi.domain.vo.ChatSessionVo;
import java.util.Collection;
import java.util.List;
/**
 * ä¼šè¯ç®¡ç†Service接口
 *
 * @author ageerle
 * @date 2025-05-03
 */
public interface IChatSessionService {
    /**
     * æŸ¥è¯¢ä¼šè¯ç®¡ç†
     */
    ChatSessionVo queryById(Long id);
    /**
     * æŸ¥è¯¢ä¼šè¯ç®¡ç†åˆ—表
     */
    TableDataInfo<ChatSessionVo> queryPageList(ChatSessionBo bo, PageQuery pageQuery);
    /**
     * æŸ¥è¯¢ä¼šè¯ç®¡ç†åˆ—表
     */
    List<ChatSessionVo> queryList(ChatSessionBo bo);
    /**
     * æ–°å¢žä¼šè¯ç®¡ç†
     */
    Boolean insertByBo(ChatSessionBo bo);
    /**
     * ä¿®æ”¹ä¼šè¯ç®¡ç†
     */
    Boolean updateByBo(ChatSessionBo bo);
    /**
     * æ ¡éªŒå¹¶æ‰¹é‡åˆ é™¤ä¼šè¯ç®¡ç†ä¿¡æ¯
     */
    Boolean deleteWithValidByIds(Collection<Long> ids, Boolean isValid);
}
ruoyi-modules-api/ruoyi-chat-api/src/main/java/org/ruoyi/service/impl/ChatSessionServiceImpl.java
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,111 @@
package org.ruoyi.service.impl;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.toolkit.Wrappers;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import org.ruoyi.common.core.utils.MapstructUtils;
import org.ruoyi.common.core.utils.StringUtils;
import org.ruoyi.core.page.TableDataInfo;
import org.ruoyi.core.page.PageQuery;
import lombok.RequiredArgsConstructor;
import org.ruoyi.domain.ChatSession;
import org.ruoyi.domain.bo.ChatSessionBo;
import org.ruoyi.domain.vo.ChatSessionVo;
import org.ruoyi.mapper.ChatSessionMapper;
import org.ruoyi.service.IChatSessionService;
import org.springframework.stereotype.Service;
import java.util.List;
import java.util.Map;
import java.util.Collection;
/**
 * ä¼šè¯ç®¡ç†Service业务层处理
 *
 * @author ageerle
 * @date 2025-05-03
 */
@RequiredArgsConstructor
@Service
public class ChatSessionServiceImpl implements IChatSessionService {
    private final ChatSessionMapper baseMapper;
    /**
     * æŸ¥è¯¢ä¼šè¯ç®¡ç†
     */
    @Override
    public ChatSessionVo queryById(Long id){
        return baseMapper.selectVoById(id);
    }
    /**
     * æŸ¥è¯¢ä¼šè¯ç®¡ç†åˆ—表
     */
    @Override
    public TableDataInfo<ChatSessionVo> queryPageList(ChatSessionBo bo, PageQuery pageQuery) {
        LambdaQueryWrapper<ChatSession> lqw = buildQueryWrapper(bo);
        Page<ChatSessionVo> result = baseMapper.selectVoPage(pageQuery.build(), lqw);
        return TableDataInfo.build(result);
    }
    /**
     * æŸ¥è¯¢ä¼šè¯ç®¡ç†åˆ—表
     */
    @Override
    public List<ChatSessionVo> queryList(ChatSessionBo bo) {
        LambdaQueryWrapper<ChatSession> lqw = buildQueryWrapper(bo);
        return baseMapper.selectVoList(lqw);
    }
    private LambdaQueryWrapper<ChatSession> buildQueryWrapper(ChatSessionBo bo) {
        Map<String, Object> params = bo.getParams();
        LambdaQueryWrapper<ChatSession> lqw = Wrappers.lambdaQuery();
        lqw.eq(bo.getUserId() != null, ChatSession::getUserId, bo.getUserId());
        lqw.eq(StringUtils.isNotBlank(bo.getSessionTitle()), ChatSession::getSessionTitle, bo.getSessionTitle());
        lqw.eq(StringUtils.isNotBlank(bo.getSessionContent()), ChatSession::getSessionContent, bo.getSessionContent());
        return lqw;
    }
    /**
     * æ–°å¢žä¼šè¯ç®¡ç†
     */
    @Override
    public Boolean insertByBo(ChatSessionBo bo) {
        ChatSession add = MapstructUtils.convert(bo, ChatSession.class);
        validEntityBeforeSave(add);
        boolean flag = baseMapper.insert(add) > 0;
        if (flag) {
            bo.setId(add.getId());
        }
        return flag;
    }
    /**
     * ä¿®æ”¹ä¼šè¯ç®¡ç†
     */
    @Override
    public Boolean updateByBo(ChatSessionBo bo) {
        ChatSession update = MapstructUtils.convert(bo, ChatSession.class);
        validEntityBeforeSave(update);
        return baseMapper.updateById(update) > 0;
    }
    /**
     * ä¿å­˜å‰çš„æ•°æ®æ ¡éªŒ
     */
    private void validEntityBeforeSave(ChatSession entity){
        //TODO åšä¸€äº›æ•°æ®æ ¡éªŒ,如唯一约束
    }
    /**
     * æ‰¹é‡åˆ é™¤ä¼šè¯ç®¡ç†
     */
    @Override
    public Boolean deleteWithValidByIds(Collection<Long> ids, Boolean isValid) {
        if(isValid){
            //TODO åšä¸€äº›ä¸šåŠ¡ä¸Šçš„æ ¡éªŒ,判断是否需要校验
        }
        return baseMapper.deleteBatchIds(ids) > 0;
    }
}
ruoyi-modules/ruoyi-chat/src/main/java/org/ruoyi/chat/service/chat/impl/ChatCostServiceImpl.java
@@ -46,6 +46,7 @@
    /**
     * æ‰£é™¤ç”¨æˆ·ä½™é¢
     */
    @Override
    public void deductToken(ChatRequest chatRequest) {
        int tokens = TikTokensUtil.tokens(chatRequest.getModel(), chatRequest.getPrompt());
@@ -53,6 +54,7 @@
        String modelName = chatRequest.getModel();
        ChatMessageBo chatMessageBo = new ChatMessageBo();
        chatMessageBo.setSessionId(chatRequest.getSessionId());
        Object userId = LocalCache.CACHE.get("userId");
        if(userId!=null){
ruoyi-modules/ruoyi-chat/src/main/java/org/ruoyi/chat/service/chat/impl/SseServiceImpl.java
@@ -24,9 +24,12 @@
import org.ruoyi.common.core.utils.file.FileUtils;
import org.ruoyi.common.core.utils.file.MimeTypeUtils;
import org.ruoyi.common.redis.utils.RedisUtils;
import org.ruoyi.domain.ChatSession;
import org.ruoyi.domain.bo.ChatSessionBo;
import org.ruoyi.domain.vo.ChatModelVo;
import org.ruoyi.service.EmbeddingService;
import org.ruoyi.service.IChatModelService;
import org.ruoyi.service.IChatSessionService;
import org.ruoyi.service.VectorStoreService;
import org.springframework.core.io.InputStreamResource;
import org.springframework.core.io.Resource;
@@ -65,6 +68,8 @@
    private final OllamaServiceImpl ollamaService;
    private final IChatSessionService chatSessionService;
    private ChatModelVo chatModelVo;
@@ -80,6 +85,15 @@
            }else {
                LocalCache.CACHE.put("userId", chatCostService.getUserId());
                chatRequest.setUserId(chatCostService.getUserId());
                // ä¿å­˜ä¼šè¯ä¿¡æ¯
                if(chatRequest.getSessionId()==null){
                    ChatSessionBo chatSessionBo = new ChatSessionBo();
                    chatSessionBo.setUserId(chatCostService.getUserId());
                    chatSessionBo.setSessionTitle(getFirst10Characters(chatRequest.getPrompt()));
                    chatSessionBo.setSessionContent(chatRequest.getPrompt());
                    chatSessionService.insertByBo(chatSessionBo);
                    chatRequest.setSessionId(chatSessionBo.getId());
                }
                // ä¿å­˜æ¶ˆæ¯è®°å½• å¹¶æ‰£é™¤è´¹ç”¨
                chatCostService.deductToken(chatRequest);
            }
@@ -93,6 +107,23 @@
    }
    /**
     * èŽ·å–å¯¹è¯æ ‡é¢˜
     *
     * @param str åŽŸå­—ç¬¦
     * @return æˆªå–后的字符
     */
    public static String getFirst10Characters(String str) {
        // åˆ¤æ–­å­—符串长度
        if (str.length() > 10) {
            // å¦‚果长度大于10,截取前10个字符
            return str.substring(0, 10);
        } else {
            // å¦‚果长度不足10,返回整个字符串
            return str;
        }
    }
    /**
     * æ£€æŸ¥æœªç™»å½•用户是否超过当日对话次数限制
     *
     * @param request å½“前请求