WinUI 3 試玩報告

1. 什麼是 WinUI 3

在微軟 Build 2020 開發者大會上,WinUI 團隊宣布可公開預覽的 WinUI 3 Preview 1,它讓開發人員可以在 Win32 中使用 WinUI。WinUI 3 Preview 1 包含新的 VisualStudio 項目模板,可以創建面向 .NET 5 的 C# 和 C++/Win32 項目。從技術上講,WinUI 3 將 UWP 的 XAML、Composition 和 Input 層分離,並通過NuGet將它們獨立分發給針對Windows 10 版本 1803 及更高版本的 Win32 應用。

WinUI 3 適用於 Win32 和 UWP,這篇文章主要討論 Win32 的情況。

2. 理解 WinUI 3

以前我們總是抱怨 WPF 多年都不提供新的主題,不提供新的控件,性能又沒提升。現在微軟索性把什麼都是新的 WinUI 3 提供給桌面開發,沒 WPF 什麼事了。

簡單來說,UWP 的開發體驗不好(關於這個話題真是一言難盡),而且出了 Bug 還必須等待下半年的 Windows 更新進行修復,但微軟的開發人員專心給 UWP 的 UI 層加各種功能;.NET Core 更新很快,但很少人有興趣有動力給陳舊的 WPF 的 UI 層進行大幅度的改進。於是 WinUI 將 UWP 的 UI 層從 Windows SDK 的其它部分分離,並將從 Windows 轉移到 Nuget。現在建一個 C++ 或 C#(.NET 5) 程序,再從 Nuget 上裝個 WinUI 3 的包套個 UI 層,一個基於 Fluent Design,觸摸友好,性能無與倫比的應用程序就誕生了。

上圖列舉了 WinUI 3 和其他平台對比的部分特性,除此之外 WinUI 3 還有很多好處,例如開源、更新更快、更新不與系統版本綁定等,更詳細的內容還是看微軟自己怎麼宣傳吧:

WinUI – The modern native UI platform of Windows.

不過要用上 WinUI 3 還要等一年半載。下面是微軟給出的發布路線圖,目前我們也只能用 Preview 版嘗嘗鮮。

3. 試玩WinUI 3

要試玩 WinUI 3 首先要有 Windows 10 1803 以上版本的電腦(WinUI 3 最低支持1803),然後還需要使用 Visual Studio 2019 16.7 以上版本(目前只能安裝預覽版)。安裝 Visual Studio 時要把以下工作負載全都選上:

  • .NET 桌面開發
  • 通用 Windows 平台開發
  • 使用 C++ 的桌面開發
  • 適用於通用 Windows 平台負載的 C++(V142) 通用 Windows 平台工具可選組件

當然 .NET 5.0 也要裝上。

然後在 https://aka.ms/winui3/previewdownload 下載並安裝 WinUI 3 Project Templates 擴展,這樣才可以在 Visual Studio 創建 WinUI 的項目。

可選 C++ 或 C# ,這裏我選擇了 C# 的“Blank App, Packaged
(WinUI in Desktop)”項目,並選擇了對應的 Windows 平台:

項目創建后 Visual Studio 生成了兩個項目。第一個包含應用的代碼,代碼結構基本和 UWP 一樣,只是少了用於打包應用的 Package.appxmanifest 和一些圖片。從依賴項里可以看到項目已經安裝了 Microsoft.WinUI 3 的包。從項目屬性里可以看到這就是個 .NET 5 的項目。

Visual Studio 生成的第二個項目是一個 Windows 應用程序打包項目,該項目經配置后可將應用生成為適合部署的 MSIX 程序包。 也就是說 UWP 項目中用於打包的部分被獨立出來了。這個項目還應該是解決方案的啟動項目。運行這個項目后創建的應用會添加到開始菜單中,這點也和UWP一樣。

到這裏為止都和預期的一樣,我之後還嘗試了將 UWP 應用移植到 WinUI ,基本上只需要將 Windows.UI 命名空間改為 Microsoft.UI就可以了,XAML 和 C# 代碼完全不用變。只可惜目前 WinUI 還很簡陋,Win2D、Community Toolkit 等微軟自己發布的 UWP 包都還沒有 WinUI 版本。而且沒有設計視圖,XAML 視圖也沒有智能感知,現在想要用 WinUI做些什麼有趣的項目會很困難。不過從目前的移植難度上來看,將來正式發布后應該可以完整地將 UWP 的 UI 的開發經驗運用在 WinUI 上。

