Pipeline模式應用

来源:https://www.cnblogs.com/Jcloud/archive/2023/12/08/17884476.html
-Advertisement-
Play Games

本文重點為管道模式的抽象與應用,上述示例僅為個人理解。實際應用中,此案例長於應對各種規則冗雜的業務場景,便於規則編排。 ...


本文記錄Pipeline設計模式在業務流程編排中的應用

前言

Pipeline模式意為管道模式,又稱為流水線模式。旨在通過預先設定好的一系列階段來處理輸入的數據,每個階段的輸出即是下一階段的輸入。

本案例通過定義PipelineProduct(管道產品),PipelineJob(管道任務),PipelineNode(管道節點),完成一整條流水線的組裝,並將“原材料”加工為“商品”。其中管道產品負責承載各個階段的產品信息;管道任務負責不同階段對產品的加工;管道節點約束了管道產品及任務的關係,通過信號量定義了任務的執行方式。

依賴

工具依賴如下

            <!-- 工具類大全 -->
            <dependency>
                <groupId>cn.hutool</groupId>
                <artifactId>hutool-all</artifactId>
                <version>最新版本</version>
            </dependency>



編程示例

1. 管道產品定義

package com.example.demo.pipeline.model;

/**
 * 管道產品介面
 *
 * @param <S> 信號量
 * @author 
 * @date 2023/05/15 11:49
 */
public interface PipelineProduct<S> {
}




2. 管道任務定義

package com.example.demo.pipeline.model;

/**
 * 管道任務介面
 *
 * @param <P> 管道產品
 * @author 
 * @date 2023/05/15 11:52
 */
@FunctionalInterface
public interface PipelineJob<P> {
    /**
     * 執行任務
     *
     * @param product 管道產品
     * @return {@link P}
     */
    P execute(P product);
}



3. 管道節點定義

package com.jd.baoxian.mall.market.service.pipeline.model;

import java.util.function.Predicate;

/**
 * 管道節點定義
 *
 * @param <S> 信號量
 * @param <P> 管道產品
 * @author 
 * @date 2023/05/15 11:54
 */
public interface PipelineNode<S, P extends PipelineProduct<S>> {
    /**
     * 節點組裝,按照上個管道任務傳遞的信號,執行 pipelineJob
     *
     * @param pipelineJob 管道任務
     * @return {@link PipelineNode}<{@link S},  {@link P}>
     */
    PipelineNode<S, P> flax(PipelineJob<P> pipelineJob);

    /**
     * 節點組裝,按照傳遞的信號,判斷當前管道的信號是否相等,執行 pipelineJob
     *
     * @param signal      信號
     * @param pipelineJob 管道任務
     * @return {@link PipelineNode}<{@link S},  {@link P}>
     */
    PipelineNode<S, P> flax(S signal, PipelineJob<P> pipelineJob);

    /**
     * 節點組裝,按照傳遞的信號,判斷當前管道的信號是否相等,執行 pipelineJob
     *
     * @param predicate   信號
     * @param pipelineJob 管道任務
     * @return {@link PipelineNode}<{@link S},  {@link P}>
     */
    PipelineNode<S, P> flax(Predicate<S> predicate, PipelineJob<P> pipelineJob);

    /**
     * 管道節點-任務執行
     *
     * @param product 管道產品
     * @return {@link P}
     */
    P execute(P product);
}




4. 管道產品、任務,節點的實現

4.1 管道產品

package com.example.demo.pipeline.factory;


import com.example.demo.model.request.DemoReq;
import com.example.demo.model.response.DemoResp;
import com.example.demo.pipeline.model.PipelineProduct;
import lombok.*;

/**
 * 樣例-管道產品
 *
 * @author 
 * @date 2023/05/15 14:04
 */
@Data
@Builder
@NoArgsConstructor
@AllArgsConstructor
public class DemoPipelineProduct implements PipelineProduct<DemoPipelineProduct.DemoSignalEnum> {
    /**
     * 信號量
     */
    private DemoSignalEnum signal;

    /**
     * 產品-入參及回參
     */
    private DemoProductData productData;

    /**
     * 異常信息
     */
    private Exception exception;

    /**
     * 流程Id
     */
    private String tradeId;

    @Data
    @Builder
    @NoArgsConstructor
    @AllArgsConstructor
    public static class DemoProductData {
        /**
         * 待驗證入參
         */
        private DemoReq userRequestData;

