Ionic2 + Angular4 + JSSDK開發中的若干問題彙總

前景

目前微信公衆號程序開發已經至關火熱,客戶要求本身的系統有一個公衆號,已是一個很常見的須要。javascript

使用公衆號能夠很方便的便於項目干係人查看信息和進行互動,還能夠很方便錄入一些電腦端不便於錄入的數據,如照片等。html

ionic是一個移動端開發框架,使用hybird技術,只要使用前端開發技術就能夠開發出電腦端,安卓端和ios端的站點程序。因爲其內置了不少仿移動端Native的控件,使用此框架進行移動端開發,既能夠減小控件和樣式開發成本,又能夠很方便將已經開發的程序打包成安卓或ios程序。前端

最近嘗試使用ionic2 + angular4 + jssdk對xxx平臺移動端進行開發,遇到過一些技術上的坑,記錄以下(持續更新)。java

 

問題

1 ios中選擇照片後,在圖片集中出現不了。

升級jssdk到1.2以上。node

 

2 使用foreach方式,一次同時上傳多張圖片,容易失敗。

應該使用同步上傳的方式,一張圖片傳完再傳下一張,也不會慢多少, 示例以下:android

    let i = 0;
    let img = this.images[i];

    let uploadImg = function () {
      wx.uploadImage({
        localId: img.picUrl, // 須要上傳的圖片的本地ID,由chooseImage接口得到
        isShowProgressTips: 0, // 默認爲1,顯示進度提示
        success: uploadSuccess,
        fail: function () {
          alert('上傳失敗');
          me.loader.dismiss();
        }
      });
    };

    let uploadSuccess = function (res: any) {
      me.uploadedImageIds.push(res.serverId);
      img = me.images[++i];
      img ? uploadImg() : me.submitImage();
    };

    uploadImg();

   

3 第一次加載的時候,老是容易出現「invalid signature"。

signature的計算,須要使用appKey, account和頁面URL等。經測試,使用jssdk的頁面的URL必須嚴格和計算signature使用的URL一致:地址 + queryString。#參數不用管。webpack

咱們從微信公衆號訪問SPA程序時,可能會帶上一些權限的參數,如authCode等,這些參數會影響到signature的計算ios

如今咱們的作法是徹底在服務器端計算signature,可是客戶端必須告訴服務器端當前的url。須要通過這兩步:web

var subUrl = window.location.href.split('#')[0];
var signatureUrl = '/[subpath]/signature?url=' + encodeURIComponent(subUrl);

  所有代碼以下:typescript

var xhr = new XMLHttpRequest();
var subUrl = window.location.href.split('#')[0];
var signatureUrl = '/wxgzh/signature?url=' + encodeURIComponent(subUrl);
var configWeixin = function () {
  var response = JSON.parse(this.response);
  wx.config({
    debug: false, // 開啓調試模式,調用的全部api的返回值會在客戶端alert出來,若要查看傳入的參數,能夠在pc端打開,參數信息會經過log打出,僅在pc端時纔會打印。
    appId: response.appId, //'wx03605b6ba300b93b', // 必填,公衆號的惟一標識
    timestamp: response.timestamp, // 必填,生成簽名的時間戳
    nonceStr: response.nonceStr, // 必填,生成簽名的隨機串
    signature: response.signature,// 必填,簽名,見附錄1
    jsApiList: ['chooseImage', 'scanQRCode', 'uploadImage'] // 必填,須要使用的JS接口列表,全部JS接口列表見附錄2
  });
};

xhr.open('get', signatureUrl);
xhr.addEventListener("load", configWeixin, false);
xhr.send();

   

4 SPA和驗證redirect問題

服務器端有可能會在咱們第一次登陸的時候,將咱們的頁面重定向到一個登陸界面。登陸好後再重定向回來。

服務器端第一次重定向到登陸界面時,會記錄第二次要重定向回來的地址。

經實驗和查證,在服務器端沒法獲取客戶端的#參數,若是index.html#photoSet,服務器只能獲取到index.html。在SPA程序中,丟失了#參數,客戶端將不能直接進入對應頁面。

目前咱們的解決辦法是,連接的地址中,#參數寫成query string, 如index.html#photoSet,寫成index.html?hash=photoSet。服務器端第二次重定向前,會檢查query string中是否有hash參數,若是有,將地址拼裝成index.html#photoSet後再執行重定向。

 

4.1 前端站點的部署問題(2018-01-31補充)

路由配置

下面有園友評論中提到了,不使用#路由,而直接使用標準路由。直接使用標準路由,對部署會有一些限制。由於標準路由是基於文件路徑的,直接使用http-server一類的服務,會直接出現404的錯誤:緣由是由於,咱們如今的url並非一個文件路徑了。