4. 和 WPF 及 UWP 進行對比

既然 WinUI 3 開發模式和 WPF 及 UWP 都很像,我當然對它們之間的對比很感興趣。

命名

首先說說命名,“WinUI” 光這個名字就 Win 了。 “UWP” 太高雅,我敢打賭國內有些 UWP 的開發(例如我)都不能好好地把 UWP 的全稱拼出來;“WPF” 好些,但 WPF 的含義也讓人很疑惑。而 Windows UI 簡稱 WinUI ,意義和發音都很清晰明確。不過這三個都比很多人都不會讀的 “Xamarin” 強多了。

可是有了 WinUI 3 ,就會有人問“那 WinUI 2 呢?”WinUI 2是一個 UWP 的控件庫,當然的只能用在 UWP 上。這就很尷尬了,WinUI 的 3 和 2 根本不是同一個概念,實在很容易讓人混淆,說不定以後會把後綴的 3 去掉(我這篇文章就常常懶得理寫這個3)。而且 UWP 中代碼的命名空間以 Windows.UI 開頭,在 WinUI 3 中則 Microsoft.UI ,按着 Office 365 改名為 Microsoft 365、Bind Ads 改名為 Microsoft Advertising 這些經驗,該不會以後 WinUI 可能改名為 Microsoft UI ,簡稱 MiUI 吧?

權限

權限方面是 WinUI 的一個亮點,因為它本質上就是個 Win32 程序,可以放開手腳隨便來。相對的 UWP 有很嚴格的權限限制,開發 UWP 時常常會感到綁手綁腳。例如下面這段代碼,大部分 WPF 開發者都難以想象只是最小化 UWP 程序而已,它就不能好好運行了:

int count = 0;
DispatcherTimer timer = new DispatcherTimer();
timer.Interval = TimeSpan.FromSeconds(1);
timer.Tick += (s, e) =>
  {
      myButton.Content = count++;
  };
timer.Start();

UWP 的生命周期如上圖,當 UWP 處於 background 運行或 suspended 狀態時應用基本處於暫停狀態,也也不會處理UI功能。我明白這是 UWP 為了省電、安全等原因才這樣設計,但對開發人員來說真的太不方便。而 WinUI 應用基本上就是個 Win32 應用,目前看來不會有這些坑。

開發體驗

說起開發體驗,WPF 好歹還算正常,Visual Studio 的設計視圖運行正常,編譯起來也快。UWP 編譯很慢,設計視圖經常出問題,Blend 也時好時壞把設計師都氣跑了。就算完全按着官方的文檔完成一個 UWP App,甚至一行代碼都不改,發布到商店后還是有可能崩潰。而對於應用商店,真是千言萬語彙聚成一個草花頭。

現在 WinUI 的 XAML 視圖連智能感知都沒有,也沒有設計視圖,實在沒法談開發體驗。很難猜測正式發布的時候會怎麼樣,希望至少和WPF保持一致吧。

性能

WPF 總是給人“慢”的印象,除了因為在它剛出來的時候(10年前)電腦性能不夠導致留下了刻板印象,還有一個主要原因是:它真的很慢。

UWP 的 XAML 有很優秀的性能表現,除此之外為了照顧已經不存在的 Windows Phone 的貧弱性能,很多控件模版都經過精心設計並大幅簡化。

為了驗證 WinUI 的性能我寫了下面這些代碼,然後分別移植到 WPF .Net Framework 4.8、WPF .NET 5、UWP、WinUI(WPF 和 UWP/WinUI 的代碼稍微有一點不同):

