☰
网页调用本地WPS:基于Node.js的HTTP桥接服务与实战
2026/10/10 3:54:39 网站建设 项目流程

简介:这是一份面向WPS插件开发者的示例项目,演示如何通过JS-SDK在网页中调用并操作WPS,适用于集成在线文档处理能力的Web应用,也适合熟悉Node.js或前端技术栈的开发者参考。项目覆盖了WPS插件从初始化、注册到任务调用的基本流程,并提供了可运行的demo页面。

压缩包共161个文件,体积仅1.52MB。除18个JavaScript、6个TypeScript、6个HTML、5个JSON、2个CSS等核心源码外,还包含11个示例文件、3个Markdown说明、3个Word文档,以及SVG图标、XML配置、TTF字体等资源,结构清晰,便于对照学习。同时,压缩包中保留了Git对象数据与仓库元信息,方便开发者查看提交历史与演进思路。

目前已有1119人下载学习。通过这套demo,开发者可以快速理解WPS开放平台的接口调用细节,获得插件骨架、前后端交互范例和文档说明。其中示例覆盖了常见的文档打开、编辑、保存等操作,参数与事件回调均有注释,配合文档可降低二次开发门槛,直接加速业务系统接入。

1. wps-js-demo:网页怎么把文件“递”给桌面的 WPS

wps-js-demo 解决的是一个看似小、实际很容易卡住的问题:网页上点一个按钮,桌面装着的 WPS 直接把对应文档打开。浏览器出于沙箱限制,既拿不到本地文件的绝对路径,也没办法直接启动 exe,用 window.open 指到一个 .docx 只会触发下载,而不是唤起 WPS。它的做法是用 Node.js 在本地起一个 HTTP 服务,网页把文件路径发给这个服务,服务再把路径通过命令行交给 WPS。这个 demo 把网络层、进程调用层、参数校验层拆成了清晰的小模块,适合正在做“网页与本地办公软件联动”的开发者,先把链路跑通,再逐步往上加保存、读取、批量转换这些能力。

2. 桥接原理与选型:为什么网页不能直接操作 WPS,三条路怎么挑

2.1 浏览器沙箱与“必须存在的中间层”

浏览器能做的只有 HTTP 请求、DOM 操作、以及受控的文件选择,拿不到用户机器的完整文件系统视图。这是安全设计,不是缺陷,但当你需要把“网页里的某个按钮”和“桌面的 WPS”接起来时,就缺了一个能同时接触两者的角色:网页够得着 HTTP,WPS 够得着进程和文件系统,中间必须有一个常驻的本地进程来转译请求。

这个中间层接管的是两类工作:第一类是把网页的 HTTP 请求转换成 WPS 可执行程序能理解的命令行参数;第二类是把 WPS 侧的执行结果(成功、失败、是否真的打开了文件)封装成 JSON 回给网页。wps-js-demo 用 Node.js 承担这个角色,理由很直接:网页本身是 JS 写的,Node 也讲 JS,两端共享同一套数据类型和心智模型,调试时不用在两种语言之间来回切换。

整个调用链可以看成一个流水线,我拆成七步来理解:

1. 网页 index.html 里的按钮被点击 2. 网页发起 fetch 请求到 http://127.0.0.1:8765/api/open 3. Node.js 的 server.js 接收到请求并解析 JSON 4. server.js 调用 src/wps.js 里的 open 方法 5. open 方法用 spawn 启动 wps.exe,并把文件路径作为参数传进去 6. WPS 打开文档,Node 立即把回执 JSON 返回给网页 7. 网页拿到回执,更新按钮状态,再轮询 check 接口确认结果

这里有一个非常容易被新手忽略的点:spawn 出去的 wps.exe 会一直开着文档,整个调用过程并不是“打开完就回收进程”,所以 HTTP 接口不能傻等子进程退出,否则网页请求会一直挂着。demo 里的做法是发出 spawn 之后立即返回回执,打开结果的确认由单独的检查接口负责,这个设计后面会细说。

2.2 三条实现路径的横向对比

在真正动手之前,我建议先把三条路放在一张表里看清楚,因为很多人在网上搜“网页调用 WPS”会看到三种完全不同的方案,容易绕晕。

路径原理能做什么主要限制适合场景
纯命令行调用Node spawn 直接执行 wps.exe 并传文件路径打开文档、打印(部分版本)、转格式(受限)拿不到文档内容,无法读取/改写正文只做“打开”这一个动作的最小 demo
本地 HTTP 桥接(本 demo)网页请求 → Node 本地服务 → 命令行/脚本驱动 WPS打开、状态查询、目录浏览,可按需加接口初次要处理跨域与路径编码,内容级操作仍需扩展需要一个可持续扩展的通用桥
WPS 加载项 / JS 宏在 WPS 内部运行 HTML/JS 插件,直接调 WPS 对象模型可以读写文档内容、做选区、改样式插件运行在 WPS 壳内,不是“网页调用”,调试门槛高深度操作文档结构,而不是只打开文件

三种方案里,最像“网页调用”的是第二条路,因为调用方是浏览器里的页面,WPS 是被动的被驱动方,中间隔着一个可替换的 Node 进程。第一条路作为最小验证很好用,我经常先拿它确认 WPS 支持哪些命令行参数,再决定要不要引入第三条路;第三条路功能最强,但它本质是“插件”,网页只是给它发指令的遥控器,不适合作为这个 demo 的主线。

2.3 为什么 wps-js-demo 采用了“HTTP 桥接 + 命令行”的双层结构

打开文档这个动作,完成它最少需要两步:第一步是把文件路径传到 WPS,第二步是让网页知道“打开成功了”。这两步如果合并到一个接口里,就要处理进程退出事件和超时逻辑,网页端的等待体验会很差。demo 把它拆成了 /api/open 和 /api/check 两个接口,前者只管发出打开指令,后者轮询确认结果,这是我很认可的一个设计,也让后面做状态记录、最近打开列表变得很简单。

命令行层的可执行文件选择,我一般会根据扩展名来:.doc/.docx 走 wps.exe,.xls/.xlsx 走 et.exe,.ppt/.pptx 走 wpp.exe。这不是官方的强制规则,但这样至少保证桌面端是用对的组件打开的,尤其表格文件,你用 wps.exe 打开它也能开,但表格的加载项和菜单不会全部激活,后面要做表格相关的 API 就吃亏了。

桥接层的接口返回格式也值得一开始就固定好。demo 里统一返回 { code, message, data },code 为 0 表示成功,非 0 表示具体错误码。这样做的好处是网页端只需要写一个统一的响应处理函数,不用针对每个接口写一遍错误判断,后面加接口时心智负担小很多。

3. 跑起来:Node.js 环境、目录结构与第一声“打开”

3.1 环境准备与常见版本前提

这个 demo 的运行环境很常规:Windows 系统上装着 WPS 2019 或更高版本,机器上有 Node.js 12 以上的运行时,npm 随 Node 一起装好。理论上 Node 的版本越高越好,但 demo 代码里用到的 child_process、express、path 都是老牌 API,不必追求新版本,Node 14 或 16 都能稳定跑。

安装依赖就一条命令,在项目根目录执行:

npm install

如果网络环境导致 npm 安装慢,可以用镜像源,但这里不展开。安装完成后先不要急着启动,先把 config.js 打开看一眼,因为里面有两个值直接影响能否调起 WPS:一个是 WPS 的安装目录探测方式,另一个是服务监听端口。

3.2 目录结构:看懂再动手

demo 的目录不大,但每一块的职责分得很清楚,我直接把精简后的结构贴在下面:

wps-js-demo/ ├── package.json ├── config.js ├── server.js ├── src/ │ ├── wps.js │ └── utils.js └── public/ └── index.html

package.json 负责声明依赖和启动脚本;config.js 集中放端口、WPS 安装目录、超时时间这几类易变参数;server.js 是入口,负责启动 HTTP 服务并把请求路由到具体处理函数;src/wps.js 封装所有和 WPS 进程打交道的逻辑,包括选择可执行文件、spawn、检查进程状态;public/index.html 是演示用的网页,里面只有几个按钮和一个日志区域,用来模拟真实业务系统的调用端。

我会严格遵守“server.js 里不写 spawn 逻辑、src/wps.js 里不写路由逻辑”这个边界。新手最常见的翻车方式是把进程调用代码直接堆在路由回调里,第一次跑通没问题,等要加第二个接口时,代码就开始变得没法维护了。

3.3 打开第一份文档

先看 config.js,这是启动前唯一需要人工确认的文件:

// config.js const path = require('path'); module.exports = { // 服务监听端口,避开 80/8080/3000 等常见端口,减少冲突误伤的概率 port: 8765, // WPS 安装目录,优先取环境变量,取不到再走默认路径 // 如果你的 WPS 装在 D 盘或自定义目录,请改这里 wpsDir: process.env.WPS_DIR || path.join(process.env.ProgramFiles || 'C:/Program Files', 'WPS Office'), // 从发出打开指令到判定“打开失败”的超时时间,单位毫秒 timeout: 15000, // 允许访问的 Origin,null 表示不限制(仅限本地调试) allowedOrigin: null };

这里的 wpsDir 我是故意不写死具体盘符的,因为每台机器的 WPS 安装位置不一样。你可以在 WPS 桌面图标上右键,选“打开文件所在位置”,把那个目录直接填进来,注意 config.js 里路径分隔符建议用正斜杠或双反斜杠,避免转义出问题。

接着是 server.js 的核心部分,只保留和打开文档相关的逻辑:

// server.js const express = require('express'); const wps = require('./src/wps'); const config = require('./config'); const app = express(); app.use(express.json()); app.post('/api/open', (req, res) => { const { filePath } = req.body || {}; if (!filePath || typeof filePath !== 'string') { return res.status(400).json({ code: 400, message: 'filePath 不能为空' }); } try { const result = wps.open(filePath); res.json({ code: 0, message: '打开指令已发出', data: result }); } catch (err) { res.status(500).json({ code: 500, message: err.message }); } }); app.listen(config.port, () => { console.log(`wps-js-demo 已启动: http://127.0.0.1:${config.port}`); });

这段代码做了三件事:解析 JSON 请求体、校验 filePath 参数、调用 wps.open 并返回标准 JSON。注意我用的是 POST 而不是 GET,因为打开文件的路径里很可能带特殊字符,放 query string 上会有转义和长度问题,POST 请求体里直接带原始字符串更稳。

src/wps.js 里最关键的函数长这样:

// src/wps.js const { spawn } = require('child_process'); const path = require('path'); const fs = require('fs'); const config = require('../config'); function getExecutable(filePath) { const ext = path.extname(filePath).toLowerCase(); // 根据扩展名选择 WPS 组件,表格和演示文稿走各自的可执行文件 if (ext === '.xls' || ext === '.xlsx') return 'et.exe'; if (ext === '.ppt' || ext === '.pptx') return 'wpp.exe'; return 'wps.exe'; } function open(filePath) { if (!fs.existsSync(filePath)) { throw new Error(`文件不存在: ${filePath}`); } const wpsExecutable = getExecutable(filePath); const wpsBin = path.join(config.wpsDir, 'office6', wpsExecutable); if (!fs.existsSync(wpsBin)) { throw new Error(`找不到 WPS 可执行程序: ${wpsBin}`); } // 用 spawn 而非 exec:参数数组传递,避免路径中的空格和中文被命令行重新解析 const child = spawn(wpsBin, [filePath], { detached: true, stdio: 'ignore' }); child.unref(); return { pid: child.pid, executable: wpsExecutable }; } module.exports = { open };

这里有三个设计点值得展开。第一,检查文件是否存在和可执行程序是否存在,都放在进程调用之前,把错误尽早地抛出来,避免网页端拿到一个“看起来成功但实际没打开”的假回执。第二,spawn 的第三个参数里 detached 和 stdio 是配套使用的,detached 让子进程独立运行,stdio 设为 ignore 则 Node 自己不监听子进程的输出流,两个一起用才能让 WPS 作为独立窗口存在,而不是把 Node 服务绑死在一起。第三,child.unref() 是关键,它告诉 Node“别等这个子进程退出”,否则 Node 事件循环会被 wps.exe 一直占住,服务会在打开文档后变得卡顿甚至无法退出。

启动命令是:

npm start

看到控制台输出“wps-js-demo 已启动”后,用浏览器打开 http://127.0.0.1:8765,在输入框里填一个本地文本或 Word 文件的绝对路径,点“打开”,如果一切正常,桌面上应该会跳出 WPS 并打开那份文件。

4. 网页端调用与接口设计:三个接口把打开、状态、目录列齐

4.1 /api/open:打开文档并带回执

网页端调用这个接口的代码很简单,就是一个标准 fetch,但要注意 console 里的日志要配合后端回执解读,不能只看网络请求状态码是 200 就说成功。我建议把回执的 JSON 完整打出来看一眼再判断:

// public/index.html 里的核心调用函数 async function openFile(filePath) { const resp = await fetch('/api/open', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ filePath }) }); const result = await resp.json(); // code 非 0 时不代表 HTTP 出错,而是业务级失败,必须单独判断 if (result.code !== 0) { console.error('打开失败:', result.message); return; } console.log('打开指令已发出,pid =', result.data.pid); // 这里不要立刻给用户提示“打开成功”,要等 check 接口确认 checkStatus(filePath); }

注意这里我特意写了“不要立刻提示成功”。打开指令发出和文档真正显示在屏幕上之间,隔着 WPS 的启动时间和文件加载时间,体验上应该用按钮 loading 来过渡,让用户感受到操作在推进。

/api/open 的参数设计和响应格式我用一张表固定下来,方便你对照调试:

字段位置类型说明
filePath请求体string要打开的本地文件绝对路径,必须真实存在
code响应体number0 表示成功,400 参数错误,500 服务端异常
message响应体string人类可读的结果描述
data.pid响应体numberWPS 子进程的 pid,注意第二次调用时可能拿到的是已存在进程的 pid
data.executable响应体string实际调用的可执行程序名,wps.exe / et.exe / wpp.exe

4.2 /api/check:轮询确认文件真的被打开

前面反复说“不能只看 open 接口”,因为 WPS 是单例进程,反复打开同一个文件时,spawn 返回的 pid 是同一个,光靠 pid 不可靠。demo 里加了一个 check 接口,用系统命令去查 WPS 进程的命令行里是否包含目标路径:

// src/wps.js 里新增的检查逻辑 const { execSync } = require('child_process'); function isOpen(filePath) { try { // wmic 查询所有 wps/et/wpp 进程的命令行,再判断目标路径是否在其中 const cmd = 'wmic process where "name=\'wps.exe\' or name=\'et.exe\' or name=\'wpp.exe\'" get commandline /format:list'; const stdout = execSync(cmd, { timeout: 5000, encoding: 'utf-8' }); return stdout.toLowerCase().includes(filePath.toLowerCase()); } catch (err) { // wmic 在某些精简系统上不可用,这里降级为只看进程是否存在 return false; } }

这个做法有一个精度问题:WPS 打开文档后,命令行里通常会保留当时的完整路径参数,所以 includes 判断是可行的,但路径大小写可能不一致,所以我在判断前统一转成小写。如果你用的系统没有 wmic,可以用 PowerShell 的 Get-CimInstance 替代,两种方案在 demo 的 README 里都有写。

对应的路由代码:

app.post('/api/check', (req, res) => { const { filePath } = req.body || {}; if (!filePath) { return res.status(400).json({ code: 400, message: 'filePath 不能为空' }); } const opened = wps.isOpen(filePath); res.json({ code: 0, data: { opened } }); });

它的价值在于:打开失败时,前端可以持续轮询这个接口,超时后给出“WPS 未能打开该文件”的明确提示,而不是闷声失败。我在实际业务里会把轮询间隔设为 1.5 到 2 秒,太频繁会让本就不快的机器雪上加霜。

4.3 /api/list:让网页拿到本地目录

这是从“demo”走向“能用”的一个重要补充接口。浏览器里的 File 对象拿不到绝对路径,远程部署的业务网页更拿不到本地目录,怎么办?让 Node 侧去列目录,把结果交给网页选择:

// server.js 里的目录浏览接口 const fs = require('fs'); const path = require('path'); app.post('/api/list', (req, res) => { const { dir } = req.body || {}; const targetDir = dir || 'C:/'; fs.readdir(targetDir, { withFileTypes: true }, (err, entries) => { if (err) { return res.status(400).json({ code: 400, message: `目录不存在或无权访问: ${err.message}` }); } const items = entries.map((entry) => ({ name: entry.name, type: entry.isDirectory() ? 'dir' : 'file', path: path.join(targetDir, entry.name) })); res.json({ code: 0, data: { dir: targetDir, items } }); }); });

网页端拿到 items 后就能渲染出一个简易文件选择器,用户点目录层层进入,点文件则回调 openFile。这一步把“网页无法感知本地路径”的硬伤绕过去了,也是这个 demo 在演示之外最有实用价值的一部分。

