Forge雲服務的本地化經驗總結與最佳化實戰

BryanHuang發表於2019-02-15

Autodesk Forge API服務的資料中心是基於AWS的海外服務搭建的,因而,由於眾所周知的原因,國內部分地區(依ISP而異)訪問Forge雲埠的速度會受到一定程度的影響。特別是Forge Viewer瀏覽大型模型,以及對反饋時間比較銘感且涉及關鍵業務的工作流等諸多場景,對於服務端的存取效率有者較高要求。所以,如何處理好雲端資料的訪問與協同工作流,包括離線載入、雲端快取等方案的最佳實踐,是大家一直關注的問題。今天,我們就總結一下幾種常見的實現方式,以期實現流程與效能的最佳化。

離線模型載入

往期有這兩篇文章可供參考:離線模型的下載和部署Viewer模型載入本地離線快取實戰,該文介紹了使用新近瀏覽器原生的Service-Worker和Cache API快取模型的方案。

但是在Viewer模型載入本地離線快取實戰例項程式碼中,針對Viewer庫和線上模型資源本身的快取是透過靜態路徑實現的:

//https://github.com/petrbroz/forge-disconnected/blob/master/public/service-worker.js
const STATIC_URLS = [
    'https://developer.api.autodesk.com/modelderivative/v2/viewers/6.*/style.css',
    'https://developer.api.autodesk.com/modelderivative/v2/viewers/6.*/viewer3D.js',
    'https://developer.api.autodesk.com/modelderivative/v2/viewers/6.*/lmvworker.js',
    'https://developer.api.autodesk.com/modelderivative/v2/viewers/6.*/res/locales/en/allstrings.json',
    'https://developer.api.autodesk.com/modelderivative/v2/viewers/6.*/res/environments/SharpHighlights_irr.logluv.dds',
    'https://developer.api.autodesk.com/modelderivative/v2/viewers/6.*/res/environments/SharpHighlights_mipdrop.logluv.dds',
...

該實現存在幾點問題:

  • 待快取的靜態連結是根據展示用的模型所需配置的,模型更換後靜態連結也需要手動更新
  • 一次性快取了所有例項模型所需的資源,超配且不必要,影響載入效能
  • Viewer庫版本一旦更新,需手動更新快取的靜態資源連結

因此,在我們後續的實戰Forge Viewer漸進應用一文中,採用先註冊完成快取任務的Service Worker再載入Viewer庫的流程。如此一來,Viewer庫依賴與模型資源的載入請求也會自動得到快取,無需手動干預快取過程,大幅增進程式碼的可維護性:

navigator.serviceWorker.register('/service-worker.js').then((registration) => {
        let script = document.createElement('script');
        script.onload = function () {
            const viewer = new Autodesk.Viewing.Private.GuiViewer3D(myViewerDiv);

            Autodesk.Viewing.Initializer(options, () => {

                ... //按需以線上或離線模式初始化Viewer並載入模型

                viewer.addEventListener(Autodesk.Viewing.GEOMETRY_LOADED_EVENT, () => {

                    const channel = new MessageChannel();
                    channel.port1.onmessage = (event) => console.log(event);

                    navigator.serviceWorker.controller.postMessage({ operation: 'EXECUTE_CACHE' }, [channel.port2]);  // 模型載入完成,該模型所需資源已作記錄,遂向ServiceWorker傳送訊息,開始快取所需資源
                    })
            });
        };
        script.src = "https://developer.api.autodesk.com/modelderivative/v2/viewers/6.*/viewer3D.min.js";
        document.head.appendChild(script) //待Worker註冊完畢開始作動後再載入Viewer,實現Viewer庫及其依賴的自動快取

})

線上模型載入

  • 建議參考下文的資料服務的最佳化,為Viewer的載入提供代理,增進國內訪問的速度。
  • 在Viewer上實現代理的方法主要透過Autodesk.Viewing.endpoint.setEndpointAndApi來重定向獲取模型資料的URL:
Autodesk.Viewing.Initializer(options, function(){
  Autodesk.Viewing.endpoint.setEndpointAndApi('https://yourhostname/your/proxy/service/path')
...

隨後在你的服務端將Viewer發來的請求代理至Forge API雲服務https://developer.api.autodesk.com即可,注意保留Viewer原生請求的路徑,這裡還可以根據Forge服務上的模型所在的資料中心按需為轉發目的地作進一步設定,詳見:BIM 360 Docs API在操作歐洲資料中心內容的一些調整

  • 亦可設定Viewer傳送的請求頭,以AOP的設計模式實現自己的訪問控制等邏輯:
Autodesk.Viewing.Initializer(options, function(){
          Autodesk.Viewing.endpoint.HTTP_REQUEST_HEADERS = {'X-My-Custom-Header':'233', ...}
...

當然,我們還需在自己的服務端設定預檢請求(Pre-flight)的返回中Access-Control-Allow-Headers的請求頭,允許之前為Viewer設定的請求頭不被瀏覽器的跨域安全機制攔截。

  • 當然,大家熟悉的Viewer原生的getAccessToken等都是依舊有效的。

Android

  • 如本地的模型,由於Viewer不支援基於File協議的模型載入,需要透過重寫WebView並攔截http請求,返回靜態資源。
webView.setWebViewClient(new WebViewClient() {
    @Override
    public boolean shouldOverrideUrlLoading(WebView view, String url) {
        if (url.startsWith("http://my/path/to/svf")) {
            // 返回靜態模型資源
            return true;
        }
    }
});
  • Viewer載入模型的URL維持http協議即可

iOS

  • 同理,iOS的實現思路是:
 func webView(_ webView: WKWebView, decidePolicyFor
       navigationAction: WKNavigationAction,
       decisionHandler: @escaping (WKNavigationActionPolicy) -> Swift.Void) {


    if navigationAction.navigationType == .linkActivated {
        if webView.url!.absoluteString == "http://path/to/my/static/svf" {
            //返回靜態模型資源
            return
        }
    }
    decisionHandler(.allow)
}

跨平臺

針對跨平臺的框架,不建議使用搭設本地靜態伺服器的方式克服Viewer不支援File協議的問題,因為很多Android ROM處於安全考量已經遮蔽了該特性。

  • React Native框架: 推薦使用react-native-webview,再透過onShouldStartLoadWithRequest攔截並返回靜態模型:

onShouldStartLoadWithRequest = (event) => {
        var url = event.url;
        if (url && url.length) {
            if (url.indexOf('http://my/path/to/svf') == 0) {
                //返回靜態模型資源
            }
        }
        return true;
    };

資料服務的最佳化

  • 建議自建雲服務訪問Forge API或部署代理,以便實現諸多最佳化事項,如快取(可以使用http-cache等庫),如集中統籌Access Token,避免為每個客戶端服務請求逐一作Forge認證,提升效率的同時增強Forge App金鑰和Token的安全性,亦便於實現高度自定的工作流。
  • 建議搭建位於海外機房的節點作代理,地理位置推薦香港或接入電信CN2線路的北美機房,訪問速度會有不小提升,詳情可以自行搜尋瞭解。
  • 亦可搭建自己的雲端儲存,便於實現高度自定義的工作流與高規格的安全機制,可以透過等Cephminio等技術實現。
  • 活用Forge Webhook API,實現基於事件回撥的非同步工作流,節省資源的同時提高可靠性,詳情可以參考這些樣例

Q&A

Q:能否避免將模型上傳到Forge資料平臺,直接進行轉換?
  • A:Forge服務只能轉換儲存在Forge資料平臺的模型,轉換功能無法作本地部署,如模型涉密,可以再轉換完成後立即從Forge資料平臺刪除,Forge對使用者資料不作任何備案。
Q:可否實現Viewer的完全本地化?即將Viewer庫和模型渲染資源下載到本地?
  • A:Forge Viewer使用許可禁止將Viewer庫下載至本地載入,亦禁止對程式碼進行修改
Q: 那如何修改Viewer原始碼?我有擴充套件原生邏輯的需求。
  • A: 可以使用Piggyback的方式,即在自己的邏輯中動態的替換、擴充套件Viewer自身的函式
Q: 針對離線載入模型的場景,如何有效的提取Viewer可讀的模型檔案(SVF)到本地?

相關文章