for (int i = 0; i < 50; i++)
{
    var rectangle = new Rectangle
    {
        Height = 500,
        Width = 500,
        Opacity = ((double)i + 40) / 100d,
        RadiusX = 108,
        RadiusY = 98,
        StrokeThickness = 3,
        Stroke = new SolidColorBrush(Color.FromArgb(255, 75, 75, (byte)(i * 250d / 50d))),
        RenderTransformOrigin = new Point(0.5, 0.5)
    };
    Root.Children.Add(rectangle);
    var angle = i * 360d / 50d;
    var transform = new RotateTransform
    {
        Angle = angle
    };

    rectangle.RenderTransform = transform;

    var storyboard = new Storyboard();
    storyboard.Children.Add(new DoubleAnimation { Duration = TimeSpan.FromSeconds(1), From = angle, To = angle + 360 });
    Storyboard.SetTarget(storyboard, transform);
    Storyboard.SetTargetProperty(storyboard, "Angle");
    storyboard.RepeatBehavior = RepeatBehavior.Forever;
    storyboard.Begin();
}

上面這段代碼是讓50個矩形旋轉,十分考驗 WPF 的性能。結果可以說出乎意料。

CPU 內存 GPU
WPF .NET Framework 4.8 12 60 76
WPF .NET 5.0 12 85 72
UWP 3 28 36
WinUI 5 65 95

我的環境是 i7-6820HQ 及集成顯卡。WPF 平台佔用 70 多%的 GPU,這我大致能猜到。UWP 十分流暢,GPU 只佔用 WPF 的一半,CPU 和 內存都有出色表現,不過我還以為會更低的。

WinUI 這個濃眉大眼的我真的萬萬沒想到,不僅掉幀明顯,還佔用了幾乎 100% GPU,也就是說它連這麼簡單的代碼都跑不起來。()順便一提,將測試代碼中旋轉的矩形減少為10個,WPF 的程序佔用 32% GPU,而 WinUI 佔用 70 多%。)

從上面的數據基本可以說明,WinUI 離設計目標還十分遙遠,畢竟是預覽版,還有一年半載可以慢慢優化。

5. 結語

總的來說微軟雄心勃勃,可是現在拿出來的 WinUI 預覽版還差得太遠,功能未完善,性能不及預期。我覺得大致方向沒錯,WinUI 對 C++、WPF、UWP 開發者都是個新的工具新的機遇,可以關注一下。

6. Q & A

Windows 7 怎麼辦?

按微軟公布的路線圖,再包括跳票等因素,等 WinUI 真正可用時 Windows 7 已停止更新很久,到時 Windows 7 的佔有率可能已經下降到開發者不會關心的程度。

基於 .NET Core 的 Wpf 還是 WinUI?

假使不想花精力將現有項目遷移到 WinUI,或者對來自 UWP 的 WinUI 沒信心,又或者舍不得 Windows 7 的用戶,並且對觸摸沒需求,當然可以繼續選用 WPF,基於 .NET Core 的 WPF 會是個很好的選擇。

MAUI 還是 WinUI ?

MAUI 還在很遙遠的將來(2021年11月),我沒試玩過,所以不好評價。如果有跨平台需求當然只能選 MAUI,如果 WinUI 團隊技高一籌實現了 MAUI 難以企及的超高性能,那就選 WinUI。不過 MAUI 這個名字太過普通/普遍,可能會被逼着改名吧。

那 UWP 呢?

權限受限的 UWP 可以說是人畜無害,對用戶來說可能也是個不錯的選擇。而且 UWP 還支持 Xbox 和 Hololens 等平台,目前看來還是有它的市場。

Winforms 呢?

人只有忘卻了過去,才能好好活着。

WinUI 有未來嗎?

我做了好多年 Silverlight 開發,買了5、6部 Windows Phone 手機,寫了幾十篇 UWP 文章,根據我豐富的經驗,我可以肯定 WinUI 是有未來的。

8. 參考

WinUI – The modern native UI platform of Windows.

Introducing WinUI 3 Preview 1 – Windows Developer Blog

Get started with WinUI 3 for desktop apps Microsoft Docs

GitHub – microsoft_microsoft-ui-xaml

Windows UI Library Roadmap

WinUI 3.0_ The future of Windows controls

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

【其他文章推薦】

※回頭車貨運收費標準

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

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

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

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

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

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

重學 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維修中心

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

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

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

爆款SUV的兩個小兄弟之間有什麼區別?

