<kbd id="afajh"><form id="afajh"></form></kbd>
<strong id="afajh"><dl id="afajh"></dl></strong>
    <del id="afajh"><form id="afajh"></form></del>
        1. <th id="afajh"><progress id="afajh"></progress></th>
          <b id="afajh"><abbr id="afajh"></abbr></b>
          <th id="afajh"><progress id="afajh"></progress></th>

          SystemDictStarter系統(tǒng)數(shù)據(jù)字典自動轉(zhuǎn)換工具

          聯(lián)合創(chuàng)作 · 2023-09-28 10:04

          0. 項(xiàng)目地址
          0.1 依賴坐標(biāo)
          1. 開始使用
          1.1 數(shù)據(jù)準(zhǔn)備
          1.2 字典緩存存儲
          1.3 DictProvider 中的字典信息變動如何刷新字典?
          2. 用法示例
          2.1 基礎(chǔ)用法示例
          2.2 靜態(tài)工具直接獲取字典信息
          3. 其他
          3.1 SpringBoot Actuator 端點(diǎn)支持
          3.2 默認(rèn) Controller 接口
          3.3 面對大量數(shù)據(jù)需要轉(zhuǎn)換的場景,是否會頻繁去調(diào)用接口獲取實(shí)際字典文本?
          3.4 配置說明

          在日常項(xiàng)目開發(fā)中,不免都會用到一些數(shù)據(jù)字典的信息,以及前端展示的時候通常也需要把這些數(shù)據(jù)字典值轉(zhuǎn)換成具體字典文本信息。遇到這種場景通常都是后端把字典的文本轉(zhuǎn)換好一起返回給前端,前端只需要直接轉(zhuǎn)換即可。一般情況下后端可能需要單獨(dú)給返回對象創(chuàng)建一個字段來存儲對應(yīng)的字典文本值,然后進(jìn)行手動的處理,這種方式通常比較繁瑣,在字段多的時候會增加更多的工作量。

          本文基于 Jackson 的自定義注解功能實(shí)現(xiàn)了這一自動轉(zhuǎn)換過程,在字段上使用特定的注解配置,Jackson序列化的時候即可自動把字典值轉(zhuǎn)換成字典文本。

          0. 項(xiàng)目地址

          0.1 依賴坐標(biāo)

          <dependency>
              <groupId>com.houkunlin</groupId>
              <artifactId>system-dict-starter</artifactId>
              <!-- 當(dāng)前版本:1.4.3 -->
              <version>${latest.version}</version>
          </dependency>

          1. 開始使用

          使用數(shù)據(jù)字典通常有兩種字典,一種是存儲在數(shù)據(jù)庫中的動態(tài)形式數(shù)據(jù)字典,一種是用枚舉對象硬編碼在代碼中的系統(tǒng)字典,本工具為了適應(yīng)第二種枚舉對象字典的情況,定義了一個枚舉字典掃描注解,需要在啟動類上使用注解,并定義要掃描的包信息。

          //  啟動類上加注解,這一個步驟是必須的
          @SystemDictScan(basePackages = "test.application.dict")
          public class Application {
              public static void main(String[] args) {
                  SpringApplication.run(Application.class);
              }
          }
          

          1.1 數(shù)據(jù)準(zhǔn)備

          直接使用枚舉對象來做字典場景,枚舉對象需要實(shí)現(xiàn)一個 DictEnum<V> 接口才能被正常掃描到,枚舉對象有兩個自定義的注解 @DictConverter@DictType 可以做一些相關(guān)配置

          • @DictType 用來標(biāo)記枚舉對象的字典類型代碼

          • @DictConverter 用來標(biāo)記是否對這個枚舉對象生成 org.springframework.core.convert.converter.Converter 轉(zhuǎn)換對象,提供使用枚舉接收參數(shù)時自動轉(zhuǎn)換字典值到相應(yīng)枚舉對象類型的功能,未加此注解將不會生成轉(zhuǎn)換器對象。

          @DictConverter
          @DictType(value = "PeopleType", comment = "用戶類型")
          @Getter
          @AllArgsConstructor
          public enum PeopleType implements DictEnum<Integer> {
              /** 系統(tǒng)管理員 */
              ADMIN(0, "系統(tǒng)管理"),
              /** 普通用戶 */
              USER(1, "普通用戶"),
              ;
              private final Integer value;
              private final String title;
          ?
              @JsonCreator
              public static PeopleType getItem(Integer code) {
                  return DictEnum.valueOf(values(), code);
              }
          }
          

          前面在啟動類上加了注解功能僅僅只是啟用了基礎(chǔ)的功能,我們的字典可能還會存儲在數(shù)據(jù)庫或本地文件等其他地方,因此需要向系統(tǒng)提供一個 DictProvider 對象

          @Component
          public class MyProvider implements DictProvider {
              @Override
              public boolean isStoreDictType() {
                  return true;
              }
          ?
              @Override
              public Iterator<DictTypeVo> dictTypeIterator() {
                  // 從其他地方(其他服務(wù)、數(shù)據(jù)庫、本地文件)加載完整的數(shù)據(jù)字典信息(字典類型+字典值列表)
                  // 從這里返回的數(shù)據(jù)字典信息將會被存入緩存中,以便下次直接調(diào)用,當(dāng)有數(shù)據(jù)變動時可以發(fā)起 RefreshDictEvent 事件通知更新字典信息
                  final DictTypeVo typeVo = DictTypeVo.newBuilder("name", "測試字典")
                      .add("1", "測試1")
                      .add("2", "測試2")
                      .build();
                  return Collections.singletonList(typeVo).iterator();
              }
          }

          上面 DictProvider 中返回的字典信息會被存儲在緩存中,但是可能我們會有一些數(shù)據(jù)量特別大的場景不適合直接把數(shù)據(jù)存儲在緩存中,有可能需要直接從數(shù)據(jù)庫中讀取,甚至去請求遠(yuǎn)程服務(wù)的信息,此時可以提供一個 RemoteDict 對象來處理這種情況,當(dāng)在緩存中找不到字典文本值的時候,會調(diào)用 RemoteDict 對象來嘗試進(jìn)一步讀取字典文本信息。

          @Component
          public class MyRemoteDict implements RemoteDict {
              @Override
              public DictTypeVo getDictType(final String type) {
                  // 從其他地方(其他服務(wù)、數(shù)據(jù)庫、本地文件)加載一個完整的數(shù)據(jù)字典信息(字典類型+字典值列表)
                  return null;
              }
          ?
              @Override
              public String getDictText(final String type, final String value) {
                  // 從其他地方(其他服務(wù)、數(shù)據(jù)庫、本地文件)加載一個字典文本信息
                  return null;
              }
          }
          ?
          

          1.2 字典緩存存儲

          在前面說到系統(tǒng)的枚舉字典和 DictProvider 提供的字典會被緩存,工具中已經(jīng)默認(rèn)提供了兩個緩存對象

          • LocalDictStore 本地 Map 緩存存儲使用了 ConcurrentHashMap 來緩存字典值/字典文本信息

          • RedisDictStore 使用了 Redis 來存儲字典值/字典文本信息,當(dāng)想啟用 Redis 存儲字典的時候只需要在項(xiàng)目中引入 org.springframework.boot:spring-boot-starter-data-redis 依賴并配置好 Redis 連接信息即可

          有時候,上面提供的兩個緩存對象可能并不適用自己的業(yè)務(wù)場景,那么我們還可以手動實(shí)現(xiàn)一個緩存存儲對象 DictStore ,在手動實(shí)現(xiàn)緩存對象時前面的 RemoteDict 并不會生效,因此需要在 DictStore 中自行處理此種情況。

          // 可參考 LocalDictStore 自行實(shí)現(xiàn)相關(guān)功能
          @Component
          @AllArgsConstructor
          public class MyDictStore implements DictStore {
              private final RemoteDict remoteDict;
          ?
              @Override
              public void store(final DictTypeVo dictType) {
          ?
              }
          ?
              @Override
              public void store(final Iterator<DictValueVo> iterator) {
          ?
              }
          ?
              @Override
              public Set<String> dictTypeKeys() {
                  return null;
              }
          ?
              @Override
              public DictTypeVo getDictType(final String type) {
                  return remoteDict.getDictType(type);
              }
          ?
              @Override
              public String getDictText(final String type, final String value) {
                  return remoteDict.getDictText(type, value);
              }
          }
          

          1.3 DictProvider 中的字典信息變動如何刷新字典?

          DictProvider 提供的字典信息是從其他地方讀取的,其字典數(shù)據(jù)有可能會產(chǎn)生變動,當(dāng)字典變動后可以發(fā)起 RefreshDictEvent 事件來觸發(fā)字典刷新。

          @Component
          @AllArgsConstructor
          public class CommandRunnerTests implements CommandLineRunner {
              private final ApplicationEventPublisher publisher;
          ?
              @Override
              public void run(final String... args) throws Exception {
                  // 發(fā)起 RefreshDictEvent 事件通知刷新字典信息
                  publisher.publishEvent(new RefreshDictEvent("test", true, true));
              }
          }

           

          2. 用法示例

          2.1 基礎(chǔ)用法示例

          為了正常能夠轉(zhuǎn)換數(shù)據(jù),因此需要使用一個 Jackson 的自定義注解 @DictText ,把此注解用在需要轉(zhuǎn)換的字段上即可。

          @Data
          @AllArgsConstructor
          class Bean {
              @DictText("PeopleType")
              private String userType;
              private String userType1;
          }
          final Bean bean = new Bean("1", null);
          final String value = objectMapper.writeValueAsString(bean);
          System.out.println(bean); // Bean(userType=1,userType1=null)
          System.out.println(value); // {"userType":"1","userTypeText":"普通用戶","userType1":null}

          我們不需要在對象中為字典文本創(chuàng)建一個單獨(dú)的字段,@DictText 會自動生成一個 字段名 + Text 的字段輸出到前端。但是有時候我們覺得 字段名 + Text 這個字段不行,想要用另外一個字段名稱,此時可以用下面這種方式:

          @Data
          @AllArgsConstructor
          class Bean {
              @DictText(value = "PeopleType", fieldName = "typeText")
              private String userType;
          }
          final Bean bean = new Bean("1");
          final String value = objectMapper.writeValueAsString(bean);
          System.out.println(bean); // Bean(userType=1)
          System.out.println(value); // {"userType":"1","typeText":"普通用戶"}

          有時候我們可能用一個字符串字段來存儲多個字典文本信息,并通過特定的符號來分隔,例如:

          @Data
          @AllArgsConstructor
          class Bean {
              @DictText(value = "PeopleType", array = @Array(split = ","))
              private String userType;
          }
          final Bean bean = new Bean("0,1");
          final String value = objectMapper.writeValueAsString(bean);
          System.out.println(bean); // Bean(userType=0,1)
          System.out.println(value); // {"userType":"0,1","userTypeText":"系統(tǒng)管理、普通用戶"}

          當(dāng)然也有可能使用一個集合來存儲多個字典文本信息:

          @Data
          @AllArgsConstructor
          class Bean {
              @DictText("PeopleType")
              private List<String> userType;
          }
          final Bean bean = new Bean(Arrays.asList("0", "1"));
          final String value = objectMapper.writeValueAsString(bean);
          System.out.println(bean); // Bean(userType=["0","1"])
          System.out.println(value); // {"userType":["0","1"],"userTypeText":"系統(tǒng)管理、普通用戶"}

          也許對于這種字典值列表可能需要輸出文本列表信息

          @Data
          @AllArgsConstructor
          class Bean {
              @DictText(value = "PeopleType", array = @Array(toText = false))
              private List<String> userType;
          }
          final Bean bean = new Bean(Arrays.asList("0", "1"));
          final String value = objectMapper.writeValueAsString(bean);
          System.out.println(bean); // Bean(userType=[0, 1])
          System.out.println(value); // {"userType":["0","1"],"userTypeText":["系統(tǒng)管理","普通用戶"]}
          

          2.2 靜態(tài)工具直接獲取字典信息

          有時候不僅僅是用在返回給前端時自動轉(zhuǎn)換,可能在程序中也需要直接用到這些字典文本,此時可以通過靜態(tài)工具類來直接獲取字典文本信息

          @Component
          @AllArgsConstructor
          public class CommandRunnerTests implements CommandLineRunner {
              @Override
              public void run(final String... args) throws Exception {
                  System.out.println(DictUtil.getDictText("PeopleType", "1"))
              }
          }

          靜態(tài)工具類無法處理多個字典的情況,也就是無法對 "0,1" 這種數(shù)據(jù)進(jìn)行自動分割,這種場景需要自行分割并獲取數(shù)據(jù)

           

          3. 其他

          3.1 SpringBoot Actuator 端點(diǎn)支持

          提供了 dictdict-system 兩個端點(diǎn)信息

          // 獲取所有的字典名稱列表和一些配置的對象名稱
          GET /actuator/dict/
          ?
          // 獲取某個字典類型的完整信息
          GET /actuator/dict/PeopleType
          ?
          // 獲取某個字典值的字典文本信息
          GET /actuator/dict/PeopleType/1
          ?
          // 獲取系統(tǒng)字典的名稱列表(枚舉對象)
          GET /actuator/dict-system
          ?
          // 獲取系統(tǒng)字典的完整信息
          GET /actuator/dict-system/PeopleType

          3.2 默認(rèn) Controller 接口

          可通過一個配置 system.dict.controller.enabled 來配置是否啟用默認(rèn)接口,使用 system.dict.controller.prefix 來配置路徑前綴信息,啟用后將提供以下4個接口

          • ${prefix}/{dict} 通過字典類型代碼獲取字典類型信息

          • ${prefix}/{dict}/{value} 通過字典類型代碼和字典值獲取字典文本信息

          • ${prefix}/?dict={dict} 通過字典類型代碼獲取字典類型信息

          • ${prefix}/?dict={dict}&value={value} 通過字典類型代碼和字典值獲取字典文本信息

          3.3 面對大量數(shù)據(jù)需要轉(zhuǎn)換的場景,是否會頻繁去調(diào)用接口獲取實(shí)際字典文本?

          DictUtil 工具中增加了一層緩存,緩存使用了 Caffeine 并配置了一定的緩存過期時間 ,當(dāng)我們獲取一個字典文本的時候并不會直接去調(diào)用 DictStore 讀取字典文本,而是先從緩存中查找是否存在,如果存在則直接返回字典文本信息,并且當(dāng)從 DictStore 讀取失敗次數(shù)達(dá)到一定量時也不會繼續(xù)從 DictStore 中讀取數(shù)據(jù)。

          這在使用 Redis 存儲的場景時可以有效的減少網(wǎng)絡(luò)請求,雖然 Redis 很快,但是也有可能會造成一定的網(wǎng)絡(luò)延時,這在轉(zhuǎn)換數(shù)量大的時候可以有效的縮短因轉(zhuǎn)換帶來的延時問題。

          3.4 配置說明

          • system.dict 字典配置

            • raw-value=false 是否顯示原生數(shù)據(jù)字典值。true 實(shí)際類型輸出,false 轉(zhuǎn)換成字符串值

            • text-value-default-null=false 字典文本的值是否默認(rèn)為null,true 默認(rèn)為null,false 默認(rèn)為空字符串

            • on-boot-refresh-dict=true 是否在啟動的時候刷新字典

            • map-value=false 是否把字典值轉(zhuǎn)換成 Map 形式,包含字典值和文本。false 時在 json 中插入字段顯示字典文本;true 時把原字段的值變成 Map 數(shù)據(jù)

            • refresh-dict-interval=60000 兩次刷新字典事件的時間間隔;兩次刷新事件時間間隔小于配置參數(shù)將不會刷新。單位:毫秒

          • system.dict.cache DictUtil 工具字典緩存

            • enabled=true 是否啟用緩存

            • maximumSize=500 緩存最大容量

            • initialCapacity=50 緩存初始化容量

            • duration=30s 有效期時長

            • missNum=50 在有效期內(nèi)同一個字典值未命中指定次數(shù)將快速返回,不再重復(fù)請求獲取數(shù)據(jù)字典信息

          • system.dict.controller 默認(rèn)控制器

            • enabled=true 是否啟用 WEB 請求接口

            • prefix=/dict WEB 請求接口前綴

           

          瀏覽 29
          點(diǎn)贊
          評論
          收藏
          分享

          手機(jī)掃一掃分享

          編輯 分享
          舉報
          評論
          圖片
          表情
          推薦
          點(diǎn)贊
          評論
          收藏
          分享

          手機(jī)掃一掃分享

          編輯 分享
          舉報
          <kbd id="afajh"><form id="afajh"></form></kbd>
          <strong id="afajh"><dl id="afajh"></dl></strong>
            <del id="afajh"><form id="afajh"></form></del>
                1. <th id="afajh"><progress id="afajh"></progress></th>
                  <b id="afajh"><abbr id="afajh"></abbr></b>
                  <th id="afajh"><progress id="afajh"></progress></th>
                  av草逼 | 天天操天天干天天爱 | 国产一区二区三区四区五区在线 | 午夜成人久久 | 欧美成人性做爱视频 |