分類
發燒車訊

重學 Java 設計模式:實戰組合模式(營銷差異化人群發券,決策樹引擎搭建場景)

作者:小傅哥
博客:https://bugstack.cn

沉澱、分享、成長,讓自己和他人都能有所收穫!

一、前言

小朋友才做選擇題,成年人我都要

頭幾年只要群里一問我該學哪個開發語言,哪個語言最好。群里肯定聊的特別火熱,有人支持PHP、有人喊號Java、也有C++和C#。但這幾年開始好像大家並不會真的刀槍棍棒、斧鉞鈎叉般討論了,大多數時候都是開玩笑的鬧一鬧。於此同時在整體的互聯網開發中很多時候是一些開發語言公用的,共同打造整體的生態圈。而大家選擇的方式也是更偏向於不同領域下選擇適合的架構,而不是一味地追求某個語言。這可以給很多初學編程的新人一些提議,不要刻意的覺得某個語言好,某個語言不好,只是在適合的場景下選擇最需要的。而你要選擇的那個語言可以參考招聘網站的需求量和薪資水平決定。

編程開發不是炫技

總會有人喜歡在整體的項目開發中用上點新特性,把自己新學的知識實踐試試。不能說這樣就是不好,甚至可以說這是一部分很熱愛學習的人,喜歡創新,喜歡實踐。但編程除了用上新特性外,還需要考慮整體的擴展性、可讀性、可維護、易擴展等方面的考慮。就像你家裡雇傭了一夥裝修師傅,有那麼一個小工喜歡炫技搞花活,在家的淋浴下安裝了馬桶。

即使是寫CRUD也應該有設計模式

往往很多大需求都是通過增刪改查堆出來的,今天要一個需求if一下,明天加個內容else擴展一下。日積月累需求也就越來越大,擴展和維護的成本也就越來越高。往往大部分研發是不具備產品思維和整體業務需求導向的,總以為寫好代碼完成功能即可。但這樣的不考慮擴展性的實現,很難讓後續的需求都快速迭代,久而久之就會被陷入惡性循環,每天都有bug要改。

二、開發環境

  1. JDK 1.8
  2. Idea + Maven
  3. 涉及工程三個,可以通過關注公眾號bugstack蟲洞棧,回復源碼下載獲取(打開獲取的鏈接,找到序號18)
工程 描述
itstack-demo-design-8-01 使用一坨代碼實現業務需求
itstack-demo-design-8-02 通過設計模式優化改造代碼,產生對比性從而學習

三、組合模式介紹

從上圖可以看到這有點像螺絲和螺母,通過一堆的鏈接組織出一棵結構樹。而這種通過把相似對象(也可以稱作是方法)組合成一組可被調用的結構樹對象的設計思路叫做組合模式。

這種設計方式可以讓你的服務組節點進行自由組合對外提供服務,例如你有三個原子校驗功能(A:身份證B:銀行卡C:手機號)服務並對外提供調用使用。有些調用方需要使用AB組合,有些調用方需要使用到CBA組合,還有一些可能只使用三者中的一個。那麼這個時候你就可以使用組合模式進行構建服務,對於不同類型的調用方配置不同的組織關係樹,而這個樹結構你可以配置到數據庫中也可以不斷的通過圖形界面來控制樹結構。

所以不同的設計模式用在恰當好處的場景可以讓代碼邏輯非常清晰並易於擴展,同時也可以減少團隊新增人員對項目的學習成本。

四、案例場景模擬

以上是一個非常簡化版的營銷規則決策樹,根據性別年齡來發放不同類型的優惠券,來刺激消費起到精準用戶促活的目的。

雖然一部分小夥伴可能並沒有開發過營銷場景,但你可能時時刻刻的被營銷着。比如你去經常瀏覽男性喜歡的机械鍵盤、筆記本電腦、汽車裝飾等等,那麼久給你推薦此類的優惠券刺激你消費。那麼如果你購物不多,或者錢不在自己手裡。那麼你是否打過車,有一段時間經常有小夥伴喊,為什麼同樣的距離他就10元,我就15元呢?其實這些都是被營銷的案例,一般對於不常使用軟件的小夥伴,經常會進行稍微大力度的促活,增加用戶粘性。

那麼在這裏我們就模擬一個類似的決策場景,體現出組合模式在其中起到的重要性。另外,組合模式不只是可以運用於規則決策樹,還可以做服務包裝將不同的接口進行組合配置,對外提供服務能力,減少開發成本。

五、用一坨坨代碼實現

這裏我們舉一個關於ifelse誕生的例子,介紹小姐姐與程序員‍‍之間的故事導致的事故

日期 需求 緊急程度 程序員(話外音)
星期一.早上 猿哥哥,老闆說要搞一下營銷拉拉量,給男生女生髮不同的優惠券,促活消費。 很緊急,下班就要 行吧,也不難,加下判斷就上線
星期二.下午 小哥哥,咱們上線后非常好。要讓咱們按照年輕、中年、成年,不同年齡加下判斷,準確刺激消費。 超緊急,明天就要 也不難,加就加吧
星期三.晚上 喂,小哥哥!睡了嗎!老闆說咱們這次活動很成功,可以不可以在細分下,把單身、結婚、有娃的都加上不同判斷。這樣更能刺激用戶消費。 賊緊急,最快上線。 已經意識到ifelse越來越多了
星期四.凌晨 哇!小哥哥你們太棒了,上的真快。嘻嘻!有個小請求,需要調整下年齡段,因為現在學生處對象的都比較早,有對象的更容易買某某某東西。要改下值!辛苦辛苦! 老闆,在等着呢! 一大片的值要修改,哎!這麼多ifelse
星期五.半夜 歪歪喂!巴巴,壞了,怎麼發的優惠券不對了,有客訴了,很多女生都來投訴。你快看看。老闆,他… (一頭汗),哎,值粘錯位置了! 終究還是一個人扛下了所有

1. 工程結構

itstack-demo-design-8-01
└── src
    └── main
        └── java
            └── org.itstack.demo.design
                └── EngineController.java
  • 公司里要都是這樣的程序員絕對省下不少成本,根本不要搭建微服務,一個工程搞定所有業務!
  • 但千萬不要這麼干!酒肉穿腸過,佛祖心中留。世人若學我,如同進魔道。

2. 代碼實現

public class EngineController {

    private Logger logger = LoggerFactory.getLogger(EngineController.class);

    public String process(final String userId, final String userSex, final int userAge) {

        logger.info("ifelse實現方式判斷用戶結果。userId:{} userSex:{} userAge:{}", userId, userSex, userAge);

        if ("man".equals(userSex)) {
            if (userAge < 25) {
                return "果實A";
            }

            if (userAge >= 25) {
                return "果實B";
            }
        }

        if ("woman".equals(userSex)) {
            if (userAge < 25) {
                return "果實C";
            }

            if (userAge >= 25) {
                return "果實D";
            }
        }

        return null;

    }

}
  • 除了我們說的擴展性和每次的維護以外,這樣的代碼實現起來是最快的。而且從樣子來看也很適合新人理解。
  • 但是我勸你別寫,寫這樣代碼不是被扣績效就是被開除。

3. 測試驗證

3.1 編寫測試類

@Test
public void test_EngineController() {
    EngineController engineController = new EngineController();
    String process = engineController.process("Oli09pLkdjh", "man", 29);
    logger.info("測試結果:{}", process);
}
  • 這裏我們模擬了一個用戶ID,並傳輸性別:man、年齡:29,我們的預期結果是:果實B。實際對應業務就是給頭禿的程序員發一張枸杞優惠券

3.2 測試結果

22:10:12.891 [main] INFO  o.i.demo.design.EngineController - ifelse實現方式判斷用戶結果。userId:Oli09pLkdjh userSex:man userAge:29
22:10:12.898 [main] INFO  org.itstack.demo.design.test.ApiTest - 測試結果:果實B

Process finished with exit code 0
  • 從測試結果上看我們的程序運行正常並且符合預期,只不過實現上並不是我們推薦的。接下來我們會採用組合模式來優化這部分代碼。

六、組合模式重構代碼

接下來使用組合模式來進行代碼優化,也算是一次很小的重構。

接下來的重構部分代碼改動量相對來說會比較大一些,為了讓我們可以把不同類型的決策節點和最終的果實組裝成一棵可被運行的決策樹,需要做適配設計和工廠方法調用,具體會體現在定義接口以及抽象類和初始化配置決策節點(性別年齡)上。建議這部分代碼多閱讀幾次,最好實踐下。

1. 工程結構

itstack-demo-design-8-02
└── src
    ├── main
    │   └── java
    │      └── org.itstack.demo.design.domain
    │          ├── model
    │          │   ├── aggregates
    │          │   │   └── TreeRich.java
    │          │   └── vo
    │          │       ├── EngineResult.java
    │          │       ├── TreeNode.java
    │          │       ├── TreeNodeLink.java    
    │          │       └── TreeRoot.java	
    │          └── service
    │              ├── engine
    │              │   ├── impl	
    │              │   │   └── TreeEngineHandle.java	   
    │              │   ├── EngineBase.java 
    │              │   ├── EngineConfig.java       
    │              │   └── IEngine.java	
    │              └── logic
    │                  ├── impl	
    │                  │   ├── LogicFilter.java	 
    │                  │   └── LogicFilter.java	    
    │                  └── LogicFilter.java	
    └── test
         └── java
             └── org.itstack.demo.design.test
                 └── ApiTest.java

