日韩成人免费在线_国产成人一二_精品国产免费人成电影在线观..._日本一区二区三区久久久久久久久不

當前位置:首頁 > 科技  > 軟件

【故障現場】控制好取值范圍,甭給別人犯錯的機會

來源: 責編: 時間:2023-12-11 09:25:13 236觀看
導讀1. 問題&分析1.1. 案例小艾剛剛和大飛哥炒了一架,心情非常低落。整個事情是這樣,小艾前段時間剛剛接手訂單系統,今天收到一大波線上 NPE (Null Pointer Exception)報警,經排查發現訂單表的商品類型(ProductType)出現一組非法

1. 問題&分析

1.1. 案例

小艾剛剛和大飛哥炒了一架,心情非常低落。整個事情是這樣,小艾前段時間剛剛接手訂單系統,今天收到一大波線上 NPE (Null Pointer Exception)報警,經排查發現訂單表的商品類型(ProductType)出現一組非法值,在展示訂單時由于系統無法識別這些非法值導致空指針異常。小艾通過排查,發現訂單來自于市場團隊,于是找到團隊負責人大飛哥,并把現狀和排查結果進行同步。經過大飛哥的排查,確實是在前端的各種跳轉過程中導致 商品類型參數 被覆蓋,立即安排緊急上線進行修復。整個事情處理速度快也沒造成太大損失,但在事故復盤過程中出現了偏差:FSm28資訊網——每日最新資訊28at.com

  1. 小艾認為核心問題是調用方沒有按規范進行傳參,所以主要責任在大飛哥;
  2. 大飛哥則認為是訂單系統未對輸入參數進行有效性校驗,致使問題數據存儲至數據庫,才出現后續的各種問題,所以主要責任在小艾;

兩人各持己見爭論不休,你認為責任在誰呢?FSm28資訊網——每日最新資訊28at.com

1.2. 問題分析

在訂單系統中,商品類型定義為 Integer 類型,使用靜態常量來表示系統所支持的具體值,核心代碼如下:FSm28資訊網——每日最新資訊28at.com

// 領域對象public class OrderItem{    private Integer productType;}// 定義 ProductTypes 管理所有支持的 ProductTypepublic class ProductTypes{    public static final Integer CLAZZ = 1;    public static final Integer BOOK = 2;    // 其他類型}// 創建訂單的請求對象@Data@ApiModel(description = "創建單個訂單")class CreateOrderRequest {    @ApiModelProperty(value = "產品類型")    private Integer productType;    @ApiModelProperty(value = "產品id")    private Integer productId;    @ApiModelProperty(value = "數量")    private Integer amount;}

對應的 Swagger 如下:FSm28資訊網——每日最新資訊28at.com

圖片圖片FSm28資訊網——每日最新資訊28at.com

由于類型定義為 Integer, 所以當輸入非法值(ProductTypes 定義之外的值)時,系統仍舊能接受并執行后續流程,這就是最核心的問題所在,如下圖所示:FSm28資訊網——每日最新資訊28at.com

圖片圖片FSm28資訊網——每日最新資訊28at.com

==商品類型(ProductType)在系統中是一個字典,有自己的固定取值范圍==,定義為 Integer 將放大可接受的值,一旦值在 ProductType 之外便會發生系統異常。FSm28資訊網——每日最新資訊28at.com

2. 解決方案

針對這個案例,小艾可以基于 ProductTypes 中定義的常量對所有入參進行校驗,并在接入文檔中進行強調。但,隨著系統的發展肯定會加入更多的流程,在新流程中產生遺漏就又會出現同樣的問題,那終極解決方案是什么?FSm28資訊網——每日最新資訊28at.com

將 ProductType 可接受的取值范圍與類型的取值范圍保存一致!!!FSm28資訊網——每日最新資訊28at.com

圖片圖片FSm28資訊網——每日最新資訊28at.com

這正是枚舉重要的應用場景。FSm28資訊網——每日最新資訊28at.com

