Hot update is a very convenient solution. Never use an open source solution when dealing with large numbers of users and deep customization. Generally the third party of this scheme, the server bandwidth is small, or not flexible enough to meet their own ideas. It is recommended that you implement the corresponding hot update scheme. Only a small amount of code is required to support it. A flexible hot update scheme is recommended below. Including client transformation, interface design, interface development, at the same time is open source! Free to modify.

Experience address: Demo User name and password are both admin

Preparation and implementation of basic data

First of all, if an APP is to support hot updates, it needs to determine whether the bundle file needs to be updated before opening the APP (or any other RN page). Here is the node where we implement hot update. Start downloading files as soon as you need hot updates, and the interface to determine that is the core of this article. The download logic for android and ios is briefly posted here.

Several important pieces of information about the client need to be attached to the head before requesting it. Client version, unique clientid :clientid, client type platform, and client brand.

Ios download example

- (void)doCheckUpdate
{
  self.upView.viewButtonStart.hidden = YES;
  if ([XCUploadManager isFileExist:[XCUploadManager bundlePathUrl].path])
  {// The downloaded Jsbundle is already in the sandbox. The sandbox file is preferred
    self.oldSign = [FileHash md5HashOfFileAtPath:[XCUploadManager bundlePathUrl].path];
  }else
  {// The bundleMD5 in the packet calculated by the real machine is changed
    // NSString *ipPath = [[NSBundle mainBundle] pathForResource:@"main" ofType:@"jsbundle"];
    // self.oldSign = [FileHash md5HashOfFileAtPath:ipPath];
    self.oldSign = projectBundleMd5;
  }
  
  AFHTTPSessionManager *_sharedClient = [[AFHTTPSessionManager alloc] initWithBaseURL:[NSURL URLWithString:@"http://test.com"]];
  
  [self initAFNetClient:_sharedClient];

  [_sharedClient GET:@"api/check" parameters:nil progress:nil success:^(NSURLSessionDataTask * __unused task, id JSON) {
    NSDictionary *dic = [JSON valueForKeyPath:@"data"];
    BOOL isNeedLoadBundle = YES;
    if ([dic isKindOfClass:[NSDictionary class]])
    {
      self.updateSign = [dic stringForKey:@"sign"];
      self.downLoadUrl = [dic stringForKey:@"downloadUrl"];

      if(self.updateSign.length && self.oldSign.length && (! [self.updateSign isEqualToString:self.oldSign])) {// The bundle needs to be updated
        self.upView.viewUpdate.hidden = NO;
        [self updateBundleNow];
        isNeedLoadBundle = NO;
      }else
      {
        // There is no need to update the bundle file to handle the skip button display logic[self.upView showSkipButtonOrNot]; }}if (isNeedLoadBundle) {
      [self loadBundle];
    }
  } failure:^(NSURLSessionDataTask *__unused task, NSError *error) {
    [self loadBundle];
  }];
}
Copy the code

Android download example

private void requestData(a) {

        subscribe = DalingNetwork
                .getDalingApi()
                .getBundleVersion()
                .subscribeOn(Schedulers.io())
                .observeOn(AndroidSchedulers.mainThread())
                .subscribe(new BaseSubscriber<BundleVersionResponse>() {
                    @Override
                    public void onError(Throwable e) {
                        startMainActivity();
                        e.printStackTrace();
                    }

                    @Override
                    public void onNext(final BundleVersionResponse response) {
                        isJSNeedUpdate = false;
                        if (response.status == 0) {
                            if(response.data ! =null) {
                                if (MainApplication.getApplication().getBundleMD5().equalsIgnoreCase(response.data.sign)) {
                                    // The same as the local version, go directly to the home page
                                    isJSNeedUpdate = false;
                                    tv_skip.setVisibility(View.VISIBLE);
                                    startMainActivity();
                                } else {
                                    // Download the upgrade
                                    isJSNeedUpdate = true; downloadSign = response.data.sign; downloadUrl = response.data.downloadUrl; downLoad(response.data.downloadUrl, response.data.sign); }}}else{ startMainActivity(); }}}); }Copy the code

System Design Scheme

Let’s start by looking at how we designed the client fetching update logic.

  1. When the client requests the version number and platform, it contains two important information.
  2. The interface takes the request and queries the corresponding local cache, if not the database.
  3. Screen three segments of data from the query result: whitelist, gray scale, and full, judging from left to right.
  4. Returns the result of the query.

Database design

The above design is the basic logic, now we continue to refine the logic. Some other solutions are designed to support better performance and distribution.

It is perfectly possible to design according to logic 😁

Database design

We chose MySQL as the base database to store the data after each release.

fe_bundle

  1. The data required by the table itself. Id, status, operator, release description.
  2. Field used to determine whether to update. Version number, platform, client ID, bundle signature, address, and zip package address.
  3. Returned as additional data on the interface. Tag ID and tag content.

The FE_labels table is stored as additional data. If you want to return complex operations on the interface, such as showing and hiding an interface, whether to load a bundle, whether to force updates, etc., you can set them here. The table itself can only be added and enabled, but cannot be deleted to prevent misoperations.

Reducing the field length can optimize the query performance of the database. Nicknames, for example, should be no longer than 10 characters. Adding indexes to large volumes of data can also improve database performance. You can also reduce the query time by querying only the required fields.

Publish and subscribe design

The publish-subscribe model is used primarily to synchronize the results of each publication. This decouples publishing from local cache updates, and multiple server support does not result in resource contention or late updates.

The redis publish-subscribe model is used here. Other options are MQ message queues and so on. Proactively update the local cache as messages are received.

Local cache design

The key to fast interface response is in the local cache. After all, a database can be very difficult to support with a large number of users accessing it. The local cache is used to reduce the number of queries in the database, so that no matter how many users are faced, only the server thread of the interface is actually working. We also take advantage of the high concurrency of NodeJS, so as long as the machine holds up, our service won’t get stuck or die. The service can support almost as many concurrent operations as the machine can support.

  1. The advantage of local caching is that the query speed is fast and there is no network request consumption.
  2. In the case of no cache, go to the database to read data and cache local.
  3. Double caching prevents multiple requests from crashing the database concurrently.
  4. Dual caching only deals with a large number of requests in special cases, such as local cache failures, server restarts, and so on. Publish subscriptions normally solve the problem of local caching.

Foreground interface development

React+Mobx+ElementUI is used for the foreground interface. The main reason for choosing this stack is convenience. After all, developers who know RN will most likely be able to learn React quickly.

  1. React, as a basic framework, takes advantage of the advantages of the framework for rapid development.
  2. Mobx as state management, this project only uses the global management of user information.
  3. ElementUI has a couple of nice UIs, but there’s a lot of design effort left over from off-the-shelf UI development.
Login screen

Login only requires a simple background + login information input box. If you are interested, you can optimize it to make the interface look better.

Bundle Management Interface

List management only needs to display key information. List several parameters of the query to facilitate the query. Click delete to pop up whether to delete the prompt, click publish also need to pop up prompt.

Label management

Tags are all about adding and using. Define the field and value types to be added at add time. It only needs one addition. Client compatible 🈚️ value case compatibility is good.

Back-end interface development

The interface is divided into two parts, one is to deal with the background edit list interface, the other part is to deal with a large number of users query interface.

Edit query interface

Interface development is actually very simple, if you are not familiar with the database can look at the corresponding documentation or tutorial. Sequelize simple tutorial

Interface development consists of three steps:

  1. Gets the parameters of the request. It is better to add default value handling and exception verification here.
  2. Query the database. Handle both normal return and catch error cases.
  3. Returns the concrete content according to the agreed specification.

If status=0 is returned, the query succeeds and all data is placed in the data field. If status=1 is returned, the query fails and the error message is placed in the MSG field.

Query interface

The query interface is divided into two threads. One thread is the network request thread, which manages the incoming network requests and filters the returns. Another thread manages local updates and triggers corresponding data updates via Redis subscription mode.

The cache update

When Redis notifies that it needs to update, it will bring the version number and platform database. These two fields are also cached as keys in our local cache.

searchFromService

Network request

The network request logic is complex. Data needs to be retrieved from the cache first. At the same time, the database may be triggered to retrieve data and process it into the cache.

  1. Filter for the presence of suitable whitelist data.
  2. Filter whether there is suitable gray data
  3. Check whether the corresponding full data exists.

Once all of this is done, you can determine if there is an appropriate bundle for this request. If not, the client doesn’t need to update. Users can open and browse normally.

There may be letters in clientid when judging grayscale. In this case, you need to convert the letters into data. The transformation here is a simple alphanumeric correspondence, represented by a percentage forward. The top 60 percent will be bigger than the bottom 40 percent. If this is required, the data can be converted from base 26 to base 10. That’s the real percentage.

Source code address

Foreground page address: foreground code

Background interface address: background code

Database address: database code