組合模式模型結構

  • 首先可以看下黑色框框的模擬指導樹結構;11112111112121122,這是一組樹結構的ID,並由節點串聯組合出一棵關係樹樹。

  • 接下來是類圖部分,左側是從LogicFilter開始定義適配的決策過濾器,BaseLogic是對接口的實現,提供最基本的通用方法。UserAgeFilterUserGenerFilter,是兩個具體的實現類用於判斷年齡性別

  • 最後則是對這顆可以被組織出來的決策樹,進行執行的引擎。同樣定義了引擎接口和基礎的配置,在配置裏面設定了需要的模式決策節點。

    • static {
           logicFilterMap = new ConcurrentHashMap<>();
           logicFilterMap.put("userAge", new UserAgeFilter());
           logicFilterMap.put("userGender", new UserGenderFilter());
      }
      
  • 接下來會對每一個類進行細緻的講解,如果感覺沒有讀懂一定是我作者的表述不夠清晰,可以添加我的微信(fustack)與我交流。

2. 代碼實現

2.1 基礎對象

包路徑 介紹
model.aggregates TreeRich 聚合對象,包含組織樹信息
model.vo EngineResult 決策返回對象信息
model.vo TreeNode 樹節點;子恭弘=叶 恭弘節點、果實節點
model.vo TreeNodeLink 樹節點鏈接鏈路
model.vo TreeRoot 樹根信息
  • 以上這部分簡單介紹,不包含邏輯只是各項必要屬性的get/set,整個源代碼可以通過關注微信公眾號:bugstack蟲洞棧,回復源碼下載打開鏈接獲取。

2.2 樹節點邏輯過濾器接口

public interface LogicFilter {

    /**
     * 邏輯決策器
     *
     * @param matterValue          決策值
     * @param treeNodeLineInfoList 決策節點
     * @return 下一個節點Id
     */
    Long filter(String matterValue, List<TreeNodeLink> treeNodeLineInfoList);

    /**
     * 獲取決策值
     *
     * @param decisionMatter 決策物料
     * @return 決策值
     */
    String matterValue(Long treeId, String userId, Map<String, String> decisionMatter);

}
  • 這一部分定義了適配的通用接口,邏輯決策器、獲取決策值,讓每一個提供決策能力的節點都必須實現此接口,保證統一性。

2.3 決策抽象類提供基礎服務

public abstract class BaseLogic implements LogicFilter {

    @Override
    public Long filter(String matterValue, List<TreeNodeLink> treeNodeLinkList) {
        for (TreeNodeLink nodeLine : treeNodeLinkList) {
            if (decisionLogic(matterValue, nodeLine)) return nodeLine.getNodeIdTo();
        }
        return 0L;
    }

    @Override
    public abstract String matterValue(Long treeId, String userId, Map<String, String> decisionMatter);

    private boolean decisionLogic(String matterValue, TreeNodeLink nodeLink) {
        switch (nodeLink.getRuleLimitType()) {
            case 1:
                return matterValue.equals(nodeLink.getRuleLimitValue());
            case 2:
                return Double.parseDouble(matterValue) > Double.parseDouble(nodeLink.getRuleLimitValue());
            case 3:
                return Double.parseDouble(matterValue) < Double.parseDouble(nodeLink.getRuleLimitValue());
            case 4:
                return Double.parseDouble(matterValue) <= Double.parseDouble(nodeLink.getRuleLimitValue());
            case 5:
                return Double.parseDouble(matterValue) >= Double.parseDouble(nodeLink.getRuleLimitValue());
            default:
                return false;
        }
    }

}
  • 在抽象方法中實現了接口方法,同時定義了基本的決策方法;1、2、3、4、5等於、小於、大於、小於等於、大於等於的判斷邏輯。
  • 同時定義了抽象方法,讓每一個實現接口的類都必須按照規則提供決策值,這個決策值用於做邏輯比對。

2.4 樹節點邏輯實現類

年齡節點

public class UserAgeFilter extends BaseLogic {

    @Override
    public String matterValue(Long treeId, String userId, Map<String, String> decisionMatter) {
        return decisionMatter.get("age");
    }

}

性別節點

public class UserGenderFilter extends BaseLogic {

    @Override
    public String matterValue(Long treeId, String userId, Map<String, String> decisionMatter) {
        return decisionMatter.get("gender");
    }

}
  • 以上兩個決策邏輯的節點獲取值的方式都非常簡單,只是獲取用戶的入參即可。實際的業務開發可以從數據庫、RPC接口、緩存運算等各種方式獲取。

2.5 決策引擎接口定義

public interface IEngine {

    EngineResult process(final Long treeId, final String userId, TreeRich treeRich, final Map<String, String> decisionMatter);

}
  • 對於使用方來說也同樣需要定義統一的接口操作,這樣的好處非常方便後續拓展出不同類型的決策引擎,也就是可以建造不同的決策工廠。

2.6 決策節點配置

public class EngineConfig {

    static Map<String, LogicFilter> logicFilterMap;

    static {
        logicFilterMap = new ConcurrentHashMap<>();
        logicFilterMap.put("userAge", new UserAgeFilter());
        logicFilterMap.put("userGender", new UserGenderFilter());
    }

    public Map<String, LogicFilter> getLogicFilterMap() {
        return logicFilterMap;
    }

    public void setLogicFilterMap(Map<String, LogicFilter> logicFilterMap) {
        this.logicFilterMap = logicFilterMap;
    }

}
  • 在這裏將可提供服務的決策節點配置到map結構中,對於這樣的map結構可以抽取到數據庫中,那麼就可以非常方便的管理。

2.7 基礎決策引擎功能

public abstract class EngineBase extends EngineConfig implements IEngine {

    private Logger logger = LoggerFactory.getLogger(EngineBase.class);

    @Override
    public abstract EngineResult process(Long treeId, String userId, TreeRich treeRich, Map<String, String> decisionMatter);

    protected TreeNode engineDecisionMaker(TreeRich treeRich, Long treeId, String userId, Map<String, String> decisionMatter) {
        TreeRoot treeRoot = treeRich.getTreeRoot();
        Map<Long, TreeNode> treeNodeMap = treeRich.getTreeNodeMap();
        // 規則樹根ID
        Long rootNodeId = treeRoot.getTreeRootNodeId();
        TreeNode treeNodeInfo = treeNodeMap.get(rootNodeId);
        //節點類型[NodeType];1子恭弘=叶 恭弘、2果實
        while (treeNodeInfo.getNodeType().equals(1)) {
            String ruleKey = treeNodeInfo.getRuleKey();
            LogicFilter logicFilter = logicFilterMap.get(ruleKey);
            String matterValue = logicFilter.matterValue(treeId, userId, decisionMatter);
            Long nextNode = logicFilter.filter(matterValue, treeNodeInfo.getTreeNodeLinkList());
            treeNodeInfo = treeNodeMap.get(nextNode);
            logger.info("決策樹引擎=>{} userId:{} treeId:{} treeNode:{} ruleKey:{} matterValue:{}", treeRoot.getTreeName(), userId, treeId, treeNodeInfo.getTreeNodeId(), ruleKey, matterValue);
        }
        return treeNodeInfo;
    }

}
  • 這裏主要提供決策樹流程的處理過程,有點像通過鏈路的關係(性別年齡)在二叉樹中尋找果實節點的過程。
  • 同時提供一個抽象方法,執行決策流程的方法供外部去做具體的實現。

2.8 決策引擎的實現

public class TreeEngineHandle extends EngineBase {

    @Override
    public EngineResult process(Long treeId, String userId, TreeRich treeRich, Map<String, String> decisionMatter) {
        // 決策流程
        TreeNode treeNode = engineDecisionMaker(treeRich, treeId, userId, decisionMatter);
        // 決策結果
        return new EngineResult(userId, treeId, treeNode.getTreeNodeId(), treeNode.getNodeValue());
    }

}
  • 這裏對於決策引擎的實現就非常簡單了,通過傳遞進來的必要信息;決策樹信息、決策物料值,來做具體的樹形結構決策。

3. 測試驗證

3.1 組裝樹關係

