微信小程序 api 缓存方案

Posted 在厕所喝茶

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了微信小程序 api 缓存方案相关的知识,希望对你有一定的参考价值。

微信小程序 api 缓存方案

背景

为了应对用户流量大,减轻服务器的压力,减少网络请求次数,加快数据的显示,以及提高用户体验。我们现在需要把一些公共请求进行本地缓存,并且提供不同的更新策略给开发者选择。

前言

与 web 端不同的是,微信小程序并没有cookielocalStoragesessionStorageindexedDBWeb SQL(已废弃),service worker这些花里胡哨的东西。只有一套已经封装好的接口。所以就需要基于微信小程序提供的这套数据缓存接口来实现我们的 api 缓存方案。

缓存方案(更新策略)

这里我们提供三种缓存方案。灵感来源于一次service workerpwa应用实践。方案如下:

  • 第一种方案:有缓存数据并且没有过期的情况下,直接返回缓存数据,不进行网络请求获取数据;没有缓存数据或者是有缓存数据,但是缓存数据的有效时间过期的情况下,发送网络请求,等待数据返回,然后存储到本地,并且设置过期时间,最后在把数据返回给前端;数据存储到本地的时候,大家最好设置一个过期时间,否则这个缓存数据就永远无法得到更新,因为有数据并且没有过期的情况下是直接返回缓存数据的,不会进行网络请求获取数据,除非用户手动清除缓存。这个方案最大的优点就是减少了网络请求的次数,加快数据的响应速度(有数据的情况下不用等待网络请求的时间),缺点就是数据不能得到及时的更新。在应对一些高并发,用户流量大的情况下,这个方案会比较好。因为这种情况下,后台会有属于自己的缓存方案,比如 CDN 缓存,nginx 缓存,这些缓存都会有一个缓存时间的,我们只要在本地的缓存数据中设置一个大于后台缓存时间的有效时间即可。这样既可以保证数据能够被更新,又能减少网络请求的次数。比方说后台的缓存时间是 5 分钟,前端可以设置过期时间是 6 分钟,前端不设置 5 分钟是因为后台缓存的时间并不一定是一个准确数,你在第五分钟获取的数据可能还是旧的数据,所以时间上要略大一点。
  • 第二种方案:有缓存数据并且没有过期的情况下,直接返回缓存数据,同时继续发送网络请求,请求的数据回来之后对缓存数据进行更新;没有缓存数据或者有缓存数据,但是缓存数据已经过期的情况下,发送网络请求,然后存储到本地,并且设置过期时间,最后再把数据返回给前端;这个方案每次都是需要发送请求的,然后在更新缓存数据的。这里我建议大家也设置一个过期时间,这样可以防止数据太旧了,比方说我现在请求了一次网络数据,然后退出了小程序,五天后再打开小程序,如果缓存数据没有设置过期的情况下,那么我们看见的将会是五天前的数据。这个方案的优点就是数据能够及时更新(每次看见的数据都是上一次请求回来的数据),加快数据的响应时间(有数据的情况下不用等待网络请求的时间),缺点就是不能减少网络请求的的次数。这个方案适用于一些要求数据及时性比较高或者网络情况比较差的情景,比如网络比较慢的时候,一个请求需要 5 秒时间,在有缓存数据的情况下,会直接返回缓存数据,由于网络请求是异步的,5 秒后请求回来之后会自动更新缓存数据,这并不影响后面的代码运行。
  • 第三种方案:当没有网络或者请求报错,并且有缓存数据的情况下,就返回缓存数据,没有缓存数据就返回空。这种方案每次请求都会对缓存数据进行更新。优点就是提高系统的健壮性和可用性,不至于没有网络或者后台报错的时候什么数据都没有,前端页面一片空白,这样就可以大大的提高用户的体验。缺点就是你需要区分哪些数据是成功请求返回来的,哪些数据是失败请求返回来的,因为失败的请求还需要给个提示用户那里出错了。
  • 第四种方案:不进行缓存,并不是所有的接口都需要缓存,有些需要保证前端和后台的数据保持一致性的,不然可能会导致数据库的修改丢失。比方说我修改用户名,张三改成李四,但是由于缓存的原因,我再次修改的时候显示的还是张三,然后直接点击保存,数据库保存的又变回张三了,张三改成李四这个修改的操作就会丢失了。