藍標版本的H2除了前臉的進氣格柵造型不太同,保留了藍標哈弗家族的多邊形大嘴以外,整體設計語言與紅標版本保持一致,都給人以一種偏向穩重厚實的形象。內飾:新創意VS家族風哈弗H2s的內飾設計元素源自手風琴,初次見到這款內飾的時候會覺得這不是哈弗品牌,儘管方向盤是四幅式,但別緻的造型也不會使得哈弗H2s的內飾看上去老氣,相反更具有一種時尚和個性。

哈弗H2s與H2到底啥關係?

月銷七萬多輛哈弗H6的哈弗品牌顯然不太滿足僅憑藉一款車打天下,哈弗最近出了一款名為H2S的小型SUV,作為哈弗H2的“兄弟車型”,此車一出,很多網友在後台給小編留言,問這車是不是H2的改款車型,與現款的哈弗H2之間應該如何抉擇?今天就帶着疑問,給各位有意向於哈弗H2和H2S兩款車之間的朋友進行一番解答。

哈弗H2s

指導價格:8.38-10.28萬

H2s是否就是哈弗H2的改款?相信這是大家最為關心的問題,小編在這裏明確回答,哈弗H2s並不是H2的改款,而是一款完全獨立開發的全新小型SUV。

而且哈弗H2s的設計團隊可謂是來頭不小,它的設計總監pierre Leclercq,曾負責設計第一代寶馬X6和第二代寶馬X5,現在任職長城汽車全球設計總監。pierre入駐長城汽車,也有望在以後的日子里提升哈弗品牌的車型整體設計水平。

外觀:H2S更顯年輕

H2s的外觀設計靈感源自2015年上海車展哈弗品牌發布的Concept B以及Concept R兩款概念車型,而實車誕生之後我們可以看出哈弗H2s在概念車的還原程度上保留了相當高的水平。

無論是紅標版本抑或是藍標版本,哈弗H2s都显示出了一種時尚感非常充沛的視覺感受。藍標版本使用了更多銳利的角度與線條,讓整個前臉顯得非常具有攻擊性。而紅標版則用了更加粗壯的風格,提升了整車的力量感。

而哈弗H2的外觀則顯得與自家銷量王者H6十分的相近,紅標版H2外觀敦實圓潤,整體看上去四平八穩,不仔細分辨的話乍一眼看上去就像是一台小了半號的H6。

藍標版本的H2除了前臉的進氣格柵造型不太同,保留了藍標哈弗家族的多邊形大嘴以外,整體設計語言與紅標版本保持一致,都給人以一種偏向穩重厚實的形象。

內飾:新創意VS家族風

哈弗H2s的內飾設計元素源自手風琴,初次見到這款內飾的時候會覺得這不是哈弗品牌,儘管方向盤是四幅式,但別緻的造型也不會使得哈弗H2s的內飾看上去老氣,相反更具有一種時尚和個性。

哈弗H2的話或許會顯得比較平淡,平直簡練的線條設計與哈弗家族汽車產品非常相近,沒有過多的亮點可言。但是這種設計也有一種好處,便是可以有着非常廣泛的接受人群,可以輕鬆的做到老少咸宜。

空間&動力:差別都不大

同樣是定位於小型SUV,哈弗H2s與哈弗H2使用了同一台1.5T渦輪增壓發動機,最大馬力150匹,峰值扭矩210牛米。但值得一提的是,在哈弗H2s身上搭載的是一套來自格特拉克的7速濕式雙離合變速箱,在這個價位來說使用7速雙離合的車型少之又少,看得出來長城汽車這次挺捨得下本的。

哈弗H2s車身尺寸為4416*1772*1638mm,軸距2550mm而哈弗H2的三圍尺寸為4335*1814*1695mm軸距為2560mm;兩台車的尺寸可謂是半斤八兩,在乘坐空間上,後排表現都屬於比較局促的類型。

購買建議:考慮小型SUV的消費者一般來說對於乘坐空間的訴求並不會特別嚴苛,個人推薦可以考慮最新出的H2s,從設計角度和配件供應角度來看,哈弗H2s將有可能成為哈弗品牌的轉型之作。

