HBuilderX

HBuilderX

极客开发工具
uni-app

uni-app

开发一次,多端覆盖
uniCloud

uniCloud

云开发平台
HTML5+

HTML5+

增强HTML5的功能体验
MUI

MUI

上万Star的前端框架

HBuilder为什么要登录。没网、内网怎么办?可否离线使用?

内网 离线使用 账户 注册 登录

HBuilderX

HBuilderX可以离线支持,登陆也不是强制的。
HBuilderX需要联网的地方包括:

  • 开发App时,仍然需要联网获取appid(不影响uni-app的非App端)
  • 云打包需要联网。不想联网也可以配置本地打包,参考https://ask.dcloud.net.cn/docs/#//ask.dcloud.net.cn/article/508
  • HBuilderX安装插件需要联网。如果是内网使用,可以在外网下载好插件,然后把plugin目录copy到内网使用。

=======================以下为老HBuilder相关内容======================================

HBuilder

首先HBuilder支持离线使用,在登录界面有一个跳过注册离线使用的按钮。
但是我们还是需要说一下我们的设计理念,为什么需要联网。

理念

为什么要设计账户体系。
我们认为单机工具没有前途,云+端的开发者服务代表未来,有更多想象空间。
而帐户体系是一切服务的基础。
我们享受了很多免费的互联网服务,但有一个代价,就是需要注册账户。
现在的手机、windows也都需要注册账户,都是云+端的趋势所致。
在开发工具领域,目前vs、dreameweaver新版也需要注册账户,未来这样的工具会越来越多。

安全性

如果开发者顾虑HBuilder会偷偷上传代码,那么不必担忧。
HBuilder需要账户,不是因为计划偷偷上传开发者的代码。
DCloud作为有更大雄心的企业,不会冒让自己身败名裂的巨大风险做坏事,而且我们也实在没动力去看开发者写的代码。
DCloud的股东包括很多开发界的知名人士,比如蒋涛、王淮,名人不会轻易代言,也算对我们的一种背书。

首次使用

HBuilder在一台电脑上第一次使用时需要联网验证激活。
如果是公司有限制,可以请求网管对HBuilder的安全性做测试,安全性肯定是没问题的,毕竟程序员还要上网查资料,没网络怎么工作。可以把dcloud.net.cn加入网络白名单(dcloud.net.cn是托管在阿里云的,旗下有service.dcloud,net.cn、update.dcloud.net.cn、ask.dcloud.net.cn等子站)。
HBuilder支持暂不登录。初次使用HBuilder,若没有网,也可以进入HBuilder。
不过我们还是推荐用户注册一下,不然每次启动HBuilder都要点下暂不登录按钮。(那话怎么说来着,注册个帐户你又不会怀孕)

后续使用

帐户激活过一次后,后续HBuilder支持完全脱线使用,也不用再每次启动点暂不登录。(未激活过还需要点暂不登录)
开发者使用笔记本会经常没有网络,此时可放心,一样是可以写代码的。
只是脱线后需要联网的功能没法用,包括:

  • 云端打包(我们也提供了本地打包)
  • 插件一键式云端下载安装(也可使用eclipse的本地插件安装方式装插件)
  • 升级,包括HBuilder及插件的升级无网络时都无法运行
  • 吐槽、问答等功能也需要联网
  • 没有网络无法自动每日签到,无法得到签到积分

其他误会

有人误以为登录会造成启动过慢,其实完全不会,1秒都不影响。
前面已经提到,没网络时HBuilder也是可直接进入的。
有人误以为我们做账户体系是为了收费,以后会推出收费免登录的功能。我们确实没这种想法。

如何让公司网管信任HBuilder

关于如何让公司网管信任HBuilder,我们也有点头疼。也欢迎大家给出好招。
但DCloud愿意配合开发者的公司网管的安全审计。
网管可以侦测HBuilder的网络通信流量,肯定是小到不足以上传工程代码的地步。

云服务的强化

后续DCloud还会继续开发更多创新的云服务,让大家体验到不一样的开发模式,账户的属性强化会越来越有意思。

最后,中国人之前从来没有做成功过开发工具,我们开发HBuilder的过程中也经历过很多质疑,
尤其是制作免费工具,不图给开发者卖钱,不就图多几个注册用户嘛,希望大家多支持我们,多帮我们发展用户,谢谢!

继续阅读 »

HBuilderX

HBuilderX可以离线支持,登陆也不是强制的。
HBuilderX需要联网的地方包括:

  • 开发App时,仍然需要联网获取appid(不影响uni-app的非App端)
  • 云打包需要联网。不想联网也可以配置本地打包,参考https://ask.dcloud.net.cn/docs/#//ask.dcloud.net.cn/article/508
  • HBuilderX安装插件需要联网。如果是内网使用,可以在外网下载好插件,然后把plugin目录copy到内网使用。

=======================以下为老HBuilder相关内容======================================

HBuilder

首先HBuilder支持离线使用,在登录界面有一个跳过注册离线使用的按钮。
但是我们还是需要说一下我们的设计理念,为什么需要联网。

理念

为什么要设计账户体系。
我们认为单机工具没有前途,云+端的开发者服务代表未来,有更多想象空间。
而帐户体系是一切服务的基础。
我们享受了很多免费的互联网服务,但有一个代价,就是需要注册账户。
现在的手机、windows也都需要注册账户,都是云+端的趋势所致。
在开发工具领域,目前vs、dreameweaver新版也需要注册账户,未来这样的工具会越来越多。

安全性

如果开发者顾虑HBuilder会偷偷上传代码,那么不必担忧。
HBuilder需要账户,不是因为计划偷偷上传开发者的代码。
DCloud作为有更大雄心的企业,不会冒让自己身败名裂的巨大风险做坏事,而且我们也实在没动力去看开发者写的代码。
DCloud的股东包括很多开发界的知名人士,比如蒋涛、王淮,名人不会轻易代言,也算对我们的一种背书。

首次使用

HBuilder在一台电脑上第一次使用时需要联网验证激活。
如果是公司有限制,可以请求网管对HBuilder的安全性做测试,安全性肯定是没问题的,毕竟程序员还要上网查资料,没网络怎么工作。可以把dcloud.net.cn加入网络白名单(dcloud.net.cn是托管在阿里云的,旗下有service.dcloud,net.cn、update.dcloud.net.cn、ask.dcloud.net.cn等子站)。
HBuilder支持暂不登录。初次使用HBuilder,若没有网,也可以进入HBuilder。
不过我们还是推荐用户注册一下,不然每次启动HBuilder都要点下暂不登录按钮。(那话怎么说来着,注册个帐户你又不会怀孕)

后续使用

帐户激活过一次后,后续HBuilder支持完全脱线使用,也不用再每次启动点暂不登录。(未激活过还需要点暂不登录)
开发者使用笔记本会经常没有网络,此时可放心,一样是可以写代码的。
只是脱线后需要联网的功能没法用,包括:

  • 云端打包(我们也提供了本地打包)
  • 插件一键式云端下载安装(也可使用eclipse的本地插件安装方式装插件)
  • 升级,包括HBuilder及插件的升级无网络时都无法运行
  • 吐槽、问答等功能也需要联网
  • 没有网络无法自动每日签到,无法得到签到积分

其他误会

有人误以为登录会造成启动过慢,其实完全不会,1秒都不影响。
前面已经提到,没网络时HBuilder也是可直接进入的。
有人误以为我们做账户体系是为了收费,以后会推出收费免登录的功能。我们确实没这种想法。

如何让公司网管信任HBuilder

关于如何让公司网管信任HBuilder,我们也有点头疼。也欢迎大家给出好招。
但DCloud愿意配合开发者的公司网管的安全审计。
网管可以侦测HBuilder的网络通信流量,肯定是小到不足以上传工程代码的地步。

云服务的强化

后续DCloud还会继续开发更多创新的云服务,让大家体验到不一样的开发模式,账户的属性强化会越来越有意思。

最后,中国人之前从来没有做成功过开发工具,我们开发HBuilder的过程中也经历过很多质疑,
尤其是制作免费工具,不图给开发者卖钱,不就图多几个注册用户嘛,希望大家多支持我们,多帮我们发展用户,谢谢!

收起阅读 »

App/uni-app离线本地存储方案

离线本地存储 离线存储 本地数据库 websql sqllite indexedDB

5+App的离线存储

