I spent ten days to do an app, named a microverarial loan, thinking of a sack of a sack, but …

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; i subValue = 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:

  1. When error is null, it means the request is successful. At this time, get data from data;
  2. When error is not null, it means the request has failed. error takes the error information, and ignores the data completely. The method I take is to discard it directly (in fact, the front and back ends have been agreed, so there is no error When it is not null, there is still data in data.

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

  1. We uploaded the App,

    li>

  2. Another leader recruited some newcomers and made an app
  3. If the app is okay, copy our functions directly and let us go offline
  4. < li>Then the leader can take credit again

  5. If the App can’t, then we’re wasting time, taking our offline, and then...

Anyway, It seems that it has nothing to do with me, unless the app is not operating well.

Leave a Comment

Your email address will not be published.