摘要:這樣里面最主要是使用一個方法,這個方法是以數據向服務器發送請求,然后返回一個的。
項目地址(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,我自己寫了一個 AngularJS 與 LocalStorage 的數據綁定的 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 請求數據結構
我們的數據結構是下面這樣的:
Requestjson{ "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 只有 code 與 message 兩個值,但是有一些情況下可能會需要提供一些額外的錯誤信息,那么都放入了 error.errors 這個數組中。
App前端是下面這樣的判斷的:
當 error 為 null 時,表示請求成功,此時從 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運營的不好。
文章版權歸作者所有,未經允許請勿轉載,若此文章存在違規行為,您可以聯系管理員刪除。
轉載請注明本文地址:http://specialneedsforspecialkids.com/yun/64372.html
摘要:瀑布流瀑布流式布局網站布局方式的一種視覺上表現為參差不齊的多欄布局。 瀑布流:瀑布流式布局(網站布局方式的一種)視覺上表現為參差不齊的多欄布局。應用領域為:電商導購、興趣圖分享等頁面;其特點為琳瑯滿目、唯美、操作簡單等特點;布局優點為有效的降低頁面復雜度、節省空間;交互方式更符合直覺;更高的參與度,以上兩點帶來的交互便捷性,可以使用戶側重于內容而不是操作上。關于瀑布流的具體操作以云南●...
摘要:推文設計模式適配器模式不兼容結構的協調適配器模式四外觀模式老倉庫的角落,我們數著一麻袋的愛跟快樂初戀的顏色麥芽糖通過外觀角色來交互,降低子系統與客戶端的耦合度。 代理模式 我決定插手你的人生,當你的時尚顧問 《陽光宅男》 通過代理對象進行交互(或占位),強調訪問控制(也能增加額外功能,比如:日志);與被代理對象具有相同接口; showImg(https://segmentfault.c...
摘要:怎么才能把臺式電腦帶回家可以寄嗎最好是自己帶著,麻煩點。如果非要寄就寄順豐,以下幾個建議供參考主機是否能找到原包裝,如果找到原包裝,里面的泡沫塑料是根據機型定制的,效果最好。怎么才能把臺式電腦帶回家?可以寄嗎?最好是自己帶著,麻煩點。如果非要寄就寄順豐,以下幾個建議供參考:主機;1.是否能找到原包裝,如果找到原包裝,里面的泡沫塑料是根據機型定制的,效果最好。2.把機箱打開,顯卡拆下單獨打包,...
摘要:原始類型又有種引用類型有而檢測這些類型的變量有種辦法,,。而關于引用類型,還可以嘗試下操作符。總而言之,如果指定則保存的實際上就是的值,是一個基本類型。 javascript的變量類型分為原始類型和引用類型。 原始類型又有5種: number string boolean null undefined 引用類型有: Function Array Date Object R...
摘要:剛看到這仨頁面的時候,我就想著可以用路由,做成三端統一。樣式這部分真的三端基本是高度統一的,部分微調一下就可以了,也正是這樣,我們后續才能迅速解決和。終于不是談談三端統一了,也是真的體驗了一次,雖然最后有點出入,但是下次基本是沒問題了。 目錄 Weex系列(序) —— 總要知道原生的一點東東(iOS) Weex系列(序) —— 總要知道原生的一點東東(Android) Weex系列(...
閱讀 2926·2021-11-24 09:39
閱讀 3609·2021-11-22 13:54
閱讀 3414·2021-11-16 11:45
閱讀 2439·2021-09-09 09:33
閱讀 3199·2019-08-30 15:55
閱讀 1296·2019-08-29 15:40
閱讀 924·2019-08-29 15:19
閱讀 3400·2019-08-29 15:14