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/8 23:54:00 阅读更多 →
AI会议记录工具横评:科会通、通义听悟、飞书妙记的自动整理效果有什么区别?

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

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

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

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

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

2026/10/11 0:14:53 阅读更多 →

最新新闻

gRPC从入门到实战:跨语言微服务通信与Python并发避坑指南

gRPC从入门到实战:跨语言微服务通信与Python并发避坑指南

我真的是太爱学习了——这种念头冒出来的时候,通常不是因为刚学会了一个妙招,而是因为刚被一个不算难的框架折腾到凌晨一点。最近这个对象就是 gRPC。很长一段时间里,我提到 gRPC 都是能躲就躲:要写 .proto、要装 protoc、跨语言还…

2026/10/12 8:44:35 阅读更多 →
从人耳到算法:MFCC梅尔倒谱系数原理与实战调参指南

从人耳到算法:MFCC梅尔倒谱系数原理与实战调参指南

1. 从人耳到算法:为什么需要梅尔倒谱系数做语音相关项目的人,迟早会撞上MFCC这个词。不管你是做语音识别、声纹比对、情感计算,还是只是想做个简单的关键词唤醒,翻开源码或者论文,MFCC几乎无处不在。但很多人第一次看到…

2026/10/12 8:44:35 阅读更多 →
2025制造业AI落地实战:视觉质检、预测性维护与排程优化

2025制造业AI落地实战:视觉质检、预测性维护与排程优化

1. 制造业AI落地的真实图景:从概念狂欢到车间实战2025年过半,我跑了十几个城市的制造企业,从年产值几十亿的汽车零部件龙头到只有百来号人的精密加工厂,聊下来最大的感受是:制造业对AI的态度已经彻底变了。前两年大家还…

2026/10/12 8:44:35 阅读更多 →
3个爆火Claude Code工具深度解析:TaoToken统一Key下如何选对赛道

3个爆火Claude Code工具深度解析:TaoToken统一Key下如何选对赛道

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/12 8:44:35 阅读更多 →
一站式接入Kimi K3:OpenAI协议兼容的零改造迁移方案

一站式接入Kimi K3:OpenAI协议兼容的零改造迁移方案

1. 项目概述:为什么“一站式接入Kimi K3”突然成了刚需?最近两周,我陆续收到七八位不同背景的朋友咨询——有做教育类SaaS产品的技术负责人,有独立开发AI助教工具的自由开发者,还有某高校实验室带学生做对话系统课题的…

2026/10/12 8:44:35 阅读更多 →
OpenAI DevDay 2026:dots、Spaces与GPT-6.1 Sol的工程化落地指南

OpenAI DevDay 2026:dots、Spaces与GPT-6.1 Sol的工程化落地指南

1. 这不是发布会速记,而是一份开发者视角的“技术落地可行性评估报告”OpenAI DevDay 2026 这个标题本身已经释放出强烈的信号:它不再是一场面向大众的炫技秀,而是一次明确指向工程实践边界的深度技术校准。我全程参与了线上直播与会后48小时…

2026/10/12 8:43:30 阅读更多 →

日新闻

复古胶片颗粒感噪点合成器:Canvas ImageData 像素高斯杂色注入算法

复古胶片颗粒感噪点合成器:Canvas ImageData 像素高斯杂色注入算法

在数码相机、高清显示屏与现代矢量图形技术高度发达的今天,画面可以做到绝对的锐利、平滑与无瑕。然而,当一张秋日手账插画或拍立得照片过于“平整无瑕”时,往往会散发出一种冰冷生硬的“数码塑料感(Digital Plasticity&#xff0…

2026/10/12 0:00:59 阅读更多 →
活字印刷古籍线装排版:Canvas 竖排文字与栏线自适应算法

活字印刷古籍线装排版:Canvas 竖排文字与栏线自适应算法

在现代网页与移动端设计中,横排(Horizontal Layout)早已经成为了绝对的主流。然而,当我们翻开泛黄的线装古籍、宋版木刻诗集,或是欣赏一张茶道雅集的手写便签时,那种**自上而下纵向书写、自右向左逐列铺展&…

2026/10/12 0:00:59 阅读更多 →
周日晚间的“精神松绑减震器”:无压力情绪倾倒箱与温和轻声陪伴

周日晚间的“精神松绑减震器”:无压力情绪倾倒箱与温和轻声陪伴

每到周日的晚上八点到十点,很多人心里都会悄悄亮起一盏警示灯。 在心理学上,这种现象有一个专门的称谓——“周日夜晚焦虑症(Sunday Scaries)”。明天又是周一,闹钟又要重新在七点响彻卧房;脑海里仿佛有一个…

2026/10/12 0:00:59 阅读更多 →

周新闻

流感时间序列预测实战:ARIMA/LSTM全流程拆解与避坑指南

流感时间序列预测实战:ARIMA/LSTM全流程拆解与避坑指南

简介:基于 ARIMA、LSTM、Transformer 等模型的流感时间序列预测 Python 源码,面向计算机相关专业课程设计与期末大作业学生,以及项目实战学习者。内容覆盖预处理、平稳性检验、定阶、残差分析、多模型对比预测的完整时序建模流程,…

2026/10/12 0:16:30 阅读更多 →
影刀RPA新手教程:键盘模拟输入实战——输入文本与模拟按键的区别

影刀RPA新手教程:键盘模拟输入实战——输入文本与模拟按键的区别

影刀RPA新手教程:键盘模拟输入实战——输入文本与模拟按键的区别 做影刀RPA自动化,十个新手有八个栽在"往输入框里填东西"这件事上:要么填不进去,要么填了一半,要么直接把原来内容追加在后面。这背后的根因&…

2026/10/12 0:16:38 阅读更多 →
影刀RPA新手教程:阅文起点小说数据采集实战——书籍信息与章节内容

影刀RPA新手教程:阅文起点小说数据采集实战——书籍信息与章节内容

影刀RPA新手教程:阅文起点小说数据采集实战——书籍信息与章节内容 1. 认识影刀:什么场景该用RPA采小说数据 起点中文网的页面结构相对稳定——分类榜单、书籍详情、章节内容三块独立页面,跳转链路清晰。这种场景非常适合影刀自动化&#x…

2026/10/12 0:16:43 阅读更多 →

月新闻

我发现了一个新思路:用 Remotion + Claude Code 像写代码一样自动化生成短视频

我发现了一个新思路:用 Remotion + Claude Code 像写代码一样自动化生成短视频

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/11 10:45:37 阅读更多 →
Windows下 Codex 中 Chrome 和 Computer Use 插件不可用问题排查及解决参考方式:TaoToken 统一 Key 配置与验证

Windows下 Codex 中 Chrome 和 Computer Use 插件不可用问题排查及解决参考方式:TaoToken 统一 Key 配置与验证

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/11 14:36:53 阅读更多 →
黑夜航拍船只数据集训练YOLOV5模型全流程解析

黑夜航拍船只数据集训练YOLOV5模型全流程解析

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/11 14:36:54 阅读更多 →