JS邊角料: 通過JS實現局域網多端設備圖片/文字互傳(NodeJS+AutoJS+WebSocket+TamperMonkey+Workflow)


---閱讀時間約 7 分鍾,復現時間約 15 分鍾---

由於之前一直在用的擴展 QPush 停止服務了,苦於一人湊齊了 Window, Android, Mac, ios 四種系統的設備,Apple與其他廠商提供的互傳又無法協同,有時只是需要在多設備使用同一串文字就在通訊App之間輾轉登錄非常影響當下如火如荼的狀態,甚至當微信發送長文字時,微信還會偷偷的剪裁,而且從 QPush 以后市面竟然沒有找到任何一款既不打廣又這樣輕量的文字協同App,一怒之下自己寫了這樣一套基於瀏覽器的簡易工具。

本文從配置到代碼,內容較多小白友好,老司機們直接右下角菜單按鈕索引到代碼部分吧。


糧草先行

- Node.js

Node.js 是一個跨平台 JavaScript 運行環境,使開發者可以搭建服務器端的JavaScript應用程序。 [ MDN ]

如果你的項目夠健全,WebServer 是由許多模塊構成的,但對於非職業選手來講,只要理解為  視圖層  和  服務層  就成,服務層提供數據,視圖層負責渲染,js 一直曾作為一個僅實現視圖層的腳本語言,必須基於瀏覽器且 Web API 貧瘠、瀏覽器廠商特立獨行的割據時代已經過了,現在的 js 可以寫 瀏覽器視圖層 / 服務層、App、小程序、游戲、PC客戶端、3D動畫 等等等,Node.js 可以稱得上是改變前端命運的語言之一了。如MDN所述它可以使用js語言,為  視圖層  提供服務、數據。

Download: [ 官網 ]

- Auto.js

一個在Android、鴻蒙平台編寫、運行JavaScript代碼的集成開發環境,包括代碼補全的編輯器、單步調試、圖形化設計,可構建為獨立apk應用,也可連接電腦開發。 [ 官方文檔 ]

如果你使用的是 ios,JSBox / Scriptable 可以替代 AutoJS,如果你會編寫JS, 那么workflow 從此可以棄用了,當然假如你更熟悉其他語言,那么大抵能找到更順手的替代品。

Download: Android / 鴻蒙 應用商店 (JSBox、Scriptable - Apple Store)

- WebSocket

WebSocket 對象提供了用於創建和管理 WebSocket 連接,以及可以通過該連接發送和接收數據的 API。  [ MDN ]

講人話就是  即時通訊  ,使服務器與多個客戶端能高並發地保持通訊狀態,我們日常生活中的大部分操作都基於  HTTP  請求,比如點擊外賣App的某家店鋪發出了請求,而App公司的服務器將這家店鋪的每個菜單的文字和圖片返回到手機並展示出來;又比如我們刷短視頻時每次下滑下一條視頻,服務器將下條視頻通過  HTTP  返回給我們。再直白點就是我們的每個操作都像是網購,只不過流量成為這次交易的貨幣,而賣家把商品交給我們也要承擔包郵部分的運費。

可是當我們觀看直播、網絡通話等操作時  HTTP  就不那么適用了,在  WebSocket  正式普及以前大家只能通過  輪詢  來實現此方法,也就是在一秒內不停地買 60 次,就可以觀看一秒 60 幀的視頻直播了,雖然 Web API 不需要我們擁有1秒60下的手速,但對瀏覽器的性能是一個很大的困擾,再者網絡、服務器波動、信號抖動等等原因造成的失敗概率也會隨着請求基數的增加而倍數增長,不是 1*60*0.01%,看直播的人不僅僅有一個,大量的用戶會不斷給服務器造成壓力,好比2009.11.11的阿里巴巴,2019的暴雪娛樂,和每一年的新浪微博。這也是以前網電和直播不普及的原因,真不是有頭腦的人少,英雄也要倚靠時勢。

Download: 項目依賴包,不需要手動安裝,下文會詳細說明。