        /**
         * 待驗證回參
         */
        private DemoResp userResponseData;
    }

    /**
     * 產品-信號量
     *
     * @author 
     * @date 2023/05/15 13:54
     */
    @Getter
    public enum DemoSignalEnum {
        /**
         *
         */
        NORMAL(0, "正常"),
        /**
         *
         */
        CHECK_NOT_PASS(1, "校驗不通過"),
        /**
         *
         */
        BUSINESS_ERROR(2, "業務異常"),
        /**
         *
         */
        LOCK_ERROR(3, "鎖處理異常"),
        /**
         *
         */
        DB_ERROR(4, "事務處理異常"),

        ;
        /**
         * 枚舉碼值
         */
        private final int code;
        /**
         * 枚舉描述
         */
        private final String desc;

        /**
         * 構造器
         *
         * @param code
         * @param desc
         */
        DemoSignalEnum(int code, String desc) {
            this.code = code;
            this.desc = desc;
        }
    }
}





4.2 管道任務(抽象類)

package com.example.demo.pipeline.factory.job;

import cn.hutool.core.util.ClassUtil;
import cn.hutool.json.JSONUtil;
import com.example.demo.pipeline.factory.DemoPipelineProduct;
import com.example.demo.pipeline.model.PipelineJob;
import lombok.extern.slf4j.Slf4j;

/**
 * 管道任務-抽象層
 *
 * @author 
 * @date 2023/05/15 19:48
 */
@Slf4j
public abstract class AbstractDemoJob implements PipelineJob<DemoPipelineProduct> {

    /**
     * 公共執行邏輯
     *
     * @param product 產品
     * @return
     */
    @Override
    public DemoPipelineProduct execute(DemoPipelineProduct product) {
        DemoPipelineProduct.DemoSignalEnum newSignal;
        try {
            newSignal = execute(product.getTradeId(), product.getProductData());
        } catch (Exception e) {
            product.setException(e);
            newSignal = DemoPipelineProduct.DemoSignalEnum.BUSINESS_ERROR;
        }
        product.setSignal(newSignal);
        defaultLogPrint(product.getTradeId(), product);
        return product;
    }

    /**
     * 子類執行邏輯
     *
     * @param tradeId     流程Id
     * @param productData 請求數據
     * @return
     * @throws Exception 異常
     */
    abstract DemoPipelineProduct.DemoSignalEnum execute(String tradeId, DemoPipelineProduct.DemoProductData productData) throws Exception;

    /**
     * 預設的日誌列印
     */
    public void defaultLogPrint(String tradeId, DemoPipelineProduct product) {
        if (!DemoPipelineProduct.DemoSignalEnum.NORMAL.equals(product.getSignal())) {
            log.info("流水線任務處理異常:流程Id=【{}】,信號量=【{}】,任務=【{}】,參數=【{}】", tradeId, product.getSignal(),
                    ClassUtil.getClassName(this, true), JSONUtil.toJsonStr(product.getProductData()), product.getException());
        }
    }

}




4.3 管道節點

package com.example.demo.pipeline.factory;


import cn.hutool.core.util.ClassUtil;
import cn.hutool.json.JSONUtil;
import com.example.demo.pipeline.model.PipelineJob;
import com.example.demo.pipeline.model.PipelineNode;
import lombok.extern.slf4j.Slf4j;

import java.util.function.Predicate;

/**
 * 審核-管道節點
 *
 * @author 
 * @date 2023/05/15 14:32
 */
@Slf4j
public class DemoPipelineNode implements PipelineNode<DemoPipelineProduct.DemoSignalEnum, DemoPipelineProduct> {

    /**
     * 下一管道節點
     */
    private DemoPipelineNode next;

    /**
     * 當前管道任務
     */
    private PipelineJob<DemoPipelineProduct> job;

    /**
     * 節點組裝,按照上個管道任務傳遞的信號,執行 pipelineJob
     *
     * @param pipelineJob 管道任務
     * @return {@link DemoPipelineNode}
     */
    @Override
    public DemoPipelineNode flax(PipelineJob<DemoPipelineProduct> pipelineJob) {
        return flax(DemoPipelineProduct.DemoSignalEnum.NORMAL, pipelineJob);
    }

    /**
     * 節點組裝,按照傳遞的信號,判斷當前管道的信號是否相等,執行 pipelineJob
     *
     * @param signal      信號
     * @param pipelineJob 管道任務
     * @return {@link DemoPipelineNode}
     */
    @Override
    public DemoPipelineNode flax(DemoPipelineProduct.DemoSignalEnum signal, PipelineJob<DemoPipelineProduct> pipelineJob) {
        return flax(signal::equals, pipelineJob);
    }