加之哈弗品牌一貫而為之的內飾用料比較厚道的做法,哈弗H2s給人的印象高檔感並不差,頂配車型不過是十萬出頭的價格,可以說也符合當下大多數人購車的預算。

全文總結:本次對比主要是為了解答眾多人的問題,那就是哈弗H2s與哈弗H2是彼此間獨立的關係。

而且,從哈弗H2s的推出以及哈弗高端子品牌WEY的發布可以看出,未來哈弗將會進一步優化和豐富產品線布局。相信憑藉這強大的設計團隊和合理的市場定位,未來哈弗爆款的車型將不會僅僅只有哈弗H6一款。本站聲明:網站內容來源於http://www.auto6s.com/,如有侵權,請聯繫我們,我們將及時處理

【其他文章推薦】

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

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

※回頭車貨運收費標準

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

※超省錢租車方案

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

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

繼續等還是放棄?那麼多人愛的帝豪GS究竟值不值得等

全景天窗真的太贊了,雖然不能打開,但是這種採光在傍晚以及晚上的時候,超有高檔車的感覺。最不滿意的地方:後備箱容積感覺上是比較小的,不過畢竟車身也不是很大的樣子。另外就是中控屏是電阻屏的,操作起來不是非常靈敏。

前言

帝豪GS,在尚未上市的時候就已經引起了不少人關注。不俗的顏值加上較高的配置,即使是和着價位相差不遠的合資緊湊型轎車相比也沒有太大的差距。但是,這款車在實際銷售上是供不應求的,提車需要等待很長的時候,不少人想要年前提車,那麼它是否真的值得購買呢?還是應該早點退訂金,換成別的車型?

吉利帝豪GS

官方指導價:7.78-10.88萬

編者意見:顏值高,還有着同級中罕見的自動剎車以及ACC自適應巡航,就內飾來說是在該價位國產汽車的前列。

車主:放羊買飛機

購買車型:帝豪GS 2016款 運動版 1.3T 自動臻尚型

裸車購買價:10.88 萬元

綜合油耗:7.5L

最滿意的地方:外觀以及配置,外觀非常時尚開出去有面子,而且內飾無論是做工還是設計都是不錯的,所以朋友們經常以為這是20多萬的車型。這個比較高的車身還有着比轎車好一點的視野,女士開起來也不費勁。

最不滿意的地方:底盤太硬了,不是很舒服,過一些坑窪的地方的時候非常明顯。另外就是隔音水平是比較一般的,在高速上可以聽到胎噪以及風噪,在80km/h以上就出現了。

車主:大山

購買車型帝豪GS 2016款 運動版 1.3T 自動臻尚型

裸車購買價:10.88 萬元

綜合油耗:8L

最滿意的地方:ACC自適應巡航在高速的時候真的非常好用,覺得自己選擇頂配是正確的,這樣的主動安全配置在這個價位真的是罕見的,關鍵給了不少的安全感。全景天窗真的太贊了,雖然不能打開,但是這種採光在傍晚以及晚上的時候,超有高檔車的感覺。

最不滿意的地方:後備箱容積感覺上是比較小的,不過畢竟車身也不是很大的樣子。另外就是中控屏是電阻屏的,操作起來不是非常靈敏。

車主:四年又四年

購買車型:帝豪GS 2016款 運動版 1.8L 自動領尚型

裸車購買價:9.48 萬元

綜合油耗:9.5L

最滿意的地方:運動版的外觀,真的非常好看,看起來非常協調。配置也是很高,這個價位的緊湊型SUV,還要有电子手剎、自動大燈、自動空調、定速巡航的真的沒有多少輛,而且自動駐車這個功能非常實用,解放了我的右腳刷新了我對開車的體驗。

最不滿意的地方:可能是因為我的是1.8L自動擋的原因,感覺油耗真的是有點高。其次還是後備箱小以及底盤是比較硬。最後是個人覺得儲物空間比較少,想要放些東西都是比較困難。

編者總結:

根據車主反映,1.3T版本是明顯要比1.8L的省油,而且無論是手動擋還是自動擋油耗都相差無幾,所以我們建議選擇1.3T自動擋車型。帝豪GS有着超高的配置水平以及顏值,加上1.3T油耗表現不俗,所以這輛車是非常值得等的。本站聲明:網站內容來源於http://www.auto6s.com/,如有侵權,請聯繫我們,我們將及時處理

