成人免费xxxxx在线视频软件_久久精品久久久_亚洲国产精品久久久_天天色天天色_亚洲人成一区_欧美一级欧美三级在线观看

【故障現(xiàn)場】將變更收斂在一處,避免散彈式更新

開發(fā) 前端
深入思考,該問題的本質(zhì)就是:對(duì)信息沒有進(jìn)行統(tǒng)一維護(hù),導(dǎo)致同一份數(shù)據(jù)在多個(gè)地方進(jìn)行管理,當(dāng)發(fā)生變化時(shí)只要有一處未及時(shí)更新便會(huì)出現(xiàn)問題。

1. 問題&分析

使用 code 真香,終于不用擔(dān)心枚舉重構(gòu)了,但還是高興的太早了,一個(gè)線上bug正在路上….

1.1. 案例

經(jīng)過連續(xù)多天奮戰(zhàn),系統(tǒng)終于上線了訂單手工取消功能,剛剛上線便收到客服部門的反饋:訂單列表中訂單狀態(tài)出現(xiàn)問題,顯示未 undefine。小艾趕緊查看后端日志,沒有發(fā)現(xiàn)任何異常,并緊急給前端負(fù)責(zé)人虎哥掛了個(gè)電話,很快虎哥便定位原因并進(jìn)行緊急修復(fù)。

事后復(fù)盤,原因是這樣的:

  1. 在訂單列表接口中,后端只返回了枚舉的 name
  2. 前端維護(hù)了一個(gè)配置文件,key 是 name,value 是顯示名稱,從接口獲取 name 后會(huì)基于配置文件進(jìn)行轉(zhuǎn)換,最終展示為 描述信息
  3. 本次修改,只改了主站的 js 配置,遺漏了客服系統(tǒng)。所以,主站沒有問題,而客服系統(tǒng)由于找不到新加的name,所以展示為 undefine

后端返回結(jié)果如下圖所示:

圖片圖片

默認(rèn)情況下,枚舉只會(huì)返回 Name,非常不利于展示,所以在前端會(huì)進(jìn)行一次翻譯,將 Name 翻譯成展示文案。

在這個(gè)接口的基礎(chǔ)上引起的問題如下圖所示:

圖片圖片

由于業(yè)務(wù)發(fā)展,OrderStatus 的枚舉值發(fā)生了變化,但只對(duì)主站頁面進(jìn)行調(diào)整,而客服系統(tǒng)被遺漏。所以:

  1. 主站頁面有最新的全量配置,信息展示準(zhǔn)確沒有任何問題
  2. 客服系統(tǒng)由于被遺忘使用的還是之前的配置,導(dǎo)致后端返回的 Name 和 配置信息不一致,由于找不到 Name 而出現(xiàn) undefine 錯(cuò)誤

1.2. 問題分析

深入思考,該問題的本質(zhì)就是:對(duì)信息沒有進(jìn)行統(tǒng)一維護(hù),導(dǎo)致同一份數(shù)據(jù)在多個(gè)地方進(jìn)行管理,當(dāng)發(fā)生變化時(shí)只要有一處未及時(shí)更新便會(huì)出現(xiàn)問題。

那解法也就很簡單了,將信息收口到后端進(jìn)行統(tǒng)一管理!

除這個(gè)問題外,還有一個(gè)非常類似的問題:前端下拉列表,也需要和后端定義保持一致,一般情況下:

  1. 前端單獨(dú)維護(hù),寫死在頁面,當(dāng)后端發(fā)生變化后,前端跟著一起調(diào)整。這個(gè)方案就會(huì)出現(xiàn)兩者不一致的問題,不鼓勵(lì)使用;
  2. 后端提供一個(gè)接口用于獲取數(shù)據(jù),然后在渲染到前端組件。這個(gè)是鼓勵(lì)的方案,但每個(gè)枚舉都需要提供一個(gè)接口,增加了后端的開發(fā)負(fù)擔(dān);

2. 解決方案

和 code 方案一致,可以使用接口對(duì)枚舉進(jìn)行約束。

2.1. 構(gòu)建統(tǒng)一接口

首先,定義統(tǒng)一的接口,用于提供描述信息:

public interface SelfDescribedEnum {
    default String getName(){
        return name();
    }

    String name();
    /**
    * 獲取描述信息
    */
    String getDescription();
}