- TamperMonkey

俗稱油猴,也是基於瀏覽器擴展程序的 JS 語言,淘系0點秒殺、自動掛網課、腳本去廣告基本用的都是它,網上已經有非常多資源這里就不介紹了。

Download: Chrome等各大瀏覽器商店,沒有梯子的可以試試 [ 擴展迷 ]。

 

采用這幾個工具的重點在於,它們的生態都很好且穩定,文檔與API保持更新,短期內不會被淘汰。


代碼部分

上文提到數據由  服務層  提供,我們可以通過 Node.js 啟動服務實現中轉; 通訊協議  作為媒介,可以選擇即時通訊的 WebSocket,或者依賴用戶行為的 HTTP,結合自身應用場景而定;由瀏覽器的 TamperMonkey  監聽剪貼板  事件;  協同設備  通過 Auto.js 接收復制好的文本流。

整個思路已經理清了,服務層 作為本業務的控制中樞,所以由 Node.js 的開發先開始。

- Node.js

由上文提到官網入口下載,安裝包會附帶一個  npm 插件  ,它是一個包管理器,直接作用是通過在 cmd 輸入 URI 的方式將網絡上的資源下載到我們的電腦,從這點來講可以理解為一個全世界在用的大號雲網盤,從本質來講也可以理解為一個龐大的工程倉庫。

安裝好后打開環境變量:

找到系統變量中的 path 編輯,將 npm 和 nodejs 的路徑 copy 至末尾:

然后  window + R  ,鍵入 cmd,回車

在命令行窗口輸入 npm -v 和 node -v 檢查安裝與環境變量是否配置成功:

如圖返回版本號即為成功,接着輸入下行代碼安裝 cnpm:

npm install -g cnpm --registry=https://registry.npm.taobao.org

npm 是我們剛剛配置變量索引到的程序,install 是 安裝 關鍵字,-g 是 安裝 到全局(global),cnpm 是淘寶鏡像,由於 npm 起於牆外,無論是服務器支持還是其內數據遠在天邊,都導致下載速度緩慢且大概率會 fail,后面一長串是下載路徑。

還是 cnpm -v 檢查,出現版本號就是安裝成功,由於是基於npm的鏡像,不需要配置環境變量。

隨便找個盤新建文件夾,名字不能隨意否則可能會造成不可預知錯誤,起碼中文是絕對不行的,也不建議駝峰式,我開發此項目過程中因此報過錯,建議小寫"a-z"與"_"組合:

直接在文件夾管理器的地址欄中鍵入 cmd 回車(下圖中文字選中高亮處),省的一直cd找URI了:

在命令行窗口中輸入 npm init,回車,緊跟着一連串配置(圖中黃字備注):

初始化后在根目錄生成一個package.json文件,該文件除了聲明項目描述,還注明了引入的依賴包和對應版本,不可刪除。