【其他文章推薦】

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

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

※回頭車貨運收費標準

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

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

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

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

潤滑油巨頭強勢發布——雪佛龍金富力

產品在抗腐蝕和抗磨損性,減少油泥與積碳,提升發動機性能和燃油經濟性這幾大方面均優於潤滑油行業最嚴格的標準。新品系列也採用了針對中國市場度身定製的配方和技術,滿足中國消費者對潤滑油產品的更高要求。”雪弗龍在發布會上展示產品的測試結果显示:金富力機油提供卓越的抗磨損保護和抗腐蝕保護,分別比ApI標準高出80%與90%。

2016年12月8日,世界500強、世界領先的一體化能源企業雪弗龍,在浙江嘉興舉行了旗下金富力品牌全系潤滑油的新品發布會。

這次發布會上,雪弗龍為消費者帶來了金富力全新系列產品,包括金富力全合成潤滑油、金富力合成型潤滑油及金富力方程式潤滑油三種。

雪弗龍金富力在此次新品發布會主推油泥防禦盾™技術,雪佛龍(中國)投資有限公司產品技術專家,王琴女士表示:“因為雪佛龍是一家比較獨特的潤滑油生產商,它是目前全球為數不多的一家既有既具備基礎油生產能力,又具備添加劑生產能力的潤滑油生產廠商。所以在研發方面,雪佛龍一直都是比較領先的。而油泥防禦盾™是雪佛龍獨有的科技,它給消費者帶來最大的好處,第一個就是保護性能很強,它能夠很好地防止發動機內部磨損,保護髮動機。另外它能夠延長發動機的使用壽命,因為金富力的抗氧化性能很好,在整個潤滑油的使用過程中,它都能像新油一樣保護髮動機。最後油泥防禦盾™也能很好地提高發動機的燃油經濟性。”

發布會上,雪佛龍潤滑油亞太區技術專家,Joyce女士介紹到:“此次上市的雪佛龍金富力全系產品,擁有包括油泥防禦盾
TM科技在內的很多創新技術。產品在抗腐蝕和抗磨損性,減少油泥與積碳,提升發動機性能和燃油經濟性這幾大方面均優於潤滑油行業最嚴格的標準。新品系列也採用了針對中國市場度身定製的配方和技術,滿足中國消費者對潤滑油產品的更高要求。”

雪弗龍在發布會上展示產品的測試結果显示:金富力機油提供卓越的抗磨損保護和抗腐蝕保護,分別比ApI標準高出80%與90%。在減少機油濾網上的油泥方面,金富力機油的性能比GM Dexos1標準要求高出 10%。採用油泥防禦盾配方的金富力機油的粘度保持性能比GM Dexos1 標準所要 求的高出70%。

雪佛龍一直秉承着專業的研發態度,追求精湛的工藝,力求將產品做到極致。這次發布會上新產品,針對中國消費習慣與獨特的路況問題,提出了可行性的解決方案,為打開中國市場做好鋪墊。隨着雪佛龍金富力的發布,雪佛龍將為中國消費者及合作夥伴帶來全球領導的品牌,一流的產品。本站聲明:網站內容來源於http://www.auto6s.com/,如有侵權,請聯繫我們,我們將及時處理

【其他文章推薦】

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

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

※回頭車貨運收費標準

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

※超省錢租車方案

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

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

實測傳說中的最強斯柯達SUV!如果定價十來萬肯定要賣瘋…

高配車型還有電動尾門,實用性非常出色。至於它的第三排座椅,在看來更多的是應急使用。進入第三排仍然會有些困難,對於家裡的一些上了年紀的家人來說,還是安安分分坐在第二排吧,較高的離地間隙也是一個硬傷。因此7座的柯迪亞克更多是讓消費者有更多的選擇,5座的車型仍然廠家宣傳的主力車型。

作為整篇的開頭,可以下一個結論了;試完柯迪亞克之後,我的心裏已經有一個答案了,看厭了大眾千篇一律的設計造型,如今的年輕人是時候換一台具有大眾品質但外觀與空間都表現更加出色的SUV了。