@Before
public void init() {
    // 節點:1
    TreeNode treeNode_01 = new TreeNode();
    treeNode_01.setTreeId(10001L);
    treeNode_01.setTreeNodeId(1L);
    treeNode_01.setNodeType(1);
    treeNode_01.setNodeValue(null);
    treeNode_01.setRuleKey("userGender");
    treeNode_01.setRuleDesc("用戶性別[男/女]");
    // 鏈接:1->11
    TreeNodeLink treeNodeLink_11 = new TreeNodeLink();
    treeNodeLink_11.setNodeIdFrom(1L);
    treeNodeLink_11.setNodeIdTo(11L);
    treeNodeLink_11.setRuleLimitType(1);
    treeNodeLink_11.setRuleLimitValue("man");
    // 鏈接:1->12
    TreeNodeLink treeNodeLink_12 = new TreeNodeLink();
    treeNodeLink_12.setNodeIdTo(1L);
    treeNodeLink_12.setNodeIdTo(12L);
    treeNodeLink_12.setRuleLimitType(1);
    treeNodeLink_12.setRuleLimitValue("woman");
    List<TreeNodeLink> treeNodeLinkList_1 = new ArrayList<>();
    treeNodeLinkList_1.add(treeNodeLink_11);
    treeNodeLinkList_1.add(treeNodeLink_12);
    treeNode_01.setTreeNodeLinkList(treeNodeLinkList_1);
    // 節點:11
    TreeNode treeNode_11 = new TreeNode();
    treeNode_11.setTreeId(10001L);
    treeNode_11.setTreeNodeId(11L);
    treeNode_11.setNodeType(1);
    treeNode_11.setNodeValue(null);
    treeNode_11.setRuleKey("userAge");
    treeNode_11.setRuleDesc("用戶年齡");
    // 鏈接:11->111
    TreeNodeLink treeNodeLink_111 = new TreeNodeLink();
    treeNodeLink_111.setNodeIdFrom(11L);
    treeNodeLink_111.setNodeIdTo(111L);
    treeNodeLink_111.setRuleLimitType(3);
    treeNodeLink_111.setRuleLimitValue("25");
    // 鏈接:11->112
    TreeNodeLink treeNodeLink_112 = new TreeNodeLink();
    treeNodeLink_112.setNodeIdFrom(11L);
    treeNodeLink_112.setNodeIdTo(112L);
    treeNodeLink_112.setRuleLimitType(5);
    treeNodeLink_112.setRuleLimitValue("25");
    List<TreeNodeLink> treeNodeLinkList_11 = new ArrayList<>();
    treeNodeLinkList_11.add(treeNodeLink_111);
    treeNodeLinkList_11.add(treeNodeLink_112);
    treeNode_11.setTreeNodeLinkList(treeNodeLinkList_11);
    // 節點:12
    TreeNode treeNode_12 = new TreeNode();
    treeNode_12.setTreeId(10001L);
    treeNode_12.setTreeNodeId(12L);
    treeNode_12.setNodeType(1);
    treeNode_12.setNodeValue(null);
    treeNode_12.setRuleKey("userAge");
    treeNode_12.setRuleDesc("用戶年齡");
    // 鏈接:12->121
    TreeNodeLink treeNodeLink_121 = new TreeNodeLink();
    treeNodeLink_121.setNodeIdFrom(12L);
    treeNodeLink_121.setNodeIdTo(121L);
    treeNodeLink_121.setRuleLimitType(3);
    treeNodeLink_121.setRuleLimitValue("25");
    // 鏈接:12->122
    TreeNodeLink treeNodeLink_122 = new TreeNodeLink();
    treeNodeLink_122.setNodeIdFrom(12L);
    treeNodeLink_122.setNodeIdTo(122L);
    treeNodeLink_122.setRuleLimitType(5);
    treeNodeLink_122.setRuleLimitValue("25");
    List<TreeNodeLink> treeNodeLinkList_12 = new ArrayList<>();
    treeNodeLinkList_12.add(treeNodeLink_121);
    treeNodeLinkList_12.add(treeNodeLink_122);
    treeNode_12.setTreeNodeLinkList(treeNodeLinkList_12);
    // 節點:111
    TreeNode treeNode_111 = new TreeNode();
    treeNode_111.setTreeId(10001L);
    treeNode_111.setTreeNodeId(111L);
    treeNode_111.setNodeType(2);
    treeNode_111.setNodeValue("果實A");
    // 節點:112
    TreeNode treeNode_112 = new TreeNode();
    treeNode_112.setTreeId(10001L);
    treeNode_112.setTreeNodeId(112L);
    treeNode_112.setNodeType(2);
    treeNode_112.setNodeValue("果實B");
    // 節點:121
    TreeNode treeNode_121 = new TreeNode();
    treeNode_121.setTreeId(10001L);
    treeNode_121.setTreeNodeId(121L);
    treeNode_121.setNodeType(2);
    treeNode_121.setNodeValue("果實C");
    // 節點:122
    TreeNode treeNode_122 = new TreeNode();
    treeNode_122.setTreeId(10001L);
    treeNode_122.setTreeNodeId(122L);
    treeNode_122.setNodeType(2);
    treeNode_122.setNodeValue("果實D");
    // 樹根
    TreeRoot treeRoot = new TreeRoot();
    treeRoot.setTreeId(10001L);
    treeRoot.setTreeRootNodeId(1L);
    treeRoot.setTreeName("規則決策樹");
    Map<Long, TreeNode> treeNodeMap = new HashMap<>();
    treeNodeMap.put(1L, treeNode_01);
    treeNodeMap.put(11L, treeNode_11);
    treeNodeMap.put(12L, treeNode_12);
    treeNodeMap.put(111L, treeNode_111);
    treeNodeMap.put(112L, treeNode_112);
    treeNodeMap.put(121L, treeNode_121);
    treeNodeMap.put(122L, treeNode_122);
    treeRich = new TreeRich(treeRoot, treeNodeMap);
}

  • 重要,這一部分是組合模式非常重要的使用,在我們已經建造好的決策樹關係下,可以創建出樹的各個節點,以及對節點間使用鏈路進行串聯。
  • 及時後續你需要做任何業務的擴展都可以在裏面添加相應的節點,並做動態化的配置。
  • 關於這部分手動組合的方式可以提取到數據庫中,那麼也就可以擴展到圖形界面的進行配置操作。

3.2 編寫測試類

@Test
public void test_tree() {
    logger.info("決策樹組合結構信息:\r\n" + JSON.toJSONString(treeRich));
    
    IEngine treeEngineHandle = new TreeEngineHandle();
    Map<String, String> decisionMatter = new HashMap<>();
    decisionMatter.put("gender", "man");
    decisionMatter.put("age", "29");
    
    EngineResult result = treeEngineHandle.process(10001L, "Oli09pLkdjh", treeRich, decisionMatter);
    
    logger.info("測試結果:{}", JSON.toJSONString(result));
}
  • 在這裏提供了調用的通過組織模式創建出來的流程決策樹,調用的時候傳入了決策樹的ID,那麼如果是業務開發中就可以方便的解耦決策樹與業務的綁定關係,按需傳入決策樹ID即可。
  • 此外入參我們還提供了需要處理;(man)、年齡(29歲),的參數信息。

3.3 測試結果

23:35:05.711 [main] INFO  o.i.d.d.d.service.engine.EngineBase - 決策樹引擎=>規則決策樹 userId:Oli09pLkdjh treeId:10001 treeNode:11 ruleKey:userGender matterValue:man
23:35:05.712 [main] INFO  o.i.d.d.d.service.engine.EngineBase - 決策樹引擎=>規則決策樹 userId:Oli09pLkdjh treeId:10001 treeNode:112 ruleKey:userAge matterValue:29
23:35:05.715 [main] INFO  org.itstack.demo.design.test.ApiTest - 測試結果:{"nodeId":112,"nodeValue":"果實B","success":true,"treeId":10001,"userId":"Oli09pLkdjh"}

Process finished with exit code 0
  • 從測試結果上看這與我們使用ifelse是一樣的,但是目前這與的組合模式設計下,就非常方便後續的拓展和修改。
  • 整體的組織關係框架以及調用決策流程已經搭建完成,如果閱讀到此沒有完全理解,可以下載代碼觀察結構並運行調試。

七、總結

  • 從以上的決策樹場景來看,組合模式的主要解決的是一系列簡單邏輯節點或者擴展的複雜邏輯節點在不同結構的組織下,對於外部的調用是仍然可以非常簡單的。
  • 這部分設計模式保證了開閉原則,無需更改模型結構你就可以提供新的邏輯節點的使用並配合組織出新的關係樹。但如果是一些功能差異化非常大的接口進行包裝就會變得比較困難,但也不是不能很好的處理,只不過需要做一些適配和特定化的開發。
  • 很多時候因為你的極致追求和稍有倔強的工匠精神,即使在面對同樣的業務需求,你能完成出最好的代碼結構和最易於擴展的技術架構。不要被遠不能給你指導提升能力的影響到放棄自己的追求!

八、推薦閱讀

  • 1. 重學 Java 設計模式:實戰工廠方法模式(多種類型商品發獎場景)
  • 2. 重學 Java 設計模式:實戰抽象工廠模式(替換Redis雙集群升級場景)
  • 3. 重學 Java 設計模式:實戰建造者模式(裝修物料組合套餐選配場景)
  • 4. 重學 Java 設計模式:實戰原型模式(多套試每人題目和答案亂序場景)
  • 5. 重學 Java 設計模式:實戰橋接模式(多支付渠道「微信、支付寶」與多支付模式「刷臉、指紋」場景)

本站聲明:網站內容來源於博客園,如有侵權,請聯繫我們,我們將及時處理

【其他文章推薦】

※教你寫出一流的銷售文案?

※廣告預算用在刀口上,台北網頁設計公司幫您達到更多曝光效益

※回頭車貨運收費標準

※別再煩惱如何寫文案,掌握八大原則!

※超省錢租車方案

※產品缺大量曝光嗎?你需要的是一流包裝設計!

※推薦台中搬家公司優質服務,可到府估價

分類
發燒車訊

熬夜之作:一文帶你了解Cat分佈式監控

Cat 是什麼?

CAT(Central Application Tracking)是基於 Java 開發的實時應用監控平台,包括實時應用監控,業務監控。

CAT 作為服務端項目基礎組件,提供了 Java, C/C++, Node.js, Python, Go 等多語言客戶端,已經在美團點評的基礎架構中間件框架(MVC 框架,RPC 框架,數據庫框架,緩存框架等,消息隊列,配置系統等)深度集成,為美團點評各業務線提供系統豐富的性能指標、健康狀況、實時告警等。

