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有坑,这一块目前还暂时没时间去研究。因此,也没法作答了。

相关文章
相关标签/搜索