前端数据字典的最优方案探索

举报
字母哥哥 发表于 2022/04/14 00:41:52 2022/04/14
【摘要】 文章目录 前置说明一、什么是数据字典二、为什么要在后端维护字典表三、在后端维护的字典表前端常规使用方法3.1. 在下拉选择器(select)中使用。3.2. 将后端传过来的key转成value显示...

前置说明

前端时间帮一个公司项目做性能调优,发现该公司的项目数据字典的使用方式比较陈旧,数据字典的使用不当对数据库造成的压力非常大。主要在以下几个场景下存在问题:

  1. 前端页面每一个下拉框,就发送一ajax请求进行加载数据字典。如果一个页面有十几个下拉框,并且用户并发量大的情况下,对于后端服务还是有一定的压力的。
  2. 前端数据表格中的字段字典value,需要翻译成字典label。有些旧的系统使用SQL将数据表关联字典表,将数据字典的value和label随着数据记录一并返回。一个表有5个字段需要数据字典翻译,就要关联字典表5次。这种SQL无论无论后期维护,还是数据库性能方面都是灾难。

可不可以一次性加载?
那么就涉及到一个问题?数据字典一个一个ajax请求的加载方式,基本上我们可以排除了。那我们就想到另外一个方案,在前端登陆之后将系统所有的数据字典缓存到前端。这种方式稍微好一点,但是这真的是一个超级大的系统,数据字典有上万条数据,如果一次性加载到前端,用户体验可想而知。显然这样也是我们不能忍受的。

解决方案:按需加载
今天在浏览技术文章的时候,看到这样一篇文章《前端数据字典的最优方案探索》。它采用的是按需加载的方式。就是第一次使用一个数据字典就发送请求加载这个数据字典,并将该数据字典放入浏览器本地缓存中。第二次再用到这个字段的时候,就不再发送网络请求,而是从浏览器本地获取数据字典的值。

这种方案能够解决一个问题:一个浏览器用户所需的数据字典只加载一次,而且是按需加载,不需要的不加载。这种分次加载的方式,用户体验也没问题。

如何处理高并发的问题
前端解决的是一个用户的数据字典反复加载的问题,处理完前端我们来看后端。通过配合后端redis缓存设计,数据字典可以将redis缓存与数据库保持一致性,进而多用户高并发查询数据字典先落到redis缓存中,redis缓存中不存在才去查询数据库,从而避免对数据库造成的压力问题。

下面的内容为转载内容:《前端数据字典的最优方案探索》 :https://juejin.cn/post/6949080259438313509

一、什么是数据字典

字典:字典是一个键值对,主要的特征是一一对应,字典中的 key 是不能重复且无序的,value 可以重复。 key 用于在前后端的传输或者在代码中做逻辑判断,value 用于向用户展示。

二、为什么要在后端维护字典表

其实前端如果非要自己维护也是可以的,这样前端开发人员在代码上确实可以省掉很多事情,我曾经也这样考虑过,但是这样有几个缺陷确实让人难以接受,所以我还是屈服了。

  1. 字典表必须永远和后端保持一致,维护难度太大,稍不注意前后端就可能对不上。
  2. 虽然字典不常修改但是不代表永远不会修改,如果一有改动,前端就要改代码,打包,部署。。。
  3. 如果出现1中的问题,前端可能会背锅(并且还甩不掉)。

需要知道的是,不管字典在哪里维护,如果代码中有逻辑判断,字典修改后都是需要改代码的,前后端的都是。

三、在后端维护的字典表前端常规使用方法

好了,现在字典全给后端维护了,以后前端都不会背字典的锅了。现在我们来看看怎么使用字典,通常情况下会有这几种使用方式。

3.1. 在下拉选择器(select)中使用。

对于第一种使用方式,我们一般会写成一个公共组件,如:DictSelect ,它会接受一个参数(dictType),在加载组件的时候通过 dictType 获取当前字典的所有选项。我们知道,不管是vue还是react,一个组件在多次使用时都是不同的实例,也就是说我每次使用DictSelect组件时都会向后端发起一个请求。想象一下,如果在一个页面中,有两个性别选择器,当我们使用DictSelect时,一进入页面就会向后端发起两次获取性别字典的请求,他们的结果还都是一样的。假如页面上还有个弹窗,弹窗里也有个性别选择器,弹窗打开时又会发起一次请求。假如我现在又要跳转到另外一个页面。。。

其实这种情况你要是不去在意它它就不是一个问题,它的问题也只是多发了几次请求而已,不仅是这个字典选择器有这个问题,其他的异步选择器也是这样。

3.2. 将后端传过来的key转成value显示(一般在表格中)。

现在来看第二种使用方式,这种方式一般在表格里面,当我获取表格数据的时候,假如里面有一项数据代表性别,后端给我的是一个 key 值,我在展示的时候需要将这个 key 转成 value。这个问题我最先想到的解决办法是vue的过滤器,但是过滤器又不能支持异步,如果非要使用过滤器的话(函数也一样),我必须在每次使用前先初始化字典数据,但是这样我就没法完全将字典相关代码从业务中脱离出来。(我花了很长时间寻找过滤器的异步方式,现在已经放弃了,根本没有。)
假如我使用一个组件。但是这样的话就和DictSelect一样了,假如表格页大小为10,那它就会同时发起10条相同的请求,这是真的接受不了。

3.3.逻辑判断

第三种情况,没有合适办法,只能在代码中写死。

四、解决方案

我封装了一个专门用来请求字典的函数, 他有两个逻辑:

每次发起请求时将当前请求的 Promise 存起来,请求结束后将Promise改为null,每次发起请求时先判断Promise是否存在,如果有就直接返回。这样可以处理那些同一时刻发起多个请求的问题。
发起请求之前先判断 Loca Storage 中是否有缓存,如果有直接取出来,没有的话再发起请求,成功之后会先缓存一份。

/**
 * 对get请求进行包装
 * 提供数据缓存和防止同时发起相同的请求
 * 相同的路径就可以理解为相同的请求
 */
import request from "@/utils//axios";

const promiseRecord = {}; // 用于缓存请求状态

/**
 * 通过路径和参数生成唯一字符
 * @param {*} apiUrl
 */
const createKey = (apiUrl) => {
  return apiUrl;
};

// 普通的 get 请求
const get = (apiUrl) => request.get(apiUrl)

/**
 * 用来发起需要缓存的请求
 * @param {String} apiUrl
 * @param {Boolean} refresh 可能在某些情况下不能使用缓存必须到后台获取
 */
const getCache = (apiUrl, refresh = false) => {
  // 用请求路径和参数生成标识,完全相同的请求的标识一样,作为储存的键
  let keyName = createKey(apiUrl);

  return new Promise((resolve, reject) => {
    let data = sessionStorage.getItem(keyName);

    let request = () => {
      get(apiUrl)
        .then((value) => {
          sessionStorage.setItem(keyName, JSON.stringify(value));
          resolve(value);
        })
        .catch((error) => {
          reject(error);
        });
    };

    if (data && !refresh) {
      // 如果用户手动修改了 sessionStorage 里的数据可能会出错,应该做下处理
      try {
        resolve(JSON.parse(data));
      } catch (e) {
        request();
      }
    } else {
      request();
    }
  });
};

/**
 * 防止重复处理
 */
const repeat = (apiUrl, request, refresh) => {
  // 用请求路径和参数生成标识,完全相同的请求的标识一样,可以使用同一个请求结果
  let keyName = createKey(apiUrl);

  if (!promiseRecord[keyName]) {
    promiseRecord[keyName] = new Promise((resolve, reject) => {
      request(apiUrl, refresh)
        .then((value) => {
          promiseRecord[keyName] = null;
          resolve(value);
        })
        .catch((error) => {
          promiseRecord[keyName] = null;
          reject(error);
        });
    });
  }

  return promiseRecord[keyName];
};

/**
 * 返回请求的函数
 * @param {String} apiUrl
 * @param {Object} options 配置项
 */
const getAxios = (apiUrl, options = {}) => {
  // 默认配置
  let defaults = {
    cache: false, // 是否开启缓存
    repeat: false, // 是否开启防止同时发起相同的请求
    refresh: false, // 是否刷新(这里也不能保证会刷新,因为get也有缓存,只能保证它会发出请求)
  };
  let _options = Object.assign(Object.assign({}, defaults), options);

  // 什么都不需要 返回原始的axiso get请求
  if (!_options.cache && !_options.repeat) {
    return get(apiUrl);
  }

  // 只需要缓存
  if (_options.cache && !_options.repeat) {
    return getCache(apiUrl, _options.refresh);
  }

  // 只需要防止同时发起相同的请求
  if (!_options.cache && _options.repeat) {
    return repeat(apiUrl, get);
  }

  // 小孩子才做选择,大人全都要
  if (_options.cache && _options.repeat) {
    return repeat(apiUrl, getCache, _options.refresh);
  }
};

export default getAxios;

  
 
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
  • 57
  • 58
  • 59
  • 60
  • 61
  • 62
  • 63
  • 64
  • 65
  • 66
  • 67
  • 68
  • 69
  • 70
  • 71
  • 72
  • 73
  • 74
  • 75
  • 76
  • 77
  • 78
  • 79
  • 80
  • 81
  • 82
  • 83
  • 84
  • 85
  • 86
  • 87
  • 88
  • 89
  • 90
  • 91
  • 92
  • 93
  • 94
  • 95
  • 96
  • 97
  • 98
  • 99
  • 100
  • 101
  • 102
  • 103
  • 104
  • 105
  • 106
  • 107
  • 108
  • 109
  • 110
  • 111
  • 112
  • 113
  • 114
  • 115
  • 116
import getAxios from "@/utils/dict-cache.js"; // 接口缓存

const dictRequest = (type, refresh = false) =>
  getAxios(`/admin/dict/type/${type}`, {
    cache: true, // 是否开启缓存
    repeat: true, // 是否开启防止同时发起多个相同请求
    refresh: refresh, // 是否刷新
  });

  
 
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8

现在再结合组件使用就完美了。

文章来源: zimug.blog.csdn.net,作者:字母哥哥,版权归原作者所有,如需转载,请联系作者。

原文链接:zimug.blog.csdn.net/article/details/122997675

【版权声明】本文为华为云社区用户转载文章,如果您发现本社区中有涉嫌抄袭的内容,欢迎发送邮件进行举报,并提供相关证据,一经查实,本社区将立刻删除涉嫌侵权内容,举报邮箱: cloudbbs@huaweicloud.com
  • 点赞
  • 收藏
  • 关注作者

评论(0

0/1000
抱歉,系统识别当前为高风险访问,暂不支持该操作

全部回复

上滑加载中

设置昵称

在此一键设置昵称,即可参与社区互动!

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。