CAT 很大的優勢是它是一個實時系統,CAT 大部分系統是分鐘級統計,但是從數據生成到服務端處理結束是秒級別,秒級定義是 48 分鐘 40 秒,基本上看到 48 分鐘 38 秒數據,整體報表的統計粒度是分鐘級;第二個優勢,監控數據是全量統計,客戶端預計算;鏈路數據是採樣計算。

Github: https://github.com/dianping/cat

Cat 功能亮點

  • 實時處理:信息的價值會隨時間銳減,尤其是事故處理過程中
  • 全量數據:全量採集指標數據,便於深度分析故障案例
  • 高可用:故障的還原與問題定位,需要高可用監控來支撐
  • 故障容忍:故障不影響業務正常運轉、對業務透明
  • 高吞吐:海量監控數據的收集,需要高吞吐能力做保證
  • 可擴展:支持分佈式、跨 IDC 部署,橫向擴展的監控系統

為什麼要用 Cat?

場景一:用戶反饋 App 無法下單,用戶反饋無法支付,用戶反饋商品無法搜索等問題

場景一的問題在於當系統出現問題后,第一反饋的總是用戶。我們需要做的是什麼,是在出問題后研發第一時間知曉,而不是讓用戶來告訴我們出問題了。

Cat 可以出故障后提供秒級別的異常告警機制,不用再等用戶來反饋問題了。

場景二:出故障后如何快速定位問題

一般傳統的方式當出現問題后,我們就會去服務器上看看服務是否還存活。如果存活就會看看日誌是否有異常信息。

在 Cat 後台的首頁,會展示各個系統的運行情況,如果有異常,則會大片飄紅,非常明顯。最直接的方式還是直接查看 Problem 報表,這裡會為我們展示直接的異常信息,快速定位問題。

場景三:用戶反饋訂單列表要 10 幾秒才展示,用戶反饋下單一直在轉圈圈

場景三屬於優化相關,對於研發來說,優化是一個長期的過程,沒有最好只有更好。優化除了需要有對應的方案,最重要的是要對症下藥。

所謂的對症下藥也就是在優化之前,你得先知道哪裡比較慢。RPC 調用慢?數據庫查詢慢?緩存更新慢?

Cat 可以提供詳細的性能數據,95 線,99 線等。更細粒度的就是可以看到某個請求或者某個業務方法的所有耗時邏輯,前提是你做了埋點操作。

Cat 報表

Cat 目前有五種報表,每種都有特定的應用場景,下面我們來具體聊聊這些報表的作用。

Transaction 報表

適用於監控一段代碼運行情況,比如:運行次數、QPS、錯誤次數、失敗率、響應時間統計(平均影響時間、Tp 分位值)等等場景。

埋點方式:

public void shopService() {
    Transaction transaction = Cat.newTransaction("ShopService", "Service");
    try {
        service();
        transaction.setStatus(Transaction.SUCCESS);
    } catch (Exception e) {
        transaction.setStatus(e); // catch 到異常,設置狀態,代表此請求失敗
        Cat.logError(e); // 將異常上報到cat上
        // 也可以選擇向上拋出: throw e;
    } finally {
        transaction.complete();
    }
}

可以在基礎框架中對 Rpc, 數據庫等框架進行埋點,這樣就可以通過 Cat 來監控這些組件了。

業務中需要埋點也可以使用 Cat 的 Transaction,比如下單,支付等核心功能,通常我們對 URL 進行埋點就可以了,也就包含了具體的業務流程。

Event 報表

適用於監控一段代碼運行次數,比如記錄程序中一個事件記錄了多少次,錯誤了多少次。Event 報表的整體結構與 Transaction 報表幾乎一樣,只缺少響應時間的統計。

埋點方式:

 Cat.logEvent("Func", "Func1");

Problem 報表

Problem 記錄整個項目在運行過程中出現的問題,包括一些異常、錯誤、訪問較長的行為。

如果有人反饋你的接口報 500 錯誤了,你進 Cat 后就直接可以去 Problem 報表了,錯誤信息就在 Problem 中。

Problem 報表不需要手動埋點,我們只需要在項目中集成日誌的 LogAppender 就可以將所有 error 異常記錄,下面的段落中會講解如何整合 LogAppender。

Heartbeat 報表

Heartbeat 報表是 CAT 客戶端,以一分鐘為周期,定期向服務端彙報當前運行時候的一些狀態。

系統指標有系統的負載信息,內存使用情況,磁盤使用情況等。

JVM 指標有 GC 相關信息,線程相關信息。

Business 報表

Business 報表對應着業務指標,比如訂單指標。與 Transaction、Event、Problem 不同,Business 更偏向於宏觀上的指標,另外三者偏向於微觀代碼的執行情況。

這個報表我也沒怎麼用過,用的多的還是前面幾個。

Cat 在 Kitty Cloud 中的應用

Kitty Cloud 的基礎組件是 Kitty,Kitty 裏面對需要的一些框架都進行了一層包裝,比如擴展,增加 Cat 埋點之類的功能。

Cat 的集成

Kitty 中對 Cat 封裝了一層,在使用的時候直接依賴 kitty-spring-cloud-starter-cat 即可整合 Cat 到項目中。

 <dependency>
       <groupId>com.cxytiandi</groupId>
       <artifactId>kitty-spring-cloud-starter-cat</artifactId>
       <version>Kitty Version</version>
 </dependency>

然後在 application 配置文件中配置 Cat 的服務端地址信息,多個英文逗號分隔:

cat.servers=47.105.66.210

在項目的 resources 目錄下創建 META-INF 目錄,然後在 META-INF 中創建 app.properties 文件配置 app.name。此名稱是在 Cat 後台显示的應用名

app.name=kitty-cloud-comment-provider

最後需要配置一下 Cat 的 LogAppender,這樣應用在記錄 error 級別的日誌時,Cat 可以及時進行異常告警操作。

在 logback.xml 增加下面的配置:

 <appender name="CatAppender" class="com.dianping.cat.logback.CatLogbackAppender"></appender>
 <root level="INFO">
     <appender-ref ref="CatAppender" />
 </root>

更詳細的內容請移步 Cat 的 Github 主頁進行查看。

MVC 框架埋點

基於 Spring Boot 做 Web 應用開發,我們最常用到的一個 Starter 包就是 spring-boot-starter-web。

如果你使用了 Kitty 來構建微服務的框架,那麼就不再需要直接依賴 spring-boot-starter-web。而是需要依賴 Kitty 中的 kitty-spring-cloud-starter-web。

kitty-spring-cloud-starter-web 在 spring-boot-starter-web 的基礎上進行了封裝,會對請求的 Url 進行 Cat 埋點,會對一些通用信息進行接收透傳,會對 RestTemplate 的調用進行 Cat 埋點。

在項目中依賴 kitty-spring-cloud-starter-web:

<dependency>
      <groupId>com.cxytiandi</groupId>
      <artifactId>kitty-spring-cloud-starter-web</artifactId>
      <version>Kitty Version</version>
</dependency>

啟動項目,然後訪問你的 REST API。可以在 Cat 的控制台看到 URL 的監控信息。

點擊 URL 進去可以看到具體的 URL 信息。

再進一步可以看到整個 URL 的信息,比如數據庫的查詢,緩存的操作,Http 的調用等。後端同學在優化性能的時候就直接從 URL 下手可以將整個請求的鏈路耗時的情況都分析清楚。

Mybatis 埋點

Kitty 中 Mybatis 是用的 Mybatis Plus, 主要是對數據庫相關操作的 SQL 進行了 Cat 埋點,可以很方便的查看 SQL 的耗時情況。

依賴 kitty-spring-cloud-starter-mybatis:

 <dependency>
     <groupId>com.cxytiandi</groupId>
     <artifactId>kitty-spring-cloud-starter-mybatis</artifactId>
     <version>Kitty Version</version>
 </dependency>

其他的使用方式還是跟 Mybatis Plus 一樣,具體參考 Mybatis Plus 文檔:https://mp.baomidou.com

只要涉及到數據庫的操作,都會在 Cat 中進行數據的展示。

點擊 SQL 進去還可以看到是哪個 Mapper 的操作。

再進一步就可以看到具體的 SQL 語句和消耗的時間。

有了這些數據,後端研發同學就可以對相關的 SQL 進行優化了。

Redis 埋點

如果需要使用 Spring Data Redis 的話,直接集成 kitty-spring-cloud-starter-redis 就可以,kitty-spring-cloud-starter-redis 中對 Redis 的命令進行了埋點,可以在 Cat 上直觀的查看對應的命令和消耗的時間。

添加對應的 Maven 依賴:

<dependency>
     <groupId>com.cxytiandi</groupId>
     <artifactId>kitty-spring-cloud-starter-redis</artifactId>
     <version>Kitty Version</version>
 </dependency>

直接使用 StringRedisTemplate:

@Autowired
private StringRedisTemplate stringRedisTemplate;
 
stringRedisTemplate.opsForValue().set("name", "yinjihuan");

Cat 中可以看到 Redis 信息。

點擊 Redis 進去可以看到有哪些命令。

再進去可以看到命令的詳細信息,比如操作的 key 和消耗的時間。

MongoDB 埋點

Kitty 中對 Spring Data Mongodb 做了封裝,只對 MongoTemplate 做了埋點。使用時需要依賴 kitty-spring-cloud-starter-mongodb。