HTML5+的离线本地存储有如下多种方案:
HTML5标准方案:cookie、localstorage、sessionstorage、websql、indexedDB
HTML5Plus扩展方案:plus.navigator.setCookie、plus.storage、plus.io、plus.sqllite

  • cookie(标准h5方案)
    体量最小,可以设置过期时间。不能跨域。
  • localstorage(标准h5方案)
    适合key、value键值对的存储,数据量一般不超过5M。是常用的轻量数据存储方案。不能跨域。
  • sessionstorage(标准h5方案)
    也是键值对,特点是关闭App就消失了,也不能跨webview,一般不用于持久化数据保存。
  • websql(标准h5方案)
    是手机端关系型数据库,各种手机都支持。注意iOS8、9的wkWebview不支持websql。如果要在iOS8、9上使用websql,请使用uiwebview内核。
  • indexedDB(标准h5方案)
    是HTML5里最新的数据存储规范,但不是基于SQL,而是基于对象。
    indexedDB性能更高,全是异步处理,学习难度偏大。最重要的是目前手机端支持度不行。Android4.4以上和iOS8以上才支持indexedDB。
  • plus.navigator.setCookie
    与HTML5的标准cookie相比,plus的扩展主要是为了跨域。所谓跨越,就是本地HTML页面和服务器HTML页面共享cookie数据,或者说本地页面的js可以操作服务器页面产生的cookie。如果没有跨越需求,不需要使用plus扩展。注意iOS8以后的wkWebview不支持setcookie。
  • plus.storage
    plus.storage也是键值对数据存储。它是把OS给原生App使用的键值对存储数据库封装一层给JS使用。
    plus.storage没有理论上的大小限制。也是持久化的,不会被当做缓存清理。
    plus.storage相比于localstorage 还有一个特点是可跨域。当一个存储数据,需要被本地和来自服务器的页面同时读写时,就涉及跨域问题。此时HTML5的localstorage不能满足需求,只能使用plus.storage。
    plus.storage操作要比localstorage慢几十毫秒,尤其是在循环里调用plus api会放大这种慢。
    有网友封装了一个框架,针对key-value数据,在localstorage超过5m时自动切换到plus.storage,参考http://ask.dcloud.net.cn/article/552。虽然这么做听起来有点复杂,但我们对这种追求性能极致的开发者非常赞赏。
  • plus.io
    plus.io是文件读写,虽然也可以通过读写txt等文件存储数据,但并不如专业的storage和websql方便。
    plus.io更多的是用于图片等多媒体文件的本地保存。
    比如图文列表的离线使用,一般有2种做法:
    1. 图片下载不通过img的src,而是plus.dowload下载的,先下载图片,存好路径后,然后img的src动态指定文件路径
    2. 图片使用img的src下载,然后用canvas把img存成图片文件。下次不联网,img的scr直接指向本地文件
  • plus.sqllite
    plus.sqllite是对原生的sqllite的封装。它也是一种可以通过sql在本地增删改查数据库的方案。
    有点类似websql,但相对于websql而言,sqllite的好处是:
    1. 可以预置基础数据库,直接打包到app里
    2. 当手机空间不足时,websql可能会被清理,而sqllite不会。
      plus.storage没有理论上的大小限制。

有人问三方清理工具清理垃圾会不会造成某些数据丢失,这个可能性是存在的,但概率并不高,取决于清理软件会不会分析你的存储数据里哪些是可以清除的垃圾数据。除了OS的清理工具外,一般没有root权限的清理工具是拿不到除了plus.io外的你的app的存储数据的。
但ios上系统存储空间很少的时候,系统会清理 cookie、localstorage、sessionstorage、websql、indexedDB 的数据,此时使用plus.storage、plus.sqllite更安全。

uni-app存储

uni-app的存储方案比5+app要少,因为cookie、localstorage、sessionstorage、websql、indexedDB只有h5端支持,其他端都不支持。
uni.storage的键值对存储,这个是全端支持的。
uni-app的Storage在不同端的实现不同,uni.storage在app侧,映射为plus.storage;h5侧映射为localstorage;各个小程序平台映射为其自带的storage键值对存储:

  • H5端为localStorage,浏览器限制5M大小,是缓存概念,可能会被清理
  • App端为原生的plus.storage,无大小限制,不是缓存,持久化
  • 各个小程序端为其自带的storage api,数据存储生命周期跟小程序本身一致,即除用户主动删除或超过一定时间被自动清理,否则数据都一直可用。
  • 微信小程序单个 key 允许存储的最大数据长度为 1MB,所有数据存储上限为 10MB。
  • 支付宝小程序单条数据转换成字符串后,字符串长度最大200*1024。同一个支付宝用户,同一个小程序缓存总上限为10MB。
  • 百度、头条小程序文档未说明大小限制

app端还支持2种方案

继续阅读 »

5+App的离线存储

HTML5+的离线本地存储有如下多种方案:
HTML5标准方案:cookie、localstorage、sessionstorage、websql、indexedDB
HTML5Plus扩展方案:plus.navigator.setCookie、plus.storage、plus.io、plus.sqllite

  • cookie(标准h5方案)
    体量最小,可以设置过期时间。不能跨域。
  • localstorage(标准h5方案)
    适合key、value键值对的存储,数据量一般不超过5M。是常用的轻量数据存储方案。不能跨域。
  • sessionstorage(标准h5方案)
    也是键值对,特点是关闭App就消失了,也不能跨webview,一般不用于持久化数据保存。
  • websql(标准h5方案)
    是手机端关系型数据库,各种手机都支持。注意iOS8、9的wkWebview不支持websql。如果要在iOS8、9上使用websql,请使用uiwebview内核。
  • indexedDB(标准h5方案)
    是HTML5里最新的数据存储规范,但不是基于SQL,而是基于对象。
    indexedDB性能更高,全是异步处理,学习难度偏大。最重要的是目前手机端支持度不行。Android4.4以上和iOS8以上才支持indexedDB。
  • plus.navigator.setCookie
    与HTML5的标准cookie相比,plus的扩展主要是为了跨域。所谓跨越,就是本地HTML页面和服务器HTML页面共享cookie数据,或者说本地页面的js可以操作服务器页面产生的cookie。如果没有跨越需求,不需要使用plus扩展。注意iOS8以后的wkWebview不支持setcookie。
  • plus.storage
    plus.storage也是键值对数据存储。它是把OS给原生App使用的键值对存储数据库封装一层给JS使用。
    plus.storage没有理论上的大小限制。也是持久化的,不会被当做缓存清理。
    plus.storage相比于localstorage 还有一个特点是可跨域。当一个存储数据,需要被本地和来自服务器的页面同时读写时,就涉及跨域问题。此时HTML5的localstorage不能满足需求,只能使用plus.storage。
    plus.storage操作要比localstorage慢几十毫秒,尤其是在循环里调用plus api会放大这种慢。
    有网友封装了一个框架,针对key-value数据,在localstorage超过5m时自动切换到plus.storage,参考http://ask.dcloud.net.cn/article/552。虽然这么做听起来有点复杂,但我们对这种追求性能极致的开发者非常赞赏。
  • plus.io
    plus.io是文件读写,虽然也可以通过读写txt等文件存储数据,但并不如专业的storage和websql方便。
    plus.io更多的是用于图片等多媒体文件的本地保存。
    比如图文列表的离线使用,一般有2种做法:
    1. 图片下载不通过img的src,而是plus.dowload下载的,先下载图片,存好路径后,然后img的src动态指定文件路径
    2. 图片使用img的src下载,然后用canvas把img存成图片文件。下次不联网,img的scr直接指向本地文件
  • plus.sqllite
    plus.sqllite是对原生的sqllite的封装。它也是一种可以通过sql在本地增删改查数据库的方案。
    有点类似websql,但相对于websql而言,sqllite的好处是:
    1. 可以预置基础数据库,直接打包到app里
    2. 当手机空间不足时,websql可能会被清理,而sqllite不会。
      plus.storage没有理论上的大小限制。

有人问三方清理工具清理垃圾会不会造成某些数据丢失,这个可能性是存在的,但概率并不高,取决于清理软件会不会分析你的存储数据里哪些是可以清除的垃圾数据。除了OS的清理工具外,一般没有root权限的清理工具是拿不到除了plus.io外的你的app的存储数据的。
但ios上系统存储空间很少的时候,系统会清理 cookie、localstorage、sessionstorage、websql、indexedDB 的数据,此时使用plus.storage、plus.sqllite更安全。

uni-app存储

uni-app的存储方案比5+app要少,因为cookie、localstorage、sessionstorage、websql、indexedDB只有h5端支持,其他端都不支持。
uni.storage的键值对存储,这个是全端支持的。
uni-app的Storage在不同端的实现不同,uni.storage在app侧,映射为plus.storage;h5侧映射为localstorage;各个小程序平台映射为其自带的storage键值对存储:

  • H5端为localStorage,浏览器限制5M大小,是缓存概念,可能会被清理
  • App端为原生的plus.storage,无大小限制,不是缓存,持久化
  • 各个小程序端为其自带的storage api,数据存储生命周期跟小程序本身一致,即除用户主动删除或超过一定时间被自动清理,否则数据都一直可用。
  • 微信小程序单个 key 允许存储的最大数据长度为 1MB,所有数据存储上限为 10MB。
  • 支付宝小程序单条数据转换成字符串后,字符串长度最大200*1024。同一个支付宝用户,同一个小程序缓存总上限为10MB。
  • 百度、头条小程序文档未说明大小限制