作為大眾旗下的一個子公司,斯柯達一直受到的關注相對來說還是少了一點。但作為一家公司而言,斯柯達肯定不會一直甘於人下,被所有人都認為是大眾小弟的。於是斯柯達在醞釀已久之後,開始在全世界展示這台充滿北歐風情,但卻符合大眾消費者審美的SUV車型。

犀利的外觀造型,人性化的設計

之前得知柯迪亞克要在廣州車展做展示的消息,尋思着在車展期間好好欣賞一下這款出自斯柯達首款7座SUV,無奈是由於眾多媒體的圍觀,雖然對它非常感興趣,但能觸摸到的時間真的不多。如今不遠萬里來到充滿熱情的西班牙,我仍然按捺住自己心裏的躁動,只想靜靜品味它獨特的外觀造型。

柯迪亞克這個有點拗口的名字是來源自身形龐大的棕熊,第一次看到它時候覺得這個名字改得相當貼切。龐大的車身採用凌厲的線條勾勒出動感的車身造型,飽滿之餘卻不臃腫。極富張力的前臉設計給人的印象相當深刻,粗壯的直瀑式進氣格柵與犀利的LED前大燈營造出的前臉氣場很足,一如它的名字。

從大燈延伸要車尾的三維腰線使得車身具有不錯的立體感,C柱與D柱之間的小舷窗的設計使得車身的造型非常協調,配合外擴式輪罩設計,使得車身充滿力量感。簡潔的車尾卻不缺少變化,標誌性的C型LED尾燈造型與視覺效果相當出色。

除了出色的外觀造型,柯迪亞克也變得相當的人性化,設計師有時候觀念的轉變最後受益是我們消費者。這個從它的一些設計小細節就可以看出。開門的時候,由於很多人都非常擔心碩大的車門會碰到隔壁的牆壁或者車,而柯迪亞克則設計了一套可伸縮的防擦條,開門的時候自動車身,很巧妙的設計卻不會影響美觀。之前在速派出現過的門板處設計摺疊傘存放槽,這在柯迪亞克也有看到,而且正副駕駛都有,是非常貼心的裝置。

熟悉的內飾,貼心的配置

進入車內,依然是熟悉的大眾設計風格,卻擁有斯柯達自家的基因,一切以實用為主。規整的中控造型配上仿木紋的裝飾面板,相當具有檔次。應用多年的老氣多輻式方向盤終於壽終正寢,換裝了一個手感良好大小適中的三輻式方向盤。

8英寸的觸摸屏显示效果比較清晰,配合觸摸式的按鍵,上手比較簡單,由於海外版本搭載是谷歌地圖,在試駕途中沒有好好體驗。分區明確的中控台無論做工還是按鍵的手感都都相當出色,符合一款旗艦SUV的身價。中控下方的儲物格空間很大,能放置I7 plus,還有無線充電功能,希望到時國內上市時還有這個提升逼格的配置。

讓驚喜是它的副駕駛後方設置了一個小桌板與手機支架,可以讓後排乘客在旅途當中不僅看看自己喜歡的電影,還能就着電影吃零食。後排頭枕兩側還有貼心護翼,後排乘客看累了電影還能舒服地睡個覺。

五座為主,7座為輔

進入車內,前排空間就不用多說了,雖然身材不算很高,但頭部空間與腿部空間都非常充裕。得益於2791mm的軸距,它的第二排空間非常寬敞,雖然坐墊有些短,但對這些身高一米7多點還是足夠的,舒適性不差。配合碩大的全景天窗,後排乘客體驗非常出色。後排整體放倒比較規整,後備箱的空間非常寬敞;高配車型還有電動尾門,實用性非常出色。

至於它的第三排座椅,在看來更多的是應急使用。進入第三排仍然會有些困難,對於家裡的一些上了年紀的家人來說,還是安安分分坐在第二排吧,較高的離地間隙也是一個硬傷。因此7座的柯迪亞克更多是讓消費者有更多的選擇,5座的車型仍然廠家宣傳的主力車型。

這是一台年輕人的SUV

