mirror of
https://gitee.com/mateos/mateclaw.git
synced 2026-09-13 03:13:41 +08:00
* feat(workspace): chat-uploads 上传目录工作空间/Agent 感知化 (#421) 把硬编码的 data/chat-uploads/{conversationId}/ 改为按工作空间/Agent 解析, 解析优先级:Agent workspaceBasePath → Workspace basePath → 可配置默认目录 (新配置 mateclaw.chat.upload.base-dir,默认 data/chat-uploads,保持现网零行为变化)。 - 新增 ChatUploadLocationResolver 中央解析器:写路径返回唯一根,读/清理 路径返回候选根列表(工作空间根 + 默认根)做双重查找,保证迁移前旧附件 仍可解析/清理;conversationId→ConversationEntity 查询带 5min 缓存。 - 新增 ChatUploadProperties + ChatUploadAutoConfiguration(启动建目录)。 - 复用 AgentGraphBuilder.resolveAgentBasePath(提升为 public)的优先级与 安全规则(相对路径在 workspace 根下解析,绝对路径逃逸被拒)。 - 所有写入/读取点改为走 resolver;读取点走双重查找。 - 解决 Spring 循环依赖(resolver → agentService → ... → conversationService → resolver):resolver 的 AgentService 注入加 @Lazy。 向后兼容:默认目录不变;双重查找覆盖历史消息里的相对路径; 服务端点 URL 契约不变,前端无需改动。 测试:新增 ChatUploadLocationResolverTest(8 用例);修复受影响的现有测试构造。 * refactor(workspace): address review findings on chat-uploads resolver (#421) 应用 code review 的 4 项修复: 1. (correctness) ChatUploadLocationResolver 缓存新增 ConversationDeletedEvent 监听器,删除会话时立即失效 conversationId→ConversationEntity 映射。 否则备份恢复后用相同 id 重建会话,会继承最长 5 分钟的过期 workspace/agent 映射,导致 cleanAttachmentFiles 走错(过期的)上传目录。复用既有 @EventListener-on-bean 模式(与 AsyncTaskService / WorkspaceLookupCache 一致)。 2. 收紧 resolveWorkspaceScopedRoot 里 3 个过宽的 catch(Exception) → MateClawException + warn,让真正的 bug(NPE / DataAccessException)暴露 而非被静默降级为 debug 日志。 3. 更新 ChatController.upload 过期注释:会话尚未创建时附件暂存默认目录, 会话创建后读取走双重查找仍能命中。 4. 移除不可达分支(agentWorkspaceId != workspaceId)—— 会话的 agent 必然 归属会话的 workspace(创建时强约束),直接用会话 workspace 即可, 少一次冗余 DB 查询与一层推测性逻辑。 测试:ChatUploadLocationResolverTest (8) + ConversationServiceCleanAttachmentFilesTest (2) 全绿。
63 lines
2.0 KiB
Java
63 lines
2.0 KiB
Java
package vip.mate.tool.video;
|
|
|
|
import cn.hutool.http.HttpUtil;
|
|
import lombok.RequiredArgsConstructor;
|
|
import lombok.extern.slf4j.Slf4j;
|
|
import org.springframework.stereotype.Component;
|
|
import vip.mate.workspace.core.service.ChatUploadLocationResolver;
|
|
|
|
import java.io.IOException;
|
|
import java.nio.file.Files;
|
|
import java.nio.file.Path;
|
|
|
|
/**
|
|
* 视频文件下载器 — 从 provider CDN 下载视频到本地存储
|
|
*
|
|
* @author MateClaw Team
|
|
*/
|
|
@Slf4j
|
|
@Component
|
|
@RequiredArgsConstructor
|
|
public class VideoFileDownloader {
|
|
|
|
private final ChatUploadLocationResolver uploadLocationResolver;
|
|
|
|
/**
|
|
* 下载视频到本地
|
|
*
|
|
* @param videoUrl provider 返回的视频 URL
|
|
* @param conversationId 会话 ID
|
|
* @param taskId 任务 ID
|
|
* @return 本地文件路径
|
|
*/
|
|
public Path download(String videoUrl, String conversationId, String taskId) throws IOException {
|
|
Path dir = uploadLocationResolver.resolveUploadRoot(conversationId).resolve(conversationId);
|
|
Files.createDirectories(dir);
|
|
|
|
String extension = guessExtension(videoUrl);
|
|
String fileName = "video_" + taskId + extension;
|
|
Path targetFile = dir.resolve(fileName);
|
|
|
|
log.info("[VideoDownloader] Downloading video from {} to {}", videoUrl, targetFile);
|
|
long size = HttpUtil.downloadFile(videoUrl, targetFile.toFile());
|
|
log.info("[VideoDownloader] Downloaded {} bytes to {}", size, targetFile);
|
|
|
|
return targetFile;
|
|
}
|
|
|
|
/**
|
|
* 构造文件的 API 访问 URL
|
|
*/
|
|
public String toServingUrl(String conversationId, Path localPath) {
|
|
return "/api/v1/chat/files/" + conversationId + "/" + localPath.getFileName().toString();
|
|
}
|
|
|
|
private String guessExtension(String url) {
|
|
String lower = url.toLowerCase();
|
|
if (lower.contains(".mp4")) return ".mp4";
|
|
if (lower.contains(".webm")) return ".webm";
|
|
if (lower.contains(".mov")) return ".mov";
|
|
return ".mp4"; // 默认 mp4
|
|
}
|
|
}
|