命令行保持這個文件夾路徑,依次鍵入安裝依賴包,項目相當於一台手機,依賴包是里面的App,提供各種功能:

  • cnpm install express --save 這個包作用是nodeJS基於此框架創建服務層業務
  • cnpm install cors --save 作用是解決跨域問題(想了解跨域可以閱讀我的另一篇文章:瀏覽器:深度理解瀏覽器的同源策略
  • cnpm install body-parser --save 以此包獲取前台傳參的參數
  • cnpm install mysql --save 幫助連接MySQL數據庫
  • cnpm install multer --save 中間件上傳文件處理formdata類型的表單數據
  • cnpm install cookie-parser --save 該包提供cookie的使用

安裝后根目錄會多出一個 node_modules 文件夾存放這些依賴包

package.json 也自動寫入了相應的注明:

里面的文件不要改不要刪,如果僅僅是為了實現功能,那么也沒必要去看,因為會掉很多頭發。

在根目錄新建一個文件 app.js,用代碼編輯器打開,VSCode 提供了wifi 局域網連接手機 Auto.js 軟件調試的插件,小白的話找個秒開級的輕量編輯器就完全沒問題了:Sublime Text 3 官網

直接  Ctrl+C  和  Ctrl+V

  1 //導入express框架
  2 var express = require("express");
  3 var app = express();
  4 //解決跨域問題
  5 const cors = require('cors');
  6 // 中間件 獲取參數的
  7 const bodyParser = require('body-parser');
  8 //讀寫文件流
  9 var fs = require("fs")
 10 //引入websocket
 11 const ws = require('nodejs-websocket');
 12 
 13 app.use(bodyParser.json());
 14 app.use(bodyParser.urlencoded({extended: true}));
 15 app.use(cors());
 16 
 17 app.all("*", function(req, res, next) {
 18     res.header("Access-Control-Allow-Origin", "*");
 19     res.header("Access-Control-Allow-Headers", "X-Requested-With");
 20     res.header("Access-Control-Allow-Methods", "PUT,POST,GET,DELETE,OP0TIONS");
 21     res.header("X-Powered-By", "3.2.1");
 22     res.header("Content-Type", "application/json;charset=utf-8");
 23     next();
 24 });
 25 
 26 
 27 app.get('/getString', function(req, res) {
 28     // console.log(5555,req.query,666,req.params,888,req.body)
 29     console.log(req.query)
 30     res.status(200)
 31     //json格式
 32     // res.json(data)
 33     //獲取json
 34     fs.readFile('./data.json','utf-8',function(err,data) {
 35         console.log(data)
 36         let params = {}
 37         if(err) {
 38             console.error()
 39             params = {
 40                 code:500,
 41                 message:"讀取失敗"
 42             }
 43         } else {
 44             params = {
 45                 code:200,
 46                 message:"成功",
 47                 data:data
 48             }
 49         }
 50         //傳入頁面
 51         res.send(params)
 52     })
 53     
 54 });
 55 
 56 app.get('/setString', function(req, res) {
 57     // console.log(5555,req.query,666,req.params,888,req.body)
 58     console.log(req.query)
 59     res.status(200)
 60     //json格式
 61     // res.json(data)
 62     //傳入頁面
 63     fs.readFile("./data.json",function(err,data){
 64         if(err) {
 65             return console.error(err)
 66         }
 67         let obj = {
 68             clips: req.query
 69         }
 70         let str = JSON.stringify(obj)
 71         fs.writeFile("./data.json",str,function(err){
 72             if(err) {
 73                 console.error(err)
 74             }
 75             console.log('-------修改成功-------')
 76         })
 77     })
 78     let params = {
 79         code:200,
 80         message:"成功"
 81     }
 82     res.send(params)
 83 });
 84 
 85 let padKey = '';
 86 const webServer = ws.createServer(conn => {
 87     // console.log('有一名用戶連接進來了...')
 88     conn.on("text", function (res) {
 89         let resa = JSON.parse(res);
 90         if(resa.msg && resa.msg === 'Request connection.') {
 91             console.log(`${resa.role} 請求連接...`)
 92             console.log('key: ', conn.key)
 93             conn.sendText(JSON.stringify({
 94                 "sid": conn.key,
 95                 "msg": "服務器連接成功!"
 96             }));//返回給客戶端的數據
 97             setTimeout(() => {
 98                 conn.sendText(JSON.stringify({
 99                 "sid": conn.key,
100                 "msg": `Hi, ${resa.role}.`
101             }))
102             }, 800)
103             if(resa.role === 'Pad') {
104                 padKey = conn.key
105             }
106         }
107         if(resa.clips && resa.role === 'Borwser') {
108             console.log(`剪貼板更新: ${resa.clips}`)
109             webServer.connections.forEach(function (conn) {
110                 if(conn.key == padKey) {
111                     conn.sendText(JSON.stringify(resa))//返回給所有客戶端的數據(相當於公告、通知)
112                 }
113             })
114         }
115     })
116     //監聽關閉
117     conn.on("close", function (code, reason) {
118         console.log("連接斷開...")
119     })
120     //監聽異常
121     conn.on("error",() => {
122         console.log('服務異常關閉...')
123     })
124 }).listen(8088)
125 
126 var server = app.listen(3000, function() {
127     var host = server.address().address;
128     var port = server.address().port;
129 
130     console.log("服務啟動: ", port);
131 })

如果你對代碼感興趣,打開抽屜並查看釋義:

授人以漁<・)))><<
----------------- 必要部分 ----------------

