同事之前給我提了一個需求,想實現在網頁里點擊鏈接地址后直接打開指定的地址(路徑是內網共享的目錄,file://share.xx.com\x\x)。
瀏覽器因為有安全的限制,是無法訪問 web 頁面時,可以打開本地PC的目錄。當你點擊帶有本地文件鏈接的超鏈接(file://),控制台上會報錯:Not allowed to load local resource:
最開始在網上搜索了一下,有二個插件看上去似乎可以滿足需求。
(1)Enable local file links
(2)Local Explore – File Manager on web browser
插件啟用后,類似下面這種效果(跟他們想要的效果還是有區別)。
Local Explore,自定義了協議,然后呼起本地 exe,再打開資源管理器,是期望的效果。但是它最大的問題是:如果路徑有中文,就亂碼,無法正常使用。
它的原理倒是比較簡單,修改超鏈接為 LocalExplore:file://xxxx,如果注冊表添加了對該協議的監聽,當瀏覽器訪問該協議時,會觸發指定的 exe 並傳入相關的參數。
我要解決亂碼問題,先處理瀏覽器擴展插件,再就是替換 exe 的實現就可以了。
(1)替換插件,解決因插件 escape 導致的亂碼問題(注:不太能理解作者為啥要用 JSON.parse 處理一下)
對比了二個插件的實現,我准備在 Local Explore 插件的基礎上進行精簡。只留下必要的代碼,然后通過開發者模式,加載進 chrome 的擴展程序里。
background.js 里的代碼被我刪光了,content.js 只進行一個操作,遍歷文檔所有超鏈接,然后修改其 href 屬性。
$(document).ready(function() { var optOpenFolders = "true"; var protocolStr = "LocalExplorer:"; // var clickEventHandler = function(evt) { // evt.preventDefault(); // chrome.extension.sendMessage({ // cmd: "click", // data: this.href // }); // }; $("a").each(function(i, e) { if (e.href !== undefined && e.href !== null && e.href !== "") { if (!e.href.match(/^file:\/\//)) { return; } if (e.href.match(/^file:\/\//)) { if (window.debug) console.log(e.href); e.href = protocolStr + e.href; if (e.target) e.target = "_self"; } // $(e).off("click", clickEventHandler); // $(e).on("click", clickEventHandler); } }); });
manifest.json 也做了一點修改
{ "update_url": "https://clients2.google.com/service/update2/crx", "version": "2021.7.6", "short_name": "Meteoric Local Explorer", "name": "Meteoric Local Explorer - File Manager on web browser", "description": "__MSG_extDescription__", "default_locale": "zh_CN", "icons": { "128": "icon/icon128.png", "32": "icon/icon32.png", "16": "icon/icon16.png" }, "browser_action": { "default_icon": "icon/icon32.png", "default_title": "Meteoric Local Explorer" }, "content_scripts": [{ "matches": ["<all_urls>"], "js": ["jquery.js", "content.js"], "all_frames": false, "run_at": "document_start" }], "background": { "scripts": ["background.js"] }, "options_page": "", // options.html "permissions": [ "http://*/*", "https://*/*", "tabs" ], "manifest_version": 2 }
(2)替換 exe,支持打開中文鏈接
這里我直接用 C# 簡單寫了一個 exe,實現了基本的功能。為了精簡 Release 版本生成的內容,我對幾處設置作了簡單的調整
(1)項目屬性里面的,生成 –> 高級 –> 高級生成設置,輸出 –> 調試信息,選擇無,避免生成 pdb 文件;
(2)App.config 的文件屬性,修改‘生成操作’為‘嵌入的資源’,避免生成 *.exe.config 文件;
這樣生成的 Release 目錄就是比較干凈的了,只有一個叫 LocalExplorer.exe 的文件。替換掉安裝在 C 盤里面的 exe(默認路徑在:"C:\Program Files (x86)\LocalExplorer\LocalExplorer.exe" )
using System; using System.Collections.Generic; using System.Diagnostics; using System.IO; using System.Linq; using System.Text; using System.Threading; using System.Threading.Tasks; using System.Windows.Forms; namespace FriendTimesLocalExplorer { class Program { static void Main(string[] args) { /* for (int i = 0, len = args.Length; i < len; i++) { // Get first param } */ if (args.Length < 1) { MessageBox.Show("File Path is Empty", "System Tips", MessageBoxButtons.OK, MessageBoxIcon.Information); return; } string filePath = args[0]; // escape filePath = Uri.UnescapeDataString(filePath).Replace("/", "\\"); // delete protocol filePath = filePath.Replace("localexplorer:", ""); filePath = filePath.Replace("file:", "");
// get right file path filePath = filePath.Substring(filePath.IndexOf('\\')); //Console.WriteLine(filePath); if (Directory.Exists(filePath) || File.Exists(filePath)) { Process p = new Process(); p.StartInfo.FileName = "explorer.exe"; p.StartInfo.CreateNoWindow = true; p.StartInfo.UseShellExecute = false; // hidden p.StartInfo.WindowStyle = ProcessWindowStyle.Hidden; p.EnableRaisingEvents = true; p.StartInfo.RedirectStandardError = true; if (File.Exists(filePath)) { p.StartInfo.Arguments = @"/select," + filePath; } else { p.StartInfo.Arguments = filePath; } try { p.Start(); // explorer.exe 異常結束時,會導致啟動不斷重啟 p.WaitForExit(); if (p != null) { p.Close(); p.Dispose(); p = null; } } catch (Exception e) { MessageBox.Show(e.ToString(), "System error", MessageBoxButtons.OK, MessageBoxIcon.Error); } } else { MessageBox.Show("Not Found Path : \n" + filePath, "System error", MessageBoxButtons.OK, MessageBoxIcon.Error); } } } }
瀏覽器插件 和 exe 都進行替換后,就能實現點擊鏈接調用本地exe,再通過 exe 打開指定的目錄了。迅雷、QQ或其它客戶端軟件,基本上也是使用類似的原理,實現點擊網頁鏈接呼起本地的客戶端應用程序(應用程序想干嘛就自己實現)
注意點擊時,會彈出一個提示。