DownloadManager 架构设计动机、实现与 SSE 协作本文介绍core/pkg/functions/download中DownloadManager的职责划分、关键数据结构、这样设计的原因以及优劣势最后说明在VSS SSEtypefile_download场景下的端到端流程。项目源码地址https://github.com/openskeye/go-vss一、组件定位项目说明包路径core/pkg/functions/download实例获取download.GetManager()进程内单例sync.Once典型消费者VSSServiceContext.DownloadManagerinternal/svc/service_context.go主要能力基于 URL 创建下载任务、HTTP 拉流写盘、向订阅方推送进度ProgressUpdate、取消与收尾它不依赖 Gin/WebSocket是可复用的下载 进度广播小内核与SSE的关系是Manager 负责「下什么、进度多少」SSE 负责「如何把进度推到浏览器」。二、核心数据结构2.1DownloadTaskTaskID当前实现里TaskID URLCreateTask中taskID : url。FilepathsaveDir 调用方传入的fileName。状态downloading/completed/error/cancelled。进度Downloaded、Total来自Content-Length未知则为 0、Progress百分比、SpeedKB/s按已读字节与耗时估算。2.2DownloadManagertasks *xmap.XMap[string, *DownloadTask] // taskID - 任务 clients *xmap.XMap[string, chan ProgressUpdate] // taskID - 订阅 channeltasks正在进行的任务索引Finished/ 结束后会Remove。clients每个taskID对应一个chan ProgressUpdate缓冲10下载循环通过notifyClients写入。xmap.XMap为线程安全泛型 Map读写锁适合多 goroutine 并发注册任务与推送进度。2.3 单例var(manager*DownloadManager once sync.Once)funcGetManager()*DownloadManager{once.Do(func(){...})returnmanager}保证全进程唯一 Manager任务与订阅表全局共享——与「按 URL 去重、运维看板统计任务数」等需求一致。三、API与行为方法作用CreateTask(url, fileName, saveDir)确保目录存在以url 为 TaskID写入tasks返回DownloadTask。StartDownload(ctx, task)当前实现未用 ctx 取消 HTTP内部GET、按块读 body 写文件每读一块updateProgressnotifyClients。Subscribe(taskID)make(chan ProgressUpdate, 10)并Set到clients同 key覆盖旧 channel。Unsubscribe/Finishedclose订阅 channel并从clients、tasks移除避免泄漏。CancelDownload(taskID)将任务标为cancelled并notifyClients下载循环在下轮读到状态后退出并删文件。CheckExists(taskID)判断tasks中是否已有该 URL 任务。TaskNum/ClientNumtasks.Len()/clients.Len()供SSEsev_state等展示。四、为什么要这样设计4.1 问题背景平台侧需要从给定 URL 拉文件到服务器磁盘同时让前端实时看到进度百分比、速度、路径。若仅用「同步 HTTP 下载 轮询 DB/Redis」复杂度高、延迟大若在业务里手写 goroutine 多处chan容易泄漏、难统一取消与统计。4.2 设计选择背后的意图任务与传输解耦DownloadManager只关心任务生命周期 进度事件谁消费进度SSE、日志、未来 WebSocket由上层Subscribe决定符合观察者模式。以 URL 为 TaskID同一 URL 在表里天然去重避免重复建任务、重复占带宽与CheckExists、file_downloadSSE里「已存在则只订阅」的语义一致。单例 全局 Map在单进程 VSS内所有下载与订阅集中管理TaskNum/ClientNum可直接用于运维 SSE 面板sev_state无需再挂一层注册中心。进度 channel 带小缓冲10下载循环写进度频率高缓冲可吸收瞬时突发减少下载 goroutine 因消费者暂时未读而立刻阻塞的概率消费者仍要跟得上。五、优势与风险5.1 优势方面说明接入简单CreateTask→go StartDownload→Subscribe读 channel心智负担低。并发安全xmap封装锁多协程注册/通知不易出现裸 map 竞态。可观测任务数、订阅数、进度结构体字段齐全易对接。与 SSE 契合推送模型一致Managerpush进度SSE转发为text/event-stream。取消路径清晰CancelDownload改状态 notify循环侧检测StatusCancelled后清理文件。5.2 风险方面说明TaskID URL相同 URL 无法并发多任务带不同 query 的 URL 会被视为不同任务可能重复下载。notifyClients为阻塞发送ch - ProgressUpdate无select若消费者从不读或读太慢下载 goroutine 会阻塞在通知上相当于背压传递到网络读。六、与 SSE 的配合过程VSSfile_download实现位置core/app/sev/vss/internal/logic/sse/file_download.go。6.1 时序概览远端 URLDownloadManagerFileDownloadLogicSSE /events浏览器 EventSource远端 URLDownloadManagerFileDownloadLogicSSE /events浏览器 EventSourcealt[任务不存在或允许取消]loop[下载读块]完成/错误/取消GET /events?typefile_downloadurl...DO(req)CreateTask(url, ...) go StartDownloadSubscribe(url)GET bodyupdateProgress notifyClientsProgressUpdate - chmessageChan - SSEResponse{Data}data: {data:...}Finished(url) 或 Unsubscribe deferDone / Err6.2 关键步骤说明Query 参数typefile_download、url、可选filename、cancel1。启动条件!CheckExists(url)或cancel时调用downloader取消走CancelDownload否则CreateTask并go StartDownload。订阅Subscribe(req.Url)与TaskIDurl一致defer Unsubscribe保证连接断开或逻辑退出时释放。向 SSE 转发for { select { case v : -ch } }将ProgressUpdate封装为SSEResponse.Data写入messageChan。降频通过NowMilli()%100 0等条件减少 SSE 帧率避免每 32KB 都打一帧拖慢浏览器终态完成/取消/错误仍强制推送。结束Finished(url)关闭订阅 channel、移除任务SSE 侧Done: true或Err结束事件流。6.3 小结层级职责DownloadManagerHTTP 下载、落盘、进度计算、向chan推送、任务/订阅表维护。SSE FileDownloadLogic解析参数、决定何时创建任务、订阅并桥接到messageChan、节流与终态帧。SSE Servertext/event-stream编码、Flush、连接生命周期。七、与运维面板的衔接sev_stateinternal/logic/sse/sev_state.go中两项「文件下载任务数量」→DownloadManager.TaskNum()「文件下载任务数量」第二项实现为ClientNum()语义上更接近当前订阅连接数用于观察下载与 SSE 订阅是否堆积可与SSE.MessageChanBuffer等配置联合调优见 SSE 专题文档。八、源码索引说明路径DownloadManager 实现core/pkg/functions/download/main.goVSS 注入core/app/sev/vss/internal/svc/service_context.goSSE 文件下载core/app/sev/vss/internal/logic/sse/file_download.go服务状态中的统计core/app/sev/vss/internal/logic/sse/sev_state.go线程安全 Mapcore/pkg/xmap/main.go本文与《VSS-SSE架构设计》中file_download一节互补前者偏传输协议本文偏下载内核与协作边界。