<dependency>
    <groupId>com.cxytiandi</groupId>
    <artifactId>kitty-spring-cloud-starter-mongodb</artifactId>
    <version>Kitty Version</version>
</dependency>

在發生 Mongo 的操作后,Cat 上就可以看到相關的數據了。

點進去就可以看到是 MongoTemplate 的哪個方法發生了調用。

再進一步就可以看到具體的 Mongo 參數和消耗的時間。

還有 Dubbo, Feign,Jetcache,ElasticSearch 等框架的埋點就不細講了,感興趣的可以移步 Github 查看代碼。

Cat 使用小技巧

埋點工具類

如果要對業務方法進行監控,我們一般會用 Transaction 功能,將業務邏輯包含在 Transaction 裏面,就能監控這個業務的耗時信息。

埋點的方式也是通過 Cat.newTransaction 來進行,具體可以參考上面 Transaction 介紹時給出的埋點示列。

像這種埋點的方式最好是有一個統一的工具類去做,將埋點的細節封裝起來。

public class CatTransactionManager {
    public static <T> T newTransaction(Supplier<T> function, String type, String name, Map<String, Object> data) {
        Transaction transaction = Cat.newTransaction(type, name);
        if (data != null && !data.isEmpty()) {
            data.forEach(transaction::addData);
        }
        try {
            T result = function.get();
            transaction.setStatus(Message.SUCCESS);
            return result;
        } catch (Exception e) {
            Cat.logError(e);
            if (e.getMessage() != null) {
                Cat.logEvent(type + "_" + name + "_Error", e.getMessage());
            }
            transaction.setStatus(e);
            throw e;
        } finally {
            transaction.complete();
        }
    }
}

工具類使用:

public SearchResponse search(SearchRequest searchRequest, RequestOptions options) {
    Map<String, Object> catData = new HashMap<>(1);
    catData.put(ElasticSearchConstant.SEARCH_REQUEST, searchRequest.toString());
    return CatTransactionManager.newTransaction(() -> {
        try {
            return restHighLevelClient.search(searchRequest, options);
        } catch (IOException e) {
            throw new RuntimeException(e);
        }
    }, ElasticSearchConstant.ES_CAT_TYPE, ElasticSearchConstant.SEARCH, catData);
}

通過使用工具類,不再需要每個監控的地方都是設置 Transaction 是否 complete,是否成功這些信息了。

註解埋點

為了讓 Transaction 使用更方便,我們可以自定義註解來做這個事情。比如需要監控下單,支付等核心業務方法,那麼就可以使用自定義的 Transaction 註解加在方法上,然後通過 AOP 去統一做監控。

定義註解:

@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.METHOD, ElementType.TYPE})
public @interface CatTransaction {
    /**
     * 類型, 默認為Method
     * @return
     */
    String type() default "";
    /**
     * 名稱, 默認為類名.方法名
     * @return
     */
    String name() default "";
    /**
     * 是否保存參數信息到Cat
     * @return
     */
    boolean isSaveParamToCat() default true;
}

定義切面:

@Aspect
public class CatTransactionAspect {
    @Around("@annotation(catTransaction)")
    public Object aroundAdvice(ProceedingJoinPoint joinpoint, CatTransaction catTransaction) throws Throwable {
        String type = catTransaction.type();
        if (StringUtils.isEmpty(type)){
            type = CatConstantsExt.METHOD;
        }
        String name = catTransaction.name();
        if (StringUtils.isEmpty(name)){
            name = joinpoint.getSignature().getDeclaringType().getSimpleName() + "." + joinpoint.getSignature().getName();
        }
        Map<String, Object> data = new HashMap<>(1);
        if (catTransaction.isSaveParamToCat()) {
            Object[] args = joinpoint.getArgs();
            if (args != null) {
                data.put("params", JsonUtils.toJson(args));
            }
        }
        return CatTransactionManager.newTransaction(() -> {
            try {
                return joinpoint.proceed();
            } catch (Throwable throwable) {
               throw new RuntimeException(throwable);
            }
        }, type, name, data);
    }

}

註解使用:

@CatTransaction
@Override
public Page<ArticleIndexBO> searchArticleIndex(ArticleIndexSearchParam param) {
}

你可能關心的幾個問題

Cat 能做鏈路跟蹤嗎?

Cat 主要是一個實時監控系統,並不是一個標準的全鏈路系統,主要是 Cat 的 logview 在異步線程等等一些場景下,不太合適,Cat 本身模型並不適合這個。Cat 的 Github 上有說明:在美團點評內部,有 mtrace 專門做全鏈路分析。

但是如果在 Mvc,遠程調用等這些框架中做好了數據的無縫傳輸,Cat 也可以充當一個鏈路跟蹤的系統,基本的場景足夠了。

Cat 也可以構建遠程消息樹,可以看到請求經過了哪些服務,每個服務的耗時等信息。只不過服務之間的依賴關係圖在 Cat 中沒有。

下圖請求從網關進行請求轉發到 articles 上面,然後 articles 裏面調用了 users 的接口。

Cat 跟 Skywalking 哪個好用?

Skywalking 也是一款非常優秀的 APM 框架,我還沒用過,不過看過一些文檔,功能點挺全的 ,界面也挺好看。最大的優勢是不用像 Cat 一樣需要埋點,使用字節碼增強的方式來對應用進行監控。

之所以列出這個小標題是因為如果大家還沒有用的話肯定會糾結要選擇落地哪個去做監控。我個人認為這兩個都可以,可以自己先弄個簡單的版本體驗體驗,結合你想要的功能點來評估落地哪個。

用 Cat 的話最好有一套基礎框架,在基礎框架中埋好點,這樣才能在 Cat 中詳細的显示各種信息來幫助我們快速定位問題和優化性能。

感興趣的 Star 下唄:https://github.com/yinjihuan/kitty-cloud

本站聲明:網站內容來源於博客園,如有侵權,請聯繫我們,我們將及時處理

【其他文章推薦】

網頁設計最專業,超強功能平台可客製化

※自行創業缺乏曝光? 網頁設計幫您第一時間規劃公司的形象門面

※回頭車貨運收費標準

※推薦評價好的iphone維修中心

※教你寫出一流的銷售文案?

台中搬家公司教你幾個打包小技巧,輕鬆整理裝箱!

台中搬家公司費用怎麼算?

分類
發燒車訊

Cypress系列(14)- 環境變量詳解

如果想從頭學起Cypress,可以看下面的系列文章哦

https://www.cnblogs.com/poloyy/category/1768839.html

 

前言

  • 環境變量,其實就是根據環境的變化,變量會有不同的值
  • 比如最常見的:開發環境、測試環境、生產環境的 URL 肯定不一樣,我們可以根據不同的環境選擇不同的環境變量
  • 這就是為什麼我們要學習環境變量的原因

 

環境變量在以下情況會很有用

  • 不同開發人員,對應的值也可能不同
  • 不同環境下的值是不同的,入:dev、test、prod
  • 某些值會頻繁變化,而且高度動態
  • 環境變量很容易會更改,尤其是在持續集成(CI)中運行時

 

栗子

不要在測試中進行硬編碼(寫死,常量),需要改的時候需要動代碼,比如:

cy.request('https://api.acme.corp') // 這將在其他環境中無法使

 

使用環境變量后

cy.request(Cypress.env('EXTERNAL_API')) // 指向動態環境變量

 

當不同環境運行時,如果需要訪問不同的 URL 我們只需要改環境變量即可了,而不用動到代碼

 

baseUrl

  • 前面我們說到可以通過環境變量設置測試套件訪問的 URL,這是其中一種方式
  • 而 Cypress 早就替我們想好了如何解決這問題,可以通過配置 baseUrl 來取代環境變量的方式
  • 當你配置了 baseUrl ,測試套件中的 cy.visit() 、 cy.request() 都會自動以 baseUrl 的值作為前綴
  • 並且,當你需要訪問某些網址或者發起接口請求時,在代碼中就可以不用再指定請求的 host 或者 url 了

 

如何配置 baseUrl

  • 細心的小夥伴已經知道,前面我講 Cypress 全局配置項的時候已經提到過 baseUrl 了
  • 只需要在 cypress.json 文件進行配置就可以啦,如下

 

代碼中調用

cy.visit("")

// 錯誤寫法  cy.visit()

記住調用 visit 或 request 時,再怎麼樣也要傳個空字符串 “” ,不能啥都不填哦

 

通過環境變量來覆蓋 baseUrl

即使配置了 baseUrl ,我們也可以通過環境變量來覆蓋它

CYPRESS_baseUrl=https://staging.app.com cypress run

 

設置環境變量

一共有五種方式

  1. 在 cypress.json 文件中設置
  2. 創建一個 cypress.env.json 文件
  3. 導出為 CYPRESS_* 
  4. 在 CLI 中傳遞為 –env (命令行運行中添加)
  5. 在插件中設置一個環境變量

 

—————————–>>>>>>>>>>>>>>>>>>> 點擊右側目錄即可跳轉

 

最常見的做法

  • 使用一種策略進行本地開發,但在 CI(持續集成)中運行時使用另一種策略
  • 在測試運行時,可以使用 Cypress.env() 訪問環境變量的值

 

cypress.json 中設置

在 cypress.json 的 env 鍵下設置的任何 key:value 都是環境變量

 

cypress.json 代碼

 

測試文件代碼

// 打印所有環境變量
Cypress.env()

