dify历史记录

This commit is contained in:
2026-02-25 18:21:25 +08:00
parent 0efca50be3
commit 2e93211697
13 changed files with 1328 additions and 74 deletions

View File

@@ -2,6 +2,8 @@ package cn.iocoder.yudao.module.tik.dify.client;
import cn.iocoder.yudao.module.tik.dify.config.DifyProperties;
import cn.iocoder.yudao.module.tik.dify.vo.DifyChatRespVO;
import cn.iocoder.yudao.module.tik.dify.vo.DifyConversationListRespVO;
import cn.iocoder.yudao.module.tik.dify.vo.DifyMessageListRespVO;
import com.fasterxml.jackson.core.type.TypeReference;
import com.fasterxml.jackson.databind.ObjectMapper;
import jakarta.annotation.Resource;
@@ -38,9 +40,10 @@ public class DifyClient {
* @param content 用户输入
* @param systemPrompt 系统提示词
* @param conversationId 会话ID可选
* @param userId 用户标识(固定)
* @return 流式响应
*/
public Flux<DifyChatRespVO> chatStream(String apiKey, String content, String systemPrompt, String conversationId) {
public Flux<DifyChatRespVO> chatStream(String apiKey, String content, String systemPrompt, String conversationId, String userId) {
String apiUrl = difyProperties.getApiUrl() + "/v1/chat-messages";
// 构建请求体 - 按照 Dify API 格式
@@ -52,7 +55,7 @@ public class DifyClient {
requestBody.put("query", content);
requestBody.put("response_mode", "streaming");
requestBody.put("conversation_id", conversationId != null ? conversationId : "");
requestBody.put("user", "user-" + System.currentTimeMillis());
requestBody.put("user", userId);
AtomicReference<String> responseConversationId = new AtomicReference<>(conversationId);
StringBuilder fullContent = new StringBuilder();
@@ -91,9 +94,10 @@ public class DifyClient {
* @param inputs 自定义输入参数
* @param content 用户输入
* @param conversationId 会话ID可选
* @param userId 用户标识(固定)
* @return 流式响应
*/
public Flux<DifyChatRespVO> chatStreamWithInputs(String apiKey, Map<String, Object> inputs, String content, String conversationId) {
public Flux<DifyChatRespVO> chatStreamWithInputs(String apiKey, Map<String, Object> inputs, String content, String conversationId, String userId) {
String apiUrl = difyProperties.getApiUrl() + "/v1/chat-messages";
Map<String, Object> requestBody = new HashMap<>();
@@ -101,7 +105,7 @@ public class DifyClient {
requestBody.put("query", content);
requestBody.put("response_mode", "streaming");
requestBody.put("conversation_id", conversationId != null ? conversationId : "");
requestBody.put("user", "user-" + System.currentTimeMillis());
requestBody.put("user", userId);
// 调试日志:打印完整请求体
try {
@@ -195,4 +199,77 @@ public class DifyClient {
}
}
/**
* 获取会话列表
*
* @param apiKey Dify API Key
* @param user 用户标识
* @param lastId 上一页最后一条记录ID用于分页
* @param limit 返回条数
* @return 会话列表
*/
public DifyConversationListRespVO getConversations(String apiKey, String user, String lastId, Integer limit) {
StringBuilder params = new StringBuilder("?user=").append(user);
if (lastId != null && !lastId.isEmpty()) {
params.append("&last_id=").append(lastId);
}
if (limit != null) {
params.append("&limit=").append(limit);
}
String url = difyProperties.getApiUrl() + "/v1/conversations" + params;
try {
String response = executeGetRequest(url, apiKey);
log.info("[getConversations] 获取会话列表成功user: {}", user);
return objectMapper.readValue(response, DifyConversationListRespVO.class);
} catch (Exception e) {
log.error("[getConversations] 获取会话列表失败user: {}", user, e);
throw new RuntimeException("获取会话列表失败: " + e.getMessage());
}
}
/**
* 获取会话历史消息
*
* @param apiKey Dify API Key
* @param conversationId 会话ID
* @param user 用户标识
* @param firstId 当前页第一条记录ID用于分页
* @param limit 返回条数
* @return 消息列表
*/
public DifyMessageListRespVO getMessages(String apiKey, String conversationId, String user, String firstId, Integer limit) {
StringBuilder params = new StringBuilder("?conversation_id=").append(conversationId).append("&user=").append(user);
if (firstId != null && !firstId.isEmpty()) {
params.append("&first_id=").append(firstId);
}
if (limit != null) {
params.append("&limit=").append(limit);
}
String url = difyProperties.getApiUrl() + "/v1/messages" + params;
try {
String response = executeGetRequest(url, apiKey);
log.info("[getMessages] 获取会话消息成功conversationId: {}, user: {}", conversationId, user);
return objectMapper.readValue(response, DifyMessageListRespVO.class);
} catch (Exception e) {
log.error("[getMessages] 获取会话消息失败conversationId: {}, user: {}", conversationId, user, e);
throw new RuntimeException("获取会话消息失败: " + e.getMessage());
}
}
/**
* 执行 GET 请求
*/
private String executeGetRequest(String url, String apiKey) {
return WebClient.builder()
.baseUrl(url)
.defaultHeader("Authorization", "Bearer " + apiKey)
.build()
.get()
.retrieve()
.bodyToMono(String.class)
.block();
}
}

View File

@@ -5,15 +5,20 @@ import cn.iocoder.yudao.framework.security.core.util.SecurityFrameworkUtils;
import cn.iocoder.yudao.module.tik.dify.service.DifyService;
import cn.iocoder.yudao.module.tik.dify.vo.DifyChatReqVO;
import cn.iocoder.yudao.module.tik.dify.vo.DifyChatRespVO;
import cn.iocoder.yudao.module.tik.dify.vo.DifyConversationListRespVO;
import cn.iocoder.yudao.module.tik.dify.vo.DifyMessageListRespVO;
import cn.iocoder.yudao.module.tik.dify.vo.ForecastRewriteReqVO;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.Parameter;
import io.swagger.v3.oas.annotations.tags.Tag;
import jakarta.annotation.Resource;
import jakarta.validation.Valid;
import org.springframework.http.MediaType;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;
import reactor.core.publisher.Flux;
@@ -27,27 +32,54 @@ import reactor.core.publisher.Flux;
@RequestMapping("/api/tik/dify")
public class AppDifyController {
private static final String DEFAULT_USER_ID = "1";
@Resource
private DifyService difyService;
@PostMapping(value = "/chat/stream", produces = MediaType.TEXT_EVENT_STREAM_VALUE)
@Operation(summary = "流式聊天")
public Flux<CommonResult<DifyChatRespVO>> chatStream(@Valid @RequestBody DifyChatReqVO reqVO) {
Long loginUserId = SecurityFrameworkUtils.getLoginUserId();
String userId = loginUserId != null ? loginUserId.toString() : "1"; // 默认用户ID
return difyService.chatStream(reqVO, userId)
return difyService.chatStream(reqVO, getCurrentUserId())
.map(CommonResult::success);
}
@PostMapping(value = "/forecast/rewrite", produces = MediaType.TEXT_EVENT_STREAM_VALUE)
@Operation(summary = "Forecast 文案改写(流式)")
public Flux<CommonResult<DifyChatRespVO>> rewriteStream(@Valid @RequestBody ForecastRewriteReqVO reqVO) {
Long loginUserId = SecurityFrameworkUtils.getLoginUserId();
String userId = loginUserId != null ? loginUserId.toString() : "1"; // 默认用户ID
return difyService.rewriteStream(reqVO, userId)
return difyService.rewriteStream(reqVO, getCurrentUserId())
.map(CommonResult::success);
}
@GetMapping("/conversations")
@Operation(summary = "获取会话列表")
@Parameter(name = "agentId", description = "智能体ID", required = true)
@Parameter(name = "lastId", description = "上一页最后一条记录ID")
@Parameter(name = "limit", description = "返回条数默认20")
public CommonResult<DifyConversationListRespVO> getConversations(
@RequestParam("agentId") Long agentId,
@RequestParam(value = "lastId", required = false) String lastId,
@RequestParam(value = "limit", required = false) Integer limit) {
return CommonResult.success(difyService.getConversations(agentId, getCurrentUserId(), lastId, limit));
}
@GetMapping("/messages")
@Operation(summary = "获取会话历史消息")
@Parameter(name = "agentId", description = "智能体ID", required = true)
@Parameter(name = "conversationId", description = "会话ID", required = true)
@Parameter(name = "firstId", description = "当前页第一条记录ID")
@Parameter(name = "limit", description = "返回条数默认20")
public CommonResult<DifyMessageListRespVO> getMessages(
@RequestParam("agentId") Long agentId,
@RequestParam("conversationId") String conversationId,
@RequestParam(value = "firstId", required = false) String firstId,
@RequestParam(value = "limit", required = false) Integer limit) {
return CommonResult.success(difyService.getMessages(agentId, conversationId, getCurrentUserId(), firstId, limit));
}
private String getCurrentUserId() {
Long loginUserId = SecurityFrameworkUtils.getLoginUserId();
return loginUserId != null ? loginUserId.toString() : DEFAULT_USER_ID;
}
}

View File

@@ -2,6 +2,8 @@ package cn.iocoder.yudao.module.tik.dify.service;
import cn.iocoder.yudao.module.tik.dify.vo.DifyChatReqVO;
import cn.iocoder.yudao.module.tik.dify.vo.DifyChatRespVO;
import cn.iocoder.yudao.module.tik.dify.vo.DifyConversationListRespVO;
import cn.iocoder.yudao.module.tik.dify.vo.DifyMessageListRespVO;
import cn.iocoder.yudao.module.tik.dify.vo.ForecastRewriteReqVO;
import reactor.core.publisher.Flux;
@@ -30,4 +32,27 @@ public interface DifyService {
*/
Flux<DifyChatRespVO> rewriteStream(ForecastRewriteReqVO reqVO, String userId);
/**
* 获取会话列表
*
* @param agentId 智能体ID
* @param userId 用户ID
* @param lastId 上一页最后一条记录ID
* @param limit 返回条数
* @return 会话列表
*/
DifyConversationListRespVO getConversations(Long agentId, String userId, String lastId, Integer limit);
/**
* 获取会话历史消息
*
* @param agentId 智能体ID
* @param conversationId 会话ID
* @param userId 用户ID
* @param firstId 当前页第一条记录ID
* @param limit 返回条数
* @return 消息列表
*/
DifyMessageListRespVO getMessages(Long agentId, String conversationId, String userId, String firstId, Integer limit);
}

View File

@@ -3,6 +3,8 @@ package cn.iocoder.yudao.module.tik.dify.service;
import cn.iocoder.yudao.module.tik.dify.client.DifyClient;
import cn.iocoder.yudao.module.tik.dify.vo.DifyChatReqVO;
import cn.iocoder.yudao.module.tik.dify.vo.DifyChatRespVO;
import cn.iocoder.yudao.module.tik.dify.vo.DifyConversationListRespVO;
import cn.iocoder.yudao.module.tik.dify.vo.DifyMessageListRespVO;
import cn.iocoder.yudao.module.tik.dify.vo.ForecastRewriteReqVO;
import cn.iocoder.yudao.module.tik.enums.AiModelTypeEnum;
import cn.iocoder.yudao.module.tik.enums.AiPlatformEnum;
@@ -47,6 +49,8 @@ public class DifyServiceImpl implements DifyService {
AtomicLong pendingRecordId = new AtomicLong();
// 用于存储会话ID
AtomicReference<String> conversationIdRef = new AtomicReference<>(reqVO.getConversationId());
// Dify 用户标识(固定格式)
String difyUserId = "user-" + userId;
return Mono.fromCallable(() -> {
// 1. 获取智能体配置
@@ -84,7 +88,8 @@ public class DifyServiceImpl implements DifyService {
context.apiKey(),
reqVO.getContent(),
context.systemPrompt(),
reqVO.getConversationId()
reqVO.getConversationId(),
difyUserId
)
.doOnNext(resp -> {
if (resp.getConversationId() != null) {
@@ -142,6 +147,8 @@ public class DifyServiceImpl implements DifyService {
AtomicLong pendingRecordId = new AtomicLong();
// 用于存储会话ID
AtomicReference<String> conversationIdRef = new AtomicReference<>("");
// Dify 用户标识(固定格式)
String difyUserId = "user-" + userId;
return Mono.fromCallable(() -> {
// 1. 获取智能体配置(通过 agentId 获取 systemPrompt
@@ -199,7 +206,8 @@ public class DifyServiceImpl implements DifyService {
context.apiKey(),
context.inputs(),
reqVO.getUserText(),
null
null,
difyUserId
)
.doOnNext(resp -> {
if (resp.getConversationId() != null) {
@@ -260,4 +268,42 @@ public class DifyServiceImpl implements DifyService {
*/
private record ForecastRewriteContext(Map<String, Object> inputs, String apiKey, Integer consumePoints) {}
@Override
public DifyConversationListRespVO getConversations(Long agentId, String userId, String lastId, Integer limit) {
// 获取智能体配置
AiAgentDO agent = aiAgentService.getAiAgent(agentId);
if (agent == null) {
throw new RuntimeException("智能体不存在");
}
// 获取积分配置(使用标准模式的 API Key
AiModelConfigDO config = pointsService.getConfig(
AiPlatformEnum.DIFY.getPlatform(),
AiModelTypeEnum.DIFY_WRITING_STANDARD.getModelCode());
// Dify 用户标识
String difyUserId = "user-" + userId;
return difyClient.getConversations(config.getApiKey(), difyUserId, lastId, limit);
}
@Override
public DifyMessageListRespVO getMessages(Long agentId, String conversationId, String userId, String firstId, Integer limit) {
// 获取智能体配置
AiAgentDO agent = aiAgentService.getAiAgent(agentId);
if (agent == null) {
throw new RuntimeException("智能体不存在");
}
// 获取积分配置(使用标准模式的 API Key
AiModelConfigDO config = pointsService.getConfig(
AiPlatformEnum.DIFY.getPlatform(),
AiModelTypeEnum.DIFY_WRITING_STANDARD.getModelCode());
// Dify 用户标识
String difyUserId = "user-" + userId;
return difyClient.getMessages(config.getApiKey(), conversationId, difyUserId, firstId, limit);
}
}

View File

@@ -0,0 +1,26 @@
package cn.iocoder.yudao.module.tik.dify.vo;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
import java.util.List;
/**
* Dify 会话列表响应 VO
*
* @author 芋道源码
*/
@Schema(description = "Dify 会话列表响应")
@Data
public class DifyConversationListRespVO {
@Schema(description = "返回条数", requiredMode = Schema.RequiredMode.REQUIRED)
private Integer limit;
@Schema(description = "是否有更多数据", requiredMode = Schema.RequiredMode.REQUIRED)
private Boolean hasMore;
@Schema(description = "会话列表", requiredMode = Schema.RequiredMode.REQUIRED)
private List<DifyConversationRespVO> data;
}

View File

@@ -0,0 +1,41 @@
package cn.iocoder.yudao.module.tik.dify.vo;
import com.fasterxml.jackson.annotation.JsonProperty;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
import java.util.Map;
/**
* Dify 会话响应 VO
*
* @author 芋道源码
*/
@Schema(description = "Dify 会话响应")
@Data
public class DifyConversationRespVO {
@Schema(description = "会话ID", requiredMode = Schema.RequiredMode.REQUIRED)
private String id;
@Schema(description = "会话名称")
private String name;
@Schema(description = "输入参数")
private Map<String, Object> inputs;
@Schema(description = "会话状态")
private String status;
@Schema(description = "开场白")
private String introduction;
@Schema(description = "创建时间(时间戳)")
@JsonProperty("created_at")
private Long createdAt;
@Schema(description = "更新时间(时间戳)")
@JsonProperty("updated_at")
private Long updatedAt;
}

View File

@@ -0,0 +1,26 @@
package cn.iocoder.yudao.module.tik.dify.vo;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
import java.util.List;
/**
* Dify 消息列表响应 VO
*
* @author 芋道源码
*/
@Schema(description = "Dify 消息列表响应")
@Data
public class DifyMessageListRespVO {
@Schema(description = "返回条数", requiredMode = Schema.RequiredMode.REQUIRED)
private Integer limit;
@Schema(description = "是否有更多数据", requiredMode = Schema.RequiredMode.REQUIRED)
private Boolean hasMore;
@Schema(description = "消息列表", requiredMode = Schema.RequiredMode.REQUIRED)
private List<DifyMessageRespVO> data;
}

View File

@@ -0,0 +1,67 @@
package cn.iocoder.yudao.module.tik.dify.vo;
import com.fasterxml.jackson.annotation.JsonProperty;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
import java.util.List;
import java.util.Map;
/**
* Dify 消息响应 VO
*
* @author 芋道源码
*/
@Schema(description = "Dify 消息响应")
@Data
public class DifyMessageRespVO {
@Schema(description = "消息ID", requiredMode = Schema.RequiredMode.REQUIRED)
private String id;
@Schema(description = "会话ID", requiredMode = Schema.RequiredMode.REQUIRED)
@JsonProperty("conversation_id")
private String conversationId;
@Schema(description = "输入参数")
private Map<String, Object> inputs;
@Schema(description = "用户问题", requiredMode = Schema.RequiredMode.REQUIRED)
private String query;
@Schema(description = "AI回答", requiredMode = Schema.RequiredMode.REQUIRED)
private String answer;
@Schema(description = "消息文件列表")
@JsonProperty("message_files")
private List<MessageFile> messageFiles;
@Schema(description = "用户反馈")
private Feedback feedback;
@Schema(description = "创建时间(时间戳)")
@JsonProperty("created_at")
private Long createdAt;
/**
* 消息文件
*/
@Data
public static class MessageFile {
private String id;
private String type;
private String url;
@JsonProperty("belongs_to")
private String belongsTo;
}
/**
* 用户反馈
*/
@Data
public static class Feedback {
private String rating;
}
}