Forge云服务的本地化经验总结与优化实战

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)到本地?

相关推荐