// 打印某個環境變量的值
Cypress.env("foor")

 

測試結果

 

優缺點

優點 缺點
適用於需要源碼託管(git)並在所有計算機保持相同的值 只適用於在所有計算機上應該有相同的值

 

創建 cypress.env.json 文件

該文件的描述

  • 可以創建自己的 cypress.env.json 文件,Cypress 將會自動檢查它
  • 並且裏面的值會覆蓋 cypress.json 中重名的環境變量
  • 它創建在 cypress.json 同級目錄下

 

用這個文件有啥用

如果將cypress.env.json 添加到.gitgnore文件中,那麼文件中的值對於每個開發人員的計算機都是不同的

 

cypress.env.json 文件代碼

 

測試文件代碼

 

測試結果

在 cypress.json 中也有一個 key 的環境變量,所以在 cypress.env.json 的 key 的值覆蓋了它的值

 

優缺點

優點 缺點
專用文件,只存放環境變量 需要單獨多處理一個新的文件
可以從其他構建過程中生成此文件 可能會過度干預 1 或 2 個環境變量
不同計算機的環境變量可能不同  

 

CYPRESS_*

重點!

  • 計算機中任何以 CYPRESS_ 或 cypress_ 開頭的環境變量都會自動被 Cypress 識別出來
  • 會直接覆蓋 cypress.json 和 cypress.env.json 文件中重名的環境變量
  • Cypress在添加環境變量時,會自動去掉 CYPRESS_ 前綴

 

在系統添加環境變量

 

總結

  • 我測試過發現並沒有生效,也不知道為啥,需要後面再研究研究
  • 其實並不推薦這種寫法,當環境變量無效時,再改起來就很麻煩了

 

–env

重點!

  • 可以通過命令行將環境變量作為命令行參數傳進來
  • 它的優先級最高,會覆蓋其他地方設置的重名環境變量 
  • 可以為 cypress open 或 cypress run 添加 –env 參數

 

cmd 命令

在 Cypress 安裝目錄下,cmd敲

yarn cypress:open --env host=poloyy.com,key=命令行參數環境變量

yarn cypress:run --env host=poloyy.com,key=命令行參數環境變量

 

測試文件代碼

 

測試結果

 

優缺點

優點 缺點
不需要對文件或配置項進行任何更改 使用 –env 並不友好
簡單明了的設置環境變量  
優先級最高,覆蓋其他形式設置的環境變量  

本站聲明:網站內容來源於博客園,如有侵權,請聯繫我們,我們將及時處理

【其他文章推薦】

※產品缺大量曝光嗎?你需要的是一流包裝設計!

※自行創業缺乏曝光? 網頁設計幫您第一時間規劃公司的形象門面

※回頭車貨運收費標準

※推薦評價好的iphone維修中心

※超省錢租車方案

台中搬家遵守搬運三大原則,讓您的家具不再被破壞!

※推薦台中搬家公司優質服務,可到府估價

分類
發燒車訊

上位機開發之西門子PLC-S7通信實踐

 

寫在前面:

就目前而言,在中國的工控市場上,西門子仍然佔了很大的份額,因此對於上位機開發而言,經常會存在需要與西門子PLC進行通信的情況。然後對於西門子PLC來說,通信方式有很多,下面簡單列舉一下:

 

(1)  S7通信:PLC作為服務器,上位機作為客戶端

(2)  開放式TCP通信:PLC作為服務器,上位機作為客戶端

(3)  開放式TCP通信:PLC作為客戶端,上位機作為服務器

(4)   ModbusTCP通信:PLC作為服務器,上位機作為客戶端

(5)   ModbusTCP通信:PLC作為客戶端,上位機作為服務器

(6)   ModbusRTU通信:PLC作為主站,上位機作為從站

(7)   ModbusRTU通信:PLC作為從站,上位機作為主站

(8)   Simatic Net OPCDA通信

(9)   Simatic Net OPCUA通信

(10) KepServer OPCDA通信

(11) KepServer OPCUA通信

由於篇幅有限,這次僅以西門子S7通信為例,說明下如何基於S7通信協議實現與西門子PLC之間的通信。

 

1. PLC軟件安裝及配置

目前西門子PLC主要使用的軟件包括STEP7-MicroWIN SMART、SIMATIC STEP7以及TIA Portal。TIA Portal已經完全兼容STEP 7,因此以後應該是STEP 7-MicroWIN SMART作為小型PLC的編程軟件,TIA作為中大型PLC的編程軟件,這裏主要以博途為例進行說明:

如果大家需要軟件的,可以關注左上方公眾號,或者搜索微信公眾號:dotNet工控上位機,關注后發送關鍵詞:200SMART編程軟件即可獲取STEP 7-MicroWIN SMART V2.5軟件,發送關鍵詞:博圖V15即可獲取TIA V15.1編程軟件。

軟件安裝完成后,PLC的配置也很簡單,如果大家手頭沒有實際的PLC,也可以通過仿真的方式搭建PLC環境,具體可以參考文章:戳↓

基於S7-PLCSIM Advanced搭建S7通信仿真環境

 

無論使用何種方式,以下兩個地方需要進行配置一下:

PLC配置一:需要將PLC的允許來自遠程對象的PUT/GET通信訪問勾選。

PLC配置二:對於DB塊的訪問,需要取消勾選優化訪問。

 

2. 通信平台測試

(1)完成以上配置后,就可以通過自己開發的喜科堂通信測試平台軟件進行測試,導航欄中選擇西門子PLC,然後輸入正確的IP地址,在CPU類型中選擇自己的CPU類型:

圖表 1新閣通信測試平台

 

(1)輸入完成之後,點擊建立連接,建立連接之後,日誌欄會有連接成功提示。

(2)在讀寫測試中,輸入相應的變量地址及變量類型,即可實現相關變量的通信讀寫及測試。

圖表 2新閣通信測試平台測試

 

3. 項目級別應用

通信測試平台僅僅只是用於測試通信是否正常,實現正常的單變量數據讀取和寫入。但是如果是項目級別開發,還需要有一套更完善的通信架構,這裏我採用的是自主開發的上位機通信配置一體化軟件(簡稱CMS配置軟件)。

(1)通過PLC設備右擊選擇西門子PLC,在打開的窗體中設置好相關參數:

設備名稱:根據實際情況填寫(無特殊字符即可)

設備備註:根據實際情況填寫(無特殊字符即可)

IP地址:根據實際PLC的IP地址填寫

機架號、插槽號:根據實際PLC的情況填寫

PLC類型:根據實際PLC的情況填寫

連接超時:PLC連接時的超時時間,默認是2000ms

容錯次數:判斷連接故障的容錯次數,默認為1,即表示某次讀取出錯,即判斷連接故障,根據實際情況可以適當放大

重連周期:通信過程中,出現斷線時,重連的周期,默認是5000ms

圖表 3創建PLC

 

(2)在PLC設備下,右擊添加通信組,根據需要填寫相應的存儲區及起始地址及長度:

圖表 4添加通信組

 

(3)通信組下面,根據實際情況配置相應的變量,輸入開始地址及變量類型即可,變量地址會自動變換,這裏可以輸入比例係數及偏移量,用於做線性變換使用:

圖表 5添加變量

 

(4)對於變量配置,左下角會有一個報警歸檔配置,主要用於配置該變量的報警類型、歸檔方式及設定限制:

圖表 6報警歸檔配置

 

(5)完成上述配置后,可以點擊保存配置,再點擊啟動運行,即可實現實時通信:

圖表 7實時通信

 

(6)同時可以通過另存為,存儲為一個配置文件的形式,再基於配置dll,可以通過快速方式實現配置解析及通信數據解析,這樣整個項目的通信框架即可搭建完成。

 

4. 整體總結

本文主要針對西門子PLC的通信配置、通信配置及項目應用做了較為詳細的描述,希望可以給一些想要去開發西門子PLC項目的同學一些幫助。這樣的一套思路同樣適用於其他品牌的PLC,我們旨在節約大家開發項目中在通信方面的時間,而將更多的精力投放在項目工藝開發中。

 

寫在後面:

很多小夥伴想要CMSPro軟件來進行學習,因此綜合考慮,現提供CMSPro軟件試用版供大家學習使用,試用版功能方面可能會存在部分刪減,但是可以滿足大部分小夥伴的學習需求,目前僅針對本公眾號粉絲,具體獲取方式,通過關注本公眾號:dotNet工控上位機,發送關鍵詞:CMSPro試用,即可獲取。同時我們的通信庫xktComm.dll也提供試用版,大家可以通過nuget搜索xktComm,安裝使用,最後祝大家工作生活愉快。

 

更多精彩內容:

(點擊即可閱讀)

西門子PLC上位機軟件開發歷程

上位機C#通過TCP/IP和庫卡機器人通訊

上位機C#通過OPCUA和西門子PLC通信

基於C#實現本地數據上傳至雲服務器

上位機開發之三菱Q系列PLC通信實踐

 

本站聲明:網站內容來源於博客園,如有侵權,請聯繫我們,我們將及時處理

【其他文章推薦】

※回頭車貨運收費標準

※產品缺大量曝光嗎?你需要的是一流包裝設計!

※自行創業缺乏曝光? 網頁設計幫您第一時間規劃公司的形象門面

※推薦評價好的iphone維修中心

※教你寫出一流的銷售文案?

台中搬家公司教你幾個打包小技巧,輕鬆整理裝箱!