    /**
     * 節點組裝,上個管道過來的信號運行 predicate 後是true的話,執行 pipelineJob
     *
     * @param predicate
     * @param pipelineJob
     * @return
     */
    @Override
    public DemoPipelineNode flax(Predicate<DemoPipelineProduct.DemoSignalEnum> predicate,
                                 PipelineJob<DemoPipelineProduct> pipelineJob) {
        this.next = new DemoPipelineNode();
        this.job = (job) -> {
            if (predicate.test(job.getSignal())) {
                return pipelineJob.execute(job);
            } else {
                return job;
            }
        };
        return next;
    }

    /**
     * 管道節點-任務執行
     *
     * @param product 管道產品
     * @return
     */
    @Override
    public DemoPipelineProduct execute(DemoPipelineProduct product) {
        // 執行當前任務
        try {
            product = job == null ? product : job.execute(product);
            return next == null ? product : next.execute(product);
        } catch (Exception e) {
            log.error("流水線處理異常:流程Id=【{}】,任務=【{}】,參數=【{}】", product.getTradeId(), ClassUtil.getClassName(job, true), JSONUtil.toJsonStr(product.getProductData()), product.getException());
            return null;
        }

    }
}





5. 業務實現

通過之前的定義,我們已經可以通過Pipeline完成流水線的搭建,接下來以“審核信息提交”這一業務場景,完成應用。

5.1 定義Api、入參、回參

package com.example.demo.api;

import com.example.demo.model.request.DemoReq;
import com.example.demo.model.response.DemoResp;
import com.example.demo.pipeline.factory.PipelineForManagerSubmit;
import org.springframework.stereotype.Service;

import javax.annotation.Resource;

/**
 * 演示-API
 *
 * @author 
 * @date 2023/08/06 16:27
 */
@Service
public class DemoManagerApi {

    /**
     * 管道-審核提交
     */
    @Resource
    private PipelineForManagerSubmit pipelineForManagerSubmit;

    /**
     * 審核提交
     *
     * @param requestData 請求數據
     * @return {@link DemoResp}
     */
    public DemoResp managerSubmit(DemoReq requestData) {
        return pipelineForManagerSubmit.managerSubmitCheck(requestData);
    }
}


package com.example.demo.model.request;

/**
 * 演示入參
 *
 * @author 
 * @date 2023/08/06 16:33
 */
public class DemoReq {
}


package com.example.demo.model.response;

import lombok.Data;

/**
 * 演示回參
 *
 * @author 
 * @date 2023/08/06 16:33
 */
@Data
public class DemoResp {
    /**
     * 成功標識
     */
    private Boolean success = false;

    /**
     * 結果信息
     */
    private String resultMsg;

    /**
     * 構造方法
     *
     * @param message 消息
     * @return {@link DemoResp}
     */
    public static DemoResp buildRes(String message) {
        DemoResp response = new DemoResp();
        response.setResultMsg(message);
        return response;
    }
}





5.2 定義具體任務

假定審核提交的流程需要包含:參數驗證、加鎖、解鎖、事務提交

package com.example.demo.pipeline.factory.job;

import cn.hutool.json.JSONUtil;
import com.example.demo.model.request.DemoReq;
import com.example.demo.pipeline.factory.DemoPipelineProduct;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Service;

/**
 * 加鎖-實現層
 *
 * @author 
 * @date 2023/05/17 17:00
 */
@Service
@Slf4j
public class CheckRequestLockJob extends AbstractDemoJob {

    /**
     * 子類執行邏輯
     *
     * @param tradeId     流程Id
     * @param productData 請求數據
     * @return
     * @throws Exception 異常
     */
    @Override
    DemoPipelineProduct.DemoSignalEnum execute(String tradeId, DemoPipelineProduct.DemoProductData productData) throws Exception {
        DemoReq userRequestData = productData.getUserRequestData();
        log.info("任務[{}]加鎖,線程號:{}", JSONUtil.toJsonStr(userRequestData), tradeId);
        return DemoPipelineProduct.DemoSignalEnum.NORMAL;
    }
}


package com.example.demo.pipeline.factory.job;

import cn.hutool.json.JSONUtil;
import com.example.demo.model.request.DemoReq;
import com.example.demo.pipeline.factory.DemoPipelineProduct;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Service;

