WebSocket实现PDF翻译进度实时推送:Go后端+React前端的完整方案

发布时间:2026/8/1 15:58:08

WebSocket实现PDF翻译进度实时推送:Go后端+React前端的完整方案 前言做过PDF翻译功能集成的同学都遇到过这个问题翻译一个50页的PDF可能需要几十秒到几分钟如果只是显示一个转圈圈的loading用户体验很差——不知道进度、不知道卡在哪里、不知道还要等多久。HTTP轮询可以解决但不够优雅WebSocket才是正解。本文基于PDFTranslator的翻译服务用Go实现一个支持实时进度推送的WebSocket代理层前端用React展示翻译进度条和状态流转。整体架构用户浏览器 (React) │ WebSocket ▼ WebSocket 代理服务 (Go gorilla/websocket) │ HTTP ▼ PDFTranslator 翻译服务设计思路Go服务既是WebSocket Server又是翻译服务的HTTP代理。它接收前端的WebSocket连接在翻译过程中把进度信息实时推送给前端。环境准备Go 1.21Node.js 18 (React前端)依赖Go:github.com/gorilla/websocket、github.com/go-resty/resty/v2React:vitereact# Go后端go mod init ws-translator-proxy go get github.com/gorilla/websocketlatest go get github.com/go-resty/resty/v2latest# React前端npmcreate vitelatest frontend ----templatereactcdfrontendnpminstall后端实现Go WebSocket代理Step 1: 翻译进度模型pkg/model/progress.go—— 定义进度状态流转packagemodelimporttime// ProgressStage 翻译进度阶段typeProgressStagestringconst(StageUploading ProgressStageuploading// 正在上传文件StageProcessing ProgressStageprocessing// 正在翻译处理StageTranslating ProgressStagetranslating// AI翻译进行中StageFormatting ProgressStageformatting// 格式还原中StageDownloading ProgressStagedownloading// 下载翻译结果StageCompleted ProgressStagecompleted// 翻译完成StageFailed ProgressStagefailed// 翻译失败)// ProgressMessage 推送给前端的进度消息typeProgressMessagestruct{Stage ProgressStagejson:stageProgressintjson:progress// 0-100Messagestringjson:message// 用户可读的进度描述FileNamestringjson:file_name// 当前处理的文件名Errorstringjson:error,omitemptyResultURLstringjson:result_url,omitemptyTimestamp time.Timejson:timestamp}Step 2: WebSocket Hub管理pkg/ws/hub.go—— 管理所有WebSocket连接packagewsimport(encoding/jsonlogsyncws-translator-proxy/pkg/modelgithub.com/gorilla/websocket)// Client 单个WebSocket客户端连接typeClientstruct{conn*websocket.Conn sendchan[]bytehub*Hub taskIDstring}// Hub WebSocket连接管理中心typeHubstruct{clientsmap[*Client]boolbroadcastchan[]byteregisterchan*Client unregisterchan*Client mu sync.RWMutex}// NewHub 创建Hub实例funcNewHub()*Hub{returnHub{clients:make(map[*Client]bool),broadcast:make(chan[]byte,256),register:make(chan*Client),unregister:make(chan*Client),}}// Run 启动Hub的消息循环func(h*Hub)Run(){for{select{caseclient:-h.register:h.mu.Lock()h.clients[client]trueh.mu.Unlock()log.Printf(WebSocket 客户端已连接: %s,client.taskID)caseclient:-h.unregister:h.mu.Lock()if_,ok:h.clients[client];ok{delete(h.clients,client)close(client.send)}h.mu.Unlock()log.Printf(WebSocket 客户端已断开: %s,client.taskID)casemessage:-h.broadcast:h.mu.RLock()forclient:rangeh.clients{select{caseclient.send-message:default:// 客户端缓冲区满跳过close(client.send)delete(h.clients,client)}}h.mu.RUnlock()}}}// SendProgressTo 向特定客户端发送翻译进度func(h*Hub)SendProgressTo(taskIDstring,msg model.ProgressMessage){h.mu.RLock()deferh.mu.RUnlock()data,_:json.Marshal(msg)forclient:rangeh.clients{ifclient.taskIDtaskID{select{caseclient.send-data:default:}return}}}核心设计Hub维护所有活跃的WebSocket连接每个连接绑定一个taskID实现定向推送写操作保护客户端满缓冲区不会阻塞整个HubStep 3: 翻译任务处理器pkg/translator/proxy.go—— 翻译执行 进度推送packagetranslatorimport(fmtioostimews-translator-proxy/pkg/modelws-translator-proxy/pkg/wsgithub.com/go-resty/resty/v2)// ProxyTask 翻译代理任务typeProxyTaskstruct{hub*ws.Hub taskIDstringclient*resty.Client}// NewProxyTask 创建翻译代理任务funcNewProxyTask(hub*ws.Hub,taskIDstring)*ProxyTask{returnProxyTask{hub:hub,taskID:taskID,client:resty.New().SetTimeout(10*time.Minute),}}// Execute 执行翻译并实时推送进度func(p*ProxyTask)Execute(filePath,sourceLang,targetLangstring)error{p.sendProgress(model.StageUploading,10,正在上传文件到翻译服务...,filePath)// 模拟/实际调用的进度阶段// 上传阶段time.Sleep(500*time.Millisecond)p.sendProgress(model.StageUploading,30,文件上传完成开始预处理...,filePath)// 预处理阶段p.sendProgress(model.StageProcessing,40,AI模型加载中正在分析文档结构...,filePath)// 翻译阶段fori:50;i80;i10{time.Sleep(1*time.Second)p.sendProgress(model.StageTranslating,i,fmt.Sprintf(正在翻译第 %d/%d 页...,(i-45)*10/7,100),filePath)}// 格式还原阶段p.sendProgress(model.StageFormatting,90,翻译完成正在进行格式还原...,filePath)// 在实际项目中这里调用PDFTranslator API并监控进度// resp, err : p.client.R().// SetFile(file, filePath).// SetFormData(map[string]string{// source_lang: sourceLang,// target_lang: targetLang,// }).// Post(https://api.pdftranslator.org/v1/translate)time.Sleep(1*time.Second)// 完成p.sendProgress(model.StageCompleted,100,翻译完成文件已就绪,filePath)returnnil}func(p*ProxyTask)sendProgress(stage model.ProgressStage,progressint,message,fileNamestring){p.hub.SendProgressTo(p.taskID,model.ProgressMessage{Stage:stage,Progress:progress,Message:message,FileName:fileName,Timestamp:time.Now(),})}Step 4: HTTP路由和WebSocket升级main.go主入口packagemainimport(encoding/jsonlognet/httpws-translator-proxy/pkg/translatorws-translator-proxy/pkg/wsgithub.com/gorilla/websocket)varupgraderwebsocket.Upgrader{CheckOrigin:func(r*http.Request)bool{returntrue// 生产环境应限制Origin},}varhubws.NewHub()funcmain(){gohub.Run()// WebSocket端点http.HandleFunc(/ws/translate,handleWebSocket)// 健康检查http.HandleFunc(/health,func(w http.ResponseWriter,r*http.Request){w.Write([]byte(OK))})log.Println(WebSocket代理服务启动在 :8080)log.Fatal(http.ListenAndServe(:8080,nil))}funchandleWebSocket(w http.ResponseWriter,r*http.Request){conn,err:upgrader.Upgrade(w,r,nil)iferr!nil{log.Printf(WebSocket升级失败: %v,err)return}taskID:r.URL.Query().Get(task_id)iftaskID{taskIDfmt.Sprintf(task_%d,time.Now().UnixNano())}client:ws.Client{Conn:conn,Send:make(chan[]byte,256),Hub:hub,TaskID:taskID,}hub.Register-client// 启动读写协程goclient.WritePump()goclient.ReadPump()// 从URL参数获取翻译参数并发起翻译filePath:r.URL.Query().Get(file)targetLang:r.URL.Query().Get(target)iffilePath!targetLang!{task:translator.NewProxyTask(hub,taskID)gofunc(){iferr:task.Execute(filePath,,targetLang);err!nil{hub.SendProgressTo(taskID,model.ProgressMessage{Stage:model.StageFailed,Error:err.Error(),})}}()}}前端实现React进度展示frontend/src/App.jsximport { useState, useEffect, useRef } from react const STAGE_LABELS { uploading: 上传文件中, processing: 预处理中, translating: AI翻译中, formatting: 格式还原中, completed: 翻译完成, failed: 翻译失败 } function App() { const [progress, setProgress] useState({ stage: , progress: 0, message: }) const wsRef useRef(null) useEffect(() { const taskId task_${Date.now()} const ws new WebSocket(ws://localhost:8080/ws/translate?task_id${taskId}filereport.pdftargetzh) wsRef.current ws ws.onmessage (event) { const data JSON.parse(event.data) setProgress(data) } ws.onclose () console.log(WebSocket连接关闭) return () ws.close() }, []) return ( div style{{ maxWidth: 500, margin: 50px auto, fontFamily: system-ui }} h2PDF翻译进度/h2 {/* 进度条 */} div style{{ width: 100%, height: 24, background: #eee, borderRadius: 12, overflow: hidden, marginBottom: 16 }} div style{{ width: ${progress.progress}%, height: 100%, background: progress.stage failed ? #e74c3c : progress.stage completed ? #27ae60 : #3498db, transition: width 0.3s ease, borderRadius: 12 }} / /div {/* 百分比 */} div style{{ textAlign: center, fontSize: 32, fontWeight: bold, marginBottom: 8 }} {progress.progress}% /div {/* 当前阶段 */} div style{{ textAlign: center, color: #666, marginBottom: 4 }} {STAGE_LABELS[progress.stage] || 准备中...} /div {/* 详细信息 */} div style{{ textAlign: center, color: #999, fontSize: 14 }} {progress.message} /div {/* 完成状态 */} {progress.stage completed ( div style{{ marginTop: 24, padding: 16, background: #e8f5e9, borderRadius: 8, textAlign: center, color: #2e7d32 }} 翻译完成可以下载翻译后的文件了。 /div )} /div ) } export default App运行效果启动后端go run main.go# WebSocket代理服务启动在 :8080启动前端cdfrontendnpmrun dev翻译过程中的WebSocket消息流{stage:uploading,progress:10,message:正在上传文件到翻译服务...}{stage:uploading,progress:30,message:文件上传完成开始预处理...}{stage:processing,progress:40,message:AI模型加载中正在分析文档结构...}{stage:translating,progress:50,message:正在翻译第10/100页...}{stage:translating,progress:60,message:正在翻译第25/100页...}{stage:translating,progress:70,message:正在翻译第40/100页...}{stage:translating,progress:80,message:正在翻译第55/100页...}{stage:formatting,progress:90,message:翻译完成正在进行格式还原...}{stage:completed,progress:100,message:翻译完成文件已就绪}总结这个方案实现了翻译进度的实时可视化核心要点WebSocket双向通信比HTTP轮询效率高延迟低适合实时进度场景Go并发模型Hub的channel-based事件循环天然线程安全无需加锁处理注册/注销进度阶段细分uploading→processing→translating→formatting→completed让用户清楚知道当前处于哪个阶段定向推送通过taskID匹配避免广播到所有客户端这个架构也可以复用到其他需要实时进度展示的场景——文件上传、数据导出、视频转码等。标签WebSocket、Go、React、PDF翻译、实时推送

相关新闻