台中搬家遵守搬運三大原則,讓您的家具不再被破壞!

分類
發燒車訊

新冠病毒是就業殺手!美太陽能產業五年的就業成長歸零

摘錄自2020年5月19日自由時報報導

路透報導,新冠病毒的大流行,導致美國太陽能產業五年來的就業成長歸零。

產業領袖周一(18日)表示,在疫情期間,美國砍掉了6萬5000多個太陽能產業工作,這個以遏止氣候變遷為目標領域五年來的就業成長倒轉回原點。美國貿易公會組織「太陽能產業協會」表示,因商店關門和居家避疫令而無法安裝太陽能設施,導致工作急劇流失。

美國整個經濟體4月流失逾2050萬個工作,是自經濟大蕭條以來受薪工作最急劇的下滑。這波裁員後預期人力剩下18萬8000多名員工,類似此產業在2014年的規模,與之前預測的30萬2000名員工相距甚遠。

能源議題
再生能源
能源轉型
國際新聞
美國
武漢肺炎
太陽能
低碳產業
就業
疫情看氣候與能源

本站聲明:網站內容來源環境資訊中心https://e-info.org.tw/,如有侵權,請聯繫我們,我們將及時處理

【其他文章推薦】

網頁設計一頭霧水該從何著手呢? 台北網頁設計公司幫您輕鬆架站!

※台北網頁設計公司全省服務真心推薦

※想知道最厲害的網頁設計公司"嚨底家"!

※推薦評價好的iphone維修中心

網頁設計最專業,超強功能平台可客製化

※別再煩惱如何寫文案,掌握八大原則!

分類
發燒車訊

購置稅減半優惠政策真的即將結束?結果可能會是這樣

所以,汽車工業協會已經向有關部門提出建議,希望繼續推行1。6L及以下乘用車購置稅優惠政策,而且,對於是否繼續延續這個優惠政策,有關部門正在進行商討。我們從某兩家車企的高層了解到,他們認為接下來小排量汽車的購置稅按7。

馬上到元旦了,近期有購車打算的小夥伴們都是一個比一個忙的,看車、試駕、砍價、簽單一氣呵成,好像現在不買車以後就買不到了一樣。

昨天表哥還跟我打電話說車子已經定好了,就等提車了,我當時就說你這個做事這麼墨跡的人怎麼這麼快就做出決定了。

他回答:“不買不行啊,到了月底購置稅減半政策就結束了,銷售也一個勁的催我,害怕多花幾千塊錢就趕緊訂車了。”

再聯想到11月份汽車銷量又創新高,所以可以看出來,這一政策對打算購車之人的影響還是非常大的。

2015年9月29日,國務院召開會議,為促進小排量汽車發展,決定對購買1.6L及以下排量的乘用車實行車輛購置稅減半的優惠政策,即10%稅率降為按5%稅率徵收。簡單的理解就是相當於在之前的購置稅基礎之上打五折。實行日期為2015年10月1日-2016年12月31日。

眼看着這個五折優惠政策即將到期,所以大家都是想在這個政策結束之前趕緊下單買車。

當時實行這個優惠政策的目的也是為了擴大內需,促進消費。這個政策確實極大的促進了汽車銷量的增長,達到了預期效果,今年前十個月國內汽車銷量同比增長約為13%,過去五年的年均增長率大約為6.4%,增速明顯。

但是這個政策更像是催熟劑,使得需求提前被釋放出來了。如果優惠政策戛然而止,那麼對車市的打擊很可能是十分巨大的。所以,汽車工業協會已經向有關部門提出建議,希望繼續推行1.6L及以下乘用車購置稅優惠政策,而且,對於是否繼續延續這個優惠政策,有關部門正在進行商討。

我們從某兩家車企的高層了解到,他們認為接下來小排量汽車的購置稅按7.5%的稅率徵收的可能性更大。簡單的理解也就是購置稅打7.5折。因為7.5%的稅率在我國早有先例,所以小編認為這個傳言還是比較可信的。

雖然沒有了5折的優惠,但是可以打7.5折還是不錯的麽,畢竟也可以省下一部分錢了。車型的價格越高,省的錢就越多。不過7.5%並不是可靠消息,只是稅率很有可能是7.5%,具體是什麼情況還是要看政策。

所以如果車價較貴,還是建議儘快下手比較好,因為誰也無法準確預測政策將會往哪裡走。本站聲明:網站內容來源於http://www.auto6s.com/,如有侵權,請聯繫我們,我們將及時處理

【其他文章推薦】

網頁設計一頭霧水該從何著手呢? 台北網頁設計公司幫您輕鬆架站!

※台北網頁設計公司全省服務真心推薦

※想知道最厲害的網頁設計公司"嚨底家"!

※推薦評價好的iphone維修中心

網頁設計最專業,超強功能平台可客製化

※別再煩惱如何寫文案,掌握八大原則!

分類
發燒車訊

保值率有啥用?看了才知道這些十幾萬的國產SUV這麼值錢

無論是從消費者口中還是市場銷量,無一不證明傳祺GS4擁有過人的產品實力。首先是原創度極高的外觀設計,年輕運動的造型風格拉攏了多少追求個性的年輕人,在整個自主SUV市場可是非常搶眼。與此同時,傳祺GS4的空間表現、動力總成,均在同級中有不錯的產品賣點,加上實惠的購車、養車成本。

二手車市場中流傳着這麼一句話:新車落地打八折。不管你是豪車還是家用車,只要是用過一段時間,先在新車的售價基礎上砍掉20%再談。因此,很多精打細算的消費者也會在準備購車前,未雨綢繆,盡量多方打聽某款車型的保值率,以便未來出手時能預測虧損區間。但是,保值率是什麼,你真的了解嗎?



長安CS75

新車價:9.28-15.88萬

保值率:57.70%

長安作為一家自主品牌的老大哥,旗下的CS系列均有口皆碑。其中,最受好評的還是長安CS75,沉穩大氣的外觀形象,雖然看起來中庸點,但卻非常符合國人骨子里對於傳統SUV的想象,寬敞又舒適的大空間也為長安CS75贏得一片掌聲。

除此之外,同級別非常有誠意的配置,成熟可靠的強勁動力,綜合起來真的不比合資SUV差。一句話下來,但凡SUV應該具備的特質,長安CS75都一個不漏地呈現給消費者,這也難怪長安CS75在二手車市場依然那麼搶手,保值率居高不下自然是常態。更何況,近期長安CS75 1.5T車型的上市,無疑進一步降低了購車門檻和成本。



傳祺GS4

新車價:9.98-15.38萬元

保值率:58.90%

上市不足兩年的傳祺GS4,一舉成為當今最受關注度的自主SUV之一。無論是從消費者口中還是市場銷量,無一不證明傳祺GS4擁有過人的產品實力。首先是原創度極高的外觀設計,年輕運動的造型風格拉攏了多少追求個性的年輕人,在整個自主SUV市場可是非常搶眼。

與此同時,傳祺GS4的空間表現、動力總成,均在同級中有不錯的產品賣點,加上實惠的購車、養車成本。這樣既有顏值又有實力,性價比還不低的傳祺GS4,又豈有賣不好的道理。另外,步入今年以來,傳祺GS4的市場表現愈加穩定,月銷兩三萬輛穩居SUV市場亞軍,以上有利因素都造就了傳祺GS4較高的保值率。值得一提的是,隨着傳祺GS4 6AT變速箱車型的上市,相信消費者對於其購買熱情只會有增無減。



哈弗H6

新車價:8.88-16.28萬

保值率:56.40%

提到自主SUV的保值率,是無法如何是避不開神車哈弗H6的。事實上,關於哈弗H6保值率高的原因也不難理解,只要了解它一個月賣出去的天量,再算一算哈弗H6在神州大地的保有量規模,你會發現其它的國產車簡直是渣渣的存在。

當然,哈弗H6的熱銷不是憑空而來,其一是抓對了早期自主SUV市場的空白期,可以算是第一個吃大螃蟹的自主SUV,為哈弗H6今時今日的頭把交椅奠定了良好的群眾基礎。其二是哈弗H6基本上完全針對國人的審美點出發打造,無論外觀還是空間,都讓人非常容易接受。此外,哈弗H6多到數不清的車型(紅藍標,運動升級版…),一定程度上幫助了哈弗H6滿足不同需求的消費者,再加上終端方面的讓利促銷。哈哈哈,如此才會延續哈弗H6越賣越好的節奏,哪怕是在二手車市場也是一車難求。

總結

以上推薦的幾款國產SUV,只是在保值率較高中比較有代表性的幾位。事實上,隨着國產SUV整體品質的提升,以及市場保有量的不斷擴大,許多有顏值有實力的國產SUV也越來越值錢,告別了以往二市車市場無人問津的尷尬。相比之下,國產轎車仍需加把勁啊。本站聲明:網站內容來源於http://www.auto6s.com/,如有侵權,請聯繫我們,我們將及時處理

【其他文章推薦】

網頁設計一頭霧水該從何著手呢? 台北網頁設計公司幫您輕鬆架站!

網頁設計公司推薦不同的風格,搶佔消費者視覺第一線

※想知道購買電動車哪裡補助最多?台中電動車補助資訊懶人包彙整

南投搬家公司費用,距離,噸數怎麼算?達人教你簡易估價知識!

※教你寫出一流的銷售文案?

※超省錢租車方案

※回頭車貨運收費標準

分類
發燒車訊