【原則】規范、流程 在沒有檢測機制相輔助時都不可靠。如有可能,請使用編譯器進行強制約束!!!FSm28資訊網——每日最新資訊28at.com

2.1. 枚舉基礎知識

關鍵詞 enum 可以將一組具名值的有限集合創建成一種新的類型,而這些具名的值可以作為常規程序組件使用。FSm28資訊網——每日最新資訊28at.com

枚舉最常見的用途便是==替換常量定義==,為其增添類型約束,完成編譯時類型驗證。FSm28資訊網——每日最新資訊28at.com

2.1.1 枚舉定義

枚舉的定義與類和常量定義非常類似。使用 enum 關鍵字替換 class 關鍵字,然后在 enum 中定義“常量”即可。FSm28資訊網——每日最新資訊28at.com

對于 ProductType 枚舉方案如下:FSm28資訊網——每日最新資訊28at.com

// 定義public enum ProductType {    CLAZZ, BOOK;}public class OrderItem{    private ProductType productType;}

getProductType 和 setProductType 所需類型為 ProductType,不在是比較寬泛的 Integer。在使用的時候可以通過 ProductType.XXX 的方式獲取對應的枚舉值,這樣對類型有了更強的限制。FSm28資訊網——每日最新資訊28at.com

2.1.2. 枚舉的單例性

枚舉值具有單例性,及枚舉中的每個值都是一個單例對象,可以直接使用 == 進行等值判斷。FSm28資訊網——每日最新資訊28at.com

枚舉是定義單例對象最簡單的方法。FSm28資訊網——每日最新資訊28at.com

2.1.3. name 和 ordrial

對于簡單的枚舉,存在兩個維度,一個是name,即為定義的名稱;一個是ordinal,即為定義的順序。FSm28資訊網——每日最新資訊28at.com

圖片圖片FSm28資訊網——每日最新資訊28at.com

簡單測試如下:FSm28資訊網——每日最新資訊28at.com