/**
 * 解鎖-實現層
 *
 * @author 
 * @date 2023/05/17 17:00
 */
@Service
@Slf4j
public class CheckRequestUnLockJob extends AbstractDemoJob {

    /**
     * 子類執行邏輯
     *
     * @param tradeId     流程Id
     * @param productData 請求數據
     * @return
     * @throws Exception 異常
     */
    @Override
    DemoPipelineProduct.DemoSignalEnum execute(String tradeId, DemoPipelineProduct.DemoProductData productData) throws Exception {
        DemoReq userRequestData = productData.getUserRequestData();
        log.info("任務[{}]解鎖,線程號:{}", JSONUtil.toJsonStr(userRequestData), tradeId);
        return DemoPipelineProduct.DemoSignalEnum.NORMAL;
    }
}

package com.example.demo.pipeline.factory.job;

import cn.hutool.json.JSONUtil;
import com.example.demo.model.request.DemoReq;
import com.example.demo.pipeline.factory.DemoPipelineProduct;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Component;


/**
 * 審核-參數驗證-實現類
 *
 * @author 
 * @date 2023/05/15 19:50
 */
@Slf4j
@Component
public class ManagerCheckParamJob extends AbstractDemoJob {

    /**
     * 執行基本入參驗證
     *
     * @param tradeId
     * @param productData 請求數據
     * @return
     */
    @Override
    DemoPipelineProduct.DemoSignalEnum execute(String tradeId, DemoPipelineProduct.DemoProductData productData) {
        /*
         * 入參驗證
         */
        DemoReq userRequestData = productData.getUserRequestData();
        log.info("任務[{}]入參驗證,線程號:{}", JSONUtil.toJsonStr(userRequestData), tradeId);
        // 非空驗證

        // 有效驗證

        // 校驗通過,退出
        return DemoPipelineProduct.DemoSignalEnum.NORMAL;
    }

}


package com.example.demo.pipeline.factory.job;

import cn.hutool.json.JSONUtil;
import com.example.demo.model.request.DemoReq;
import com.example.demo.model.response.DemoResp;
import com.example.demo.pipeline.factory.DemoPipelineProduct;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Service;

/**
 * 審核-信息提交-業務實現
 *
 * @author 
 * @date 2023/05/12 14:36
 */
@Service
@Slf4j
public class ManagerSubmitJob extends AbstractDemoJob {

    /**
     * 子類執行邏輯
     *
     * @param tradeId     流程Id
     * @param productData 請求數據
     * @return
     * @throws Exception 異常
     */
    @Override
    DemoPipelineProduct.DemoSignalEnum execute(String tradeId, DemoPipelineProduct.DemoProductData productData) throws Exception {
        DemoReq userRequestData = productData.getUserRequestData();
        try {
            /*
             * DB操作
             */
            log.info("任務[{}]信息提交,線程號:{}", JSONUtil.toJsonStr(userRequestData), tradeId);
            productData.setUserResponseData(DemoResp.buildRes("成功"));
        } catch (Exception ex) {
            log.error("審核-信息提交-DB操作失敗,入參:{}", JSONUtil.toJsonStr(userRequestData), ex);
            throw ex;
        }
        return DemoPipelineProduct.DemoSignalEnum.NORMAL;
    }
}





5.3 完成流水線組裝

針對入回參轉換,管道任務執行順序及執行信號量的構建

package com.example.demo.pipeline.factory;

import com.example.demo.model.request.DemoReq;
import com.example.demo.model.response.DemoResp;
import com.example.demo.pipeline.factory.job.CheckRequestLockJob;
import com.example.demo.pipeline.factory.job.CheckRequestUnLockJob;
import com.example.demo.pipeline.factory.job.ManagerCheckParamJob;
import com.example.demo.pipeline.factory.job.ManagerSubmitJob;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Service;

import javax.annotation.PostConstruct;
import java.util.Objects;
import java.util.UUID;

/**
 * 管道工廠入口-審核流水線
 *
 * @author 
 * @date 2023/05/15 19:52
 */
@Slf4j
@Service
@RequiredArgsConstructor
public class PipelineForManagerSubmit {

    /**
     * 審核-管道節點
     */
    private final DemoPipelineNode managerSubmitNode = new DemoPipelineNode();


    /**
     * 審核-管道任務-提交-防刷鎖-加鎖
     */
    private final CheckRequestLockJob checkRequestLockJob;