解決辦法:IIS的實現 :https://blogs.msdn.microsoft.com/premier_developer/2017/06/14/tips-for-running-an-angular-app-in-iis。tomcat也有相似的設置。整體原則是經過rewrite規則,讓資源服務器雖然識別的是一個完整路徑,但響應的時候會根據文件是否存在,而只使用特定的資源去響應,好比「二級路徑/index.html"。

 

部署環境的選擇

不少剛接觸的同行常常會問,前端站點搞好了,放哪呢?

放哪均可以,只要提供http靜態資源服務就好了。

值得一提的是,若是要調試jssdk, 須要在微信管理界面配置所謂的「信任域名」,而後你須要將站點綁定信任域名,纔可能調成功。

咱們知道,域名綁定須要公網ip,而咱們開發環境在局域網甚至localhost。除非公司給你做端口映射,將公司公網映射至你的電腦,不然調試會成爲一件很麻煩的事。通常,咱們會將本身的代碼,經過必定的方式發佈到服務器上調試jssdk,因此發佈的方便程度,會直接影響jssdk的高度效率。

我的實踐下來:感受阿里雲的sso算是不錯的選擇,特別是想作我的公衆號的園友。它知足幾個特色:1同步方便;2網速極快;3能夠估CDN;4價格實惠。再多說會有廣告嫌疑了,我沒有必要給它們打廣告,它們也不會給我報酬,呵呵。

 

5 DatePicker控件的時間差問題

第一次使用DatePicker時,發現錄入的時間總會超前8小時。指定了控件的timeOffset屬性後,就正確了。

 

6 性能問題

從默認的sample到如今的程序,在性能上,咱們主要作了兩點優化:

 

6.1 延遲加載頁面(可自行google 關鍵字ionic3 lazy load page)

當須要使用某個頁面的時候,再單獨加載某個頁面,而不是一開始所有加載好了緩存。這對於頁面比較多的SPA,做用會比較明顯。以咱們如今系統爲爲例,也能夠節省約一秒的時間。

延遲加載的關鍵技術有兩點:給每一個page組件標記上IonicPage,註明name和segment,給每一個頁面一個單獨的module,import和export這個頁面。

若是此頁面使用了普通組件,頁面module須要將組件所在module import進來。

 

6.2 build --prod

在使用ionic-app-scripts對ionic2程序進行編譯時,可使用--prod命令對編譯進行優化。

使用--prod進行編譯時,會發現可能一些原先執行ionic serve時能夠經過的代碼,此時沒法編譯經過,好比字符串的interpolation, 及其它一些不嚴格的寫法,改過來就行了。

prod編譯的優化主要使用AOT技術, 會將模板解析成js代碼,讓DOM操做更加高效。另外,這樣編譯出來的代碼也更精簡,更難讀懂(安全)。

另外,angular的enableProdMode也要在main.ts中開啓。

 

7 緩存問題

開發過程當中,緩存是一個很麻煩的問題。

在android系統中,公衆號頁面更新後,能夠在微信瀏覽器中打開debugx5.qq.com頁面來清除緩存。可是在IOS中,這一招不行。因此,在IOS中調試微信公衆號,可能會很痛苦。早些時候,咱們經歷了反覆的卸載,重裝過程。

如今項目一階段已經完結,重視這個問題,解決方案也已經出來,並實施:

  1. 前面說到,請求SAP的首頁面,會經過後端的攔截器進行攔截以進行權限驗證(java)。若是使用DotNet的童鞋,可使用ashx去處理這個請求。在這裏,可使用兩種作法,當html頁面不大時,能夠在response請求頭中,加上「Cache-Control:no-cache」;另外,咱們還能夠配置上當前公衆號的版本,response時,redirect url時拼上querySetring: ?v=[version];

  2. html頁面的緩存問題解決了,接下來解決js的緩存問題。通常而言,咱們這樣解決js的緩存問題:

        <script src="test.js?v=[版本]"></script>
        <script src="test.js?rndstr=[隨機數字]"></script>

        我比較傾向於第一種,由於版本號不變時,緩存功能仍是有用的。爲了動態使用版本號(不在html中寫死),首頁中加入以下js片斷:   

(function(){
      window.BIMRUN_VERSION = 1.1;
var loadJs = function(name){
var dom = document.createElement("script");
dom.async = false;
dom.src = "build/"+name+".js?v="+BIMRUN_VERSION;
document.body.appendChild(dom);
return dom;
};
loadJs("polyfills");
loadJs("main");
})();

     分別用於引用polyfills和main更新後的強制緩存更新。

 

正常狀況下,到這裏就應該算解決了。可是,咱們引入了page的lazy loading技術,而延遲請求模塊js文件,是ionic script調用webpack注入的代碼進行的。若是這個問題不解決,前面那些都是白搭。