app端还支持2种方案

收起阅读 »

plus初始化原理及plus is not defined,mui is not defined 错误汇总

plusready plus is not defined

关于plus是哪里来的问题

plus是5+Runtime的内部对象。
就像chrome浏览器里有chrome.开头的一些对象方法,5+runtime内部内置了plus对象。
因为plus和mui不一样,plus是引擎级别的,不需要前端框架。而mui是前端框架,所以是要引入mui.js才能使用的。

不要在没有plus和mui的环境下调用相关API

普通浏览器里没有plus环境,只有HBuilder真机运行、打包后、或流应用环境下才能运行plus api。  
在普通浏览器里运行时plus api时控制台必然会输出plus is not defined错误提示。  
mui作为一个前端框架,你必须保证当前页面引入了mui.js。否则也会出现mui is not defined。  

不要在plus和mui未完成初始化时调用相关API

就像在dom初始化完成前(DOMContentLoaded)去操作dom,就会报错是一样的道理。  
plus和mui都需要初始化,在初始化完成后调用再调用。  
一般我们在plusready的回调事件里调用plus api。
        document.addEventListener('plusready',function () {  
        // 在这里调用plus api  
    },false);

执行更高效的写法是这样,如果plus已经存在,就直接使用

        function plusReady(){  
            // 在这里调用plus api  
        }  
        if(window.plus){  
            plusReady();  
        }else{  
            document.addEventListener('plusready',plusReady,false);  
        }
mui框架对此进行了封装,写法更简单:  
        mui.plusReady(function(){  
             // 在这里调用plus api  
        });
事实上,mui作为一个框架,也有初始化的过程,但mui的初始化过程在DOMContentLoaded完毕后就结束了。  
mui ready的写法是这样,但一般无需使用。  
        mui.ready(function () {  

        })

关于加载顺序

plus的ready的时间,在2016年12月后的ios版本上,其实是随时可用状态,不需要plus ready概念,但为了保持向下兼容,也仍然存在plus的ready事件;而安卓版本,plus的ready的时间是可以调节的。具体参考http://ask.dcloud.net.cn/article/921
而mui的ready一般伴随DOMContentLoaded而完成。
详细的启动时序参考这里:http://ask.dcloud.net.cn/article/571

继续阅读 »

关于plus是哪里来的问题

plus是5+Runtime的内部对象。
就像chrome浏览器里有chrome.开头的一些对象方法,5+runtime内部内置了plus对象。
因为plus和mui不一样,plus是引擎级别的,不需要前端框架。而mui是前端框架,所以是要引入mui.js才能使用的。

不要在没有plus和mui的环境下调用相关API

普通浏览器里没有plus环境,只有HBuilder真机运行、打包后、或流应用环境下才能运行plus api。  
在普通浏览器里运行时plus api时控制台必然会输出plus is not defined错误提示。  
mui作为一个前端框架,你必须保证当前页面引入了mui.js。否则也会出现mui is not defined。  

不要在plus和mui未完成初始化时调用相关API

就像在dom初始化完成前(DOMContentLoaded)去操作dom,就会报错是一样的道理。  
plus和mui都需要初始化,在初始化完成后调用再调用。  
一般我们在plusready的回调事件里调用plus api。
        document.addEventListener('plusready',function () {  
        // 在这里调用plus api  
    },false);

执行更高效的写法是这样,如果plus已经存在,就直接使用

        function plusReady(){  
            // 在这里调用plus api  
        }  
        if(window.plus){  
            plusReady();  
        }else{  
            document.addEventListener('plusready',plusReady,false);  
        }
mui框架对此进行了封装,写法更简单:  
        mui.plusReady(function(){  
             // 在这里调用plus api  
        });
事实上,mui作为一个框架,也有初始化的过程,但mui的初始化过程在DOMContentLoaded完毕后就结束了。  
mui ready的写法是这样,但一般无需使用。  
        mui.ready(function () {  

        })

关于加载顺序

plus的ready的时间,在2016年12月后的ios版本上,其实是随时可用状态,不需要plus ready概念,但为了保持向下兼容,也仍然存在plus的ready事件;而安卓版本,plus的ready的时间是可以调节的。具体参考http://ask.dcloud.net.cn/article/921
而mui的ready一般伴随DOMContentLoaded而完成。
详细的启动时序参考这里:http://ask.dcloud.net.cn/article/571

收起阅读 »

App图标和启动封面不变的原因汇总

启动图片 图标
  1. 真机运行无法更换App图标和启动封面
    因为这类信息的更改是必须打包后才能变化的。而真机运行不是打包,仅仅是替换我们的基座包的资源文件。
  2. 打包时选择的图片不对
    请严格按照指定的尺寸使用png格式提交打包,尺寸不能差。
    务必注意不能把jpg等文件改名为png来使用。
  3. 打包时漏选了图片
    如果打包时漏选了图片,在漏选的平台上就会出现HBuilder默认的图片。比如只选了iphone没有选ipad,那ipad上就会有问题
  4. 缓存
    其他配置都对,但打包后图标还不变化,如果在新安装的手机没问题,那就是老手机的桌面缓存,重启下手机,或者更换手机主题。
继续阅读 »
  1. 真机运行无法更换App图标和启动封面
    因为这类信息的更改是必须打包后才能变化的。而真机运行不是打包,仅仅是替换我们的基座包的资源文件。
  2. 打包时选择的图片不对
    请严格按照指定的尺寸使用png格式提交打包,尺寸不能差。
    务必注意不能把jpg等文件改名为png来使用。
  3. 打包时漏选了图片
    如果打包时漏选了图片,在漏选的平台上就会出现HBuilder默认的图片。比如只选了iphone没有选ipad,那ipad上就会有问题
  4. 缓存
    其他配置都对,但打包后图标还不变化,如果在新安装的手机没问题,那就是老手机的桌面缓存,重启下手机,或者更换手机主题。
收起阅读 »

插件安装指南

vim tomcat CVS 插件 全屏插件 JDT ADT FTP

前言

HBuilder是基于eclipse 3.7 开发的,英文版本为indigo,对eclipse3.x版本的插件是兼容的,不兼容eclipse4.x版本的插件。目前在HBuilder的工具→插件安装中已经集成了svn、git、php等常用的插件。以下为其他一些插件,如需要请按本文步骤进行安装。

VIM插件

1.依次点击工具-插件安装

  1. 点击手动安装eclipse插件
  2. 点击添加
  3. 在弹出框的名称随便填,地址填http://update.dcloud.net.cn/test/plugin_for_test如下图

    5.点击确定,等待加载,选择vrapper
  4. 一直点下一步,出现授权协议时点同意,装完后重启即可

    node.js插件

    参见node.js插件安装

    全屏插件

    点击这里下载全屏插件下载
    解压到HBuilder根目录(注意不要改文件夹的名字),启动HBuilder,然后再次重启HBuilder即可。默认热键是CTRL+ALT+Z,你可以在工具-选项中搜索快捷键,在快捷键列表中搜索全屏,修改绑定热键。

    Eclipse Java Development Tools

    Java开发环境,如需编辑java类和jsp需安装此插件,安装办法参见JDT插件安装办法

    Eclipse Android Development Tools

    Android开发环境,安装办法参见ADT插件安装办法

    CVS

    CVS插件被我们精简掉了,如需使用cvs插件,请下载cvs插件。将压缩包里的jar包放到HBuilder的dropins目录下,重启HBuilder即可

    sexftp

    HBuilder自带的插件列表中已包含ftp插件,但有用户反馈目前集成的ftp插件不是很好用,而且在某些情况下目前集成的ftp插件会出现问题。有不少推荐了sexftp,但是由于sexftp插件依赖JDT,导致暂时无法集成该插件,如有需要可按此步骤安装插件SexFtp插件安装

    tomcat插件

    需要先安装JDT,然后安装tomcat插件,下载后把com.sysdeo.eclipse.tomcat_3.2.1目录放在HBuilder的dropins目录下重启HBuilder即可

    PDF预览插件

    依次点击工具-插件安装-手动安装eclipse插件
    点击添加,在名称处填写pdf(可以随意填写),在地址处填写http://borisvl.github.io/Pdf4Eclipse/如下图
    pdf插件
    点击确定等待加载,加载完毕后勾选安装即可