行2、3 - 引入express框架,定義變量app接收將API實例化。

行5 - 引入cors,使得瀏覽器與其他設備可以跨域請求該服務。

---------- 手動http部分(可選) ----------

行7 - 引入body-parser,以獲取  HTTP  請求的參數(僅使用  WebSocket  時可略)。

行9 - 引入node.js的fs模塊,以讀寫文件流內容(僅使用  WebSocket  時可略)。

------- 自動websocket部分(可選) ------

行11 - 引入websocket,作為網絡交互協議。

---------- 手動http部分(可選) ----------

行13、14、15 - 對該服務激活跨域插件與中間件,變量app為行2引入express框架的實例化實現,下文不再贅述。

行17~14 - 設置所有  httpResponse  的響應頭。

行27~54 - 響應 http get( ) 的接口服務,對應請求路徑應為 'http://IPv4 Address:端口號/getString',IPv4可以通過cmd中鍵入ipconfig查詢,下文不再贅述。

行27 - 函數括號內兩個形參 req 接收請求體,res 接收響應體。

行29 - http.get 請求通過query傳參,例如請求路徑'http://192.168.0.1/getString?id=1&name=97z4moon',服務就可通過上述引入的中間件依賴包獲取到兩個參數 { id: '1', name: '97z4moon'}。想傳不同參數時,只需要改變路徑'?'后面跟的值即可,多個參數以'&'連接。

行34 - 通過fs模塊讀操作,'./data.json','./'為同目錄下,'../'為上一級,比如我的app.js文件路徑為 'C:\clipboard_project\app.js','./data.json' 即為 'C:\clipboard_project\data.json','../data.json' 為 'C:\data.json',它們都是相對路徑,字面意思就是比較代碼所處文件app.js位置的對應路徑。'utf-8' 是以該編碼接收,參數err接收錯誤時實參,data接收讀取文件流的內容。

行51 - 將響應體發送至客戶端,也就是接收的人,該角色在本業務中對應的是持有Auto.js軟件的移動設備,實參params將所期待的剪貼板數據返回給請求者,假設一直不執行send()方法,請求者會將該進程掛起,直到網絡請求超時。

行56~83 - 與getString同理,思路是油猴監聽瀏覽器剪貼板事件,在鍵盤鍵入復制操作時將剪貼板的內容寫入data.json文件中,以便移動端獲取。假設我的局域網ip為192.168.31.109,則我的油猴腳本請求路徑應為'http://192.168.31.109:3000/setString?str=剪貼板文本'。

行71 - 通過fs模塊寫操作,在行67~69定義一個對象obj,在obj的堆中增加一個鍵值對,如上所說,形參req接收的是請求體,req.query即為上述請求路徑中最后'?'緊跟的'str=剪貼板文本'。

------- 自動websocket部分(可選) ------

行85~124 - websocket通訊自動同步到移動設備部分。