需要注意的是,/api/list 默认从 C:/ 列起,这在演示环境没问题,但放到真实内网业务里时,应该让后端限制可访问的根目录范围,否则任何网页都能读取本机目录结构,风险不可接受。后面进阶章会讲怎么把这个口子收紧。

5. 踩坑实录:跨域、单例、挂起与路径编码的四个坑

5.1 现象:远程网页调用本地服务时直接报 CORS 错误

当你的页面是 http://127.0.0.1:8765 本身时没有这个问题。一旦把页面部署到内网其他机器,或者放在 http://localhost:8080,浏览器控制台就会报“No 'Access-Control-Allow-Origin' header is present”。原因很简单:fetch 从 8080 端口发往 8765 端口,跨源了,浏览器默认拦截。

解决:在 server.js 里统一加 CORS 响应头,demo 的做法是加载 express 中间件,手动设置允许的源。本地调试时可以直接放开,但内网部署时建议限定为业务页面的具体源。

app.use((req, res, next) => { res.setHeader('Access-Control-Allow-Origin', config.allowedOrigin || '*'); res.setHeader('Access-Control-Allow-Methods', 'POST, OPTIONS'); res.setHeader('Access-Control-Allow-Headers', 'Content-Type, X-Token'); if (req.method === 'OPTIONS') return res.sendStatus(204); next(); });

如果用了 allowedOrigin 白名单,记得把 options 请求也放行,否则浏览器预检直接拦截,接口根本到不了后端。我在这里踩过一次,加了 Allow-Origin 却忘了 Allow-Headers,结果每次调用都先被预检掐断,控制台报错看了十分钟才发现是中招。

5.2 现象:路径含空格或中文时,文档迟迟没反应

用 exec 拼接命令字符串时,路径里有空格会被命令行拆分,中文路径在部分区域设置下会出现乱码。真实业务里用户的文件路径往往既有空格又有中文,这个坑几乎必踩。

原因:exec 走的是 shell 解析,参数经过一次字符串拼接和词法分析,带引号规则很麻烦;spawn 走的是直接传参,不经过 shell 重新解析。

解决:所有进程调用一律用 spawn(或 spawnSync),参数以数组形式传递,不要把整个命令行拼成一个字符串。这是血泪经验,也是 demo 里从一开始就坚持 spawn 的原因。

// 正确:参数数组 spawn(wpsBin, [filePath]); // 错误:字符串拼接,路径里有空格就炸 exec(`"${wpsBin}" "${filePath}"`);

如果你确实需要对输出做捕获,又想避开 shell 解析,可以把 spawn 的 stdio 配成 pipe,再用子进程的 stdout 和 stderr 事件去收集,不要退回到 exec。

5.3 现象:第二次调用同一个文件,拿到的 PID 和第一次一样

WPS 桌面端默认是单例进程,后续打开文件的操作会复用已有进程,所以 spawn 返回的 child.pid 指向的是同一个进程,pid 大小并不能代表窗口数量或文档数量。

原因:WPS 的架构决定了它不再为每个文档新建进程,这与 Word 老版本的行为不同。

解决:不要把 pid 当作“第几个窗口”的标识,只把它当作“调用是否发出”的回执。要确认文档是否真的打开,用开头的 /api/check 接口查进程命令行里是否出现目标路径,而不是数进程数量。

// 错误:用 pid 判断是否是新窗口 if (result.data.pid !== previousPid) { console.log('这是一个新窗口'); } // 正确:把 pid 当作回执,把 check 结果当作真相 const { opened } = await checkApi(filePath); console.log(opened ? '文件已在 WPS 中打开' : '打开失败或尚未加载完成');

这个坑在 demo 的 README 里有专门一段说明,因为我一开始也天真地准备用 pid 维护窗口列表,后来发现根本行不通,才改成 check 轮询方案。

5.4 现象:接口请求一直 pending,过一会整个 Node 服务卡住

第一次跑 demo 时,我调完 /api/open 后 HTTP 请求迟迟不返回,最后浏览器直接超时。原因是 spawn 出来的 wps.exe 不退出,如果代码里在等 close 事件或 promise.resolve,这个等待永远不会结束,Node 事件循环被占满,新请求也进不来。

原因:WPS 是长驻进程,不像命令行工具那样执行完就退出。open 接口的业务逻辑应该在“指令发出后”就结束,而不是“进程退出后”。