    /**
     * 審核-管道任務-提交-防刷鎖-解鎖
     */
    private final CheckRequestUnLockJob checkRequestUnLockJob;

    /**
     * 審核-管道任務-參數驗證
     */
    private final ManagerCheckParamJob managerCheckParamJob;

    /**
     * 審核-管道任務-事務操作
     */
    private final ManagerSubmitJob managerSubmitJob;


    /**
     * 組裝審核的處理鏈
     */
    @PostConstruct
    private void assembly() {
        assemblyManagerSubmit();
    }

    /**
     * 組裝處理鏈
     */
    private void assemblyManagerSubmit() {

        managerSubmitNode
                // 參數驗證及填充
                .flax(managerCheckParamJob)
                // 防刷鎖
                .flax(checkRequestLockJob)
                // 事務操作
                .flax(managerSubmitJob)
                // 鎖釋放
                .flax((ignore) -> true, checkRequestUnLockJob);
    }

    /**
     * 審核-提交處理
     *
     * @param requestData 入參
     * @return
     */
    public DemoResp managerSubmitCheck(DemoReq requestData) {
        DemoPipelineProduct initialProduct = managerSubmitCheckInitial(requestData);
        DemoPipelineProduct finalProduct = managerSubmitNode.execute(initialProduct);
        if (Objects.isNull(finalProduct) || Objects.nonNull(finalProduct.getException())) {
            return DemoResp.buildRes("未知異常");
        }
        return finalProduct.getProductData().getUserResponseData();
    }

    /**
     * 審核-初始化申請的流水線數據
     *
     * @param requestData 入參
     * @return 初始的流水線數據
     */
    private DemoPipelineProduct managerSubmitCheckInitial(DemoReq requestData) {
        // 初始化
        return DemoPipelineProduct.builder()
                .signal(DemoPipelineProduct.DemoSignalEnum.NORMAL)
                .tradeId(UUID.randomUUID().toString())
                .productData(DemoPipelineProduct.DemoProductData.builder().userRequestData(requestData).build())
                .build();
    }
}




總結

本文重點為管道模式的抽象與應用,上述示例僅為個人理解。實際應用中,此案例長於應對各種規則冗雜的業務場景,便於規則編排。
待改進點:

  1. 各個任務其實隱含了執行的先後順序,此項內容可進一步實現;

  2. 針對最後“流水線組裝”這一步,可通過配置描述的方式,進一步抽象,從而將變動控制在每個“管道任務”的描述上,針對規則項做到“可插拔”式處理。

作者:京東保險 侯亞東

來源:京東雲開發者社區 轉載請註明來源


您的分享是我們最大的動力!

-Advertisement-
Play Games
更多相關文章
  • tmux教程 功能 分屏:可以在一個開發框里分屏 允許terminal在連接斷開之後可以繼續運行,讓進程不會因為斷開連接而中斷 結構 // 一個tmux可以包含多個session,一個session可以包含多個window,一個window可以包含多個pane。 tmux: session 0: w ...
  • 本文分享自華為雲社區《GaussDB資料庫SQL系列-層次遞歸查詢》,作者: Gauss松鼠會小助手2。 一、前言 層次遞歸查詢是一種常見的SQL查詢方式,特別是在一些層次化的數據存儲結構中經常用到。本文主要以GaussDB資料庫為實驗平臺,為大家講解其使用方法。 二、GuassDB資料庫層次遞歸查 ...
  • 在我們應用中的使用場景來看,簡單來說通常會看中了clickhouse在處理大批量數據的寫入和讀取分析方面的性能,MySQL會主要負責一些基於模型進行指標二次加工的高頻查詢及複雜join的查詢。 ...
  • 這裡給大家分享我在網上總結出來的一些知識,希望對大家有所幫助 前言 大家好,今天聊一下在做uniapp多端適配項目,需要用到自定義導航時,如何解決狀態欄塌陷及導航欄安全區域多端適配問題,下文只針對H5、APP、微信小程式三端進行適配,通過封裝一個通用高階組件包裹自定義導航欄內容,主要是通過設置pad ...
  • 求上進的人,不要總想著靠誰,人都是自私的,自己才是最靠得住的人。 React 中生命周期劃時代幾個節點,React 16.2 之前處於老的生命周期,之後提出了新的生命周期。而函數式組件在 React 16.8 之前是沒有狀態和生命周期的,在 React 16.8 版本通過引入 Hooks 使得函數式 ...
  • HTML中的title標簽是非常重要的標簽之一,它用來描述網頁的標題。在搜索引擎優化中,title標簽是非常關鍵的,因為搜索引擎會將title標簽中的文字作為頁面的主要描述,並根據其相關性來判斷網頁內容的質量和權重。 ...
  • 小程式中最常見的功能就是底部導航欄了,今天就來看一下怎麼設置一個好看的導航欄~這裡我們使用的是支付寶官方小程式 IDE 做示範。 ...
  • antd Pro組件ProFormList自定義action ProFormList是ant design pro的結構化數據組件,通常用來實現動態表單。 現在有個需求,除了組件自帶的刪除和複製,還需要增加兩個按鈕來實現每個item位置的上下移動,如圖所示: 查看官方文檔,組件有提供自定義actio ...
