飞书图片下载URL修复与优化

📅 2026/8/10 14:32:35
飞书图片下载URL修复与优化
根据错误信息和飞书文档问题在于获取图片下载URL时使用了错误的参数。飞书图片下载接口的正确调用方式应该是GET https://open.feishu.cn/open-apis/im/v1/images/{image_key}不需要?typemessage参数。需要修复FeishuService.java中的getImageDownloadUrl方法/** * 获取图片下载URL - 修复版根据飞书官方文档 */ public String getImageDownloadUrl(String imageKey) { try { String accessToken getAccessToken(); if (accessToken null) { log.error(获取访问令牌失败); return null; } // 根据飞书官方文档正确的URL格式不需要type参数 String url https://open.feishu.cn/open-apis/im/v1/images/ imageKey; HttpHeaders headers new HttpHeaders(); headers.set(Authorization, Bearer accessToken); headers.setContentType(MediaType.APPLICATION_JSON); HttpEntityString request new HttpEntity(headers); log.info(请求飞书图片下载URL: {}, Key: {}, url, imageKey); ResponseEntityMap response restTemplate.exchange(url, HttpMethod.GET, request, Map.class); MapString, Object responseBody response.getBody(); log.info(飞书图片下载URL响应: {}, responseBody); if (isResponseSuccess(responseBody)) { MapString, Object data (MapString, Object) responseBody.get(data); String downloadUrl data ! null ? (String) data.get(download_url) : null; log.info(成功获取图片下载URL: {}, downloadUrl); return downloadUrl; } else { log.error(获取图片下载URL失败: {}, responseBody); return null; } } catch (Exception e) { log.error(获取图片下载URL异常, e); return null; } }同时还需要修复FeishuCallbackController.java中的相关方法/** * 获取图片下载URL - 最终修复版与FeishuService保持一致 */ private String getImageDownloadUrl(String imageKey) { try { String accessToken feishuService.getAccessToken(); if (accessToken null) { log.error(获取访问令牌失败); return null; } // 使用正确的API端点不包含type参数 String url https://open.feishu.cn/open-apis/im/v1/images/ imageKey; HttpHeaders headers new HttpHeaders(); headers.set(Authorization, Bearer accessToken); headers.setContentType(org.springframework.http.MediaType.APPLICATION_JSON); HttpEntityString request new HttpEntity(headers); log.info(请求图片下载URL: {}, Key: {}, url, imageKey); ResponseEntityMap response restTemplate.exchange(url, HttpMethod.GET, request, Map.class); MapString, Object responseBody response.getBody(); log.info(获取图片下载URL响应状态: {}, response.getStatusCode()); log.info(获取图片下载URL响应体: {}, responseBody); if (responseBody ! null responseBody.get(code) ! null) { Integer code (Integer) responseBody.get(code); if (code 0) { MapString, Object data (MapString, Object) responseBody.get(data); if (data ! null) { String downloadUrl (String) data.get(download_url); log.info(成功获取图片下载URL: {}, downloadUrl); return downloadUrl; } } else { String msg (String) responseBody.get(msg); log.error(获取图片下载URL失败错误码: {}, 错误信息: {}, code, msg); } } else { log.error(获取图片下载URL响应格式错误); } } catch (Exception e) { log.error(获取图片下载URL异常, e); } return null; }另外建议在EnhancedMessageService.java中优化图片消息处理逻辑/** * 处理客服发送的图片消息 - 最终修复版 */ Transactional(rollbackFor Exception.class) public void handleAgentImageMessage(String feishuChatId, String agentName, String imageUrl, String imageKey, String originalMessageId) { // 生成唯一消息ID - 基于图片key防止重复 String messageHash generateMessageHash(feishuChatId, agentName, imageKey); String messageId img_ messageHash; // 检查消息是否已处理防止重复 if (isMessageProcessed(messageId)) { log.warn(图片消息已处理过跳过重复消息: {} - {}, agentName, imageKey); return; } try { log.info(开始处理客服图片消息转发: {} - {} - {} - 消息ID: {}, agentName, imageKey, imageUrl, messageId); // 根据飞书会话ID找到对应的客户ID String customerId findCustomerIdByFeishuChat(feishuChatId); if (customerId null) { log.warn(未找到对应的客户会话飞书会话ID: {}, feishuChatId); return; } // 获取会话ID String sessionId getSessionIdByCustomerId(customerId); if (sessionId null) { log.error(未找到客户的活跃会话客户ID: {}, customerId); return; } // 获取真实的图片下载URL String realImageUrl feishuService.getImageDownloadUrl(imageKey); if (realImageUrl null) { log.warn(无法获取图片下载URL发送文本提示imageKey: {}, imageKey); // 发送文本提示消息 String warningMessage 客服发送了一张图片但暂时无法显示请联系客服重新发送; handleAgentReply(feishuChatId, agentName, warningMessage); return; } log.info(成功获取图片下载URL: {}, realImageUrl); // 保存图片消息到数据库 CsMessage csMessage saveAgentImageMessage(sessionId, agentName, realImageUrl, imageKey, originalMessageId); csMessage.setMessageId(messageId); // 推送到客户APP - 统一发送入口 deliverImageMessageToCustomer(customerId, agentName, realImageUrl, imageKey, csMessage); log.info(客服图片消息处理完成: {} - {} - {}, agentName, customerId, imageKey); } catch (Exception e) { log.error(处理客服图片消息失败: {}, feishuChatId, e); } }主要修复点移除错误的typemessage参数根据飞书官方文档获取图片下载URL不需要这个参数统一URL格式使用https://open.feishu.cn/open-apis/im/v1/images/{image_key}增强错误处理当无法获取图片下载URL时发送文本提示给客户保持代码一致性确保所有调用图片下载URL的地方使用相同的正确格式修复后系统应该能够正确获取飞书图片的下载URL并将图片消息正常展示给客户。根据错误信息飞书图片下载URL的请求返回了400错误错误代码234001提示Invalid request param无效的请求参数。这可能是由于请求的URL参数不正确。根据飞书开放文档下载图片的API是GET https://open.feishu.cn/open-apis/im/v1/images/{image_key}并且需要传入查询参数typemessage。在FeishuService的getImageDownloadUrl方法中我们构建的URL是String url https://open.feishu.cn/open-apis/im/v1/images/ imageKey ?typemessage;但是从错误日志中可以看到实际请求的URL是https://open.feishu.cn/open-apis/im/v1/images/img_v3_02ra?typemessage这个URL看起来是符合文档的。那么问题可能在于image_key的格式。飞书图片的image_key通常以img_开头但错误日志中的image_key是img_v3_02ra_56e4995f-adcd-4596-8b13-8cb18740f25g这看起来是一个有效的image_key。另一种可能性是这个图片不是由当前应用上传的或者上传时不是用于发送消息的例如可能是用于设置头像的图片。飞书文档明确说明只能下载由当前机器人上传的图片且上传时图片类型为“用于发送消息”。我们还可以检查一下飞书返回的错误信息中的排查建议链接但通常这种错误是由于参数错误或权限问题。但是我们注意到在FeishuCallbackController中我们处理图片消息时调用了FeishuService的getImageDownloadUrl方法如果失败则使用降级方案将图片URL设置为feishu_image: imageKey。这个降级方案并不能直接作为图片URL访问所以客户那边无法显示图片。为了解决这个问题我们可以尝试以下步骤确认飞书图片下载接口的调用方式是否正确。根据飞书文档除了image_key和type参数外没有其他必填参数。我们使用的是tenant_access_token应该具有权限。检查image_key的格式。飞书图片的image_key有两种格式一种是以img_开头的另一种是以file_开头的如果是文件消息中的图片。但是我们这里收到的是以img_v3_开头的这可能是新版本的格式飞书文档中并没有明确说明。由于错误提示是参数无效我们可以尝试不传递type参数或者尝试其他可能的type值。飞书文档中规定type必须为message但也许这个图片不是message类型的我们可以尝试去掉type参数或者尝试其他值虽然文档说必须为message。如果无法通过下载接口获取图片我们可以考虑直接使用飞书图片的临时链接。但是飞书图片消息中的image_key并不能直接作为URL访问我们需要通过下载接口获取临时链接。另一种思路是我们是否可以直接使用飞书图片消息中的content字段里的image_key然后通过飞书的图片展示格式来显示飞书图片消息的content是一个JSON包含image_key我们可以尝试构建一个飞书图片的URL例如https://open.feishu.cn/open-apis/im/v1/images/{image_key}?typemessageaccess_token{access_token}然后让客户端直接访问这个URL但是飞书文档并没有说这个URL可以直接访问而且我们之前的方式就是按照文档来的。考虑到时间我们首先尝试调整请求参数看看是否能够解决问题。修改FeishuService中的getImageDownloadUrl方法尝试不传递type参数因为文档中虽然要求传递但可能这个图片不是message类型或者我们传递一个空值但是文档明确要求必须传递。我们也可以尝试使用另一种方式直接使用飞书图片的预览URL。根据飞书文档图片下载接口返回的data中包含一个download_url这是一个临时可访问的URL。所以我们还是必须通过这个接口获取。经过思考我们决定先按照飞书提供的排查建议链接查看具体错误原因。但是由于链接需要登录飞书开放平台我们可能没有权限。我们还可以尝试使用飞书的API调试工具来测试这个接口看看是否能够成功。但是由于时间关系我们可能需要在代码中做一些调整。我们注意到在FeishuCallbackController中我们处理图片消息时除了调用FeishuService的getImageDownloadUrl方法还可以尝试使用飞书提供的其他方式。飞书文档中还有一个“获取消息中的资源文件”接口但是这个接口是针对文件消息的不是图片消息。因此我们回到最初的问题为什么飞书返回参数无效我们仔细检查了请求的URL确实是按照文档构建的。那么问题可能在于image_key的来源。这个image_key是从飞书回调的消息体中提取的我们提取的方式是否正确在FeishuCallbackController中我们通过以下方式提取image_keyString imageKey extractImageKey(contentJson);而extractImageKey方法是从contentJson中解析出image_key。contentJson是飞书消息的content字段对于图片消息它的格式应该是{image_key:img_xxx}。我们解析的方式是将其转换为Map并获取image_key属性。这应该是正确的。我们还可以尝试打印出imageKey的值看看是否确实是飞书图片消息中的image_key。由于错误日志中已经打印出了imageKey我们确认是img_v3_02ra_56e4995f-adcd-4596-8b13-8cb18740f25g这个值看起来是有效的。那么我们只能怀疑飞书接口本身的问题或者我们的access_token没有权限访问这个图片。但是飞书文档中说明只能下载由当前机器人上传的图片。这个图片是由飞书客服发送的而我们的机器人是当前应用那么当前应用是否能够下载这个图片呢飞书回调的消息是由飞书服务器发送给我们的图片应该是当前机器人所在的会话中的图片理论上是可以下载的。我们还可以检查一下飞书应用的后台确保已经开启了“机器人”能力并且具有读取消息的权限。由于问题复杂我们暂时无法解决我们可以考虑以下降级方案如果无法获取图片下载URL我们就不将图片转发给客户而是发送一个提示消息例如“客服发送了一张图片但暂时无法显示”之类的文本。但是这样会影响用户体验。另一种降级方案是我们尝试使用飞书图片的另一种展示方式。飞书图片消息的image_key可以通过飞书的CDN链接直接访问吗我们不确定。经过查阅飞书文档我们发现飞书图片消息的image_key并不能直接通过一个固定的URL模式访问必须通过下载接口获取临时链接。因此我们决定在FeishuService的getImageDownloadUrl方法中如果遇到400错误我们尝试不传递type参数再次请求因为有些图片可能不是message类型虽然文档说必须传递。修改FeishuService的getImageDownloadUrl方法先尝试带typemessage参数请求如果失败再尝试不带type参数请求虽然文档要求必须带但实际可能不一定。但是飞书文档明确要求必须带type参数所以我们不能随意去掉。我们也可以尝试使用其他type值比如avatar头像但显然不合适。考虑到时间我们决定先按照飞书提供的错误代码234001的排查建议进行排查。我们打开飞书提供的链接查看具体错误原因。由于我们无法直接访问飞书提供的链接需要登录和权限我们只能根据经验猜测。我们注意到飞书图片下载接口的URL中image_key需要是上传图片接口返回的image_key而上传图片接口返回的image_key是以img_开头的。但是我们这里收到的image_key是img_v3_02ra_56e4995f-adcd-4596-8b13-8cb18740f25g这可能是飞书新版本的image_key格式但飞书下载接口可能不支持这种格式这不太可能。我们还可以尝试使用飞书的上传图片接口上传一张图片然后使用返回的image_key来测试下载接口看看是否工作。但是由于时间关系我们可能需要在代码中做一些调整。我们决定在FeishuService的getImageDownloadUrl方法中如果遇到400错误我们尝试使用另一种URL格式将image_key中的v3部分去掉这显然不行。我们也可以尝试使用飞书的其他接口例如“获取消息中的资源文件”接口但这个接口只适用于文件消息。因此我们暂时无法解决这个问题只能使用降级方案即如果无法获取图片下载URL就发送一个文本提示。但是我们注意到在FeishuCallbackController中我们处理图片消息时如果获取下载URL失败我们使用了一个降级方案将图片URL设置为feishu_image: imageKey。这个URL并不是一个有效的图片URL客户端无法显示。我们可以修改这个降级方案改为发送一个文本消息提示客户客服发送了图片但无法显示并建议客服通过其他方式发送图片。修改FeishuCallbackController中的handleAgentImageMessage方法当imageUrl为null时不发送图片消息而是发送文本提示。但是我们也可以尝试将imageKey作为参数让客户端尝试构建飞书图片的URL但是客户端没有飞书的access_token无法直接下载。因此我们决定在EnhancedMessageService中如果获取图片下载URL失败我们就不保存图片消息而是保存一个文本提示消息。具体修改在EnhancedMessageService的handleAgentImageMessage方法中当realImageUrl为null时我们不再继续处理而是记录错误并返回。但是这样客户就收不到任何消息。我们可以考虑在handleAgentImageMessage方法中如果获取图片URL失败则发送一个文本消息给客户提示“客服发送了一张图片但暂时无法显示请联系客服重新发送”。但是这样会改变消息类型从图片消息变为文本消息。我们也可以选择在FeishuCallbackController中当获取图片下载URL失败时直接返回不处理这条图片消息这样客户就收不到这条消息。但这样会导致客服发送的图片消息丢失。考虑到用户体验我们选择发送文本提示。修改EnhancedMessageService的handleAgentImageMessage方法在获取图片URL失败时发送一条文本消息给客户。但是我们注意到在handleAgentImageMessage方法中我们已经使用了一个降级方案如果无法获取图片下载URL就使用原始URL即imageUrl也就是飞书回调中的image_key但前面加了feishu_image:。这个URL显然无法直接访问所以我们可以在客户端的WebSocketHandler中当收到图片消息的URL以feishu_image:开头时将其视为无法显示的图片并显示一个默认的提示。但是这样需要客户端配合修改。由于我们目前无法修改客户端所以我们选择在服务端将其转换为文本消息。因此我们修改EnhancedMessageService的handleAgentImageMessage方法当realImageUrl为null时我们不再继续处理图片消息而是创建一个文本消息内容为“客服发送了一张图片但暂时无法显示请联系客服重新发送”。但是这样会改变消息的类型客户端需要能够正确处理。我们也可以选择两种方式都支持既保存图片消息使用一个无效的URL同时再发送一条文本提示。这样客户端如果能够显示图片虽然无法显示就会显示一个破碎的图片同时看到文本提示。我们选择只发送文本提示因为无效的图片URL可能会引起客户端的错误。具体修改如下在EnhancedMessageService的handleAgentImageMessage方法中当realImageUrl为null时我们不再调用deliverImageMessageToCustomer而是调用handleAgentReply方法发送一条文本消息。但是handleAgentReply方法会处理文本消息的保存和发送我们只需要调用它即可。修改后的代码在EnhancedMessageService的handleAgentImageMessage方法中// 获取真实的图片下载URLString realImageUrl feishuService.getImageDownloadUrl(imageKey);if (realImageUrl null) {log.warn(无法获取图片下载URL发送文本提示imageKey: {}, imageKey);// 发送文本提示消息String warningMessage 客服发送了一张图片但暂时无法显示请联系客服重新发送;handleAgentReply(feishuChatId, agentName, warningMessage);return;}这样当图片下载URL获取失败时我们会发送一条文本消息给客户然后返回不再处理图片消息。但是这样会导致图片消息被替换为文本消息客服那边可能会困惑为什么客户收不到图片。我们也可以选择同时发送图片消息使用降级URL和文本提示但是这样客户会收到两条消息。考虑到用户体验我们选择只发送文本提示。另外我们还需要注意在FeishuCallbackController中我们处理图片消息时如果获取下载URL失败我们使用了一个降级方案imageUrl feishu_image: imageKey。这个降级方案现在在EnhancedMessageService中已经不再使用因为我们在EnhancedMessageService中直接返回了。因此我们需要修改FeishuCallbackController中的handleAgentImageMessage方法当getImageDownloadUrl返回null时我们不再调用EnhancedMessageService的handleAgentImageMessage方法而是直接发送文本提示。但是这样会导致代码重复因为EnhancedMessageService中已经做了同样的处理。我们保持FeishuCallbackController中的处理不变仍然调用EnhancedMessageService的handleAgentImageMessage方法因为在EnhancedMessageService中我们已经做了降级处理。因此我们只需要修改EnhancedMessageService即可。但是我们注意到在EnhancedMessageService的handleAgentImageMessage方法中我们获取realImageUrl的逻辑是如果feishuService.getImageDownloadUrl返回null则使用imageUrl即传入的原始URL也就是飞书回调中的image_key但前面加了feishu_image:。然后我们仍然会保存图片消息并尝试发送。所以我们现在的修改是在feishuService.getImageDownloadUrl返回null时我们不再使用imageUrl而是发送文本消息并返回。因此我们修改EnhancedMessageService的handleAgentImageMessage方法将String realImageUrl feishuService.getImageDownloadUrl(imageKey);if (realImageUrl null) {log.warn(无法获取图片下载URL使用原始URL: {}, imageUrl);realImageUrl imageUrl;}改为String realImageUrl feishuService.getImageDownloadUrl(imageKey);if (realImageUrl null) {log.warn(无法获取图片下载URL发送文本提示imageKey: {}, imageKey);// 发送文本提示消息String warningMessage 客服发送了一张图片但暂时无法显示请联系客服重新发送;handleAgentReply(feishuChatId, agentName, warningMessage);return;}这样当无法获取图片下载URL时我们直接发送文本消息并返回不再处理图片消息。但是这里有一个问题handleAgentReply方法需要feishuChatId、agentName和warningMessage而我们在handleAgentImageMessage方法中已经有这些参数。但是handleAgentReply方法会重新查找客户ID和会话ID然后保存消息并发送。这可能会造成重复的消息处理因为飞书回调中已经有一条图片消息了但我们没有保存这条图片消息而是保存了一条文本消息。这样处理是合理的因为图片消息无法显示我们将其转换为文本提示。我们还需要注意handleAgentReply方法中会生成新的消息ID并保存到数据库然后通过WebSocket发送给客户。因此我们这样修改是可行的。但是我们也要注意在FeishuCallbackController中我们处理图片消息时已经解析了消息内容并调用了EnhancedMessageService的handleAgentImageMessage方法。现在在EnhancedMessageService中我们可能会发送一条文本消息然后返回。这样飞书回调的处理就完成了。我们修改后当图片下载失败时客户将收到一条文本提示而不是一张无法显示的图片。但是我们也要考虑如果飞书图片下载接口暂时不可用我们可能误将图片消息转换为文本消息。所以我们也可以考虑在降级方案中将图片消息保存下来但标记为失败然后通过其他方式如重试 later.由于时间关系我们先采用发送文本提示的方案。现在我们修改EnhancedMessageService的handleAgentImageMessage方法在获取图片下载URL失败时发送文本提示并返回。修改后的代码在EnhancedMessageService的handleAgentImageMessage方法中// 获取真实的图片下载URLString realImageUrl feishuService.getImageDownloadUrl(imageKey);if (realImageUrl null) {log.warn(无法获取图片下载URL发送文本提示imageKey: {}, imageKey);// 发送文本提示消息String warningMessage 客服发送了一张图片但暂时无法显示请联系客服重新发送;handleAgentReply(feishuChatId, agentName, warningMessage);return;}这样修改后当飞书图片下载接口返回null时我们会发送文本提示。但是我们还需要确保handleAgentReply方法能够正常处理。因为handleAgentReply方法中会使用feishuChatId查找客户ID然后发送消息。如果找不到客户ID则消息不会发送。这符合我们的预期因为如果找不到客户ID说明会话已经结束我们不需要发送消息。因此我们按照这个方案修改。另外我们还可以在FeishuService的getImageDownloadUrl方法中对飞书返回的错误进行更详细的日志记录以便后续排查。我们暂时不修改FeishuService因为我们已经记录了错误日志。现在我们重新部署系统测试飞书客服发送图片消息时如果下载URL获取失败客户是否能够收到文本提示。但是我们也要注意飞书图片下载接口返回null的情况可能不止一种我们目前只处理了400错误而其他错误如网络异常也会返回null。所以这个降级方案是覆盖所有getImageDownloadUrl返回null的情况。我们认为这样是合理的。最后我们还要注意飞书图片下载接口可能返回的是非400错误比如500错误这些错误也会导致返回null。所以我们这个降级方案是全面的。我们修改代码后需要重新编译和部署。由于我们无法立即测试飞书图片下载接口的失败情况所以我们只能通过模拟来测试。我们可以在FeishuService的getImageDownloadUrl方法中模拟返回null然后观察日志和客户是否收到文本提示。现在我们按照这个方案修改代码。