缓存那些接口

缓存方案有了,那么我们需要缓存那些接口呢。一般来说,我们的接口会分为三种类型:public,my,protect。

  • public:一般是公共接口,用于公共页面的,就是那些游客可以看见的就是公共页面,通常来说是列表接口来的。请求方法是get,后台会有缓存。
  • protect:一般来说是需要用户登录之后才能调用的接口,接口需要根据用户的信息才能出数据的,还有就是这些接口是需要权限的。通常就是用于只有登录会后才能看见的一些页面。请求方法是post,因为需要带上 token。protect 类型的接口后台也会进行缓存
  • my:一般用于个人空间那些模块,就是需要编辑,删除或者新增的模块,这些操作都需要数据及时更新,后台不会进行缓存,直接走数据库。

上面有三种类型的接口,我们只缓存public类型的接口;my类型的接口由于要求数据的及时性比较高,所以不做缓存;至于protect类型的接口也不做缓存,因为接口数据是跟用户信息(token)有关的,不同的用户调用同样的接口,返回的数据是不一样的,并且缓存数据的时候我们都是根据接口 url+请求参数作为key值。当然,如果想要缓存protect类型的接口也是可以的,我们给接口加上对应的标识(userId 等,最好不要用 token 这些,因为会经常变化,导致命中不了缓存),标识这个接口对应的是哪个用户就可以了,这就要求我们实现的缓存工具类需要有一个可以自定义缓存的key值的功能,让开发者自己去定义key值。

Storage 缓存工具类

基于上面的描述,我们首先需要封装一个操作缓存数据的工具类,需要有如下核心功能:

1、获取数据

get(key:string,def?:any)

根据传入的key值获取数据,def是没有数据的时候默认返回来的数据。获取数据的时候还需要判断这个数据有没有过期,过期就需要删除数据,并且返回null或者def

2、设置数据

set(key:string, val:any, options?:Object)

按照key-val的形式存储数据,options是其他一些配置参数,比如设置有效期时长

3、删除最近最少使用的项的缓存对象

我们需要考虑的一个问题就是,缓存的数据量可能会比较多,但是存储的空间不够(微信小程序最大可以缓存 10MB 数据,这种情况一般不会发生)或者只想缓存 10 条请求,超出的部分需要首先淘汰掉一部分已经存储起来的数据,然后再把超出的部分存储进去。淘汰算法使用最近最少使用的。思路如下:

  • 新建一个数组,用来保存缓存数据的key
  • get获取数据的时候,将数组中对应的key值放到最后一位,这样可以保证最近使用过的在最后一位,最近最少使用的就放在首位。
  • set设置数据的时候,首先检查数组中是否已经包含了对应的key值,包含就删除。最后把key值放置到数组中最后一位。

实现:

class Storage {
  // 设置值
  set(key, val, options) {
    //   将用户传入的配置和默认配置进行合并
    const config = Object.assign({}, defaultConfig, options || {});
    // 判断是否超出储存长度,超出则删除最近最少使用的项的缓存对象
    isOverLimitSize(this.maxSize);
    // 将需要存储的数据转化为我们需要的格式
    const data = initData(val, config);
    // 存储数据
    wx.setStorageSync(key, data);
    // 保存key值到数组中
    addCacheKey(key);
    return val;
  }

  // 获取值
  get(key, def) {
    //   根据key值获取value值
    const val = wx.getStorageSync(key);
    if (isNotDefine(val)) {
      // 如果是空值则返回默认值
      return def;
    }
    // 检查格式是否正确,因为有些数据不是通过这个工具类存储的
    if (isFlag(val)) {
      // 检查数据有效期
      const isExpire = checkExpire(val);
      if (!isExpire) {
        // 没过期,修改key值的存储位置
        addCacheKey(key);
        return val.data;
      }
      // 过期,则删除缓存数据
      this.remove(key);

      return def;
    }
    // 修改key值的位置
    addCacheKey(key);
    return val;
  }

