资讯专栏INFORMATION COLUMN

花了十天时间做了一个App,取名一麻贷,想着一麻袋一麻袋的放款,但是……

Miracle / 1957人阅读

摘要:这样里面最主要是使用一个方法,这个方法是以数据向服务器发送请求,然后返回一个的。

项目地址(http://sack.doraemoney.com)

6月14号,和另外两个同事商量着不能再像最近这几个月这样了,似乎每一个公司的产品经理与码农们都是死对头,我也没有逃出这个怪圈,每天在对产品的“精雕细琢”中,让我对产品越发的反感,不经意间,看了看自己的 Git Commits List,好长啊,每天都有好多,然后就想着看看自己的干了些什么,突然之间,发现这就是一个循环啊,基本上是下面这样的:

for var keepGoing = true; keepGoing  {
    // 4B中
}

不行啊,我们得自己整一个,但是不能在上班时间整,因为这是一个只有我们参与的事情,而且也不希望他人对我们的指指点点,所以,决定每天的空余时间抽出几个小时,计划着一个星期之内整一个新的东西出来,恩,是的,App,最后还是花了我们3个人十天的时间。

这还是一个借款给有需要的人的App,没有风控模型,但是我们有完善的催债模型和真实性模型,我们只做一件事情,让借款给你的人更快的相信你在按时还款,所以,我们选择了通讯录、通话记录、地理位置、手机型号等这些通过一个App能快速获取到的数据。

然后我们开始了规划,简单的设计,接口的定义以及数据结构的定义,这花了一天的时间,我们按着花了三天时间把整个系统开发完了,也就是所有的功能都有了,接着花了两天时间把所有的功能接口串连上,最后再连了四天的时间测试、调试与Bug修复,Ok,一个全新的App就这么出来了。

技术使用的是:

Java

Ionic

Cordova

一些必要的插件

Java

选择Java的原因很简单,也很纯粹,我们的核心业务系统就是Java的,为了能更快速的开发,我们还是直接使用Java,这样很多接口的代码可以直接复制过来改改就能使用,这为我们节省了很多开发的时间。

Ionic

这个不用想,简单的App开发中的神器,有了这个东西,即使我对App开发一无所知,我也能仅使用我自己会的前端技术实现一个完善的App。

Cordova

这为我们的App兼容到各种平台 iOA/Andoird等提供支持。

我是怎么做的 关于本地的数据存储

因为数据量很少,所以直接使用了 LocalStorage,我自己写了一个 AngularJSLocalStorage 的数据绑定的 Angular Module,代码如下:

javascript/**
 * 本地存储
 */
app.factory("$storage", [
  "$rootScope",
  "$window",
  function(
      $rootScope,
      $window
  ){

    var webStorage = $window["localStorage"] || (console.warn("This browser does not support Web Storage!"), {}),
        storage = {
          $default: function(items) {
            for (var k in items) {
              angular.isDefined(storage[k]) || (storage[k] = items[k]);
            }

            return storage;
          },
          $reset: function(items) {
            for (var k in storage) {
              "$" === k[0] || delete storage[k];
            }

            return storage.$default(items);
          }
        },
        _laststorage,
        _debounce;

    for (var i = 0, k; i < webStorage.length; i++) {


      (k = webStorage.key(i)) && "storage-" === k.slice(0, 8) && (storage[k.slice(8)] = angular.fromJson(webStorage.getItem(k)));
    }

    _laststorage = angular.copy(storage);

    $rootScope.$watch(function() {
      _debounce || (_debounce = setTimeout(function() {
        _debounce = null;

        if (!angular.equals(storage, _laststorage)) {
          angular.forEach(storage, function(v, k) {
            angular.isDefined(v) && "$" !== k[0] && webStorage.setItem("storage-" + k, angular.toJson(v));

            delete _laststorage[k];
          });

          for (var k in _laststorage) {
            webStorage.removeItem("storage-" + k);
          }

          _laststorage = angular.copy(storage);
        }
      }, 100));
    });


    "localStorage" === "localStorage" && $window.addEventListener && $window.addEventListener("storage", function(event) {
      if ("storage-" === event.key.slice(0, 10)) {
        event.newValue ? storage[event.key.slice(10)] = angular.fromJson(event.newValue) : delete storage[event.key.slice(10)];

        _laststorage = angular.copy(storage);

        $rootScope.$apply();
      }
    });

    return storage;
  }
]);

使用起来很简单:

javascript$storage.token = "TOKEN_STRING"; // 这就会在localStorage 中存储一个 `key` 为 `storage-token` 而 `value` 为 `TOKEN_STRING` 的键值对,这是一个单向存储的过程,也就是我们再手工修改 `localStorage` 里面的值是没有用的,`100ms` 之后就会被 `$storage.token` 的值覆盖,这是一个更新存储的时间。
数据请求

因为我们这边的接口走的不是 AngularJS 的默认请求方式,数据结构为类似表单提交,所以,我还修改了 Angular 中的 $http,转换对象为 x-www-form-urlencoded 序列代的字符串:

javascript/**
 * 配置
 */
app.config([
  "$ionicConfigProvider",
  "$logProvider",
  "$httpProvider",
  function(
      $ionicConfigProvider,
      $logProvider,
      $httpProvider
  ) {
    // .. 其它代码
    // 开启日志
    $logProvider.debugEnabled(true);

    /**
     * 服务器接口端要求在发起请求时,同时发送 Content-Type 头信息,且其值必须为: application/x-www-form-urlencoded
     * 可选添加字符编码,在此处我默认将编码设置为 utf-8
     *
     * @type {string}
     */

    $httpProvider.defaults.headers.post["Content-Type"] = "application/x-www-form-urlencoded;charset=utf-8";
    $httpProvider.defaults.headers.put["Content-Type"] = "application/x-www-form-urlencoded;charset=utf-8";

    /**
     * 请求只接受服务器端返回 JSON 数据
     * @type {string}
     */
    $httpProvider.defaults.headers.post["Accept"] = "application/json";

    /**
     * AngularJS 对默认提交的数据结构为 json 格式的,但是我们NiuBilitity的服务器端不能解析 JSON 数据,所以
     * 我们经 x-www-form-urlencoded 的方式提交,此处将对数据进行封装为 foo=bar&bar=other 的方式
     * @type {*[]}
     */
    $httpProvider.defaults.transformRequest = [function(data) {
      /**
       * 转换对象为 x-www-form-urlencoded 序列代的字符串
       * @param {Object} obj
       * @return {String}
       */
      var param = function(obj) {
        var query = "";
        var name, value, fullSubName, subName, subValue, innerObj, i;

        for (name in obj) {
          value = obj[name];

          if (value instanceof Array) {
            for (i = 0; i < value.length; ++i) {
              subValue = value[i];
              fullSubName = name + "[" + i + "]";
              innerObj = {};
              innerObj[fullSubName] = subValue;
              query += param(innerObj) + "&";
            }
          } else if (value instanceof Object) {
            for (subName in value) {
              subValue = value[subName];
              fullSubName = name + "[" + subName + "]";
              innerObj = {};
              innerObj[fullSubName] = subValue;
              query += param(innerObj) + "&";
            }
          } else if (value !== undefined && value !== null) {
            query += encodeURIComponent(name) + "="
                + encodeURIComponent(value) + "&";
          }
        }

        return query.length ? query.substr(0, query.length - 1) : query;
      };

      return angular.isObject(data) && String(data) !== "[object File]"
          ? param(data)
          : data;
    }];

  }
]);
JSON 请求数据结构

我们的数据结构是下面这样的:

Request
json{
  "apiVersion" : "0.0.1",
  "token" : "TOKEN_STRING",
  "requestId" : "ID_STRING",
  "data" : {
    // Data goes here
  }
}
Response
json{
  "apiVersion" : "0.0.1",
  "data" : {},
  "error" : {
    "code" : ERROR_CODE_NUMBER,
    "message" : "Error Message Here",
    "errors" : [
      {
        "code" : 0,
        "message" : "",
        "location" : ""
      }
    ]
  }
}
说明

在上面的这些数据结构中,请求的很好理解,响应的 json 结构只有三个字段, apiVersion 表示了当前请求的接口版本号, data 就是数据对象, error 则是错误对象,一般情况下,一个 error 只有 codemessage 两个值,但是有一些情况下可能会需要提供一些额外的错误信息,那么都放入了 error.errors 这个数组中。

App前端是下面这样的判断的:

errornull 时,表示请求成功,此时从 data 中取数据;

error 不为 null 时,表示请求失败,此时从 error 中取错误信息,而完全不管 data ,我采取的方式是直接抛弃(其实前后端已经约定了,所以不存在 error 不为 null 时,data 中还有数据的情况出现。

关于 $http

我没有直接将接口的 url 地址、$http 请求等暴露给 Controller,而是做了一层封装,我叫作为 sack(也就是 App 的名称):

javascriptapp.factory("sack", [
  "$http",
  "$q",
  "$log",
  "$location",
  "$ionicPopup",
  "$storage",
  "API_VERSION",
  "API_PROTOCOL",
  "API_HOSTNAME",
  "API_URI_MAP",
  "util",
  function(
      $http,
      $q,
      $log,
      $location,
      $ionicPopup,
      $storage,
      API_VERSION,
      API_PROTOCOL,
      API_HOSTNAME,
      API_URI_MAP,
      util
  ){
    var HTTPUnknownError = {code: -1, message: "出现未知错误"};
    var HTTPAuthFaildError = {code: -1, message: "授权失败"};
    var APIPanicError = {code: -1, message: "服务器端出现未知错误"};
    var _host = API_PROTOCOL + "://" + API_HOSTNAME + "/",
        _map = API_URI_MAP,
        _apiVersion = API_VERSION,
        _token = (function(){return $storage.token;}()) ;

    setInterval(function(){
      _token = (function(){return $storage.token;}());
      //$log.info("Got Token: " + _token);
    }, 1000);

    var appendTransform = function(defaultFunc, transFunc) {
      // We can"t guarantee that the default transformation is an array
      defaultFunc = angular.isArray(defaultFunc) ? defaultFunc : [defaultFunc];

      // Append the new transformation to the defaults
      return defaultFunc.concat(transFunc);
    };

    var _prepareRequestData = function(originData) {
      originData.token = _token;
      originData.apiVersion = _apiVersion;
      originData.requestId = util.getRandomUniqueRequestId();
      return originData;
    };

    var _prepareRequestJson = function(originData) {
      return angular.toJson({
        apiVersion: _apiVersion,
        token: _token,
        requestId: util.getRandomUniqueRequestId(),
        data: originData
      });
    };

    var _getUriObject = function(uon) {
      // 若传入的参数带有 _host 头
      if((typeof uon === "string" && (uon.indexOf(_host) == 0) ) || uon === "") {
        return {
          uri: uon.replace(_host, ""),
          methods: ["post"]
        };
      }

      if(typeof _map === "undefined") {
        return {
          uri: "",
          methods: ["post"]
        };
      }

      var _uon = uon.split("."),
          _ns,
          _n;

      if(_uon.length == 1) {
        return {
          uri: "",
          methods: ["post"]
        };
      }
      _ns = _uon[0];
      _n = _uon[1];

      _mod = _map[_ns];

      if(typeof _mod === "undefined") {
        return {
          uri: "",
          methods: ["post"]
        };
      }

      _uriObject = _mod[_n];

      if(typeof _uriObject === "undefined") {
        return {
          uri: "",
          methods: ["post"]
        };
      }

      return _uriObject;
    };

    var _getUri = function(uon) {
      return _getUriObject(uon).uri;
    };

    var _getUrl = function(uon) {
      return _host + _getUri(uon);
    };

    var _auth = function(uon) {
      var _uo = _getUriObject(uon),
          _authed = false;
      $log.log("Check Auth of : " + uon);
      $log.log("Is this api need auth: " + angular.toJson(_uo.needAuth));
      $log.log("Is check passed: " + angular.toJson(!(!_token && _uo.needAuth)));
      $log.log("Token is: " + _token);
      if(!_token && _uo.needAuth) {

        $ionicPopup.alert({
          title: "提示",
          subTitle: "您当前的登录状态已失效,请重新登录。"
        }).then(function(){
          $location.path("/sign");
        });

        $location.path("/sign");
      } else {
        _authed = true;
      }
      return _authed;
    };

    var get = function(uon) {
      return $http.get(_getUrl(uon));
    };

    var post = function(uon, data, headers) {
      var _url = _getUrl(uon),
          _data = _prepareRequestData(data);
      $log.info("========> POST START [ " + uon + " ] ========>");
      $log.log("REQUEST URL  : " + _url);
      $log.log("REQUEST DATA : " + angular.toJson(_data));

      return $http.post(_url, _data, {
        transformResponse: appendTransform($http.defaults.transformResponse, function(value) {
          $log.log("RECEIVED JSON : " + angular.toJson(value));
          if(typeof value.ex != "undefined") {
            return {
              error: APIPanicError
            };
          }
          return value;
        })
      });
    };

    var promise = function(uon, data, headers) {
      var defer = $q.defer();

      if(!_auth(uon)) {
        defer.reject(HTTPAuthFaildError);
        return defer.promise;
      }

      post(uon, data, headers).success(function(res){
        if(res.error) {
          defer.reject(res.error);
        } else {
          defer.resolve(res.data);
        }
      }).error(function(res){
        defer.reject(HTTPUnknownError);
      });
      return defer.promise;
    };

    var postJson = function(uon, data, headers) {
      var _url = _getUrl(uon),
          _json = _prepareRequestJson(data);
      $log.info("========> POST START [ " + uon + " ] ========>");
      $log.log("REQUEST URL  : " + _url);
      $log.log("REQUEST JSON : " + _json);
      return $http.post(_url, _json, {
        transformResponse: appendTransform($http.defaults.transformResponse, function(value) {
          $log.log("RECEIVED JSON : " + angular.toJson(value));
          if(typeof value.ex != "undefined") {
            return {
              error: APIPanicError
            };
          }
          return value;
        })
      });
    };

    var promiseJson = function(uon, data, headers) {
      var defer = $q.defer();

      if(!_auth(uon)) {
        defer.reject(HTTPAuthFaildError);
        return defer.promise;
      }

      postJson(uon, data, headers).success(function(res){
        if(res.error) {
          defer.reject(res.error);
        } else {
          defer.resolve(res.data);
        }
      }).error(function(res){
        defer.reject(HTTPUnknownError);
      });
      return defer.promise;
    };

    return {
      get: get,
      post: post,
      promise: promise,

      postJson: postJson,
      promiseJson: promiseJson,
      _auth: _auth,
      HTTPAuthFaildError: HTTPAuthFaildError
    };
  }
]);

这样里面最主要是使用一个方法: sack.promiseJson,这个方法是以 json 数据向服务器发送请求,然后返回一个 promise 的。

上面的 API_URI_MAP 的数据结构类似于下面这样的:

javascriptapp.constant("API_URI_MAP", {
  user : {
    sign : {
      needAuth: false,
      uri : "sack/user/sign.json",
      methods: [
        "post"
      ],
      params: {
        mobile: "string", // 手机号码
        captcha: "string" // 验证码
      }
    },
    unsign: {
      needAuth: true,
      uri: "sack/user/unsign.json",
      methods: [
        "post"
      ],
      params: {
        token: "string"
      }
    },
    //...
  }
  //...
});

然后,更具体的,在 Controller 中也不直接使用 sack.promiseJson 这个方法,而是使用封装好的服务进行,比如下面这个服务:

javascriptapp.factory("UserService", function($rootScope, $q, $storage, API_CACHE_TIME, sack) {
  var sign = function(data) {
    return sack.promiseJson("user.sign", data);
  };

  return {
    sign: sign
  }
});

这样的好处是,我可以直接使用类似下面这样发起请求:

UserService.sign({mobile:"xxxxxxxxxxx",captcha:"000000"}).then(function(res){
  // 授权成功
}, function(err){
  // 授权失败
});
但是

好吧,又来但是了,App做完了之后,我们可爱的领导们感觉这个还可以,然后就又要开始发挥他们的各种NB的指导了,还好从一开始我们就没有使用上班时间,这使得我们有理由拒绝领导的指导,但是,公司却说了,不接受指导那就不让上,好吧,那就不上呗,这似乎惹怒了我们的领导们,所以,就直接没有跟我们通气的开始招兵买马要上App了,我瞬间就想问:

  

我们的战略不是说不做App么?现在怎么看到App比现在的简单就又开始做了

然后我又想到一种可能

我们把App上了,

另一个领导带招一些新人把也做了一个App

如果App还可以的话,把我们的功能直接复制过去,然后让我们的下线

然后领导又可以邀功了

如果App不可以的话,那我们是在浪费时间,把我们的下线,然后……

反正,似乎都跟我没半毛钱关系了,除非这个App运营的不好。

文章版权归作者所有,未经允许请勿转载,若此文章存在违规行为,您可以联系管理员删除。

转载请注明本文地址:https://www.ucloud.cn/yun/64372.html

相关文章

  • 瀑布流—云南●十八怪

    摘要:瀑布流瀑布流式布局网站布局方式的一种视觉上表现为参差不齐的多栏布局。 瀑布流:瀑布流式布局(网站布局方式的一种)视觉上表现为参差不齐的多栏布局。应用领域为:电商导购、兴趣图分享等页面;其特点为琳琅满目、唯美、操作简单等特点;布局优点为有效的降低页面复杂度、节省空间;交互方式更符合直觉;更高的参与度,以上两点带来的交互便捷性,可以使用户侧重于内容而不是操作上。关于瀑布流的具体操作以云南●...

    cjie 评论0 收藏0
  • 忘了再看设计模式-结构型

    摘要:推文设计模式适配器模式不兼容结构的协调适配器模式四外观模式老仓库的角落,我们数着一麻袋的爱跟快乐初恋的颜色麦芽糖通过外观角色来交互,降低子系统与客户端的耦合度。 代理模式 我决定插手你的人生,当你的时尚顾问 《阳光宅男》 通过代理对象进行交互(或占位),强调访问控制(也能增加额外功能,比如:日志);与被代理对象具有相同接口; showImg(https://segmentfault.c...

    URLOS 评论0 收藏0
  • 国庆怎么把主机带回家-怎么把台式电脑带回家?

    摘要:怎么才能把台式电脑带回家可以寄吗最好是自己带着,麻烦点。如果非要寄就寄顺丰,以下几个建议供参考主机是否能找到原包装,如果找到原包装,里面的泡沫塑料是根据机型定制的,效果最好。怎么才能把台式电脑带回家?可以寄吗?最好是自己带着,麻烦点。如果非要寄就寄顺丰,以下几个建议供参考:主机;1.是否能找到原包装,如果找到原包装,里面的泡沫塑料是根据机型定制的,效果最好。2.把机箱打开,显卡拆下单独打包,...

    Rango 评论0 收藏0
  • 关于js类型检测

    摘要:原始类型又有种引用类型有而检测这些类型的变量有种办法,,。而关于引用类型,还可以尝试下操作符。总而言之,如果指定则保存的实际上就是的值,是一个基本类型。 javascript的变量类型分为原始类型和引用类型。 原始类型又有5种: number string boolean null undefined 引用类型有: Function Array Date Object R...

    Cheriselalala 评论0 收藏0
  • Weex系列(4) —— 老生常谈三端统

    摘要:刚看到这仨页面的时候,我就想着可以用路由,做成三端统一。样式这部分真的三端基本是高度统一的,部分微调一下就可以了,也正是这样,我们后续才能迅速解决和。终于不是谈谈三端统一了,也是真的体验了一次,虽然最后有点出入,但是下次基本是没问题了。 目录 Weex系列(序) —— 总要知道原生的一点东东(iOS) Weex系列(序) —— 总要知道原生的一点东东(Android) Weex系列(...

    wzyplus 评论0 收藏0

发表评论

0条评论

最新活动
阅读需要支付1元查看
<