ai模块
This commit is contained in:
@@ -13,7 +13,6 @@ import org.xyzh.ai.client.dto.*;
|
||||
import org.xyzh.ai.client.callback.StreamCallback;
|
||||
import org.xyzh.ai.config.DifyConfig;
|
||||
import org.xyzh.ai.exception.DifyException;
|
||||
import org.xyzh.api.ai.dto.DifyFileInfo;
|
||||
|
||||
import java.io.BufferedReader;
|
||||
import java.io.File;
|
||||
@@ -483,11 +482,11 @@ public class DifyApiClient {
|
||||
}
|
||||
|
||||
if (!data.isEmpty()) {
|
||||
// 使用Fastjson2解析SSE数据
|
||||
// 解析SSE数据
|
||||
JSONObject jsonNode = JSON.parseObject(data);
|
||||
String event = jsonNode.containsKey("event") ? jsonNode.getString("event") : "";
|
||||
|
||||
// 转发所有事件到回调(包含完整数据)
|
||||
// 转发事件给回调(用于前端SSE转发)
|
||||
callback.onEvent(event, data);
|
||||
|
||||
switch (event) {
|
||||
|
||||
@@ -0,0 +1,97 @@
|
||||
package org.xyzh.ai.client.dto;
|
||||
|
||||
import com.alibaba.fastjson2.annotation.JSONField;
|
||||
|
||||
import lombok.Data;
|
||||
|
||||
/**
|
||||
* Dify文件上传响应DTO
|
||||
* 对应 Dify API /files/upload 的响应结构
|
||||
*/
|
||||
@Data
|
||||
public class DifyFileInfo {
|
||||
|
||||
/**
|
||||
* 文件ID(Dify返回)
|
||||
*/
|
||||
private String id;
|
||||
|
||||
/**
|
||||
* 文件名
|
||||
*/
|
||||
private String name;
|
||||
|
||||
/**
|
||||
* 文件大小(字节)
|
||||
*/
|
||||
private Integer size;
|
||||
|
||||
/**
|
||||
* 文件扩展名
|
||||
*/
|
||||
private String extension;
|
||||
|
||||
/**
|
||||
* 文件MIME类型
|
||||
*/
|
||||
@JSONField(name="mime_type")
|
||||
private String mimeType;
|
||||
|
||||
/**
|
||||
* 上传人ID
|
||||
*/
|
||||
@JSONField(name="created_by")
|
||||
private String createdBy;
|
||||
|
||||
/**
|
||||
* 上传时间(时间戳)
|
||||
*/
|
||||
@JSONField(name="created_at")
|
||||
private Long createdAt;
|
||||
|
||||
/**
|
||||
* 预览URL
|
||||
*/
|
||||
@JSONField(name="preview_url")
|
||||
private String previewUrl;
|
||||
|
||||
/**
|
||||
* 源文件URL
|
||||
*/
|
||||
@JSONField(name="source_url")
|
||||
private String sourceUrl;
|
||||
|
||||
/**
|
||||
* 文件类型:image、document、audio、video、file
|
||||
*/
|
||||
private String type;
|
||||
|
||||
/**
|
||||
* 传输方式:remote_url、local_file
|
||||
*/
|
||||
@JSONField(name="transfer_method")
|
||||
private String transferMethod;
|
||||
|
||||
/**
|
||||
* 文件URL或ID
|
||||
*/
|
||||
private String url;
|
||||
|
||||
/**
|
||||
* 本地文件上传ID
|
||||
*/
|
||||
@JSONField(name="upload_file_id")
|
||||
private String uploadFileId;
|
||||
|
||||
/**
|
||||
* 系统文件ID
|
||||
*/
|
||||
@JSONField(name="sys_file_id")
|
||||
private String sysFileId;
|
||||
|
||||
/**
|
||||
* 文件路径(从系统文件表获取)
|
||||
*/
|
||||
@JSONField(name="file_path")
|
||||
private String filePath;
|
||||
}
|
||||
@@ -0,0 +1,147 @@
|
||||
package org.xyzh.ai.controller;
|
||||
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.security.access.prepost.PreAuthorize;
|
||||
import org.springframework.validation.annotation.Validated;
|
||||
import org.springframework.web.bind.annotation.*;
|
||||
import org.xyzh.ai.service.impl.AgentServiceImpl;
|
||||
import org.xyzh.api.ai.dto.TbAgent;
|
||||
import org.xyzh.common.core.domain.ResultDomain;
|
||||
import org.xyzh.common.core.page.PageRequest;
|
||||
import org.xyzh.common.utils.validation.ValidationResult;
|
||||
import org.xyzh.common.utils.validation.ValidationParam;
|
||||
import org.xyzh.common.utils.validation.ValidationUtils;
|
||||
|
||||
import jakarta.validation.constraints.NotNull;
|
||||
|
||||
import java.util.Arrays;
|
||||
|
||||
/**
|
||||
* @description 智能体控制器
|
||||
* @filename AgentController.java
|
||||
* @author yslg
|
||||
* @copyright xyzh
|
||||
* @since 2025-12-17
|
||||
*/
|
||||
@Slf4j
|
||||
@Validated
|
||||
@RestController
|
||||
@RequestMapping("/ai/agent")
|
||||
public class AgentController {
|
||||
|
||||
@Autowired
|
||||
private AgentServiceImpl agentService;
|
||||
|
||||
/**
|
||||
* @description 创建智能体
|
||||
* @param agent
|
||||
* @author yslg
|
||||
* @since 2025-12-17
|
||||
*/
|
||||
@PostMapping
|
||||
@PreAuthorize("@ss.hasPermi('ai:agent:create')")
|
||||
public ResultDomain<TbAgent> createAgent(@RequestBody TbAgent agent) {
|
||||
log.info("创建智能体: name={}", agent.getName());
|
||||
// 参数校验
|
||||
ValidationResult result = ValidationUtils.validate(agent, Arrays.asList(
|
||||
ValidationUtils.requiredString("name", "智能体名称", 1, 100),
|
||||
ValidationUtils.requiredString("apiKey", "API密钥", 1, 100),
|
||||
ValidationUtils.requiredString("link", "智能体url",10,500)
|
||||
));
|
||||
if (!result.isValid()) {
|
||||
return ResultDomain.failure(result.getAllErrors());
|
||||
}
|
||||
return agentService.addAgent(agent);
|
||||
}
|
||||
|
||||
/**
|
||||
* @description 更新智能体
|
||||
* @param agent
|
||||
* @author yslg
|
||||
* @since 2025-12-17
|
||||
*/
|
||||
@PutMapping
|
||||
@PreAuthorize("@ss.hasPermi('ai:agent:update')")
|
||||
public ResultDomain<TbAgent> updateAgent(@RequestBody TbAgent agent) {
|
||||
log.info("更新智能体: agentId={}", agent.getAgentId());
|
||||
// 参数校验
|
||||
ValidationResult result = ValidationUtils.validate(agent, Arrays.asList(
|
||||
ValidationUtils.requiredString("agentId", "智能体ID"),
|
||||
ValidationUtils.requiredString("name", "智能体名称", 1, 100),
|
||||
ValidationUtils.requiredString("apiKey", "API密钥", 1, 100),
|
||||
ValidationUtils.requiredString("link", "智能体url",10,500)
|
||||
));
|
||||
if (!result.isValid()) {
|
||||
return ResultDomain.failure(result.getAllErrors());
|
||||
}
|
||||
return agentService.updateAgent(agent);
|
||||
}
|
||||
|
||||
/**
|
||||
* @description 删除智能体
|
||||
* @param agentId
|
||||
* @author yslg
|
||||
* @since 2025-12-17
|
||||
*/
|
||||
@DeleteMapping("/{agentId}")
|
||||
@PreAuthorize("@ss.hasPermi('ai:agent:delete')")
|
||||
public ResultDomain<TbAgent> deleteAgent(@PathVariable("agentId") @NotNull String agentId) {
|
||||
log.info("删除智能体: agentId={}", agentId);
|
||||
TbAgent agent = new TbAgent();
|
||||
agent.setAgentId(agentId);
|
||||
return agentService.deleteAgent(agent);
|
||||
}
|
||||
|
||||
/**
|
||||
* @description 获取智能体详情
|
||||
* @param agentId
|
||||
* @author yslg
|
||||
* @since 2025-12-17
|
||||
*/
|
||||
@GetMapping("/{agentId}")
|
||||
@PreAuthorize("@ss.hasPermi('ai:agent:view')")
|
||||
public ResultDomain<TbAgent> getAgent(@PathVariable("agentId") @NotNull String agentId) {
|
||||
log.info("获取智能体: agentId={}", agentId);
|
||||
ResultDomain<TbAgent> agentResult = agentService.selectAgentById(agentId);
|
||||
if (agentResult.getSuccess() && agentResult.getData() != null) {
|
||||
return ResultDomain.success("查询成功", agentResult.getData());
|
||||
}
|
||||
return ResultDomain.failure("智能体不存在"+agentResult.getMessage());
|
||||
}
|
||||
|
||||
/**
|
||||
* @description 分页查询智能体
|
||||
* @param pageRequest
|
||||
* @author yslg
|
||||
* @since 2025-12-17
|
||||
*/
|
||||
@PostMapping("/page")
|
||||
@PreAuthorize("@ss.hasPermi('ai:agent:view')")
|
||||
public ResultDomain<TbAgent> getAgentPage(@RequestBody PageRequest<TbAgent> pageRequest) {
|
||||
log.info("分页查询智能体");
|
||||
// 参数校验(支持嵌套属性路径)
|
||||
ValidationResult result = ValidationUtils.validate(pageRequest, Arrays.asList(
|
||||
ValidationParam.builder().fieldName("pageParam").fieldLabel("分页参数").required().build(),
|
||||
ValidationUtils.requiredNumber("pageParam.page", "页码", 1, null),
|
||||
ValidationUtils.requiredNumber("pageParam.pageSize", "每页条数", 1, 100)
|
||||
));
|
||||
if (!result.isValid()) {
|
||||
return ResultDomain.failure(result.getAllErrors());
|
||||
}
|
||||
return agentService.getAgentPage(pageRequest);
|
||||
}
|
||||
|
||||
/**
|
||||
* @description 获取智能体列表
|
||||
* @param tbAgent
|
||||
* @author yslg
|
||||
* @since 2025-12-17
|
||||
*/
|
||||
@GetMapping("/list")
|
||||
@PreAuthorize("@ss.hasPermi('ai:agent:view')")
|
||||
public ResultDomain<TbAgent> getAgentList(TbAgent tbAgent) {
|
||||
log.info("获取智能体列表");
|
||||
return agentService.getAgentList(tbAgent);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,305 @@
|
||||
package org.xyzh.ai.controller;
|
||||
|
||||
import jakarta.validation.constraints.NotNull;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.http.MediaType;
|
||||
import org.springframework.validation.annotation.Validated;
|
||||
import org.springframework.web.bind.annotation.*;
|
||||
import org.springframework.web.multipart.MultipartFile;
|
||||
import org.springframework.web.servlet.mvc.method.annotation.SseEmitter;
|
||||
import org.xyzh.api.ai.dto.ChatPrepareData;
|
||||
import org.xyzh.api.ai.dto.TbChat;
|
||||
import org.xyzh.api.ai.dto.TbChatMessage;
|
||||
import org.xyzh.api.ai.service.AIFileUploadService;
|
||||
import org.xyzh.api.ai.service.AgentChatService;
|
||||
import org.xyzh.common.auth.utils.LoginUtil;
|
||||
import org.xyzh.common.core.domain.LoginDomain;
|
||||
import org.xyzh.common.core.domain.ResultDomain;
|
||||
import org.xyzh.common.utils.NonUtils;
|
||||
import org.xyzh.common.utils.validation.ValidationParam;
|
||||
import org.xyzh.common.utils.validation.ValidationResult;
|
||||
import org.xyzh.common.utils.validation.ValidationUtils;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.Arrays;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* @description 智能体对话控制器 所有接口开放
|
||||
* @filename ChatController.java
|
||||
* @author yslg
|
||||
* @copyright xyzh
|
||||
* @since 2025-12-17
|
||||
*/
|
||||
@Slf4j
|
||||
@RestController
|
||||
@Validated
|
||||
@RequestMapping("/ai/chat")
|
||||
public class ChatController {
|
||||
|
||||
@Autowired
|
||||
private AgentChatService chatService;
|
||||
|
||||
@Autowired
|
||||
private AIFileUploadService fileUploadService;
|
||||
|
||||
// ====================== 会话管理 ======================
|
||||
|
||||
/**
|
||||
* @description 创建会话
|
||||
* @param
|
||||
* @author yslg
|
||||
* @since 2025-12-17
|
||||
*/
|
||||
@PostMapping("/conversation")
|
||||
public ResultDomain<TbChat> createChat(@RequestBody TbChat chat, @RequestHeader("Authorization") String token) {
|
||||
ValidationResult result = ValidationUtils.validate(chat, Arrays.asList(
|
||||
ValidationUtils.requiredString("agentId", "智能体id", 1, 100),
|
||||
ValidationUtils.requiredString("title", "对话标题", 1, 100),
|
||||
ValidationUtils.requiredString("userId", "用户id", 1, 100)
|
||||
));
|
||||
if (!result.isValid()) {
|
||||
return ResultDomain.failure(result.getAllErrors());
|
||||
}
|
||||
// 默认来客,如果token不为空且token对应的用户存在,说明是员工
|
||||
chat.setUserType(false);
|
||||
if(NonUtils.isNotEmpty(token)){
|
||||
LoginDomain loginDomain = LoginUtil.getCurrentLogin();
|
||||
if (NonUtils.isNotEmpty(loginDomain)) {
|
||||
chat.setUserType(true);
|
||||
}
|
||||
}
|
||||
|
||||
log.info("创建会话: agentId={}, title={}, userId={}, userType={}", chat.getAgentId(), chat.getTitle(), chat.getUserId());
|
||||
return chatService.createChat(chat);
|
||||
}
|
||||
|
||||
/**
|
||||
* @description 更新对话
|
||||
* @param
|
||||
* @author yslg
|
||||
* @since 2025-12-17
|
||||
*/
|
||||
@PutMapping("/conversation")
|
||||
public ResultDomain<TbChat> updateChat(@RequestBody TbChat chat, @RequestHeader("Authorization") String token) {
|
||||
ValidationResult result = ValidationUtils.validate(chat, Arrays.asList(
|
||||
ValidationUtils.requiredString("agentId", "智能体id", 1, 100),
|
||||
ValidationUtils.requiredString("title", "对话标题", 1, 100),
|
||||
ValidationUtils.requiredString("userId", "用户id", 1, 100),
|
||||
ValidationParam.builder().fieldName("userType").fieldLabel("用户类型").required().fieldType(Boolean.class).build()
|
||||
));
|
||||
if (!result.isValid()) {
|
||||
return ResultDomain.failure(result.getAllErrors());
|
||||
}
|
||||
chat.setUserType(false);
|
||||
if(NonUtils.isNotEmpty(token)){
|
||||
LoginDomain loginDomain = LoginUtil.getCurrentLogin();
|
||||
if (NonUtils.isNotEmpty(loginDomain)) {
|
||||
chat.setUserType(true);
|
||||
}
|
||||
}
|
||||
log.info("更新会话: chatId={}", chat.getChatId());
|
||||
return chatService.updateChat(chat);
|
||||
}
|
||||
|
||||
/**
|
||||
* @description 删除对话
|
||||
* @param
|
||||
* @author yslg
|
||||
* @since 2025-12-17
|
||||
*/
|
||||
@DeleteMapping("/conversation")
|
||||
public ResultDomain<TbChat> deleteChat(@RequestBody TbChat chat, @RequestHeader("Authorization") String token) {
|
||||
chat.setUserType(false);
|
||||
if(NonUtils.isNotEmpty(token)){
|
||||
LoginDomain loginDomain = LoginUtil.getCurrentLogin();
|
||||
if (NonUtils.isNotEmpty(loginDomain)) {
|
||||
chat.setUserType(true);
|
||||
}
|
||||
}
|
||||
log.info("删除会话: chatId={}", chat.getChatId());
|
||||
return chatService.deleteChat(chat);
|
||||
}
|
||||
|
||||
/**
|
||||
* @description 获取对话列表
|
||||
* @param
|
||||
* @author yslg
|
||||
* @since 2025-12-17
|
||||
*/
|
||||
@GetMapping("/conversations")
|
||||
public ResultDomain<TbChat> getChatList(@RequestBody TbChat filter, @RequestHeader("Authorization") String token) {
|
||||
log.info("获取会话列表: agentId={}", filter.getAgentId());
|
||||
|
||||
filter.setUserType(false);
|
||||
if(NonUtils.isNotEmpty(token)){
|
||||
LoginDomain loginDomain = LoginUtil.getCurrentLogin();
|
||||
if (NonUtils.isNotEmpty(loginDomain)) {
|
||||
filter.setUserType(true);
|
||||
}
|
||||
}
|
||||
return chatService.getChatList(filter);
|
||||
}
|
||||
|
||||
// ====================== 消息管理 ======================
|
||||
|
||||
/**
|
||||
* @description 获取对话消息列表
|
||||
* @param
|
||||
* @author yslg
|
||||
* @since 2025-12-17
|
||||
*/
|
||||
@PostMapping("/messages")
|
||||
public ResultDomain<TbChatMessage> getMessageList(@RequestBody TbChat filter, @RequestHeader("Authorization") String token) {
|
||||
ValidationResult result = ValidationUtils.validate(filter, Arrays.asList(
|
||||
ValidationUtils.requiredString("agentId", "智能体id",10,50),
|
||||
ValidationUtils.requiredString("chatId", "对话Id", 10, 50),
|
||||
ValidationUtils.requiredString("userId", "用户Id")
|
||||
));
|
||||
|
||||
filter.setUserType(false);
|
||||
if(NonUtils.isNotEmpty(token)){
|
||||
LoginDomain loginDomain = LoginUtil.getCurrentLogin();
|
||||
if (NonUtils.isNotEmpty(loginDomain)) {
|
||||
filter.setUserType(true);
|
||||
}
|
||||
}
|
||||
log.info("获取消息列表: chatId={}", filter.getChatId());
|
||||
return chatService.getChatMessageList(filter);
|
||||
}
|
||||
|
||||
// ====================== 流式对话 ======================
|
||||
|
||||
/**
|
||||
* @description 准备流式对话会话数据
|
||||
* @param chatPrepareData
|
||||
* @author yslg
|
||||
* @since 2025-12-17
|
||||
*/
|
||||
@PostMapping("/stream/prepare")
|
||||
public ResultDomain<String> prepareStreamChat(@RequestBody ChatPrepareData chatPrepareData, @RequestHeader("Authorization") String token) {
|
||||
ValidationResult result = ValidationUtils.validate(chatPrepareData, Arrays.asList(
|
||||
ValidationUtils.requiredString("agentId", "智能体Id", 1, 50),
|
||||
ValidationUtils.requiredString("chatId", "会话Id", 1, 50),
|
||||
ValidationUtils.requiredString("query", "用户问题"),
|
||||
ValidationUtils.requiredString("userId", "用户Id", 1, 100)
|
||||
));
|
||||
if(!result.isValid()){
|
||||
return ResultDomain.failure(result.getAllErrors());
|
||||
}
|
||||
// 设置用户类型
|
||||
chatPrepareData.setUserType(false);
|
||||
if(NonUtils.isNotEmpty(token)){
|
||||
LoginDomain loginDomain = LoginUtil.getCurrentLogin();
|
||||
if (NonUtils.isNotEmpty(loginDomain)) {
|
||||
chatPrepareData.setUserType(true);
|
||||
}
|
||||
}
|
||||
log.info("准备流式对话: agentId={}, chatId={}, query={}", chatPrepareData.getAgentId(), chatPrepareData.getChatId(), chatPrepareData.getQuery());
|
||||
return chatService.prepareChatMessageSession(chatPrepareData);
|
||||
}
|
||||
|
||||
/**
|
||||
* @description 进行流式对话
|
||||
* @param
|
||||
* @author yslg
|
||||
* @since 2025-12-17
|
||||
*/
|
||||
@GetMapping(value = "/stream", produces = MediaType.TEXT_EVENT_STREAM_VALUE)
|
||||
public SseEmitter streamChat(@RequestParam("sessionId") String sessionId) {
|
||||
if(NonUtils.isEmpty(sessionId)){
|
||||
SseEmitter emitter = new SseEmitter(300000L);
|
||||
try {
|
||||
emitter.send(SseEmitter.event().name("error").data("{\"message\":\"会话不存在\"}"));
|
||||
} catch (IOException e) {
|
||||
log.error("发送错误事件失败", e);
|
||||
}finally {
|
||||
emitter.complete();
|
||||
}
|
||||
return emitter;
|
||||
}
|
||||
log.info("建立SSE连接: sessionId={}", sessionId);
|
||||
return chatService.streamChatMessageWithSse(sessionId);
|
||||
}
|
||||
|
||||
/**
|
||||
* @description 停止会话
|
||||
* @param
|
||||
* @author yslg
|
||||
* @since 2025-12-17
|
||||
*/
|
||||
@PostMapping("/stop")
|
||||
public ResultDomain<Boolean> stopChat(@RequestBody Map<String, String> params, @RequestHeader("Authorization") String token) {
|
||||
ValidationResult result = ValidationUtils.validateMap(params, Arrays.asList(
|
||||
ValidationUtils.requiredString("taskId", "任务ID"),
|
||||
ValidationUtils.requiredString("agentId", "智能体ID"),
|
||||
ValidationUtils.requiredString("userId", "用户ID")
|
||||
));
|
||||
if (!result.isValid()) {
|
||||
return ResultDomain.failure(result.getAllErrors());
|
||||
}
|
||||
TbChat filter = new TbChat();
|
||||
filter.setAgentId(params.get("agentId"));
|
||||
filter.setUserId(params.get("userId"));
|
||||
filter.setUserType(false);
|
||||
if(NonUtils.isNotEmpty(token)){
|
||||
LoginDomain loginDomain = LoginUtil.getCurrentLogin();
|
||||
if (NonUtils.isNotEmpty(loginDomain)) {
|
||||
filter.setUserType(true);
|
||||
}
|
||||
}
|
||||
log.info("停止对话: taskId={}", params.get("taskId"));
|
||||
return chatService.stopChatMessageByTaskId(filter, params.get("taskId"));
|
||||
}
|
||||
|
||||
/**
|
||||
* @description 评价消息
|
||||
* @param
|
||||
* @author yslg
|
||||
* @since 2025-12-17
|
||||
*/
|
||||
@PostMapping("/comment")
|
||||
public ResultDomain<Boolean> commentMessage(@RequestBody Map<String, String> params, @RequestHeader("Authorization") String token) {
|
||||
ValidationResult result = ValidationUtils.validateMap(params, Arrays.asList(
|
||||
ValidationUtils.requiredString("agentId", "智能体ID"),
|
||||
ValidationUtils.requiredString("chatId", "对话ID"),
|
||||
ValidationUtils.requiredString("messageId", "消息ID"),
|
||||
ValidationUtils.requiredString("comment", "评价"),
|
||||
ValidationUtils.requiredString("userId", "用户ID")
|
||||
));
|
||||
if (!result.isValid()) {
|
||||
return ResultDomain.failure(result.getAllErrors());
|
||||
}
|
||||
TbChat filter = new TbChat();
|
||||
filter.setAgentId(params.get("agentId"));
|
||||
filter.setChatId(params.get("chatId"));
|
||||
filter.setUserId(params.get("userId"));
|
||||
filter.setUserType(false);
|
||||
if(NonUtils.isNotEmpty(token)){
|
||||
LoginDomain loginDomain = LoginUtil.getCurrentLogin();
|
||||
if (NonUtils.isNotEmpty(loginDomain)) {
|
||||
filter.setUserType(true);
|
||||
}
|
||||
}
|
||||
log.info("评价消息: messageId={}, comment={}", params.get("messageId"), params.get("comment"));
|
||||
return chatService.commentChatMessage(filter, params.get("messageId"), params.get("comment"));
|
||||
}
|
||||
|
||||
// ====================== 文件上传 ======================
|
||||
|
||||
/**
|
||||
* @description 上传文件用于对话(图文多模态)
|
||||
* @param file 文件
|
||||
* @param agentId 智能体ID
|
||||
* @author yslg
|
||||
* @since 2025-12-17
|
||||
*/
|
||||
@PostMapping(value = "/file/upload", consumes = MediaType.MULTIPART_FORM_DATA_VALUE)
|
||||
public ResultDomain<Map<String, Object>> uploadFileForChat(
|
||||
@RequestPart("file") @NotNull MultipartFile file,
|
||||
@RequestPart("agentId") @NotNull String agentId) {
|
||||
log.info("上传对话文件: agentId={}, fileName={}", agentId, file.getOriginalFilename());
|
||||
return fileUploadService.uploadFileForChat(file, agentId);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,350 @@
|
||||
package org.xyzh.ai.controller;
|
||||
|
||||
import com.alibaba.fastjson2.JSONObject;
|
||||
import jakarta.validation.Valid;
|
||||
import jakarta.validation.constraints.NotBlank;
|
||||
import jakarta.validation.constraints.NotEmpty;
|
||||
import jakarta.validation.constraints.NotNull;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.http.MediaType;
|
||||
import org.springframework.security.access.prepost.PreAuthorize;
|
||||
import org.springframework.validation.annotation.Validated;
|
||||
import org.springframework.web.bind.annotation.*;
|
||||
import org.springframework.web.multipart.MultipartFile;
|
||||
import org.xyzh.ai.service.impl.DifyProxyServiceImpl;
|
||||
import org.xyzh.ai.service.impl.KnowledgeServiceImpl;
|
||||
import org.xyzh.api.ai.dto.TbKnowledge;
|
||||
import org.xyzh.api.ai.dto.TbKnowledgeFile;
|
||||
import org.xyzh.common.core.domain.ResultDomain;
|
||||
import org.xyzh.common.core.page.PageRequest;
|
||||
import org.xyzh.common.utils.validation.ValidationResult;
|
||||
import org.xyzh.common.utils.validation.ValidationUtils;
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.util.HashSet;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* @description 知识库控制器
|
||||
* @filename KnowledgeController.java
|
||||
* @author yslg
|
||||
* @copyright xyzh
|
||||
* @since 2025-12-17
|
||||
*/
|
||||
@RestController
|
||||
@Validated
|
||||
@RequestMapping("/ai/knowledge")
|
||||
public class KnowledgeController {
|
||||
private static final Logger logger = LoggerFactory.getLogger(KnowledgeController.class);
|
||||
|
||||
@Autowired
|
||||
private KnowledgeServiceImpl knowledgeService;
|
||||
|
||||
@Autowired
|
||||
private DifyProxyServiceImpl difyProxyService;
|
||||
|
||||
// ====================== 知识库管理 ======================
|
||||
|
||||
/**
|
||||
* @description 创建知识库基础信息,包含dify知识库各种参数的配置
|
||||
* @param knowledge
|
||||
* @author yslg
|
||||
* @since 2025-12-18
|
||||
*/
|
||||
@PreAuthorize("@ss.hasPermission('ai:knowledge:create')")
|
||||
@PostMapping
|
||||
public ResultDomain<TbKnowledge> createKnowledge(@RequestBody TbKnowledge knowledge) {
|
||||
ValidationResult result = ValidationUtils.validate(knowledge, Arrays.asList(
|
||||
ValidationUtils.requiredString("title", "知识库标题", 1, 50),
|
||||
ValidationUtils.inSet("difyIndexingTechnique", "Dify索引方式(high_quality/economy)" , true, new HashSet<>(Arrays.asList("high_quality", "economy"))),
|
||||
ValidationUtils.inSet("category", "所属分类 workcase 内部知识库、外部知识库", true, new HashSet<>(Arrays.asList("default", "内部知识库", "外部知识库")))
|
||||
));
|
||||
if(!result.isValid()){
|
||||
return ResultDomain.failure(result.getAllErrors());
|
||||
}
|
||||
logger.info("创建知识库: title={}", knowledge.getTitle());
|
||||
return knowledgeService.createKnowledge(knowledge, "PUBLIC", null, null);
|
||||
}
|
||||
|
||||
/**
|
||||
* @description 更新知识库,包含dify知识库各种参数的配置
|
||||
* @param knowledge
|
||||
* @author yslg
|
||||
* @since 2025-12-18
|
||||
*/
|
||||
@PreAuthorize("@ss.hasPermission('ai:knowledge:update')")
|
||||
@PutMapping
|
||||
public ResultDomain<TbKnowledge> updateKnowledge(@RequestBody @Valid TbKnowledge knowledge) {
|
||||
ValidationResult result = ValidationUtils.validate(knowledge, Arrays.asList(
|
||||
ValidationUtils.requiredString("title", "知识库标题", 1, 50),
|
||||
ValidationUtils.inSet("difyIndexingTechnique", "Dify索引方式(high_quality/economy)" , true, new HashSet<>(Arrays.asList("high_quality", "economy"))),
|
||||
ValidationUtils.inSet("category", "所属分类 workcase 内部知识库、外部知识库", true, new HashSet<>(Arrays.asList("default", "内部知识库", "外部知识库")))
|
||||
|
||||
));
|
||||
if(!result.isValid()){
|
||||
return ResultDomain.failure(result.getAllErrors());
|
||||
}
|
||||
logger.info("更新知识库: knowledgeId={}", knowledge.getKnowledgeId());
|
||||
return knowledgeService.updateKnowledge(knowledge);
|
||||
}
|
||||
|
||||
/**
|
||||
* @description 删除知识库,同时删除dify知识库
|
||||
* @param knowledgeId
|
||||
* @author yslg
|
||||
* @since 2025-12-18
|
||||
*/
|
||||
@PreAuthorize("@ss.hasPermission('ai:knowledge:delete')")
|
||||
@DeleteMapping("/{knowledgeId}")
|
||||
public ResultDomain<Boolean> deleteKnowledge(@PathVariable("knowledgeId") @NotBlank String knowledgeId) {
|
||||
logger.info("删除知识库: knowledgeId={}", knowledgeId);
|
||||
return knowledgeService.deleteKnowledge(knowledgeId);
|
||||
}
|
||||
|
||||
/**
|
||||
* @description 获取知识库详情,包含dify知识库各种参数的配置
|
||||
* @param knowledgeId
|
||||
* @author yslg
|
||||
* @since 2025-12-18
|
||||
*/
|
||||
@PreAuthorize("@ss.hasPermission('ai:knowledge:view')")
|
||||
@GetMapping("/{knowledgeId}")
|
||||
public ResultDomain<TbKnowledge> getKnowledge(@PathVariable("knowledgeId") @NotBlank String knowledgeId) {
|
||||
logger.info("获取知识库: knowledgeId={}", knowledgeId);
|
||||
return knowledgeService.getKnowledgeById(knowledgeId);
|
||||
}
|
||||
|
||||
/**
|
||||
* @description 查询知识库列表
|
||||
* @param filter
|
||||
* @author yslg
|
||||
* @since 2025-12-18
|
||||
*/
|
||||
@PreAuthorize("@ss.hasPermission('ai:knowledge:view')")
|
||||
@PostMapping("/list")
|
||||
public ResultDomain<TbKnowledge> listKnowledges(@RequestBody(required = false) TbKnowledge filter) {
|
||||
logger.info("查询知识库列表");
|
||||
return knowledgeService.listKnowledges(filter);
|
||||
}
|
||||
|
||||
/**
|
||||
* @description 分页查询知识库
|
||||
* @param pageRequest
|
||||
* @author yslg
|
||||
* @since 2025-12-18
|
||||
*/
|
||||
@PreAuthorize("@ss.hasPermission('ai:knowledge:view')")
|
||||
@PostMapping("/page")
|
||||
public ResultDomain<TbKnowledge> pageKnowledges(@RequestBody @Valid PageRequest<TbKnowledge> pageRequest) {
|
||||
logger.info("分页查询知识库");
|
||||
return knowledgeService.pageKnowledges(pageRequest.getFilter(), pageRequest.getPageParam());
|
||||
}
|
||||
|
||||
/**
|
||||
* @description 获取知识库统计信息
|
||||
* @param knowledgeId
|
||||
* @author yslg
|
||||
* @since 2025-12-18
|
||||
*/
|
||||
@PreAuthorize("@ss.hasPermission('ai:knowledge:view')")
|
||||
@GetMapping("/{knowledgeId}/stats")
|
||||
public ResultDomain<TbKnowledge> getKnowledgeStats(@PathVariable("knowledgeId") @NotBlank String knowledgeId) {
|
||||
logger.info("获取知识库统计: knowledgeId={}", knowledgeId);
|
||||
return knowledgeService.getKnowledgeStats(knowledgeId);
|
||||
}
|
||||
|
||||
// ====================== 文件管理 ======================
|
||||
|
||||
/**
|
||||
* @description 获取知识库文档列表
|
||||
* @param knowledgeId 知识库id
|
||||
* @author yslg
|
||||
* @since 2025-12-18
|
||||
*/
|
||||
@PreAuthorize("@ss.hasPermission('ai:knowledge:file:view')")
|
||||
@GetMapping("/{knowledgeId}/documents")
|
||||
public ResultDomain<Map<String, Object>> getDocumentList(
|
||||
@PathVariable("knowledgeId") @NotBlank String knowledgeId,
|
||||
@RequestParam(value = "page", defaultValue = "1") Integer page,
|
||||
@RequestParam(value = "limit", defaultValue = "20") Integer limit) {
|
||||
logger.info("获取文档列表: knowledgeId={}", knowledgeId);
|
||||
return knowledgeService.getDocumentList(knowledgeId, page, limit);
|
||||
}
|
||||
|
||||
/**
|
||||
* @description 上传文件到知识库(同步到Dify)
|
||||
* @param file 上传文件
|
||||
* @param knowledgeId 知识库id
|
||||
* @param indexingTechnique 索引方式
|
||||
* @author yslg
|
||||
* @since 2025-12-18
|
||||
*/
|
||||
@PreAuthorize("@ss.hasPermission('ai:knowledge:file:upload')")
|
||||
@PostMapping(name = "/file/upload", consumes = MediaType.MULTIPART_FORM_DATA_VALUE)
|
||||
public ResultDomain<TbKnowledgeFile> uploadToKnowledge(
|
||||
@RequestParam("file") @NotNull MultipartFile file,
|
||||
@RequestParam("knowledgeId") @NotBlank String knowledgeId,
|
||||
@RequestParam(value = "indexingTechnique", required = false) String indexingTechnique) {
|
||||
logger.info("上传知识库文件: knowledgeId={}, fileName={}", knowledgeId, file.getOriginalFilename());
|
||||
return knowledgeService.uploadKnowledgeFile(knowledgeId, file, indexingTechnique);
|
||||
}
|
||||
|
||||
/**
|
||||
* @description 批量上传文件到知识库
|
||||
* @param files 文件数组
|
||||
* @param knowledgeId 知识库id
|
||||
* @param indexingTechnique 索引方式
|
||||
* @author yslg
|
||||
* @since 2025-12-18
|
||||
*/
|
||||
@PreAuthorize("@ss.hasPermission('ai:knowledge:file:upload')")
|
||||
@PostMapping(name = "/file/batch-upload", consumes = MediaType.MULTIPART_FORM_DATA_VALUE)
|
||||
public ResultDomain<TbKnowledgeFile> batchUploadToKnowledge(
|
||||
@RequestParam("files") @NotEmpty List<MultipartFile> files,
|
||||
@RequestParam("knowledgeId") @NotBlank String knowledgeId,
|
||||
@RequestParam(value = "indexingTechnique", required = false) String indexingTechnique) {
|
||||
logger.info("批量上传知识库文件: knowledgeId={}, fileCount={}", knowledgeId, files.size());
|
||||
return knowledgeService.batchUploadKnowledgeFile(knowledgeId, files, indexingTechnique);
|
||||
}
|
||||
|
||||
/**
|
||||
* 上传新文件来更新知识库文件,注意fileRootId一致,生成新id
|
||||
* @param file 上传文件
|
||||
* @param knowledgeId 知识库id
|
||||
* @param fileRootId 文件Rootid,多version下一致
|
||||
* @author yslg
|
||||
* @since 2025-12-18
|
||||
*/
|
||||
@PreAuthorize("@ss.hasPermission('ai:knowledge:file:update')")
|
||||
@PutMapping(value = "/file/upload", consumes = MediaType.MULTIPART_FORM_DATA_VALUE)
|
||||
public ResultDomain<TbKnowledgeFile> updateFile(
|
||||
@RequestParam("file") @NotNull MultipartFile file,
|
||||
@RequestParam("knowledgeId") @NotBlank String knowledgeId,
|
||||
@RequestParam("fileRootId") @NotBlank String fileRootId) {
|
||||
logger.info("更新知识库文件: knowledgeId={}, fileName={}", knowledgeId, file.getOriginalFilename());
|
||||
return knowledgeService.updateKnowledgeFileVersion(knowledgeId, file, fileRootId);
|
||||
}
|
||||
|
||||
/**
|
||||
* 删除知识库文件(同时删除Dify文档),所有filtRootId的文件一起软删除
|
||||
* @param fileId 文件id
|
||||
* @author yslg
|
||||
* @since 2025-12-18
|
||||
*/
|
||||
@PreAuthorize("@ss.hasPermission('ai:knowledge:file:delete')")
|
||||
@DeleteMapping("/file/{fileId}")
|
||||
public ResultDomain<Boolean> deleteFile(@PathVariable("fileId") @NotBlank String fileId) {
|
||||
logger.info("删除知识库文件: fileId={}", fileId);
|
||||
return knowledgeService.deleteKnowledgeFileById(fileId);
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取文件历史版本,获取fileRootId下所有version
|
||||
* @param fileRootId 文件id
|
||||
* @author yslg
|
||||
* @since 2025-12-18
|
||||
*/
|
||||
@PreAuthorize("@ss.hasPermission('ai:knowledge:file:view')")
|
||||
@GetMapping("/file/{fileId}/history")
|
||||
public ResultDomain<TbKnowledgeFile> getFileHistory(@PathVariable("fileId") @NotBlank String fileRootId) {
|
||||
logger.info("获取文件历史: fileRootId={}", fileRootId);
|
||||
return knowledgeService.getKnowledgeFileHistory(fileRootId);
|
||||
}
|
||||
|
||||
// ====================== 文档分段管理 ======================
|
||||
|
||||
/**
|
||||
* @description 获取文档分段列表
|
||||
* @param datasetId Dify数据集ID
|
||||
* @param documentId Dify文档ID
|
||||
* @author yslg
|
||||
* @since 2025-12-18
|
||||
*/
|
||||
@PreAuthorize("@ss.hasPermission('ai:dify:segment:view')")
|
||||
@GetMapping("/datasets/{datasetId}/documents/{documentId}/segments")
|
||||
public ResultDomain<JSONObject> getDocumentSegments(
|
||||
@PathVariable("datasetId") @NotBlank String datasetId,
|
||||
@PathVariable("documentId") @NotBlank String documentId) {
|
||||
logger.info("获取文档分段: datasetId={}, documentId={}", datasetId, documentId);
|
||||
return difyProxyService.getDocumentSegments(datasetId, documentId);
|
||||
}
|
||||
|
||||
/**
|
||||
* @description 创建文档分段
|
||||
* @param datasetId Dify数据集ID
|
||||
* @param documentId Dify文档ID
|
||||
* @param requestBody 分段内容
|
||||
* @author yslg
|
||||
* @since 2025-12-18
|
||||
*/
|
||||
@PreAuthorize("@ss.hasPermission('ai:dify:segment:create')")
|
||||
@PostMapping("/datasets/{datasetId}/documents/{documentId}/segments")
|
||||
public ResultDomain<String> createSegment(
|
||||
@PathVariable("datasetId") @NotBlank String datasetId,
|
||||
@PathVariable("documentId") @NotBlank String documentId,
|
||||
@RequestBody Map<String, Object> requestBody) {
|
||||
logger.info("创建文档分段: datasetId={}, documentId={}", datasetId, documentId);
|
||||
return difyProxyService.createSegment(datasetId, documentId, requestBody);
|
||||
}
|
||||
|
||||
/**
|
||||
* @description 更新文档分段
|
||||
* @param datasetId Dify数据集ID
|
||||
* @param documentId Dify文档ID
|
||||
* @param segmentId Dify分段ID
|
||||
* @param requestBody 分段内容
|
||||
* @author yslg
|
||||
* @since 2025-12-18
|
||||
*/
|
||||
@PreAuthorize("@ss.hasPermission('ai:dify:segment:update')")
|
||||
@PatchMapping("/datasets/{datasetId}/documents/{documentId}/segments/{segmentId}")
|
||||
public ResultDomain<String> updateSegment(
|
||||
@PathVariable("datasetId") @NotBlank String datasetId,
|
||||
@PathVariable("documentId") @NotBlank String documentId,
|
||||
@PathVariable("segmentId") @NotBlank String segmentId,
|
||||
@RequestBody Map<String, Object> requestBody) {
|
||||
logger.info("更新文档分段: datasetId={}, documentId={}, segmentId={}", datasetId, documentId, segmentId);
|
||||
return difyProxyService.updateSegment(datasetId, documentId, segmentId, requestBody);
|
||||
}
|
||||
|
||||
/**
|
||||
* @description 删除文档分段
|
||||
* @param datasetId Dify数据集ID
|
||||
* @param documentId Dify文档ID
|
||||
* @param segmentId Dify分段ID
|
||||
* @author yslg
|
||||
* @since 2025-12-18
|
||||
*/
|
||||
@PreAuthorize("@ss.hasPermission('ai:dify:segment:delete')")
|
||||
@DeleteMapping("/datasets/{datasetId}/documents/{documentId}/segments/{segmentId}")
|
||||
public ResultDomain<String> deleteSegment(
|
||||
@PathVariable("datasetId") @NotBlank String datasetId,
|
||||
@PathVariable("documentId") @NotBlank String documentId,
|
||||
@PathVariable("segmentId") @NotBlank String segmentId) {
|
||||
logger.info("删除文档分段: datasetId={}, documentId={}, segmentId={}", datasetId, documentId, segmentId);
|
||||
return difyProxyService.deleteSegment(datasetId, documentId, segmentId);
|
||||
}
|
||||
|
||||
// ====================== 文档状态管理 ======================
|
||||
|
||||
/**
|
||||
* @description 更新文档状态(启用/禁用/归档)
|
||||
* @param datasetId Dify数据集ID
|
||||
* @param action 操作类型: enable/disable/archive/un_archive
|
||||
* @param requestBody 请求体
|
||||
* @author yslg
|
||||
* @since 2025-12-18
|
||||
*/
|
||||
@PreAuthorize("@ss.hasPermission('ai:dify:document:status')")
|
||||
@PatchMapping("/datasets/{datasetId}/documents/{action}/status")
|
||||
public ResultDomain<String> updateDocumentStatus(
|
||||
@PathVariable("datasetId") @NotBlank String datasetId,
|
||||
@PathVariable("action") @NotBlank String action,
|
||||
@RequestBody Map<String, Object> requestBody) {
|
||||
logger.info("更新文档状态: datasetId={}, action={}", datasetId, action);
|
||||
return difyProxyService.updateDocumentStatus(datasetId, action, requestBody);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,64 @@
|
||||
package org.xyzh.ai.handler;
|
||||
|
||||
import com.alibaba.fastjson2.JSON;
|
||||
import com.alibaba.fastjson2.TypeReference;
|
||||
import org.apache.ibatis.type.BaseTypeHandler;
|
||||
import org.apache.ibatis.type.JdbcType;
|
||||
import org.apache.ibatis.type.MappedTypes;
|
||||
import org.xyzh.api.ai.dto.PromptCard;
|
||||
|
||||
import java.sql.CallableStatement;
|
||||
import java.sql.PreparedStatement;
|
||||
import java.sql.ResultSet;
|
||||
import java.sql.SQLException;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* @description 提示卡片列表类型处理器(JSONB <-> List<PromptCard>)
|
||||
* @filename PromptCardsTypeHandler.java
|
||||
* @author yslg
|
||||
* @copyright xyzh
|
||||
* @since 2025-12-17
|
||||
*/
|
||||
@MappedTypes({List.class})
|
||||
public class PromptCardsTypeHandler extends BaseTypeHandler<List<PromptCard>> {
|
||||
|
||||
@Override
|
||||
public void setNonNullParameter(PreparedStatement ps, int i, List<PromptCard> parameter, JdbcType jdbcType) throws SQLException {
|
||||
if (parameter == null || parameter.isEmpty()) {
|
||||
ps.setString(i, "[]");
|
||||
} else {
|
||||
ps.setString(i, JSON.toJSONString(parameter));
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<PromptCard> getNullableResult(ResultSet rs, String columnName) throws SQLException {
|
||||
String jsonString = rs.getString(columnName);
|
||||
return parseToList(jsonString);
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<PromptCard> getNullableResult(ResultSet rs, int columnIndex) throws SQLException {
|
||||
String jsonString = rs.getString(columnIndex);
|
||||
return parseToList(jsonString);
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<PromptCard> getNullableResult(CallableStatement cs, int columnIndex) throws SQLException {
|
||||
String jsonString = cs.getString(columnIndex);
|
||||
return parseToList(jsonString);
|
||||
}
|
||||
|
||||
private List<PromptCard> parseToList(String jsonString) {
|
||||
if (jsonString == null || jsonString.trim().isEmpty()) {
|
||||
return new ArrayList<>();
|
||||
}
|
||||
try {
|
||||
return JSON.parseObject(jsonString, new TypeReference<List<PromptCard>>() {});
|
||||
} catch (Exception e) {
|
||||
return new ArrayList<>();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,70 @@
|
||||
package org.xyzh.ai.mapper;
|
||||
|
||||
import org.apache.ibatis.annotations.Mapper;
|
||||
import org.apache.ibatis.annotations.Param;
|
||||
import org.xyzh.api.ai.dto.TbAgent;
|
||||
import org.xyzh.common.core.page.PageParam;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* @description 智能体数据访问层
|
||||
* @filename AgentMapper.java
|
||||
* @author yslg
|
||||
* @copyright xyzh
|
||||
* @since 2025-12-17
|
||||
*/
|
||||
@Mapper
|
||||
public interface TbAgentMapper {
|
||||
|
||||
/**
|
||||
* 插入智能体
|
||||
*/
|
||||
int insertAgent(TbAgent agent);
|
||||
|
||||
/**
|
||||
* 更新智能体(只更新非null字段)
|
||||
*/
|
||||
int updateAgent(TbAgent agent);
|
||||
|
||||
/**
|
||||
* 逻辑删除智能体
|
||||
*/
|
||||
int deleteAgent(TbAgent agent);
|
||||
|
||||
/**
|
||||
* 根据ID查询智能体
|
||||
*/
|
||||
TbAgent selectAgentById(@Param("agentId") String agentId);
|
||||
|
||||
/**
|
||||
* 根据ApiKey查询智能体
|
||||
*/
|
||||
TbAgent selectAgentByApiKey(@Param("apiKey") String apiKey);
|
||||
|
||||
/**
|
||||
* 查询智能体列表
|
||||
*/
|
||||
List<TbAgent> selectAgentList(@Param("filter") TbAgent filter);
|
||||
|
||||
/**
|
||||
* 分页查询智能体
|
||||
*/
|
||||
List<TbAgent> selectAgentPage(
|
||||
@Param("filter") TbAgent filter,
|
||||
@Param("pageParam") PageParam pageParam
|
||||
);
|
||||
|
||||
/**
|
||||
* 统计智能体数量
|
||||
*/
|
||||
long countAgents(@Param("filter") TbAgent filter);
|
||||
|
||||
/**
|
||||
* 根据名称检查是否存在
|
||||
*/
|
||||
int countByName(
|
||||
@Param("name") String name,
|
||||
@Param("excludeId") String excludeId
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,60 @@
|
||||
package org.xyzh.ai.mapper;
|
||||
|
||||
import org.apache.ibatis.annotations.Mapper;
|
||||
import org.apache.ibatis.annotations.Param;
|
||||
import org.xyzh.api.ai.dto.TbChat;
|
||||
import org.xyzh.common.core.page.PageParam;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* @description 对话数据访问层
|
||||
* @filename ChatMapper.java
|
||||
* @author yslg
|
||||
* @copyright xyzh
|
||||
* @since 2025-12-17
|
||||
*/
|
||||
@Mapper
|
||||
public interface TbChatMapper {
|
||||
|
||||
/**
|
||||
* 插入对话
|
||||
*/
|
||||
int insertChat(TbChat chat);
|
||||
|
||||
/**
|
||||
* 更新对话
|
||||
*/
|
||||
int updateChat(TbChat chat);
|
||||
|
||||
/**
|
||||
* 逻辑删除对话
|
||||
*/
|
||||
int deleteChat(TbChat chat);
|
||||
|
||||
/**
|
||||
* 根据ID查询对话
|
||||
*/
|
||||
TbChat selectChatById(@Param("chatId") String chatId);
|
||||
|
||||
/**
|
||||
* 根据智能体ID和用户ID查询对话列表
|
||||
*/
|
||||
List<TbChat> selectChatList(
|
||||
@Param("agentId") String agentId,
|
||||
@Param("userId") String userId
|
||||
);
|
||||
|
||||
/**
|
||||
* 分页查询对话
|
||||
*/
|
||||
List<TbChat> selectChatPage(
|
||||
@Param("filter") TbChat filter,
|
||||
@Param("pageParam") PageParam pageParam
|
||||
);
|
||||
|
||||
/**
|
||||
* 统计对话数量
|
||||
*/
|
||||
long countChats(@Param("filter") TbChat filter);
|
||||
}
|
||||
@@ -0,0 +1,62 @@
|
||||
package org.xyzh.ai.mapper;
|
||||
|
||||
import org.apache.ibatis.annotations.Mapper;
|
||||
import org.apache.ibatis.annotations.Param;
|
||||
import org.xyzh.api.ai.dto.TbChatMessage;
|
||||
import org.xyzh.common.core.page.PageParam;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* @description 对话消息数据访问层
|
||||
* @filename ChatMessageMapper.java
|
||||
* @author yslg
|
||||
* @copyright xyzh
|
||||
* @since 2025-12-17
|
||||
*/
|
||||
@Mapper
|
||||
public interface TbChatMessageMapper {
|
||||
|
||||
/**
|
||||
* 插入消息
|
||||
*/
|
||||
int insertChatMessage(TbChatMessage message);
|
||||
|
||||
/**
|
||||
* 更新消息
|
||||
*/
|
||||
int updateChatMessage(TbChatMessage message);
|
||||
|
||||
/**
|
||||
* 逻辑删除消息
|
||||
*/
|
||||
int deleteChatMessage(TbChatMessage message);
|
||||
|
||||
/**
|
||||
* 根据ID查询消息
|
||||
*/
|
||||
TbChatMessage selectMessageById(@Param("messageId") String messageId);
|
||||
|
||||
/**
|
||||
* 根据对话ID查询消息列表
|
||||
*/
|
||||
List<TbChatMessage> selectMessagesByChatId(@Param("chatId") String chatId);
|
||||
|
||||
/**
|
||||
* 分页查询消息
|
||||
*/
|
||||
List<TbChatMessage> selectMessagePage(
|
||||
@Param("chatId") String chatId,
|
||||
@Param("pageParam") PageParam pageParam
|
||||
);
|
||||
|
||||
/**
|
||||
* 统计消息数量
|
||||
*/
|
||||
long countMessages(@Param("chatId") String chatId);
|
||||
|
||||
/**
|
||||
* 批量删除消息(根据对话ID)
|
||||
*/
|
||||
int deleteMessagesByChatId(@Param("chatId") String chatId);
|
||||
}
|
||||
@@ -0,0 +1,78 @@
|
||||
package org.xyzh.ai.mapper;
|
||||
|
||||
import org.apache.ibatis.annotations.Mapper;
|
||||
import org.apache.ibatis.annotations.Param;
|
||||
import org.xyzh.api.ai.dto.TbKnowledgeFile;
|
||||
import org.xyzh.common.core.page.PageParam;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* @description 知识库文件数据访问层
|
||||
* @filename KnowledgeFileMapper.java
|
||||
* @author yslg
|
||||
* @copyright xyzh
|
||||
* @since 2025-12-17
|
||||
*/
|
||||
@Mapper
|
||||
public interface TbKnowledgeFileMapper {
|
||||
|
||||
/**
|
||||
* 插入知识库文件
|
||||
*/
|
||||
int insertKnowledgeFile(TbKnowledgeFile file);
|
||||
|
||||
/**
|
||||
* 更新知识库文件
|
||||
*/
|
||||
int updateKnowledgeFile(TbKnowledgeFile file);
|
||||
|
||||
/**
|
||||
* 逻辑删除知识库文件
|
||||
*/
|
||||
int deleteKnowledgeFile(TbKnowledgeFile file);
|
||||
|
||||
/**
|
||||
* 根据知识库ID和文件ID查询
|
||||
*/
|
||||
TbKnowledgeFile selectKnowledgeFile(
|
||||
@Param("knowledgeId") String knowledgeId,
|
||||
@Param("fileId") String fileId
|
||||
);
|
||||
|
||||
/**
|
||||
* 根据知识库ID查询文件列表
|
||||
*/
|
||||
List<TbKnowledgeFile> selectFilesByKnowledgeId(@Param("knowledgeId") String knowledgeId);
|
||||
|
||||
/**
|
||||
* 根据文件根ID查询所有版本
|
||||
*/
|
||||
List<TbKnowledgeFile> selectFileVersions(@Param("fileRootId") String fileRootId);
|
||||
|
||||
/**
|
||||
* 分页查询知识库文件
|
||||
*/
|
||||
List<TbKnowledgeFile> selectFilePage(
|
||||
@Param("knowledgeId") String knowledgeId,
|
||||
@Param("pageParam") PageParam pageParam
|
||||
);
|
||||
|
||||
/**
|
||||
* 统计知识库文件数量
|
||||
*/
|
||||
long countFiles(@Param("knowledgeId") String knowledgeId);
|
||||
|
||||
/**
|
||||
* 批量删除知识库文件(根据文件根ID)
|
||||
*/
|
||||
int deleteFilesByRootId(@Param("fileRootId") String fileRootId);
|
||||
|
||||
/**
|
||||
* 获取文件最新版本号
|
||||
*/
|
||||
Integer selectLatestVersion(
|
||||
@Param("knowledgeId") String knowledgeId,
|
||||
@Param("fileRootId") String fileRootId
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,62 @@
|
||||
package org.xyzh.ai.mapper;
|
||||
|
||||
import org.apache.ibatis.annotations.Mapper;
|
||||
import org.apache.ibatis.annotations.Param;
|
||||
import org.xyzh.api.ai.dto.TbKnowledge;
|
||||
import org.xyzh.common.core.page.PageParam;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* @description 知识库数据访问层
|
||||
* @filename KnowledgeMapper.java
|
||||
* @author yslg
|
||||
* @copyright xyzh
|
||||
* @since 2025-12-17
|
||||
*/
|
||||
@Mapper
|
||||
public interface TbKnowledgeMapper {
|
||||
|
||||
/**
|
||||
* 插入知识库
|
||||
*/
|
||||
int insertKnowledge(TbKnowledge knowledge);
|
||||
|
||||
/**
|
||||
* 更新知识库
|
||||
*/
|
||||
int updateKnowledge(TbKnowledge knowledge);
|
||||
|
||||
/**
|
||||
* 逻辑删除知识库
|
||||
*/
|
||||
int deleteKnowledge(TbKnowledge knowledge);
|
||||
|
||||
/**
|
||||
* 根据ID查询知识库
|
||||
*/
|
||||
TbKnowledge selectKnowledgeById(@Param("knowledgeId") String knowledgeId);
|
||||
|
||||
/**
|
||||
* 根据DifyDatasetId查询知识库
|
||||
*/
|
||||
TbKnowledge selectKnowledgeByDifyId(@Param("difyDatasetId") String difyDatasetId);
|
||||
|
||||
/**
|
||||
* 查询知识库列表
|
||||
*/
|
||||
List<TbKnowledge> selectKnowledgeList(@Param("filter") TbKnowledge filter);
|
||||
|
||||
/**
|
||||
* 分页查询知识库
|
||||
*/
|
||||
List<TbKnowledge> selectKnowledgePage(
|
||||
@Param("filter") TbKnowledge filter,
|
||||
@Param("pageParam") PageParam pageParam
|
||||
);
|
||||
|
||||
/**
|
||||
* 统计知识库数量
|
||||
*/
|
||||
long countKnowledges(@Param("filter") TbKnowledge filter);
|
||||
}
|
||||
@@ -0,0 +1,207 @@
|
||||
package org.xyzh.ai.service.impl;
|
||||
|
||||
import org.apache.dubbo.config.annotation.DubboService;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.util.StringUtils;
|
||||
import org.springframework.web.multipart.MultipartFile;
|
||||
import org.xyzh.ai.client.DifyApiClient;
|
||||
import org.xyzh.ai.client.dto.DifyFileInfo;
|
||||
import org.xyzh.ai.client.dto.DocumentUploadRequest;
|
||||
import org.xyzh.ai.client.dto.DocumentUploadResponse;
|
||||
import org.xyzh.api.ai.dto.TbAgent;
|
||||
import org.xyzh.api.ai.service.AIFileUploadService;
|
||||
import org.xyzh.common.auth.utils.LoginUtil;
|
||||
import org.xyzh.common.core.domain.ResultDomain;
|
||||
|
||||
import java.io.File;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* @description AI文件上传服务实现(只负责与Dify交互,不处理minio和数据库)
|
||||
* @filename AIFileUploadServiceImpl.java
|
||||
* @author yslg
|
||||
* @copyright xyzh
|
||||
* @since 2025-12-17
|
||||
*/
|
||||
@DubboService(version = "1.0.0", group = "ai", timeout = 30000, retries = 0)
|
||||
public class AIFileUploadServiceImpl implements AIFileUploadService {
|
||||
private static final Logger logger = LoggerFactory.getLogger(AIFileUploadServiceImpl.class);
|
||||
|
||||
@Autowired
|
||||
private DifyApiClient difyApiClient;
|
||||
|
||||
@Autowired
|
||||
private AgentServiceImpl agentService;
|
||||
|
||||
// ============================ 对话文件管理 ============================
|
||||
|
||||
@Override
|
||||
public ResultDomain<Map<String, Object>> uploadFileForChat(MultipartFile file, String agentId) {
|
||||
// 1. 参数校验
|
||||
if (file == null || file.isEmpty()) {
|
||||
return ResultDomain.failure("文件不能为空");
|
||||
}
|
||||
if (!StringUtils.hasText(agentId)) {
|
||||
return ResultDomain.failure("智能体ID不能为空");
|
||||
}
|
||||
|
||||
// 2. 获取智能体API Key
|
||||
ResultDomain<TbAgent> agentResult = agentService.selectAgentById(agentId);
|
||||
if (!agentResult.getSuccess() || agentResult.getData() == null) {
|
||||
return ResultDomain.failure("智能体不存在");
|
||||
}
|
||||
TbAgent agent = agentResult.getData();
|
||||
|
||||
File tempFile = null;
|
||||
try {
|
||||
// 3. 将MultipartFile转换为临时File
|
||||
tempFile = File.createTempFile("upload_", "_" + file.getOriginalFilename());
|
||||
file.transferTo(tempFile);
|
||||
|
||||
// 4. 获取当前用户
|
||||
String userId = LoginUtil.getCurrentUserId();
|
||||
if (!StringUtils.hasText(userId)) {
|
||||
userId = "anonymous";
|
||||
}
|
||||
|
||||
// 5. 上传到Dify
|
||||
DifyFileInfo difyFile = difyApiClient.uploadFileForChat(tempFile, file.getOriginalFilename(), userId, agent.getApiKey());
|
||||
if (difyFile != null && StringUtils.hasText(difyFile.getId())) {
|
||||
logger.info("上传对话文件成功: agentId={}, fileId={}", agentId, difyFile.getId());
|
||||
Map<String, Object> result = new java.util.HashMap<>();
|
||||
result.put("id", difyFile.getId());
|
||||
result.put("name", difyFile.getName());
|
||||
result.put("size", difyFile.getSize());
|
||||
result.put("type", difyFile.getType());
|
||||
result.put("upload_file_id", difyFile.getUploadFileId());
|
||||
return ResultDomain.success("上传成功", result);
|
||||
}
|
||||
return ResultDomain.failure("上传文件失败");
|
||||
} catch (Exception e) {
|
||||
logger.error("上传对话文件异常: {}", e.getMessage(), e);
|
||||
return ResultDomain.failure("上传文件异常: " + e.getMessage());
|
||||
} finally {
|
||||
if (tempFile != null && tempFile.exists()) {
|
||||
tempFile.delete();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// ============================ 知识库Dify文档管理 ============================
|
||||
|
||||
/**
|
||||
* @description 上传文件到Dify知识库(只负责Dify上传,不处理minio和数据库)
|
||||
* @param difyDatasetId Dify知识库ID
|
||||
* @param file 文件
|
||||
* @param fileName 文件名
|
||||
* @param indexingTechnique 索引方式
|
||||
* @return ResultDomain<String> Dify文档ID
|
||||
* @author yslg
|
||||
* @since 2025-12-18
|
||||
*/
|
||||
@Override
|
||||
public ResultDomain<String> uploadFileToDify(String difyDatasetId, File file, String fileName, String indexingTechnique) {
|
||||
if (!StringUtils.hasText(difyDatasetId)) {
|
||||
return ResultDomain.failure("Dify知识库ID不能为空");
|
||||
}
|
||||
if (file == null || !file.exists()) {
|
||||
return ResultDomain.failure("文件不能为空");
|
||||
}
|
||||
|
||||
try {
|
||||
DocumentUploadRequest uploadRequest = new DocumentUploadRequest();
|
||||
uploadRequest.setName(fileName);
|
||||
if (StringUtils.hasText(indexingTechnique)) {
|
||||
uploadRequest.setIndexingTechnique(indexingTechnique);
|
||||
}
|
||||
|
||||
DocumentUploadResponse uploadResponse = difyApiClient.uploadDocumentByFile(
|
||||
difyDatasetId,
|
||||
file,
|
||||
fileName,
|
||||
uploadRequest
|
||||
);
|
||||
|
||||
if (uploadResponse != null && uploadResponse.getDocument() != null) {
|
||||
String difyDocumentId = uploadResponse.getDocument().getId();
|
||||
logger.info("上传文件到Dify成功: difyDatasetId={}, difyDocumentId={}", difyDatasetId, difyDocumentId);
|
||||
return ResultDomain.success("上传成功", difyDocumentId);
|
||||
}
|
||||
return ResultDomain.failure("上传文件到Dify失败");
|
||||
} catch (Exception e) {
|
||||
logger.error("上传文件到Dify异常: {}", e.getMessage(), e);
|
||||
return ResultDomain.failure("上传文件到Dify异常: " + e.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @description 从Dify知识库删除文档
|
||||
* @param difyDatasetId Dify知识库ID
|
||||
* @param difyDocumentId Dify文档ID
|
||||
* @return ResultDomain<Boolean> 删除结果
|
||||
* @author yslg
|
||||
* @since 2025-12-18
|
||||
*/
|
||||
@Override
|
||||
public ResultDomain<Boolean> deleteFileFromDify(String difyDatasetId, String difyDocumentId) {
|
||||
if (!StringUtils.hasText(difyDatasetId)) {
|
||||
return ResultDomain.failure("Dify知识库ID不能为空");
|
||||
}
|
||||
if (!StringUtils.hasText(difyDocumentId)) {
|
||||
return ResultDomain.failure("Dify文档ID不能为空");
|
||||
}
|
||||
|
||||
try {
|
||||
difyApiClient.deleteDocument(difyDatasetId, difyDocumentId);
|
||||
logger.info("从Dify删除文档成功: difyDatasetId={}, difyDocumentId={}", difyDatasetId, difyDocumentId);
|
||||
return ResultDomain.success("删除成功", true);
|
||||
} catch (Exception e) {
|
||||
logger.error("从Dify删除文档异常: {}", e.getMessage(), e);
|
||||
return ResultDomain.failure("从Dify删除文档异常: " + e.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @description 批量从Dify知识库删除文档
|
||||
* @param difyDatasetId Dify知识库ID
|
||||
* @param difyDocumentIds Dify文档ID列表
|
||||
* @return ResultDomain<Boolean> 删除结果
|
||||
* @author yslg
|
||||
* @since 2025-12-18
|
||||
*/
|
||||
@Override
|
||||
public ResultDomain<Boolean> batchDeleteFilesFromDify(String difyDatasetId, List<String> difyDocumentIds) {
|
||||
if (!StringUtils.hasText(difyDatasetId)) {
|
||||
return ResultDomain.failure("Dify知识库ID不能为空");
|
||||
}
|
||||
if (difyDocumentIds == null || difyDocumentIds.isEmpty()) {
|
||||
return ResultDomain.failure("Dify文档ID列表不能为空");
|
||||
}
|
||||
|
||||
int successCount = 0;
|
||||
int failCount = 0;
|
||||
for (String difyDocumentId : difyDocumentIds) {
|
||||
if (StringUtils.hasText(difyDocumentId)) {
|
||||
try {
|
||||
difyApiClient.deleteDocument(difyDatasetId, difyDocumentId);
|
||||
successCount++;
|
||||
} catch (Exception e) {
|
||||
logger.warn("批量删除Dify文档失败: difyDocumentId={}, error={}", difyDocumentId, e.getMessage());
|
||||
failCount++;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
logger.info("批量从Dify删除文档完成: 成功={}, 失败={}", successCount, failCount);
|
||||
if (failCount == 0) {
|
||||
return ResultDomain.success("批量删除成功", true);
|
||||
} else if (successCount == 0) {
|
||||
return ResultDomain.failure("批量删除全部失败");
|
||||
} else {
|
||||
return ResultDomain.success("部分删除成功", true);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,476 @@
|
||||
package org.xyzh.ai.service.impl;
|
||||
|
||||
import com.alibaba.fastjson2.JSONObject;
|
||||
|
||||
import org.apache.dubbo.config.annotation.DubboReference;
|
||||
import org.apache.dubbo.config.annotation.DubboService;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.transaction.annotation.Transactional;
|
||||
import org.springframework.util.StringUtils;
|
||||
import org.springframework.web.servlet.mvc.method.annotation.SseEmitter;
|
||||
import org.xyzh.ai.client.DifyApiClient;
|
||||
import org.xyzh.ai.client.callback.StreamCallback;
|
||||
import org.xyzh.ai.client.dto.ChatRequest;
|
||||
import org.xyzh.ai.mapper.TbChatMapper;
|
||||
import org.xyzh.ai.mapper.TbChatMessageMapper;
|
||||
import org.xyzh.api.ai.dto.ChatPrepareData;
|
||||
import org.xyzh.api.ai.dto.DifyFileInfo;
|
||||
import org.xyzh.api.ai.dto.TbAgent;
|
||||
import org.xyzh.api.ai.dto.TbChat;
|
||||
import org.xyzh.api.ai.dto.TbChatMessage;
|
||||
import org.xyzh.api.ai.service.AgentChatService;
|
||||
import org.xyzh.api.ai.service.AgentService;
|
||||
import org.xyzh.common.core.domain.ResultDomain;
|
||||
import org.xyzh.common.redis.service.RedisService;
|
||||
import org.xyzh.common.utils.id.IdUtil;
|
||||
import org.xyzh.common.auth.utils.LoginUtil;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
/**
|
||||
* @description 智能体对话服务实现
|
||||
* @filename AgentChatServiceImpl.java
|
||||
* @author yslg
|
||||
* @copyright xyzh
|
||||
* @since 2025-12-17
|
||||
*/
|
||||
@DubboService(
|
||||
version = "1.0.0",
|
||||
group = "ai",
|
||||
timeout = 3000,
|
||||
retries = 0
|
||||
)
|
||||
public class AgentChatServiceImpl implements AgentChatService {
|
||||
private static final Logger logger = LoggerFactory.getLogger(AgentChatServiceImpl.class);
|
||||
|
||||
private static final String CHAT_SESSION_PREFIX = "ai:chat:session:";
|
||||
private static final long SESSION_TTL = 5 * 60;
|
||||
|
||||
@Autowired
|
||||
private TbChatMapper chatMapper;
|
||||
|
||||
@Autowired
|
||||
private TbChatMessageMapper chatMessageMapper;
|
||||
|
||||
@DubboReference
|
||||
private AgentService agentService;
|
||||
|
||||
@Autowired
|
||||
private DifyApiClient difyApiClient;
|
||||
|
||||
@Autowired
|
||||
private RedisService redisService;
|
||||
|
||||
/**
|
||||
* @description 根据 userType 获取用户ID
|
||||
* @param chat 会话信息(包含 userId 和 userType)
|
||||
* @return 真实的系统用户ID
|
||||
*/
|
||||
private String getUserIdByType(TbChat chat) {
|
||||
if (!chat.getUserType()) {
|
||||
// 来客(userType=false):直接返回传入的 userId(已经是真正的系统 userId)
|
||||
return chat.getUserId();
|
||||
} else {
|
||||
// 员工(userType=true):从登录信息获取 userId
|
||||
return LoginUtil.getCurrentUserId();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @description 判断智能体是否是outer
|
||||
* @param agentId 智能体ID
|
||||
* @return true-是outer,false-不是outer
|
||||
*/
|
||||
private Boolean isOuterAgent(String agentId){
|
||||
// 智能体必须是outer
|
||||
ResultDomain<TbAgent> agentResult = agentService.selectAgentById(agentId);
|
||||
if(!agentResult.getSuccess()|| agentResult.getData() == null || !agentResult.getData().getOuter()){
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
// ====================== 智能体会话管理 ======================
|
||||
|
||||
@Override
|
||||
@Transactional(rollbackFor = Exception.class)
|
||||
public ResultDomain<TbChat> createChat(TbChat chat) {
|
||||
// 如果是来客(userType=false),校验智能体是否是 outer
|
||||
if(!chat.getUserType()){
|
||||
if(!isOuterAgent(chat.getAgentId())){
|
||||
return ResultDomain.failure("智能体不可用");
|
||||
}
|
||||
}
|
||||
// 设置chat(来客传入的 userId 已经是真正的系统 userId)
|
||||
chat.setOptsn(IdUtil.getOptsn());
|
||||
chat.setChatId(IdUtil.generateID());
|
||||
chatMapper.insertChat(chat);
|
||||
return ResultDomain.success("创建成功", chat);
|
||||
}
|
||||
|
||||
@Override
|
||||
@Transactional(rollbackFor = Exception.class)
|
||||
public ResultDomain<TbChat> updateChat(TbChat chat) {
|
||||
// 1. 校验会话
|
||||
TbChat chat2 = chatMapper.selectChatById(chat.getChatId());
|
||||
if (chat2 == null) {
|
||||
return ResultDomain.failure("会话不存在");
|
||||
}
|
||||
// 判断agent是否是outer
|
||||
if(!isOuterAgent(chat.getAgentId())){
|
||||
return ResultDomain.failure("智能体不可用");
|
||||
}
|
||||
|
||||
// 2. 获取用户ID并校验权限
|
||||
String userId = getUserIdByType(chat);
|
||||
if (userId == null) {
|
||||
return ResultDomain.failure("用户信息获取失败");
|
||||
}
|
||||
if (!chat2.getUserId().equals(userId)) {
|
||||
return ResultDomain.failure("无权修改此会话");
|
||||
}
|
||||
|
||||
// 3. 更新会话
|
||||
TbChat update = new TbChat();
|
||||
update.setChatId(chat.getChatId());
|
||||
update.setTitle(chat.getTitle());
|
||||
|
||||
int rows = chatMapper.updateChat(update);
|
||||
if (rows > 0) {
|
||||
return ResultDomain.success("更新会话成功", update);
|
||||
}
|
||||
|
||||
return ResultDomain.failure("更新会话失败");
|
||||
}
|
||||
|
||||
@Override
|
||||
@Transactional(rollbackFor = Exception.class)
|
||||
public ResultDomain<TbChat> deleteChat(TbChat filter) {
|
||||
// 1. 校验会话
|
||||
TbChat chat = chatMapper.selectChatById(filter.getChatId());
|
||||
if (chat == null) {
|
||||
return ResultDomain.failure("会话不存在");
|
||||
}
|
||||
// 判断agent是否是outer
|
||||
if(!isOuterAgent(chat.getAgentId())){
|
||||
return ResultDomain.failure("智能体不可用");
|
||||
}
|
||||
// 2. 获取用户ID并校验权限
|
||||
String userId = getUserIdByType(filter);
|
||||
if (userId == null) {
|
||||
return ResultDomain.failure("用户信息获取失败");
|
||||
}
|
||||
if (!chat.getUserId().equals(userId)) {
|
||||
return ResultDomain.failure("无权删除此会话");
|
||||
}
|
||||
|
||||
// 3. 删除会话消息
|
||||
chatMessageMapper.deleteMessagesByChatId(filter.getChatId());
|
||||
|
||||
// 4. 删除会话
|
||||
TbChat delete = new TbChat();
|
||||
delete.setChatId(filter.getChatId());
|
||||
int rows = chatMapper.deleteChat(delete);
|
||||
if (rows > 0) {
|
||||
logger.info("删除会话成功: chatId={}", filter.getChatId());
|
||||
return ResultDomain.success("删除会话成功", chat);
|
||||
}
|
||||
|
||||
return ResultDomain.failure("删除会话失败");
|
||||
}
|
||||
|
||||
@Override
|
||||
public ResultDomain<TbChat> getChatList(TbChat filter) {
|
||||
// 判断agent是否是outer
|
||||
if(!isOuterAgent(filter.getAgentId())){
|
||||
return ResultDomain.failure("智能体不可用");
|
||||
}
|
||||
// 获取用户ID
|
||||
String userId = getUserIdByType(filter);
|
||||
if (userId == null) {
|
||||
return ResultDomain.failure("用户信息获取失败");
|
||||
}
|
||||
|
||||
List<TbChat> chatList = chatMapper.selectChatList(filter.getAgentId(), userId);
|
||||
return ResultDomain.success("查询成功", chatList);
|
||||
}
|
||||
|
||||
// ====================== 智能体聊天管理 ======================
|
||||
|
||||
@Override
|
||||
public ResultDomain<TbChatMessage> getChatMessageList(TbChat filter) {
|
||||
// 1. 校验会话
|
||||
TbChat chat = chatMapper.selectChatById(filter.getChatId());
|
||||
if (chat == null) {
|
||||
return ResultDomain.failure("会话不存在");
|
||||
}
|
||||
// 判断agent是否是outer
|
||||
if(!isOuterAgent(chat.getAgentId())){
|
||||
return ResultDomain.failure("智能体不可用");
|
||||
}
|
||||
// 2. 获取用户ID并校验权限
|
||||
String userId = getUserIdByType(filter);
|
||||
if (userId == null) {
|
||||
return ResultDomain.failure("用户信息获取失败");
|
||||
}
|
||||
if (!chat.getUserId().equals(userId)) {
|
||||
return ResultDomain.failure("无权查看此会话");
|
||||
}
|
||||
|
||||
List<TbChatMessage> messages = chatMessageMapper.selectMessagesByChatId(filter.getChatId());
|
||||
return ResultDomain.success("查询成功", messages);
|
||||
}
|
||||
|
||||
@Override
|
||||
public ResultDomain<String> prepareChatMessageSession(ChatPrepareData prepareData) {
|
||||
String agentId = prepareData.getAgentId();
|
||||
String chatId = prepareData.getChatId();
|
||||
String query = prepareData.getQuery();
|
||||
|
||||
// 1. 校验智能体
|
||||
ResultDomain<TbAgent> agentResult = agentService.selectAgentById(agentId);
|
||||
if (!agentResult.getSuccess() || agentResult.getData() == null || !agentResult.getData().getOuter()) {
|
||||
return ResultDomain.failure("智能体不存在或不可用");
|
||||
}
|
||||
TbAgent agent = agentResult.getData();
|
||||
|
||||
// 2. 获取用户ID(根据userType处理来客/员工)
|
||||
TbChat chatFilter = new TbChat();
|
||||
chatFilter.setAgentId(agentId);
|
||||
chatFilter.setUserId(prepareData.getUserId());
|
||||
chatFilter.setUserType(prepareData.getUserType());
|
||||
|
||||
String userId = getUserIdByType(chatFilter);
|
||||
if (userId == null) {
|
||||
return ResultDomain.failure("用户信息获取失败");
|
||||
}
|
||||
|
||||
// 3. 校验会话
|
||||
if (StringUtils.hasText(chatId)) {
|
||||
TbChat chat = chatMapper.selectChatById(chatId);
|
||||
if (chat == null) {
|
||||
return ResultDomain.failure("会话不存在");
|
||||
}
|
||||
if (!chat.getUserId().equals(userId)) {
|
||||
return ResultDomain.failure("无权操作此会话");
|
||||
}
|
||||
}
|
||||
|
||||
// 4. 生成临时消息ID(sessionId)
|
||||
String sessionId = IdUtil.getSnowflakeId();
|
||||
|
||||
// 5. 存储会话数据到Redis
|
||||
Map<String, Object> sessionData = new HashMap<>();
|
||||
sessionData.put("agentId", agentId);
|
||||
sessionData.put("chatId", chatId);
|
||||
sessionData.put("query", query);
|
||||
sessionData.put("userId", userId);
|
||||
sessionData.put("filesData", prepareData.getFiles());
|
||||
sessionData.put("apiKey", agent.getApiKey());
|
||||
|
||||
String cacheKey = CHAT_SESSION_PREFIX + sessionId;
|
||||
redisService.set(cacheKey, sessionData, SESSION_TTL, TimeUnit.SECONDS);
|
||||
|
||||
logger.info("准备对话会话: sessionId={}, agentId={}", sessionId, agentId);
|
||||
return ResultDomain.success("准备成功", sessionId);
|
||||
}
|
||||
|
||||
@Override
|
||||
public SseEmitter streamChatMessageWithSse(String sessionId) {
|
||||
SseEmitter emitter = new SseEmitter(300000L);
|
||||
|
||||
// 1. 从Redis获取会话数据
|
||||
String cacheKey = CHAT_SESSION_PREFIX + sessionId;
|
||||
@SuppressWarnings("unchecked")
|
||||
Map<String, Object> sessionData = (Map<String, Object>) redisService.get(cacheKey);
|
||||
|
||||
if (sessionData == null) {
|
||||
try {
|
||||
emitter.send(SseEmitter.event().name("error").data("{\"message\":\"会话已过期\"}"));
|
||||
emitter.complete();
|
||||
} catch (IOException e) {
|
||||
logger.error("发送错误事件失败", e);
|
||||
}
|
||||
return emitter;
|
||||
}
|
||||
|
||||
// 2. 解析会话数据
|
||||
String agentId = (String) sessionData.get("agentId");
|
||||
String chatId = (String) sessionData.get("chatId");
|
||||
String query = (String) sessionData.get("query");
|
||||
String userId = (String) sessionData.get("userId");
|
||||
String apiKey = (String) sessionData.get("apiKey");
|
||||
@SuppressWarnings("unchecked")
|
||||
List<DifyFileInfo> filesData = (List<DifyFileInfo>) sessionData.get("filesData");
|
||||
|
||||
// 3. 删除已使用的会话数据
|
||||
redisService.delete(cacheKey);
|
||||
|
||||
// 4. 保存用户消息
|
||||
String userMessageId = IdUtil.getSnowflakeId();
|
||||
TbChatMessage userMessage = new TbChatMessage();
|
||||
userMessage.setOptsn(IdUtil.getOptsn());
|
||||
userMessage.setMessageId(userMessageId);
|
||||
userMessage.setChatId(chatId);
|
||||
userMessage.setRole("user");
|
||||
userMessage.setContent(query);
|
||||
chatMessageMapper.insertChatMessage(userMessage);
|
||||
|
||||
// 5. 构建Dify请求
|
||||
ChatRequest chatRequest = new ChatRequest();
|
||||
chatRequest.setQuery(query);
|
||||
chatRequest.setUser(userId);
|
||||
chatRequest.setResponseMode("streaming");
|
||||
|
||||
if (filesData != null && !filesData.isEmpty()) {
|
||||
chatRequest.setFiles(filesData);
|
||||
}
|
||||
|
||||
// 6. 准备AI消息记录
|
||||
String aiMessageId = IdUtil.getSnowflakeId();
|
||||
StringBuilder aiContent = new StringBuilder();
|
||||
|
||||
// 7. 发起流式请求
|
||||
difyApiClient.streamChat(chatRequest, apiKey, new StreamCallback() {
|
||||
@Override
|
||||
public void onEvent(String event, String data) {
|
||||
try {
|
||||
// 使用SseEmitter标准格式发送:event: xxx\ndata: xxx
|
||||
emitter.send(SseEmitter.event().name(event).data(data));
|
||||
} catch (IOException e) {
|
||||
logger.error("发送SSE事件失败: event={}", event, e);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onMessage(String content) {
|
||||
aiContent.append(content);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onMessageEnd(String data) {
|
||||
// 从message_end事件中提取difyMessageId
|
||||
String difyMessageId = null;
|
||||
try {
|
||||
JSONObject json = JSONObject.parseObject(data);
|
||||
if (json != null && json.containsKey("message_id")) {
|
||||
difyMessageId = json.getString("message_id");
|
||||
}
|
||||
} catch (Exception e) {
|
||||
logger.warn("解析difyMessageId失败: {}", e.getMessage());
|
||||
}
|
||||
|
||||
// 保存AI回复消息
|
||||
TbChatMessage aiMessage = new TbChatMessage();
|
||||
aiMessage.setOptsn(IdUtil.getOptsn());
|
||||
aiMessage.setMessageId(aiMessageId);
|
||||
aiMessage.setDifyMessageId(difyMessageId);
|
||||
aiMessage.setChatId(chatId);
|
||||
aiMessage.setRole("ai");
|
||||
aiMessage.setContent(aiContent.toString());
|
||||
chatMessageMapper.insertChatMessage(aiMessage);
|
||||
|
||||
logger.info("对话完成: chatId={}, aiMessageId={}, difyMessageId={}", chatId, aiMessageId, difyMessageId);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onComplete() {
|
||||
try {
|
||||
emitter.complete();
|
||||
} catch (Exception e) {
|
||||
logger.error("完成SSE失败", e);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onError(Throwable throwable) {
|
||||
logger.error("流式对话异常", throwable);
|
||||
try {
|
||||
JSONObject errorData = new JSONObject();
|
||||
errorData.put("message", throwable.getMessage());
|
||||
emitter.send(SseEmitter.event().name("error").data(errorData.toJSONString()));
|
||||
emitter.complete();
|
||||
} catch (IOException e) {
|
||||
logger.error("发送错误事件失败", e);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
return emitter;
|
||||
}
|
||||
|
||||
@Override
|
||||
public ResultDomain<Boolean> stopChatMessageByTaskId(TbChat filter, String taskId) {
|
||||
// 1. 获取智能体
|
||||
ResultDomain<TbAgent> agentResult = agentService.selectAgentById(filter.getAgentId());
|
||||
if (!agentResult.getSuccess() || agentResult.getData() == null || !agentResult.getData().getOuter()) {
|
||||
return ResultDomain.failure("智能体不存在");
|
||||
}
|
||||
TbAgent agent = agentResult.getData();
|
||||
|
||||
// 2. 获取用户ID
|
||||
String userId = getUserIdByType(filter);
|
||||
if (userId == null) {
|
||||
return ResultDomain.failure("用户信息获取失败");
|
||||
}
|
||||
|
||||
try {
|
||||
// 3. 调用Dify停止接口
|
||||
difyApiClient.stopChatMessage(taskId, userId, agent.getApiKey());
|
||||
logger.info("停止对话成功: taskId={}", taskId);
|
||||
return ResultDomain.success("停止成功", true);
|
||||
} catch (Exception e) {
|
||||
logger.error("停止对话失败: taskId={}", taskId, e);
|
||||
return ResultDomain.failure("停止对话失败: " + e.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
@Transactional(rollbackFor = Exception.class)
|
||||
public ResultDomain<Boolean> commentChatMessage(TbChat filter, String messageId, String comment) {
|
||||
// 1. 校验消息
|
||||
TbChatMessage message = chatMessageMapper.selectMessageById(messageId);
|
||||
if (message == null) {
|
||||
return ResultDomain.failure("消息不存在");
|
||||
}
|
||||
|
||||
// 2. 获取用户ID
|
||||
String userId = getUserIdByType(filter);
|
||||
if (userId == null) {
|
||||
return ResultDomain.failure("用户信息获取失败");
|
||||
}
|
||||
|
||||
// 3. 更新评价
|
||||
TbChatMessage update = new TbChatMessage();
|
||||
update.setMessageId(messageId);
|
||||
update.setComment(comment);
|
||||
|
||||
int rows = chatMessageMapper.updateChatMessage(update);
|
||||
if (rows > 0) {
|
||||
// 4. 同步到Dify(转换评价格式,使用difyMessageId)
|
||||
if (StringUtils.hasText(message.getDifyMessageId())) {
|
||||
ResultDomain<TbAgent> agentResult = agentService.selectAgentById(filter.getAgentId());
|
||||
if (agentResult.getSuccess() && agentResult.getData() != null) {
|
||||
TbAgent agent = agentResult.getData();
|
||||
String rating = "like".equals(comment) ? "like" : ("dislike".equals(comment) ? "dislike" : null);
|
||||
try {
|
||||
difyApiClient.submitMessageFeedback(message.getDifyMessageId(), rating, userId, comment, agent.getApiKey());
|
||||
} catch (Exception e) {
|
||||
logger.warn("同步评价到Dify失败: difyMessageId={}", message.getDifyMessageId(), e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
logger.info("评价消息成功: messageId={}, comment={}", messageId, comment);
|
||||
return ResultDomain.success("评价成功", true);
|
||||
}
|
||||
|
||||
return ResultDomain.failure("评价失败");
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,253 @@
|
||||
package org.xyzh.ai.service.impl;
|
||||
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.apache.dubbo.config.annotation.DubboService;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.stereotype.Service;
|
||||
import org.springframework.transaction.annotation.Transactional;
|
||||
import org.springframework.util.StringUtils;
|
||||
import org.xyzh.ai.mapper.TbAgentMapper;
|
||||
import org.xyzh.api.ai.dto.TbAgent;
|
||||
import org.xyzh.api.ai.service.AgentService;
|
||||
import org.xyzh.common.core.domain.ResultDomain;
|
||||
import org.xyzh.common.core.page.PageDomain;
|
||||
import org.xyzh.common.core.page.PageParam;
|
||||
import org.xyzh.common.core.page.PageRequest;
|
||||
import org.xyzh.common.redis.service.RedisService;
|
||||
import org.xyzh.common.utils.id.IdUtil;
|
||||
import org.xyzh.common.auth.utils.LoginUtil;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.concurrent.locks.ReentrantLock;
|
||||
|
||||
/**
|
||||
* @description 智能体服务实现
|
||||
* @filename AgentServiceImpl.java
|
||||
* @author yslg
|
||||
* @copyright xyzh
|
||||
* @since 2025-12-17
|
||||
*/
|
||||
@DubboService(version = "1.0.0", group = "ai", timeout = 3000, retries = 0)
|
||||
public class AgentServiceImpl implements AgentService {
|
||||
private static final Logger logger = LoggerFactory.getLogger(AgentServiceImpl.class);
|
||||
|
||||
private static final String AGENT_CACHE_PREFIX = "ai:agent:";
|
||||
private static final long AGENT_CACHE_TTL = 24 * 60 * 60;
|
||||
|
||||
private final ReentrantLock agentLock = new ReentrantLock();
|
||||
|
||||
@Autowired
|
||||
private TbAgentMapper agentMapper;
|
||||
|
||||
@Autowired
|
||||
private RedisService redisService;
|
||||
|
||||
@Override
|
||||
@Transactional(rollbackFor = Exception.class)
|
||||
public ResultDomain<TbAgent> addAgent(TbAgent tbAgent) {
|
||||
// 1. 参数校验
|
||||
if (!StringUtils.hasText(tbAgent.getName())) {
|
||||
return ResultDomain.failure("智能体名称不能为空");
|
||||
}
|
||||
if (!StringUtils.hasText(tbAgent.getApiKey())) {
|
||||
return ResultDomain.failure("智能体API Key不能为空");
|
||||
}
|
||||
if (!StringUtils.hasText(tbAgent.getIntroduce())) {
|
||||
return ResultDomain.failure("引导词不能为空");
|
||||
}
|
||||
if (!StringUtils.hasText(tbAgent.getCategory())) {
|
||||
return ResultDomain.failure("分类不能为空");
|
||||
}
|
||||
|
||||
// 2. 检查名称是否重复
|
||||
int count = agentMapper.countByName(tbAgent.getName(), null);
|
||||
if (count > 0) {
|
||||
return ResultDomain.failure("智能体名称已存在");
|
||||
}
|
||||
|
||||
// 3. 检查ApiKey是否重复
|
||||
TbAgent existAgent = agentMapper.selectAgentByApiKey(tbAgent.getApiKey());
|
||||
if (existAgent != null) {
|
||||
return ResultDomain.failure("该API Key已被使用");
|
||||
}
|
||||
|
||||
// 4. 生成ID和流水号
|
||||
tbAgent.setOptsn(IdUtil.getOptsn());
|
||||
tbAgent.setAgentId(IdUtil.getSnowflakeId());
|
||||
|
||||
// 5. 设置创建者
|
||||
String userId = LoginUtil.getCurrentUserId();
|
||||
if (StringUtils.hasText(userId)) {
|
||||
tbAgent.setCreator(userId);
|
||||
}
|
||||
|
||||
// 6. 插入数据库
|
||||
int rows = agentMapper.insertAgent(tbAgent);
|
||||
if (rows > 0) {
|
||||
logger.info("创建智能体成功: agentId={}, name={}", tbAgent.getAgentId(), tbAgent.getName());
|
||||
return ResultDomain.success("创建智能体成功", tbAgent);
|
||||
}
|
||||
|
||||
return ResultDomain.failure("创建智能体失败");
|
||||
}
|
||||
|
||||
@Override
|
||||
@Transactional(rollbackFor = Exception.class)
|
||||
public ResultDomain<TbAgent> updateAgent(TbAgent tbAgent) {
|
||||
// 1. 参数校验
|
||||
if (!StringUtils.hasText(tbAgent.getAgentId())) {
|
||||
return ResultDomain.failure("智能体ID不能为空");
|
||||
}
|
||||
|
||||
// 2. 检查是否存在
|
||||
TbAgent existAgent = agentMapper.selectAgentById(tbAgent.getAgentId());
|
||||
if (existAgent == null) {
|
||||
return ResultDomain.failure("智能体不存在");
|
||||
}
|
||||
|
||||
// 3. 检查名称是否重复(排除自身)
|
||||
if (StringUtils.hasText(tbAgent.getName())) {
|
||||
int count = agentMapper.countByName(tbAgent.getName(), tbAgent.getAgentId());
|
||||
if (count > 0) {
|
||||
return ResultDomain.failure("智能体名称已存在");
|
||||
}
|
||||
}
|
||||
|
||||
// 4. 加锁更新(避免并发问题)
|
||||
agentLock.lock();
|
||||
try {
|
||||
// 5. 设置更新者
|
||||
String userId = LoginUtil.getCurrentUserId();
|
||||
if (StringUtils.hasText(userId)) {
|
||||
tbAgent.setUpdater(userId);
|
||||
}
|
||||
|
||||
// 6. 更新数据库
|
||||
int rows = agentMapper.updateAgent(tbAgent);
|
||||
if (rows > 0) {
|
||||
// 7. 返回最新数据
|
||||
TbAgent updated = agentMapper.selectAgentById(tbAgent.getAgentId());
|
||||
// 8. 更新缓存
|
||||
String cacheKey = AGENT_CACHE_PREFIX + tbAgent.getAgentId();
|
||||
logger.info("更新智能体成功: agentId={}", tbAgent.getAgentId());
|
||||
redisService.set(cacheKey, updated);
|
||||
return ResultDomain.success("更新智能体成功", updated);
|
||||
}
|
||||
} finally {
|
||||
agentLock.unlock();
|
||||
}
|
||||
|
||||
return ResultDomain.failure("更新智能体失败");
|
||||
}
|
||||
|
||||
@Override
|
||||
@Transactional(rollbackFor = Exception.class)
|
||||
public ResultDomain<TbAgent> deleteAgent(TbAgent tbAgent) {
|
||||
// 1. 参数校验
|
||||
if (!StringUtils.hasText(tbAgent.getAgentId())) {
|
||||
return ResultDomain.failure("智能体ID不能为空");
|
||||
}
|
||||
|
||||
// 2. 检查是否存在
|
||||
TbAgent existAgent = agentMapper.selectAgentById(tbAgent.getAgentId());
|
||||
if (existAgent == null) {
|
||||
return ResultDomain.failure("智能体不存在");
|
||||
}
|
||||
|
||||
// 3. 设置更新者
|
||||
String userId = LoginUtil.getCurrentUserId();
|
||||
if (StringUtils.hasText(userId)) {
|
||||
tbAgent.setUpdater(userId);
|
||||
}
|
||||
|
||||
// 4. 逻辑删除
|
||||
int rows = agentMapper.deleteAgent(tbAgent);
|
||||
if (rows > 0) {
|
||||
// 5. 删除缓存
|
||||
String cacheKey = AGENT_CACHE_PREFIX + tbAgent.getAgentId();
|
||||
redisService.delete(cacheKey);
|
||||
|
||||
logger.info("删除智能体成功: agentId={}", tbAgent.getAgentId());
|
||||
return ResultDomain.success("删除智能体成功", existAgent);
|
||||
}
|
||||
|
||||
return ResultDomain.failure("删除智能体失败");
|
||||
}
|
||||
|
||||
/**
|
||||
* 根据ID获取智能体(优先从缓存获取,双检加锁)
|
||||
*/
|
||||
public ResultDomain<TbAgent> selectAgentById(String agentId) {
|
||||
if (!StringUtils.hasText(agentId)) {
|
||||
return ResultDomain.failure("智能体ID不能为空");
|
||||
}
|
||||
|
||||
String cacheKey = AGENT_CACHE_PREFIX + agentId;
|
||||
|
||||
// 1. 先从缓存获取
|
||||
TbAgent agent = (TbAgent) redisService.get(cacheKey);
|
||||
if (agent != null) {
|
||||
return ResultDomain.success("查询成功", agent);
|
||||
}
|
||||
|
||||
// 2. 双检加锁
|
||||
agentLock.lock();
|
||||
try {
|
||||
// 再次检查缓存
|
||||
agent = (TbAgent) redisService.get(cacheKey);
|
||||
if (agent != null) {
|
||||
return ResultDomain.success("查询成功", agent);
|
||||
}
|
||||
|
||||
// 3. 从数据库获取
|
||||
agent = agentMapper.selectAgentById(agentId);
|
||||
if (agent != null) {
|
||||
// 4. 写入缓存
|
||||
redisService.set(cacheKey, agent);
|
||||
return ResultDomain.success("查询成功", agent);
|
||||
}
|
||||
|
||||
return ResultDomain.failure("智能体不存在");
|
||||
} finally {
|
||||
agentLock.unlock();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 根据ApiKey获取智能体
|
||||
*/
|
||||
public TbAgent getAgentByApiKey(String apiKey) {
|
||||
if (!StringUtils.hasText(apiKey)) {
|
||||
return null;
|
||||
}
|
||||
return agentMapper.selectAgentByApiKey(apiKey);
|
||||
}
|
||||
|
||||
@Override
|
||||
public ResultDomain<TbAgent> getAgentPage(PageRequest<TbAgent> pageRequest) {
|
||||
TbAgent filter = pageRequest.getFilter();
|
||||
PageParam pageParam = pageRequest.getPageParam();
|
||||
|
||||
// 查询总数
|
||||
long total = agentMapper.countAgents(filter);
|
||||
pageParam.setTotal((int) total);
|
||||
|
||||
// 查询分页数据
|
||||
List<TbAgent> list = agentMapper.selectAgentPage(filter, pageParam);
|
||||
|
||||
PageDomain<TbAgent> pageDomain = new PageDomain<>(pageParam, list);
|
||||
return ResultDomain.success("查询成功", pageDomain);
|
||||
}
|
||||
|
||||
@Override
|
||||
public ResultDomain<TbAgent> getAgentList(TbAgent filter) {
|
||||
// 查询列表
|
||||
List<TbAgent> list = agentMapper.selectAgentList(filter);
|
||||
|
||||
ResultDomain<TbAgent> result = ResultDomain.success("查询成功");
|
||||
result.setDataList(list);
|
||||
return result;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,165 @@
|
||||
package org.xyzh.ai.service.impl;
|
||||
|
||||
import com.alibaba.fastjson2.JSON;
|
||||
import com.alibaba.fastjson2.JSONObject;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.apache.dubbo.config.annotation.DubboService;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.stereotype.Service;
|
||||
import org.springframework.util.StringUtils;
|
||||
import org.xyzh.ai.client.DifyApiClient;
|
||||
import org.xyzh.api.ai.service.DifyProxyService;
|
||||
import org.xyzh.common.core.domain.ResultDomain;
|
||||
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* @description Dify代理服务实现(直接转发请求到Dify API)
|
||||
* @filename DifyProxyServiceImpl.java
|
||||
* @author yslg
|
||||
* @copyright xyzh
|
||||
* @since 2025-12-17
|
||||
*/
|
||||
@DubboService(version="1.0.0", group="ai", timeout=3000, retries=0)
|
||||
public class DifyProxyServiceImpl implements DifyProxyService {
|
||||
private static final Logger logger = LoggerFactory.getLogger(DifyProxyServiceImpl.class);
|
||||
|
||||
@Autowired
|
||||
private DifyApiClient difyApiClient;
|
||||
|
||||
@Override
|
||||
public ResultDomain<JSONObject> getDocumentSegments(String datasetId, String documentId) {
|
||||
// 1. 参数校验
|
||||
if (!StringUtils.hasText(datasetId)) {
|
||||
return ResultDomain.failure("数据集ID不能为空");
|
||||
}
|
||||
if (!StringUtils.hasText(documentId)) {
|
||||
return ResultDomain.failure("文档ID不能为空");
|
||||
}
|
||||
|
||||
try {
|
||||
// 2. 调用Dify API获取分段
|
||||
String url = String.format("/datasets/%s/documents/%s/segments", datasetId, documentId);
|
||||
String response = difyApiClient.get(url, null);
|
||||
|
||||
if (StringUtils.hasText(response)) {
|
||||
JSONObject result = JSON.parseObject(response);
|
||||
logger.info("获取文档分段成功: datasetId={}, documentId={}", datasetId, documentId);
|
||||
return ResultDomain.success("查询成功", result);
|
||||
}
|
||||
|
||||
return ResultDomain.failure("获取文档分段失败");
|
||||
} catch (Exception e) {
|
||||
logger.error("获取文档分段异常: datasetId={}, documentId={}, error={}", datasetId, documentId, e.getMessage(), e);
|
||||
return ResultDomain.failure("获取文档分段异常: " + e.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public ResultDomain<String> updateSegment(String datasetId, String documentId, String segmentId, Map<String, Object> requestBody) {
|
||||
// 1. 参数校验
|
||||
if (!StringUtils.hasText(datasetId)) {
|
||||
return ResultDomain.failure("数据集ID不能为空");
|
||||
}
|
||||
if (!StringUtils.hasText(documentId)) {
|
||||
return ResultDomain.failure("文档ID不能为空");
|
||||
}
|
||||
if (!StringUtils.hasText(segmentId)) {
|
||||
return ResultDomain.failure("分段ID不能为空");
|
||||
}
|
||||
|
||||
try {
|
||||
// 2. 调用Dify API更新分段
|
||||
String url = String.format("/datasets/%s/documents/%s/segments/%s", datasetId, documentId, segmentId);
|
||||
String response = difyApiClient.patch(url, requestBody, null);
|
||||
|
||||
logger.info("更新分段成功: datasetId={}, documentId={}, segmentId={}", datasetId, documentId, segmentId);
|
||||
return ResultDomain.success("更新成功", response);
|
||||
} catch (Exception e) {
|
||||
logger.error("更新分段异常: datasetId={}, documentId={}, segmentId={}, error={}",
|
||||
datasetId, documentId, segmentId, e.getMessage(), e);
|
||||
return ResultDomain.failure("更新分段异常: " + e.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public ResultDomain<String> createSegment(String datasetId, String documentId, Map<String, Object> requestBody) {
|
||||
// 1. 参数校验
|
||||
if (!StringUtils.hasText(datasetId)) {
|
||||
return ResultDomain.failure("数据集ID不能为空");
|
||||
}
|
||||
if (!StringUtils.hasText(documentId)) {
|
||||
return ResultDomain.failure("文档ID不能为空");
|
||||
}
|
||||
|
||||
try {
|
||||
// 2. 调用Dify API创建分段
|
||||
String url = String.format("/datasets/%s/documents/%s/segments", datasetId, documentId);
|
||||
String response = difyApiClient.post(url, requestBody, null);
|
||||
|
||||
logger.info("创建分段成功: datasetId={}, documentId={}", datasetId, documentId);
|
||||
return ResultDomain.success("创建成功", response);
|
||||
} catch (Exception e) {
|
||||
logger.error("创建分段异常: datasetId={}, documentId={}, error={}", datasetId, documentId, e.getMessage(), e);
|
||||
return ResultDomain.failure("创建分段异常: " + e.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public ResultDomain<String> deleteSegment(String datasetId, String documentId, String segmentId) {
|
||||
// 1. 参数校验
|
||||
if (!StringUtils.hasText(datasetId)) {
|
||||
return ResultDomain.failure("数据集ID不能为空");
|
||||
}
|
||||
if (!StringUtils.hasText(documentId)) {
|
||||
return ResultDomain.failure("文档ID不能为空");
|
||||
}
|
||||
if (!StringUtils.hasText(segmentId)) {
|
||||
return ResultDomain.failure("分段ID不能为空");
|
||||
}
|
||||
|
||||
try {
|
||||
// 2. 调用Dify API删除分段
|
||||
String url = String.format("/datasets/%s/documents/%s/segments/%s", datasetId, documentId, segmentId);
|
||||
String response = difyApiClient.delete(url, null);
|
||||
|
||||
logger.info("删除分段成功: datasetId={}, documentId={}, segmentId={}", datasetId, documentId, segmentId);
|
||||
return ResultDomain.success("删除成功", response);
|
||||
} catch (Exception e) {
|
||||
logger.error("删除分段异常: datasetId={}, documentId={}, segmentId={}, error={}",
|
||||
datasetId, documentId, segmentId, e.getMessage(), e);
|
||||
return ResultDomain.failure("删除分段异常: " + e.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public ResultDomain<String> updateDocumentStatus(String datasetId, String action, Map<String, Object> requestBody) {
|
||||
// 1. 参数校验
|
||||
if (!StringUtils.hasText(datasetId)) {
|
||||
return ResultDomain.failure("数据集ID不能为空");
|
||||
}
|
||||
if (!StringUtils.hasText(action)) {
|
||||
return ResultDomain.failure("操作类型不能为空");
|
||||
}
|
||||
|
||||
// 校验action合法性
|
||||
if (!action.equals("enable") && !action.equals("disable") &&
|
||||
!action.equals("archive") && !action.equals("un_archive")) {
|
||||
return ResultDomain.failure("操作类型不合法,支持:enable/disable/archive/un_archive");
|
||||
}
|
||||
|
||||
try {
|
||||
// 2. 调用Dify API更新文档状态
|
||||
String url = String.format("/datasets/%s/documents/%s/status", datasetId, action);
|
||||
String response = difyApiClient.patch(url, requestBody, null);
|
||||
|
||||
logger.info("更新文档状态成功: datasetId={}, action={}", datasetId, action);
|
||||
return ResultDomain.success("更新成功", response);
|
||||
} catch (Exception e) {
|
||||
logger.error("更新文档状态异常: datasetId={}, action={}, error={}", datasetId, action, e.getMessage(), e);
|
||||
return ResultDomain.failure("更新文档状态异常: " + e.getMessage());
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,661 @@
|
||||
package org.xyzh.ai.service.impl;
|
||||
|
||||
import org.apache.dubbo.config.annotation.DubboReference;
|
||||
import org.apache.dubbo.config.annotation.DubboService;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.util.StringUtils;
|
||||
import org.springframework.web.multipart.MultipartFile;
|
||||
import org.xyzh.ai.client.DifyApiClient;
|
||||
import org.xyzh.ai.client.dto.DatasetCreateRequest;
|
||||
import org.xyzh.ai.client.dto.DatasetCreateResponse;
|
||||
import org.xyzh.ai.client.dto.DatasetDetailResponse;
|
||||
import org.xyzh.ai.client.dto.DocumentListResponse;
|
||||
import org.xyzh.ai.mapper.TbKnowledgeFileMapper;
|
||||
import org.xyzh.ai.mapper.TbKnowledgeMapper;
|
||||
import org.xyzh.api.ai.dto.TbKnowledge;
|
||||
import org.xyzh.api.ai.dto.TbKnowledgeFile;
|
||||
import org.xyzh.api.ai.service.AIFileUploadService;
|
||||
import org.xyzh.api.ai.service.KnowledgeService;
|
||||
import org.xyzh.api.file.dto.TbSysFileDTO;
|
||||
import org.xyzh.api.file.service.FileService;
|
||||
import org.xyzh.common.core.domain.ResultDomain;
|
||||
import org.xyzh.common.core.page.PageParam;
|
||||
import org.xyzh.common.utils.id.IdUtil;
|
||||
import org.xyzh.common.auth.utils.LoginUtil;
|
||||
|
||||
import java.io.File;
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* @description 知识库服务实现
|
||||
* @filename KnowledgeServiceImpl.java
|
||||
* @author yslg
|
||||
* @copyright xyzh
|
||||
* @since 2025-12-17
|
||||
*/
|
||||
@DubboService(version="1.0.0", group="ai", timeout=3000, retries=0)
|
||||
public class KnowledgeServiceImpl implements KnowledgeService {
|
||||
private static final Logger logger = LoggerFactory.getLogger(KnowledgeServiceImpl.class);
|
||||
|
||||
@Autowired
|
||||
private TbKnowledgeMapper knowledgeMapper;
|
||||
|
||||
@Autowired
|
||||
private TbKnowledgeFileMapper knowledgeFileMapper;
|
||||
|
||||
@Autowired
|
||||
private DifyApiClient difyApiClient;
|
||||
|
||||
@DubboReference(version = "1.0.0", group = "file", timeout = 30000)
|
||||
private FileService fileService;
|
||||
|
||||
@DubboReference(version = "1.0.0", group = "ai", timeout = 30000)
|
||||
private AIFileUploadService aiFileUploadService;
|
||||
|
||||
// ================================= 知识库管理 =================================
|
||||
|
||||
/**
|
||||
* @description 创建知识库基础信息,包含dify知识库各种参数的配置
|
||||
* 注意:涉及外部API调用,不使用@Transactional,采用手动补偿机制
|
||||
* @param knowledge 知识库信息
|
||||
* @param permissionType 权限类型:PUBLIC-公开,DEPARTMENT-部门,ROLE-角色,PRIVATE-私有
|
||||
* @param deptIds 部门ID列表(DEPARTMENT类型需要)
|
||||
* @param roleIds 角色ID列表(ROLE/PRIVATE类型需要)
|
||||
* @return ResultDomain<TbKnowledge> 创建结果
|
||||
* @author yslg
|
||||
* @since 2025-12-18
|
||||
*/
|
||||
@Override
|
||||
public ResultDomain<TbKnowledge> createKnowledge(TbKnowledge knowledge, String permissionType, List<String> deptIds, List<String> roleIds) {
|
||||
// 1. 参数校验
|
||||
if (!StringUtils.hasText(knowledge.getTitle())) {
|
||||
return ResultDomain.failure("知识库标题不能为空");
|
||||
}
|
||||
|
||||
// 2. 获取当前用户
|
||||
String userId = LoginUtil.getCurrentUserId();
|
||||
if (!StringUtils.hasText(userId)) {
|
||||
return ResultDomain.failure("用户未登录");
|
||||
}
|
||||
|
||||
// 3. 生成ID
|
||||
knowledge.setOptsn(IdUtil.getOptsn());
|
||||
knowledge.setKnowledgeId(IdUtil.getSnowflakeId());
|
||||
knowledge.setCreator(userId);
|
||||
|
||||
// 4. 创建Dify知识库
|
||||
String difyDatasetId = null;
|
||||
try {
|
||||
DatasetCreateRequest createRequest = new DatasetCreateRequest();
|
||||
createRequest.setName(knowledge.getTitle());
|
||||
createRequest.setDescription(knowledge.getDescription());
|
||||
if (StringUtils.hasText(knowledge.getDifyIndexingTechnique())) {
|
||||
createRequest.setIndexingTechnique(knowledge.getDifyIndexingTechnique());
|
||||
}
|
||||
|
||||
DatasetCreateResponse difyResponse = difyApiClient.createDataset(createRequest);
|
||||
if (difyResponse != null && StringUtils.hasText(difyResponse.getId())) {
|
||||
difyDatasetId = difyResponse.getId();
|
||||
knowledge.setDifyDatasetId(difyDatasetId);
|
||||
} else {
|
||||
return ResultDomain.failure("创建Dify知识库失败");
|
||||
}
|
||||
} catch (Exception e) {
|
||||
logger.error("创建Dify知识库异常: {}", e.getMessage(), e);
|
||||
return ResultDomain.failure("创建Dify知识库异常: " + e.getMessage());
|
||||
}
|
||||
|
||||
// 5. 插入数据库
|
||||
int rows = knowledgeMapper.insertKnowledge(knowledge);
|
||||
if (rows > 0) {
|
||||
logger.info("创建知识库成功: knowledgeId={}, title={}", knowledge.getKnowledgeId(), knowledge.getTitle());
|
||||
return ResultDomain.success("创建知识库成功", knowledge);
|
||||
}
|
||||
|
||||
// 数据库保存失败,补偿删除Dify知识库
|
||||
if (StringUtils.hasText(difyDatasetId)) {
|
||||
try {
|
||||
difyApiClient.deleteDataset(difyDatasetId);
|
||||
logger.info("补偿删除Dify知识库成功: difyDatasetId={}", difyDatasetId);
|
||||
} catch (Exception e) {
|
||||
logger.warn("补偿删除Dify知识库失败: {}", e.getMessage());
|
||||
}
|
||||
}
|
||||
return ResultDomain.failure("创建知识库失败");
|
||||
}
|
||||
|
||||
/**
|
||||
* @description 更新知识库,包含dify知识库各种参数的配置
|
||||
* @param knowledge 知识库信息
|
||||
* @return ResultDomain<TbKnowledge> 更新结果
|
||||
* @author yslg
|
||||
* @since 2025-12-18
|
||||
*/
|
||||
@Override
|
||||
public ResultDomain<TbKnowledge> updateKnowledge(TbKnowledge knowledge) {
|
||||
// 1. 参数校验
|
||||
if (!StringUtils.hasText(knowledge.getKnowledgeId())) {
|
||||
return ResultDomain.failure("知识库ID不能为空");
|
||||
}
|
||||
|
||||
// 2. 检查是否存在
|
||||
TbKnowledge existing = knowledgeMapper.selectKnowledgeById(knowledge.getKnowledgeId());
|
||||
if (existing == null) {
|
||||
return ResultDomain.failure("知识库不存在");
|
||||
}
|
||||
|
||||
// 3. 设置更新者
|
||||
String userId = LoginUtil.getCurrentUserId();
|
||||
if (StringUtils.hasText(userId)) {
|
||||
knowledge.setUpdater(userId);
|
||||
}
|
||||
|
||||
// 4. 更新数据库
|
||||
int rows = knowledgeMapper.updateKnowledge(knowledge);
|
||||
if (rows > 0) {
|
||||
logger.info("更新知识库成功: knowledgeId={}", knowledge.getKnowledgeId());
|
||||
TbKnowledge updated = knowledgeMapper.selectKnowledgeById(knowledge.getKnowledgeId());
|
||||
return ResultDomain.success("更新知识库成功", updated);
|
||||
}
|
||||
|
||||
return ResultDomain.failure("更新知识库失败");
|
||||
}
|
||||
|
||||
/**
|
||||
* @description 删除知识库,同时删除dify知识库
|
||||
* 注意:Dify删除失败不影响本地删除
|
||||
* @param knowledgeId 知识库ID
|
||||
* @return ResultDomain<Boolean> 删除结果
|
||||
* @author yslg
|
||||
* @since 2025-12-18
|
||||
*/
|
||||
@Override
|
||||
public ResultDomain<Boolean> deleteKnowledge(String knowledgeId) {
|
||||
// 1. 参数校验
|
||||
if (!StringUtils.hasText(knowledgeId)) {
|
||||
return ResultDomain.failure("知识库ID不能为空");
|
||||
}
|
||||
|
||||
// 2. 检查是否存在
|
||||
TbKnowledge existing = knowledgeMapper.selectKnowledgeById(knowledgeId);
|
||||
if (existing == null) {
|
||||
return ResultDomain.failure("知识库不存在");
|
||||
}
|
||||
|
||||
// 3. 删除Dify知识库
|
||||
if (StringUtils.hasText(existing.getDifyDatasetId())) {
|
||||
try {
|
||||
difyApiClient.deleteDataset(existing.getDifyDatasetId());
|
||||
} catch (Exception e) {
|
||||
logger.warn("删除Dify知识库失败: {}", e.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
// 4. 设置更新者并软删除
|
||||
TbKnowledge delete = new TbKnowledge();
|
||||
delete.setKnowledgeId(knowledgeId);
|
||||
String userId = LoginUtil.getCurrentUserId();
|
||||
if (StringUtils.hasText(userId)) {
|
||||
delete.setUpdater(userId);
|
||||
}
|
||||
|
||||
int rows = knowledgeMapper.deleteKnowledge(delete);
|
||||
if (rows > 0) {
|
||||
logger.info("删除知识库成功: knowledgeId={}", knowledgeId);
|
||||
return ResultDomain.success("删除知识库成功", true);
|
||||
}
|
||||
|
||||
return ResultDomain.failure("删除知识库失败");
|
||||
}
|
||||
|
||||
/**
|
||||
* @description 获取知识库详情,包含dify知识库各种参数的配置
|
||||
* @param knowledgeId 知识库ID
|
||||
* @return ResultDomain<TbKnowledge> 知识库信息
|
||||
* @author yslg
|
||||
* @since 2025-12-18
|
||||
*/
|
||||
@Override
|
||||
public ResultDomain<TbKnowledge> getKnowledgeById(String knowledgeId) {
|
||||
if (!StringUtils.hasText(knowledgeId)) {
|
||||
return ResultDomain.failure("知识库ID不能为空");
|
||||
}
|
||||
|
||||
TbKnowledge knowledge = knowledgeMapper.selectKnowledgeById(knowledgeId);
|
||||
if (knowledge == null) {
|
||||
return ResultDomain.failure("知识库不存在");
|
||||
}
|
||||
|
||||
return ResultDomain.success("查询成功", knowledge);
|
||||
}
|
||||
|
||||
/**
|
||||
* @description 查询知识库列表
|
||||
* @param filter 过滤条件
|
||||
* @return ResultDomain<TbKnowledge> 知识库列表
|
||||
* @author yslg
|
||||
* @since 2025-12-18
|
||||
*/
|
||||
@Override
|
||||
public ResultDomain<TbKnowledge> listKnowledges(TbKnowledge filter) {
|
||||
List<TbKnowledge> list = knowledgeMapper.selectKnowledgeList(filter);
|
||||
return ResultDomain.success("查询成功", list);
|
||||
}
|
||||
|
||||
/**
|
||||
* @description 分页查询知识库
|
||||
* @param filter 过滤条件
|
||||
* @param pageParam 分页参数
|
||||
* @return ResultDomain<TbKnowledge> 分页结果
|
||||
* @author yslg
|
||||
* @since 2025-12-18
|
||||
*/
|
||||
@Override
|
||||
public ResultDomain<TbKnowledge> pageKnowledges(TbKnowledge filter, PageParam pageParam) {
|
||||
List<TbKnowledge> list = knowledgeMapper.selectKnowledgePage(filter, pageParam);
|
||||
long total = knowledgeMapper.countKnowledges(filter);
|
||||
pageParam.setTotal((int) total);
|
||||
return ResultDomain.success("查询成功", list);
|
||||
}
|
||||
|
||||
/**
|
||||
* @description 获取知识库统计信息(从Dify同步文档数量和分段数量)
|
||||
* @param knowledgeId 知识库ID
|
||||
* @return ResultDomain<TbKnowledge> 统计信息
|
||||
* @author yslg
|
||||
* @since 2025-12-18
|
||||
*/
|
||||
@Override
|
||||
public ResultDomain<TbKnowledge> getKnowledgeStats(String knowledgeId) {
|
||||
TbKnowledge knowledge = knowledgeMapper.selectKnowledgeById(knowledgeId);
|
||||
if (knowledge == null) {
|
||||
return ResultDomain.failure("知识库不存在");
|
||||
}
|
||||
|
||||
// 从Dify同步统计信息
|
||||
if (StringUtils.hasText(knowledge.getDifyDatasetId())) {
|
||||
try {
|
||||
DatasetDetailResponse detail = difyApiClient.getDatasetDetail(knowledge.getDifyDatasetId());
|
||||
if (detail != null) {
|
||||
TbKnowledge update = new TbKnowledge();
|
||||
update.setKnowledgeId(knowledgeId);
|
||||
update.setDocumentCount(detail.getDocumentCount());
|
||||
update.setTotalChunks(detail.getWordCount());
|
||||
knowledgeMapper.updateKnowledge(update);
|
||||
|
||||
knowledge.setDocumentCount(detail.getDocumentCount());
|
||||
knowledge.setTotalChunks(detail.getWordCount());
|
||||
}
|
||||
} catch (Exception e) {
|
||||
logger.warn("同步Dify统计信息失败: {}", e.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
return ResultDomain.success("查询成功", knowledge);
|
||||
}
|
||||
|
||||
/**
|
||||
* @description 获取可用的嵌入模型列表
|
||||
* @return ResultDomain<Map<String, Object>> 嵌入模型列表
|
||||
* @author yslg
|
||||
* @since 2025-12-18
|
||||
*/
|
||||
@Override
|
||||
public ResultDomain<Map<String, Object>> getAvailableEmbeddingModels() {
|
||||
try {
|
||||
var response = difyApiClient.getAvailableEmbeddingModels();
|
||||
Map<String, Object> result = new HashMap<>();
|
||||
result.put("data", response.getData());
|
||||
return ResultDomain.success("查询成功", result);
|
||||
} catch (Exception e) {
|
||||
logger.error("获取嵌入模型列表失败: {}", e.getMessage(), e);
|
||||
return ResultDomain.failure("获取嵌入模型列表失败: " + e.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @description 获取可用的Rerank模型列表
|
||||
* @return ResultDomain<Map<String, Object>> Rerank模型列表
|
||||
* @author yslg
|
||||
* @since 2025-12-18
|
||||
*/
|
||||
@Override
|
||||
public ResultDomain<Map<String, Object>> getAvailableRerankModels() {
|
||||
try {
|
||||
var response = difyApiClient.getAvailableRerankModels();
|
||||
Map<String, Object> result = new HashMap<>();
|
||||
result.put("data", response.getData());
|
||||
return ResultDomain.success("查询成功", result);
|
||||
} catch (Exception e) {
|
||||
logger.error("获取Rerank模型列表失败: {}", e.getMessage(), e);
|
||||
return ResultDomain.failure("获取Rerank模型列表失败: " + e.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @description 获取知识库文档列表(从Dify获取)
|
||||
* @param knowledgeId 知识库ID
|
||||
* @param page 页码(从1开始)
|
||||
* @param limit 每页数量
|
||||
* @return ResultDomain<Map<String, Object>> 文档列表
|
||||
* @author yslg
|
||||
* @since 2025-12-18
|
||||
*/
|
||||
@Override
|
||||
public ResultDomain<Map<String, Object>> getDocumentList(String knowledgeId, Integer page, Integer limit) {
|
||||
TbKnowledge knowledge = knowledgeMapper.selectKnowledgeById(knowledgeId);
|
||||
if (knowledge == null || !StringUtils.hasText(knowledge.getDifyDatasetId())) {
|
||||
return ResultDomain.failure("知识库不存在或未关联Dify");
|
||||
}
|
||||
|
||||
try {
|
||||
DocumentListResponse response = difyApiClient.listDocuments(knowledge.getDifyDatasetId(), page, limit);
|
||||
Map<String, Object> result = new HashMap<>();
|
||||
result.put("data", response.getData());
|
||||
result.put("total", response.getTotal());
|
||||
result.put("page", response.getPage());
|
||||
result.put("limit", response.getLimit());
|
||||
return ResultDomain.success("查询成功", result);
|
||||
} catch (Exception e) {
|
||||
logger.error("获取Dify文档列表失败: {}", e.getMessage(), e);
|
||||
return ResultDomain.failure("获取文档列表失败: " + e.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
// ================================= 文件管理 =================================
|
||||
|
||||
/**
|
||||
* @description 上传文件到知识库(完整流程:minio + Dify + 数据库)
|
||||
* @param knowledgeId 知识库ID
|
||||
* @param file 上传的文件
|
||||
* @param indexingTechnique 索引方式
|
||||
* @return ResultDomain<TbKnowledgeFile> 上传结果
|
||||
* @author yslg
|
||||
* @since 2025-12-18
|
||||
*/
|
||||
@Override
|
||||
public ResultDomain<TbKnowledgeFile> uploadKnowledgeFile(String knowledgeId, MultipartFile file, String indexingTechnique) {
|
||||
// 1. 参数校验
|
||||
if (!StringUtils.hasText(knowledgeId)) {
|
||||
return ResultDomain.failure("知识库ID不能为空");
|
||||
}
|
||||
if (file == null || file.isEmpty()) {
|
||||
return ResultDomain.failure("文件不能为空");
|
||||
}
|
||||
|
||||
// 2. 获取知识库
|
||||
TbKnowledge knowledge = knowledgeMapper.selectKnowledgeById(knowledgeId);
|
||||
if (knowledge == null) {
|
||||
return ResultDomain.failure("知识库不存在");
|
||||
}
|
||||
if (!StringUtils.hasText(knowledge.getDifyDatasetId())) {
|
||||
return ResultDomain.failure("知识库未关联Dify");
|
||||
}
|
||||
|
||||
// 3. 调用FileService上传到minio
|
||||
ResultDomain<TbSysFileDTO> fileResult = fileService.uploadFile(file, "knowledge", knowledgeId);
|
||||
if (!fileResult.getSuccess() || fileResult.getData() == null) {
|
||||
return ResultDomain.failure("上传文件到存储服务失败: " + fileResult.getMessage());
|
||||
}
|
||||
TbSysFileDTO sysFile = fileResult.getData();
|
||||
String fileId = sysFile.getFileId();
|
||||
logger.info("上传文件到minio成功: fileId={}, path={}", fileId, sysFile.getPath());
|
||||
|
||||
// 4. 上传到Dify知识库
|
||||
String difyFileId = null;
|
||||
File tempFile = null;
|
||||
try {
|
||||
tempFile = File.createTempFile("knowledge_", "_" + file.getOriginalFilename());
|
||||
file.transferTo(tempFile);
|
||||
|
||||
String technique = StringUtils.hasText(indexingTechnique) ? indexingTechnique : knowledge.getDifyIndexingTechnique();
|
||||
ResultDomain<String> difyResult = aiFileUploadService.uploadFileToDify(
|
||||
knowledge.getDifyDatasetId(), tempFile, file.getOriginalFilename(), technique);
|
||||
|
||||
if (difyResult.getSuccess() && StringUtils.hasText(difyResult.getData())) {
|
||||
difyFileId = difyResult.getData();
|
||||
logger.info("上传文件到Dify成功: difyFileId={}", difyFileId);
|
||||
} else {
|
||||
logger.error("上传文件到Dify失败: {}", difyResult.getMessage());
|
||||
fileService.deleteFile(fileId);
|
||||
return ResultDomain.failure("上传文件到Dify失败: " + difyResult.getMessage());
|
||||
}
|
||||
} catch (Exception e) {
|
||||
logger.error("上传文件到Dify异常: {}", e.getMessage(), e);
|
||||
fileService.deleteFile(fileId);
|
||||
return ResultDomain.failure("上传文件到Dify异常: " + e.getMessage());
|
||||
} finally {
|
||||
if (tempFile != null && tempFile.exists()) {
|
||||
tempFile.delete();
|
||||
}
|
||||
}
|
||||
|
||||
// 5. 保存tb_knowledge_file
|
||||
TbKnowledgeFile knowledgeFile = new TbKnowledgeFile();
|
||||
knowledgeFile.setOptsn(IdUtil.getOptsn());
|
||||
knowledgeFile.setKnowledgeId(knowledgeId);
|
||||
knowledgeFile.setFileRootId(fileId);
|
||||
knowledgeFile.setFileId(fileId);
|
||||
knowledgeFile.setDifyFileId(difyFileId);
|
||||
knowledgeFile.setVersion(1);
|
||||
|
||||
int rows = knowledgeFileMapper.insertKnowledgeFile(knowledgeFile);
|
||||
if (rows > 0) {
|
||||
logger.info("保存知识库文件记录成功: knowledgeId={}, fileId={}, difyFileId={}", knowledgeId, fileId, difyFileId);
|
||||
return ResultDomain.success("上传成功", knowledgeFile);
|
||||
}
|
||||
|
||||
// 补偿删除
|
||||
fileService.deleteFile(fileId);
|
||||
aiFileUploadService.deleteFileFromDify(knowledge.getDifyDatasetId(), difyFileId);
|
||||
return ResultDomain.failure("保存知识库文件记录失败");
|
||||
}
|
||||
|
||||
/**
|
||||
* @description 批量上传文件到知识库
|
||||
* @param knowledgeId 知识库ID
|
||||
* @param files 文件列表
|
||||
* @param indexingTechnique 索引方式
|
||||
* @return ResultDomain<TbKnowledgeFile> 上传结果
|
||||
* @author yslg
|
||||
* @since 2025-12-18
|
||||
*/
|
||||
@Override
|
||||
public ResultDomain<TbKnowledgeFile> batchUploadKnowledgeFile(String knowledgeId, List<MultipartFile> files, String indexingTechnique) {
|
||||
if (!StringUtils.hasText(knowledgeId)) {
|
||||
return ResultDomain.failure("知识库ID不能为空");
|
||||
}
|
||||
if (files == null || files.isEmpty()) {
|
||||
return ResultDomain.failure("文件列表不能为空");
|
||||
}
|
||||
|
||||
List<TbKnowledgeFile> successList = new ArrayList<>();
|
||||
List<String> failedFiles = new ArrayList<>();
|
||||
|
||||
for (MultipartFile file : files) {
|
||||
ResultDomain<TbKnowledgeFile> result = uploadKnowledgeFile(knowledgeId, file, indexingTechnique);
|
||||
if (result.getSuccess() && result.getData() != null) {
|
||||
successList.add(result.getData());
|
||||
} else {
|
||||
failedFiles.add(file.getOriginalFilename() + "(" + result.getMessage() + ")");
|
||||
}
|
||||
}
|
||||
|
||||
if (failedFiles.isEmpty()) {
|
||||
return ResultDomain.success("批量上传成功", successList);
|
||||
} else if (successList.isEmpty()) {
|
||||
return ResultDomain.failure("批量上传全部失败: " + String.join(", ", failedFiles));
|
||||
} else {
|
||||
return ResultDomain.success("部分上传成功,失败: " + String.join(", ", failedFiles), successList);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @description 上传新版本文件(fileRootId一致,version递增)
|
||||
* @param knowledgeId 知识库ID
|
||||
* @param file 新文件
|
||||
* @param fileRootId 文件根ID
|
||||
* @return ResultDomain<TbKnowledgeFile> 新版本文件信息
|
||||
* @author yslg
|
||||
* @since 2025-12-18
|
||||
*/
|
||||
@Override
|
||||
public ResultDomain<TbKnowledgeFile> updateKnowledgeFileVersion(String knowledgeId, MultipartFile file, String fileRootId) {
|
||||
// 1. 参数校验
|
||||
if (!StringUtils.hasText(knowledgeId)) {
|
||||
return ResultDomain.failure("知识库ID不能为空");
|
||||
}
|
||||
if (file == null || file.isEmpty()) {
|
||||
return ResultDomain.failure("文件不能为空");
|
||||
}
|
||||
if (!StringUtils.hasText(fileRootId)) {
|
||||
return ResultDomain.failure("文件根ID不能为空");
|
||||
}
|
||||
|
||||
// 2. 获取知识库
|
||||
TbKnowledge knowledge = knowledgeMapper.selectKnowledgeById(knowledgeId);
|
||||
if (knowledge == null) {
|
||||
return ResultDomain.failure("知识库不存在");
|
||||
}
|
||||
if (!StringUtils.hasText(knowledge.getDifyDatasetId())) {
|
||||
return ResultDomain.failure("知识库未关联Dify");
|
||||
}
|
||||
|
||||
// 3. 获取旧版本
|
||||
List<TbKnowledgeFile> oldVersions = knowledgeFileMapper.selectFileVersions(fileRootId);
|
||||
if (oldVersions == null || oldVersions.isEmpty()) {
|
||||
return ResultDomain.failure("原文件不存在");
|
||||
}
|
||||
|
||||
// 4. 上传新版本到minio
|
||||
ResultDomain<TbSysFileDTO> fileResult = fileService.uploadFileVersion(file, "knowledge", knowledgeId, fileRootId);
|
||||
if (!fileResult.getSuccess() || fileResult.getData() == null) {
|
||||
return ResultDomain.failure("上传新版本文件失败: " + fileResult.getMessage());
|
||||
}
|
||||
TbSysFileDTO sysFile = fileResult.getData();
|
||||
String newFileId = sysFile.getFileId();
|
||||
int newVersion = sysFile.getVersion();
|
||||
logger.info("上传新版本到minio成功: fileId={}, version={}", newFileId, newVersion);
|
||||
|
||||
// 5. 删除Dify旧文档
|
||||
for (TbKnowledgeFile oldFile : oldVersions) {
|
||||
if (StringUtils.hasText(oldFile.getDifyFileId())) {
|
||||
aiFileUploadService.deleteFileFromDify(knowledge.getDifyDatasetId(), oldFile.getDifyFileId());
|
||||
}
|
||||
}
|
||||
|
||||
// 6. 上传新文件到Dify
|
||||
String newDifyFileId = null;
|
||||
File tempFile = null;
|
||||
try {
|
||||
tempFile = File.createTempFile("knowledge_update_", "_" + file.getOriginalFilename());
|
||||
file.transferTo(tempFile);
|
||||
|
||||
ResultDomain<String> difyResult = aiFileUploadService.uploadFileToDify(
|
||||
knowledge.getDifyDatasetId(), tempFile, file.getOriginalFilename(), knowledge.getDifyIndexingTechnique());
|
||||
|
||||
if (difyResult.getSuccess() && StringUtils.hasText(difyResult.getData())) {
|
||||
newDifyFileId = difyResult.getData();
|
||||
} else {
|
||||
fileService.deleteFile(newFileId);
|
||||
return ResultDomain.failure("上传新文件到Dify失败: " + difyResult.getMessage());
|
||||
}
|
||||
} catch (Exception e) {
|
||||
logger.error("上传新文件到Dify异常: {}", e.getMessage(), e);
|
||||
fileService.deleteFile(newFileId);
|
||||
return ResultDomain.failure("上传新文件到Dify异常: " + e.getMessage());
|
||||
} finally {
|
||||
if (tempFile != null && tempFile.exists()) {
|
||||
tempFile.delete();
|
||||
}
|
||||
}
|
||||
|
||||
// 7. 保存tb_knowledge_file
|
||||
TbKnowledgeFile newKnowledgeFile = new TbKnowledgeFile();
|
||||
newKnowledgeFile.setOptsn(IdUtil.getOptsn());
|
||||
newKnowledgeFile.setKnowledgeId(knowledgeId);
|
||||
newKnowledgeFile.setFileRootId(fileRootId);
|
||||
newKnowledgeFile.setFileId(newFileId);
|
||||
newKnowledgeFile.setDifyFileId(newDifyFileId);
|
||||
newKnowledgeFile.setVersion(newVersion);
|
||||
|
||||
int rows = knowledgeFileMapper.insertKnowledgeFile(newKnowledgeFile);
|
||||
if (rows > 0) {
|
||||
logger.info("保存新版本记录成功: knowledgeId={}, fileRootId={}, newVersion={}", knowledgeId, fileRootId, newVersion);
|
||||
return ResultDomain.success("更新成功", newKnowledgeFile);
|
||||
}
|
||||
|
||||
// 补偿
|
||||
fileService.deleteFile(newFileId);
|
||||
aiFileUploadService.deleteFileFromDify(knowledge.getDifyDatasetId(), newDifyFileId);
|
||||
return ResultDomain.failure("保存新版本记录失败");
|
||||
}
|
||||
|
||||
/**
|
||||
* @description 删除知识库文件(根据fileRootId删除所有版本)
|
||||
* @param fileRootId 文件根ID
|
||||
* @return ResultDomain<Boolean> 删除结果
|
||||
* @author yslg
|
||||
* @since 2025-12-18
|
||||
*/
|
||||
@Override
|
||||
public ResultDomain<Boolean> deleteKnowledgeFileById(String fileRootId) {
|
||||
if (!StringUtils.hasText(fileRootId)) {
|
||||
return ResultDomain.failure("文件根ID不能为空");
|
||||
}
|
||||
|
||||
// 1. 获取所有版本
|
||||
List<TbKnowledgeFile> versions = knowledgeFileMapper.selectFileVersions(fileRootId);
|
||||
if (versions == null || versions.isEmpty()) {
|
||||
return ResultDomain.failure("文件不存在");
|
||||
}
|
||||
|
||||
// 2. 删除Dify中的文档
|
||||
TbKnowledge knowledge = knowledgeMapper.selectKnowledgeById(versions.get(0).getKnowledgeId());
|
||||
if (knowledge != null && StringUtils.hasText(knowledge.getDifyDatasetId())) {
|
||||
List<String> difyDocIds = new ArrayList<>();
|
||||
for (TbKnowledgeFile file : versions) {
|
||||
if (StringUtils.hasText(file.getDifyFileId())) {
|
||||
difyDocIds.add(file.getDifyFileId());
|
||||
}
|
||||
}
|
||||
if (!difyDocIds.isEmpty()) {
|
||||
aiFileUploadService.batchDeleteFilesFromDify(knowledge.getDifyDatasetId(), difyDocIds);
|
||||
}
|
||||
}
|
||||
|
||||
// 3. 软删除本地记录和minio文件
|
||||
int rows = knowledgeFileMapper.deleteFilesByRootId(fileRootId);
|
||||
if (rows > 0) {
|
||||
logger.info("删除知识库文件成功: fileRootId={}", fileRootId);
|
||||
for (TbKnowledgeFile file : versions) {
|
||||
fileService.deleteFile(file.getFileId());
|
||||
}
|
||||
return ResultDomain.success("删除成功", true);
|
||||
}
|
||||
|
||||
return ResultDomain.failure("删除文件失败");
|
||||
}
|
||||
|
||||
/**
|
||||
* @description 获取文件历史版本
|
||||
* @param fileRootId 文件根ID
|
||||
* @return ResultDomain<TbKnowledgeFile> 文件历史版本列表
|
||||
* @author yslg
|
||||
* @since 2025-12-18
|
||||
*/
|
||||
@Override
|
||||
public ResultDomain<TbKnowledgeFile> getKnowledgeFileHistory(String fileRootId) {
|
||||
if (!StringUtils.hasText(fileRootId)) {
|
||||
return ResultDomain.failure("文件根ID不能为空");
|
||||
}
|
||||
|
||||
List<TbKnowledgeFile> versions = knowledgeFileMapper.selectFileVersions(fileRootId);
|
||||
return ResultDomain.success("查询成功", versions);
|
||||
}
|
||||
}
|
||||
@@ -5,18 +5,19 @@ server:
|
||||
# context-path: /urban-lifeline/agent # 微服务架构下,context-path由Gateway管理
|
||||
|
||||
# ================== Auth ====================
|
||||
urban-lifeline:
|
||||
auth:
|
||||
enabled: true
|
||||
whitelist:
|
||||
- /swagger-ui/**
|
||||
- /swagger-ui.html
|
||||
- /v3/api-docs/**
|
||||
- /webjars/**
|
||||
- /favicon.ico
|
||||
- /error
|
||||
- /actuator/health
|
||||
- /actuator/info
|
||||
auth:
|
||||
enabled: true
|
||||
gateway-mode: true
|
||||
whitelist:
|
||||
- /swagger-ui/**
|
||||
- /swagger-ui.html
|
||||
- /v3/api-docs/**
|
||||
- /webjars/**
|
||||
- /favicon.ico
|
||||
- /error
|
||||
- /actuator/health
|
||||
- /actuator/info
|
||||
- /ai/chat/* # AI对话,有非系统用户对话的接口,无登录状态
|
||||
|
||||
security:
|
||||
aes:
|
||||
|
||||
146
urbanLifelineServ/ai/src/main/resources/mapper/TbAgentMapper.xml
Normal file
146
urbanLifelineServ/ai/src/main/resources/mapper/TbAgentMapper.xml
Normal file
@@ -0,0 +1,146 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
|
||||
<mapper namespace="org.xyzh.ai.mapper.TbAgentMapper">
|
||||
|
||||
<resultMap id="BaseResultMap" type="org.xyzh.api.ai.dto.TbAgent">
|
||||
<result column="optsn" property="optsn" jdbcType="VARCHAR"/>
|
||||
<result column="agent_id" property="agentId" jdbcType="VARCHAR"/>
|
||||
<result column="name" property="name" jdbcType="VARCHAR"/>
|
||||
<result column="description" property="description" jdbcType="VARCHAR"/>
|
||||
<result column="link" property="link" jdbcType="VARCHAR"/>
|
||||
<result column="api_key" property="apiKey" jdbcType="VARCHAR"/>
|
||||
<result column="outer" property="outer" jdbcType="BOOLEAN"/>
|
||||
<result column="introduce" property="introduce" jdbcType="VARCHAR"/>
|
||||
<result column="prompt_cards" property="promptCards" jdbcType="OTHER" typeHandler="org.xyzh.ai.handler.PromptCardsTypeHandler"/>
|
||||
<result column="category" property="category" jdbcType="VARCHAR"/>
|
||||
<result column="creator" property="creator" jdbcType="VARCHAR"/>
|
||||
<result column="updater" property="updater" jdbcType="VARCHAR"/>
|
||||
<result column="create_time" property="createTime" jdbcType="TIMESTAMP"/>
|
||||
<result column="update_time" property="updateTime" jdbcType="TIMESTAMP"/>
|
||||
<result column="delete_time" property="deleteTime" jdbcType="TIMESTAMP"/>
|
||||
<result column="deleted" property="deleted" jdbcType="BOOLEAN"/>
|
||||
</resultMap>
|
||||
|
||||
<sql id="Base_Column_List">
|
||||
optsn, agent_id, name, description, link, api_key, outer, introduce, prompt_cards,
|
||||
category, creator, updater, create_time, update_time, delete_time, deleted
|
||||
</sql>
|
||||
|
||||
<insert id="insertAgent" parameterType="org.xyzh.api.ai.dto.TbAgent">
|
||||
INSERT INTO ai.tb_agent (
|
||||
optsn, agent_id, name, api_key, introduce, category
|
||||
<if test="outer !=null">, outer</if>
|
||||
<if test="description != null">, description</if>
|
||||
<if test="link != null">, link</if>
|
||||
<if test="promptCards != null">, prompt_cards</if>
|
||||
<if test="creator != null">, creator</if>
|
||||
) VALUES (
|
||||
#{optsn}, #{agentId}, #{name}, #{apiKey}, #{introduce}, #{category}
|
||||
<if test="outer !=null">, #{outer}</if>
|
||||
<if test="description != null">, #{description}</if>
|
||||
<if test="link != null">, #{link}</if>
|
||||
<if test="promptCards != null">, #{promptCards, typeHandler=org.xyzh.ai.handler.PromptCardsTypeHandler}</if>
|
||||
<if test="creator != null">, #{creator}</if>
|
||||
)
|
||||
</insert>
|
||||
|
||||
<update id="updateAgent" parameterType="org.xyzh.api.ai.dto.TbAgent">
|
||||
UPDATE ai.tb_agent
|
||||
<set>
|
||||
<if test="name != null">name = #{name},</if>
|
||||
<if test="description != null">description = #{description},</if>
|
||||
<if test="link != null">link = #{link},</if>
|
||||
<if test="apiKey != null">api_key = #{apiKey},</if>
|
||||
<if test="outer != null">outer = #{outer},</if>
|
||||
<if test="introduce != null">introduce = #{introduce},</if>
|
||||
<if test="promptCards != null">prompt_cards = #{promptCards, typeHandler=org.xyzh.ai.handler.PromptCardsTypeHandler},</if>
|
||||
<if test="category != null">category = #{category},</if>
|
||||
<if test="updater != null">updater = #{updater},</if>
|
||||
update_time = now()
|
||||
</set>
|
||||
WHERE agent_id = #{agentId} AND deleted = false
|
||||
</update>
|
||||
|
||||
<update id="deleteAgent" parameterType="org.xyzh.api.ai.dto.TbAgent">
|
||||
UPDATE ai.tb_agent
|
||||
SET deleted = true,
|
||||
delete_time = now(),
|
||||
updater = #{updater}
|
||||
WHERE agent_id = #{agentId} AND deleted = false
|
||||
</update>
|
||||
|
||||
<select id="selectAgentById" resultMap="BaseResultMap">
|
||||
SELECT <include refid="Base_Column_List"/>
|
||||
FROM ai.tb_agent
|
||||
WHERE agent_id = #{agentId} AND deleted = false
|
||||
</select>
|
||||
|
||||
<select id="selectAgentByApiKey" resultMap="BaseResultMap">
|
||||
SELECT <include refid="Base_Column_List"/>
|
||||
FROM ai.tb_agent
|
||||
WHERE api_key = #{apiKey} AND deleted = false
|
||||
</select>
|
||||
|
||||
<select id="selectAgentList" resultMap="BaseResultMap">
|
||||
SELECT <include refid="Base_Column_List"/>
|
||||
FROM ai.tb_agent
|
||||
WHERE deleted = false
|
||||
<if test="filter != null">
|
||||
<if test="filter.name != null and filter.name != ''">
|
||||
AND name LIKE CONCAT('%', #{filter.name}, '%')
|
||||
</if>
|
||||
<if test="filter.category != null and filter.category != ''">
|
||||
AND category = #{filter.category}
|
||||
</if>
|
||||
<if test="filter.creator != null and filter.creator != ''">
|
||||
AND creator = #{filter.creator}
|
||||
</if>
|
||||
</if>
|
||||
ORDER BY create_time DESC
|
||||
</select>
|
||||
|
||||
<select id="selectAgentPage" resultMap="BaseResultMap">
|
||||
SELECT <include refid="Base_Column_List"/>
|
||||
FROM ai.tb_agent
|
||||
WHERE deleted = false
|
||||
<if test="filter != null">
|
||||
<if test="filter.name != null and filter.name != ''">
|
||||
AND name LIKE CONCAT('%', #{filter.name}, '%')
|
||||
</if>
|
||||
<if test="filter.category != null and filter.category != ''">
|
||||
AND category = #{filter.category}
|
||||
</if>
|
||||
<if test="filter.creator != null and filter.creator != ''">
|
||||
AND creator = #{filter.creator}
|
||||
</if>
|
||||
</if>
|
||||
ORDER BY create_time DESC
|
||||
LIMIT #{pageParam.pageSize} OFFSET #{pageParam.offset}
|
||||
</select>
|
||||
|
||||
<select id="countAgents" resultType="long">
|
||||
SELECT COUNT(*)
|
||||
FROM ai.tb_agent
|
||||
WHERE deleted = false
|
||||
<if test="filter != null">
|
||||
<if test="filter.name != null and filter.name != ''">
|
||||
AND name LIKE CONCAT('%', #{filter.name}, '%')
|
||||
</if>
|
||||
<if test="filter.category != null and filter.category != ''">
|
||||
AND category = #{filter.category}
|
||||
</if>
|
||||
<if test="filter.creator != null and filter.creator != ''">
|
||||
AND creator = #{filter.creator}
|
||||
</if>
|
||||
</if>
|
||||
</select>
|
||||
|
||||
<select id="countByName" resultType="int">
|
||||
SELECT COUNT(*)
|
||||
FROM ai.tb_agent
|
||||
WHERE deleted = false AND name = #{name}
|
||||
<if test="excludeId != null and excludeId != ''">
|
||||
AND agent_id != #{excludeId}
|
||||
</if>
|
||||
</select>
|
||||
</mapper>
|
||||
103
urbanLifelineServ/ai/src/main/resources/mapper/TbChatMapper.xml
Normal file
103
urbanLifelineServ/ai/src/main/resources/mapper/TbChatMapper.xml
Normal file
@@ -0,0 +1,103 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
|
||||
<mapper namespace="org.xyzh.ai.mapper.TbChatMapper">
|
||||
|
||||
<resultMap id="BaseResultMap" type="org.xyzh.api.ai.dto.TbChat">
|
||||
<result column="optsn" property="optsn" jdbcType="VARCHAR"/>
|
||||
<result column="chat_id" property="chatId" jdbcType="VARCHAR"/>
|
||||
<result column="agent_id" property="agentId" jdbcType="VARCHAR"/>
|
||||
<result column="user_id" property="userId" jdbcType="VARCHAR"/>
|
||||
<result column="user_type" property="userType" jdbcType="BOOLEAN"/>
|
||||
<result column="title" property="title" jdbcType="VARCHAR"/>
|
||||
<result column="create_time" property="createTime" jdbcType="TIMESTAMP"/>
|
||||
<result column="update_time" property="updateTime" jdbcType="TIMESTAMP"/>
|
||||
<result column="delete_time" property="deleteTime" jdbcType="TIMESTAMP"/>
|
||||
<result column="deleted" property="deleted" jdbcType="BOOLEAN"/>
|
||||
</resultMap>
|
||||
|
||||
<sql id="Base_Column_List">
|
||||
optsn, chat_id, agent_id, user_id, user_type, title,
|
||||
create_time, update_time, delete_time, deleted
|
||||
</sql>
|
||||
|
||||
<insert id="insertChat" parameterType="org.xyzh.api.ai.dto.TbChat">
|
||||
INSERT INTO ai.tb_chat (
|
||||
optsn, chat_id, agent_id, user_id, title
|
||||
<if test="userType != null">, user_type</if>
|
||||
) VALUES (
|
||||
#{optsn}, #{chatId}, #{agentId}, #{userId}, #{title}
|
||||
<if test="userType != null">, #{userType}</if>
|
||||
)
|
||||
</insert>
|
||||
|
||||
<update id="updateChat" parameterType="org.xyzh.api.ai.dto.TbChat">
|
||||
UPDATE ai.tb_chat
|
||||
<set>
|
||||
<if test="title != null">title = #{title},</if>
|
||||
update_time = now()
|
||||
</set>
|
||||
WHERE chat_id = #{chatId} AND deleted = false
|
||||
</update>
|
||||
|
||||
<update id="deleteChat" parameterType="org.xyzh.api.ai.dto.TbChat">
|
||||
UPDATE ai.tb_chat
|
||||
SET deleted = true,
|
||||
delete_time = now()
|
||||
WHERE chat_id = #{chatId} AND deleted = false
|
||||
</update>
|
||||
|
||||
<select id="selectChatById" resultMap="BaseResultMap">
|
||||
SELECT <include refid="Base_Column_List"/>
|
||||
FROM ai.tb_chat
|
||||
WHERE chat_id = #{chatId} AND deleted = false
|
||||
</select>
|
||||
|
||||
<select id="selectChatList" resultMap="BaseResultMap">
|
||||
SELECT <include refid="Base_Column_List"/>
|
||||
FROM ai.tb_chat
|
||||
WHERE deleted = false
|
||||
<if test="agentId != null and agentId != ''">
|
||||
AND agent_id = #{agentId}
|
||||
</if>
|
||||
<if test="userId != null and userId != ''">
|
||||
AND user_id = #{userId}
|
||||
</if>
|
||||
ORDER BY update_time DESC, create_time DESC
|
||||
</select>
|
||||
|
||||
<select id="selectChatPage" resultMap="BaseResultMap">
|
||||
SELECT <include refid="Base_Column_List"/>
|
||||
FROM ai.tb_chat
|
||||
WHERE deleted = false
|
||||
<if test="filter != null">
|
||||
<if test="filter.agentId != null and filter.agentId != ''">
|
||||
AND agent_id = #{filter.agentId}
|
||||
</if>
|
||||
<if test="filter.userId != null and filter.userId != ''">
|
||||
AND user_id = #{filter.userId}
|
||||
</if>
|
||||
<if test="filter.title != null and filter.title != ''">
|
||||
AND title LIKE CONCAT('%', #{filter.title}, '%')
|
||||
</if>
|
||||
</if>
|
||||
ORDER BY update_time DESC, create_time DESC
|
||||
LIMIT #{pageParam.pageSize} OFFSET #{pageParam.offset}
|
||||
</select>
|
||||
|
||||
<select id="countChats" resultType="long">
|
||||
SELECT COUNT(*)
|
||||
FROM ai.tb_chat
|
||||
WHERE deleted = false
|
||||
<if test="filter != null">
|
||||
<if test="filter.agentId != null and filter.agentId != ''">
|
||||
AND agent_id = #{filter.agentId}
|
||||
</if>
|
||||
<if test="filter.userId != null and filter.userId != ''">
|
||||
AND user_id = #{filter.userId}
|
||||
</if>
|
||||
<if test="filter.title != null and filter.title != ''">
|
||||
AND title LIKE CONCAT('%', #{filter.title}, '%')
|
||||
</if>
|
||||
</if>
|
||||
</select>
|
||||
</mapper>
|
||||
@@ -0,0 +1,89 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
|
||||
<mapper namespace="org.xyzh.ai.mapper.TbChatMessageMapper">
|
||||
|
||||
<resultMap id="BaseResultMap" type="org.xyzh.api.ai.dto.TbChatMessage">
|
||||
<result column="optsn" property="optsn" jdbcType="VARCHAR"/>
|
||||
<result column="message_id" property="messageId" jdbcType="VARCHAR"/>
|
||||
<result column="dify_message_id" property="difyMessageId" jdbcType="VARCHAR"/>
|
||||
<result column="chat_id" property="chatId" jdbcType="VARCHAR"/>
|
||||
<result column="role" property="role" jdbcType="VARCHAR"/>
|
||||
<result column="content" property="content" jdbcType="VARCHAR"/>
|
||||
<result column="files" property="files" jdbcType="ARRAY" typeHandler="org.xyzh.common.utils.json.StringArrayTypeHandler"/>
|
||||
<result column="comment" property="comment" jdbcType="VARCHAR"/>
|
||||
<result column="create_time" property="createTime" jdbcType="TIMESTAMP"/>
|
||||
<result column="update_time" property="updateTime" jdbcType="TIMESTAMP"/>
|
||||
<result column="delete_time" property="deleteTime" jdbcType="TIMESTAMP"/>
|
||||
<result column="deleted" property="deleted" jdbcType="BOOLEAN"/>
|
||||
</resultMap>
|
||||
|
||||
<sql id="Base_Column_List">
|
||||
optsn, message_id, dify_message_id, chat_id, role, content, files, comment,
|
||||
create_time, update_time, delete_time, deleted
|
||||
</sql>
|
||||
|
||||
<insert id="insertChatMessage" parameterType="org.xyzh.api.ai.dto.TbChatMessage">
|
||||
INSERT INTO ai.tb_chat_message (
|
||||
optsn, message_id, chat_id, role, content
|
||||
<if test="difyMessageId != null">, dify_message_id</if>
|
||||
<if test="files != null">, files</if>
|
||||
<if test="comment != null">, comment</if>
|
||||
) VALUES (
|
||||
#{optsn}, #{messageId}, #{chatId}, #{role}, #{content}
|
||||
<if test="difyMessageId != null">, #{difyMessageId}</if>
|
||||
<if test="files != null">, #{files, typeHandler=org.xyzh.common.utils.json.StringArrayTypeHandler}</if>
|
||||
<if test="comment != null">, #{comment}</if>
|
||||
)
|
||||
</insert>
|
||||
|
||||
<update id="updateChatMessage" parameterType="org.xyzh.api.ai.dto.TbChatMessage">
|
||||
UPDATE ai.tb_chat_message
|
||||
<set>
|
||||
<if test="content != null">content = #{content},</if>
|
||||
<if test="comment != null">comment = #{comment},</if>
|
||||
update_time = now()
|
||||
</set>
|
||||
WHERE message_id = #{messageId} AND deleted = false
|
||||
</update>
|
||||
|
||||
<update id="deleteChatMessage" parameterType="org.xyzh.api.ai.dto.TbChatMessage">
|
||||
UPDATE ai.tb_chat_message
|
||||
SET deleted = true,
|
||||
delete_time = now()
|
||||
WHERE message_id = #{messageId} AND deleted = false
|
||||
</update>
|
||||
|
||||
<select id="selectMessageById" resultMap="BaseResultMap">
|
||||
SELECT <include refid="Base_Column_List"/>
|
||||
FROM ai.tb_chat_message
|
||||
WHERE message_id = #{messageId} AND deleted = false
|
||||
</select>
|
||||
|
||||
<select id="selectMessagesByChatId" resultMap="BaseResultMap">
|
||||
SELECT <include refid="Base_Column_List"/>
|
||||
FROM ai.tb_chat_message
|
||||
WHERE chat_id = #{chatId} AND deleted = false
|
||||
ORDER BY create_time ASC
|
||||
</select>
|
||||
|
||||
<select id="selectMessagePage" resultMap="BaseResultMap">
|
||||
SELECT <include refid="Base_Column_List"/>
|
||||
FROM ai.tb_chat_message
|
||||
WHERE chat_id = #{chatId} AND deleted = false
|
||||
ORDER BY create_time ASC
|
||||
LIMIT #{pageParam.pageSize} OFFSET #{pageParam.offset}
|
||||
</select>
|
||||
|
||||
<select id="countMessages" resultType="long">
|
||||
SELECT COUNT(*)
|
||||
FROM ai.tb_chat_message
|
||||
WHERE chat_id = #{chatId} AND deleted = false
|
||||
</select>
|
||||
|
||||
<update id="deleteMessagesByChatId">
|
||||
UPDATE ai.tb_chat_message
|
||||
SET deleted = true,
|
||||
delete_time = now()
|
||||
WHERE chat_id = #{chatId} AND deleted = false
|
||||
</update>
|
||||
</mapper>
|
||||
@@ -0,0 +1,96 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
|
||||
<mapper namespace="org.xyzh.ai.mapper.TbKnowledgeFileMapper">
|
||||
|
||||
<resultMap id="BaseResultMap" type="org.xyzh.api.ai.dto.TbKnowledgeFile">
|
||||
<result column="optsn" property="optsn" jdbcType="VARCHAR"/>
|
||||
<result column="knowledge_id" property="knowledgeId" jdbcType="VARCHAR"/>
|
||||
<result column="file_root_id" property="fileRootId" jdbcType="VARCHAR"/>
|
||||
<result column="file_id" property="fileId" jdbcType="VARCHAR"/>
|
||||
<result column="dify_file_id" property="difyFileId" jdbcType="VARCHAR"/>
|
||||
<result column="version" property="version" jdbcType="INTEGER"/>
|
||||
<result column="create_time" property="createTime" jdbcType="TIMESTAMP"/>
|
||||
<result column="update_time" property="updateTime" jdbcType="TIMESTAMP"/>
|
||||
<result column="delete_time" property="deleteTime" jdbcType="TIMESTAMP"/>
|
||||
<result column="deleted" property="deleted" jdbcType="BOOLEAN"/>
|
||||
</resultMap>
|
||||
|
||||
<sql id="Base_Column_List">
|
||||
optsn, knowledge_id, file_root_id, file_id, dify_file_id, version,
|
||||
create_time, update_time, delete_time, deleted
|
||||
</sql>
|
||||
|
||||
<insert id="insertKnowledgeFile" parameterType="org.xyzh.api.ai.dto.TbKnowledgeFile">
|
||||
INSERT INTO ai.tb_knowledge_file (
|
||||
optsn, knowledge_id, file_root_id, file_id, dify_file_id
|
||||
<if test="version != null">, version</if>
|
||||
) VALUES (
|
||||
#{optsn}, #{knowledgeId}, #{fileRootId}, #{fileId}, #{difyFileId}
|
||||
<if test="version != null">, #{version}</if>
|
||||
)
|
||||
</insert>
|
||||
|
||||
<update id="updateKnowledgeFile" parameterType="org.xyzh.api.ai.dto.TbKnowledgeFile">
|
||||
UPDATE ai.tb_knowledge_file
|
||||
<set>
|
||||
<if test="difyFileId != null">dify_file_id = #{difyFileId},</if>
|
||||
<if test="version != null">version = #{version},</if>
|
||||
update_time = now()
|
||||
</set>
|
||||
WHERE knowledge_id = #{knowledgeId} AND file_id = #{fileId} AND deleted = false
|
||||
</update>
|
||||
|
||||
<update id="deleteKnowledgeFile" parameterType="org.xyzh.api.ai.dto.TbKnowledgeFile">
|
||||
UPDATE ai.tb_knowledge_file
|
||||
SET deleted = true,
|
||||
delete_time = now()
|
||||
WHERE knowledge_id = #{knowledgeId} AND file_id = #{fileId} AND deleted = false
|
||||
</update>
|
||||
|
||||
<select id="selectKnowledgeFile" resultMap="BaseResultMap">
|
||||
SELECT <include refid="Base_Column_List"/>
|
||||
FROM ai.tb_knowledge_file
|
||||
WHERE knowledge_id = #{knowledgeId} AND file_id = #{fileId} AND deleted = false
|
||||
</select>
|
||||
|
||||
<select id="selectFilesByKnowledgeId" resultMap="BaseResultMap">
|
||||
SELECT <include refid="Base_Column_List"/>
|
||||
FROM ai.tb_knowledge_file
|
||||
WHERE knowledge_id = #{knowledgeId} AND deleted = false
|
||||
ORDER BY create_time DESC
|
||||
</select>
|
||||
|
||||
<select id="selectFileVersions" resultMap="BaseResultMap">
|
||||
SELECT <include refid="Base_Column_List"/>
|
||||
FROM ai.tb_knowledge_file
|
||||
WHERE file_root_id = #{fileRootId} AND deleted = false
|
||||
ORDER BY version DESC
|
||||
</select>
|
||||
|
||||
<select id="selectFilePage" resultMap="BaseResultMap">
|
||||
SELECT <include refid="Base_Column_List"/>
|
||||
FROM ai.tb_knowledge_file
|
||||
WHERE knowledge_id = #{knowledgeId} AND deleted = false
|
||||
ORDER BY create_time DESC
|
||||
LIMIT #{pageParam.pageSize} OFFSET #{pageParam.offset}
|
||||
</select>
|
||||
|
||||
<select id="countFiles" resultType="long">
|
||||
SELECT COUNT(*)
|
||||
FROM ai.tb_knowledge_file
|
||||
WHERE knowledge_id = #{knowledgeId} AND deleted = false
|
||||
</select>
|
||||
|
||||
<update id="deleteFilesByRootId">
|
||||
UPDATE ai.tb_knowledge_file
|
||||
SET deleted = true,
|
||||
delete_time = now()
|
||||
WHERE file_root_id = #{fileRootId} AND deleted = false
|
||||
</update>
|
||||
|
||||
<select id="selectLatestVersion" resultType="java.lang.Integer">
|
||||
SELECT COALESCE(MAX(version), 0)
|
||||
FROM ai.tb_knowledge_file
|
||||
WHERE knowledge_id = #{knowledgeId} AND file_root_id = #{fileRootId}
|
||||
</select>
|
||||
</mapper>
|
||||
@@ -0,0 +1,198 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
|
||||
<mapper namespace="org.xyzh.ai.mapper.TbKnowledgeMapper">
|
||||
|
||||
<resultMap id="BaseResultMap" type="org.xyzh.api.ai.dto.TbKnowledge">
|
||||
<result column="optsn" property="optsn" jdbcType="VARCHAR"/>
|
||||
<result column="knowledge_id" property="knowledgeId" jdbcType="VARCHAR"/>
|
||||
<result column="title" property="title" jdbcType="VARCHAR"/>
|
||||
<result column="avatar" property="avatar" jdbcType="VARCHAR"/>
|
||||
<result column="description" property="description" jdbcType="VARCHAR"/>
|
||||
<result column="dify_dataset_id" property="difyDatasetId" jdbcType="VARCHAR"/>
|
||||
<result column="dify_indexing_technique" property="difyIndexingTechnique" jdbcType="VARCHAR"/>
|
||||
<result column="embedding_model" property="embeddingModel" jdbcType="VARCHAR"/>
|
||||
<result column="embedding_model_provider" property="embeddingModelProvider" jdbcType="VARCHAR"/>
|
||||
<result column="rerank_model" property="rerankModel" jdbcType="VARCHAR"/>
|
||||
<result column="rerank_model_provider" property="rerankModelProvider" jdbcType="VARCHAR"/>
|
||||
<result column="reranking_enable" property="rerankingEnable" jdbcType="INTEGER"/>
|
||||
<result column="retrieval_top_k" property="retrievalTopK" jdbcType="INTEGER"/>
|
||||
<result column="retrieval_score_threshold" property="retrievalScoreThreshold" jdbcType="DECIMAL"/>
|
||||
<result column="document_count" property="documentCount" jdbcType="INTEGER"/>
|
||||
<result column="total_chunks" property="totalChunks" jdbcType="INTEGER"/>
|
||||
<result column="service" property="service" jdbcType="VARCHAR"/>
|
||||
<result column="project_id" property="projectId" jdbcType="VARCHAR"/>
|
||||
<result column="category" property="category" jdbcType="VARCHAR"/>
|
||||
<result column="creator" property="creator" jdbcType="VARCHAR"/>
|
||||
<result column="dept_path" property="deptPath" jdbcType="VARCHAR"/>
|
||||
<result column="updater" property="updater" jdbcType="VARCHAR"/>
|
||||
<result column="create_time" property="createTime" jdbcType="TIMESTAMP"/>
|
||||
<result column="update_time" property="updateTime" jdbcType="TIMESTAMP"/>
|
||||
<result column="delete_time" property="deleteTime" jdbcType="TIMESTAMP"/>
|
||||
<result column="deleted" property="deleted" jdbcType="BOOLEAN"/>
|
||||
</resultMap>
|
||||
|
||||
<sql id="Base_Column_List">
|
||||
optsn, knowledge_id, title, avatar, description, dify_dataset_id,
|
||||
dify_indexing_technique, embedding_model, embedding_model_provider,
|
||||
rerank_model, rerank_model_provider, reranking_enable,
|
||||
retrieval_top_k, retrieval_score_threshold, document_count, total_chunks,
|
||||
service, project_id, category, creator, dept_path, updater,
|
||||
create_time, update_time, delete_time, deleted
|
||||
</sql>
|
||||
|
||||
<insert id="insertKnowledge" parameterType="org.xyzh.api.ai.dto.TbKnowledge">
|
||||
INSERT INTO ai.tb_knowledge (
|
||||
optsn, knowledge_id, title, creator
|
||||
<if test="avatar != null">, avatar</if>
|
||||
<if test="description != null">, description</if>
|
||||
<if test="difyDatasetId != null">, dify_dataset_id</if>
|
||||
<if test="difyIndexingTechnique != null">, dify_indexing_technique</if>
|
||||
<if test="embeddingModel != null">, embedding_model</if>
|
||||
<if test="embeddingModelProvider != null">, embedding_model_provider</if>
|
||||
<if test="rerankModel != null">, rerank_model</if>
|
||||
<if test="rerankModelProvider != null">, rerank_model_provider</if>
|
||||
<if test="rerankingEnable != null">, reranking_enable</if>
|
||||
<if test="retrievalTopK != null">, retrieval_top_k</if>
|
||||
<if test="retrievalScoreThreshold != null">, retrieval_score_threshold</if>
|
||||
<if test="service != null">, service</if>
|
||||
<if test="projectId != null">, project_id</if>
|
||||
<if test="category != null">, category</if>
|
||||
<if test="deptPath != null">, dept_path</if>
|
||||
) VALUES (
|
||||
#{optsn}, #{knowledgeId}, #{title}, #{creator}
|
||||
<if test="avatar != null">, #{avatar}</if>
|
||||
<if test="description != null">, #{description}</if>
|
||||
<if test="difyDatasetId != null">, #{difyDatasetId}</if>
|
||||
<if test="difyIndexingTechnique != null">, #{difyIndexingTechnique}</if>
|
||||
<if test="embeddingModel != null">, #{embeddingModel}</if>
|
||||
<if test="embeddingModelProvider != null">, #{embeddingModelProvider}</if>
|
||||
<if test="rerankModel != null">, #{rerankModel}</if>
|
||||
<if test="rerankModelProvider != null">, #{rerankModelProvider}</if>
|
||||
<if test="rerankingEnable != null">, #{rerankingEnable}</if>
|
||||
<if test="retrievalTopK != null">, #{retrievalTopK}</if>
|
||||
<if test="retrievalScoreThreshold != null">, #{retrievalScoreThreshold}</if>
|
||||
<if test="service != null">, #{service}</if>
|
||||
<if test="projectId != null">, #{projectId}</if>
|
||||
<if test="category != null">, #{category}</if>
|
||||
<if test="deptPath != null">, #{deptPath}</if>
|
||||
)
|
||||
</insert>
|
||||
|
||||
<update id="updateKnowledge" parameterType="org.xyzh.api.ai.dto.TbKnowledge">
|
||||
UPDATE ai.tb_knowledge
|
||||
<set>
|
||||
<if test="title != null">title = #{title},</if>
|
||||
<if test="avatar != null">avatar = #{avatar},</if>
|
||||
<if test="description != null">description = #{description},</if>
|
||||
<if test="difyDatasetId != null">dify_dataset_id = #{difyDatasetId},</if>
|
||||
<if test="difyIndexingTechnique != null">dify_indexing_technique = #{difyIndexingTechnique},</if>
|
||||
<if test="embeddingModel != null">embedding_model = #{embeddingModel},</if>
|
||||
<if test="embeddingModelProvider != null">embedding_model_provider = #{embeddingModelProvider},</if>
|
||||
<if test="rerankModel != null">rerank_model = #{rerankModel},</if>
|
||||
<if test="rerankModelProvider != null">rerank_model_provider = #{rerankModelProvider},</if>
|
||||
<if test="rerankingEnable != null">reranking_enable = #{rerankingEnable},</if>
|
||||
<if test="retrievalTopK != null">retrieval_top_k = #{retrievalTopK},</if>
|
||||
<if test="retrievalScoreThreshold != null">retrieval_score_threshold = #{retrievalScoreThreshold},</if>
|
||||
<if test="documentCount != null">document_count = #{documentCount},</if>
|
||||
<if test="totalChunks != null">total_chunks = #{totalChunks},</if>
|
||||
<if test="service != null">service = #{service},</if>
|
||||
<if test="projectId != null">project_id = #{projectId},</if>
|
||||
<if test="category != null">category = #{category},</if>
|
||||
<if test="updater != null">updater = #{updater},</if>
|
||||
update_time = now()
|
||||
</set>
|
||||
WHERE knowledge_id = #{knowledgeId} AND deleted = false
|
||||
</update>
|
||||
|
||||
<update id="deleteKnowledge" parameterType="org.xyzh.api.ai.dto.TbKnowledge">
|
||||
UPDATE ai.tb_knowledge
|
||||
SET deleted = true,
|
||||
delete_time = now(),
|
||||
updater = #{updater}
|
||||
WHERE knowledge_id = #{knowledgeId} AND deleted = false
|
||||
</update>
|
||||
|
||||
<select id="selectKnowledgeById" resultMap="BaseResultMap">
|
||||
SELECT <include refid="Base_Column_List"/>
|
||||
FROM ai.tb_knowledge
|
||||
WHERE knowledge_id = #{knowledgeId} AND deleted = false
|
||||
</select>
|
||||
|
||||
<select id="selectKnowledgeByDifyId" resultMap="BaseResultMap">
|
||||
SELECT <include refid="Base_Column_List"/>
|
||||
FROM ai.tb_knowledge
|
||||
WHERE dify_dataset_id = #{difyDatasetId} AND deleted = false
|
||||
</select>
|
||||
|
||||
<select id="selectKnowledgeList" resultMap="BaseResultMap">
|
||||
SELECT <include refid="Base_Column_List"/>
|
||||
FROM ai.tb_knowledge
|
||||
WHERE deleted = false
|
||||
<if test="filter != null">
|
||||
<if test="filter.title != null and filter.title != ''">
|
||||
AND title LIKE CONCAT('%', #{filter.title}, '%')
|
||||
</if>
|
||||
<if test="filter.service != null and filter.service != ''">
|
||||
AND service = #{filter.service}
|
||||
</if>
|
||||
<if test="filter.projectId != null and filter.projectId != ''">
|
||||
AND project_id = #{filter.projectId}
|
||||
</if>
|
||||
<if test="filter.category != null and filter.category != ''">
|
||||
AND category = #{filter.category}
|
||||
</if>
|
||||
<if test="filter.creator != null and filter.creator != ''">
|
||||
AND creator = #{filter.creator}
|
||||
</if>
|
||||
</if>
|
||||
ORDER BY create_time DESC
|
||||
</select>
|
||||
|
||||
<select id="selectKnowledgePage" resultMap="BaseResultMap">
|
||||
SELECT <include refid="Base_Column_List"/>
|
||||
FROM ai.tb_knowledge
|
||||
WHERE deleted = false
|
||||
<if test="filter != null">
|
||||
<if test="filter.title != null and filter.title != ''">
|
||||
AND title LIKE CONCAT('%', #{filter.title}, '%')
|
||||
</if>
|
||||
<if test="filter.service != null and filter.service != ''">
|
||||
AND service = #{filter.service}
|
||||
</if>
|
||||
<if test="filter.projectId != null and filter.projectId != ''">
|
||||
AND project_id = #{filter.projectId}
|
||||
</if>
|
||||
<if test="filter.category != null and filter.category != ''">
|
||||
AND category = #{filter.category}
|
||||
</if>
|
||||
<if test="filter.creator != null and filter.creator != ''">
|
||||
AND creator = #{filter.creator}
|
||||
</if>
|
||||
</if>
|
||||
ORDER BY create_time DESC
|
||||
LIMIT #{pageParam.pageSize} OFFSET #{pageParam.offset}
|
||||
</select>
|
||||
|
||||
<select id="countKnowledges" resultType="long">
|
||||
SELECT COUNT(*)
|
||||
FROM ai.tb_knowledge
|
||||
WHERE deleted = false
|
||||
<if test="filter != null">
|
||||
<if test="filter.title != null and filter.title != ''">
|
||||
AND title LIKE CONCAT('%', #{filter.title}, '%')
|
||||
</if>
|
||||
<if test="filter.service != null and filter.service != ''">
|
||||
AND service = #{filter.service}
|
||||
</if>
|
||||
<if test="filter.projectId != null and filter.projectId != ''">
|
||||
AND project_id = #{filter.projectId}
|
||||
</if>
|
||||
<if test="filter.category != null and filter.category != ''">
|
||||
AND category = #{filter.category}
|
||||
</if>
|
||||
<if test="filter.creator != null and filter.creator != ''">
|
||||
AND creator = #{filter.creator}
|
||||
</if>
|
||||
</if>
|
||||
</select>
|
||||
</mapper>
|
||||
Reference in New Issue
Block a user