blob.png

那麼怎麼辦呢?經調查,咱們發現,在mainjs中,負責加載其它頁面模塊的代碼以下:

blob.png

若是能加上紅框中的代碼,就能夠實現延遲加載的模塊也能因版本升級而更新緩存。因此,直接的作法是,每次編譯新版本後,手動更改mainjs中的代碼。

固然,這樣作太不優雅了,萬一哪天疏忽了,但是要出大問題的。那麼,怎麼讓咱們每次直接編譯成這樣的代碼呢?

咱們知道,這些代碼,都是webpack在編譯時,注入進來的。那麼,這些代碼必然在webpack中存在,因此,在node_modules/webpack中搜上面44-48行的代碼,你會很快定位到一個文件:webpack/lib/JsonpMainTemplatePlugin.js。原來,webpack把注入的這些代碼,放在了一個模板中:(放入上下文)解析模板生成對應代碼再注入。

咱們將模板進行更改一下,這樣就能夠避免每次生成mainjs後再手動更改了:

blob.png

調試一下看看,效果如預期:

blob.png

 

8 Ionic引入自定義圖標(2018-01-31)

參考:https://yannbraga.com/2017/06/28/how-to-use-custom-icons-on-ionic-3/

核心原理:
在icon的使用中, <ion-icon name="fa-glass"></ion-icon>  會默認使用ion-ios-fa-glass或ion-md-fa-glass樣式。
因此,對於自定義圖標(以font-awesome中的fa-glass爲例), 咱們添加上ion-ios-fa-glass和ion-md-fa-glass兩個樣式就好了,以下:
.fa-glass:before,
.ion-ios-fa-glass:before,
.ion-md-fa-glass:before {
  content: "\f000";
}

另外,咱們須要統一爲之指定字體:

ion-icon[class*="ion-ios-fa"],
ion-icon[class*="ion-md-fa"]{
  font-family: FontAwesome;
}

固然,手動添加這些比較麻煩,因此,我寫了一段程序來作這些事:

var file = IoEx.GetSelectFilePath();
if (string.IsNullOrEmpty(file)) return;

var sb =new StringBuilder();
var lines = File.ReadAllLines(file);
foreach (var line in lines)
{
    if (line.EndsWith(":before {"))
    {
        var className = line.Replace(":before {", "").TrimStart('.');

        if (!className.EndsWith("-o"))
        {
            sb.AppendLine($".ion-ios-{className}:before,");
            sb.AppendLine($".ion-md-{className}:before,");
        }
        else
        {
            className = className.Substring(0, className.Length - 2);
            sb.AppendLine($".ion-ios-{className}-outline:before,");
            sb.AppendLine($".ion-md-{className}-outline:before,");
        }

    } 
    sb.AppendLine(line); 
}

var savePath = IoEx.GetSaveFilePath();
if (!string.IsNullOrEmpty(savePath))
{
    File.WriteAllText(savePath,sb.ToString());
}

IoEx中的代碼爲調用系統FileSaveDialog和FileSelectDialog。

對於阿里圖庫導出的樣式,判斷邏輯有差異,但不大。

 

9 部分微信jssdk至Observable對象的封裝(2018-01-31)

下面的代碼主要給各位,尤爲是對Observable不太熟和園友一個示例,不全,風格也未必你喜歡,見諒。

export interface WxImgSelectResult {
  sourceType: string;
  localIds: string[];
  errMsg: string;
}

export interface WxImgUploadResult {
  serverId: string;
  mediaUrl: string; // empty string
  errMsg: string; // uploadImage:ok
}

export interface  QrCodeResult{
  resultStr:string,
  errmsg:string
}

/*
 Generated class for the WxProvider provider.

 See https://mp.weixin.qq.com/wiki?t=resource/res_main&id=mp1445241432
 */
@Injectable()
export class WxProvider {
  PATH_JSCONFIG = API_SERVICE_DOMAIN + "/api/wx/jsconfig";

  initialized = false;

  get wx(): any {
    return window['wx'];
  }

  constructor(public http: HttpClient,
              public toastCtrl: ToastController,
              private auth: AuthService
  ) {
    this.auth.authenticated();
  }

