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翻译、实时推送

相关新闻

3分钟实现百度网盘满速下载:免费开源网盘直链下载助手完整指南

3分钟实现百度网盘满速下载:免费开源网盘直链下载助手完整指南

3分钟实现百度网盘满速下载:免费开源网盘直链下载助手完整指南 【免费下载链接】baiduyun 油猴脚本 - 一个免费开源的网盘下载助手 项目地址: https://gitcode.com/gh_mirrors/ba/baiduyun 还在为百度网盘几十KB/s的下载速度而烦恼吗?想要免费享受…

2026/10/30 4:08:21 阅读更多 →
AI会议记录工具横评:科会通、通义听悟、飞书妙记的自动整理效果有什么区别?

AI会议记录工具横评:科会通、通义听悟、飞书妙记的自动整理效果有什么区别?

我是互联网企业的资深产品运营专员,日常每周要跟进8场以上跨部门需求评审、用户访谈和项目复盘会,过去每次会后都要花1-2小时整理会议记录,经常因为漏记关键决策拖慢项目进度。最近不少同行都在问AI会议记录工具横评中科会通、通义听悟、飞书…

2026/11/2 10:58:09 阅读更多 →
AES查表加速:从算法原理到工程优化的性能飞跃

AES查表加速:从算法原理到工程优化的性能飞跃

1. 从“算”到“查”:AES性能优化的核心思路 在密码学领域,AES(高级加密标准)无疑是应用最广泛的对称加密算法之一。无论是保护你的HTTPS连接、加密硬盘上的文件,还是保障移动应用的数据安全,背后大概率都有…

2026/10/30 11:17:41 阅读更多 →

最新新闻

Electron桌面应用开发实战:从环境搭建到打包发布全流程

Electron桌面应用开发实战:从环境搭建到打包发布全流程

做前端这些年,我大部分时间都在跟浏览器标签页打交道,直到第一次把桌面应用跑起来,才发现原来自己一直被限制在地址栏和刷新按钮围起来的框框里。Electron这个词,最开始是在某段时间频繁出现在同事的讨论里,一个用HTML…

2026/11/3 1:09:25 阅读更多 →
滑盖智能锁T109深度解析:结构原理、安装调试与故障排查

滑盖智能锁T109深度解析:结构原理、安装调试与故障排查

简介:亚太天能T109滑盖智能锁产品说明书是一份PDF格式的官方技术文档,主要面向智能锁安装人员、终端用户及售后维护者,帮助快速掌握产品安装、配置与常见故障处理。全包仅1个PDF文件,大小4.37MB,内容结构清晰&#xff…

2026/11/2 10:46:11 阅读更多 →
PMSM弱磁控制原理与Simulink建模:高速AGV驱动系统设计指南

PMSM弱磁控制原理与Simulink建模:高速AGV驱动系统设计指南

上个月我在调试一台高速AGV的驱动系统,整车要求空载跑到8m/s,电机转速已经拉到额定点,驱动器的输出电压也顶到了母线电压极限附近。再往上踩速度指令,转速纹丝不动,三相电流反而开始震荡。那会儿我的第一反应就是&…

2026/11/2 10:47:40 阅读更多 →
C++核心知识点:从内存管理到STL的实战指南

C++核心知识点:从内存管理到STL的实战指南

C 这门语言我前前后后折腾了十几年,用它写过嵌入式驱动、做过游戏服务端,也搭过基础组件库。每次看到新人一头扎进"C 核心知识点"这个搜索词,我其实挺感慨的——这门语言的难点从来不是语法多,而是知识点之间的耦合太深…

2026/11/3 2:27:21 阅读更多 →
T109滑盖智能锁怎么装?安装调试与故障排查全攻略

T109滑盖智能锁怎么装?安装调试与故障排查全攻略

简介:亚太天能T109滑盖智能锁产品说明书以PDF格式发布,适合智能锁用户、安装人员与售后维修人员阅读。内容围绕T109系列指纹密码刷卡锁展开,先介绍前面板、后面板、锁体、机械钥匙、电池盒盖等部件名称与功能,再给出完整安装步骤&…

2026/11/2 10:46:31 阅读更多 →
LLM知识降熵:从信息垄断到理解权下放

LLM知识降熵:从信息垄断到理解权下放

你有没有过这种经历:翻开源码注释、论文摘要或者一份专业报告,每个字都认识,连在一起却像天书。想找人聊聊,身边要么没人懂,要么懂的懒得跟你解释。后来我试着把同一段内容丢给一个大语言模型,几秒钟之后&a…

2026/11/2 10:46:26 阅读更多 →

日新闻

周新闻

月新闻