Commit 0b99f865 authored by 林洋洋's avatar 林洋洋

调整项目结构

parent 9602b9a8
package com.ask.api.dto;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
import java.util.List;
/**
* 文档分段结果DTO
*
* @author ai
* @date 2024/12/19
*/
@Data
@Schema(description = "文档分段结果")
public class DocumentSegmentResult {
/**
* 文件名
*/
@Schema(description = "文件名")
private String fileName;
/**
* 文档分段列表
*/
@Schema(description = "文档分段列表")
private List<DocumentSegment> segments;
/**
* 总分段数
*/
@Schema(description = "总分段数")
private Integer totalSegments;
/**
* 文档总字符数
*/
@Schema(description = "文档总字符数")
private Integer totalCharacters;
/**
* 文档分段详情
*/
@Data
@Schema(description = "文档分段详情")
public static class DocumentSegment {
/**
* 分段序号
*/
@Schema(description = "分段序号")
private Integer index;
/**
* 分段内容
*/
@Schema(description = "分段内容")
private String content;
/**
* 分段字符数
*/
@Schema(description = "分段字符数")
private Integer charCount;
/**
* 分段token数(预估)
*/
@Schema(description = "分段token数(预估)")
private Integer tokenCount;
}
}
\ No newline at end of file
package com.ask.api.dto;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
@Data
public class TableDto {
/**
* 表名
*/
@Schema(description = "表名")
private String tableName;
/**
* 表名
*/
@Schema(description = "表注释")
private String tableComment;
}
package com.ask.api.dto;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
@Data
public class TableParam {
@Schema(description = "数据源名称")
private String dsName;
/**
* 数据源类型
*/
@Schema(description = "数据源类型")
private String dbType;
/**
* 表名
*/
@Schema(description = "表名")
private String tableName;
}
package com.ask.api.entity;
import com.ask.common.base.BaseEntity;
import com.baomidou.mybatisplus.annotation.IdType;
import com.baomidou.mybatisplus.annotation.TableId;
import com.baomidou.mybatisplus.annotation.TableLogic;
import com.baomidou.mybatisplus.annotation.TableName;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
import lombok.EqualsAndHashCode;
/**
* 对话记录实体
*
* @author ai
* @date 2024/03/21
*/
@Data
@TableName("ask_chat_conversation")
@EqualsAndHashCode(callSuper = true)
@Schema(description = "对话记录")
public class ChatConversation extends BaseEntity {
private static final long serialVersionUID = 1L;
/**
* 主键ID
*/
@TableId(type = IdType.AUTO)
@Schema(description = "主键ID")
private Long id;
/**
* 会话ID
*/
@Schema(description = "会话ID")
private String conversationId;
/**
* 标题
*/
@Schema(description = "标题")
private String title = "新建会话窗口";
/**
* 智能体ID
*/
@Schema(description = "智能体ID")
private Integer agentId;
/**
* 用户ID
*/
@Schema(description = "用户ID")
private Long userId;
/**
* 删除标记,0未删除,1已删除
*/
@TableLogic
private String delFlag;
}
\ No newline at end of file
package com.ask.api.entity;
import com.baomidou.mybatisplus.annotation.IdType;
import com.baomidou.mybatisplus.annotation.TableId;
import com.baomidou.mybatisplus.annotation.TableLogic;
import com.baomidou.mybatisplus.annotation.TableName;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
import java.time.LocalDateTime;
/**
* AI对话记忆存储实体
*
* @author ai
* @date 2024/03/21
*/
@Data
@TableName("ask_chat_conversation_detail")
@Schema(description = "AI对话记忆存储")
public class ChatConversationDetail {
/**
* 主键ID
*/
@TableId(type = IdType.AUTO)
@Schema(description = "主键ID")
private Long id;
/**
* 会话ID
*/
@Schema(description = "会话ID")
private String conversationId;
/**
* 消息内容
*/
@Schema(description = "消息内容")
private String content;
/**
* 消息类型(user/assistant)
*/
@Schema(description = "消息类型")
private String type;
/**
* 时间戳
*/
@Schema(description = "时间戳")
private LocalDateTime timestamp;
/**
* 删除标记,0未删除,1已删除
*/
@TableLogic
private String delFlag;
}
\ No newline at end of file
/*
* Copyright (c) 2018-2025, lengleng All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
* Redistributions of source code must retain the above copyright notice,
* this list of conditions and the following disclaimer.
* Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
* Neither the name of the pig4cloud.com developer nor the names of its
* contributors may be used to endorse or promote products derived from
* this software without specific prior written permission.
* Author: lengleng (wangiegie@gmail.com)
*/
package com.ask.api.entity;
import com.baomidou.mybatisplus.annotation.*;
import com.baomidou.mybatisplus.extension.activerecord.Model;
import lombok.Data;
import lombok.EqualsAndHashCode;
import java.time.LocalDateTime;
/**
* 数据源表
*
* @author lengleng
* @date 2019-03-31 16:00:20
*/
@Data
@TableName("gen_datasource_conf")
@EqualsAndHashCode(callSuper = true)
public class GenDatasourceConf extends Model<GenDatasourceConf> {
private static final long serialVersionUID = 1L;
/**
* 主键
*/
@TableId(type = IdType.AUTO)
private Long id;
/**
* 名称
*/
private String name;
/**
* 数据库类型
*/
private String dsType;
/**
* 配置类型 (0 主机形式 | 1 url形式)
*/
private Integer confType;
/**
* 主机地址
*/
private String host;
/**
* 端口
*/
private Integer port;
/**
* jdbc-url
*/
private String url;
/**
* 实例
*/
private String instance;
/**
* 数据库名称
*/
private String dsName;
/**
* 用户名
*/
private String username;
/**
* 密码
*/
private String password;
/**
* 创建时间
*/
@TableField(fill = FieldFill.INSERT)
private LocalDateTime createTime;
/**
* 修改时间
*/
@TableField(fill = FieldFill.UPDATE)
private LocalDateTime updateTime;
/**
* 0-正常,1-删除
*/
@TableLogic
@TableField(fill = FieldFill.INSERT)
private String delFlag;
}
/*
* Copyright (c) 2018-2025, lengleng All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
* Redistributions of source code must retain the above copyright notice,
* this list of conditions and the following disclaimer.
* Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
* Neither the name of the pig4cloud.com developer nor the names of its
* contributors may be used to endorse or promote products derived from
* this software without specific prior written permission.
* Author: lengleng (wangiegie@gmail.com)
*/
package com.ask.api.entity;
import com.baomidou.mybatisplus.annotation.IdType;
import com.baomidou.mybatisplus.annotation.TableId;
import com.baomidou.mybatisplus.extension.activerecord.Model;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
import lombok.EqualsAndHashCode;
import java.time.LocalDateTime;
/**
* 列属性
*
* @author pigx code generator
* @date 2023-02-06 20:34:55
*/
@Data
@EqualsAndHashCode(callSuper = true)
@Schema(description = "列属性")
public class GenTable extends Model<GenTable> {
private static final long serialVersionUID = 1L;
/**
* id
*/
@TableId(type = IdType.AUTO)
@Schema(description = "id")
private Long id;
/**
* 数据源名称
*/
@Schema(description = "数据源名称")
private String dsName;
/**
* 数据源类型
*/
@Schema(description = "数据源类型")
private String dbType;
/**
* 表名
*/
@Schema(description = "表名")
private String tableName;
/**
* 类名
*/
@Schema(description = "类名")
private String className;
/**
* 说明
*/
@Schema(description = "说明")
private String tableComment;
/**
* 作者
*/
@Schema(description = "作者")
private String author;
/**
* 邮箱
*/
@Schema(description = "邮箱")
private String email;
/**
* 项目包名
*/
@Schema(description = "项目包名")
private String packageName;
/**
* 项目版本号
*/
@Schema(description = "项目版本号")
private String version;
/**
* 生成方式 0:zip压缩包 1:自定义目录
*/
@Schema(description = "生成方式 0:zip压缩包 1:自定义目录")
private String generatorType;
/**
* 后端生成路径
*/
@Schema(description = "后端生成路径")
private String backendPath;
/**
* 前端生成路径
*/
@Schema(description = "前端生成路径")
private String frontendPath;
/**
* 模块名
*/
@Schema(description = "模块名")
private String moduleName;
/**
* 功能名
*/
@Schema(description = "功能名")
private String functionName;
/**
* 表单布局 1:一列 2:两列
*/
@Schema(description = "表单布局 1:一列 2:两列")
private Integer formLayout;
/**
* 基类ID
*/
@Schema(description = "基类ID")
private Long baseclassId;
/**
* 创建时间
*/
@Schema(description = "创建时间")
private LocalDateTime createTime;
/**
* 代码生成风格
*/
private Long style;
/**
* 子表名称
*/
private String childTableName;
/**
* 主表关联键
*/
private String mainField;
/**
* 子表关联键
*/
private String childField;
// /**
// * 字段列表
// */
// @TableField(exist = false)
// private List<GenTableColumnEntity> fieldList;
//
// /**
// * 子表字段列表
// */
// @TableField(exist = false)
// private List<GenTableColumnEntity> childFieldList;
// /**
// * 代码风格(模版分组信息)
// */
// @TableField(exist = false)
// private List<GenGroupEntity> groupList;
}
/*
* Copyright (c) 2018-2025, lengleng All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
* Redistributions of source code must retain the above copyright notice,
* this list of conditions and the following disclaimer.
* Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
* Neither the name of the pig4cloud.com developer nor the names of its
* contributors may be used to endorse or promote products derived from
* this software without specific prior written permission.
* Author: lengleng (wangiegie@gmail.com)
*/
package com.ask.api.entity;
import com.baomidou.mybatisplus.annotation.FieldStrategy;
import com.baomidou.mybatisplus.annotation.IdType;
import com.baomidou.mybatisplus.annotation.TableField;
import com.baomidou.mybatisplus.annotation.TableId;
import com.baomidou.mybatisplus.extension.activerecord.Model;
import lombok.Data;
import lombok.EqualsAndHashCode;
/**
* @author lengleng
* @date 2023-02-06
*
* 记录表字段的配置信息
*/
@Data
@EqualsAndHashCode(callSuper = true)
public class GenTableColumnEntity extends Model<GenDatasourceConf> {
private static final long serialVersionUID = 1L;
/**
* 主键
*/
@TableId(type = IdType.AUTO)
private Long id;
/**
* 数据源名
*/
private String dsName;
/**
* 表名称
*/
private String tableName;
/**
* 字段名称
*/
private String fieldName;
/**
* 排序
*/
private Integer sort;
/**
* 字段类型
*/
private String fieldType;
/**
* 字段说明
*/
private String fieldComment;
/**
* 属性名
*/
private String attrName;
/**
* 属性类型
*/
private String attrType;
/**
* 属性包名
*/
private String packageName;
/**
* 自动填充
*/
private String autoFill;
/**
* 主键 0:否 1:是
*/
private String primaryPk;
/**
* 基类字段 0:否 1:是
*/
private String baseField;
/**
* 表单项 0:否 1:是
*/
private String formItem;
/**
* 表单必填 0:否 1:是
*/
private String formRequired;
/**
* 表单类型
*/
private String formType;
/**
* 表单效验
*/
private String formValidator;
/**
* 列表项 0:否 1:是
*/
private String gridItem;
/**
* 列表排序 0:否 1:是
*/
private String gridSort;
/**
* 查询项 0:否 1:是
*/
private String queryItem;
/**
* 查询方式
*/
private String queryType;
/**
* 查询表单类型
*/
private String queryFormType;
/**
* 字段字典类型
*/
@TableField(updateStrategy = FieldStrategy.ALWAYS)
private String fieldDict;
}
package com.ask.api.entity;
import com.baomidou.mybatisplus.annotation.IdType;
import com.baomidou.mybatisplus.annotation.TableId;
import com.baomidou.mybatisplus.annotation.TableLogic;
import com.baomidou.mybatisplus.annotation.TableName;
import com.ask.common.base.BaseEntity;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
import lombok.EqualsAndHashCode;
/**
* 知识库实体
*
* @author ai
* @date 2024/12/19
*/
@Data
@TableName("ask_knowledge_base")
@EqualsAndHashCode(callSuper = true)
@Schema(description = "知识库")
public class KnowledgeBase extends BaseEntity {
private static final long serialVersionUID = 1L;
/**
* 主键ID
*/
@TableId(type = IdType.AUTO)
@Schema(description = "主键ID")
private Long id;
/**
* 知识库名称
*/
@Schema(description = "知识库名称")
private String name;
/**
* 知识库描述
*/
@Schema(description = "知识库描述")
private String description;
/**
* 删除标记,0未删除,1已删除
*/
@TableLogic
@Schema(description = "删除标记")
private String delFlag;
}
\ No newline at end of file
package com.ask.api.entity;
import com.baomidou.mybatisplus.annotation.IdType;
import com.baomidou.mybatisplus.annotation.TableId;
import com.baomidou.mybatisplus.annotation.TableLogic;
import com.baomidou.mybatisplus.annotation.TableName;
import com.ask.common.base.BaseEntity;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
import lombok.EqualsAndHashCode;
/**
* 知识库文档实体
*
* @author ai
* @date 2024/12/19
*/
@Data
@TableName("ask_knowledge_document")
@EqualsAndHashCode(callSuper = true)
@Schema(description = "知识库文档")
public class KnowledgeDocument extends BaseEntity {
private static final long serialVersionUID = 1L;
/**
* 主键ID
*/
@TableId(type = IdType.AUTO)
@Schema(description = "主键ID")
private Long id;
/**
* 知识库ID
*/
@Schema(description = "知识库ID")
private Long knowledgeBaseId;
/**
* 文档名称
*/
@Schema(description = "文档名称")
private String name;
/**
* 原始文件名
*/
@Schema(description = "原始文件名")
private String fileName;
/**
* 文件存储路径
*/
@Schema(description = "文件存储路径")
private String filePath;
/**
* 文件大小(字节)
*/
@Schema(description = "文件大小(字节)")
private Long fileSize;
/**
* 文件类型
*/
@Schema(description = "文件类型")
private String fileType;
/**
* 处理状态:0-待处理,1-处理中,2-处理完成,3-处理失败
*/
@Schema(description = "处理状态:0-待处理,1-处理中,2-处理完成,3-处理失败")
private Integer status;
/**
* 分段数量
*/
@Schema(description = "分段数量")
private Integer segmentCount;
/**
* 总token数量
*/
@Schema(description = "总token数量")
private Integer tokenCount;
/**
* 删除标记,0未删除,1已删除
*/
@TableLogic
@Schema(description = "删除标记")
private String delFlag;
}
\ No newline at end of file
//package com.ask.config;
//
//import lombok.extern.slf4j.Slf4j;
//import org.springframework.context.annotation.Bean;
//import org.springframework.context.annotation.Configuration;
//import org.springframework.scheduling.annotation.EnableAsync;
//import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor;
//
//import java.util.concurrent.Executor;
//import java.util.concurrent.ThreadPoolExecutor;
//
///**
// * 异步配置类
// *
// * @author ai
// * @date 2024/12/19
// */
//@Slf4j
//@Configuration
//@EnableAsync
//public class AsyncConfig {
//
// /**
// * 向量化任务执行器
// */
// @Bean("vectorizeExecutor")
// public Executor vectorizeExecutor() {
// ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
// executor.setCorePoolSize(2);
// executor.setMaxPoolSize(5);
// executor.setQueueCapacity(100);
// executor.setKeepAliveSeconds(60);
// executor.setThreadNamePrefix("vectorize-");
// executor.setRejectedExecutionHandler(new ThreadPoolExecutor.CallerRunsPolicy());
// executor.setWaitForTasksToCompleteOnShutdown(true);
// executor.setAwaitTerminationSeconds(60);
// executor.initialize();
//
// log.info("向量化任务执行器初始化完成");
// return executor;
// }
//}
\ No newline at end of file
package com.ask.config;
import org.springframework.ai.chat.client.ChatClient;
import org.springframework.ai.chat.client.advisor.MessageChatMemoryAdvisor;
import org.springframework.ai.chat.client.advisor.api.Advisor;
import org.springframework.ai.chat.memory.ChatMemory;
import org.springframework.ai.chat.memory.ChatMemoryRepository;
import org.springframework.ai.chat.memory.MessageWindowChatMemory;
import org.springframework.ai.chat.memory.repository.jdbc.JdbcChatMemoryRepository;
import org.springframework.ai.openai.OpenAiChatModel;
import org.springframework.ai.rag.advisor.RetrievalAugmentationAdvisor;
import org.springframework.ai.rag.generation.augmentation.ContextualQueryAugmenter;
import org.springframework.ai.rag.retrieval.search.VectorStoreDocumentRetriever;
import org.springframework.ai.vectorstore.VectorStore;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.jdbc.core.JdbcTemplate;
import java.util.ArrayList;
import java.util.List;
@Configuration
public class CommonConfiguration {
@Bean
public ChatMemory chatMemory (JdbcTemplate jdbcTemplate,PostgresChatMemoryDialect postgresChatMemoryDialect) {
ChatMemoryRepository chatMemoryRepository = JdbcChatMemoryRepository.builder()
.jdbcTemplate(jdbcTemplate)
.dialect(postgresChatMemoryDialect)
.build();
return MessageWindowChatMemory.builder()
.chatMemoryRepository(chatMemoryRepository)
.maxMessages(10)
.build();
}
@Bean
public ChatClient chatClient(OpenAiChatModel model, ChatMemory chatMemory) {
List<Advisor> advisors = new ArrayList<>();
Advisor messageChatMemoryAdvisor =MessageChatMemoryAdvisor.builder(chatMemory).build();
advisors.add(messageChatMemoryAdvisor);
return ChatClient.builder(model)
.defaultAdvisors(advisors)
.defaultAdvisors().build();
}
@Bean
public RetrievalAugmentationAdvisor retrievalAugmentationAdvisor(VectorStore vectorStore) {
return RetrievalAugmentationAdvisor.builder()
.documentRetriever(VectorStoreDocumentRetriever.builder()
.similarityThreshold(0.60)
.topK(5)
.vectorStore(vectorStore)
.build())
.queryAugmenter(ContextualQueryAugmenter.builder()
.allowEmptyContext(true)
.build())
.build();
}
}
//package com.ask.config;
//
//import com.pig4cloud.pig.ask.api.entity.GenDatasourceConf;
//import com.pig4cloud.pig.ask.service.GenDatasourceConfService;
//import org.jasypt.encryption.StringEncryptor;
//import org.springframework.beans.factory.annotation.Autowired;
//import org.springframework.boot.CommandLineRunner;
//import org.springframework.stereotype.Component;
//
//import java.util.List;
//
//@Component
//public class InitDataBase implements CommandLineRunner {
//
// @Autowired
// private GenDatasourceConfService genDatasourceConfService;
// @Autowired
// private StringEncryptor stringEncryptor;
//
// @Override
// public void run(String... args) throws Exception {
// List<GenDatasourceConf> genDatasourceConfList = genDatasourceConfService.list();
// genDatasourceConfList.forEach(conf->{
// conf.setPassword(stringEncryptor.decrypt(conf.getPassword()));
// genDatasourceConfService.addDynamicDataSource(conf);
// });
// }
//}
package com.ask.config;
import org.springframework.ai.chat.memory.repository.jdbc.JdbcChatMemoryRepositoryDialect;
import org.springframework.stereotype.Component;
@Component
public class PostgresChatMemoryDialect implements JdbcChatMemoryRepositoryDialect {
@Override
public String getSelectMessagesSql() {
return "SELECT content, type FROM ask_chat_conversation_detail WHERE conversation_id = ? ORDER BY \"timestamp\"";
}
@Override
public String getInsertMessageSql() {
return "INSERT INTO ask_chat_conversation_detail (conversation_id, content, type, \"timestamp\") VALUES ( ?, ?, ?, ?)";
}
@Override
public String getSelectConversationIdsSql() {
return "SELECT DISTINCT conversation_id FROM ask_chat_conversation_detail";
}
@Override
public String getDeleteMessagesSql() {
return "UPDATE ask_chat_conversation_detail set del_flag = '1' WHERE conversation_id = ? ";
}
}
package com.ask.controller;
import com.ask.api.entity.ChatConversation;
import com.ask.common.core.R;
import com.ask.service.ChatConversationService;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.Parameter;
import io.swagger.v3.oas.annotations.tags.Tag;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.ai.chat.client.ChatClient;
import org.springframework.ai.chat.memory.ChatMemory;
import org.springframework.ai.chat.messages.Message;
import org.springframework.ai.chat.messages.SystemMessage;
import org.springframework.ai.chat.messages.UserMessage;
import org.springframework.ai.chat.prompt.Prompt;
import org.springframework.ai.rag.advisor.RetrievalAugmentationAdvisor;
import org.springframework.ai.rag.retrieval.search.VectorStoreDocumentRetriever;
import org.springframework.ai.vectorstore.filter.Filter;
import org.springframework.ai.vectorstore.filter.FilterExpressionBuilder;
import org.springframework.web.bind.annotation.*;
import reactor.core.publisher.Flux;
import java.util.Arrays;
import java.util.Objects;
import java.util.UUID;
@Slf4j
@RestController
@RequiredArgsConstructor
@RequestMapping("/chat/ai")
@Tag(description = "ai", name = "AI对话模块")
public class ChatController {
private final ChatClient chatClient;
private final ChatConversationService chatConversationService;
private final RetrievalAugmentationAdvisor retrievalAugmentationAdvisor;
/**
* 获取会话ID
* @return 新的会话ID
*/
@Operation(summary = "创建对话", description = "创建对话")
@GetMapping("/create/client")
public R<ChatConversation> getConversationId(@Parameter(description = "智能体ID") @RequestParam Integer agentId,
@CookieValue(value = "userId") Long userId) {
if(Objects.isNull(agentId)) {
throw new RuntimeException("userID不能为NULL!");
}
ChatConversation chatConversation =new ChatConversation();
String conversationId= UUID.randomUUID().toString().replaceAll("-","");
chatConversation.setConversationId(conversationId);
chatConversation.setAgentId(agentId);
chatConversation.setUserId(userId);
chatConversationService.save(chatConversation);
return R.ok(chatConversation);
}
/**
* 最基本的AI流式输出对话
*
* * @param message
* @return
*/
@Operation(summary = "普通对话", description = "普通对话")
@GetMapping(value = "/chat", produces = "text/html;charset=utf-8")
public Flux<String> chat(@Parameter(description = "对话内容") @RequestParam String message,
@Parameter(description = "会话ID") @RequestParam String conversationId) {
// 创建系统消息,告诉大模型只返回工具名和参数
Message systemMessage = new SystemMessage("你是一个AI客服助手。请严格按照以下格式回答每个问题:");
// 用户消息
String question = "请严格按以下格式回答:\n" +
"<think>\n" +
"[你的逐步推理过程]\n" +
"</think>\n" +
"<answer>\n" +
"[最终答案]\n" +
"</answer>\n" +
"推理过程不要设计`<think>` 和 `<answer>` \n" +
"问题:"+message+"\n" ;
Message userMessage = new UserMessage(question);
// 创建提示,包含系统消息和用户消息
Prompt prompt = new Prompt(Arrays.asList(systemMessage, userMessage));
// 使用修改后的提示获取响应
return chatClient.prompt(prompt).advisors(a -> a.param(ChatMemory.CONVERSATION_ID, conversationId)).stream().content();
}
/**
* 最基本的AI流式输出对话
*
* * @param message
* @return
*/
@Operation(summary = "知识库对话", description = "知识库对话")
@GetMapping(value = "/rag/chat", produces = "text/html;charset=utf-8")
public Flux<String> ragChat(String message, String conversationId) {
// 创建系统消息,告诉大模型只返回工具名和参数
Message systemMessage = new SystemMessage("你是一个AI客服助手。请严格按照以下格式回答每个问题:");
Message userMessage = new UserMessage(message);
// 创建提示,包含系统消息和用户消息
Prompt prompt = new Prompt(Arrays.asList(systemMessage, userMessage));
// 使用修改后的提示获取响应
FilterExpressionBuilder builder = new FilterExpressionBuilder();
Filter.Expression filter = builder.eq("source","1").build();
return chatClient.prompt(prompt)
.advisors(a -> a.param(ChatMemory.CONVERSATION_ID, conversationId))
.advisors(a -> a.param(VectorStoreDocumentRetriever.FILTER_EXPRESSION, filter))
.advisors(retrievalAugmentationAdvisor)
.stream().content();
}
}
\ No newline at end of file
package com.ask.controller;
import com.ask.api.entity.ChatConversation;
import com.ask.common.core.R;
import com.ask.service.ChatConversationService;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.baomidou.mybatisplus.core.toolkit.Wrappers;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.Parameter;
import io.swagger.v3.oas.annotations.tags.Tag;
import jakarta.servlet.http.Cookie;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import lombok.RequiredArgsConstructor;
import org.springframework.http.HttpHeaders;
import org.springframework.web.bind.annotation.*;
import java.util.Arrays;
import java.util.Objects;
import java.util.UUID;
import java.time.LocalDateTime;
import java.time.ZoneOffset;
/**
* 对话记录管理
*
* @author ai
* @date 2024/03/21
*/
@RestController
@RequiredArgsConstructor
@RequestMapping("/chat/conversation")
@Tag(description = "conversation", name = "对话记录管理")
public class ChatConversationController {
private final ChatConversationService chatConversationService;
/**
* 生成用户Cookie
* @param request HTTP请求
* @param response HTTP响应
* @return R
*/
@Operation(summary = "生成用户Cookie", description = "如果不存在则生成用户Cookie并设置到响应中")
@PostMapping("/generate-cookie")
public R<Long> generateUserCookie(HttpServletRequest request, HttpServletResponse response) {
// 检查Cookie是否存在
Cookie[] cookies = request.getCookies();
if (cookies != null) {
for (Cookie cookie : cookies) {
if ("userId".equals(cookie.getName())) {
return R.ok(Long.parseLong(cookie.getValue()));
}
}
}
// 生成userId (时间戳 + 随机数)
long timestamp = LocalDateTime.now().toEpochSecond(ZoneOffset.UTC);
long random = (long) (Math.random() * 1000);
Long userId = timestamp * 1000 + random;
Cookie cookie = new Cookie("userId", String.valueOf(userId));
// Cookie配置
cookie.setPath("/"); // 路径
cookie.setMaxAge(-1); // 永久有效(浏览器关闭前)
cookie.setHttpOnly(true); // 防止XSS攻击
cookie.setSecure(true); // 仅HTTPS传输
response.addCookie(cookie);
return R.ok(userId);
}
/**
* 分页查询
*
* @param page 分页对象
* @param agentId 智能体Id
* @return 分页数据
*/
@Operation(summary = "分页查询", description = "分页查询")
@GetMapping("/page")
public R<IPage<ChatConversation>> getPage(@Parameter(description = "分页对象") Page page,
@Parameter(description = "智能体Id") @RequestParam(required = false) Integer agentId,
@CookieValue(value = "userId") Long userId) {
if(Objects.isNull(userId)){
throw new RuntimeException("userID 不能为NULL!");
}
return R.ok(chatConversationService.page(page, Wrappers.<ChatConversation>lambdaQuery().eq(!Objects.isNull(agentId),ChatConversation::getAgentId, agentId).eq(ChatConversation::getUserId, userId)));
}
/**
* 通过id查询对话记录
*
* @param id id
* @return R
*/
@Operation(summary = "通过id查询", description = "通过id查询")
@GetMapping("/{id}")
public R<ChatConversation> getById(@Parameter(description = "对话记录ID") @PathVariable("id") Long id) {
return R.ok(chatConversationService.getById(id));
}
// /**
// * 新增对话记录
// *
// * @param chatConversation 对话记录
// * @return R
// */
// @Operation(summary = "新增对话记录", description = "新增对话记录")
// @PostMapping
// public R<Boolean> save(@Parameter(description = "对话记录") @RequestBody ChatConversation chatConversation,
// @CookieValue(value = "userId", defaultValue = "0") Long userId) {
// if(Objects.isNull(userId)){
// throw new RuntimeException("userID 不能为NULL!");
// }
// chatConversation.setUserId(userId);
// return R.ok(chatConversationService.save(chatConversation));
// }
/**
* 修改对话记录
*
* @param chatConversation 对话记录
* @return R
*/
@Operation(summary = "修改对话记录", description = "修改对话记录")
@PutMapping
public R<Boolean> updateById(@Parameter(description = "对话记录") @RequestBody ChatConversation chatConversation,
@CookieValue(value = "userId", defaultValue = "0") Long userId) {
if(Objects.isNull(userId)){
throw new RuntimeException("userID 不能为NULL!");
}
chatConversation.setUserId(userId);
return R.ok(chatConversationService.updateById(chatConversation));
}
/**
* 通过id删除对话记录
*
* @param id id
* @return R
*/
@Operation(summary = "通过id删除对话记录", description = "通过id删除对话记录")
@DeleteMapping("/{id}")
public R<Boolean> removeById(@Parameter(description = "对话记录ID") @PathVariable Long id) {
return R.ok(chatConversationService.removeById(id));
}
}
\ No newline at end of file
package com.ask.controller;
import com.ask.api.entity.ChatConversationDetail;
import com.ask.common.core.R;
import com.ask.service.ChatConversationDetailService;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.baomidou.mybatisplus.core.toolkit.Wrappers;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.tags.Tag;
import lombok.RequiredArgsConstructor;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import java.util.List;
/**
* 对话详细记录管理
*
* @author ai
* @date 2024/03/21
*/
@RestController
@RequiredArgsConstructor
@RequestMapping("/chat/conversation/detail")
@Tag(description = "conversation-detail", name = "对话详细记录管理")
public class ChatConversationDetailController {
private final ChatConversationDetailService chatConversationDetailService;
/**
* 根据会话ID分页获取对话详情
* @param page 分页对象
* @param conversationId 会话ID
* @return 分页数据
*/
@Operation(summary = "根据会话ID分页获取对话详情", description = "根据会话ID分页获取对话详情")
@GetMapping("/page/{conversationId}")
public R<IPage<ChatConversationDetail>> pageByConversationId(Page page, @PathVariable String conversationId) {
IPage<ChatConversationDetail> result =chatConversationDetailService.page(page,Wrappers.<ChatConversationDetail>lambdaQuery()
.eq(ChatConversationDetail::getConversationId, conversationId)
.orderByAsc(ChatConversationDetail::getTimestamp));
return R.ok(result);
}
/**
* 根据会话ID获取对话详情列表
* @param conversationId 会话ID
* @return 对话详情列表
*/
@Operation(summary = "根据会话ID获取对话详情列表", description = "根据会话ID获取对话详情列表")
@GetMapping("/list/{conversationId}")
public R<List<ChatConversationDetail>> listByConversationId(@PathVariable String conversationId) {
return R.ok(chatConversationDetailService.list(Wrappers.<ChatConversationDetail>lambdaQuery()
.eq(ChatConversationDetail::getConversationId, conversationId)
.orderByAsc(ChatConversationDetail::getTimestamp)));
}
}
\ No newline at end of file
package com.ask.controller;
import com.ask.api.entity.KnowledgeBase;
import com.ask.common.core.R;
import com.ask.service.KnowledgeBaseService;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.baomidou.mybatisplus.core.toolkit.Wrappers;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.Parameter;
import io.swagger.v3.oas.annotations.tags.Tag;
import lombok.RequiredArgsConstructor;
import org.springframework.http.HttpHeaders;
import org.springframework.util.StringUtils;
import org.springframework.web.bind.annotation.*;
/**
* 知识库管理
*
* @author ai
* @date 2024/12/19
*/
@RestController
@RequiredArgsConstructor
@RequestMapping("/knowledge/base")
@Tag(description = "knowledgeBase", name = "知识库管理")
public class KnowledgeBaseController {
private final KnowledgeBaseService knowledgeBaseService;
/**
* 分页查询
* @param page 分页对象
* @param knowledgeBase 查询条件
* @return 分页数据
*/
@Operation(summary = "分页查询", description = "分页查询")
@GetMapping("/page")
public R<IPage<KnowledgeBase>> getPage(@Parameter(description = "分页对象") Page page,
@Parameter(description = "查询条件") KnowledgeBase knowledgeBase) {
return R.ok(knowledgeBaseService.page(page, Wrappers.query(knowledgeBase)));
}
/**
* 通过id查询知识库
* @param id id
* @return R
*/
@Operation(summary = "通过id查询", description = "通过id查询")
@GetMapping("/{id}")
public R<KnowledgeBase> getById(@Parameter(description = "知识库ID") @PathVariable("id") Long id) {
return R.ok(knowledgeBaseService.getById(id));
}
/**
* 新增知识库
* @param knowledgeBase 知识库
* @return R
*/
@Operation(summary = "新增知识库", description = "新增知识库")
@PostMapping
public R<Boolean> save(@Parameter(description = "知识库信息") @RequestBody KnowledgeBase knowledgeBase) {
// 校验知识库名称是否为空
if (!StringUtils.hasText(knowledgeBase.getName())) {
return R.failed("知识库名称不能为空");
}
// 校验知识库名称是否重复
if (knowledgeBaseService.checkNameExists(knowledgeBase.getName(), null)) {
return R.failed("知识库名称已存在,请修改后重试");
}
return R.ok(knowledgeBaseService.save(knowledgeBase));
}
/**
* 修改知识库
* @param knowledgeBase 知识库
* @return R
*/
@Operation(summary = "修改知识库", description = "修改知识库")
@PutMapping
public R<Boolean> updateById(@Parameter(description = "知识库信息") @RequestBody KnowledgeBase knowledgeBase) {
// 校验知识库名称是否为空
if (!StringUtils.hasText(knowledgeBase.getName())) {
return R.failed("知识库名称不能为空");
}
// 校验知识库名称是否重复
if (knowledgeBaseService.checkNameExists(knowledgeBase.getName(), knowledgeBase.getId())) {
return R.failed("知识库名称已存在,请修改后重试");
}
return R.ok(knowledgeBaseService.updateById(knowledgeBase));
}
/**
* 通过id删除知识库
* @param id id
* @return R
*/
@Operation(summary = "通过id删除知识库", description = "通过id删除知识库")
@DeleteMapping("/{id}")
public R<Boolean> removeById(@Parameter(description = "知识库ID") @PathVariable Long id) {
return R.ok(knowledgeBaseService.removeById(id));
}
/**
* 校验知识库名称是否重复
* @param name 知识库名称
* @param id 知识库ID(可选,修改时传入)
* @return R
*/
@Operation(summary = "校验知识库名称", description = "校验知识库名称是否重复")
@GetMapping("/checkName")
public R<Boolean> checkName(@Parameter(description = "知识库名称") @RequestParam String name,
@Parameter(description = "知识库ID(可选,修改时传入)") @RequestParam(required = false) Long id) {
if (!StringUtils.hasText(name)) {
return R.ok(false);
}
boolean exists = knowledgeBaseService.checkNameExists(name, id);
if (exists) {
return R.ok(false);
}
return R.ok(true);
}
}
\ No newline at end of file
package com.ask.controller;
import com.ask.api.dto.DocumentSegmentResult;
import com.ask.api.entity.KnowledgeDocument;
import com.ask.common.core.R;
import com.ask.service.KnowledgeDocumentService;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.baomidou.mybatisplus.core.toolkit.Wrappers;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.Parameter;
import io.swagger.v3.oas.annotations.security.SecurityRequirement;
import io.swagger.v3.oas.annotations.tags.Tag;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.http.HttpHeaders;
import org.springframework.http.MediaType;
import org.springframework.util.StringUtils;
import org.springframework.web.bind.annotation.*;
import org.springframework.web.multipart.MultipartFile;
import java.util.List;
/**
* 知识库文档管理
*
* @author ai
* @date 2024/12/19
*/
@Slf4j
@RestController
@RequiredArgsConstructor
@RequestMapping("/knowledge/document")
@Tag(description = "knowledgeDocument", name = "知识库文档管理")
public class KnowledgeDocumentController {
private final KnowledgeDocumentService knowledgeDocumentService;
/**
* 分页查询
* @param page 分页对象
* @param knowledgeBaseId 知识库ID(必填)
* @param name 文档名称(非必填,模糊搜索)
* @return 分页数据
*/
@Operation(summary = "分页查询", description = "分页查询")
@GetMapping("/page")
public R<IPage<KnowledgeDocument>> getPage(Page page,
@Parameter(description = "知识库id") @RequestParam Long knowledgeBaseId,
@Parameter(description = "知识库名称") @RequestParam(required = false) String name) {
LambdaQueryWrapper<KnowledgeDocument> wrapper = Wrappers.lambdaQuery();
// 知识库ID必填条件
wrapper.eq(KnowledgeDocument::getKnowledgeBaseId, knowledgeBaseId);
// 文档名称模糊搜索(非必填)
if (StringUtils.hasText(name)) {
wrapper.like(KnowledgeDocument::getName, name.trim());
}
// 按创建时间倒序排列
wrapper.orderByDesc(KnowledgeDocument::getCreateTime);
return R.ok(knowledgeDocumentService.page(page, wrapper));
}
/**
* 通过id查询知识库文档
* @param id id
* @return R
*/
@Operation(summary = "通过id查询", description = "通过id查询")
@GetMapping("/{id}")
public R<KnowledgeDocument> getById(@PathVariable("id") Long id) {
return R.ok(knowledgeDocumentService.getById(id));
}
/**
* 新增知识库文档
* @param knowledgeBaseId 知识库ID
* @param segmentResults 文档分段结果列表
* @return R
*/
@Operation(summary = "新增知识库文档", description = "保存文档分段结果到知识库")
@PostMapping
public R<Boolean> save(@RequestParam Long knowledgeBaseId,
@RequestBody List<DocumentSegmentResult> segmentResults) {
// 校验知识库ID
if (knowledgeBaseId == null || knowledgeBaseId <= 0) {
return R.failed("知识库ID不能为空且必须大于0");
}
// 校验分段结果
if (segmentResults == null || segmentResults.isEmpty()) {
return R.failed("文档分段结果不能为空");
}
try {
boolean result = knowledgeDocumentService.saveSegmentResults(knowledgeBaseId, segmentResults);
return R.ok(result);
} catch (Exception e) {
log.error("知识库文档保存失败,知识库ID: {}, 错误: {}", knowledgeBaseId, e.getMessage(), e);
return R.failed("知识库文档保存失败:" + e.getMessage());
}
}
/**
* 修改知识库文档
* @param knowledgeDocument 知识库文档
* @return R
*/
@Operation(summary = "修改知识库文档", description = "修改知识库文档")
@PutMapping
public R<Boolean> updateById(@RequestBody KnowledgeDocument knowledgeDocument) {
return R.ok(knowledgeDocumentService.updateById(knowledgeDocument));
}
/**
* 通过id删除知识库文档
* @param id id
* @return R
*/
@Operation(summary = "通过id删除知识库文档", description = "通过id删除知识库文档")
@DeleteMapping("/{id}")
public R<Boolean> removeById(@PathVariable Long id) {
return R.ok(knowledgeDocumentService.removeById(id));
}
/**
* 文档分段处理
* @param knowledgeBaseId 知识库ID
* @param files 文件数组
* @return 文档分段结果列表
*/
@Operation(summary = "文档分段处理", description = "上传文档并进行分段处理,支持PDF、Word、Excel、TXT、MD等格式")
@PostMapping(value = "/segment", consumes = MediaType.MULTIPART_FORM_DATA_VALUE)
public R<List<DocumentSegmentResult>> segmentDocuments(
@Parameter(description = "知识库ID", required = true)
@RequestParam Long knowledgeBaseId,
@Parameter(description = "文件数组", required = true)
@RequestParam("files") MultipartFile[] files) {
// 校验知识库ID
if (knowledgeBaseId == null || knowledgeBaseId <= 0) {
return R.failed("知识库ID不能为空且必须大于0");
}
// 校验文件
if (files == null || files.length == 0) {
return R.failed("请选择要上传的文件");
}
// 校验文件大小和类型
for (MultipartFile file : files) {
if (file.isEmpty()) {
return R.failed("文件不能为空:" + file.getOriginalFilename());
}
// 限制文件大小(50MB)
if (file.getSize() > 50 * 1024 * 1024) {
return R.failed("文件大小不能超过50MB:" + file.getOriginalFilename());
}
// 检查文件类型
String fileName = file.getOriginalFilename();
if (fileName == null || !isValidFileType(fileName)) {
return R.failed("不支持的文件类型:" + fileName + ",支持的格式:PDF、DOC、DOCX、XLS、XLSX、TXT、MD、RTF、ODT");
}
}
try {
List<DocumentSegmentResult> results = knowledgeDocumentService.segmentDocuments(knowledgeBaseId, files);
return R.ok(results);
} catch (Exception e) {
log.error("文档分段处理失败,知识库ID: {}, 错误: {}", knowledgeBaseId, e.getMessage(), e);
return R.failed("文档分段处理失败:" + e.getMessage());
}
}
/**
* 检查文件类型是否支持
* @param fileName 文件名
* @return 是否支持
*/
private boolean isValidFileType(String fileName) {
if (!StringUtils.hasText(fileName)) {
return false;
}
String extension = fileName.toLowerCase();
return extension.endsWith(".pdf") ||
extension.endsWith(".doc") ||
extension.endsWith(".docx") ||
extension.endsWith(".xls") ||
extension.endsWith(".xlsx") ||
extension.endsWith(".txt") ||
extension.endsWith(".md") ||
extension.endsWith(".rtf") ||
extension.endsWith(".odt");
}
}
\ No newline at end of file
package com.ask.mapper;
import com.ask.api.entity.ChatConversationDetail;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import org.apache.ibatis.annotations.Mapper;
/**
* 对话详细记录Mapper接口
*
* @author ai
* @date 2024/03/21
*/
@Mapper
public interface ChatConversationDetailMapper extends BaseMapper<ChatConversationDetail> {
}
\ No newline at end of file
package com.ask.mapper;
import com.ask.api.entity.ChatConversation;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import org.apache.ibatis.annotations.Mapper;
/**
* 对话记录Mapper接口
*
* @author ai
* @date 2024/03/21
*/
@Mapper
public interface ChatConversationMapper extends BaseMapper<ChatConversation> {
}
\ No newline at end of file
///*
// * Copyright (c) 2018-2025, lengleng All rights reserved.
// *
// * Redistribution and use in source and binary forms, with or without
// * modification, are permitted provided that the following conditions are met:
// *
// * Redistributions of source code must retain the above copyright notice,
// * this list of conditions and the following disclaimer.
// * Redistributions in binary form must reproduce the above copyright
// * notice, this list of conditions and the following disclaimer in the
// * documentation and/or other materials provided with the distribution.
// * Neither the name of the pig4cloud.com developer nor the names of its
// * contributors may be used to endorse or promote products derived from
// * this software without specific prior written permission.
// * Author: lengleng (wangiegie@gmail.com)
// */
//package com.ask.mapper;
//
//import com.ask.api.entity.GenDatasourceConf;
//import com.baomidou.mybatisplus.core.mapper.BaseMapper;
//import org.apache.ibatis.annotations.Mapper;
//
///**
// * 数据源表 Mapper 接口
// *
// * @author lengleng
// * @date 2019-03-31 16:00:20
// */
//@Mapper
//public interface GenDatasourceConfMapper extends BaseMapper<GenDatasourceConf> {
//
//}
///*
// * Copyright (c) 2018-2025, lengleng All rights reserved.
// *
// * Redistribution and use in source and binary forms, with or without
// * modification, are permitted provided that the following conditions are met:
// *
// * Redistributions of source code must retain the above copyright notice,
// * this list of conditions and the following disclaimer.
// * Redistributions in binary form must reproduce the above copyright
// * notice, this list of conditions and the following disclaimer in the
// * documentation and/or other materials provided with the distribution.
// * Neither the name of the pig4cloud.com developer nor the names of its
// * contributors may be used to endorse or promote products derived from
// * this software without specific prior written permission.
// * Author: lengleng (wangiegie@gmail.com)
// */
//
//package com.ask.mapper;
//
//import com.ask.api.entity.GenTable;
//import com.baomidou.mybatisplus.core.mapper.BaseMapper;
//import org.apache.ibatis.annotations.Mapper;
//
///**
// * 代码生成表 Mapper 接口
// *
// * @author lengleng
// * @date 2025/05/31
// */
//@Mapper
//public interface GenTableMapper extends BaseMapper<GenTable> {
//
//}
package com.ask.mapper;
import com.ask.api.entity.KnowledgeBase;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import org.apache.ibatis.annotations.Mapper;
/**
* 知识库Mapper接口
*
* @author ai
* @date 2024/12/19
*/
@Mapper
public interface KnowledgeBaseMapper extends BaseMapper<KnowledgeBase> {
}
\ No newline at end of file
package com.ask.mapper;
import com.ask.api.entity.KnowledgeDocument;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import org.apache.ibatis.annotations.Mapper;
/**
* 知识库文档Mapper接口
*
* @author ai
* @date 2024/12/19
*/
@Mapper
public interface KnowledgeDocumentMapper extends BaseMapper<KnowledgeDocument> {
}
\ No newline at end of file
package com.ask.service;
import com.ask.api.entity.ChatConversationDetail;
import com.baomidou.mybatisplus.extension.service.IService;
/**
* 对话详细记录服务接口
*
* @author ai
* @date 2024/03/21
*/
public interface ChatConversationDetailService extends IService<ChatConversationDetail> {
}
\ No newline at end of file
package com.ask.service;
import com.ask.api.entity.ChatConversation;
import com.baomidou.mybatisplus.extension.service.IService;
/**
* 对话记录服务接口
*
* @author ai
* @date 2024/03/21
*/
public interface ChatConversationService extends IService<ChatConversation> {
}
\ No newline at end of file
///*
// * Copyright (c) 2018-2025, lengleng All rights reserved.
// *
// * Redistribution and use in source and binary forms, with or without
// * modification, are permitted provided that the following conditions are met:
// *
// * Redistributions of source code must retain the above copyright notice,
// * this list of conditions and the following disclaimer.
// * Redistributions in binary form must reproduce the above copyright
// * notice, this list of conditions and the following disclaimer in the
// * documentation and/or other materials provided with the distribution.
// * Neither the name of the pig4cloud.com developer nor the names of its
// * contributors may be used to endorse or promote products derived from
// * this software without specific prior written permission.
// * Author: lengleng (wangiegie@gmail.com)
// */
//package com.ask.service;
//
//import com.ask.api.entity.GenDatasourceConf;
//import com.baomidou.mybatisplus.extension.service.IService;
//
//
//
///**
// * 数据源配置服务接口 提供数据源的增删改查及校验等功能
// *
// * @author lengleng
// * @date 2025/05/31
// */
//public interface GenDatasourceConfService extends IService<GenDatasourceConf> {
//
// /**
// * 保存数据源并加密
// * @param genDatasourceConf 数据源配置信息
// * @return 保存是否成功
// */
// Boolean saveDsByEnc(GenDatasourceConf genDatasourceConf);
//
// /**
// * 更新数据源
// * @param genDatasourceConf 数据源配置信息
// * @return 更新是否成功
// */
// Boolean updateDsByEnc(GenDatasourceConf genDatasourceConf);
//
// /**
// * 添加动态数据源
// * @param datasourceConf 数据源配置信息
// */
// void addDynamicDataSource(GenDatasourceConf datasourceConf);
//
// /**
// * 校验数据源配置是否有效
// * @param datasourceConf 数据源配置信息
// * @return true表示有效,false表示无效
// */
// Boolean checkDataSource(GenDatasourceConf datasourceConf);
//
// /**
// * 通过数据源ID删除数据源
// * @param dsIds 数据源ID数组
// * @return 删除是否成功
// */
// Boolean removeByDsId(Long[] dsIds);
//
//}
///*
// * Copyright (c) 2018-2025, lengleng All rights reserved.
// *
// * Redistribution and use in source and binary forms, with or without
// * modification, are permitted provided that the following conditions are met:
// *
// * Redistributions of source code must retain the above copyright notice,
// * this list of conditions and the following disclaimer.
// * Redistributions in binary form must reproduce the above copyright
// * notice, this list of conditions and the following disclaimer in the
// * documentation and/or other materials provided with the distribution.
// * Neither the name of the pig4cloud.com developer nor the names of its
// * contributors may be used to endorse or promote products derived from
// * this software without specific prior written permission.
// * Author: lengleng (wangiegie@gmail.com)
// */
//
//package com.ask.service;
//
//import com.ask.api.dto.TableDto;
//import com.ask.api.dto.TableParam;
//import com.ask.api.entity.GenTable;
//import com.baomidou.mybatisplus.core.metadata.IPage;
//import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
//import com.baomidou.mybatisplus.extension.service.IService;
//
//import java.util.List;
//
///**
// * 代码生成表服务接口
// *
// * @author lengleng
// * @date 2025/05/31
// */
//public interface GenTableService extends IService<GenTable> {
//
// /**
// * 查询对应数据源的表
// * @param page 分页信息
// * @param table 查询条件
// * @return 表
// */
// IPage<TableDto> queryTablePage(Page<TableDto> page, TableParam table);
//
//
// /**
// * 查询表ddl 语句
// * @param dsName 数据源名称
// * @param tableName 表名称
// * @return ddl 语句
// * @throws Exception
// */
// String queryTableDdl(String dsName, String tableName) throws Exception;
//
// /**
// * 查询数据源里面的全部表
// * @param dsName 数据源名称
// * @return table
// */
// List<String> queryTableList(String dsName);
//
// /**
// * 查询表的全部字段
// * @param dsName 数据源
// * @param tableName 表名称
// * @return column
// */
// List<String> queryTableColumn(String dsName, String tableName);
//
//}
package com.ask.service;
import com.ask.api.entity.KnowledgeBase;
import com.baomidou.mybatisplus.extension.service.IService;
/**
* 知识库服务接口
*
* @author ai
* @date 2024/12/19
*/
public interface KnowledgeBaseService extends IService<KnowledgeBase> {
/**
* 校验知识库名称是否重复
* @param name 知识库名称
* @param id 知识库ID(修改时传入,新增时传null)
* @return true-重复,false-不重复
*/
boolean checkNameExists(String name, Long id);
}
\ No newline at end of file
package com.ask.service;
import com.ask.api.dto.DocumentSegmentResult;
import com.ask.api.entity.KnowledgeDocument;
import com.baomidou.mybatisplus.extension.service.IService;
import org.springframework.web.multipart.MultipartFile;
import java.util.List;
/**
* 知识库文档服务接口
*
* @author ai
* @date 2024/12/19
*/
public interface KnowledgeDocumentService extends IService<KnowledgeDocument> {
/**
* 文档分段处理
* @param knowledgeBaseId 知识库ID
* @param files 文件数组
* @return 文档分段结果列表
*/
List<DocumentSegmentResult> segmentDocuments(Long knowledgeBaseId, MultipartFile[] files);
/**
* 保存文档分段结果
* @param knowledgeBaseId 知识库ID
* @param segmentResults 分段结果列表
* @return 保存结果
*/
boolean saveSegmentResults(Long knowledgeBaseId, List<DocumentSegmentResult> segmentResults);
}
\ No newline at end of file
//package com.ask.service;
//
//
//import com.ask.api.dto.DocumentSegmentResult;
//
//import java.util.List;
//
///**
// * 向量存储服务接口
// *
// * @author ai
// * @date 2024/12/19
// */
//public interface VectorStoreService {
//
// /**
// * 异步向量化文档分段
// * @param knowledgeBaseId 知识库ID
// * @param documentId 文档ID
// * @param segmentResults 分段结果列表
// */
// void vectorizeDocumentsAsync(Long knowledgeBaseId, Long documentId, List<DocumentSegmentResult> segmentResults);
//
// /**
// * 向量化单个文档的分段
// * @param knowledgeBaseId 知识库ID
// * @param documentId 文档ID
// * @param segmentResult 分段结果
// */
// void vectorizeDocument(Long knowledgeBaseId, Long documentId, DocumentSegmentResult segmentResult);
//
//}
\ No newline at end of file
package com.ask.service.impl;
import com.ask.api.entity.ChatConversationDetail;
import com.ask.mapper.ChatConversationDetailMapper;
import com.ask.service.ChatConversationDetailService;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Service;
/**
* 对话详细记录服务实现类
*
* @author ai
* @date 2024/03/21
*/
@Slf4j
@Service
@RequiredArgsConstructor
public class ChatConversationDetailServiceImpl extends ServiceImpl<ChatConversationDetailMapper, ChatConversationDetail> implements ChatConversationDetailService {
}
\ No newline at end of file
package com.ask.service.impl;
import com.ask.api.entity.ChatConversation;
import com.ask.mapper.ChatConversationMapper;
import com.ask.service.ChatConversationService;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Service;
/**
* 对话记录服务实现类
*
* @author ai
* @date 2024/03/21
*/
@Slf4j
@Service
@RequiredArgsConstructor
public class ChatConversationServiceImpl extends ServiceImpl<ChatConversationMapper, ChatConversation> implements ChatConversationService {
}
\ No newline at end of file
///*
// * Copyright (c) 2018-2025, lengleng All rights reserved.
// *
// * Redistribution and use in source and binary forms, with or without
// * modification, are permitted provided that the following conditions are met:
// *
// * Redistributions of source code must retain the above copyright notice,
// * this list of conditions and the following disclaimer.
// * Redistributions in binary form must reproduce the above copyright
// * notice, this list of conditions and the following disclaimer in the
// * documentation and/or other materials provided with the distribution.
// * Neither the name of the pig4cloud.com developer nor the names of its
// * contributors may be used to endorse or promote products derived from
// * this software without specific prior written permission.
// * Author: lengleng (wangiegie@gmail.com)
// */
//package com.ask.service.impl;
//
//import com.ask.api.entity.GenDatasourceConf;
//import com.ask.mapper.GenDatasourceConfMapper;
//import com.ask.service.GenDatasourceConfService;
//import com.baomidou.dynamic.datasource.DynamicRoutingDataSource;
//import com.baomidou.dynamic.datasource.creator.DataSourceCreator;
//import com.baomidou.dynamic.datasource.creator.DataSourceProperty;
//import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
//import lombok.RequiredArgsConstructor;
//import lombok.extern.slf4j.Slf4j;
//import org.springframework.stereotype.Service;
//
//import javax.sql.DataSource;
//import java.sql.Connection;
//import java.sql.DriverManager;
//import java.sql.SQLException;
//
///**
// * 数据源配置服务实现类
// *
// * <p>
// * 提供数据源的增删改查及校验功能,支持数据源密码加密存储
// * </p>
// *
// * @author lengleng
// * @date 2025/05/31
// */
//@Slf4j
//@Service
//@RequiredArgsConstructor
//public class GenDatasourceConfServiceImpl extends ServiceImpl<GenDatasourceConfMapper, GenDatasourceConf>
// implements GenDatasourceConfService {
//
// private final StringEncryptor stringEncryptor;
//
// private final DataSourceCreator hikariDataSourceCreator;
//
//
// /**
// * 保存数据源配置并进行加密处理
// * @param conf 数据源配置信息
// * @return 保存成功返回true,失败返回false
// */
// @Override
// public Boolean saveDsByEnc(GenDatasourceConf conf) {
// // 校验配置合法性
// if (!checkDataSource(conf)) {
// return Boolean.FALSE;
// }
//
// // 添加动态数据源
// addDynamicDataSource(conf);
//
// // 更新数据库配置
// conf.setPassword(stringEncryptor.encrypt(conf.getPassword()));
// this.baseMapper.insert(conf);
// return Boolean.TRUE;
// }
//
// /**
// * 更新加密数据源
// * @param conf 数据源配置信息
// * @return 更新成功返回true,失败返回false
// */
// @Override
// public Boolean updateDsByEnc(GenDatasourceConf conf) {
// if (!checkDataSource(conf)) {
// return Boolean.FALSE;
// }
// // 先移除
// DynamicRoutingDataSource dynamicRoutingDataSource = SpringContextHolder.getBean(DynamicRoutingDataSource.class);
// dynamicRoutingDataSource.removeDataSource(baseMapper.selectById(conf.getId()).getName());
//
// // 再添加
// addDynamicDataSource(conf);
//
// // 更新数据库配置
// if (StrUtil.isNotBlank(conf.getPassword())) {
// conf.setPassword(stringEncryptor.encrypt(conf.getPassword()));
// }
// this.baseMapper.updateById(conf);
// return Boolean.TRUE;
// }
//
// /**
// * 通过数据源ID删除数据源
// * @param dsIds 数据源ID数组
// * @return 删除是否成功
// */
// @Override
// public Boolean removeByDsId(Long[] dsIds) {
// DynamicRoutingDataSource dynamicRoutingDataSource = SpringContextHolder.getBean(DynamicRoutingDataSource.class);
// this.baseMapper.selectByIds(CollUtil.toList(dsIds))
// .forEach(ds -> dynamicRoutingDataSource.removeDataSource(ds.getName()));
// this.baseMapper.deleteByIds(CollUtil.toList(dsIds));
// return Boolean.TRUE;
// }
//
// /**
// * 添加动态数据源
// * @param conf 数据源配置信息
// */
// @Override
// public void addDynamicDataSource(GenDatasourceConf conf) {
// DataSourceProperty dataSourceProperty = new DataSourceProperty();
// dataSourceProperty.setPoolName(conf.getName());
// dataSourceProperty.setUrl(conf.getUrl());
// dataSourceProperty.setUsername(conf.getUsername());
// dataSourceProperty.setPassword(conf.getPassword());
// DataSource dataSource = hikariDataSourceCreator.createDataSource(dataSourceProperty);
//
// DynamicRoutingDataSource dynamicRoutingDataSource = SpringContextHolder.getBean(DynamicRoutingDataSource.class);
// dynamicRoutingDataSource.addDataSource(dataSourceProperty.getPoolName(), dataSource);
// }
//
// /**
// * 校验数据源配置是否有效
// * @param conf 数据源配置信息
// * @return 数据源配置是否有效,true表示有效
// * @throws RuntimeException 数据库连接失败时抛出异常
// */
// @Override
// public Boolean checkDataSource(GenDatasourceConf conf) {
// String url;
// // JDBC 配置形式
// if (DsConfTypeEnum.JDBC.getType().equals(conf.getConfType())) {
// url = conf.getUrl();
// }
// else if (DsJdbcUrlEnum.MSSQL.getDbName().equals(conf.getDsType())) {
// // 主机形式 sql server 特殊处理
// DsJdbcUrlEnum urlEnum = DsJdbcUrlEnum.get(conf.getDsType());
// url = String.format(urlEnum.getUrl(), conf.getHost(), conf.getPort(), conf.getDsName());
// }
// else {
// DsJdbcUrlEnum urlEnum = DsJdbcUrlEnum.get(conf.getDsType());
// url = String.format(urlEnum.getUrl(), conf.getHost(), conf.getPort(), conf.getDsName());
// }
//
// conf.setUrl(url);
//
// try (Connection connection = DriverManager.getConnection(url, conf.getUsername(), conf.getPassword())) {
// }
// catch (SQLException e) {
// log.error("数据源配置 {} , 获取链接失败", conf.getName(), e);
// throw new RuntimeException("数据库配置错误,链接失败");
// }
// return Boolean.TRUE;
// }
//
//}
///*
// * Copyright (c) 2018-2025, lengleng All rights reserved.
// *
// * Redistribution and use in source and binary forms, with or without
// * modification, are permitted provided that the following conditions are met:
// *
// * Redistributions of source code must retain the above copyright notice,
// * this list of conditions and the following disclaimer.
// * Redistributions in binary form must reproduce the above copyright
// * notice, this list of conditions and the following disclaimer in the
// * documentation and/or other materials provided with the distribution.
// * Neither the name of the pig4cloud.com developer nor the names of its
// * contributors may be used to endorse or promote products derived from
// * this software without specific prior written permission.
// * Author: lengleng (wangiegie@gmail.com)
// */
//package com.ask.service.impl;
//
//import cn.hutool.core.util.EnumUtil;
//import cn.hutool.core.util.StrUtil;
//import com.baomidou.dynamic.datasource.toolkit.DynamicDataSourceContextHolder;
//import com.baomidou.mybatisplus.core.metadata.IPage;
//import com.baomidou.mybatisplus.core.toolkit.StringUtils;
//import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
//import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
//import com.pig4cloud.pig.ask.api.dto.TableDto;
//import com.pig4cloud.pig.ask.api.dto.TableParam;
//import com.pig4cloud.pig.ask.api.entity.GenTable;
//import com.pig4cloud.pig.ask.api.entity.GenTableColumnEntity;
//import com.pig4cloud.pig.ask.api.enums.AutoFillEnum;
//import com.pig4cloud.pig.ask.api.enums.BoolFillEnum;
//import com.pig4cloud.pig.ask.api.enums.CommonColumnFiledEnum;
//import com.pig4cloud.pig.ask.mapper.GenTableMapper;
//import com.pig4cloud.pig.ask.service.GenTableService;
//import com.pig4cloud.pig.ask.utils.DataSourceQueryUtils;
//import com.pig4cloud.pig.common.datasource.enums.DsJdbcUrlEnum;
//import lombok.RequiredArgsConstructor;
//import lombok.extern.slf4j.Slf4j;
//import org.anyline.metadata.Column;
//import org.anyline.metadata.Table;
//import org.anyline.proxy.CacheProxy;
//import org.anyline.proxy.ServiceProxy;
//import org.jetbrains.annotations.NotNull;
//import org.springframework.stereotype.Service;
//
//import java.util.*;
//
///**
// * 代码生成表服务实现类
// *
// * @author lengleng
// * @date 2025/05/31
// */
//@Slf4j
//@Service
//@RequiredArgsConstructor
//public class GenTableServiceImpl extends ServiceImpl<GenTableMapper, GenTable> implements GenTableService {
// private final DataSourceQueryUtils dataSourceQueryUtils;
//
// /**
// * 查询表ddl 语句
// *
// * @param dsName 数据源名称
// * @param tableName 表名称
// * @return ddl 语句
// * @throws Exception
// */
// @Override
// public String queryTableDdl(String dsName, String tableName) throws Exception {
// // 手动切换数据源
// DynamicDataSourceContextHolder.push(dsName);
// Table table = ServiceProxy.metadata().table(tableName); // 获取表结构
// table.execute(false);// 不执行SQL
// ServiceProxy.ddl().create(table);
// return table.getDdl();// 返回创建表的DDL
// }
//
// /**
// * 查询表的全部字段
// *
// * @param dsName 数据源
// * @param tableName 表名称
// * @return column
// */
// @Override
// public List<String> queryTableColumn(String dsName, String tableName) {
// // 手动切换数据源
// DynamicDataSourceContextHolder.push(dsName);
// CacheProxy.clear();
// return ServiceProxy.metadata().columns(tableName).values().stream().map(Column::getName).toList();
// }
//
// /**
// * 查询对应数据源的表
// *
// * @param page 分页信息
// * @param table 查询条件
// * @return 表
// */
// @Override
// public IPage<TableDto> queryTablePage(Page<TableDto> page, TableParam table) {
// String sql = null;
// Map<String, Object> params = new HashMap<>();
// if (DsJdbcUrlEnum.MYSQL.getDbName().equals(table.getDbType())) {
// sql = "SELECT table_name,table_comment,create_time FROM information_schema.tables WHERE table_schema = (SELECT database()) AND table_type = 'BASE TABLE' ORDER BY table_name;";
// params.put("table_name", table.getTableName());
//
// } else if (DsJdbcUrlEnum.PG.getDbName().equals(table.getDbType())) {
// sql = """
// SELECT
// t.table_name,
// obj_description((t.table_schema || '.' || t.table_name)::regclass, 'pg_class') as table_comment
// FROM
// information_schema.tables t
// WHERE
// t.table_schema = current_schema()
// AND t.table_type = 'BASE TABLE'
// AND (:tableName IS NULL OR t.table_name ILIKE :tableName)
// ORDER BY
// t.table_name
// """;
// params.put("tableName",
// StrUtil.isBlank(table.getTableName()) ? null : "%" + table.getTableName() + "%");
// }
// if (StringUtils.isBlank(sql)) {
// return new Page<>(page.getCurrent(), page.getSize());
// }
//
// return dataSourceQueryUtils.executePageQuery(page,table.getDsName(),sql,TableDto.class);
// }
//
//
// /**
// * 查询数据源里面的全部表
// *
// * @param dsName 数据源名称
// * @return table
// */
// @Override
// public List<String> queryTableList(String dsName) {
// // 手动切换数据源
// DynamicDataSourceContextHolder.push(dsName);
// CacheProxy.clear();
// return ServiceProxy.metadata().tables().values().stream().map(Table::getName).toList();
// }
//
//
// /**
// * 获取表字段信息
// *
// * @param dsName 数据源信息
// * @param tableName 表名称
// * @param tableMetadata 表的元数据
// * @return list
// */
// private static @NotNull List<GenTableColumnEntity> getGenTableColumnEntities(String dsName, String tableName,
// Table tableMetadata) {
// List<GenTableColumnEntity> tableFieldList = new ArrayList<>();
// LinkedHashMap<String, Column> columns = tableMetadata.getColumns();
// columns.forEach((columnName, column) -> {
// GenTableColumnEntity genTableColumnEntity = new GenTableColumnEntity();
// genTableColumnEntity.setTableName(tableName);
// genTableColumnEntity.setDsName(dsName);
// genTableColumnEntity.setFieldName(column.getName());
// genTableColumnEntity.setFieldComment(column.getComment());
// genTableColumnEntity.setFieldType(column.getTypeName());
// genTableColumnEntity.setPrimaryPk(
// column.isPrimaryKey() == 1 ? BoolFillEnum.TRUE.getValue() : BoolFillEnum.FALSE.getValue());
// genTableColumnEntity.setAutoFill(AutoFillEnum.DEFAULT.name());
// genTableColumnEntity.setFormItem(BoolFillEnum.TRUE.getValue());
// genTableColumnEntity.setGridItem(BoolFillEnum.TRUE.getValue());
//
// // 审计字段处理
// if (EnumUtil.contains(CommonColumnFiledEnum.class, column.getName())) {
// CommonColumnFiledEnum commonColumnFiledEnum = CommonColumnFiledEnum.valueOf(column.getName());
// genTableColumnEntity.setFormItem(commonColumnFiledEnum.getFormItem());
// genTableColumnEntity.setGridItem(commonColumnFiledEnum.getGridItem());
// genTableColumnEntity.setAutoFill(commonColumnFiledEnum.getAutoFill());
// genTableColumnEntity.setSort(commonColumnFiledEnum.getSort());
// }
// tableFieldList.add(genTableColumnEntity);
// });
// return tableFieldList;
// }
//
//}
package com.ask.service.impl;
import com.ask.api.entity.KnowledgeBase;
import com.ask.mapper.KnowledgeBaseMapper;
import com.ask.service.KnowledgeBaseService;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Service;
import org.springframework.util.StringUtils;
/**
* 知识库服务实现类
*
* @author ai
* @date 2024/12/19
*/
@Slf4j
@Service
@RequiredArgsConstructor
public class KnowledgeBaseServiceImpl extends ServiceImpl<KnowledgeBaseMapper, KnowledgeBase> implements KnowledgeBaseService {
@Override
public boolean checkNameExists(String name, Long id) {
if (!StringUtils.hasText(name)) {
return false;
}
LambdaQueryWrapper<KnowledgeBase> wrapper = new LambdaQueryWrapper<>();
wrapper.eq(KnowledgeBase::getName, name.trim());
// 修改时排除当前记录
if (id != null) {
wrapper.ne(KnowledgeBase::getId, id);
}
return this.count(wrapper) > 0;
}
}
\ No newline at end of file
package com.ask.service.impl;
import com.ask.api.dto.DocumentSegmentResult;
import com.ask.api.entity.KnowledgeDocument;
import com.ask.mapper.KnowledgeDocumentMapper;
import com.ask.service.KnowledgeDocumentService;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.ai.document.Document;
import org.springframework.ai.reader.tika.TikaDocumentReader;
import org.springframework.ai.transformer.splitter.TokenTextSplitter;
import org.springframework.core.io.InputStreamResource;
import org.springframework.stereotype.Service;
import org.springframework.util.StringUtils;
import org.springframework.web.multipart.MultipartFile;
import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
/**
* 知识库文档服务实现类
*
* @author ai
* @date 2024/12/19
*/
@Slf4j
@Service
@RequiredArgsConstructor
public class KnowledgeDocumentServiceImpl extends ServiceImpl<KnowledgeDocumentMapper, KnowledgeDocument> implements KnowledgeDocumentService {
@Override
public List<DocumentSegmentResult> segmentDocuments(Long knowledgeBaseId, MultipartFile[] files) {
List<DocumentSegmentResult> results = new ArrayList<>();
if (files == null || files.length == 0) {
log.warn("文件数组为空,知识库ID: {}", knowledgeBaseId);
return results;
}
// 初始化文本分割器,设置分段参数
TokenTextSplitter textSplitter = new TokenTextSplitter(
1000, // 默认分段大小(token数)
200, // 重叠token数
5, // 最小分段大小
10000, // 最大分段大小
true // 保持分隔符
);
for (MultipartFile file : files) {
if (file.isEmpty()) {
log.warn("跳过空文件: {}", file.getOriginalFilename());
continue;
}
try {
// 使用Tika读取文档
TikaDocumentReader documentReader = new TikaDocumentReader(
new InputStreamResource(file.getInputStream())
);
// 读取文档内容
List<Document> documents = documentReader.get();
if (documents.isEmpty()) {
log.warn("文档解析失败或内容为空: {}", file.getOriginalFilename());
continue;
}
// 合并所有文档内容
StringBuilder contentBuilder = new StringBuilder();
for (Document doc : documents) {
if (StringUtils.hasText(doc.getText())) {
contentBuilder.append(doc.getText()).append("\n");
}
}
String fullContent = contentBuilder.toString().trim();
if (!StringUtils.hasText(fullContent)) {
log.warn("文档内容为空: {}", file.getOriginalFilename());
continue;
}
// 对文档进行分段
Document fullDocument = new Document(fullContent);
List<Document> segments = textSplitter.apply(List.of(fullDocument));
// 构建分段结果
DocumentSegmentResult result = new DocumentSegmentResult();
result.setFileName(file.getOriginalFilename());
result.setTotalSegments(segments.size());
result.setTotalCharacters(fullContent.length());
List<DocumentSegmentResult.DocumentSegment> segmentList = new ArrayList<>();
for (int i = 0; i < segments.size(); i++) {
Document segment = segments.get(i);
DocumentSegmentResult.DocumentSegment segmentDto = new DocumentSegmentResult.DocumentSegment();
segmentDto.setIndex(i + 1);
segmentDto.setContent(segment.getText());
segmentDto.setCharCount(segment.getText().length());
// 简单估算token数(大约1个中文字符=1.5token,英文单词=1token)
segmentDto.setTokenCount(estimateTokenCount(segment.getText()));
segmentList.add(segmentDto);
}
result.setSegments(segmentList);
results.add(result);
log.info("文档分段完成: {}, 分段数: {}, 总字符数: {}",
file.getOriginalFilename(), segments.size(), fullContent.length());
} catch (IOException e) {
log.error("文档处理失败: {}, 错误: {}", file.getOriginalFilename(), e.getMessage(), e);
// 创建错误结果
DocumentSegmentResult errorResult = new DocumentSegmentResult();
errorResult.setFileName(file.getOriginalFilename());
errorResult.setSegments(new ArrayList<>());
errorResult.setTotalSegments(0);
errorResult.setTotalCharacters(0);
results.add(errorResult);
}
}
return results;
}
@Override
public boolean saveSegmentResults(Long knowledgeBaseId, List<DocumentSegmentResult> segmentResults) {
if (segmentResults == null || segmentResults.isEmpty()) {
log.warn("分段结果为空,无法保存,知识库ID: {}", knowledgeBaseId);
return false;
}
List<KnowledgeDocument> knowledgeDocuments = new ArrayList<>();
for (DocumentSegmentResult result : segmentResults) {
// 创建文档记录
KnowledgeDocument document = new KnowledgeDocument();
document.setKnowledgeBaseId(knowledgeBaseId);
document.setName(result.getFileName());
document.setFileName(result.getFileName());
document.setStatus(0); // 默认待处理状态
document.setSegmentCount(result.getTotalSegments());
document.setTokenCount(result.getSegments().stream()
.mapToInt(DocumentSegmentResult.DocumentSegment::getTokenCount)
.sum());
knowledgeDocuments.add(document);
}
// 批量保存文档
boolean saveResult = this.saveBatch(knowledgeDocuments);
// if (saveResult) {
// // 异步进行向量化处理
// for (int i = 0; i < knowledgeDocuments.size(); i++) {
// KnowledgeDocument document = knowledgeDocuments.get(i);
// DocumentSegmentResult segmentResult = segmentResults.get(i);
//
// // 异步向量化
// vectorStoreService.vectorizeDocumentsAsync(
// knowledgeBaseId,
// document.getId(),
// List.of(segmentResult)
// );
//
// log.info("启动异步向量化处理: 文档ID={}, 文件名={}",
// document.getId(), segmentResult.getFileName());
// }
// }
return saveResult;
}
/**
* 估算token数量
*
* @param content 文本内容
* @return 预估token数
*/
private Integer estimateTokenCount(String content) {
if (!StringUtils.hasText(content)) {
return 0;
}
// 简单的token估算逻辑
// 中文字符按1.5个token计算,英文单词按1个token计算
int chineseCharCount = 0;
int englishWordCount = 0;
for (char c : content.toCharArray()) {
if (c >= 0x4e00 && c <= 0x9fff) {
chineseCharCount++;
}
}
// 估算英文单词数
String[] words = content.replaceAll("[\\u4e00-\\u9fff]", "").split("\\s+");
englishWordCount = words.length;
return Math.round(chineseCharCount * 1.5f + englishWordCount);
}
}
\ No newline at end of file
//package com.ask.service.impl;
//
//
//import com.ask.api.dto.DocumentSegmentResult;
//import com.ask.api.entity.KnowledgeDocument;
//import com.ask.service.KnowledgeDocumentService;
//import com.ask.service.VectorStoreService;
//import lombok.extern.slf4j.Slf4j;
//import org.springframework.ai.document.Document;
//import org.springframework.ai.embedding.EmbeddingModel;
//import org.springframework.ai.vectorstore.VectorStore;
//import org.springframework.beans.factory.annotation.Autowired;
//import org.springframework.context.annotation.Lazy;
//import org.springframework.scheduling.annotation.Async;
//import org.springframework.stereotype.Service;
//
//import java.util.ArrayList;
//import java.util.HashMap;
//import java.util.List;
//import java.util.Map;
//
///**
// * 向量存储服务实现类
// *
// * @author ai
// * @date 2024/12/19
// */
//@Slf4j
//@Service
//public class VectorStoreServiceImpl implements VectorStoreService {
//
// private final VectorStore vectorStore;
// private final EmbeddingModel embeddingModel;
//
// @Autowired
// @Lazy
// private KnowledgeDocumentService knowledgeDocumentService;
//
// public VectorStoreServiceImpl(VectorStore vectorStore, EmbeddingModel embeddingModel) {
// this.vectorStore = vectorStore;
// this.embeddingModel = embeddingModel;
// }
//
// @Async("vectorizeExecutor")
// @Override
// public void vectorizeDocumentsAsync(Long knowledgeBaseId, Long documentId, List<DocumentSegmentResult> segmentResults) {
// try {
// log.info("开始异步向量化处理,知识库ID: {}, 文档ID: {}", knowledgeBaseId, documentId);
//
// // 更新文档状态为处理中
// KnowledgeDocument document = knowledgeDocumentService.getById(documentId);
// if (document != null) {
// document.setStatus(1); // 1-处理中
// knowledgeDocumentService.updateById(document);
// }
//
// List<Document> documents = new ArrayList<>();
//
// for (DocumentSegmentResult segmentResult : segmentResults) {
// if (segmentResult.getSegments() != null) {
// for (DocumentSegmentResult.DocumentSegment segment : segmentResult.getSegments()) {
// // 创建文档对象
// Document doc = new Document(segment.getContent());
//
// // 添加元数据
// Map<String, Object> metadata = new HashMap<>();
// metadata.put("knowledge_base_id", knowledgeBaseId);
// metadata.put("document_id", documentId);
// metadata.put("file_name", segmentResult.getFileName());
// metadata.put("segment_index", segment.getIndex());
// metadata.put("char_count", segment.getCharCount());
// metadata.put("token_count", segment.getTokenCount());
//
// doc.setMetadata(metadata);
// documents.add(doc);
// }
// }
// }
//
// // 批量向量化并存储
// if (!documents.isEmpty()) {
// vectorStore.add(documents);
// log.info("向量化完成,共处理 {} 个分段", documents.size());
//
// // 更新文档状态为处理完成
// if (document != null) {
// document.setStatus(2); // 2-处理完成
// knowledgeDocumentService.updateById(document);
// }
// }
//
// } catch (Exception e) {
// log.error("向量化处理失败,知识库ID: {}, 文档ID: {}, 错误: {}", knowledgeBaseId, documentId, e.getMessage(), e);
//
// // 更新文档状态为处理失败
// KnowledgeDocument document = knowledgeDocumentService.getById(documentId);
// if (document != null) {
// document.setStatus(3); // 3-处理失败
// knowledgeDocumentService.updateById(document);
// }
// }
// }
//
// @Override
// public void vectorizeDocument(Long knowledgeBaseId, Long documentId, DocumentSegmentResult segmentResult) {
// try {
// List<Document> documents = new ArrayList<>();
//
// if (segmentResult.getSegments() != null) {
// for (DocumentSegmentResult.DocumentSegment segment : segmentResult.getSegments()) {
// // 创建文档对象
// Document doc = new Document(segment.getContent());
//
// // 添加元数据
// Map<String, Object> metadata = new HashMap<>();
// metadata.put("knowledge_base_id", knowledgeBaseId);
// metadata.put("document_id", documentId);
// metadata.put("file_name", segmentResult.getFileName());
// metadata.put("segment_index", segment.getIndex());
// metadata.put("char_count", segment.getCharCount());
// metadata.put("token_count", segment.getTokenCount());
//
// doc.setMetadata(metadata);
// documents.add(doc);
// }
// }
//
// // 向量化并存储
// if (!documents.isEmpty()) {
// vectorStore.add(documents);
// log.info("单个文档向量化完成: {}, 分段数: {}", segmentResult.getFileName(), documents.size());
// }
//
// } catch (Exception e) {
// log.error("单个文档向量化失败,知识库ID: {}, 文档ID: {}, 文件名: {}, 错误: {}",
// knowledgeBaseId, documentId, segmentResult.getFileName(), e.getMessage(), e);
// }
// }
//}
\ No newline at end of file
package com.ask.common.base;
import com.baomidou.mybatisplus.annotation.FieldFill;
import com.baomidou.mybatisplus.annotation.TableField;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Getter;
import lombok.Setter;
import java.io.Serializable;
import java.time.LocalDateTime;
/**
* 基础实体抽象类,包含通用实体字段
*
* @author lengleng
* @date 2025/05/31
*/
@Getter
@Setter
public class BaseEntity implements Serializable {
/**
* 创建者
*/
@Schema(description = "创建人")
@TableField(fill = FieldFill.INSERT)
private String createBy;
/**
* 创建时间
*/
@Schema(description = "创建时间")
@TableField(fill = FieldFill.INSERT)
private LocalDateTime createTime;
/**
* 更新者
*/
@Schema(description = "更新人")
@TableField(fill = FieldFill.INSERT_UPDATE)
private String updateBy;
/**
* 更新时间
*/
@Schema(description = "更新时间")
@TableField(fill = FieldFill.INSERT_UPDATE)
private LocalDateTime updateTime;
}
package com.ask.common.factory;
import org.springframework.beans.factory.config.YamlPropertiesFactoryBean;
import org.springframework.core.env.PropertiesPropertySource;
import org.springframework.core.env.PropertySource;
import org.springframework.core.io.support.EncodedResource;
import org.springframework.core.io.support.PropertySourceFactory;
import org.springframework.lang.Nullable;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.util.Properties;
/**
* YAML属性源工厂类:用于读取自定义YAML文件并转换为属性源
*
* @author lengleng
* @date 2025/05/30
*/
public class YamlPropertySourceFactory implements PropertySourceFactory {
/**
* 创建属性源
* @param name 属性源名称,可为空
* @param resource 编码资源
* @return 属性源对象
* @throws IOException 读取资源时可能抛出IO异常
*/
@Override
public PropertySource<?> createPropertySource(@Nullable String name, EncodedResource resource) throws IOException {
Properties propertiesFromYaml = loadYamlIntoProperties(resource);
String sourceName = name != null ? name : resource.getResource().getFilename();
return new PropertiesPropertySource(sourceName, propertiesFromYaml);
}
/**
* 将YAML资源加载为Properties对象
* @param resource 编码后的资源对象
* @return 加载后的Properties对象
* @throws FileNotFoundException 当资源文件不存在时抛出
*/
private Properties loadYamlIntoProperties(EncodedResource resource) throws FileNotFoundException {
try {
YamlPropertiesFactoryBean factory = new YamlPropertiesFactoryBean();
factory.setResources(resource.getResource());
factory.afterPropertiesSet();
return factory.getObject();
}
catch (IllegalStateException e) {
Throwable cause = e.getCause();
if (cause instanceof FileNotFoundException) {
throw (FileNotFoundException) e.getCause();
}
throw e;
}
}
}
# swagger 配置
swagger:
enabled: true
title: Pig Swagger API
gateway: http://${GATEWAY-HOST:127.0.0.1}:${GATEWAY-PORT:9999}/admin
token-url: ${swagger.gateway}/admin/oauth2/token
scope: server
...@@ -157,6 +157,16 @@ ...@@ -157,6 +157,16 @@
<groupId>org.springframework.ai</groupId> <groupId>org.springframework.ai</groupId>
<artifactId>spring-ai-advisors-vector-store</artifactId> <artifactId>spring-ai-advisors-vector-store</artifactId>
</dependency> </dependency>
<!--RAG相关依赖-->
<dependency>
<groupId>org.springframework.ai</groupId>
<artifactId>spring-ai-rag</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.ai</groupId>
<artifactId>spring-ai-tika-document-reader</artifactId>
</dependency>
<!-- 对话记忆--> <!-- 对话记忆-->
<dependency> <dependency>
......
...@@ -10,6 +10,9 @@ import org.springframework.ai.chat.memory.MessageWindowChatMemory; ...@@ -10,6 +10,9 @@ import org.springframework.ai.chat.memory.MessageWindowChatMemory;
import org.springframework.ai.chat.memory.repository.jdbc.JdbcChatMemoryRepository; import org.springframework.ai.chat.memory.repository.jdbc.JdbcChatMemoryRepository;
import org.springframework.ai.chat.memory.repository.jdbc.PostgresChatMemoryRepositoryDialect; import org.springframework.ai.chat.memory.repository.jdbc.PostgresChatMemoryRepositoryDialect;
import org.springframework.ai.openai.OpenAiChatModel; import org.springframework.ai.openai.OpenAiChatModel;
import org.springframework.ai.rag.advisor.RetrievalAugmentationAdvisor;
import org.springframework.ai.rag.generation.augmentation.ContextualQueryAugmenter;
import org.springframework.ai.rag.retrieval.search.VectorStoreDocumentRetriever;
import org.springframework.ai.tool.ToolCallbackProvider; import org.springframework.ai.tool.ToolCallbackProvider;
import org.springframework.ai.vectorstore.SearchRequest; import org.springframework.ai.vectorstore.SearchRequest;
import org.springframework.ai.vectorstore.VectorStore; import org.springframework.ai.vectorstore.VectorStore;
...@@ -42,12 +45,23 @@ public class CommonConfiguration { ...@@ -42,12 +45,23 @@ public class CommonConfiguration {
List<Advisor> advisors = new ArrayList<>(); List<Advisor> advisors = new ArrayList<>();
Advisor messageChatMemoryAdvisor =MessageChatMemoryAdvisor.builder(chatMemory).build(); Advisor messageChatMemoryAdvisor =MessageChatMemoryAdvisor.builder(chatMemory).build();
advisors.add(messageChatMemoryAdvisor); advisors.add(messageChatMemoryAdvisor);
// Advisor questionAnswerAdvisor =QuestionAnswerAdvisor.builder(vectorStore).searchRequest(SearchRequest.builder().build()).build();
// advisors.add(questionAnswerAdvisor);
return ChatClient.builder(model) return ChatClient.builder(model)
.defaultAdvisors(advisors) .defaultAdvisors(advisors)
// .defaultToolCallbacks(toolCallbackProvider)
.defaultAdvisors().build(); .defaultAdvisors().build();
} }
@Bean
public RetrievalAugmentationAdvisor retrievalAugmentationAdvisor(VectorStore vectorStore) {
return RetrievalAugmentationAdvisor.builder()
.documentRetriever(VectorStoreDocumentRetriever.builder()
.similarityThreshold(0.60)
.topK(5)
.vectorStore(vectorStore)
.build())
.queryAugmenter(ContextualQueryAugmenter.builder()
.allowEmptyContext(true)
.build())
.build();
}
} }
...@@ -19,6 +19,12 @@ import org.springframework.ai.chat.messages.SystemMessage; ...@@ -19,6 +19,12 @@ import org.springframework.ai.chat.messages.SystemMessage;
import org.springframework.ai.chat.messages.UserMessage; import org.springframework.ai.chat.messages.UserMessage;
import org.springframework.ai.chat.model.ChatResponse; import org.springframework.ai.chat.model.ChatResponse;
import org.springframework.ai.chat.prompt.Prompt; import org.springframework.ai.chat.prompt.Prompt;
import org.springframework.ai.rag.advisor.RetrievalAugmentationAdvisor;
import org.springframework.ai.rag.generation.augmentation.ContextualQueryAugmenter;
import org.springframework.ai.rag.retrieval.search.VectorStoreDocumentRetriever;
import org.springframework.ai.vectorstore.VectorStore;
import org.springframework.ai.vectorstore.filter.Filter;
import org.springframework.ai.vectorstore.filter.FilterExpressionBuilder;
import org.springframework.http.HttpHeaders; import org.springframework.http.HttpHeaders;
import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RequestMapping;
...@@ -42,6 +48,8 @@ public class ChatController { ...@@ -42,6 +48,8 @@ public class ChatController {
private final ChatConversationService chatConversationService; private final ChatConversationService chatConversationService;
private final RetrievalAugmentationAdvisor retrievalAugmentationAdvisor;
/** /**
* 获取会话ID * 获取会话ID
* @return 新的会话ID * @return 新的会话ID
...@@ -86,4 +94,30 @@ public class ChatController { ...@@ -86,4 +94,30 @@ public class ChatController {
// 使用修改后的提示获取响应 // 使用修改后的提示获取响应
return chatClient.prompt(prompt).advisors(a -> a.param(ChatMemory.CONVERSATION_ID, conversationId)).stream().content(); return chatClient.prompt(prompt).advisors(a -> a.param(ChatMemory.CONVERSATION_ID, conversationId)).stream().content();
} }
/**
* 最基本的AI流式输出对话
*
* * @param message
* @return
*/
@Operation(summary = "知识库对话", description = "知识库对话")
@GetMapping(value = "/rag/chat", produces = "text/html;charset=utf-8")
public Flux<String> ragChat(String message, String conversationId) {
// 创建系统消息,告诉大模型只返回工具名和参数
Message systemMessage = new SystemMessage("你是一个AI客服助手。请严格按照以下格式回答每个问题:");
Message userMessage = new UserMessage(message);
// 创建提示,包含系统消息和用户消息
Prompt prompt = new Prompt(Arrays.asList(systemMessage, userMessage));
// 使用修改后的提示获取响应
FilterExpressionBuilder builder = new FilterExpressionBuilder();
Filter.Expression filter = builder.eq("source","1").build();
return chatClient.prompt(prompt)
.advisors(a -> a.param(ChatMemory.CONVERSATION_ID, conversationId))
.advisors(a -> a.param(VectorStoreDocumentRetriever.FILTER_EXPRESSION, filter))
.advisors(retrievalAugmentationAdvisor)
.stream().content();
}
} }
\ No newline at end of file
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment