It took ten days to make an app, named Yima loan, thinking about the loan of one sack and one sack, but

Time:2021-10-21

It took ten days to make an app, named Yima loan, thinking about the loan of one sack and one sack, but

Project address(http://sack.doraemoney.com

On June 14, I discussed with two other colleagues that we should not be like this in recent months. It seems that every company’s product manager and code farmers are dead enemies. I didn’t escape this strange circle. Every day, in the “careful carving” of the products, I became more and more disgusted with the products. Inadvertently, I looked at my git commitments list. It’s so long. There are a lot of them every day, Then I wanted to see what I had done. Suddenly, I found that this was a cycle, basically as follows:

for var keepGoing = true; keepGoing  {
    //4b medium
}

No, we have to do it ourselves, but we can’t do it during working hours, because it’s a thing that only we participate in, and we don’t want others to point out to us, so we decided to spare a few hours every day and plan a new thing in a week. Well, yes, app, Finally, it took the three of us ten days.

This is also an app that lends money to people in need. There is no risk control model, but we have a perfect debt collection model and authenticity model. We only do one thing to make the people who lend money to you believe that you are paying back on time. Therefore, we chose address book, call record, geographical location, mobile phone model and other data that can be quickly obtained through an app.

Then we started planning, simple design, interface definition and data structure definition, which took one day. We spent three days to develop the whole system, that is, all functions were available, then spent two days to connect all functional interfaces in series, and finally spent four days testing, debugging and bug repair, OK, A new app came out.

The technology uses:

  • Java
  • Ionic
  • Cordova
  • Some necessary plug-ins

Java

The reason for choosing Java is simple and pure. Our core business system is Java. In order to develop faster, we still use Java directly, so that many interface codes can be copied and modified directly, which saves us a lot of development time.

Ionic

This is a simple artifact in app development. With this thing, even if I know nothing about app development, I can only use my own front-end technology to realize a perfect app.

Cordova

This supports the compatibility of our app to various platforms, such as IOA / Andoird.

How did I do it

About local data storage

Because the amount of data is small, it is used directlyLocalStorage, I wrote one myselfAngularJSAndLocalStorageData boundAngular Module, 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 < 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;
  }
]);

Easy to use:

javascript$storage.token = 'TOKEN_ STRING'; //  This will store a 'key' as' storage token 'and' value 'as' token' in localstorage_ The key value pair of string is a one-way storage process, that is, it is useless for us to manually modify the value in 'localstorage'. After '100ms', it will be overwritten by the value of ` $storage. Token'. This is the time to update the storage.

Data request

Because our interface is notAngularJSThe data structure is similar to form submission, so I also modified itAngularMedium$http, convert the object to the string of x-www-form-urlencoded sequence generation:

javascript/**
 *Disposition
 */
app.config([
  '$ionicConfigProvider',
  '$logProvider',
  '$httpProvider',
  function(
      $ionicConfigProvider,
      $logProvider,
      $httpProvider
  ) {
    //.. Other codes
    //Open log
    $logProvider.debugEnabled(true);

    /**
     *The server interface side requires that the content type header information be sent at the same time when the request is initiated, and its value must be: application / x-www-form-urlencoded
     *Optional 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 JSON data returned by the server
     * @type {string}
     */
    $httpProvider.defaults.headers.post['Accept'] = 'application/json';

    /**
     *The data structure submitted by angularjs by default is in JSON format, but the server side of Niubillity cannot parse JSON data, so
     *We submit it in the form of x-www-form-urlencoded. Here, the data will be encapsulated in the form of foo = Bar & Bar = other
     * @type {*[]}
     */
    $httpProvider.defaults.transformRequest = [function(data) {
      /**
       *Convert the object to a string of the x-www-form-urlencoded sequence
       * @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 data structure

Our data structure is as follows:

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" : ""
      }
    ]
  }
}

explain

In the above data structures, the request is well understood and the response is simplejsonThe structure has only three fields,apiVersionIndicates the currently requested interface version number,dataIs the data object,errorIs the wrong object. Generally, aerroronlycodeAndmessageTwo values, but in some cases, you may need to provide some additional error information, so they are put inerror.errorsIn this array.

The app front end is judged as follows:

  1. WhenerrorbynullWhen, it indicates that the request is successful. At this time, fromdataFetching data from;
  2. WhenerrorNot fornullWhen, it indicates that the request failed. At this time, fromerrorTake the error message from, regardless of itdata, the way I take is to abandon it directly (in fact, the front and back ends have been agreed, so it doesn’t existerrorNot fornullWhen,dataThere is also data in.

about$http

I didn’t put the interface directlyurlAddress$httpRequest, etc. exposed toControllerInstead, I made a layer of packaging, which I call assack(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,
      util
  ){
    Var httpunknownerror = {Code: - 1, message: 'unknown error'};
    Var httpauthfaildererror = {Code: - 1, message: 'authorization failed'};
    Var apipanicerror = {Code: - 1, message: 'unknown error on server'};
    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 passed in parameter has_ Host header
      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 login 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);
      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
    };
  }
]);

In this way, one method is mainly used:sack.promiseJson, this method is based onjsonData sends a request to the server and then returns apromiseof

aboveAPI_URI_MAPThe data structure of is similar to the following:

javascriptapp.constant('API_URI_MAP', {
  user : {
    sign : {
      needAuth: false,
      uri : 'sack/user/sign.json',
      methods: [
        'post'
      ],
      params: {
        Mobile: 'string', // mobile number
        Captcha: 'string' // verification code
      }
    },
    unsign: {
      needAuth: true,
      uri: 'sack/user/unsign.json',
      methods: [
        'post'
      ],
      params: {
        token: 'string'
      }
    },
    //...
  }
  //...
});

Then, more specifically, inControllerIt is not directly used insack.promiseJsonInstead, this method uses encapsulated services, such as the following services:

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

  return {
    sign: sign
  }
});

The advantage is that I can directly initiate a request using the following method:

UserService.sign({mobile:'xxxxxxxxxxx',captcha:'000000'}).then(function(res){
  //Authorization succeeded
}, function(err){
  //Authorization failed
});

however

Well, here we go again. After the app is finished, our lovely leaders feel that this is OK, and then they will start to give full play to their various NB guidance. Fortunately, we didn’t use working hours from the beginning, which makes us have reason to refuse the guidance of leaders. However, the company said that if we don’t accept guidance, we won’t let them go. Well, we won’t go, This seems to annoy our leaders. Therefore, we didn’t communicate with us directly and began to recruit people to join the app. I wanted to ask in an instant:

Isn’t our strategy to say no app? How can we see that the app is simpler than it is now and start doing it again

Then I thought of another possibility

  1. We put the app on,
  2. Another leader recruited some new people and made an app
  3. If the app is OK, copy our functions directly, and then let us go offline
  4. Then the leaders can take credit again
  5. If the app can’t, we’re wasting time, putting our offline, and then

Anyway, it doesn’t seem to have anything to do with me, unless the app doesn’t operate well.