解决:使用 detached + unref 的组合,让 Node 与子进程解耦。代码里明确只做“spawn 和返回 pid”,不监听 close/exit 事件,除非你做的是打印或转换这类本来就需要等待退出的操作。

// 错误:等一个永远不会退出的进程 const child = spawn(wpsBin, [filePath]); child.on('exit', () => resolve()); // 正确:发出指令即返回,退出与否交给操作系统 const child = spawn(wpsBin, [filePath], { detached: true, stdio: 'ignore' }); child.unref(); resolve({ pid: child.pid });

如果确实需要等待,比如调 WPS 做文档转换并等待转换完成,必须加一个超时定时器去兜底,不然偶发一次界面卡死就够你排查半天。

5.5 现象:启动服务时报 EADDRINUSE,端口被占

8765 这个端口不是系统保留端口,但业内在很多本地服务里会用到,运行多个 demo 或老服务残留时,会直接启动失败。

原因:端口被占用,Node 无法绑定监听地址。

解决:启动逻辑中加入端口占用提示,甚至自动向后递增寻找可用端口。平时用 netstat 先查一遍也可以,但 demo 里更推荐直接报错退出,省得接手的同事(包括未来的你)在原地卡住。

const server = app.listen(config.port); server.on('error', (err) => { if (err.code === 'EADDRINUSE') { console.error(`端口 ${config.port} 被占用,换端口启动或先结束占用进程`); process.exit(1); } });

如果你经常在一台机器上跑多个本地服务,建议在启动脚本里加一句 netstat 查端口,把“端口占用”这个常见翻车点提前暴露在日志里,而不是等浏览器访问失败再去抓包。

6. 进阶:加一层 token 与目录浏览,把它变成可交给别人的本地工具

前面的链路已经能完成“网页 → 本地服务 → WPS”的打开动作,但直接这样交给业务方,有两个隐患:一是任何网页都能无鉴权地调用本地服务,恶意页面可以让你的 WPS 反复开关文件;二是远程页面拿不到本地路径,文件选择体验很糟糕。这一章把这两点补上,让 demo 变成可交付的本地工具雏形。

首先给服务加一层轻量鉴权。启动时在 config 里配一个启动 token,调用接口时要求请求头 X-Token 与之一致,服务端用中间件统一校验:

// server.js 里追加的 token 中间件 app.use((req, res, next) => { // /api/list 这种给前端用的接口也必须校验,不能因为“只是列目录”就放行 const token = req.headers['x-token']; if (config.token && token !== config.token) { return res.status(401).json({ code: 401, message: 'token 无效' }); } next(); });

token 怎么传给网页?本地调试时可以直接把 token 写进 config 与前端常量,但真正交付时应该让网页通过一次性授权码换取 token,或者用系统剪贴板/二维码中转。一个更省事的降级方案是只校验 Origin 白名单,配合所有请求从内网网关反代进来,也能把风险压到可接受水平。

第二件事是组合交互:/api/list 列目录 → 网页渲染文件列表 → 点击文件调 /api/open → 轮询 /api/check。把这四段串起来,用户就能在不碰命令行的情况下完成整个操作。我一般在 public/index.html 里做一个两栏布局,左边目录树,右边打开结果日志,点击目录项进入子目录,点击文件直接触发打开。

最后给一个自检清单,你照着过一遍就敢把 demo 展示给别人了:

检查项操作方式预期结果
服务启动npm start控制台输出端口监听日志
打开 Word/api/open 传入 .docx 路径WPS 打开文档,接口返回 code 0
打开 Excel/api/open 传入 .xlsx 路径et.exe 打开文档,接口返回 code 0
中文路径路径含“测试文档 01.docx”正常打开,无乱码
跨域调用从 8080 端口页面调用不报 CORS 错误
Token 拦截不带 X-Token 调用返回 401

这套 demo 跑顺之后,我已经把它用在一个内部系统的“合同预览落地”场景里:网页端点合同编号,经过审批流后自动调起本地 WPS 打开合同原文,省去手动找文件的时间。从那以后我每次写这种本地桥接服务,都强制走一遍五连查:端口是否固定、路径是否验存在、spawn 是否 detached、响应是否带 code、token 是否在场。顺序错了就停下来重新捋,少一步后面都要用加班来还。希望这个 demo 的思路和这些坑能帮你在自己的项目里少走一段弯路。

本文还有配套的精品资源,点击获取

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询