2.2. 枚舉實(shí)現(xiàn)接口

然后,讓我們的枚舉實(shí)現(xiàn) SelfDescribedEnum 接口,具體如下:

public enum SelfDescribedEnumBasedOrderStatus implements SelfDescribedEnum {
    CREATED("待支付"),
    TIMEOUT_CANCELLED("超時(shí)取消"),
    MANUAL_CANCELLED("手工取消"),
    PAID("支付成功"),
    FINISHED("已完成");
    private final String description;

    SelfDescribedEnumBasedOrderStatus(String description) {
        this.description = description;
    }

    @Override
    public String getDescription() {
        return description;
    }
}

2.3. 集成 Spring MVC 返回結(jié)果

在完成上述工作后,我們將 OrderVO 中的 status 屬性類型更新為 SelfDescribedEnumBasedOrderStatus,具體如下:

@Data
public class OrderVO {
    private Long id;
    private SelfDescribedEnumBasedOrderStatus status;
}

最后一步也是最關(guān)鍵的一步便是,對(duì) Jackson 序列化器進(jìn)行定制,核心代碼如下:

@Configuration
public class SelfDescribedEnumJacksonCustomizer {

    @Bean
    public Jackson2ObjectMapperBuilderCustomizer commonEnumBuilderCustomizer(){
        return builder ->{
            // 注冊(cè)自定義枚舉序列化器
            builder.serializerByType(SelfDescribedEnum.class, new SelfDescribedEnumJsonSerializer());
        };
    }

    static class SelfDescribedEnumJsonSerializer extends JsonSerializer {

        @Override
        public void serialize(Object o, JsonGenerator jsonGenerator, SerializerProvider serializerProvider) throws IOException {
            SelfDescribedEnum selfDescribedEnum = (SelfDescribedEnum) o;
            SelfDescribedEnumVO selfDescribedEnumVO = SelfDescribedEnumVO.from(selfDescribedEnum);
            jsonGenerator.writeObject(selfDescribedEnumVO);
        }
    }
}

// SelfDescribedEnumVO 為定義的一個(gè) VO,具體如下:
@Data
public class SelfDescribedEnumVO {
    @ApiModelProperty(notes = "Name")
    private final String name;

    @ApiModelProperty(notes = "描述")
    private final String desc;

    public static SelfDescribedEnumVO from(SelfDescribedEnum selfDescribedEnum){
        if (selfDescribedEnum == null){
            return null;
        }
        return new SelfDescribedEnumVO(selfDescribedEnum.getName(), selfDescribedEnum.getDescription());
    }

    public static List<SelfDescribedEnumVO> from(List<SelfDescribedEnum> commonEnums){
        if (CollectionUtils.isEmpty(commonEnums)){
            return Collections.emptyList();
        }
        return commonEnums.stream()
                .filter(Objects::nonNull)
                .map(SelfDescribedEnumVO::from)
                .filter(Objects::nonNull)
                .collect(Collectors.toList());
    }
}

最后,啟動(dòng)服務(wù)查看新返回值,具體如下:

圖片圖片

可以看,status 字段原本只返回了 name,現(xiàn)在返回的是一個(gè)包括 name 和 desc 的對(duì)象。前端無需進(jìn)行轉(zhuǎn)換,只需直接讀取 status.desc 信息即可。

2.4. 提供統(tǒng)一字典服務(wù)

對(duì)于下來列表、選擇框的場景,最優(yōu)方案是為前端提供一個(gè)統(tǒng)一的字典接口,由該接口來返回所有字典信息。

核心代碼如下:

public class EnumDictController {
    private Map<String, List<SelfDescribedEnum>> enumDict = new HashMap<String, List<SelfDescribedEnum>>();

    public EnumDictController(){
        add("OrderStatus", SelfDescribedEnumBasedOrderStatus.values());
    }

    private void add(String type, SelfDescribedEnumBasedOrderStatus[] values) {
        this.enumDict.put(type, Arrays.asList(values));
    }

    /**
     * 獲取所有字典信息
     * @return
     */
    @GetMapping("all")
    public RestResult<Map<String, List<SelfDescribedEnumVO>>> allEnums(){
        Map<String, List<SelfDescribedEnumVO>> dictVo = Maps.newHashMapWithExpectedSize(enumDict.size());
        for (Map.Entry<String, List<SelfDescribedEnum>> entry : enumDict.entrySet()){
            dictVo.put(entry.getKey(), SelfDescribedEnumVO.from(entry.getValue()));
        }
        return RestResult.success(dictVo);
    }