其他

此文章将会不停补充,如果大家也可以向官方推荐自己常用的一些插件,同时如果遇到安装某个插件装不上的问题也可以联系我

继续阅读 »

前言

HBuilder是基于eclipse 3.7 开发的,英文版本为indigo,对eclipse3.x版本的插件是兼容的,不兼容eclipse4.x版本的插件。目前在HBuilder的工具→插件安装中已经集成了svn、git、php等常用的插件。以下为其他一些插件,如需要请按本文步骤进行安装。

VIM插件

1.依次点击工具-插件安装

  1. 点击手动安装eclipse插件
  2. 点击添加
  3. 在弹出框的名称随便填,地址填http://update.dcloud.net.cn/test/plugin_for_test如下图

    5.点击确定,等待加载,选择vrapper
  4. 一直点下一步,出现授权协议时点同意,装完后重启即可

    node.js插件

    参见node.js插件安装

    全屏插件

    点击这里下载全屏插件下载
    解压到HBuilder根目录(注意不要改文件夹的名字),启动HBuilder,然后再次重启HBuilder即可。默认热键是CTRL+ALT+Z,你可以在工具-选项中搜索快捷键,在快捷键列表中搜索全屏,修改绑定热键。

    Eclipse Java Development Tools

    Java开发环境,如需编辑java类和jsp需安装此插件,安装办法参见JDT插件安装办法

    Eclipse Android Development Tools

    Android开发环境,安装办法参见ADT插件安装办法

    CVS

    CVS插件被我们精简掉了,如需使用cvs插件,请下载cvs插件。将压缩包里的jar包放到HBuilder的dropins目录下,重启HBuilder即可

    sexftp

    HBuilder自带的插件列表中已包含ftp插件,但有用户反馈目前集成的ftp插件不是很好用,而且在某些情况下目前集成的ftp插件会出现问题。有不少推荐了sexftp,但是由于sexftp插件依赖JDT,导致暂时无法集成该插件,如有需要可按此步骤安装插件SexFtp插件安装

    tomcat插件

    需要先安装JDT,然后安装tomcat插件,下载后把com.sysdeo.eclipse.tomcat_3.2.1目录放在HBuilder的dropins目录下重启HBuilder即可

    PDF预览插件

    依次点击工具-插件安装-手动安装eclipse插件
    点击添加,在名称处填写pdf(可以随意填写),在地址处填写http://borisvl.github.io/Pdf4Eclipse/如下图
    pdf插件
    点击确定等待加载,加载完毕后勾选安装即可

其他

此文章将会不停补充,如果大家也可以向官方推荐自己常用的一些插件,同时如果遇到安装某个插件装不上的问题也可以联系我

收起阅读 »

从网上下载的mui包放入xcode本地打包的程序中时出错

从网上下载的mui包,包括从gethub上,dcloud中建立的mui等

放入xcode中的本地打包程序中时,再按照官网的本地打包流程修改后,运行会出错,

解决方法虽然很简单但是,真是不合乎常理啊,望给新人提供点经验

解决方法:将manifest.json中的注释删除掉便可

继续阅读 »

从网上下载的mui包,包括从gethub上,dcloud中建立的mui等

放入xcode中的本地打包程序中时,再按照官网的本地打包流程修改后,运行会出错,

解决方法虽然很简单但是,真是不合乎常理啊,望给新人提供点经验

解决方法:将manifest.json中的注释删除掉便可

收起阅读 »

TabBar中使用WebView模式子页面中返回主页面时遇到的问题

在TabBar中使用WebView模式子页面中,有这样的一个流程,首页是index.html页面,能实现四个tab之间的切换


此时,tabbar已经不再显示了,然后再页面A里面再mui.open打开页面B,页面B有一个按钮,点击返回主页,我用的也是mui.openWindow()打开index.html,结果返回到我的index.html时候,无论我点击哪一个tab时候只显示第一个页面,这是什么原因呢?

继续阅读 »

在TabBar中使用WebView模式子页面中,有这样的一个流程,首页是index.html页面,能实现四个tab之间的切换


此时,tabbar已经不再显示了,然后再页面A里面再mui.open打开页面B,页面B有一个按钮,点击返回主页,我用的也是mui.openWindow()打开index.html,结果返回到我的index.html时候,无论我点击哪一个tab时候只显示第一个页面,这是什么原因呢?

收起阅读 »

插件开发示例汇总

5+App开发 插件开发

Android平台插件

调用系统通讯录选择手机号

http://ask.dcloud.net.cn/article/153

系统通知栏显示进度条

http://ask.dcloud.net.cn/article/155

iOS平台插件

IOS指纹识别插件开发方法

http://ask.dcloud.net.cn/article/1348

继续阅读 »

Android平台插件

调用系统通讯录选择手机号

http://ask.dcloud.net.cn/article/153

系统通知栏显示进度条

http://ask.dcloud.net.cn/article/155

iOS平台插件

IOS指纹识别插件开发方法

http://ask.dcloud.net.cn/article/1348

收起阅读 »

iOS平台Webview窗口侧滑返回滚动问题

5+App开发

从HBuilder5.2.2以后版本修复了Webview窗口在侧滑返回时页面仍然可以上下滚动的问题,在大多数情况侧滑返回时Webview窗口不可上下滚动,但是由于系统机制原因目前还存在以下缺陷:

  • 如果Webview窗口页面存在横向滚动条,那么侧滑返回功能失效
    在HelloH5应用中"Webview" -> "窗口嵌套",在地址栏输入“http://www.dcloud.io/”可重现,此时无法通过侧滑返回关闭Webview窗口

    暂时只能避免出现横向滚动条的出现来解决此问题

  • 如果Webview窗口通过监听touch*事件来实现div的滚动,依然会出现侧滑返回是可滚动div
    在HelloMUI应用中"pull to refresh(下拉刷新和上拉加载更多)"可重现,侧滑返回时仍然可以滚动下拉刷新

    这种情况可通过监听popGesture事件来动态开关div的滚动

    function plusReady(){  
    var wvs=plus.webview.currentWebview();  
    wvs.addEventListener( "popGesture", function(e){  
        if(e.type=="start"){  
            //关闭div滚动  
        }else if(type="end"){  
            //开启div滚动  
        }  
    }, false );  
    }  
    document.addEventListener("plusready",plusReady,false);
继续阅读 »

从HBuilder5.2.2以后版本修复了Webview窗口在侧滑返回时页面仍然可以上下滚动的问题,在大多数情况侧滑返回时Webview窗口不可上下滚动,但是由于系统机制原因目前还存在以下缺陷:

  • 如果Webview窗口页面存在横向滚动条,那么侧滑返回功能失效
    在HelloH5应用中"Webview" -> "窗口嵌套",在地址栏输入“http://www.dcloud.io/”可重现,此时无法通过侧滑返回关闭Webview窗口

    暂时只能避免出现横向滚动条的出现来解决此问题

  • 如果Webview窗口通过监听touch*事件来实现div的滚动,依然会出现侧滑返回是可滚动div
    在HelloMUI应用中"pull to refresh(下拉刷新和上拉加载更多)"可重现,侧滑返回时仍然可以滚动下拉刷新

    这种情况可通过监听popGesture事件来动态开关div的滚动

    function plusReady(){  
    var wvs=plus.webview.currentWebview();  
    wvs.addEventListener( "popGesture", function(e){  
        if(e.type=="start"){  
            //关闭div滚动  
        }else if(type="end"){  
            //开启div滚动  
        }  
    }, false );  
    }  
    document.addEventListener("plusready",plusReady,false);
收起阅读 »

设计基于HTML5的APP登录功能及安全调用接口的方式(原理篇)

登录 保存密码 安全 加密

最近发现群内大伙对用Hbuilder做的APP怎么做登录功能以及维护登录状态非常困惑,而我前一段时间正好稍微研究了一下,所以把我知道的告诉大家,节约大家查找资料的时间。

你是否真的需要登录功能?

把这个问题放在最前面并不是灌水,而是真的见过很多并不需要登录的APP去做了登录功能,或者是并不需要强制登录的APP把登录作为启动页。
用户对你的APP一无所知,你就要求对方注册并登录,除非APP本身已经很有名气或者是用户有强需求,否则正常人应该会直接把它删掉。
比较温和的方式是将一些并不需要登录,但可以给用户带来帮助的东西,第一时间展现给他们,让他们产生兴趣,再在合适的时机引导他们注册(比如使用需要使用更高级的功能,或用户需要收藏某个喜欢的信息时)。

登录和注册要足够简单