行85 - 定義變量padKey存儲本次通訊接收者的唯一key,該key由node的websocket插件自動分配,如果需要多設備,則將行104改為:padKey+=conn.key,行110改為:if(padKey.indexOf(conn.key)>-1){ 。

行86 - ws已由行11部分實例化了websocket包,通過該包提供的API - createServer創建一個socket通訊,定義常量webServer接收,因為該服務保持通訊,僅隨着項目關閉或服務器維護而關閉,所以定義為常量為最優,通過形參conn接收每一次建立起的socket通訊。

行88 - 通過某次連接的原型函數on(),監聽 'text' 事件,並定義一個function在監聽到事件時執行,以形參res接收。

行93~96 - 將一個JSON字符串處理后的對象傳入給本次通訊連接的發起者。

行97~102 - 同上,通過定時器setTimeout延遲 800ms 執行。

行109 - webServer是本次socket服務實例,其[key]connections對應的是當前socket服務下所有的連接用戶,通過forEach遍歷找到需要接收的用戶,通過API - sendText() 向其發送剪貼板內容。

行117~119 - 監聽本次socket服務中所有成功連接的用戶的退出連接事件。

行121~123 - 監聽本次socket服務中所有成功連接的用戶的異常錯誤事件。

行124 - 以第一個實參8088為端口啟動socket服務 。

行126 - 以第一個實參3000為端口啟動http服務,也就是上述中請求路徑的 'http://192.168.31.109:3000/getString' 。

- TamperMonkey

手動版思路:監聽瀏覽器剪貼板事件 -> 將剪貼板內容通過http發送給服務層,node.js接收到query將其保存至data.json文件中,移動設備執行auto.js的代碼向服務層發起請求,node.js拿到data.json中的剪貼板內容放進響應體返回給移動設備,移動設備通過auto.js API - setClip()將內容設置到設備剪貼板。

 1 // ==UserScript==
 2 // @name         setClipString
 3 // @namespace    http://tampermonkey.net/
 4 // @license     GPL version 3
 5 // @encoding    utf-8
 6 // @description  try to take over the world!
 7 // @author       97z4moon
 8 // @include      *
 9 // @icon         https://www.google.com/s2/favicons?domain=tampermonkey.net
10 // @grant        GM_xmlhttpRequest
11 // @grant        GM_download
12 // @run-at      document-end
13 // @version     1.0.0
14 // ==/UserScript==
15 
16 (function() {
17     // Your code here...
18     let urls = document.location.href
19     document.addEventListener("copy",function(e){
20         fetch("http://localhost:3000/setString?str="+window.getSelection(0).toString()+"&url="+urls,{
21             "headers":{
22                 "accept": "application/json, text/plain, */*",
23                 "accept-language": "zh-CN,zh;q=0.9",
24                 "authorization":"Basic " + btoa(JSON.stringify({
25                     "li":"administrator","pd":"superadmin"
26                 })),
27                 "referrer": urls,
28                 "referrerPolicy": "no-referrer-when-downgrade",
29                 "body": null,
30                 "method": "GET",
31                 "mode": "cors",
32                 "credentials": "include"
33             }}).then(response=>response.json()).then(data=>{
34                 console.log(data)
35             }).catch(e=>{
36                 console.log(e)
37             })
38     })
39 })();

行1~14 - 腳本聲明與配置。

行16~39 - IIFE函數。

行18 - 通過DOM的location對象獲取到復制操作的網站鏈接,保存在定義的字符串變量urls中。

行19 - 對整個DOM設置監聽器,第一個參數定義監聽器監聽'copy'事件,第二個參數監聽到時執行函數。

行20 - 通過Fetch API對服務層發起請求,該方法提供了一種簡單,合理的方式來跨網絡異步獲取資源。fetch() 可以接受跨域cookie,也可以建立起跨域對話,fetch() 不會發送 cookie。如果需要在 IE11 及以下版本中使用 fetch,通過 Fetch Polyfill 來實現。[MDN]

行21~32 - 設置請求頭。

-------

自動版思路:在瀏覽器打開的頁面中建立websocket通訊,連接到node.js啟動在8088端口的socket服務,TamperMonkey監聽到瀏覽器復制操作時,將剪貼板內容發送至服務層node.js處理,node.js再將該內容下發到key值對應的移動設備中。只需將移動設備socket通訊時發送的參數role改變為預設值即可,如我在node.js代碼中設置的條件是:if(resa.role === 'Pad') 。當移動設備接收到剪貼板內容時,使用auto.js將其設為剪貼板。

 

 1 // ==UserScript==
 2 // @name         setClipString2
 3 // @namespace    http://tampermonkey.net/
 4 // @license     GPL version 3
 5 // @encoding    utf-8
 6 // @description  try to take over the world!
 7 // @author       97z4moon
 8 // @include      *
 9 // @icon         https://www.google.com/s2/favicons?domain=tampermonkey.net
10 // @grant        GM_xmlhttpRequest
11 // @grant        GM_download
12 // @run-at      document-end
13 // @version     2.0.0
14 // ==/UserScript==
15 
16 (function() {
17     // Your code here...
18     let ws = new WebSocket('ws://localhost:8088');//實例化websocket
19     let obj = {
20         role: 'Borwser',
21         msg: 'Request connection.'
22     }
23     ws.onopen = function () {
24         console.log("socket has been opend")
25         ws.send(JSON.stringify(obj))
26     }
27     document.addEventListener("copy",function(e){
28         console.log("data: ", window.getSelection(0).toString())
29         obj.clips = window.getSelection(0).toString()
30         obj.msg = 'ClipBoard has been updated.'
31         ws.send(JSON.stringify(obj))
32     })
33 })();

 

行18 - 實例化websocket,路徑'ws://……'為關鍵字,'localhost'不可替換為IPv4,否則會報錯,8088為node.js設置的socket服務端口。(自行擴展可在node.js可以啟動多個socket服務,分別對應不同功能)

行23~26 - 在socket連接成功后在瀏覽器控制台輸出提示,並向node.js發送實參obj表明身份與來意。send()事件不可在連接成功前執行,否則會導致該頁面生命周期下的所有socket連接失敗。

行29 - window對象API - window.getSelection(0) 獲取剪貼板信息,使用toString將其格式化為剪貼板內容。

- Auto.js

 1 let ws = $web.newWebSocket("ws://192.168.31.109:8088", {
 2     eventThread: 'this'
 3 });
 4 console.show();
 5 
 6 let padSid = '';
 7 ws.on("open",(res,ws)=>{
 8     log("WebSocket has been ready...")
 9 }).on("failure",(err,res,ws)=>{
10     log("Connect fail...")
11     ws.close(1000,null)
12     console.hide()
13 }).on("closing",(code,reason,ws)=>{
14     log("WebSocket is closing...")
15 }).on("text",(text, ws)=>{
16     let res = JSON.parse(text)
17     if(res.sid) {
18         padSid = res.sid
19     }
20     console.info("Receive msg: ", res.msg)
21     if(res.clips) {
22         setClip(res.clips)
23     }
24 }).on("binary",(bytes,ws)=>{
25     console.info("Receive binary:")
26     console.info("hex: ",bytes.hex())
27     console.info("base64: ",bytes.base64())
28     console.info("md5: ",bytes.md5())
29     console.info("size: ",bytes.size())
30     console.info("bytes: ",bytes.toByteArray())
31 }).on("closed",(code,reason,ws)=>{
32     log("WebSocket closed: code = %d, reason = %s")
33 })
34 
35 let params = {
36     role: 'Pad',
37     msg: 'Request connection.'
38 }
39 ws.send(JSON.stringify(params));
40 setTimeout(()=>{
41     log("connect not WebSocket...")
42     ws.close(1000,null)
43     console.hide()
44 },600000)

行1 - 定義變量ws實例化一個socket服務,請求地址為 'ws://192.168.31.109:8088' 。

行2 - eventThread定義為this事件將在創建WebSocket的線程觸發,如果該線程被阻塞,則事件也無法被及時派發。

行4 - 打開控制台懸浮窗。

行6 - 定義字符串變量padSid接收node.js中socket服務分配的本次通訊設備唯一key。

行7 - 監聽socket包服務的啟動事件。

行9 - 監聽與socket服務層斷線的事件。

行11 - 關閉本次socket通訊。

行12 - 隱藏控制台懸浮窗。

行13 - 監聽socket通訊關閉中事件。

行15 - 監聽socket通訊接收到文本事件。

行22 - Auto.js API - setClip() 設置剪貼板內容。

行24 - 監聽socket通訊接收到二進制信息事件。

行31 - 監聽socket通訊關閉完成的生命周期。

行39 - 向服務層發送socket訊息表明身份和來意。

行40 - 定時器 10 分鍾后關閉本次socket通訊服務,如果不設則通訊會在執行完js后立即結束,如果想永久掛起,可以將行40~44改為:setInterval(()=>{}),需要注意的是這樣做會占用許多不必要的性能資源,時間長了以后可能造成內存溢出,宏隊列擁擠造成socket通訊較高的延遲。


WebSocket版演示

IOS 通過 workflow 接收文字

利用上文中node.js提供的http服務,data.json的存儲以及workflow的局域網請求。

使用時手動執行快捷指令接收,或者通過siri執行。

例如,當我在pc的瀏覽器中復制了想要傳給iphone的長文字,只需要:

"hey, siri"

"pc剪貼板協同"(快捷指令的名字)

如果siri回答: "完成",此時iphone的剪貼板中,就是我們剛剛從PC復制好的文字了。

- 操作演示

PC與移動設備的圖片互傳

pc -> phone

首先如果phone要請求PC中圖片的話,需要將圖片放在node.js的項目目錄中,通過node的fs模塊獲取,並使用public存儲該靜態資源目錄(圖片)。

在前幾節所示node.js代碼中,在express引入語句下新增:

app.use('/public', express.static('public'));

在Auto.js / workflow 中通過HTTP獲取該文件,請求路徑應為:'http://IPv4 address:port/public/images/xxx.png',

相應的,也應該在node項目的根目錄中新建文件夾並命名為 'public',便於維護性分類在public文件夾中新建文件夾 'images',將圖片放入其中,請求路徑中的 'xxx.png' 應與圖片文件名一致。

phone -> pc

與上述node.js節代碼的兩個app.get()相同,以http請求接收:

app.post('/imagesReceiver', function(req, res) {
	console.log('接收到圖片傳輸請求...')
	console.log(req.host)
	// console.info('接收到數據: ', req)

	var form = new formidable.IncomingForm();   //創建上傳表單
	form.encoding = 'utf-8';        //設置編輯
    form.uploadDir = 'public' + '/images/';     //設置上傳目錄
    form.keepExtensions = true;     //保留后綴
    form.maxFieldsSize = 2 * 1024 * 1024;   //文件大小

    let data = {
		msg: '發送成功!'
	};

    form.parse(req, function(err, fields, files) {
        if (err) {
			res.status(500);
			data.msg = '服務器解析錯誤...';
			res.json(JSON.stringify(data));
          return;        
        }
		console.log(fields)
       
        var extName = '';  //后綴名
        switch (files.image.type) {
            case 'image/pjpeg':
                extName = 'jpg';
                break;
            case 'image/jpeg':
                extName = 'jpg';
                break;         
            case 'image/png':
                extName = 'png';
                break;
            case 'image/x-png':
                extName = 'png';
                break;         
        }

        if(extName.length == 0){
			res.status(400);
			data.msg = '只支持png和jpg格式圖片...';
			res.json(JSON.stringify(data));
            return;                   
        }

        let nd = new Date();
        let dateNow = `${nd.getFullYear()}${nd.getMonth()+1}${nd.getDate()}${nd.getHours()}${nd.getMinutes()}${nd.getSeconds()}${nd.getMilliseconds()}`;

        var imageName = fields.role + dateNow + '.' + extName;
        var newPath = form.uploadDir + imageName;

		res.status(200);
		data.msg = '發送成功...';
		res.json(JSON.stringify(data));
        console.log(newPath);
        fs.renameSync(files.image.path, newPath);  //重命名
    });

})

通過workflow傳入(頭部可忽略):

Android通過Auto.js傳入不再贅述,值得一提的是Auto.js提供了workflow沒有的websocket通訊,所以我們仍然可以通過油猴監聽瀏覽器下載圖片事件,將下載后的圖片直接傳入Android設備的相冊app中,或者干脆像QQ微信那樣直接將一台Android手機的照片通過服務中樞發送到另一台Android手機中。具體實現思路與傳文本一致,圖片處理代碼直接引用本節pc->phone的http中邏輯即可。

執行結果:

- END -


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM