数据字典技术方案实战

前言

在后台与中台系统开发领域,数据字典是极为常见且至关重要的概念,相信大多数从事相关开发工作的朋友都对其耳熟能详。几乎每一个成熟的项目,都会专门设置一个字典模块,用于精心维护各类字典数据。这些字典数据在系统中扮演着举足轻重的角色,为下拉框、转义值、单选按钮等组件提供了不可或缺的基础数据支撑。

我自工作以来参与过很多个项目,既有从零开始搭建的,也有接手他人项目的。在实践过程中,我发现不同项目对字典的实现方式各不相同,且各有侧重。例如,对于项目中的字典基本不会发生变化的,项目通常会采用首次全部加载到本地缓存的方式。这种方式能显著节省网络请求次数,提升系统响应速度。然而,对于项目中的字典经常变动的,项目则会采用按需加载的方式,即哪里需要使用字典值,就在哪里进行加载。但这种方式也存在弊端,当某个页面需要使用十多个字典值时,首次进入页面会一次性发出十多个请求来获取这些字典值,影响用户体验。

常见字典方案剖析

在当下,数据字典的实现方案丰富多样,各有优劣。下面将详细介绍几种常见的方案,并分析其特点。我将详细介绍几种常见的方案,并深入剖析其特点。这几种方案皆是我通过实践精心总结而来,其中方案四的思路是由我不爱吃鱼啦提供。

方案一:首次全部加载到本地进行缓存

方案描述

系统启动或用户首次访问时,将所有字典数据一次性加载到本地缓存中。后续使用过程中,直接从缓存中获取所需字典数据,无需再次向服务器发起请求。

优点

  • 访问速度快:后续访问时直接从本地缓存读取数据,无需等待网络请求,响应速度极快。
  • 减少网络请求:一次性加载后,后续使用无需频繁发起网络请求,降低了网络开销。
  • 网络依赖小:即使在网络不稳定的情况下,也能正常使用已缓存的字典数据,保证了系统的稳定性。

缺点

  • 首次加载时间长:若字典数据量较大,首次加载时可能需要较长时间,影响用户体验。
  • 占用存储空间:将所有字典数据存储在本地,会占用较多的本地存储空间,尤其是当字典数据量庞大时。
  • 缓存更新复杂:若字典数据频繁更新,需要设计复杂的缓存同步和更新机制,否则容易出现数据不一致的问题。

方案二:按需加载不缓存

方案描述

当用户触发特定操作,需要使用字典数据时,才从后端实时加载所需数据,且不进行本地缓存。每次使用字典数据时,都重新从服务器获取最新数据。

优点

  • 节省存储空间:不进行本地缓存,节省了本地存储空间,尤其适用于存储资源有限的设备。
  • 数据实时性高:每次获取的数据都是最新的,不存在缓存数据与后端不一致的问题,保证了数据的准确性。

缺点

  • 网络请求频繁:每次使用都需要发起网络请求,在网络状况不佳时,会导致加载时间变长,影响用户体验。
  • 增加服务器负担:频繁的网络请求会增加服务器的负担,尤其是在高并发场景下,可能影响服务器的性能。

方案三:首次按需加载并缓存

方案描述

用户首次访问某个字典数据时,从后端加载该数据并缓存到本地。后续再次访问该字典数据时,直接从缓存中读取,无需再次向服务器发起请求。

优点

  • 减少网络请求:结合了前两种方案的部分优点,既在一定程度上减少了网络请求次数,又不会一次性加载过多数据。
  • 节省存储空间:相较于首次全部加载到本地缓存的方式,不会一次性占用大量本地存储空间,节省了部分存储资源。

缺点

  • 缓存管理复杂:需要记录哪些数据已缓存,以便后续判断是否需要从缓存中读取或重新加载,增加了缓存管理的复杂度。
  • 缓存占用问题:对于不常使用的字典数据,缓存可能会占用不必要的存储空间,造成资源浪费。
  • 缓存更新难题:同样面临缓存更新的问题,需要设计合理的缓存更新策略,以保证数据的准确性和一致性。

方案四:按需加载 + 版本校验更新缓存

方案描述

用户按需发起字典数据请求,首次访问某个字典数据时,从后端加载并缓存到本地。在后端响应头中携带该字典数据的版本信息,后续每次请求该字典数据时,前端对比本地缓存的版本信息和响应头中的版本信息。若版本信息不一致,则清除本地缓存中对应的字典数据,并重新从后端加载最新数据;若版本信息一致,则直接使用本地缓存的数据。

优点

  • 数据实时性有保障:通过版本校验机制,能够及时获取到字典数据的更新,确保前端使用的数据与后端保持一致,避免了因缓存数据未及时更新而导致的业务问题。
  • 减少不必要的网络请求:在字典数据未更新时,直接使用本地缓存,无需发起网络请求,节省了网络带宽和服务器资源。
  • 平衡存储与性能:既不会像首次全部加载那样占用大量本地存储空间,又能在一定程度上减少网络请求,在存储和性能之间取得了较好的平衡。

缺点

  • 版本管理复杂:后端需要维护字典数据的版本信息,并且要确保版本号的准确性和唯一性,这增加了后端开发的复杂度和维护成本。
  • 额外开销:每次请求都需要进行版本信息对比操作,虽然开销较小,但在高并发场景下,可能会对系统性能产生一定影响。
  • 首次加载体验:首次加载字典数据时,依然需要从后端获取数据,若数据量较大或网络状况不佳,可能会影响用户体验。

方案选型建议

建议根据项目特性选择方案,没有最好的技术方案,只有最适合项目的技术方案:

  • 字典稳定且量小:方案一全量缓存
  • 字典频繁更新:方案四版本校验缓存
  • 存储敏感场景:方案三按需缓存
  • 实时性要求极高:方案二无缓存方案

ps:如果大家有更好的方案,也可以在评论区提出,让我们大家一起学习成长

代码实现(方案四)

下述代码的实现基于vue3+pinia,该代码实现了统一管理全局字典数据,支持按需加载、缓存复用、版本控制、动态更新、批量处理字典数据等功能。

pinia store的实现

ts 复制代码
import { defineStore } from 'pinia'
import { ref } from 'vue'
import { getDictDetails, type Details } from '@/api/system/dict'

export const useDictStore = defineStore('dict', () => {
    // 存储字典数据,键为字典名称,值为字典详情数组
    const dictData = ref<Record<string, Details[]>>({})
    // 存储字典版本信息,键为字典名称,值为版本号
    const dictVersions = ref<string>('')

    /**
     * 更新字典版本信息
     * @param version 新的字典版本号
     */
    const updateDictVersion = (version: string) => {
        dictVersions.value = version
    }

    /**
     * 获取字典版本
     * @returns 字典版本号
     */
    const getDictVersion = () => {
        return dictVersions.value || ''
    }

    /**
     * 加载字典数据
     * @param dictNames 字典名称数组
     * @returns 加载的字典数据对象
     */
    const getDicts = async (dictNames: string[]) => {
        try {
            if (!Array.isArray(dictNames)) {
                return {};
            }
            // 过滤并去重有效字典名称
            const uniqueNames = [...new Set(dictNames.filter(name => 
                typeof name === 'string' && name.trim()
            ))];
            
            if (uniqueNames.length === 0) {
                return {};
            }

            const result: Record<string, Details[]> = {};
            const unloadedDicts: string[] = [];

            // 分离已加载和未加载的字典
            dictNames.forEach(name => {
                if (dictData.value[name]) {
                    result[name] = dictData.value[name];
                } else {
                    unloadedDicts.push(name);
                }
            });

            // 如果有未加载的字典,从接口请求获取
            if (unloadedDicts.length > 0) {
                const { data } = await getDictDetails(unloadedDicts);

                // 合并新加载的数据到结果
                Object.assign(result, data);

                // 更新全局字典缓存
                Object.assign(dictData.value, data);
            }

            return result;
        } catch (error) {
            console.error('加载字典数据失败:', error);
            return {};
        }
    };

    /**
     * 根据字典名称获取字典数据
     * @param name 字典名称
     * @returns 字典详情数组
     */
    const getDict = (name: string) => {
        return dictData.value[name] || []
    }

    /**
     * 根据字典名称和值获取字典标签
     * @param name 字典名称
     * @param value 字典值
     * @returns 字典标签
     */
    const getDictLabel = (name: string, value: string) => {
        const dict = getDict(name)
        const item = dict.find(item => item.value === value)
        return item?.label || ''
    }

    /**
     * 根据字典名称和标签获取字典值
     * @param name 字典名称
     * @param label 字典标签
     * @returns 字典值
     */
    const getDictValue = (name: string, label: string) => {
        const dict = getDict(name)
        const item = dict.find(item => item.label === label)
        return item?.value || ''
    }

    /**
     * 清除指定字典数据
     * @param names 字典名称
     */
    const clearDicts = (names: string[]) => {
        names.forEach(name => {
            clearDict(name)
        })
    }


    /**
     * 清除指定字典数据
     * @param name 字典名称
     */
    const clearDict = (name: string) => {
        delete dictData.value[name]
    }

    /**
     * 清除所有字典数据
     */
    const clearAllDict = () => {
        dictData.value = {}
    }

    return {
        dictData,
        updateDictVersion,
        getDictVersion,
        getDict,
        getDicts,
        getDictLabel,
        getDictValue,
        clearDict,
        clearDicts,
        clearAllDict
    }
})

useDict 实现

为组件提供字典数据的统一访问入口,封装了字典数据的初始化加载、详情查询、标签/值转换等高频操作,简化组件层对字典数据的调用逻辑。

ts 复制代码
import { type Details } from '@/api/system/dict'
import { useDictStore } from '@/store/dict'

// 根据字典值的name获取字典详情
export const useDict = (params: string[] = []) => {

  const dict = ref<Record<string, Details[]>>()
  const dictStore = useDictStore()

  const getDicts = async () => {
    dict.value = await dictStore.getDicts(params)
  }

  // 初始化字典数据
  getDicts()

  // 根据字典名称获取字典数据
  const getDict = (name: string) => {
    return dictStore.getDict(name)
  }

  // 根据字典值获取字典label
  const getDictLabel = (name: string, value: string) => {
    return dictStore.getDictLabel(name, value)
  }

  return {
    dict,
    getDict,
    getDictLabel
  }
}

响应拦截

主要用于获取字典的版本信息,通过对比版本信息,从而确定是否清除本地的字典缓存数据,并更新本地缓存的版本信息

ts 复制代码
// 响应拦截器
service.interceptors.response.use(
  // AxiosResponse
  (response: AxiosResponse) => {
    const dictVersion = response.headers['x-dictionary-version']
    if (dictVersion) {
      const dictStore = useDictStore()
      // 对比版本是否有更新
      if (dictStore.getDictVersion() !== dictVersion) {
        dictStore.clearAllDict()
        dictStore.updateDictVersion(dictVersion || '')
      }
    }
    // ...项目中的业务逻辑
  }
)

项目中的具体使用

下述的怎么使用封装的字典管理的简单demo

ts 复制代码
<script setup lang="ts">
import { useDict } from '@/hooks/useDict'
// 获取dict
const { dict, getDictLabel } =  useDict(['status', 'sex'])
console.log(dict.status, dict.sex)
</script>

结语

本文介绍了四种主流的数据字典实现方案,从全量加载到按需加载,从无缓存到版本校验缓存,每种方案都展现了其独特的优势与缺点。通过对比分析,我们不难发现,没有一种方案能够适用于所有场景,而是需要根据项目的具体特性进行灵活选择。对于字典稳定且量小的项目,全量缓存方案能够带来极致的响应速度;对于字典频繁更新的场景,版本校验缓存方案则能在保障数据实时性的同时,实现存储空间与网络请求的平衡优化。未来,随着技术的不断进步与应用场景的不断拓展,数据字典的实现方案也将持续演进。

博客主要记录一些学习的文章,如有不足,望大家指出,谢谢。

相关推荐
狗哥哥2 小时前
前端基础数据中心:从混乱到统一的架构演进
前端·vue.js·架构
AutoMQ2 小时前
Kafka 性能调优:linger.ms 和 batch.size 的最佳实践
架构
爱吃大芒果2 小时前
Flutter 基础组件详解:Text、Image、Button 使用技巧
开发语言·javascript·flutter·华为·ecmascript·harmonyos
CinzWS2 小时前
基于Cortex-M3的PMU架构--科学设计原则与设计目标
架构·pmu
leafff1232 小时前
深度拆解 Claude 的 Agent 架构:MCP + PTC、Skills 与 Subagents 的三维协同
人工智能·架构
大布布将军2 小时前
一种名为“Webpack 配置工程师”的已故职业—— Vite 与“零配置”的快乐
前端·javascript·学习·程序人生·webpack·前端框架·学习方法
JosieBook2 小时前
【Vue】02 Vue技术——搭建 Vue 开发框架:在VS Code中创建一个Vue项目
前端·javascript·vue.js
utmhikari2 小时前
【架构艺术】简述LLM增强产品研发角色
ai·架构·llm·agent·产品经理·系统设计
科普瑞传感仪器2 小时前
航空航天制造升级:机器人高精度力控打磨如何赋能复合材料加工?
java·前端·人工智能·机器人·无人机·制造