这是小小的手机端,用再好的输入法,打字也是不方便的,所以别把登录页设计得需要填很多东西。如果有可能的话,只填手机号,让用户收到短信验证码就完成注册是最好不过的了。想获得更多信息?想想大公司的APP是怎么做的,他们会告诉用户,现在的个人资料完善程度是30%,如果想获得更多积分,你需要填完。
tips:如果你想发布在Appstore并且同时包含注册功能,那么注册页面必须做一个用户许可协议的链接,否则有可能通不过审核。

实现登录后的session有几种方式?

APP当浏览器用,直接载入远程页面

这种情况是很多偷懒的程序员或者傻X的老板选择的方式,因为做起来实在太快。如果本身网站是响应式布局,那么很有可能不需要做什么更改,就只要在开发时打开首页就好了,这样Hybird的APP外壳就纯粹成为了一个浏览器。
但比起这样做带来的无数缺点来,开发速度快的优点几乎可以忽略不计。
首先,在网络环境不佳时,纯大白页,用户体验0;
然后,CSS和JS等资源不在本地,需要远程载入,如果使用了bootstrap之类的框架,那用户为了开一下APP而耗费的流量真是令人感动;
再然后,网页里常用的jquery,在手机的webview里速度并不理想,而如果是非ajax的网页那就更糟心了,每次操作都要跳转和页面渲染,要让人把它当成APP那实在是笑话。
再再然后,这样的所谓APP,要通过Appstore的审查,那是做梦的(除非审核员当天闹肚子严重,拿着纸巾奔向厕所前误点了通过……),苹果的要求是,这得是APP,而不能是某个网站做成APP的样子,那样的情况适合做Web APP。而据我所知,国内几个较大的Android市场,这样的APP也是无法通过审核的。

调用后端接口

这是个很好的时代,因为无论后端你是用Java、PHP,还是node.js,都可以通过xml、json来和APP通讯。遥想当年写服务端要自己写包结构,然后为了解决并发问题还折腾了半年IOCP模型,真心觉得现在太幸福了。
把刚才那个用APP当浏览器使的案例的所有缺点反过来看,就是这样做的优点,在优化完善的情况下体验接近原生,而且通讯流量极少,通过各种审核也是妥妥的。
tips:通过plus对象中的XMLHttpRequest来Get、Post远程的后端接口,或者使用Mui中封装好的AJAX相关函数

插一段代码,我把mui的ajax又做了进一步的封装,对超时进行了自动重试,而对invalid_token等情况也做相应处理:

;mui.web_query = function(func_url, params, onSuccess, onError, retry){  
    var onSuccess = arguments[2]?arguments[2]:function(){};  
    var onError = arguments[3]?arguments[3]:function(){};  
    var retry = arguments[4]?arguments[4]:3;  
    func_url = 'http://www.xxxxxx.com/ajax/?fn=' + func_url;  
    mui.ajax(func_url, {  
        data:params,  
        dataType:'json',  
        type:'post',  
        timeout:3000,  
        success:function(data){  
            if(data.err === 'ok'){  
                onSuccess(data);  
            }  
            else{  
                onError(data.code);  
            }  
        },  
        error:function(xhr,type,errorThrown){  
            retry--;  
            if(retry > 0) return mui.web_query(func_url, params, onSuccess, onError, retry);  
            onError('FAILED_NETWORK');  
        }  
    })  
};
var onError = function(errcode){  
    switch(errcode){  
    case 'FAILED_NETWORK':  
        mui.toast('网络不佳');  
        break;  
    case 'INVALID_TOKEN':  
        wv_login.show();  
        break;  
    default:  
        console.log(errcode);  
    }  
};  
var params = {per:10, pageno:coms_current_pageno};  
mui.web_query('get_com_list', params, onSuccess, onError, 3);

调用后端接口怎么样才安全?

在APP中保存登录数据,每次调用接口时传输

程序员总能给自己找到偷懒的方法,有的程序为了省事,会在用户登录后,直接把用户名和密码保存在本地,然后每次调用后端接口时作为参数传递。真省事儿啊!可这种方法简单就像拿着一袋子钱在路上边走边喊“快来抢我呀!快来抢我呀!”,一个小小的嗅探器就能把用户的密码拿到手,如果用户习惯在所有地方用一个密码,那么你闯大祸了,黑客通过撞库的方法能把用户的所有信息一锅端。

登录时请求一次token,之后用token调用接口