  // 爲充分利用加載時間,這部分在index中調用...這段代碼我通常不用,而是直接在index.html中裸寫。由於加載完Index到程序初始化完成,有一大段時間。
  configWx() {
    let url = window.location.href.split('#')[0];
    // if(url[url.length-1] === '/')url=url.substr(0,url.length-1);
    // url =encodeURIComponent(url);

    this.http.post(this.PATH_JSCONFIG, {url})
      .subscribe((response: any) => {
        this.wx.config({
          debug: false, // 開啓調試模式,調用的全部api的返回值會在客戶端alert出來,若要查看傳入的參數,能夠在pc端打開,參數信息會經過log打出,僅在pc端時纔會打印。
          appId: response.data.appId, //'wx03605b6ba300b93b', // 必填,公衆號的惟一標識
          timestamp: response.data.timestamp, // 必填,生成簽名的時間戳
          nonceStr: response.data.nonceStr, // 必填,生成簽名的隨機串
          signature: response.data.signature,// 必填,簽名,見附錄1
          jsApiList: ['chooseImage', 'scanQRCode', 'uploadImage'] // 必填,須要使用的JS接口列表,全部JS接口列表見附錄2
        });

        this.initialized = true;
      })
  }

  scanQr(): Observable<QrCodeResult> {
    return Observable.create(observer => {
      this.wx.scanQRCode({
        needResult: 1,
        scanType: ["qrCode"], // 能夠指定掃二維碼仍是一維碼,默認兩者都有, "barCode"
        success: res => {
          observer.next(res);
          observer.complete();
        },
        fail: observer.error
      });
    })
  }

  // https://mp.weixin.qq.com/wiki?t=resource/res_main&id=mp1421141115

  /**
   * 返回選擇並上傳到微信服務器端的照片的serverId, 用於後端從微信服務器端拉取。
   *
   * 由於從微信服務器獲取圖片須要使用到公衆號的secretKey等敏感信息,因此,咱們只能多傳一次了。
   * @returns {any}
   */
  choosePictures(): Observable<string> {
    const me = this;

    return Observable.create(observer => {
      this.wx.chooseImage({
        count: 9, // 最多能夠選擇的圖片張數,默認9
        sizeType: ['original', 'compressed'], // original 原圖,compressed 壓縮圖,默認兩者都有
        sourceType: ['album', 'camera'], // album 從相冊選圖,camera 使用相機,默認兩者都有
        success: (res: WxImgSelectResult)=> {
          // 若是沒有選擇任何圖片
          if (!res.localIds.length) {
            observer.complete();
            return;
          }

          let i = 0;
          let img = res.localIds[i];

          // 上傳圖片至微信服務器
          let uploadImg = () => {
            me.wx.uploadImage({
              localId: img, // 須要上傳的圖片的本地ID,由chooseImage接口得到
              isShowProgressTips: 0, // 默認爲1,顯示進度提示
              success: uploadSuccess,
              fail: observer.error
            });
          };

          // 傳完一張再傳下一張,不然會掛掉一些。
          let uploadSuccess = (upd: WxImgUploadResult) => {
            observer.next(upd.serverId);
            img = res.localIds[++i];
            img ? uploadImg() : observer.complete();
          }

          uploadImg();
        },
        fail: observer.error
      })
    });
  }
}

 使用示例:

    this.pictures = [];

    this.wx.choosePictures()
      .switchMap(id => this.wxService.WeixinApi_Media([new VmMedia({id})]))
      .subscribe(pics=> {
        pics.forEach(it => {
          it.picPath = API_SERVICE_DOMAIN + it.picPath;
          it.picPathThumb = API_SERVICE_DOMAIN + it.picPathThumb;
        })
        this.pictures = this.pictures.concat(pics)
      });

   

待解決/未徹底解決的問題

1 條件編譯

開發中,常常會遇到不一樣環境的問題,好比dev環境,爲了繞過驗證,咱們可能採用p_auth驗證,將用戶名和密碼放在請求頭中,這一段代碼每每寫在httpConfig中。並且,dev時,因爲代碼在本地,接口在服務器端,域名不一致,還須要服務器端經過Nginx統一添加跨域請求頭,但生產環境確定不會這樣了。

對於一些簡單的,不太敏感的策略,新建一個app.config.ts裏面export const ENVIRONMENT  = "DEV/TEST/RC2/PROD"就好了。其它的地方寫上和環境對應的代碼,好比main.ts中可能會寫上:

ENVIRONMENT == 'PROD' && enableProdMode();

可是,對於一些敏感信息,咱們不能這樣作。若是沒有每件編譯,意味着咱們得每次註釋掉一些代碼後再build,這樣不優雅。

ionic script使用tsc對ts文件進行編譯,若是使用typescript-plus,能夠有條件編譯的功能。問題是,ionic的命令行,把這些都整合死了,若是要改。。。算了,我仍是老老實實的註釋了發佈吧。

也許不久,tsc就會支持條件編譯了,希望吧。  

 

---不按期更新

--tab中的navCtrl有坑,這一塊目前還暫時沒時間去研究。因此,也沒法作答了。

相關文章
相關標籤/搜索