Project address (http://sack.doraemoney.com)
June 14, with two other colleagues It’s been discussed that it can’t be like this anymore in the past few months. It seems that the product managers and coders of every company are rivals, and I haven’t escaped from this vicious circle. I became more and more disgusted with the product. I accidentally looked at my Git Commits List. It was so long. There were a lot of them every day. Then I wanted to see what I did. Suddenly, I realized that it was a cycle. Ah, it basically looks like this:
for var keepGoing = true; keepGoing {
// 4B
}
No, we have to do it ourselves The whole one, but not during working hours, because this is a matter that only we are involved in, and we don’t want others to give us pointers, so I decided to spare a few hours every day and plan within a week. A whole new thing came out, um, yes, App, it took ten days for the three of us in the end.
This is still an app that lends money to people in need. There is no risk control model, but we have a complete debt collection model and authenticity model. We only do one thing to let the person who borrows you Believe that you are repaying on time faster, so we chose the data that can be quickly obtained through an App, such as address book, call history, geographic location, mobile phone model, etc.
Then we started planning, simple design, interface definition and data structure definition. This took one day, and we spent three days to develop the entire system, that is, all I have all the functions, and then it took two days to connect all the functional interfaces, and finally four days of testing, debugging, and bug fixes. Ok, a brand new App came out.
The technology used is:
- Java
- Ionic
- Cordova
- Some necessary plug-ins
Java
The reason for choosing Java is very simple and pure. Our core business system is Java. For faster development, we still use it directly Java, so that many interface codes can be directly copied and changed and then used, which saves us a lot of development time.
Ionic
No need to think about this, a simple artifact in App development. With this thing, even if I don’t know anything about App development, I can only use my own knowledge. The front-end technology realizes a complete App.
Cordova
This provides support for our App to be compatible with various platforms such as iOA/Andoird.
How did I do it
About local data storage
Because the amount of data is very small, I directly use LocalStorage
, I wrote a Angular Module
that binds data between AngularJS
and LocalStorage
. The code is as follows:
javascript/* *
* Local storage
*/
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
( 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();
}
});< br />
return storage;
}
]);
It’s easy to use:
javascript$storage.token ='TOKEN_STRING' ; // This will store a key-value pair with `key` as `storage-token` and `value` as `TOKEN_STRING` in localStorage. This is a one-way storage process, that is, we manually modify `localStorage. The value in `is useless. After `100ms`, it will be overwritten by the value of `$storage.token`, which is the time to update the storage.
Data request
Because our interface is not the default request method of AngularJS
, the data structure is similar to form submission, so I also modified The $http
in Angular
, the conversion object is a string of x-www-form-urlencoded serialization:
javascript/**
* Configuration
*/
app.config([
'$ionicConfigProvider',
'$logProvider',
'$httpProvider',
function(
$ionicConfigProvider,
$logProvider,
$httpProvider
) {
// .. other code
// open log
$logProvider.debugEnabled(true);
/**
* The server interface requires the Content-Type header to be sent at the same time when the request is initiated, and its value must be: application/x -www-form-urlencoded
* Optionally add character encoding, here I set the encoding to utf-8 by default
*
* @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';
/**
* The request only accepts the server Return JSON data from the end
* @ty pe {string}
*/
$httpProvider.defaults.headers.post['Accept'] ='application/json';
/**
* AngularJS submits the default data structure in json format, but our NiuBilitity server can’t parse JSON data, so
* We submit via x-www-form-urlencoded, here we will encapsulate the data as foo=bar&bar=other way
* @type {*[]}
*/
$httpProvider.defaults.transformRequest = [function(data) {
/**< br /> * Convert the object to a string of x-www-form-urlencoded serial code
* @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; isubValue = value[i];
fullSubName = name +'[' + i +']';
innerObj = {};
in nerObj[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 data structure
< p>Our data structure is as follows:
Requ est
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": ""
}
]
}
}
Description
In the above data structure, the request is well understood, and the response is The json
structure has only three fields, apiVersion
represents the interface version number of the current request, data
is the data object, and error
is Error object, under normal circumstances, a error
has only two values of code
and message
, but in some cases it may be necessary to provide some additional The error information is put into the error.errors
array.
The App front end judges as follows:
- When
error
isnull
, it means the request is successful. At this time, get data fromdata
; - When
error
is notnull
, it means the request has failed.error
takes the error information, and ignores thedata
completely. The method I take is to discard it directly (in fact, the front and back ends have been agreed, so there is noerror
When it is notnull
, there is still data indata
.
About $http
I did not directly expose the interface's url
address, $http
request, etc. to Controller
, but did a Layer encapsulation, I call it as sack
(that is, the name of the 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,
u til
){
var HTTPUnknownError = {code: -1, message:'An unknown error occurred'};
var HTTPAuthFaildError = {code: -1, message:'Authorization failed'};
var APIPanicError = {code: -1, message:'An unknown error occurred on the server side'};
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) {
// If the incoming parameter has a _host header< br /> 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:'Prompt',
subTitle:'Your current login status has expired, please log in again. '
}).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);< br /> 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.pr omise;
};
return {
get: get,
post: post,
promise: promise,
postJson: postJson,
promiseJson: promiseJson,
_auth: _auth,
HTTPAuthFaildError: HTTPAuthFaildError
};
}
]);
< p>In this way, the most important method is to use one method: sack.promiseJson
, this method is to send a request to the server with json
data, and then return a promise
Of.
The data structure of the above API_URI_MAP
is similar to the following:
javascriptapp.constant('API_URI_MAP', {
user: {< br /> sign: {
needAuth: false,
uri:'sack/user/sign.json',
methods: [
'post'
],
params: {
mobile:'string', // mobile phone number
captcha:'string' // verification code
}
},
unsign : {
needAuth: true,
uri:'sack/user/unsign.json',
methods: [
'post'
],
params: {
token:'string'
}
},
//...
}
//...
});
Then, to be more specific, the sack.promiseJson
method is not used directly in the Controller
, but the packaged service is used For example, the following service:
javascriptapp.factory('UserService', function($rootScope, $q, $storage, API_CACHE_TIME, sack) {
var sign = function(data) {
return sack.promiseJson('user.sign', data);
};
return {
sign: sign< br /> }
});
The advantage of this is that I can directly initiate a request like the following:
UserService.sign({mobile:'xxxxxxxxxxx' ,captcha:'000000')).then(function(res){
// authorization succeeded
}, function(err){
// authorization failed
});
But
Ok, but again, after the App is finished, our lovely leaders feel that this is okay, and then they will start to play their various NB Fortunately, we did not use working hours from the beginning, which gave us reason to reject the leadership’s guidance. However, the company said, if you don’t accept the guidance, then you won’t let it go. Well, then don’t go. It seems to irritate our leaders, so we just started recruiting for the app without ventilating with us. I immediately wanted to ask:
Our strategy is not to say not to do the app? ? How can I see that the App is easier than it is now and I started to do it again
Then I thought of another possibility
- We uploaded the App,
li>
- Another leader recruited some newcomers and made an app
- If the app is okay, copy our functions directly and let us go offline
- If the App can’t, then we’re wasting time, taking our offline, and then...
< li>Then the leader can take credit again
Anyway, It seems that it has nothing to do with me, unless the app is not operating well.