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
  • 下麵是一個標準的IDistributedCache用例: public class SomeService(IDistributedCache cache) { public async Task<SomeInformation> GetSomeInformationAsync (string na ...
  • 這個庫提供了在啟動期間實例化已註冊的單例,而不是在首次使用它時實例化。 單例通常在首次使用時創建,這可能會導致響應傳入請求的延遲高於平時。在註冊時創建實例有助於防止第一次Request請求的SLA 以往我們要在註冊的時候實例單例可能會這樣寫: //註冊: services.AddSingleton< ...
  • 最近公司的很多項目都要改單點登錄了,不過大部分都還沒敲定,目前立刻要做的就只有一個比較老的項目 先改一個試試手,主要目標就是最短最快實現功能 首先因為要保留原登錄方式,所以頁面上的改動就是在原來登錄頁面下加一個SSO登錄入口 用超鏈接寫的入口,頁面改造後如下圖: 其中超鏈接的 href="Staff ...
  • Like運算符很好用,特別是它所提供的其中*、?這兩種通配符,在Windows文件系統和各類項目中運用非常廣泛。 但Like運算符僅在VB中支持,在C#中,如何實現呢? 以下是關於LikeString的四種實現方式,其中第四種為Regex正則表達式實現,且在.NET Standard 2.0及以上平... ...
  • 一:背景 1. 講故事 前些天有位朋友找到我,說他們的程式記憶體會偶發性暴漲,自己分析了下是非托管記憶體問題,讓我幫忙看下怎麼回事?哈哈,看到這個dump我還是非常有興趣的,居然還有這種游戲幣自助機類型的程式,下次去大玩家看看他們出幣的機器後端是不是C#寫的?由於dump是linux上的程式,剛好win ...
  • 前言 大家好,我是老馬。很高興遇到你。 我們為 java 開發者實現了 java 版本的 nginx https://github.com/houbb/nginx4j 如果你想知道 servlet 如何處理的,可以參考我的另一個項目: 手寫從零實現簡易版 tomcat minicat 手寫 ngin ...
  • 上一次的介紹,主要圍繞如何統一去捕獲異常,以及為每一種異常添加自己的Mapper實現,並且我們知道,當在ExceptionMapper中返回非200的Response,不支持application/json的響應類型,而是寫死的text/plain類型。 Filter為二方包異常手動捕獲 參考:ht ...
  • 大家好,我是R哥。 今天分享一個爽飛了的面試輔導 case: 這個杭州兄弟空窗期 1 個月+,面試了 6 家公司 0 Offer,不知道問題出在哪,難道是杭州的 IT 崩盤了麽? 報名面試輔導後,經過一個多月的輔導打磨,現在成功入職某上市公司,漲薪 30%+,955 工作制,不咋加班,還不捲。 其他 ...
  • 引入依賴 <!--Freemarker wls--> <dependency> <groupId>org.freemarker</groupId> <artifactId>freemarker</artifactId> <version>2.3.30</version> </dependency> ...
  • 你應如何運行程式 互動式命令模式 開始一個互動式會話 一般是在操作系統命令行下輸入python,且不帶任何參數 系統路徑 如果沒有設置系統的PATH環境變數來包括Python的安裝路徑,可能需要機器上Python可執行文件的完整路徑來代替python 運行的位置:代碼位置 不要輸入的內容:提示符和註 ...