@Testpublic void nameTest(){    for (ProductType productType : ProductType.values()){        // 枚舉的name維度        String name = productType.name();        System.out.println("ProductType:" + name);        // 通過name獲取定義的枚舉        ProductType productType1 = ProductType.valueOf(name);        System.out.println(productType == productType1);    }}

輸出結果為:FSm28資訊網——每日最新資訊28at.com

ProductType:CLAZZtrueProductType:BOOKtrue

ordrial測試如下:FSm28資訊網——每日最新資訊28at.com

@Testpublic void ordinalTest(){    for (ProductType productType : ProductType.values()){        // 枚舉的ordinal維度        int ordinal = productType.ordinal();        System.out.println("ProductType:" + ordinal);        // 通過ordinal獲取定義的枚舉        ProductType productType1 = ProductType.values()[ordinal];        System.out.println(productType == productType1);    }}

輸出結果如下:FSm28資訊網——每日最新資訊28at.com

ProductType:0trueProductType:1true

從輸出上可以清晰的看出:FSm28資訊網——每日最新資訊28at.com

  1. name 是我們在枚舉中定義變量的名稱
  2. ordrial 是我們在枚舉中定義變量的順序

2.1.4. 枚舉的本質

enum可以理解為編譯器的語法糖,在創建 enum 時,編譯器會為你生成一個相關的類,這個類繼承自 java.lang.Enum。FSm28資訊網——每日最新資訊28at.com

先看下Enum提供了什么:FSm28資訊網——每日最新資訊28at.com

public abstract class Enum<E extends Enum<E>>        implements Comparable<E>, Serializable {    // 枚舉的Name維度    private final String name;    public final String name() {        return name;    }    // 枚舉的ordinal維度    private final int ordinal;    public final int ordinal() {        return ordinal;    }    // 枚舉構造函數    protected Enum(String name, int ordinal) {        this.name = name;        this.ordinal = ordinal;    }    /**     * 重寫toString方法, 返回枚舉定義名稱     */    public String toString() {        return name;    }    // 重寫equals,由于枚舉對象為單例,所以直接使用==進行比較    public final boolean equals(Object other) {        return this==other;    }    // 重寫hashCode    public final int hashCode() {        return super.hashCode();    }    /**     * 枚舉為單例對象,不允許clone     */    protected final Object clone() throws CloneNotSupportedException {        throw new CloneNotSupportedException();    }    /**     * 重寫compareTo方法,同種類型按照定義順序進行比較     */    public final int compareTo(E o) {        Enum<?> other = (Enum<?>)o;        Enum<E> self = this;        if (self.getClass() != other.getClass() && // optimization            self.getDeclaringClass() != other.getDeclaringClass())            throw new ClassCastException();        return self.ordinal - other.ordinal;    }    /**     * 返回定義枚舉的類型     */    @SuppressWarnings("unchecked")    public final Class<E> getDeclaringClass() {        Class<?> clazz = getClass();        Class<?> zuper = clazz.getSuperclass();        return (zuper == Enum.class) ? (Class<E>)clazz : (Class<E>)zuper;    }    /**     * 靜態方法,根據name獲取枚舉值     * @since 1.5     */    public static <T extends Enum<T>> T valueOf(Class<T> enumType,                                                String name) {        T result = enumType.enumConstantDirectory().get(name);        if (result != null)            return result;        if (name == null)            throw new NullPointerException("Name is null");        throw new IllegalArgumentException(            "No enum constant " + enumType.getCanonicalName() + "." + name);    }    protected final void finalize() { }    /**     * 枚舉為單例對象,禁用反序列化     */    private void readObject(ObjectInputStream in) throws IOException,        ClassNotFoundException {        throw new InvalidObjectException("can't deserialize enum");    }    private void readObjectNoData() throws ObjectStreamException {        throw new InvalidObjectException("can't deserialize enum");    }}

從 Enum 中我們可以得到:FSm28資訊網——每日最新資訊28at.com

  1. Enum 中對 name 和 ordrial(final)的屬性進行定義,并提供構造函數進行初始化
  2. 重寫了equals、hashCode、toString方法,其中toString方法默認返回 name
  3. 實現了Comparable 接口,重寫 compareTo,使用枚舉定義順序進行比較
  4. 實現了Serializable 接口,并重寫禁用了clone、readObject 等方法,以保障枚舉的單例性
  5. 提供 valueOf 方法使用反射機制,通過name獲取枚舉值

到此已經解釋了枚舉類的大多數問題,ProductType.values(), ProductType.CLAZZ, ProductType.BOOK,又是從怎么來的呢?這些是編譯器為其添加的。FSm28資訊網——每日最新資訊28at.com

@Testpublic void enumTest(){    System.out.println("Fields");    for (Field field : ProductType.class.getDeclaredFields()){        field.getModifiers();        StringBuilder fieldBuilder = new StringBuilder();        fieldBuilder.append(Modifier.toString(field.getModifiers()))                .append(" ")                .append(field.getType())                .append(" ")                .append(field.getName());        System.out.println(fieldBuilder.toString());    }    System.out.println();    System.out.println("Methods");    for (Method method : ProductType.class.getDeclaredMethods()){        StringBuilder methodBuilder = new StringBuilder();        methodBuilder.append(Modifier.toString(method.getModifiers()));        methodBuilder.append(method.getReturnType())                .append(" ")                .append(method.getName())                .append("(");        Parameter[] parameters = method.getParameters();        for (int i=0; i< method.getParameterCount(); i++){            Parameter parameter = parameters[i];            methodBuilder.append(parameter.getType())                    .append(" ")                    .append(parameter.getName());            if (i != method.getParameterCount() -1) {                    methodBuilder.append(",");            }        }        methodBuilder.append(")");        System.out.println(methodBuilder);    }}

我們分別對 ProductType 中的屬性和方法進行打印,結果如下:FSm28資訊網——每日最新資訊28at.com

Fieldspublic static final class com.example.enumdemo.ProductType CLAZZpublic static final class com.example.enumdemo.ProductType BOOKprivate static final class [Lcom.example.enumdemo.ProductType; $VALUESMethodspublic staticclass [Lcom.example.enumdemo.ProductType; values()public staticclass com.example.enumdemo.ProductType valueOf(class java.lang.String arg0)

從輸出,我們可知編譯器為我們添加了以下幾個特性:FSm28資訊網——每日最新資訊28at.com

  1. 針對每一個定義的枚舉值,添加一個同名的 public static final 的屬性
  2. 添加一個private static final <pre>不能識別此Latex公式: VALUES 屬性記錄枚舉中所有的值信息
  3. 添加一個靜態的 values 方法,返回枚舉中所有的值信息(</pre>VALUES)
  4. 添加一個靜態的 valueOf 方法,用于通過 name 獲取枚舉值(調用 Enum 中的 valueOf 方法)

2.2. 修復方案

了解枚舉的基礎知識后,落地方案也就變的非常簡單,只需:FSm28資訊網——每日最新資訊28at.com

  • 構建一個枚舉類 ProductType,將所有支持的類型添加到枚舉中;
  • 將原來 OrderItem 中的 productType 從原來的 Integer 替換為 ProductType;

具體代碼如下:FSm28資訊網——每日最新資訊28at.com

// 將產品類型定義為 枚舉public enum ProductType {    CLAZZ, BOOK; // 定義系統所支持的類型}// 領域對象中直接使用 ProductType 枚舉public class OrderItem{    // 將原來的 Integer 替換為 ProductType    private ProductType productType;}// 創建單個訂單的請求對象@Data@ApiModel(description = "創建單個訂單")class CreateOrderRequest {    @ApiModelProperty(value = "產品類型")    private ProductType productType;    @ApiModelProperty(value = "產品id")    private Integer productId;    @ApiModelProperty(value = "數量")    private Integer amount;}

新的 Swagger 如下:FSm28資訊網——每日最新資訊28at.com

圖片圖片FSm28資訊網——每日最新資訊28at.com

可見,ProductType 被定義為枚舉類型,并直接給出了全部備選項。FSm28資訊網——每日最新資訊28at.com

3. 更多應用場景

枚舉的核心是==具有固定值的集合==,非常適用于各種類型(Type)、狀態(Status) 這些場景,所以在系統中看到 Type、Status、State 等關鍵字時,需要慎重考慮是否可以使用枚舉。FSm28資訊網——每日最新資訊28at.com

但,枚舉作為一種特殊的類,也為很多場景提供了更優雅的解決方案。FSm28資訊網——每日最新資訊28at.com

3.1. Switch

在Java 1.5之前,只有一些簡單類型(int,short,char,byte)可以用于 switch 的 case 語句,我們習慣采用 ‘常量+case’ 的方式增加代碼的可讀性,但是丟失了類型系統的校驗。由于枚舉的 ordinal 特性的存在,可以將其用于case語句。FSm28資訊網——每日最新資訊28at.com

public class FruitConstant {    public static final int APPLE = 1;    public static final int BANANA = 2;    public static final int PEAR = 3;}// 沒有類型保障public String nameByConstant(int fruit){    switch (fruit){        case FruitConstant.APPLE:            return "蘋果";        case FruitConstant.BANANA:            return "香蕉";        case FruitConstant.PEAR:            return "梨";    }    return "未知";}// 使用枚舉public enum FruitEnum {    APPLE,    BANANA,    PEAR;}// 有類型保障public String nameByEnum(FruitEnum fruit){    switch (fruit){        case APPLE:            return "蘋果";        case BANANA:            return "香蕉";        case PEAR:            return "梨";    }    return "未知";}

3.2. 單例

Java中單例的編寫主要有餓漢式、懶漢式、靜態內部類等幾種方式(雙重鎖判斷存在缺陷),但還有一種簡單的方式是基于枚舉的單例。FSm28資訊網——每日最新資訊28at.com

public interface Converter<S, T> {    T convert(S source);}// 每一個枚舉值都是一個單例對象public enum Date2StringConverters implements Converter<Date, String>{    yyyy_MM_dd("yyyy-MM-dd"),    yyyy_MM_dd_HH_mm_ss("yyyy-MM-dd HH:mm:ss"),    HH_mm_ss("HH:mm:ss");    private final String dateFormat;    Date2StringConverters(String dateFormat) {        this.dateFormat = dateFormat;    }    @Override    public String convert(Date source) {        return new SimpleDateFormat(this.dateFormat).format(source);    }}public class ConverterTests {    private final Converter<Date, String> converter1 = Date2StringConverters.yyyy_MM_dd;    private final Converter<Date, String> converter2 = Date2StringConverters.yyyy_MM_dd_HH_mm_ss;    private final Converter<Date, String> converter3 = Date2StringConverters.HH_mm_ss;    public void formatTest(Date date){        System.out.println(converter1.convert(date));        System.out.println(converter2.convert(date));        System.out.println(converter3.convert(date));    }}

3.3. 狀態機

狀態機是解決業務流程中的一種有效手段,而枚舉的單例性,為構建狀態機提供了便利。FSm28資訊網——每日最新資訊28at.com

以下是一個訂單的狀態扭轉流程,所涉及的狀態包括 Created、Canceled、Confirmed、Overtime、Paied;所涉及的動作包括cancel、confirm、timeout、pay。FSm28資訊網——每日最新資訊28at.com

graph TBNone{開始}--> |create|CreatedCreated-->|confirm|ConfirmedCreated-->|cancel|CanceldConfirmed-->|cancel|CanceldConfirmed-->|timeout|OvertimeConfirmed-->|pay| Paied
// 狀態操作接口,管理所有支持的動作public interface IOrderState {    void cancel(OrderStateContext context);    void confirm(OrderStateContext context);    void timeout(OrderStateContext context);    void pay(OrderStateContext context);}// 狀態機上下文public interface OrderStateContext {    void setStats(OrderState state);}// 訂單實際實現public class Order{    private OrderState state;    private void setStats(OrderState state) {        this.state = state;    }    // 將請求轉發給狀態機    public void cancel() {        this.state.cancel(new StateContext());    }    // 將請求轉發給狀態機    public void confirm() {        this.state.confirm(new StateContext());    }    // 將請求轉發給狀態機    public void timeout() {        this.state.timeout(new StateContext());    }    // 將請求轉發給狀態機    public void pay() {        this.state.pay(new StateContext());    }    // 內部類,實現OrderStateContext,回寫Order的狀態    class StateContext implements OrderStateContext{        @Override        public void setStats(OrderState state) {            Order.this.setStats(state);        }    }}// 基于枚舉的狀態機實現public enum OrderState implements IOrderState{    CREATED{        // 允許進行cancel操作,并把狀態設置為CANCELD        @Override        public void cancel(OrderStateContext context){            context.setStats(CANCELD);        }        // 允許進行confirm操作,并把狀態設置為CONFIRMED        @Override        public void confirm(OrderStateContext context) {            context.setStats(CONFIRMED);        }    },    CONFIRMED{        // 允許進行cancel操作,并把狀態設置為CANCELD        @Override        public void cancel(OrderStateContext context) {            context.setStats(CANCELD);        }        // 允許進行timeout操作,并把狀態設置為OVERTIME        @Override        public void timeout(OrderStateContext context) {            context.setStats(OVERTIME);        }        // 允許進行pay操作,并把狀態設置為PAIED        @Override        public void pay(OrderStateContext context) {            context.setStats(PAIED);        }    },    // 最終狀態,不允許任何操作    CANCELD{    },    // 最終狀態,不允許任何操作    OVERTIME{    },    // 最終狀態,不允許任何操作    PAIED{    };    @Override    public void cancel(OrderStateContext context) {        throw new NotSupportedException();    }    @Override    public void confirm(OrderStateContext context) {        throw new NotSupportedException();    }    @Override    public void timeout(OrderStateContext context) {        throw new NotSupportedException();    }    @Override    public void pay(OrderStateContext context) {        throw new NotSupportedException();    }}

3.4. 責任鏈

在責任鏈模式中,程序可以使用多種方式來處理一個問題,然后把他們鏈接起來,當一個請求進來后,他會遍歷整個鏈,找到能夠處理該請求的處理器并對請求進行處理。FSm28資訊網——每日最新資訊28at.com

枚舉可以實現某個接口,加上其天生的單例特性,可以成為組織責任鏈處理器的一種方式。FSm28資訊網——每日最新資訊28at.com

// 消息類型public enum MessageType {    TEXT, BIN, XML, JSON;}// 定義的消息體@Valuepublic class Message {    private final MessageType type;    private final Object object;    public Message(MessageType type, Object object) {        this.type = type;        this.object = object;    }}// 消息處理器public interface MessageHandler {    boolean handle(Message message);}
// 基于枚舉的處理器管理public enum MessageHandlers implements MessageHandler{    TEXT_HANDLER(MessageType.TEXT){        @Override        boolean doHandle(Message message) {            System.out.println("text");            return true;        }    },    BIN_HANDLER(MessageType.BIN){        @Override        boolean doHandle(Message message) {            System.out.println("bin");            return true;        }    },    XML_HANDLER(MessageType.XML){        @Override        boolean doHandle(Message message) {            System.out.println("xml");            return true;        }    },    JSON_HANDLER(MessageType.JSON){        @Override        boolean doHandle(Message message) {            System.out.println("json");            return true;        }    };    // 接受的類型    private final MessageType acceptType;    MessageHandlers(MessageType acceptType) {        this.acceptType = acceptType;    }    // 抽象接口    abstract boolean doHandle(Message message);    // 如果消息體是接受類型,調用doHandle進行業務處理    @Override    public boolean handle(Message message) {        return message.getType() == this.acceptType && doHandle(message);    }}
// 消息處理鏈public class MessageHandlerChain {    public boolean handle(Message message){        for (MessageHandler handler : MessageHandlers.values()){            if (handler.handle(message)){                return true;            }        }        return false;    }}

3.5. 分發器

分發器根據輸入的數據,找到對應的處理器,并將請求轉發給處理器進行處理。 由于 EnumMap 其出色的性能,特別適合根據特定類型作為分發策略的場景。FSm28資訊網——每日最新資訊28at.com

// 消息體@Valuepublic class Message {    private final MessageType type;    private final Object data;    public Message(MessageType type, Object data) {        this.type = type;        this.data = data;    }}// 消息類型public enum MessageType {    // 登錄    LOGIN,    // 進入房間    ENTER_ROOM,    // 退出房間    EXIT_ROOM,    // 登出    LOGOUT;}// 消息處理器public interface MessageHandler {    void handle(Message message);}
// 基于EnumMap的消息分發器public class MessageDispatcher {    private final Map<MessageType, MessageHandler> dispatcherMap =             new EnumMap<MessageType, MessageHandler>(MessageType.class);    public MessageDispatcher(){        dispatcherMap.put(MessageType.LOGIN, message -> System.out.println("Login"));        dispatcherMap.put(MessageType.ENTER_ROOM, message -> System.out.println("Enter Room"));        dispatcherMap.put(MessageType.EXIT_ROOM, message -> System.out.println("Exit Room"));        dispatcherMap.put(MessageType.LOGOUT, message -> System.out.println("Logout"));    }    public void dispatch(Message message){        MessageHandler handler = this.dispatcherMap.get(message.getType());        if (handler != null){            handler.handle(message);        }    }}

4. 示例&源碼

倉庫地址:https://gitee.com/litao851025/learnFromBug/FSm28資訊網——每日最新資訊28at.com

代碼地址:https://gitee.com/litao851025/learnFromBug/tree/master/src/main/java/com/geekhalo/demo/enums/limitFSm28資訊網——每日最新資訊28at.com

本文鏈接:http://www.www897cc.com/showinfo-26-41674-0.html【故障現場】控制好取值范圍,甭給別人犯錯的機會

聲明:本網頁內容旨在傳播知識,若有侵權等問題請及時與本網聯系,我們將在第一時間刪除處理。郵件:2376512515@qq.com

上一篇: 昇騰 AI 開發者創享日?廣州站成功舉辦 四大儀式激發人工智能產業創新活力

下一篇: 聊聊分布式數據庫TDSQL的技術架構

標簽:
  • 熱門焦點
  • MIX Fold3包裝盒泄露 新機本月登場

    小米的全新折疊屏旗艦MIX Fold3將于本月發布,近日該機的真機包裝盒在網上泄露。從圖上來看,新的MIX Fold3包裝盒在外觀設計方面延續了之前的方案,變化不大,這也是目前小米旗艦
  • 對標蘋果的靈動島 華為帶來實況窗功能

    繼蘋果的靈動島之后,華為也在今天正式推出了“實況窗”功能。據今天鴻蒙OS 4.0的現場演示顯示,華為的實況窗可以更高效的展現出實時通知,比如鎖屏上就能看到外賣、打車、銀行
  • 十個可以手動編寫的 JavaScript 數組 API

    JavaScript 中有很多API,使用得當,會很方便,省力不少。 你知道它的原理嗎? 今天這篇文章,我們將對它們進行一次小總結。現在開始吧。1.forEach()forEach()用于遍歷數組接收一參
  • 十個簡單但很有用的Python裝飾器

    裝飾器(Decorators)是Python中一種強大而靈活的功能,用于修改或增強函數或類的行為。裝飾器本質上是一個函數,它接受另一個函數或類作為參數,并返回一個新的函數或類。它們通常用
  • 如何使用JavaScript創建一只圖像放大鏡?

    譯者 | 布加迪審校 | 重樓如果您曾經瀏覽過購物網站,可能遇到過圖像放大功能。它可以讓您放大圖像的特定區域,以便瀏覽。結合這個小小的重要功能可以大大改善您網站的用戶體驗
  • 自動化在DevOps中的力量:簡化軟件開發和交付

    自動化在DevOps中扮演著重要角色,它提升了DevOps的效能。通過自動化工具和方法,DevOps團隊可以實現以下目標:消除手動和重復性任務。簡化流程。在整個軟件開發生命周期中實現更
  • 破圈是B站頭上的緊箍咒

    來源 | 光子星球撰文 | 吳坤諺編輯 | 吳先之每年的暑期檔都少不了瞄準追劇女孩們的古偶劇集,2021年有優酷的《山河令》,2022年有愛奇藝的《蒼蘭訣》,今年卻輪到小破站抓住了追
  • ESG的面子與里子

    來源 | 光子星球撰文 | 吳坤諺編輯 | 吳先之三伏大幕拉起,各地高溫預警不絕,但處于厄爾尼諾大&ldquo;烤&rdquo;之下的除了眾生,還有各大企業發布的ESG報告。ESG是&ldquo;環境保
  • 造車兩年股價跌六成,小米的估值邏輯變了嗎?

    如果從小米官宣造車后的首個交易日起持有小米集團的股票,那么截至2023年上半年最后一個交易日,投資者將浮虧59.16%,同區間的恒生科技指數跌幅為52.78%
Top 主站蜘蛛池模板: 东乌| 常山县| 阳新县| 江津市| 罗田县| 邯郸市| 土默特左旗| 宣武区| 甘谷县| 博罗县| 葫芦岛市| 桓仁| 尚义县| 特克斯县| 涞源县| 东兴市| 司法| 宁夏| 澄城县| 平和县| 临颍县| 清远市| 维西| 舟山市| 辽宁省| 怀仁县| 巴林左旗| 鱼台县| 中阳县| 芜湖县| 舒兰市| 泊头市| 揭西县| 衡阳县| 陆川县| 疏勒县| 社旗县| 聂荣县| 德惠市| 蓝山县| 扎赉特旗|