  // 根据key移除缓存
  remove(key) {
    //   将key值从数组中删除
    deleteCacheKey(key);
    return wx.removeStorageSync(key);
  }

  // ...  其他功能
}

ApiCache 类实现

配置

我们需要有一些配置给用户选择,比如选择什么缓存方案,那些接口需要缓存等

  • cache:false 就是禁止进行缓存。1 就是上述的第一种缓存方案。2 就是上述第二种缓存方案。默认是 1。
  • cacheKey:函数,缓存数据的key值,该函数会有 2 个参数,一个是请求配置,一个是缓存配置。默认返回接口 url+请求参数。
  • shouldCache:函数,判断接口是否需要进行缓存,返回 true 就是缓存,false 就是不缓存。该函数会有 2 个参数,一个是请求配置,一个是缓存配置。默认缓存get请求。

封装 request 请求

这里使用的请求库是我自己封装的一个基于Promise的 Http 请求库,参考了axios源码的设计思想。有兴趣的同学可以点击这里。这里封装 request 请求就是基于这个库,然后加一层缓存下去的。

实现:

let defaultCacheConfig = {
  // cache: false-不缓存;1-本地有数据就使用本地数据,不请求;2-本地有数据就返回本地数据,然后请求数据回来之后更新缓存
  cache: 1,
  cacheKey(requestConfig, cacheOptions) {
    return requestConfig.url+queryString(requestConfig.data);
  },
  shouldCache(requestConfig, cacheOptions) {
    return requestConfig.method === 'get';
  }
};

function ApiCache() {}

ApiCache.prototype.request = function (method, url, config, cacheOptions) {
  // 合并请求配置
  const requestConfig = Object.assign({}, config || {}, {
    method,
    url,
  });
  //   合并缓存配置
  const cacheConfig = Object.assign({}, defaultCacheConfig, cacheOptions);
  if (
    cacheConfig.cache &&
    cacheConfig.shouldCache(requestConfig, cacheOptions)
  ) {
    // 开启缓存
    const cacheKey = cacheConfig.cacheKey(requestConfig, cacheOptions);
    const cacheValue = storage.get(cacheKey);
    if (cacheValue) {
      //   有缓存值得时候
      if (cacheConfig.cache === 2) {
        //   方案2需要发送请求更新缓存数据
        request
          .request(requestConfig)
          .then((res) => {
            storage.set(cacheKey, res, cacheConfig);
          })
          .catch(() => {});
      }

      return Promise.resolve(cacheValue);
    } else {
      //   不存在缓存值得时候,通过请求获取数据
      return request
        .request(requestConfig)
        .then((res) => {
          storage.set(cacheKey, res, cacheConfig);
          return Promise.resolve(res);
        })
        .catch((error) => {
          return Promise.reject(error);
        });
    }
  } else {
    //   没有开始缓存的
    return request.request(requestConfig);
  }
};

总结

到此,微信小程序 api 缓存方案已经实现完毕了。实现起来并不是很难,难点就在于缓存方案的设计上面,怎么做到快速响应数据,怎么更新数据,那些接口需要缓存,那些接口不需要缓存等等这些都是需要我们去考虑的。当然这套方案不仅可以用于微信小程序中,也可以用在web端的,实现起来大同小异。如果有兴趣想了解一下这方面的知识,可以关注我的开源项目微信小程序组件库,里面包含了一些扩展功能,用来解决一些微信小程序的业务场景。

欢迎扫码体验
二维码

以上是关于微信小程序 api 缓存方案的主要内容,如果未能解决你的问题,请参考以下文章

微信小程序入门-指南针

微信小程序代码片段分享

【微信小程序】本地缓存

微信小程序入门(七):缓存数据-单条数据

微信小程序海报 uniapp

微信小程序海报 uniapp