聊完最基礎的外觀、內飾與空間,剩下來的肯定就是好好試駕一番這台身材不少的SUV了。它的尺寸比目前剛上市的進口途觀還要大點,這樣的龐大的身軀,看到需要比較強勁的動力總成了,因此那款1.4T的發動機不會搭載在柯迪亞克身上。

雖然歐洲是柴油車的天下,但為了更好與國內市場相一致,捨棄了試駕柴油版的柯迪亞克,選擇了一台搭載了2.0T發動機,可是號稱7.5秒能把柯迪亞克從0加速到100Km/h的車型。至於1.8T車型,想留着到時國內上市之後再來試駕。

只要你開過斯柯達,那麼柯迪亞克是相當的容易上手。相比其他斯柯達車型,它有着更高的坐姿與更出色的視野,當然它的調校與其他斯柯達的車型還是有所區別的,那就是整體會更硬朗一些,很貼合它的名字與造型。

在試駕的過程中,這台2.0T的發動機動力輸出非常直接,與其匹配的DQ500平順性做得相當出色,只是一路試駕的途中基本沒有體驗頓挫的酸爽感,換擋的非常积極聰明。如今唯一的念想就是希望國產之後仍然是這台出色的變速箱了。硬朗的懸挂調校,讓人在行駛當中非常踏實。雖然在試駕過程中路面不算很好,但柯迪亞克在靜音水平做得比較出色,相比途觀真的進步很大。

試駕的這台車型還是四驅版本,車尾的4×4出賣了它的身份。它搭載的這台四驅系統與Tiguan相差不大,是一台適時四驅系統,通過多片離合器式限滑差速器來分配動力,但主要還是以前驅為主,對於操控有一定的提升,千萬不要以為能進行極限越野。

合理的定價,將會是斯柯達的雄起之作

從最近這幾年斯柯達的發展來說,已經有了很大的進步了;在明銳與晶銳身上可以逐漸看到全新的設計元素,以前比較老氣造型已經被丟進博物館了。再到最新的速派,採用MQB平台生產的中級車,雖然銷量仍然比不上帕薩特,但已經上了不止一個台階了。那麼未來的成績要想更上一層樓的話,就肯定需要一個非常切合年輕人的定價。

柯迪亞克擁有着出眾的外觀顏值,非常實用的空間體驗,這些優勢已經吸引了許多消費者的關注,但是如果真的要賣到我們手中,恐怕差的只是一個合適的價格了。這個從它亮相以來不斷的有網友跟說,只要它性價比不錯,首選SUV一定是它了。既然這樣,就來猜測一下,假如定一個比較低的起步價的話,例如18萬的起步價格、同時爭取上市的時間比國產途觀更早,相信一定有很多年輕人會把它收入囊中的。如今消費者已經出好題目了,就看斯柯達會不會給個令人滿意的答案了。本站聲明:網站內容來源於http://www.auto6s.com/,如有侵權,請聯繫我們,我們將及時處理

【其他文章推薦】

※回頭車貨運收費標準

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

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

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

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

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

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

連年旱災牧草不生 澳洲羊隻大減創下空前新低

摘錄自2020年5月28日中央社報導

今(28日)公布的農業數據顯示,由於澳洲東部連年旱災,全國羊隻數量減少到100多年前有紀錄以來的歷史新低。

農人去年低價拋售或丟棄總價值210億澳幣(約台幣4166億元)的牲畜,全國羊隻數量減至6600萬,至少是1905年以來最低。

澳洲統計局(Australian Bureau of Statistics)表示:「東部數州旱災惡化且牧草不足,許多養牛人和養羊人不得不減少牲口數量。」澳洲統計局並指出,2018-19財政年度,牲口數量減少了7%。

過去150年來,羊隻、特別是羊毛,都是澳洲經濟的最大支柱。今天公布的最新數字,並不涵蓋大片森林和農地發生毀滅性大火的2019年底和2020年初。

農林漁牧業
環境經濟
土地利用
循環經濟
國際新聞
澳洲
旱災
牧草
牧羊

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

【其他文章推薦】

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

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

※回頭車貨運收費標準

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

※超省錢租車方案

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

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