织梦手机电影网站模板,网站软件免费下载大全,自贡网站建设哪家好,wordpress文章列表获取文章摘要实现基于 Spring Boot 的 Dify AI 聊天接口代理#xff0c;支持流式响应#xff0c;并排查接口调用异常问题
一、Dify 代理接口实现原理
1. 核心功能
通过后端代理转发前端聊天请求至 Dify AI 平台#xff08;https://api.dify.ai/v1/chat-messages#xff09;#xff…实现基于 Spring Boot 的 Dify AI 聊天接口代理支持流式响应并排查接口调用异常问题一、Dify 代理接口实现原理1. 核心功能通过后端代理转发前端聊天请求至 Dify AI 平台https://api.dify.ai/v1/chat-messages实现用户鉴权、参数透传、流式响应返回核心流程如下鉴权层从请求头获取accessToken调用UserFeignClient校验用户身份未登录则返回标准化超时响应参数解析层解析前端传入的query聊天问题、conversationId会话 ID等参数校验必填项请求构建层封装 Dify 平台要求的请求参数含inputs、response_modestreaming、user等添加 Dify API Token 认证头流式调用层基于 Spring WebFlux 的WebClient调用 Dify 流式接口接收 SSEServer-Sent Events流式响应响应返回层将 Dify 返回的流式数据直接透传给前端异常时封装标准化错误信息遵循 SSE 格式。2. 技术选型核心框架Spring Boot Spring WebFlux支持异步流式响应HTTP 客户端WebClient替代传统 HttpClient适配流式响应服务注册 / 发现Nacos服务间调用及路由转发接口规范SSEtext/event-stream流式响应兼容前端实时接收聊天回复。package cn.com.xxx.mainservice.rest; import cn.com.xxx.common.feign.user.UserFeignClient; import cn.com.xxa.common.model.database.User; import cn.com.xxx.common.util.BaseUtils; import cn.com.xxxa.common.util.StaticValue; import com.alibaba.fastjson.JSON; import com.alibaba.fastjson.JSONObject; import jakarta.servlet.http.HttpServletRequest; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Qualifier; import org.springframework.http.HttpHeaders; import org.springframework.http.MediaType; import org.springframework.web.bind.annotation.RequestBody; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RequestMethod; import org.springframework.web.bind.annotation.RestController; import org.springframework.web.reactive.function.client.WebClient; import reactor.core.publisher.Flux; import reactor.core.publisher.Mono; import java.util.HashMap; import java.util.Map; RestController public class ChatRest { Qualifier(cn.com.xxx.common.feign.user.UserFeignClient) Autowired private UserFeignClient userFeignClient; private final String DIFY_API_URL http://192.xxx.x.193/v1/chat-messages; private final String DIFY_API_TOKEN app-kam4xxxxxxx50klwoJH; // 替换为实际token /** * 构建WebClientSpring WebFlux原生工具 */ private final WebClient webClient WebClient.builder() .codecs(configurer - configurer.defaultCodecs().maxInMemorySize(1024 * 1024)) // 调整缓冲区大小 .build(); /** * Dify聊天Rest接口流式响应 * 返回类型为FluxString直接返回SSE流式数据 */ RequestMapping( value /rest/v1/chat/difyProxy, method RequestMethod.POST, produces MediaType.TEXT_EVENT_STREAM_VALUE // 声明SSE响应类型 ) public FluxString difyChatProxy(RequestBody String jsonBody, HttpServletRequest request) { // 解析前端聊天参数 鉴权 String accessToken request.getHeader(StaticValue.HEADER_ACCESSTOKEN); User loginUser userFeignClient.getUserByAccessToken(accessToken); // 鉴权失败返回流式错误信息 if (loginUser null) { String errorData String.format(data: %s\n\n, JSON.toJSONString(BaseUtils.loginTimeoutReturn())); return Flux.just(errorData); } String loginUserName loginUser.getName(); JSONObject parmJson JSON.parseObject(jsonBody); String query parmJson.getString(query); String conversationId parmJson.getString(conversationId); // 查询为空返回流式错误信息 if (BaseUtils.isBlankStr(query)) { String errorData String.format(data: %s\n\n, JSON.toJSONString(BaseUtils.httpErrReturn())); return Flux.just(errorData); } // 构建Dify请求参数 MapString, Object difyParam new HashMap(); difyParam.put(inputs, new HashMap()); // 固定空inputs difyParam.put(query, query); // 前端传入的问题 difyParam.put(response_mode, streaming); // 流式响应模式 difyParam.put(user, loginUserName); // 用户名 difyParam.put(auto_generate_name, true); // 固定参数 // 可选参数conversationId非空则添加 if (!BaseUtils.isBlankStr(conversationId)) { difyParam.put(conversation_id, conversationId); } // 调用Dify流式接口并返回 return webClient.post() .uri(DIFY_API_URL) // 设置Dify认证头 .header(HttpHeaders.AUTHORIZATION, Bearer DIFY_API_TOKEN) .contentType(MediaType.APPLICATION_JSON) .bodyValue(JSON.toJSONString(difyParam)) .retrieve() // 处理非2xx响应 .onStatus(status - !status.is2xxSuccessful(), clientResponse - { return clientResponse.bodyToMono(String.class) .flatMap(errorMsg - { String errorData String.format(data: %s\n\n, JSON.toJSONString(Map.of( error, Dify接口调用失败 clientResponse.statusCode() 详情 errorMsg ))); return Mono.error(new RuntimeException(errorData)); }); }) // 解析流式响应为字符串 .bodyToFlux(String.class) // 日志打印 .doOnNext(line - { if (line.startsWith(data: )) { System.out.println(Dify流式数据 line); } }) // 异常处理返回标准化错误信息 .onErrorResume(e - { String errorData String.format(data: %s\n\n, JSON.toJSONString(Map.of( error, 服务端代理异常 e.getMessage() ))); return Flux.just(errorData); }) // 响应完成日志 .doFinally(signalType - { System.out.println(Dify流式响应接收完毕信号类型 signalType); }) // 设置响应头关键声明SSE格式 .contextWrite(ctx - { HttpHeaders headers new HttpHeaders(); headers.setContentType(MediaType.TEXT_EVENT_STREAM); headers.set(Cache-Control, no-cache); headers.set(Connection, keep-alive); return ctx.put(responseHeaders, headers); }); } }一开始把功能写在了微服务各个层(rest、controller、service、dto、feign等)但简单业务没有涉及数据库操作没必要写得那么复杂具体业务具体分析吧。二、问题排查记录问题 1编译报错 - 程序包 org.springframework.web.reactive.function.client 不存在现象编译ChatRest.java时提示程序包org.springframework.web.reactive.function.client不存在无法识别WebClient类。根因项目依赖配置异常重复引入spring-webflux依赖且未指定版本spring-boot-starter-webflux依赖冗余已包含spring-webflux核心包无需单独引入。解决方案移除重复的spring-webflux依赖仅保留spring-boot-starter-webflux核心依赖刷新 Maven 依赖并重新编译项目确保WebClient相关包正常引入。问题 2运行报错 - 找不到主类cn.com.xxx.mainservice.MainServiceMainApplication现象启动项目时提示 “找不到或无法加载主类”无法正常启动服务。根因主类包名与文件目录结构未严格匹配Java 编译规范要求编译产物缺失target/classes下无主类.class文件IDE 缓存 / 编译缓存未清理导致加载旧配置。解决方案核对主类包名cn.com.geohwa.mainservice与文件路径src/main/java/cn/com/geohwa/mainservice/一致性执行mvn clean compile清理并重新编译生成完整的.class文件清理 IDE 缓存IDEAInvalidate Caches / Restart重新导入项目。问题 3接口调用报错 - 404 Not Found现象前端 / ApiPost 请求/api/v1/chat/difyProxy返回 404提示 “请求的资源不存在”。根因接口路径不匹配代码中接口注解为/rest/v1/chat/difyProxy但请求路径为/api/v1/chat/difyProxy缺失rest前缀网关路由配置缺失初始仅配置/api/v1/user/**路由未添加/api/v1/chat/**路由规则临时解决方案补充 Gateway 路由配置通过RewritePath将/api/v1/chat/**重写为/rest/v1/chat/**转发至main-service服务。问题 4路由转发异常 - 请求被转发至同事的服务实例现象网关路由配置生效后请求仍未正常响应排查发现请求被转发至同网段192.xxx.x.202:8848另一同事的main-service实例。根因Nacos 注册中心中多实例使用相同服务名main-service且部署在同一 Nacos 节点192.xxx.x.202:8848网关负载均衡随机转发至非目标实例。解决方案修改本地服务的 Nacos 服务节点为201