    /**
     * 獲取支持的全部字典類型
     * @return
     */
    @GetMapping("types")
    public RestResult<List<String>> enumTypes(){
        return RestResult.success(Lists.newArrayList(enumDict.keySet()));
    }

    /**
     * 獲取指定字典的全部值
     * @param type
     * @return
     */
    @GetMapping("/{type}")
    public RestResult<List<SelfDescribedEnumVO>> dictByType(@PathVariable("type") String type){
        List<SelfDescribedEnum> enums = enumDict.get(type);

        return RestResult.success(SelfDescribedEnumVO.from(enums));
    }
}

啟動(dòng)服務(wù),驗(yàn)證字典接口。

獲取全部字典信息,返回結(jié)果如下:

圖片圖片

一次性返回全部字典對(duì)性能有損耗,那可以返回指定字典,結(jié)果如下:

圖片圖片

此時(shí),前端只需從接口中獲取所需要的數(shù)據(jù),無需在 js 中進(jìn)行單獨(dú)維護(hù)。

3. 示例&源碼

代碼倉庫:https://gitee.com/litao851025/learnFromBug

代碼地址:https://gitee.com/litao851025/learnFromBug/tree/master/src/main/java/com/geekhalo/demo/enums/descr


責(zé)任編輯:武曉燕 來源: geekhalo
相關(guān)推薦

2025-02-13 00:34:22

Spring對(duì)象系統(tǒng)

2009-06-06 09:07:05

微軟蓋茨莊園

2019-10-09 13:39:39

Python編程語言異常錯(cuò)誤

2023-08-29 17:40:28

技術(shù)大會(huì)

2018-04-27 14:18:01

2014-08-21 14:49:32

MIUI 6

2017-05-11 08:46:35

全閃存數(shù)據(jù)中心容量

2024-08-09 08:25:32

Spring流程注解

2025-03-12 08:00:00

單點(diǎn)登錄單設(shè)備登錄程序

2009-06-12 16:55:10

VPN客戶端故障

2021-12-02 07:50:30

NFS故障內(nèi)存

2023-12-05 14:10:00

接口可讀性

2010-11-19 17:11:48

RG-SSO組件五位一體銳捷網(wǎng)絡(luò)

2019-08-22 14:02:00

Spring BootRestful APIJava

2024-01-29 09:22:59

死鎖線程池服務(wù)

2025-02-05 11:30:00

單點(diǎn)故障MySQL數(shù)據(jù)庫

2020-02-19 08:00:00

微服務(wù)架構(gòu)分布式代碼

2020-05-26 13:48:05

后端框架異常

2022-01-17 09:19:12

Transformer數(shù)據(jù)人工智能
點(diǎn)贊
收藏

51CTO技術(shù)棧公眾號(hào)

主站蜘蛛池模板: 欧美亚洲激情 | 中文字幕在线视频免费观看 | 亚洲夜夜爽 | 99re超碰| 懂色一区二区三区免费观看 | 一区二区三区不卡视频 | 99久久精品国产毛片 | 成人av播放 | 久久99这里只有精品 | 久热久热 | 国产精品久久久久久久久久免费看 | 欧美国产一区二区 | 国产精品国产精品国产专区不卡 | 色婷婷婷婷色 | 日本精品免费 | 毛片1 | 精品一区二区三区四区 | 国内精品久久久久久 | 亚洲国产成人精品久久 | 国产在线观看一区二区 | 日韩在线小视频 | 99免费视频 | 正在播放国产精品 | 男人天堂免费在线 | 免费视频一区二区 | 中文字幕亚洲精品 | 九九热这里 | 做a视频| 网站黄色在线免费观看 | 成人黄色av网址 | 亚洲成人免费观看 | 欧美日本一区 | 男女免费网站 | 成人午夜性成交 | 国产一级片一区二区 | 国产精品99久久久久 | 中文字幕视频在线观看免费 | 亚洲国产精品久久久久秋霞不卡 | 日韩久久久久久 | 亚洲午夜在线 | a毛片视频网站 |