返回公众号历史文章目录

文章 / 公众号历史文章

『JSA宏神助攻之五』使用搭建本地web服务让JSA宏连接本地文件读写

在JSA宏中,没有办法对本地的文件进行读写,虽然仿效VBA实现了一个FreeFile的函数来处理读写,但也仅限于文本文件的读写。 如下代码(接录于WPS Office JavaScript 宏教程(JS宏)): 读取可能还算好,写入很坑的,会将本来的内容,在前后加上了一个单引号,将原本好好的文本给破

在JSA宏中,没有办法对本地的文件进行读写,虽然仿效VBA实现了一个FreeFile的函数来处理读写,但也仅限于文本文件的读写。

如下代码(接录于WPS Office JavaScript 宏教程(JS宏)):

公众号文章插图

公众号文章插图

读取可能还算好,写入很坑的,会将本来的内容,在前后加上了一个单引号,将原本好好的文本给破坏了。

在JSA上没有,如何才能创造让它产生有?最有效的方式,就是提供一些外部服务,它来调用就完事了。这样的方式,也是无限扩展JSA宏的能力边界的一种非常有效的手段。

这些外部服务,最简单的,就是给它一个web服务来调用,当然可以自己架设个服务器来部署个web api服务,供JSA使用xmlhttpRequest或fetch来访问。

如果要在极端环境下使用,并且想调用本地资源,那就只有在本地电脑上搭建一个web服务就算事了。

如何搭建这个web服务,可能各路编程神仙又跑出来说用python/nodejs/r等各种方式搭建最方便啦,几句代码就立马开启了一个web服务。这个方便确实是方便,只是对开发者方便,对用户端,也不会太方便,单单满足有各种语言的运行环境就足够头大,就算打包整个环境到用户机器上使用,也是庞然大物。

既然是windows环境,除了.NET,谁还敢来称说比它容易的事。所以gpt时代,理当最合适的语言做最合适的事情。用.NET来搭建这个本地web服务准没错了。

在C#里,有个启动 OWIN 自托管的 Web API 服务,不用部署IIS,直接双击exe就开启了一个web 服务。代码也不复杂,毕竟是gpt吧,直接叫它写就完事了。具体的代码直接贴上,照抄来改进即可。