这是比较安全的方式,用户在登录时,APP调用获取token的接口(比如http://api.abc.com/get_token/),用post将用户名和密码的摘要传递给服务器,然后服务器比对数据库中的用户信息,匹配则返回绑定该用户的token(这一般翻译为令牌,很直观的名字,一看就知道是有了这玩意,就会对你放行),而数据库中,在用户的token表中也同时插入了这个token相关的数据:这个token属于谁?这个token的有效期是多久?这个token当前登录的ip地址是?这个token对应的deviceid是?……
这样即便token被有心人截获,也不会造成太大的安全风险。因为没有用户名和密码,然后如果黑客通过这个token伪造用户请求,我们在服务器端接口被调用时就可以对发起请求的ip地址、user-agent之类的信息作比对,以防止伪造。再然后,如果token的有效期设得小,过一会儿它就过期了,除非黑客可以持续截获你的token,否则他只能干瞪眼。(插一句题外话:看到这里,是不是明白为什么不推荐在外面随便接入来历不明的wifi热点了?)
tips:token如何生成? 可以根据用户的信息及一些随机信息(比如时间戳)再通过hash编码(比如md5、sha1等)生成唯一的编码。
tips:token的安全级别,取决于你的实际需求,所以如果不是涉及财产安全的领域,并不建议太严格(比如用户走着走着,3G换了个基站,闪断了一下IP地址变了,尼玛token过期了,这就属于为了不必要的安全丢了用户体验,当然如果变换的IP地址跨省的话还是应该验证一下的,想想QQ有时候会让填验证码就明白了)。
tips:接口在返回信息时,可以包含本次请求的状态,比如成功调用,那么result['status']可能就是'success',而反之则是'error',而如果是'error',则result['errcode']中就可以包含错误的原因,比如errcode中是'invalid_token'就可以告诉APP这个token过期或无效,这时APP应弹出登录框或者用本地存储的用户名或密码再次请求token(用户选择“记住密码”,就应该在本地保存用户名和密码的摘要,方法见plus.storage的文档)。

再插点代码,基于plus.storage的用户信息类,注意:需要在plusReady之后再使用。

;function UserInfo(){  
};  

//清除登录信息  
UserInfo.clear = function(){  
    plus.storage.removeItem('username');  
    plus.storage.removeItem('password');  
    plus.storage.removeItem('token');  
}  

//检查是否包含自动登录的信息  
UserInfo.auto_login = function(){  
    var username = UserInfo.username();  
    var pwd = UserInfo.password();  
    if(!username || !pwd){  
        return false;  
    }  
    return true;  
}  

//检查是否已登录  
UserInfo.has_login = function(){  
    var username = UserInfo.username();  
    var pwd = UserInfo.password();  
    var token = UserInfo.token();  
    if(!username || !pwd || !token){  
        return false;  
    }  
    return true;  
};  

UserInfo.username = function(){  
    if(arguments.length == 0){  
        return plus.storage.getItem('username');          
    }  
    if(arguments[0] === ''){  
        plus.storage.removeItem('username');  
        return;  
    }  
    plus.storage.setItem('username', arguments[0]);  
};  

UserInfo.password = function(){  
    if(arguments.length == 0){  
        return plus.storage.getItem('password');          
    }  
    if(arguments[0] === ''){  
        plus.storage.removeItem('password');  
        return;  
    }  
    plus.storage.setItem('password', arguments[0]);  
};  

UserInfo.token = function(){  
    if(arguments.length == 0){  
        return plus.storage.getItem('token');         
    }  
    if(arguments[0] === ''){  
        plus.storage.removeItem('token');  
        return;  
    }  
    plus.storage.setItem('token', arguments[0]);  
};

这样当用户启动APP或使用了需要登录才能使用的功能时,就可以使用UserInfo.has_login()来判断是否已经登录,如果已登录,则使用UserInfo.token()来获取到token数据,作为参数调用远程的后端接口。

if(UserInfo.has_login()){  
    //打开需要展示给用户的页面,或者是调用远端接口  
}  
else{  
    wv_login.show('slide-in-up');   //从底部向上滑出登录页面  
}

在登录页面中,用户输入了用户名和密码后,并点击了”登录“按钮,我们下一步做什么?再插段代码(注意:此处使用的是我刚才代码中扩展的web_query函数,你也可以直接使用mui的ajax):


function get_pwd_hash(pwd){  
    var salt = 'hbuilder';  //此处的salt是为了避免黑客撞库,而在md5之前对原文做一定的变形,可以设为自己喜欢的,只要和服务器验证时的salt一致即可。  
    return md5(salt + pwd); //此处假设你已经引用了md5相关的库,比如github上的JavaScript-MD5  
}  

//这里假设你已经通过DOM操作获取到了用户名和密码,分别保存在username和password变量中。  
var username = xxx;  
var password = xxx;  
var pwd_hash = get_pwd_hash(password);  

var onSuccess = function(data){  
    UserInfo.username(username);  
    UserInfo.password(pwd_hash);  
    UserInfo.token(data.token); //把获取到的token保存到storage中  
    var wc = plus.webview.currentWebview();  
    wc.hide('slide-out-bottom');    //此处假设是隐藏登录页回到之前的页面,实际你也可以干点儿别的  
}  

var onError = function(errcode){  
    switch(errcode){  
    case 'INCORRECT_PASSWORD':  
        mui.toast('密码不正确');  
        break;  
    case 'USER_NOT_EXISTS':  
        mui.toast('用户尚未注册');  
        break;  
    }  
}  

mui.web_query('get_token', {username:username,password:pwd_hash}, onSuccess, onError, 3);

更安全一点,获取token通过SSL

刚才的方法,机智一点儿的读者大概会心存疑虑:那获取token时不还是得明文传输一次密码吗?
是的,你可以将这个获取token的地址,用SSL来保护(比如https://api.abc.com/get_token/),这样黑客即使截了包,一时半会儿也解不出什么信息。
SSL证书的获取渠道很多,我相信你总有办法查到,所以不废话了。不过话说namecheap上的SSL证书比godaddy的要便宜得多……(这是吐槽)
tips:前段时间OpenSSL漏洞让很多服务器遭殃,所以如果自己搭服务器,一定记得装补丁。
tips:可以把所有接口都弄成SSL的吗?可以。但会拖慢服务器,如果是配置并不自信的VPS,建议不折腾。

还要更更安全(这标题真省事)

还记得刚才APP向服务器请求token时,可以加入的用户信息吗?比如用户的设备deviceid。
如果我们在调用接口时,还附带一个当前时间戳参数timestamp,同时,用deviceid和这个时间戳再生成一个参数sign,比如 md5(deviceid timestamp token)这样的形式。而服务端首先验证一下参数中的时间戳与当前服务器时间是否一致(误差保持在合理范围内即可,比如5分钟),然后根据用户保存在服务器中的deviceid来对参数中的时间戳进行相同的变形,验证是否匹配,那便自然“更更安全”了。
tips:如果对整个调用请求中的参数进行排序,再以deviceid和timestamp加上排序后的参数来对整个调用生成1个sign,黑客即使截获sign,不同的时间点、参数请求所使用的sign也是不同的,难以伪造,自然会更安全。当然,写起来也更费事。
tips:明白了原理,整个验证过程是可以根据自己的需求改造的。

----------------------------------------------------------华丽的分割线-------------------------------------------------------

如果整篇文章中有不明白的名词,欢迎询问?不,不欢迎询问,因为搜索引擎都能找到。国内这方面的资料较少,但国外非常丰富,正所谓外事问google,内事问百度,程序员的第一个门槛就是要学会搜索。

什么?google上不去?开什么玩笑,你可以选择折腾点儿地天天找代理,也可以选择花一点点钱用红杏或类似的科学上网工具。
这里有福利→ 用我的邀请来注册红杏,购买30天以上的套餐,你我都可以各多10天

管理员,要求大大的加分!

继续阅读 »

最近发现群内大伙对用Hbuilder做的APP怎么做登录功能以及维护登录状态非常困惑,而我前一段时间正好稍微研究了一下,所以把我知道的告诉大家,节约大家查找资料的时间。

你是否真的需要登录功能?

把这个问题放在最前面并不是灌水,而是真的见过很多并不需要登录的APP去做了登录功能,或者是并不需要强制登录的APP把登录作为启动页。
用户对你的APP一无所知,你就要求对方注册并登录,除非APP本身已经很有名气或者是用户有强需求,否则正常人应该会直接把它删掉。
比较温和的方式是将一些并不需要登录,但可以给用户带来帮助的东西,第一时间展现给他们,让他们产生兴趣,再在合适的时机引导他们注册(比如使用需要使用更高级的功能,或用户需要收藏某个喜欢的信息时)。

登录和注册要足够简单

这是小小的手机端,用再好的输入法,打字也是不方便的,所以别把登录页设计得需要填很多东西。如果有可能的话,只填手机号,让用户收到短信验证码就完成注册是最好不过的了。想获得更多信息?想想大公司的APP是怎么做的,他们会告诉用户,现在的个人资料完善程度是30%,如果想获得更多积分,你需要填完。
tips:如果你想发布在Appstore并且同时包含注册功能,那么注册页面必须做一个用户许可协议的链接,否则有可能通不过审核。

实现登录后的session有几种方式?

APP当浏览器用,直接载入远程页面

这种情况是很多偷懒的程序员或者傻X的老板选择的方式,因为做起来实在太快。如果本身网站是响应式布局,那么很有可能不需要做什么更改,就只要在开发时打开首页就好了,这样Hybird的APP外壳就纯粹成为了一个浏览器。
但比起这样做带来的无数缺点来,开发速度快的优点几乎可以忽略不计。
首先,在网络环境不佳时,纯大白页,用户体验0;
然后,CSS和JS等资源不在本地,需要远程载入,如果使用了bootstrap之类的框架,那用户为了开一下APP而耗费的流量真是令人感动;
再然后,网页里常用的jquery,在手机的webview里速度并不理想,而如果是非ajax的网页那就更糟心了,每次操作都要跳转和页面渲染,要让人把它当成APP那实在是笑话。
再再然后,这样的所谓APP,要通过Appstore的审查,那是做梦的(除非审核员当天闹肚子严重,拿着纸巾奔向厕所前误点了通过……),苹果的要求是,这得是APP,而不能是某个网站做成APP的样子,那样的情况适合做Web APP。而据我所知,国内几个较大的Android市场,这样的APP也是无法通过审核的。

调用后端接口

这是个很好的时代,因为无论后端你是用Java、PHP,还是node.js,都可以通过xml、json来和APP通讯。遥想当年写服务端要自己写包结构,然后为了解决并发问题还折腾了半年IOCP模型,真心觉得现在太幸福了。
把刚才那个用APP当浏览器使的案例的所有缺点反过来看,就是这样做的优点,在优化完善的情况下体验接近原生,而且通讯流量极少,通过各种审核也是妥妥的。
tips:通过plus对象中的XMLHttpRequest来Get、Post远程的后端接口,或者使用Mui中封装好的AJAX相关函数

插一段代码,我把mui的ajax又做了进一步的封装,对超时进行了自动重试,而对invalid_token等情况也做相应处理:

;mui.web_query = function(func_url, params, onSuccess, onError, retry){  
    var onSuccess = arguments[2]?arguments[2]:function(){};  
    var onError = arguments[3]?arguments[3]:function(){};  
    var retry = arguments[4]?arguments[4]:3;  
    func_url = 'http://www.xxxxxx.com/ajax/?fn=' + func_url;  
    mui.ajax(func_url, {  
        data:params,  
        dataType:'json',  
        type:'post',  
        timeout:3000,  
        success:function(data){  
            if(data.err === 'ok'){  
                onSuccess(data);  
            }  
            else{  
                onError(data.code);  
            }  
        },  
        error:function(xhr,type,errorThrown){  
            retry--;  
            if(retry > 0) return mui.web_query(func_url, params, onSuccess, onError, retry);  
            onError('FAILED_NETWORK');  
        }  
    })  
};
var onError = function(errcode){  
    switch(errcode){  
    case 'FAILED_NETWORK':  
        mui.toast('网络不佳');  
        break;  
    case 'INVALID_TOKEN':  
        wv_login.show();  
        break;  
    default:  
        console.log(errcode);  
    }  
};  
var params = {per:10, pageno:coms_current_pageno};  
mui.web_query('get_com_list', params, onSuccess, onError, 3);

调用后端接口怎么样才安全?

在APP中保存登录数据,每次调用接口时传输

程序员总能给自己找到偷懒的方法,有的程序为了省事,会在用户登录后,直接把用户名和密码保存在本地,然后每次调用后端接口时作为参数传递。真省事儿啊!可这种方法简单就像拿着一袋子钱在路上边走边喊“快来抢我呀!快来抢我呀!”,一个小小的嗅探器就能把用户的密码拿到手,如果用户习惯在所有地方用一个密码,那么你闯大祸了,黑客通过撞库的方法能把用户的所有信息一锅端。

登录时请求一次token,之后用token调用接口

这是比较安全的方式,用户在登录时,APP调用获取token的接口(比如http://api.abc.com/get_token/),用post将用户名和密码的摘要传递给服务器,然后服务器比对数据库中的用户信息,匹配则返回绑定该用户的token(这一般翻译为令牌,很直观的名字,一看就知道是有了这玩意,就会对你放行),而数据库中,在用户的token表中也同时插入了这个token相关的数据:这个token属于谁?这个token的有效期是多久?这个token当前登录的ip地址是?这个token对应的deviceid是?……
这样即便token被有心人截获,也不会造成太大的安全风险。因为没有用户名和密码,然后如果黑客通过这个token伪造用户请求,我们在服务器端接口被调用时就可以对发起请求的ip地址、user-agent之类的信息作比对,以防止伪造。再然后,如果token的有效期设得小,过一会儿它就过期了,除非黑客可以持续截获你的token,否则他只能干瞪眼。(插一句题外话:看到这里,是不是明白为什么不推荐在外面随便接入来历不明的wifi热点了?)
tips:token如何生成? 可以根据用户的信息及一些随机信息(比如时间戳)再通过hash编码(比如md5、sha1等)生成唯一的编码。
tips:token的安全级别,取决于你的实际需求,所以如果不是涉及财产安全的领域,并不建议太严格(比如用户走着走着,3G换了个基站,闪断了一下IP地址变了,尼玛token过期了,这就属于为了不必要的安全丢了用户体验,当然如果变换的IP地址跨省的话还是应该验证一下的,想想QQ有时候会让填验证码就明白了)。
tips:接口在返回信息时,可以包含本次请求的状态,比如成功调用,那么result['status']可能就是'success',而反之则是'error',而如果是'error',则result['errcode']中就可以包含错误的原因,比如errcode中是'invalid_token'就可以告诉APP这个token过期或无效,这时APP应弹出登录框或者用本地存储的用户名或密码再次请求token(用户选择“记住密码”,就应该在本地保存用户名和密码的摘要,方法见plus.storage的文档)。

再插点代码,基于plus.storage的用户信息类,注意:需要在plusReady之后再使用。

;function UserInfo(){  
};  

//清除登录信息  
UserInfo.clear = function(){  
    plus.storage.removeItem('username');  
    plus.storage.removeItem('password');  
    plus.storage.removeItem('token');  
}  

//检查是否包含自动登录的信息  
UserInfo.auto_login = function(){  
    var username = UserInfo.username();  
    var pwd = UserInfo.password();  
    if(!username || !pwd){  
        return false;  
    }  
    return true;  
}  

//检查是否已登录  
UserInfo.has_login = function(){  
    var username = UserInfo.username();  
    var pwd = UserInfo.password();  
    var token = UserInfo.token();  
    if(!username || !pwd || !token){  
        return false;  
    }  
    return true;  
};  

UserInfo.username = function(){  
    if(arguments.length == 0){  
        return plus.storage.getItem('username');          
    }  
    if(arguments[0] === ''){  
        plus.storage.removeItem('username');  
        return;  
    }  
    plus.storage.setItem('username', arguments[0]);  
};  

UserInfo.password = function(){  
    if(arguments.length == 0){  
        return plus.storage.getItem('password');          
    }  
    if(arguments[0] === ''){  
        plus.storage.removeItem('password');  
        return;  
    }  
    plus.storage.setItem('password', arguments[0]);  
};  

UserInfo.token = function(){  
    if(arguments.length == 0){  
        return plus.storage.getItem('token');         
    }  
    if(arguments[0] === ''){  
        plus.storage.removeItem('token');  
        return;  
    }  
    plus.storage.setItem('token', arguments[0]);  
};

这样当用户启动APP或使用了需要登录才能使用的功能时,就可以使用UserInfo.has_login()来判断是否已经登录,如果已登录,则使用UserInfo.token()来获取到token数据,作为参数调用远程的后端接口。

if(UserInfo.has_login()){  
    //打开需要展示给用户的页面,或者是调用远端接口  
}  
else{  
    wv_login.show('slide-in-up');   //从底部向上滑出登录页面  
}

在登录页面中,用户输入了用户名和密码后,并点击了”登录“按钮,我们下一步做什么?再插段代码(注意:此处使用的是我刚才代码中扩展的web_query函数,你也可以直接使用mui的ajax):


function get_pwd_hash(pwd){  
    var salt = 'hbuilder';  //此处的salt是为了避免黑客撞库,而在md5之前对原文做一定的变形,可以设为自己喜欢的,只要和服务器验证时的salt一致即可。  
    return md5(salt + pwd); //此处假设你已经引用了md5相关的库,比如github上的JavaScript-MD5  
}  

//这里假设你已经通过DOM操作获取到了用户名和密码,分别保存在username和password变量中。  
var username = xxx;  
var password = xxx;  
var pwd_hash = get_pwd_hash(password);  

var onSuccess = function(data){  
    UserInfo.username(username);  
    UserInfo.password(pwd_hash);  
    UserInfo.token(data.token); //把获取到的token保存到storage中  
    var wc = plus.webview.currentWebview();  
    wc.hide('slide-out-bottom');    //此处假设是隐藏登录页回到之前的页面,实际你也可以干点儿别的  
}  

var onError = function(errcode){  
    switch(errcode){  
    case 'INCORRECT_PASSWORD':  
        mui.toast('密码不正确');  
        break;  
    case 'USER_NOT_EXISTS':  
        mui.toast('用户尚未注册');  
        break;  
    }  
}  

mui.web_query('get_token', {username:username,password:pwd_hash}, onSuccess, onError, 3);

更安全一点,获取token通过SSL

刚才的方法,机智一点儿的读者大概会心存疑虑:那获取token时不还是得明文传输一次密码吗?
是的,你可以将这个获取token的地址,用SSL来保护(比如https://api.abc.com/get_token/),这样黑客即使截了包,一时半会儿也解不出什么信息。
SSL证书的获取渠道很多,我相信你总有办法查到,所以不废话了。不过话说namecheap上的SSL证书比godaddy的要便宜得多……(这是吐槽)
tips:前段时间OpenSSL漏洞让很多服务器遭殃,所以如果自己搭服务器,一定记得装补丁。
tips:可以把所有接口都弄成SSL的吗?可以。但会拖慢服务器,如果是配置并不自信的VPS,建议不折腾。

还要更更安全(这标题真省事)

还记得刚才APP向服务器请求token时,可以加入的用户信息吗?比如用户的设备deviceid。
如果我们在调用接口时,还附带一个当前时间戳参数timestamp,同时,用deviceid和这个时间戳再生成一个参数sign,比如 md5(deviceid timestamp token)这样的形式。而服务端首先验证一下参数中的时间戳与当前服务器时间是否一致(误差保持在合理范围内即可,比如5分钟),然后根据用户保存在服务器中的deviceid来对参数中的时间戳进行相同的变形,验证是否匹配,那便自然“更更安全”了。
tips:如果对整个调用请求中的参数进行排序,再以deviceid和timestamp加上排序后的参数来对整个调用生成1个sign,黑客即使截获sign,不同的时间点、参数请求所使用的sign也是不同的,难以伪造,自然会更安全。当然,写起来也更费事。
tips:明白了原理,整个验证过程是可以根据自己的需求改造的。

----------------------------------------------------------华丽的分割线-------------------------------------------------------

如果整篇文章中有不明白的名词,欢迎询问?不,不欢迎询问,因为搜索引擎都能找到。国内这方面的资料较少,但国外非常丰富,正所谓外事问google,内事问百度,程序员的第一个门槛就是要学会搜索。

什么?google上不去?开什么玩笑,你可以选择折腾点儿地天天找代理,也可以选择花一点点钱用红杏或类似的科学上网工具。
这里有福利→ 用我的邀请来注册红杏,购买30天以上的套餐,你我都可以各多10天

管理员,要求大大的加分!

收起阅读 »

如何安装sexftp插件

FTP 插件 HBuilder sexftp

说明

有用户反馈目前集成的ftp插件不是很好用,而且在某些情况下目前集成的ftp插件会出现问题。有用户推荐了sexftp,但是由于sexftp插件依赖JDT,导致暂时无法集成该插件,如有用户需要安装sexftp可按以下步骤安装

安装JDT插件

安装步骤参见http://ask.dcloud.net.cn/article/146中JDT安装部分

下载sexftp

sexftp_2012.0.0.201202120942.jar
下载完毕后把这个jar包放到HBuilder根目录下的dropins目录下

让HBuilder加载sexftp

启动HBuilder,此时HBuilder会扫描dropins目录下的插件,扫描到后静默进行插件的配置(此过程非常快),然后重启HBuilder即可。(如果你是在HBuilder运行期间拷贝到dropins目录的,只需要重启即可)

sexftp简单说明

安装完毕后,在toolbar最右边会出现一个小鸟图标,点击即可使用sexftp

继续阅读 »

说明

有用户反馈目前集成的ftp插件不是很好用,而且在某些情况下目前集成的ftp插件会出现问题。有用户推荐了sexftp,但是由于sexftp插件依赖JDT,导致暂时无法集成该插件,如有用户需要安装sexftp可按以下步骤安装

安装JDT插件

安装步骤参见http://ask.dcloud.net.cn/article/146中JDT安装部分

下载sexftp

sexftp_2012.0.0.201202120942.jar
下载完毕后把这个jar包放到HBuilder根目录下的dropins目录下

让HBuilder加载sexftp

启动HBuilder,此时HBuilder会扫描dropins目录下的插件,扫描到后静默进行插件的配置(此过程非常快),然后重启HBuilder即可。(如果你是在HBuilder运行期间拷贝到dropins目录的,只需要重启即可)

sexftp简单说明

安装完毕后,在toolbar最右边会出现一个小鸟图标,点击即可使用sexftp

收起阅读 »

系统通知栏显示进度条Android插件

插件开发 通知栏显示进度

app新版更新时,在系统通知栏显示下载进度条,代码还有地方要改进。
使用:

        var url = "";  
    var options = {method:"GET"};  
    dtask = plus.downloader.createDownload( url, options );  
    plus.notification.setNotification("新版下载", "开始下载");//插件调用  
    dtask.addEventListener( "statechanged", function(task,status){  
        switch(task.state) {  
            case 1: // 开始  
                break;  
            case 2: //已连接到服务器  
                 break;  
            case 3: // 已接收到数据  
                var current = parseInt(100*task.downloadedSize/task.totalSize);  
                plus.notification.setProgress(current);//插件调用  
                 break;  
            case 4: // 下载完成           
                plus.notification.compProgressNotification("下载完成");//插件调用  
                plus.runtime.install(plus.io.convertLocalFileSystemURL(task.filename),//安装APP  
                               {force:true},function(){  

                },function(){  
                    mui.toast('安装失败');  
                });  
                break;  
        }  
    } );

添加权限:

"notification":{  
            "description": "通知栏"  
        }

plugin.js:

document.addEventListener("plusready",  function()  
{  
    var B = window.plus.bridge;  
    var notification =   
    {  
        "setProgress":function(incr){  
            return B.exec("notification", "setProgress", [incr]);  
        },  
        "setNotification":function(contentTitle, ticker){  
            return B.exec("notification", "setProgressNotification", [contentTitle, ticker]);  
        },  
        "compProgressNotification":function(contentTitle){  
            return B.exec("notification", "compProgressNotification", [contentTitle]);  
        }  
    };  
    window.plus.notification = notification;  
}, true);

Notify.java:

import io.dcloud.DHInterface.AbsMgr;  
import io.dcloud.DHInterface.IFeature;  
import io.dcloud.DHInterface.IWebview;  
import io.dcloud.util.JSUtil;  
import io.dcloud.adapter.util.AndroidResources;  
import android.R.integer;  
import android.R.string;  
import android.app.Activity;  
import android.app.Notification;  
import android.app.NotificationManager;  
import android.content.Context;  
import android.os.Bundle;  
import android.util.Log;  

public class Notify implements IFeature{  

    //IWebview webview;  
    //Context context;  
    NotificationManager manager;  
    Notification.Builder builder;  
    Activity activity;  

    @Override  
    public String execute(final IWebview pWebview, final String action, final String[] pArgs) {  
        activity = pWebview.getActivity();  
        manager = (NotificationManager)activity.getSystemService(Activity.NOTIFICATION_SERVICE);  
        builder = new Notification.Builder(activity);  
        builder.setWhen(System.currentTimeMillis())  
            .setPriority(Notification.PRIORITY_DEFAULT)  
            .setContentTitle("新版下载")  
            .setTicker("开始下载")  
            .setSmallIcon(R.drawable.icon)  
            .setVibrate(null);  

        activity.runOnUiThread(new Runnable() {  
            @Override  
            public void run() {  
                if("setNotification".equals(action))  
                {  
                    String title = pArgs[0];  
                    String ticker = pArgs[1];  
                    builder.setContentTitle(title).setTicker(ticker);  
                    manager.notify(1000, builder.build());  
                }  
                else if("setProgress".equals(action))  
                {  

                    int incr = Integer.parseInt(pArgs[0]);  
                    builder.setProgress(100, incr, false);  
                    manager.notify(1000, builder.build());  
                }  
                else if("compProgressNotification".equals(action))  
                {  
                    String title = pArgs[0];  
                    builder.setContentTitle(title).setProgress(0, 0, false);  
                    manager.notify(1000, builder.build());  
                }  
            }  
        });  
        return null;  
    }  

    @Override  
    public void init(AbsMgr arg0, String arg1) {  

    }  
    @Override  
    public void dispose(String arg0) {  
    }  
}
继续阅读 »

app新版更新时,在系统通知栏显示下载进度条,代码还有地方要改进。
使用:

        var url = "";  
    var options = {method:"GET"};  
    dtask = plus.downloader.createDownload( url, options );  
    plus.notification.setNotification("新版下载", "开始下载");//插件调用  
    dtask.addEventListener( "statechanged", function(task,status){  
        switch(task.state) {  
            case 1: // 开始  
                break;  
            case 2: //已连接到服务器  
                 break;  
            case 3: // 已接收到数据  
                var current = parseInt(100*task.downloadedSize/task.totalSize);  
                plus.notification.setProgress(current);//插件调用  
                 break;  
            case 4: // 下载完成           
                plus.notification.compProgressNotification("下载完成");//插件调用  
                plus.runtime.install(plus.io.convertLocalFileSystemURL(task.filename),//安装APP  
                               {force:true},function(){  

                },function(){  
                    mui.toast('安装失败');  
                });  
                break;  
        }  
    } );

添加权限:

"notification":{  
            "description": "通知栏"  
        }

plugin.js:

document.addEventListener("plusready",  function()  
{  
    var B = window.plus.bridge;  
    var notification =   
    {  
        "setProgress":function(incr){  
            return B.exec("notification", "setProgress", [incr]);  
        },  
        "setNotification":function(contentTitle, ticker){  
            return B.exec("notification", "setProgressNotification", [contentTitle, ticker]);  
        },  
        "compProgressNotification":function(contentTitle){  
            return B.exec("notification", "compProgressNotification", [contentTitle]);  
        }  
    };  
    window.plus.notification = notification;  
}, true);

Notify.java:

import io.dcloud.DHInterface.AbsMgr;  
import io.dcloud.DHInterface.IFeature;  
import io.dcloud.DHInterface.IWebview;  
import io.dcloud.util.JSUtil;  
import io.dcloud.adapter.util.AndroidResources;  
import android.R.integer;  
import android.R.string;  
import android.app.Activity;  
import android.app.Notification;  
import android.app.NotificationManager;  
import android.content.Context;  
import android.os.Bundle;  
import android.util.Log;  

public class Notify implements IFeature{  

    //IWebview webview;  
    //Context context;  
    NotificationManager manager;  
    Notification.Builder builder;  
    Activity activity;  

    @Override  
    public String execute(final IWebview pWebview, final String action, final String[] pArgs) {  
        activity = pWebview.getActivity();  
        manager = (NotificationManager)activity.getSystemService(Activity.NOTIFICATION_SERVICE);  
        builder = new Notification.Builder(activity);  
        builder.setWhen(System.currentTimeMillis())  
            .setPriority(Notification.PRIORITY_DEFAULT)  
            .setContentTitle("新版下载")  
            .setTicker("开始下载")  
            .setSmallIcon(R.drawable.icon)  
            .setVibrate(null);  

        activity.runOnUiThread(new Runnable() {  
            @Override  
            public void run() {  
                if("setNotification".equals(action))  
                {  
                    String title = pArgs[0];  
                    String ticker = pArgs[1];  
                    builder.setContentTitle(title).setTicker(ticker);  
                    manager.notify(1000, builder.build());  
                }  
                else if("setProgress".equals(action))  
                {  

                    int incr = Integer.parseInt(pArgs[0]);  
                    builder.setProgress(100, incr, false);  
                    manager.notify(1000, builder.build());  
                }  
                else if("compProgressNotification".equals(action))  
                {  
                    String title = pArgs[0];  
                    builder.setContentTitle(title).setProgress(0, 0, false);  
                    manager.notify(1000, builder.build());  
                }  
            }  
        });  
        return null;  
    }  

    @Override  
    public void init(AbsMgr arg0, String arg1) {  

    }  
    @Override  
    public void dispose(String arg0) {  
    }  
}
收起阅读 »