一周排行
    -Advertisement-
    Play Games
  • 移動開發(一):使用.NET MAUI開發第一個安卓APP 對於工作多年的C#程式員來說,近來想嘗試開發一款安卓APP,考慮了很久最終選擇使用.NET MAUI這個微軟官方的框架來嘗試體驗開發安卓APP,畢竟是使用Visual Studio開發工具,使用起來也比較的順手,結合微軟官方的教程進行了安卓 ...
  • 前言 QuestPDF 是一個開源 .NET 庫,用於生成 PDF 文檔。使用了C# Fluent API方式可簡化開發、減少錯誤並提高工作效率。利用它可以輕鬆生成 PDF 報告、發票、導出文件等。 項目介紹 QuestPDF 是一個革命性的開源 .NET 庫,它徹底改變了我們生成 PDF 文檔的方 ...
  • 項目地址 項目後端地址: https://github.com/ZyPLJ/ZYTteeHole 項目前端頁面地址: ZyPLJ/TreeHoleVue (github.com) https://github.com/ZyPLJ/TreeHoleVue 目前項目測試訪問地址: http://tree ...
  • 話不多說,直接開乾 一.下載 1.官方鏈接下載: https://www.microsoft.com/zh-cn/sql-server/sql-server-downloads 2.在下載目錄中找到下麵這個小的安裝包 SQL2022-SSEI-Dev.exe,運行開始下載SQL server; 二. ...
  • 前言 隨著物聯網(IoT)技術的迅猛發展,MQTT(消息隊列遙測傳輸)協議憑藉其輕量級和高效性,已成為眾多物聯網應用的首選通信標準。 MQTTnet 作為一個高性能的 .NET 開源庫,為 .NET 平臺上的 MQTT 客戶端與伺服器開發提供了強大的支持。 本文將全面介紹 MQTTnet 的核心功能 ...
  • Serilog支持多種接收器用於日誌存儲,增強器用於添加屬性,LogContext管理動態屬性,支持多種輸出格式包括純文本、JSON及ExpressionTemplate。還提供了自定義格式化選項,適用於不同需求。 ...
  • 目錄簡介獲取 HTML 文檔解析 HTML 文檔測試參考文章 簡介 動態內容網站使用 JavaScript 腳本動態檢索和渲染數據,爬取信息時需要模擬瀏覽器行為,否則獲取到的源碼基本是空的。 本文使用的爬取步驟如下: 使用 Selenium 獲取渲染後的 HTML 文檔 使用 HtmlAgility ...
  • 1.前言 什麼是熱更新 游戲或者軟體更新時,無需重新下載客戶端進行安裝,而是在應用程式啟動的情況下,在內部進行資源或者代碼更新 Unity目前常用熱更新解決方案 HybridCLR,Xlua,ILRuntime等 Unity目前常用資源管理解決方案 AssetBundles,Addressable, ...
  • 本文章主要是在C# ASP.NET Core Web API框架實現向手機發送驗證碼簡訊功能。這裡我選擇是一個互億無線簡訊驗證碼平臺,其實像阿裡雲,騰訊雲上面也可以。 首先我們先去 互億無線 https://www.ihuyi.com/api/sms.html 去註冊一個賬號 註冊完成賬號後,它會送 ...
  • 通過以下方式可以高效,並保證數據同步的可靠性 1.API設計 使用RESTful設計,確保API端點明確,並使用適當的HTTP方法(如POST用於創建,PUT用於更新)。 設計清晰的請求和響應模型,以確保客戶端能夠理解預期格式。 2.數據驗證 在伺服器端進行嚴格的數據驗證,確保接收到的數據符合預期格 ...