同樣是傳祺的SUV,為啥大家都愛GS4不買GS5?真是GS5不好嗎?

那就是如今傳祺非常紅火的GS4車型了,它較低的起步價更加的親民。而時尚的外觀與內飾設計配合一樣出色的底盤,因此它熱銷就不難想象了。就現在來看,傳祺GS5真的不值得購買么。今天就來好好對比下,看看它們哪款比較出色。

國產SUV在最近這幾年發力非常猛,大有超過日韓SUV的氣勢。究其原因是自主品牌經過多年的技術積累,同時堅持正向研發配合大量的資金投入的基礎下的成果。而且如今的自主廠商也漸漸的意識到只有原創的設計,符合大眾的審美的車型才能在激烈競爭的市場上生存下來。

回顧前幾年的SUV市場,當然合資品牌還沒有很多SUV車型推出。因此當然也有一些自主品牌看中了合資品牌沒有佔領了15萬這個區間的緊湊SUV市場,於是推出一些實力還算比較出色的SUV車型來提升自家的品牌形象,其中傳祺GS5 Super就是最突出的例子。它的外觀原創度很高,整體的底盤調校與做工都非常出色,無奈是當時由於定價的偏高與消費者對自主SUV的認可還沒有現在這麼深,於是它的市場表現一直不算很出色。

雖然GS5 Super有點出師不利,但它前期推出市場還是有不少的消費者購買的,它出色的做工與良好的底盤響應都使得傳祺在市場上有了不錯的口碑,於是就有了後來的事情了。那就是如今傳祺非常紅火的GS4車型了,它較低的起步價更加的親民。而時尚的外觀與內飾設計配合一樣出色的底盤,因此它熱銷就不難想象了。就現在來看,傳祺GS5真的不值得購買么?今天就來好好對比下,看看它們哪款比較出色?

綜上所述,目前的GS5 Super相比GS4除了動力有一定的優勢之外,它的價格仍然是一大硬傷,當然如今消費者對傳祺的認可已經很不錯了,就像其最新推出的GS8一樣,只要品質足夠的出色,熱銷真的不是問題。

那麼未來GS5 Super要怎麼才能重新煥發青春呢?在看來,如今最亟待改變的就是換裝全新的家族式面孔,與傳祺的其他車型有良好的延續,同時內飾的造型與配置都要相應的提升,這樣只要保持之前的行駛質感的話再把價格降低一些。這樣的話,相信GS5 Super要熱賣真的不成問題。本站聲明:網站內容來源於http://www.auto6s.com/,如有侵權,請聯繫我們,我們將及時處理

【其他文章推薦】

網頁設計一頭霧水該從何著手呢? 台北網頁設計公司幫您輕鬆架站!

網頁設計公司推薦不同的風格,搶佔消費者視覺第一線

※Google地圖已可更新顯示潭子電動車充電站設置地點!!

※廣告預算用在刀口上,台北網頁設計公司幫您達到更多曝光效益

※別再煩惱如何寫文案,掌握八大原則!

網頁設計最專業,超強功能平台可客製化

※回頭車貨運收費標準

分類
發燒車訊

傳奇新生 換裝1.5L發動機的新捷達有什麼變化

電影結束后,一汽-大眾汽車有限公司董事、總經理張丕傑上前發表感言致辭,喚醒“國民情捷”,引發情感共鳴宣布《平凡成就偉大》這部舞台劇正式演出,男主角為了尋找父親以前賣了的老捷達,從而找上尋車的道路。外觀更時尚年輕化外觀方面,我們可以看到像邁騰、朗逸那樣大眾最新的橫貫式前臉設計語言,從正前臉看去,X造型的設計元素使車頭帶來很強的視覺衝擊,彰顯了新捷達的力量和運動感。

問起老一輩人最熟悉的車型,他們肯定會異口同聲的說老三樣:捷達、富康、桑塔納,也許連他們自己都難以想象,捷達已經和國人的緣分已經走過了25個年頭,捷達承載的不只是一代人甚至是兩代人的美好回憶,卓越的品質使其締造了一個又一個傳奇。

12月7日晚,備受關注的新捷達在廣州亞運城綜合體育館正式上市,提供了1.4L、1.5L、1.4T三種動力共9款車型,售價區間在7.99-13.49萬元之間。

從場外觀看,可見捷達的發布會充滿着時尚的元素,藍色主題的燈光,音樂節般的場景布局,是否意味着這次新捷達更時尚年輕化的設計路線呢?

走進發布會的場館內,彷彿走進了一個汽車的博物館,每一輛車子都是時間的見證者,25年來所推出的所有捷達車型湧現眼前。

一部簡述捷達的國民情節大電影拉開了發布會的帷幕,描述捷達在中國汽車市場的25年傳奇經歷,多項技術的創新、時代的發展、團隊的奮鬥都歷歷在目。

電影結束后,一汽-大眾汽車有限公司董事、總經理張丕傑上前發表感言致辭,喚醒“國民情捷”,引發情感共鳴宣布《平凡成就偉大》這部舞台劇正式演出,男主角為了尋找父親以前賣了的老捷達,從而找上尋車的道路。

外觀

更時尚年輕化

外觀方面,我們可以看到像邁騰、朗逸那樣大眾最新的橫貫式前臉設計語言,從正前臉看去,X造型的設計元素使車頭帶來很強的視覺衝擊,彰顯了新捷達的力量和運動感。

側面的簡潔流暢額腰線從前翼子板一直延伸至車尾,使整個車身顯得更加修長,搭配硬朗有力的曲面輪廓,給人很緊緻飽滿的感覺,全新造型的輪轂,更突顯了整車的運動氛圍,尾部的設計頗有種小寶來的味道。

內飾

全面升級

沿用了大眾最新的內飾設計風格,全新設計的組合儀錶盤和平底式方向盤,金屬質感的裝飾板和烤漆面板的合理搭配,營造了很好的質感,更讓車內充滿時尚運動的氣息,配置方面根據車型不同,配備有自動空調、感應雨刷、自動頭燈、座椅加熱、定速巡航、胎壓監測等非常豐富的配置。

動力

全新的動力總成

全新的EA211系列1.5L自然吸氣發動機取代了之前的1.6L發動機,最大功率110馬力,最大扭矩150牛米,擁有雙VVT正時調節系統,持續可調式機油泵等先進技術,降低油耗的同時,排放和噪音也改善了不少,與之匹配的是5擋手動或者6擋自動變速器,而1.4L和1.4T發動機得以保留。

總結:本次上的新捷達外觀上有了較大的變化,更家族化的外觀使大眾的車型更加統一了,更時尚年輕的外觀造型適應的人群也更廣了,內飾的配置、造型、用料都有很大的調節和提升,同時新增的1.5L發動機無論是動力和燃油經濟性都有所提升,可以說捷達的競爭力提升了很多。本站聲明:網站內容來源於http://www.auto6s.com/,如有侵權,請聯繫我們,我們將及時處理

【其他文章推薦】

網頁設計公司推薦不同的風格,搶佔消費者視覺第一線

※廣告預算用在刀口上,台北網頁設計公司幫您達到更多曝光效益

※自行創業缺乏曝光? 網頁設計幫您第一時間規劃公司的形象門面

南投搬家公司費用需注意的眉眉角角,別等搬了再說!

※教你寫出一流的銷售文案?

※回頭車貨運收費標準

※別再煩惱如何寫文案,掌握八大原則!

分類
發燒車訊

把寶馬開到0.9升油耗!我是如何做到的?

而想全程用電機行駛,就需要切換到Max eDrive模式。電量不足的情況下,你可以選擇Save Battery模式來存電。不得不說,在純電模式下,X1 25Le表現得非常安靜,加上因為完全靠電機驅動,峰值扭矩能全數輸出,所以起步階段非常迅速,完全不用擔心起步遲緩的問題。

寶馬長期以來都是以直六+后驅的形象示人,但自從推出了UKL前置前驅平台之後,就出現了一些批評聲音,部分車迷表示接受不了。而寶馬的全新X1正是這個平台的產物,但不同的是,國產X1除了加長軸距外,還推出了以後驅為主的插電式混合動力車型。

在行駛的過程中,X1 25Le會根據不同的eDrive模式切換不同的驅動形式。在Auto eDrive模式中,ECU會自行在純電、油電混合和純發動機驅動三個模式中自動切換。而想全程用電機行駛,就需要切換到Max eDrive模式。電量不足的情況下,你可以選擇Save Battery模式來存電。

而在自動模式下,只要轉速超過2000轉,發動機就會自動介入,驅動前輪行駛。但要說有什麼不同,還真的沒必要去感受,因為整套系統運行得非常順暢。

另外X1 25Le所使用的電池凈容量為10.7kWh,理論巡航里程是60km左右,但極限情況下可達80km。通過220V的電源接口充電,充滿電只需3.7小時。

本站聲明:網站內容來源於http://www.auto6s.com/,如有侵權,請聯繫我們,我們將及時處理

【其他文章推薦】

※自行創業缺乏曝光? 網頁設計幫您第一時間規劃公司的形象門面

網頁設計一頭霧水該從何著手呢? 台北網頁設計公司幫您輕鬆架站!

※想知道最厲害的網頁設計公司"嚨底家"!

※別再煩惱如何寫文案,掌握八大原則!

※產品缺大量曝光嗎?你需要的是一流包裝設計!

※回頭車貨運收費標準

台中搬家公司費用怎麼算?