using System;using System.Collections.Specialized;using System.IO;using System.Net;using System.Text;using System.Threading;using System.Threading.Tasks;using System.Web;  
namespace SelfHostedWebApiConsole{    class Program    {        private static HttpListener _listener;        private static readonly string _url = $"http://localhost:{Properties.Settings.Default.WebApiPort}/";  
        static void Main(string[] args)        {            StartServer();  
            while (_listener.IsListening) // Loop to keep the main thread running while the listener is active.            {                Thread.Sleep(1000);            }        }  
        public static void StartServer()        {            _listener = new HttpListener();            _listener.Prefixes.Add(_url);            _listener.Start();  
            Console.WriteLine("Listening for connections on {0}", _url);  
            Task.Run(() =>            {                while (_listener.IsListening)                {                    try                    {                        var context = _listener.GetContext();                        ProcessRequest(context);                    }                    catch (HttpListenerException)                    {                        // This will occur on _listener.Stop()                        break;                    }                }            });        }  
        public static void StopServer()        {            if (_listener != null)            {                _listener.Stop();                _listener.Close();            }        }  
        private static void ProcessRequest(HttpListenerContext context)        {            var request = context.Request;            var response = context.Response;  
            try            {                switch (request.Url.AbsolutePath)                {                    case "/api/file/read":                        ProcessFileRead(request, response);                        break;                    case "/api/file/write":                        ProcessFileWrite(request, response);                        break;                    default:                        response.StatusCode = (int)HttpStatusCode.NotFound;                        break;                }            }            finally            {                response.OutputStream.Close();            }        }  
        private static void ProcessFileRead(HttpListenerRequest request, HttpListenerResponse response)        {            string filePath = GetFilePath(request);            filePath = HttpUtility.UrlDecode(filePath, System.Text.Encoding.UTF8);            var isBinary = bool.Parse(request.QueryString["isBinary"] ?? "false");            var encodingName = request.QueryString["encoding"] ?? "utf-8"; // 默认为 UTF-8  
  
            if (!File.Exists(filePath))            {                response.StatusCode = (int)HttpStatusCode.NotFound;                return;            }  
            if (isBinary)            {                var fileBytes = File.ReadAllBytes(filePath);                response.ContentType = "application/octet-stream";                response.OutputStream.Write(fileBytes, 0, fileBytes.Length);            }            else            {                Encoding encoding;                try                {                    encoding = Encoding.GetEncoding(encodingName);                }                catch (ArgumentException)                {                    response.StatusCode = (int)HttpStatusCode.BadRequest;                    var errorMessage = Encoding.UTF8.GetBytes("Invalid encoding specified.");                    response.OutputStream.Write(errorMessage, 0, errorMessage.Length);                    return;                }  
                var fileContent = File.ReadAllText(filePath, encoding);                var buffer = Encoding.UTF8.GetBytes(fileContent);                response.ContentType = "text/plain; charset=utf-8";                response.OutputStream.Write(buffer, 0, buffer.Length);            }  
            response.StatusCode = (int)HttpStatusCode.OK;        }  
        private static string GetFilePath(HttpListenerRequest request)        {            string rawUrl = request.RawUrl;            Uri uri = new Uri("http://dummy" + rawUrl); // 加前缀是因为Uri需要完整的格式  
            // 解析查询字符串            NameValueCollection queryParameters = HttpUtility.ParseQueryString(uri.Query);            string encodedFilePath = queryParameters["filePath"];            if (!string.IsNullOrEmpty(encodedFilePath))            {                string decodedFilePath = Uri.UnescapeDataString(encodedFilePath);                return Environment.ExpandEnvironmentVariables(decodedFilePath);            }            else            {                return string.Empty;            }  
        }  
        private static void ProcessFileWrite(HttpListenerRequest request, HttpListenerResponse response)        {            string filePath = GetFilePath(request);            var isBinary = bool.Parse(request.QueryString["isBinary"] ?? "false");            var encodingName = request.QueryString["encoding"] ?? "utf-8"; // 默认为 UTF-8  
            try            {                Encoding encoding = Encoding.GetEncoding(encodingName);                if (isBinary)                {                    // 二进制模式,直接从流中读取并写入文件                    using (var fs = new FileStream(filePath, FileMode.Create, FileAccess.Write))                    {                        request.InputStream.CopyTo(fs);                    }                }                else                {                    // 文本模式,使用指定的编码读取文本内容并写入文件                    using (var reader = new StreamReader(request.InputStream, encoding))                    {                        var content = reader.ReadToEnd();                        File.WriteAllText(filePath, content, encoding);  // 使用提供的编码写入文件                    }                }                response.StatusCode = (int)HttpStatusCode.OK;                var responseMessage = "File written successfully";                var responseBuffer = Encoding.UTF8.GetBytes(responseMessage);                response.OutputStream.Write(responseBuffer, 0, responseBuffer.Length);            }            catch (ArgumentException)            {                // 编码不支持或无效                response.StatusCode = (int)HttpStatusCode.BadRequest;                var errorMessage = Encoding.UTF8.GetBytes("Invalid encoding specified.");                response.OutputStream.Write(errorMessage, 0, errorMessage.Length);            }        }  
  
    }}

有了这个读写文件的接口,在JSA上,直接调用这个本地接口就完事了,也送上JSA的代码段:

async function testTextFileOperations() {    // 定义读取和写入的文件名及其他参数    const readFileName = 'C:/Users/19026/Desktop/测试文本.txt';    const writeFileName = 'C:/Users/19026/Desktop/测试文本写入.txt';    const isBinary = false; // 明确指定处理非二进制(文本)文件  
    // 构建读取请求的URL    const readUrl = `http://localhost:6789/api/file/read?filePath=${encodeURIComponent(readFileName)}&isBinary=${isBinary}`;  
    try {        // 使用fetch读取文本文件        const readResponse = await fetch(readUrl);        if (!readResponse.ok) {            throw new Error('Failed to read file: ' + await readResponse.text());        }        const textContent = await readResponse.text();        console.log('File read successfully:', textContent);  
        // 可以在此处修改textContent,例如添加一些文本        const modifiedContent = textContent + "\nAdditional line added by the script.";  
        // 第二步:将修改后的文本内容写入另一个文件        const writeUrl = `http://localhost:6789/api/file/write?filePath=${encodeURIComponent(writeFileName)}&isBinary=${isBinary}`;  
        // 使用fetch写入文本文件        const writeResponse = await fetch(writeUrl, {            method: 'POST',            headers: {                'Content-Type': 'text/plain; charset=utf-8'            },            body: modifiedContent        });  
        if (!writeResponse.ok) {            throw new Error('Failed to write file: ' + await writeResponse.text());        }        console.log('File written successfully');    } catch (error) {        console.error('Error:', error);    }}

笔者本来接口实现的是读取文件,可以是二进制,也可以是文本,遗憾的是二进制文件的读写,在JSA上失败了,还在找官方问原因,希望未来可以修复吧。

最后,在JSA中,使用Shell函数,来启用这个本地Web服务,启用完后,就可以使用http请求的方式,进行get/post请求,访问上面的读写文件的接口了(WPS最新版本竟然又有bug,这个Shell函数用不了!!!等他们修复好再尝试吧,先手动双击下exe运行测试)。

公众号文章插图

公众号文章插图

总结

现在的程序交互中,大量使用了web服务来作为程序间交互通信的手段。一般很少会自己和自己玩,在本地建立个web服务来访问。

但因为WPS的弱鸡性,只能用各种方式来增强它,使用web服务,是个不错的选择,特别是JSA现在原生支持发起http请求。

在web服务的搭建上,强烈建议使用.NET来完成,简单快捷,发布时的文件足够小,充分利用windows的现有环境跑起来(其他语言来搭建单单在用户机器上弄个环境头都大)。

欢迎持续关注本号给大家探索出的高能黑科技。也欢迎有RPA/BI/OFFICE二次开发等企业项目数据需求的,找笔者来定制支持,笔者将使用所积攒的各种技术给您交付一个高质量的解决方案。