分類
發燒車訊

疑似 OPPO Find X3 安兔兔跑分曝光,搭載高通 S888 旗艦處理器測出 77.1 萬分刷新最高分紀錄_包裝設計

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

上新台中搬家公司提供您一套專業有效率且人性化的辦公室搬遷、公司行號搬家及工廠遷廠的搬家服務

日前首款發表的高通 Snapdragon 888 旗艦  5G 手機小米11 官方公布安兔兔性能跑分實測為 745,942 分,隨後這成績也很快被接下來 vivo 即將在 1 月 11 日發表的 iQOO 7 以 752,935 分給超越。不過今日是稍早有一款來自 OPPO 的裝置的安兔兔跑分成績出爐,以 771,491 分的高分再次刷新了高通 Snapdragon 888 跑分的新紀錄,而它極有可能就是未來將發表的 OPPO Find X3 。

▲圖片來源:TheLeaks3(Twitter)

疑似 OPPO Find X3 安兔兔跑分曝光,搭載高通 S888 旗艦處理器測出 77.1 萬分刷新最高分紀錄

在小米11 發表之前, Qualcomm 官方就率先公開 Snapdragon 8888 旗艦處理器的工程樣機安兔兔跑分測試結果,當時連續測試三次測得最高 740,847 分、平均分 735,439 分。
除了已經公開的小米11 和 iQOO 7 這兩款 Snapdragon 888 旗艦手機的安兔兔跑分成績已經公開,今日 i冰宇宙在微博上傳一張 OPPO 型號 PEEM00 裝置安兔兔跑分達 771,491 分,這成績也是截至目前的最高分紀錄,在該則貼文更表示這成績是在常溫下測出的,這也表示若在實驗室的環境下測試,分數可能還會再更高。

▲圖片來源:i冰宇宙(微博)

除了強悍的性能表現,關於螢幕方面我們之前也曾引述 Evan Blass 在 Voice 揭露了 OPPO Find X 系列下一代機型 Find X3 系列的傳聞規格,當時爆料指出 OPPO Find X3 系列將推出三款機型,其中最高階的 Find X3 Pro 內部代號為「Fussi」,螢幕採用 6.7 吋 QHD+ 解析度螢幕(3216 x 1440、525 PPI),螢幕將支援 10Hz – 120Hz 可變螢幕更新率,能依照使用情境調節螢幕更新率(最高支持 120Hz)。此外,螢幕也將支持 HDR10+ 10.7 億色顯色。

▲圖片來源:TheLeaks3(Twitter)

去年 OPPO 未來科技大會 2020 已預告 Find X3 系列也將首搭載 OPPO 「全鏈路色彩管理系統」。 OPPO 全鏈路色彩管理系統是一套基於硬件所開發的色彩管理系統,該色彩管理系統擁有兩大核心技術,全鏈路 10-bit 及色彩管理。能夠大幅度提升色彩採集的豐富度,在顯示環節則能讓畫質更加清晰且更精準的還原顯示圖像色彩。

▲圖片來源:OPPO

這組來自 TheLeaks 製作的 OPPO Find X3 系列高清晰渲染圖,最值得關注的是主相機設計這部分採用類似 iPhone 12 Pro 系列的矩陣設計。回顧之前 Evan Blass 描述 Find X3 Pro 將配備 5000 萬像素四鏡頭主相機,當時提到標準鏡頭和超廣角鏡頭都將採用 5000 萬像素的 SONY IMX766 感光元件,日前 IMX766 感光元件率先在 Oppo Reno5 Pro+ 採用。

▲圖片來源:TheLeaks3(Twitter)

近期也有新的傳聞指出 OPPO 目前正與 SONY 緊密合作,即將推出採用 SONY IMX789 感光元件的旗艦手機,不過據數碼閒聊站在微博的消息則表示 IMX789 可能趕不上在這次 Find X3 系列採用。充電方面,傳聞 Find X3 Pro 快充方面預計支援 65W SuperVOOC 2.0 有線快充和 30W AirVOOC 無線快充,也將彌補之前 Find X2 系列未支持無線充電的遺憾。

▲圖片來源:TheLeaks3(Twitter)

昨(4)日, OPPO 在微信公眾號 OPPODAILY 發表了一篇來自 OPPO 總裁陳明永的新年致辭,該主題為「奮進向上,破局高端」。文內另有回顧 2020 年 OPPO 在 Find X2 系列、 Reno 系列等產品上品牌策略,以及 OPPO 於海內外市場和軟硬體的多方佈局。在接下來的 2021 年,也是 OPPO 高階旗艦 Find 系列的 10 週年,並確定將在第一季發表 OPPO Find X3 系列。

▲圖片來源:OPPODAILY(微信公眾號)

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

窩窩觸角包含自媒體、自有平台及其他國家營銷業務等,多角化經營並具有國際觀的永續理念。

消息來源:i冰宇宙(微博)|TheLeaks3(Twitter)

延伸閱讀:
Redmi K40 Pro 最新渲染圖曝光:可能是最便宜的 S888 旗艦 5G 手機之一

小米 POCO 官方 Facebook 粉專成立、官網上線,即將以 POCO 品牌「重返」台灣市場

您也許會喜歡:

【推爆】終身$0月租 打電話只要1元/分

立達合法徵信社-讓您安心的選擇

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

網動廣告出品的網頁設計,採用精簡與質感的CSS語法,提升企業的專業形象與簡約舒適的瀏覽體驗,讓瀏覽者第一眼就愛上她。

分類
發燒車訊

中國 YouTuber 搶先揭露 i9-11900K 的測試影片,單核心擊敗對手 Ryzen 9 5900X,時脈達 5.3GHz_包裝設計

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

上新台中搬家公司提供您一套專業有效率且人性化的辦公室搬遷、公司行號搬家及工廠遷廠的搬家服務

繼昨日的 i7-11700K 之後,最多遊戲玩家期待的 i9-11900K,也有新傳聞出現了,而且這次還是直接被中國 YouTuber 洩漏評測影片,即便只是測試版的 BIOS,單核心跑分依舊贏過上一代的 i9-10900KF,也擊敗競爭對手的 Ryzen 9 5900X,非常亮眼。另外溫度部分也不意外相當高,壓力測試浮點 FPU 直接過熱降頻,有意入手的人,記得也準備好散熱器的錢。

中國 YouTuber 搶先揭露 i9-11900K 評測影片

上個月中中國就曾出現過第 11 代桌上型處理器 i9-11900 的 ES 工程版,因此這次 i9-11900K 再度於中國現身其實也不讓人意外。雖然這位潮玩客並沒有特別說明是 ES 版還是正式版,但就時間點來看,ES 版的可能性很大。

搭配的主機板為 Socket 1200 LGA,時脈最高可達到 5.3GHz,採 8 核心與 16 執行緒設計:

而實際測試時,目前安裝的 BIOS 還是測試版,因此會有一些問題存在,像是 RAM 最高只能上到 3200MHz、PCI 支援度也有問題,無法正常點亮 PCIe 4.0 的 AMD 與 NVIDIA 顯示卡,因此他們只能搭配 GTX 1660Ti。

全核心運行最高時脈是 4.7GHz,單核則可以達到 5.3GHz,跑 CINEBENCH R20 功耗為 230W:

浮點 FPU 壓力測試功耗更來到 260W,每個核心都快到 100 度,理所當然就過熱降頻了:

CPU-Z 測試單核心獲得 708 分,多核心則是 6443 分,對比 i9-10900KF 與競爭對手的 R9 5900X,單核心都獲勝,不過多核心部分就輸了。這其實合理,畢竟 i9-11900K 目前傳出是 8 核心 16 執行緒,而上一代的 i9-10900KF 為 10 核心 20 執行緒,AMD R9 5900X 更是高達 12 核心 24 執行緒:

另外雖然 i9-11900K 的核心數下降且同樣是 14nm 製程,但在第 11 處理器的架構上,Intel 有做許多改進,因此已經可以跟基於 7nm 製程的 AMD Zen 3 核心比拼。

CINEBENCH R20 單核心獲得 363 分,多核心為 5725 分,單核心一樣贏 i9-10900KF 與 R9 5900X:

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

網動廣告出品的網頁設計,採用精簡與質感的CSS語法,提升企業的專業形象與簡約舒適的瀏覽體驗,讓瀏覽者第一眼就愛上她。

不過遊戲測試就不太理想,R9 5900X 平均有 388 FPS,i9-10900KF 也達到 285 FPS,但 i9-11900K 僅 220FPS:

其他遊戲也一樣:

從 AIDA64 GPGPU Benchmark 測試結果可以看到,VRAM 的數字 i9-11900K 不太正常:

也僅使用 PCIe x16 1.1 通道,難怪跑出這樣的數據:

目前他們還找不出解決辦法,看來只能等正式版 BIOS 推出後,才能真正了解到遊戲表現究竟能不能勝過對手與上一代。

下方是完整影片:

AMD桌上型電腦處理器全球市佔率50.8% 睽違15年終於反超Intel

您也許會喜歡:

【推爆】終身$0月租 打電話只要1元/分

立達合法徵信社-讓您安心的選擇

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

窩窩觸角包含自媒體、自有平台及其他國家營銷業務等,多角化經營並具有國際觀的永續理念。

分類
發燒車訊

Nintendo Switch Pro 4K 版本傳聞再現!國外分析師表示今年有望見到_網頁設計公司

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

網站的第一印象網頁設計,決定了客戶是否繼續瀏覽的意願。台北網動廣告製作的RWD網頁設計,採用精簡與質感的CSS語法,提升企業的專業形象與簡約舒適的瀏覽體驗,讓瀏覽者第一眼就愛上它。

Nintendo Switch Pro 傳聞又來了!這台幾乎每年都再傳,不過今年看起來確實蠻有機會的,畢竟 Sony PlayStation 5、Xbox Series X 都已經推出,Nintendo 想在重返市場最熱門的遊戲機話題,無疑就是要靠新款 Switch。近日就有國外分析師表示,他預測今年 Nintendo 會推出玩家期待許久的 Switch 升級版,而且將會是 4K 解析度版本。

Nintendo Switch Pro 4K 版本傳聞再現!

根據外媒 Nintendo Life 的報導,國外許多業界專家、分析師都紛紛表示,今年會是 Nintendo 非常重要的一年,他們預測 2021 年賣最好、最暢銷的遊戲主機,不會是 PlayStation 5,也不是 Xbox Series X,而是 Nintendo 的 Switch 系列,現行的 Switch Lite、Switch 與今年有機會見到 Switch Pro 總額加起來,將超越這兩間競爭對手。

Kantan Games 的 Serkan Toto 博士在與 Gamesindustry.biz 訪談中提到:「我預測加強版的任天堂 4K 設備,會在這個年度亮相」。Ampere Analysis 的 Piers Harding-Rolls 也表示贊同,不過他不確定規格會是怎樣,但確實很有機會見到新款 Switch」。

至於是基於什麼原因,他們就沒特別強調。

不過去年確實陸續有相關消息出現,像是夏季時,任天堂曾通知開發人員,建議他們準備好 4K 解析度的遊戲。也有報導指出,任天堂正與 Innolux 公司合作,預計下一代遊戲設備會配備 mini LED 螢幕:

※綠能、環保無空污,成為電動車最新代名詞,目前市場使用率逐漸普及化

台中景泰電動車行只是一個單純的理由,將來台灣的環境,出門可以自由放心的深呼吸,讓空氣回歸自然的乾淨,減少污染,留給我們下一代有好品質無空污的優質環境

至於規格,統整目前市場上的傳言,Switch Pro 有望具備類似 DLSS 的技術,為玩家提供更流暢的 4K 遊戲體驗。CPU 可能是基於 NVIDIA Tegra Xavier Soc 客製化版本,效能接近 Xbox One。

下方是國外製作的 Switch Pro 概念影片:

資料來源:Nintendo Life

國外開發者成功在 M1 Mac 上運行 Nintendo Switch 遊戲

您也許會喜歡:

【推爆】終身$0月租 打電話只要1元/分

立達合法徵信社-讓您安心的選擇

※如何讓商品強力曝光呢? 網頁設計公司幫您建置最吸引人的網站,提高曝光率!

以設計的實用美學觀點,規劃出舒適、美觀的視覺畫面,有效提昇使用者的心理期待,營造出輕鬆、愉悅的網站瀏覽體驗。

分類
發燒車訊

AirPods Pro 推超「牛」限量版,主動式降噪讓你隨時找到專屬自己的空間_網頁設計公司

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

RWD(響應式網頁設計)是透過瀏覽器的解析度來判斷要給使用者看到的樣貌

才剛跨進 2021,緊跟著的農曆新年,Apple 也想到可以帶來一些新年禮物的好選擇 — 或該說,好「牛」的選擇。Apple Store 線上商店今天開賣了一款喜氣洋洋的 AirPods Pro 牛年限量版。繼續閱讀 AirPods Pro 推超「牛」限量版,主動式降噪讓你隨時找到專屬自己的空間報導內文。

AirPods Pro 牛年限量版官網(點我)
享最高 12 個月 0% 利率分期

▲圖片來源:Apple

AirPods Pro 推超「牛」限量版,主動式降噪讓你隨時找到專屬自己的空間

▲圖片來源:Apple

新年新希望,紅包很好有禮物也棒。雖說蘋果才剛剛推出更高階耳罩式降噪耳機 AirPods Max。不過對許多人而言,更好攜帶聆聽也相對低價的 AirPods Pro 應該仍是最佳體驗的降噪耳機選擇之一。重點是它也同樣能透過 H1 晶片、各式感測器及耳塞設計,提供主動式降噪搭配優質的通透模式的體驗,也支援「空間音訊(Spatial Audio)」的神奇功能,都是現階段最好的 AirPods 使用體驗。

▲圖片來源:Apple

這次蘋果為了迎接農曆新年,帶來了「很 Emoji」的牛年生肖 Logo 的 AirPods Pro 牛年限量版。在定價依然不變維持在 NT$7,990 的前提下,提供更符合年節主題的鐫刻與紅色 Logo 外包裝,希望有機會為消費者提供新年送禮的好選擇。是說,雖然你也可以自行在非限量版的 AirPods Pro 訂製鐫刻 — 還可以刻不只一個,而且也有 12 生肖可選。

AirPods Pro 牛年限量版官網(點我)
享最高 12 個月 0% 利率分期

不過這牛上加牛的 Happy 牛 year 意涵的特殊 Logo 搭配包裝,顯然還是比較有新年氛圍一些些(吧?)。總之,也趁這個機會先跟大家預祝新年快樂囉!

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

透過資料庫的網站架設建置,建立公司的形象或購物系統,並提供最人性化的使用介面,讓使用者能即時接收到相關的資訊

延伸閱讀:

Google 語音助理新增「耍廢模式(Do Nothing Mode)」讓你不得不 Chill

MagSafe 也能這麼實用!MOFT 磁吸支架開箱體驗

您也許會喜歡:

【推爆】終身$0月租 打電話只要1元/分

立達合法徵信社-讓您安心的選擇

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

當全世界的人們隨著網路時代而改變向上時您還停留在『網站美醜不重要』的舊有思維嗎?機會是留給努力改變現況的人們,別再浪費一分一秒可以接觸商機的寶貴時間!

分類
發燒車訊

Netty源碼學習系列之3-ServerBootstrap的初始化_網頁設計公司

※如何讓商品強力曝光呢? 網頁設計公司幫您建置最吸引人的網站,提高曝光率!

以設計的實用美學觀點,規劃出舒適、美觀的視覺畫面,有效提昇使用者的心理期待,營造出輕鬆、愉悅的網站瀏覽體驗。

前言

    根據前文我們知道,NioEventLoopGroup和NioEventLoop是netty對Reactor線程模型的實現,而本文要說的ServerBootstrap是對上面二者的整合與調用,是一個統籌者和協調者。具體netty使用的是Reactor單線程模型還是多線程模型、抑或者主從多線程模型,都是ServerBootstrap的不同配置決定的。

    下面照例粘貼一下示例demo(以Reactor多線程模式構建),開始正文。

 1 public class NettyDemo1 {
 2     // netty服務端的一般性寫法
 3     public static void main(String[] args) {
 4         EventLoopGroup boss = new NioEventLoopGroup(1);
 5         EventLoopGroup worker = new NioEventLoopGroup();
 6         try {
 7             ServerBootstrap bootstrap = new ServerBootstrap();
 8             bootstrap.group(boss, worker).channel(NioServerSocketChannel.class)
 9                     .option(ChannelOption.SO_BACKLOG, 100)
10                     .childHandler(new ChannelInitializer<SocketChannel>() {
11                         @Override
12                         protected void initChannel(SocketChannel socketChannel) throws Exception {
13                             ChannelPipeline pipeline = socketChannel.pipeline();
14                             pipeline.addLast(new StringDecoder());
15                             pipeline.addLast(new StringEncoder());
16                             pipeline.addLast(new NettyServerHandler());
17                         }
18                     });
19             ChannelFuture channelFuture = bootstrap.bind(90);
20             channelFuture.channel().closeFuture().sync();
21         } catch (Exception e) {
22             e.printStackTrace();
23         } finally {
24             boss.shutdownGracefully();
25             worker.shutdownGracefully();
26         }
27     }
28 }

 

一、ServerBootstrap的初始化

    ServerBootstrap的無參構造器啥都沒做,它使用的build模式給屬性賦值,即上面示例中看到的,每執行一個賦值方法都會返回當前對象的引用使得可以繼續鏈式調用。下面挨個方法追蹤。

1 public ServerBootstrap() { }

 

1、ServerBootstrap.group方法

    ServerBootstrap有兩個可用重載group方法(如下的兩個),其中接收一個group入參的方法會調用有兩個入參的group方法,只是兩個參數傳同一個group。這兩個group方法決定了netty使用的Reactor線程模型的類型,一個group入參的方法對應Reactor單線程模型,兩個入參且不是同一個group的方法對應Reactor多線程模型或主從多線程模型(具體是哪一種取決於實例化parentGroup時的線程數)。此處只是提一下,先有個印象,後面會對線程模型進行詳細研究。

1 public ServerBootstrap group(EventLoopGroup group) {
2         return group(group, group);
3     }
1 public ServerBootstrap group(EventLoopGroup parentGroup, EventLoopGroup childGroup) {
2         super.group(parentGroup);
3         ObjectUtil.checkNotNull(childGroup, "childGroup");
4         if (this.childGroup != null) {
5             throw new IllegalStateException("childGroup set already");
6         }
7         this.childGroup = childGroup;
8         return this;
9     }

    可以看到上述group方法對兩個入參進行了不同位置的賦值,將第一個參數parentGroup傳給了父類AbstractBootstrap的group方法,如下,即最終賦值給了AbstractBootstrap中的group屬性。第二個參數直接賦值給了ServerBootstrap的childGroup屬性。

1 public B group(EventLoopGroup group) {
2         ObjectUtil.checkNotNull(group, "group");
3         if (this.group != null) {
4             throw new IllegalStateException("group set already");
5         }
6         this.group = group;
7         return self();
8     }

 

2、ServerBootstrap.option/childOption方法和ServerBootstrap.attr/childAttr方法

    這四個方法只是做了屬性的賦值,分別賦值給了AbstractBootstrap的options屬性和attrs屬性以及ServerBootstrap的childOptions屬性和childAttrs屬性。

 1 public <T> B option(ChannelOption<T> option, T value) {
 2         ObjectUtil.checkNotNull(option, "option");
 3         if (value == null) {
 4             synchronized (options) {
 5                 options.remove(option);
 6             }
 7         } else {
 8             synchronized (options) {
 9                 options.put(option, value);
10             }
11         }
12         return self();
13     }
 1 public <T> B attr(AttributeKey<T> key, T value) {
 2         ObjectUtil.checkNotNull(key, "key");
 3         if (value == null) {
 4             synchronized (attrs) {
 5                 attrs.remove(key);
 6             }
 7         } else {
 8             synchronized (attrs) {
 9                 attrs.put(key, value);
10             }
11         }
12         return self();
13     }

 

※綠能、環保無空污,成為電動車最新代名詞,目前市場使用率逐漸普及化

台中景泰電動車行只是一個單純的理由,將來台灣的環境,出門可以自由放心的深呼吸,讓空氣回歸自然的乾淨,減少污染,留給我們下一代有好品質無空污的優質環境

3、ServerBootstrap.channel方法

    調用的是父類AbstractBootstrap的channel方法:

1 public B channel(Class<? extends C> channelClass) {
2         return channelFactory(new ReflectiveChannelFactory<C>(
3                 ObjectUtil.checkNotNull(channelClass, "channelClass")
4         ));
5     }

    可以看到先封裝成了一個ReflectiveChannelFactory對象,然後調用channelFactory方法,下面挨個看。ReflectiveChannelFactory的構造器如下,可見就是將傳入class對象的構造器取出來賦值,此時constructor存放的就是NioServerSocketChannel的構造器。

public ReflectiveChannelFactory(Class<? extends T> clazz) {
        ObjectUtil.checkNotNull(clazz, "clazz");
        try {
            this.constructor = clazz.getConstructor();
        } catch (NoSuchMethodException e) {
            throw new IllegalArgumentException("Class " + StringUtil.simpleClassName(clazz) +
                    " does not have a public non-arg constructor", e);
        }
    }

    channelFactory方法的工作是將上面創建的ReflectiveChannelFactory對象賦值給AbstractBootstrap的channelFactory屬性:

1 public B channelFactory(ChannelFactory<? extends C> channelFactory) {
2         ObjectUtil.checkNotNull(channelFactory, "channelFactory");
3         if (this.channelFactory != null) {
4             throw new IllegalStateException("channelFactory set already");
5         }
6 
7         this.channelFactory = channelFactory;
8         return self();
9     }

 

4、ServerBootstrap.handler方法和ServerBootstrap.childHandler方法

    handler方法的入參賦值給了AbstractBootstrap的handler屬性,childHandler方法的入參賦值給了ServerBootstrap的childHandler屬性。看到這裏想必園友們也能看出ServerBootstrap的賦值規律了,凡是child開頭的都放在ServerBootstrap中,而不帶child的大多放在其父類ABstractBootstrap中。

1 public B handler(ChannelHandler handler) {
2         this.handler = ObjectUtil.checkNotNull(handler, "handler");
3         return self();
4     }
1 public ServerBootstrap childHandler(ChannelHandler childHandler) {
2         this.childHandler = ObjectUtil.checkNotNull(childHandler, "childHandler");
3         return this;
4     }

 

5、完成賦值后ServerBootstrap的快照圖

 

 

小結

    ServerBootstrap的初始化過程看起來賦了很多值,但都只是做了準備工作,看起來輕鬆又簡單,但請注意,這是暴風雨前寧靜。前面的各種賦值到底有什麼用處?很多屬性分為有child前綴和沒有child前綴,這樣設置又有什麼意圖?下一期將進入ServerBootstrap的bind方法,這是netty的深水區,很多謎底也將在這裏得到揭曉,敬請期待!

 

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

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

網站的第一印象網頁設計,決定了客戶是否繼續瀏覽的意願。台北網動廣告製作的RWD網頁設計,採用精簡與質感的CSS語法,提升企業的專業形象與簡約舒適的瀏覽體驗,讓瀏覽者第一眼就愛上它。

分類
發燒車訊

ASP.NET Core MVC+Layui使用EF Core連接MySQL執行簡單的CRUD操作_網頁設計公司

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

RWD(響應式網頁設計)是透過瀏覽器的解析度來判斷要給使用者看到的樣貌

前言:

  本章主要通過一個完整的示例講解ASP.NET Core MVC+EF Core對MySQL數據庫進行簡單的CRUD操作,希望能夠為剛入門.NET Core的小夥伴們提供一個完整的參考實例。關於ASP.NET Core MVC+EF操作MsSQL Server詳情請參考官方文檔(https://docs.microsoft.com/zh-cn/aspnet/core/data/ef-mvc/?view=aspnetcore-3.1)。

示例實現功能預覽:

 博客實例源碼下載地址:

https://github.com/YSGStudyHards/ASP.NET-Core-MVC-Layui-EF-Core-CRUD_Sample

一、創建ASP.NET Core Web應用程序:

注意,本章節主要以APS.NET Core 3.1版本作為博客的樣式實例!

 

二、添加EF Core NuGet包:

  若要在項目中使用EF Core操作MySQL數據庫,需要安裝相應的數據庫驅動包。 本章教程主要使用 MySQL數據庫,所以我們需要安裝相關驅動包MySql.Data.EntityFrameworkCore。

安裝方式:

點擊工具=>NuGet包管理器=>程序包管理器控制台輸入以下命令:

Install-Package MySql.Data.EntityFrameworkCore -Version 8.0.20

點擊工具=>NuGet包管理器=>管理解決方案的NuGet程序包:

搜索:MySql.Data.EntityFrameworkCore  點擊安裝。

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

當全世界的人們隨著網路時代而改變向上時您還停留在『網站美醜不重要』的舊有思維嗎?機會是留給努力改變現況的人們,別再浪費一分一秒可以接觸商機的寶貴時間!

三、創建對應數據庫表的實體模型:

   注意該篇博客使用的是手動模型優先的方式進行數據庫表字段與模型屬性映射,當然如果大家覺得這樣子比較麻煩的話可以真正意義上的模型優先,直接創建模型在program.cs中配置創建對應模型的數據庫邏輯代碼即可無需手動創建數據庫,可參考官網文檔教程(https://docs.microsoft.com/zh-cn/aspnet/core/data/ef-rp/intro?view=aspnetcore-3.1&tabs=visual-studio#create-the-database)。

創建用戶模型(UserInfo):

注意:屬性大小寫和數據庫中的表字段保持一致,Id 屬性成為此類對應的數據庫表的主鍵列。 默認情況下,EF Core 將名為 Id 或 xxxID 的屬性視為主鍵。 有關詳細信息,請參閱 F Core – 密鑰。

    /// <summary>
    /// 學生信息模型
    /// </summary>
    public class UserInfo
    {
        /// <summary>
        /// 學生編號
        /// </summary>
        [Description("學生編號")]
        public int? Id { get; set; }

        /// <summary>
        /// 學生姓名
        /// </summary>
        [Description("學生姓名")]
        public string UserName { get; set; }

        /// <summary>
        /// 學生性別
        /// </summary>
        [Description("學生性別")]
        public string Sex { get; set; }

        /// <summary>
        /// 學生聯繫電話
        /// </summary>
        [Description("學生聯繫電話")]
        public string Phone { get; set; }

        /// <summary>
        /// 學生描述
        /// </summary>
        [Description("學生描述")]
        public string Description { get; set; }

        /// <summary>
        /// 學生愛好
        /// </summary>
        [Description("學生愛好")]
        public string Hobby { get; set; }
    }

四、將數據庫連接字符串添加到 appsettings.json:

{
  "Logging": {
    "LogLevel": {
      "Default": "Information",
      "Microsoft": "Warning",
      "Microsoft.Hosting.Lifetime": "Information"
    }
  },
  "AllowedHosts": "*",
  "ConnectionStrings": {
        "MySqlConnection":"Data Source=127.0.0.1;User ID=root;Password=root;DataBase=SchoolUserInfo_db"
  }
}

五、創建數據庫上下文:

概述:

 數據庫上下文類是為給定數據模型協調 EF Core 功能的主類。 上下文派生自 Microsoft.EntityFrameworkCore.DbContext。 上下文指定數據模型中包含哪些實體。 在此項目中將數據庫上下文類命名為 SchoolUserInfoContext。

創建:

using Microsoft.EntityFrameworkCore;
using Model;

namespace Dal
{
    public class SchoolUserInfoContext : DbContext
    {
        public SchoolUserInfoContext(DbContextOptions<SchoolUserInfoContext> options)
            : base(options)
        {
        }

        /// <summary>
        /// DbSet實體集屬性對應數據庫中的表(注意實體集名必須與表明一致)
        /// </summary>
        public DbSet<UserInfo> UserInfos { get; set; }

        /// <summary>
        /// TODO:當數據庫創建完成后, EF 創建一系列數據表,表名默認和 DbSet 屬性名相同。 集合屬性的名稱一般使用複數形式,但不同的開發人員的命名習慣可能不一樣,
/// 開發人員根據自己的情況確定是否使用複數形式。 在定義 DbSet 屬性的代碼之後,添加下面代碼,對DbContext指定單數的表名來覆蓋默認的表名。
/// </summary> /// <param name="modelBuilder"></param> protected override void OnModelCreating(ModelBuilder modelBuilder) { modelBuilder.Entity<UserInfo>().ToTable("UserInfo"); } } }

六、將上下文添加到 Startup.cs 中的依賴項注入:

        // This method gets called by the runtime. Use this method to add services to the container.
        public void ConfigureServices(IServiceCollection services)
        {
            //注入EF Core數據庫上下文服務
            services.AddDbContext<SchoolUserInfoContext>(options =>
                options.UseMySQL(Configuration.GetConnectionString("MySqlConnection")));

            services.AddControllersWithViews();
        }

七、引入Layui樣式和js:

前往官網下載Layui相關樣式和js包,下載地址:https://www.layui.com/

Layui彈出層插件layer.js(有很多地方需要用到彈窗),下載地址:https://layer.layui.com/

將相關文件存放到wwwroot文件下:

 

將相關文件引入默認布局頁面中:

八、 ASP.NET Core MVC 和 EF Core實現MySQL  CRUD功能:

注意在這裏主要展示的EF Core與數據庫操作的部分代碼,詳細代碼可下載實例源碼查看。

Create:

        /// <summary>
        /// 學生信息添加
        /// </summary>
        /// <param name="addUserInfo"></param>
        /// <returns></returns>
        public async Task<bool> Create(AddUserInfoViewModel addUserInfo)
        {
            try
            {
                var userInfo=new UserInfo()
                {
                    UserName = addUserInfo.UserName,
                    Sex = addUserInfo.Sex,
                    Hobby = addUserInfo.Hobby,
                    Phone = addUserInfo.Phone,
                    Description = addUserInfo.Description
                };

                _shoSchoolUserInfoContext.UserInfos.Add(userInfo);

                await _shoSchoolUserInfoContext.SaveChangesAsync();

                return true;
            }
            catch
            {
                return false;
            }
        }

Retrieve:

        /// <summary>
        /// 獲取用戶信息
        /// </summary>
        /// <param name="page">當前頁碼</param>
        /// <param name="limit">显示條數</param>
        /// <param name="userName">用戶姓名</param>
        /// <returns></returns>
        public async Task<PageSearchModel> GetPageListData(int page = 1, int limit = 15, string userName = "")
        {
            try
            {
                List<UserInfo> listData;
                var totalCount = 0;
                if (!string.IsNullOrWhiteSpace(userName))
                {
                    listData = await _shoSchoolUserInfoContext.UserInfos.Where(x => x.UserName.Contains(userName)).OrderByDescending(x => x.Id).Skip((page - 1) * limit).Take(limit).ToListAsync();

                    totalCount = _shoSchoolUserInfoContext.UserInfos
                        .Count(x => x.UserName.Contains(userName));
                }
                else
                {
                    listData = await _shoSchoolUserInfoContext.UserInfos.OrderByDescending(x => x.Id).Skip((page - 1) * limit).Take(limit).ToListAsync();

                    totalCount = _shoSchoolUserInfoContext.UserInfos.Count();
                }

                return new PageSearchModel()
                {
                    ResultMsg = "success",
                    Code = 200,
                    TotalCount = totalCount,
                    DataList = listData
                };
            }
            catch (Exception e)
            {
                return new PageSearchModel() { Code = 400, ResultMsg = e.Message };
            }
        }

Update:

        /// <summary>
        /// 學生信息修改
        /// </summary>
        /// <param name="userInfo"></param>
        /// <returns></returns>
        public async Task<bool> Update(UserInfo userInfo)
        {

            try
            {
                _shoSchoolUserInfoContext.UserInfos.Update(userInfo);

                await _shoSchoolUserInfoContext.SaveChangesAsync();

                return true;
            }
            catch
            {
                return false;
            }
        }

Delete:

        /// <summary>
        /// 學生信息刪除
        /// </summary>
        /// <param name="id"></param>
        /// <returns></returns>
        public async Task<bool> Delete(int? id)
        {
            try
            {
                var searchUserInfo = await _shoSchoolUserInfoContext.UserInfos.FindAsync(id);

                if (searchUserInfo == null)
                {
                    return false;
                }

                _shoSchoolUserInfoContext.UserInfos.Remove(searchUserInfo);
                await _shoSchoolUserInfoContext.SaveChangesAsync();

                return true;
            }
            catch
            {
                return false;
            }
        }

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

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

透過資料庫的網站架設建置,建立公司的形象或購物系統,並提供最人性化的使用介面,讓使用者能即時接收到相關的資訊

分類
發燒車訊

SpringAOP使用及源碼分析(SpringBoot下)_網頁設計

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

擁有專業的維修技術團隊,同時聘請資深iphone手機維修專家,現場說明手機問題,快速修理,沒修好不收錢

一、SpringAOP應用

  1. 先搭建一個SpringBoot項目
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
	xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
	<modelVersion>4.0.0</modelVersion>
	<parent>
		<groupId>org.springframework.boot</groupId>
		<artifactId>spring-boot-starter-parent</artifactId>
		<version>2.1.7.RELEASE</version>
		<relativePath/> <!-- lookup parent from repository -->
	</parent>
	<groupId>com.mmc</groupId>
	<artifactId>springboot-study</artifactId>
	<version>0.0.1-SNAPSHOT</version>
	<name>springboot-study</name>
	<description>Demo project for Spring Boot</description>

	<properties>
		<java.version>1.8</java.version>
	</properties>

	<dependencies>
		<dependency>
			<groupId>org.springframework.boot</groupId>
			<artifactId>spring-boot-starter-web</artifactId>
		</dependency>
		
	</dependencies>
	
	<build>
		<plugins>
			<plugin>
				<groupId>org.springframework.boot</groupId>
				<artifactId>spring-boot-maven-plugin</artifactId>
			</plugin>
		</plugins>
	</build>

</project>
  1. 定義一個業務邏輯類,作為切面
public interface CalculationService {

    /**
     * 加法運算
     * @param x
     * @param y
     * @return
     */
    public Integer add(Integer x,Integer y);
}

/**
 * @description:
 * @author: mmc
 * @create: 2020-06-01 14:22
 **/
@Service
public class CalculationServiceImpl implements CalculationService {

    @Override
    public Integer add(Integer x, Integer y) {
        if(x==null||y==null){
            throw  new NullPointerException("參數不能為空");
        }
        return x+y;
    }
}
  1. 定義一個切面類,添加通知方法
  • 前置通知(@Before):logStart:在目標方法(div)運行之前運行
  • 後置通知(@After):logEnd:在目標方法(add)運行結束之後運行(無論方法正常結束還是異常結束)
  • 返回通知(@AfterReturning):logReturn:在目標方法(add)正常返回之後運行
  • 異常通知(@AfterThrowing):logException:在目標方法(add)出現異常以後運行
  • 環繞通知(@Around):動態代理,手動推進目標方法運行(joinPoint.procced())

/**
 * @description:  切面類
 * @author: mmc
 * @create: 2020-06-01 14:24
 **/
@Aspect
@Component
public class LogAspects {

    //抽取公共的切入點表達式
    //1、本類引用
    //2、其他的切面引用
    @Pointcut("execution(public Integer com.mmc.springbootstudy.service.CalculationService.*(..))")
    public void pointCut(){};

    @Before("pointCut()")
    public void logStart(JoinPoint joinPoint){
        Object[] args = joinPoint.getArgs();
        System.out.println(""+joinPoint.getSignature().getName()+"運行。。。@Before:參數列表是:{"+Arrays.asList(args)+"}");
    }

    @After("pointCut()")
    public void logEnd(JoinPoint joinPoint){
        System.out.println(""+joinPoint.getSignature().getName()+"結束。。。@After");
    }


    //JoinPoint一定要出現在參數表的第一位
    @AfterReturning(value="pointCut()",returning="result")
    public void logReturn(JoinPoint joinPoint,Object result){
        System.out.println(""+joinPoint.getSignature().getName()+"正常返回。。。@AfterReturning:運行結果:{"+result+"}");
    }

    @AfterThrowing(value="pointCut()",throwing="exception")
    public void logException(JoinPoint joinPoint,Exception exception){
        System.out.println(""+joinPoint.getSignature().getName()+"異常。。。異常信息:{"+exception+"}");
    }
}
  1. 寫一個controller測試
@RequestMapping("/testaop")
   @ResponseBody
    public Integer testaop(Integer x,Integer y){
       Integer result = calculationService.add(x, y);
       return result;
   }
  1. 測試

add運行。。。@Before:參數列表是:{[2, 3]}
add結束。。。@After
add正常返回。。。@AfterReturning:運行結果:{5}

二、源碼分析

主線流程圖:

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

窩窩以「數位行銷」「品牌經營」「網站與應用程式」「印刷品設計」等四大主軸,為每一位客戶客製建立行銷脈絡及洞燭市場先機。

  1. spring.factories文件里引入了AopAutoConfiguration類
@Configuration
@ConditionalOnClass({ EnableAspectJAutoProxy.class, Aspect.class, Advice.class, AnnotatedElement.class })
@ConditionalOnProperty(prefix = "spring.aop", name = "auto", havingValue = "true", matchIfMissing = true)
public class AopAutoConfiguration {

	@Configuration
	@EnableAspectJAutoProxy(proxyTargetClass = false)
	//看配置文件,如果配置的spring.aop.proxy-target-class為false則引入JdkDynamicAutoProxyConfiguration
	@ConditionalOnProperty(prefix = "spring.aop", name = "proxy-target-class", havingValue = "false",
			matchIfMissing = false)
	public static class JdkDynamicAutoProxyConfiguration {

	}

	@Configuration
	//開啟AspectJAutoProxy
	@EnableAspectJAutoProxy(proxyTargetClass = true)
	//看配置文件,如果配置的spring.aop.proxy-target-class為true則引入CglibAutoProxyConfiguration 
	@ConditionalOnProperty(prefix = "spring.aop", name = "proxy-target-class", havingValue = "true",
			matchIfMissing = true)
	public static class CglibAutoProxyConfiguration {

	}

}

在包目錄下找到配置文件,並且發現他的值為true

在上面的方法上有EnableAspectJAutoProxy註解,並傳入了proxyTargetClass=true

  1. 進入@EnableAspectJAutoProxy註解
@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
//引入了AspectJAutoProxyRegistrar
@Import({AspectJAutoProxyRegistrar.class})
public @interface EnableAspectJAutoProxy {
    boolean proxyTargetClass() default false;

    boolean exposeProxy() default false;
}
  1. 進入AspectJAutoProxyRegistrar類
class AspectJAutoProxyRegistrar implements ImportBeanDefinitionRegistrar {
    AspectJAutoProxyRegistrar() {
    }

    public void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry) {
        //註冊了自動自動代理類
        AopConfigUtils.registerAspectJAnnotationAutoProxyCreatorIfNecessary(registry);
        AnnotationAttributes enableAspectJAutoProxy = AnnotationConfigUtils.attributesFor(importingClassMetadata, EnableAspectJAutoProxy.class);
        if (enableAspectJAutoProxy != null) {
            if (enableAspectJAutoProxy.getBoolean("proxyTargetClass")) {
                AopConfigUtils.forceAutoProxyCreatorToUseClassProxying(registry);
            }

            if (enableAspectJAutoProxy.getBoolean("exposeProxy")) {
                AopConfigUtils.forceAutoProxyCreatorToExposeProxy(registry);
            }
        }

    }
}
  1. 進入registerAspectJAnnotationAutoProxyCreatorIfNecessary方法裏面
 public static BeanDefinition registerAspectJAnnotationAutoProxyCreatorIfNecessary(BeanDefinitionRegistry registry, @Nullable Object source) {
        return registerOrEscalateApcAsRequired(AnnotationAwareAspectJAutoProxyCreator.class, registry, source);
    }

可以看到返回了一個BeanDefinition,裏面的BeanClass類型是AnnotationAwareAspectJAutoProxyCreator,這個類看名字是一個AOP的動態代理創建類,裏面沒有啥可疑的方法。在IDEA里按Ctrl+H看他的繼承結構。有一個父類AbstractAutoProxyCreator,這個類實現了BeanPostProcessor接口。這個接口是Bean的擴展接口,在bean初始化完成後會調用到他的postProcessAfterInitialization(Object bean, String beanName)方法。

  1. 方法內容如下
 public Object postProcessAfterInitialization(@Nullable Object bean, String beanName) {
        if (bean != null) {
            Object cacheKey = this.getCacheKey(bean.getClass(), beanName);
            if (this.earlyProxyReferences.remove(cacheKey) != bean) {
                //如果有必要,進行包裝  
                return this.wrapIfNecessary(bean, beanName, cacheKey);
            }
        }

        return bean;
    }
    
    protected Object wrapIfNecessary(Object bean, String beanName, Object cacheKey) {
        if (StringUtils.hasLength(beanName) && this.targetSourcedBeans.contains(beanName)) {
            return bean;
        } else if (Boolean.FALSE.equals(this.advisedBeans.get(cacheKey))) {
            return bean;
        } else if (!this.isInfrastructureClass(bean.getClass()) && !this.shouldSkip(bean.getClass(), beanName)) {
        //獲取切面的方法,第9點那裡展開討論
            Object[] specificInterceptors = this.getAdvicesAndAdvisorsForBean(bean.getClass(), beanName, (TargetSource)null);
            if (specificInterceptors != DO_NOT_PROXY) {
                this.advisedBeans.put(cacheKey, Boolean.TRUE);
                //創建動態代理
                Object proxy = this.createProxy(bean.getClass(), beanName, specificInterceptors, new SingletonTargetSource(bean));
                this.proxyTypes.put(cacheKey, proxy.getClass());
                return proxy;
            } else {
                this.advisedBeans.put(cacheKey, Boolean.FALSE);
                return bean;
            }
        } else {
            this.advisedBeans.put(cacheKey, Boolean.FALSE);
            return bean;
        }
    }
  1. 可以看出這裏已經在開始創建動態代理了
  protected Object createProxy(Class<?> beanClass, @Nullable String beanName, @Nullable Object[] specificInterceptors, TargetSource targetSource) {
        if (this.beanFactory instanceof ConfigurableListableBeanFactory) {
            AutoProxyUtils.exposeTargetClass((ConfigurableListableBeanFactory)this.beanFactory, beanName, beanClass);
        }
        //動態代理工廠
        ProxyFactory proxyFactory = new ProxyFactory();
        proxyFactory.copyFrom(this);
        if (!proxyFactory.isProxyTargetClass()) {
            if (this.shouldProxyTargetClass(beanClass, beanName)) {
                proxyFactory.setProxyTargetClass(true);
            } else {
                this.evaluateProxyInterfaces(beanClass, proxyFactory);
            }
        }

        Advisor[] advisors = this.buildAdvisors(beanName, specificInterceptors);
        //切面那裡的方法
        proxyFactory.addAdvisors(advisors);
        proxyFactory.setTargetSource(targetSource);
        this.customizeProxyFactory(proxyFactory);
        proxyFactory.setFrozen(this.freezeProxy);
        if (this.advisorsPreFiltered()) {
            proxyFactory.setPreFiltered(true);
        }
        //獲取動態代理類
        return proxyFactory.getProxy(this.getProxyClassLoader());
    }
  1. 學過AOP的人都知道動態代理的方式有兩種,一種JDK代理,一種CGLIB動態代理。那麼Spring裏面是怎麼選擇的呢?答案就在這裏:
 public AopProxy createAopProxy(AdvisedSupport config) throws AopConfigException {
   // 1.config.isOptimize()是否使用優化的代理策略,目前使用與CGLIB
        // config.isProxyTargetClass() 是否目標類本身被代理而不是目標類的接口
        // hasNoUserSuppliedProxyInterfaces()是否存在代理接口

        if (!config.isOptimize() && !config.isProxyTargetClass() && !this.hasNoUserSuppliedProxyInterfaces(config)) {
            return new JdkDynamicAopProxy(config);
        } else {
            Class<?> targetClass = config.getTargetClass();
            if (targetClass == null) {
                throw new AopConfigException("TargetSource cannot determine target class: Either an interface or a target is required for proxy creation.");
            } else {
                //目標類不是接口或不是代理類就使用cglib代理
                return (AopProxy)(!targetClass.isInterface() && !Proxy.isProxyClass(targetClass) ? new ObjenesisCglibAopProxy(config) : new JdkDynamicAopProxy(config));
            }
        }
    }
  1. Cglib的代理類是CglibAopProxy、ObjenesisCglibAopProxy,JDK的代理類是JdkDynamicAopProxy。在這些類裏面對目標類進行了代理,在執行方法的時候就是執行的代理類的方法,而實現了切面編程的效果。
  2. 主線流程就是這些了,還有一個沒說的就是我們如何獲取的切面方法,@Before(“pointCut()”)這些註解又是如何生效的?再回到AbstractAutoProxyCreator的wrapIfNecessary()方法
    裏面有這句代碼:
 Object[] specificInterceptors = this.getAdvicesAndAdvisorsForBean(bean.getClass(), beanName, (TargetSource)null);
 
 
  @Nullable
    protected Object[] getAdvicesAndAdvisorsForBean(Class<?> beanClass, String beanName, @Nullable TargetSource targetSource) {
        List<Advisor> advisors = this.findEligibleAdvisors(beanClass, beanName);
        return advisors.isEmpty() ? DO_NOT_PROXY : advisors.toArray();
    }
    
    protected List<Advisor> findEligibleAdvisors(Class<?> beanClass, String beanName) {
        //查找候選的要切面附加的方法,這裏加進去的
        List<Advisor> candidateAdvisors = this.findCandidateAdvisors();
        List<Advisor> eligibleAdvisors = this.findAdvisorsThatCanApply(candidateAdvisors, beanClass, beanName);
        this.extendAdvisors(eligibleAdvisors);
        if (!eligibleAdvisors.isEmpty()) {
            eligibleAdvisors = this.sortAdvisors(eligibleAdvisors);
        }

        return eligibleAdvisors;
    }
    
    
    
  1. 他會找到Aspect類,然後遍歷裏面的方法,並獲取Pointcut,然後構造出Advisor,加入到集合List advisors里,供動態代理時使用

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

台北網頁設計公司這麼多該如何選擇?

網動是一群專業、熱情、向前行的工作團隊,我們擁有靈活的組織與溝通的能力,能傾聽客戶聲音,激發創意的火花,呈現完美的作品

分類
發燒車訊

Hystrix微服務容錯處理及回退方法源碼分析_貨運

※智慧手機時代的來臨,RWD網頁設計為架站首選

網動結合了許多網際網路業界的菁英共同研發簡單易操作的架站工具,及時性的更新,為客戶創造出更多的網路商機。

前言

在 SpringCloud 微服務項目中,我們有了 Eureka 做服務的註冊中心,進行服務的註冊與發現和服務治理。使得我們可以摒棄硬編碼式的 ip:端口 + 映射路徑 來發送請求。我們有了 Feign 作為聲明式服務調用組件,可以像調用本地服務一樣來調用遠程服務。基於 Ribbon 我們又實現了客戶端負載均衡,輕鬆的在集群環境下選取合適的服務提供者。這樣看來我們的微服務貌似很完善了。是這樣的嗎?

並非如此,想想我們在編碼過程中進行的健壯性檢查。類比一下服務與服務調用是否也應該更加健壯一些呢?我們目前的微服務在正常運行的時候是沒有問題的,但若是某個偏下游的服務提供者不可用,造成服務積壓,接連引起上游的服務消費者宕機,引法雪崩效應。是不是就顯得我們的微服務不堪一擊呢?因此我們需要一個組件來解決這樣的問題,前輩們參考生活中保險絲的原理做出了微服務中的保險絲-Hystrix熔斷器。下面讓我們來一起使用一下

聲明:本文首發於博客園,作者:后青春期的Keats;地址:https://www.cnblogs.com/keatsCoder/ 轉載請註明,謝謝!

Hystrix簡介

Hystrix主要實現了下面的功能:

  • 包裹請求:使用 HystrixCommand(或 HystrixObservableCommand) 包裹對依賴的調用邏輯。每個命令在獨立的線程中執行,使用了設計模式中的‘命令模式’
  • 跳閘機制:當某微服務的錯誤率超過一定閾值時,可以自動跳閘,停止請求該服務一段時間
  • 資源隔離:Hystrix 為每個微服務都維護了一個小型的線程池(或信號量)如果該線程池已滿,發往該依賴的請求就會被立即拒絕
  • 監控:Hystrix 可以近乎實時的監控運行指標和配置的變化,例如成功、失敗、超時和被拒絕的請求等
  • 回退機制:當請求成功、失敗、超時和被拒絕或者斷路器打開時,執行回退邏輯。回退邏輯可由開發人員自行提供
  • 自我修復:斷路器打開一段時間后,會進入‘半開’狀態,允許一個請求訪問服務提供方,如果成功。則關閉斷路器

使用 Hystrix

引入依賴

        <!-- 熔斷器 hystrix -->
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-netflix-hystrix</artifactId>
        </dependency>

在啟動類上添加 @EnableHystrix

兩種情況下的回退方法

非 Feign 調用下的回退方法

編寫回退方法
/**
 * getUserByAge 方法 Hystrix 回退方法
 * @param age
 * @return
 */
public User getUserByAgeFallBack(Integer age){
    User user = new User();
    user.setName("默認用戶");
    user.setAge(age);
    return user;
}
在客戶端的方法上聲明
@HystrixCommand(fallbackMethod = "getUserByAgeFallBack")

測試:將服務提供方的代碼打斷點。調用服務消費方,會發現返回了默認用戶

需要注意:

  1. 回退方法的返回值類型需要和原來方法返回值類型相同(否則會報 FallbackDefinitionException: Incompatible return types)
  2. 回退方法的參數列表也要和原來方法相同(否則會報 FallbackDefinitionException: fallback method wasn’t found: getUserByAgeFallBack([class java.lang.Integer]))
  3. 當我寫下第二句時,發現書中下一節介紹說可以通過在回退方法中添加第二個參數:ThrowEable 來捕獲異常,分析調用失敗的原因,我就知道我錯了。為了避免繼續得到錯誤的結論,我決定讀一讀 Hystrix 處理回退方法的源碼
加點料:Hystrix 對回退方法的封裝的源碼如下:
com.netflix.hystrix.contrib.javanica.utils.MethodProvider
public FallbackMethod find(Class<?> enclosingType, Method commandMethod, boolean extended) {
	// 首先判斷該方法的 HystrixCommand 註解上有沒有 defaultFallback / fallbackMethod 配置回退方法名稱
    if (this.canHandle(enclosingType, commandMethod)) {
    	// 調用 doFind 方法
        return this.doFind(enclosingType, commandMethod, extended);
    } else {
    	// 沒有配置的化就接着下一個判斷
        return this.next != null ? this.next.find(enclosingType, commandMethod, extended) : FallbackMethod.ABSENT;
    }
}

find 方法在用戶所請求的方法的 HystrixCommand 註解上有用 defaultFallback / fallbackMethod 配置回退方法名稱的時候,會調用 doFind 方法來尋找回退方法。該方法的參數有兩個,enclosingType 是用戶所請求的方法的類字節碼文件,commandMethod 是用戶所請求的方法

首先通過 this.getFallbackName 獲取回退方法名稱,接着通過獲取 commandMethod 的參數類型們

接着分兩種情況:

  1. 回調方法繼承於 commandMethod 且最後一個參數類型是 Throwable,則去掉回退方法參數列表中的 Throwable 類型進行匹配
  2. 回調方法不繼承於 commandMethod ,則存在兩個可能的參數類型列表: fallbackParameterTypes 和 extendedFallbackParameterTypes 前者是 commandMethod 是參數列表,後者是前者 + Throwable。然後兩個都進行匹配。接着使用 Java8 Optional API,按順序選取前者匹配到的方法 / 後者 / 空返回
private FallbackMethod doFind(Class<?> enclosingType, Method commandMethod, boolean extended) {
    String name = this.getFallbackName(enclosingType, commandMethod);
    Class<?>[] fallbackParameterTypes = null;
    if (this.isDefault()) {
        fallbackParameterTypes = new Class[0];
    } else {
        fallbackParameterTypes = commandMethod.getParameterTypes();
    }

    if (extended && fallbackParameterTypes[fallbackParameterTypes.length - 1] == Throwable.class) {
        fallbackParameterTypes = (Class[])ArrayUtils.remove(fallbackParameterTypes, fallbackParameterTypes.length - 1);
    }

    Class<?>[] extendedFallbackParameterTypes = (Class[])Arrays.copyOf(fallbackParameterTypes, fallbackParameterTypes.length + 1);
    extendedFallbackParameterTypes[fallbackParameterTypes.length] = Throwable.class;
    Optional<Method> exFallbackMethod = MethodProvider.getMethod(enclosingType, name, extendedFallbackParameterTypes);
    Optional<Method> fMethod = MethodProvider.getMethod(enclosingType, name, fallbackParameterTypes);
    Method method = (Method)exFallbackMethod.or(fMethod).orNull();
    if (method == null) {
        throw new FallbackDefinitionException("fallback method wasn't found: " + name + "(" + Arrays.toString(fallbackParameterTypes) + ")");
    } else {
        return new FallbackMethod(method, exFallbackMethod.isPresent(), this.isDefault());
    }
}

由源碼可以得到結論:回退方法要麼參數列表和原始方法相同,要麼加且僅加一個類型為 Throwable 的參數。其他的都不行

Feign 客戶端下的回退方法

  1. 設置:feign.hystrix.enabled: true

    ※回頭車貨運收費標準

    宇安交通關係企業,自成立迄今,即秉持著「以誠待人」、「以實處事」的企業信念

  2. Feign 客戶端接口上的 @FeignClient 添加 fallback 屬性,指向回退類

  3. 回退類實現客戶端接口

# feign的配置
feign:
  hystrix:
    enabled: true # 打開 feign 的 hystrix 支持

注意回退類加上 @Component 接口,避免因為 Spring 容器找不到該類而啟動報錯

// Feign 客戶端接口上的 @FeignClient 添加 fallback 屬性,指向回退類
@FeignClient(name = "SERVICE-PROVIDER", fallback = UserServiceFeignClientFallBack.class)
public interface UserServiceFeignClient {

    @GetMapping("/api/v1/user/{age}")
    User getUser(@PathVariable("age") Integer age);

    /**
     * 用戶列表
     * @return
     */
    @GetMapping("/api/v1/users")
    List<User> getUsers();
}
// 回退類實現客戶端接口
@Component 
public class UserServiceFeignClientFallBack implements UserServiceFeignClient {
    @Override
    public User getUser(Integer age) {
        return null;
    }

    @Override
    public List<User> getUsers() {
        return null;
    }
}

當採用 Feign 客戶端來實現回退的時候,前面的捕捉異常方法就不起作用了,那我們應該如何來處理異常呢?可以使用 @FeignClient 的 fallbackFactory 屬性

@FeignClient(name = "SERVICE-PROVIDER", fallbackFactory = UserServiceFallbackFactory.class)

@Component
@Slf4j
public class UserServiceFallbackFactory implements FallbackFactory<UserServiceFeignClient> {
    @Override
    public UserServiceFeignClient create(Throwable t) {
        // 日誌最好寫在各個 fallback 方法中,而不要直接卸載 create方法中
        // 否則引用啟動時就會打印該日誌

        return new UserServiceFeignClient() {
            @Override
            public User getUser(Integer age) {
                log.info("調用User服務提供者失敗", t);
                User user = new User();
                user.setName("默認用戶");
                user.setAge(age);
                return user;
            }

            @Override
            public List<User> getUsers() {
                return null;
            }
        };
    }
}

注意: **fallback 和 fallbackFactory 屬性同時存在時,fallback 的優先級更高。因此開發中如果需要處理異常,只需配置 fallbackFactory 屬性即可 **

避免業務異常走進回退方法

在某些場景下,當發生業務異常時,我們並不想觸發 fallback。例如業務中判斷年齡 age 不能小於 1,否則拋出異常

if(age < 1){
    throw new KeatsException(ExceptionEnum.NUM_LESS_THAN_MIN);
}

這時 Hystrix 會捕捉到異常然後執行 fallback 方法,我們可以通過下面兩個方法來避免:

  1. 繼承 HystrixBadRequestException 該類繼承自 RunntimeException
  2. 在 @HystrixCommand 添加屬性 ignoreExceptions = {KeatsException.class}

為 Feign 禁用 Hystrix

只要打開 feign 的 hystrix 支持開關,feign 就會使用斷路器包裹 feign 客戶端的所有方法,但很多場景並不需要這樣。該如何禁用呢?

  • 為指定客戶端禁用。需要藉助 Feign 的自定義配置。首先添加一個自定義配置類,然後配置到 @FeignClient 的 configuration 屬性中
@Configuration
public class FeignDisableHystrixConfiguration {
    @Bean
    @Scope("prototype")
    public Feign.Builder feignBuilder(){
        return Feign.builder();
    }
}

@FeignClient(name = "SERVICE-PROVIDER", configuration = {FeignDisableHystrixConfiguration.class})
  • 全局禁用: feign.hystrix.enabled: false

本博客中所有示例代碼都已上傳至 github倉庫: https://github.com/keatsCoder/cloud-cli

參考文獻:《Spring Cloud與Docker 微服務架構實戰》 — 周立

碼字不易,如果你覺得讀完以後有收穫,不妨點個推薦讓更多的人看到吧!

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

※評比南投搬家公司費用收費行情懶人包大公開

搬家價格與搬家費用透明合理,不亂收費。本公司提供下列三種搬家計費方案,由資深專業組長到府估價,替客戶量身規劃選擇最經濟節省的計費方式

分類
發燒車訊

漢字不能編程?只是看着有點豪橫!容易被開除!_包裝設計

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

上新台中搬家公司提供您一套專業有效率且人性化的辦公室搬遷、公司行號搬家及工廠遷廠的搬家服務

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

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

一、前言

在編程的路上你是否想過,用漢字寫一寫代碼?

最近有初學編程的小夥伴問小傅哥,漢字可以寫代碼嗎。自己英文不好,要是漢字可以寫代碼就好了。難道你要的是易語言?其實並不是,小夥伴也是學習 Java 的初學者,剛剛學習到 Spring 看着一片沒有註釋的代碼實在不好理解,要是都是漢字寫的,那不和讀作文一樣了嗎!

說道註釋,我想到大部分程序員討厭的兩件事

  1. 不喜歡寫註釋
  2. 不喜歡別人不寫註釋

其實對於學習編程來說,初學時寫寫案例,完成簡單的功能,反覆練習夯實基礎。數學和英文都還並不是你的絆腳石,因為你不需要做複雜的邏輯處理,比如算法。也不需要查閱大量的資料,比如原版的英文資料以及國內沒有翻譯的技術書籍等。所以這個時候對你來說,只是需要不斷的學,不斷的寫。並逐步強加自己的數學和英文能力。

回到我們的說的,既然你問漢字可以寫代碼嗎。其實在 java 里,原則上你可以寫漢字的屬性方法JVM虛擬機也是可以通過編譯執行的。只是這樣的代碼並不能很好的維護,甚至說亂碼了也很麻煩。再者,有人寫方言怎麼辦!

好!那麼我們接下來就使用漢字的方式來編寫一段關於 SpringAop 的案例!

二、開發環境

  1. JDK 1.8.0
  2. Spring 4.3.24.RELEASE
  3. 本篇涉及的源碼下載,可以關注公眾號bugstack蟲洞棧 獲取,並且還可以獲取更多原創案例。

三、技術實現

為了這個案例更加真實,我們模擬電影清朝韋小寶時期,太監入宮的過程。說白了也就是 SpringAOP 面向切面,的編程。

在做案例之前,我們先了解一下 AOP 的基本概念;

  1. @Aspect,定義切面的註解
  2. @Pointcut,切入點,一般會在方法上設定通配符表達式
  3. @Around,環繞,也就是你原本的方法會在這裏處理
  4. @Before,前置處理
  5. @After,後置處理

1. 定義切面

紫禁城.內務府.敬事房.臏.太監臏.凈身監管.java

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

窩窩觸角包含自媒體、自有平台及其他國家營銷業務等,多角化經營並具有國際觀的永續理念。

@Aspect
@Component
public class 凈身監管 {

    @Pointcut("execution(public * 紫禁城.內務府.敬事房.利器庫..*.軍刺切(..))")
    public void 監管員(){

    }

    @Before("監管員()")
    public void 敬事前(){
        System.out.println("敬事前:---------準備下刀... ...");
    }

    @After("監管員()")
    public void 敬事後(){
        System.out.println("敬事後:---------切面完成... ...");
    }

    @Around("監管員()")
    public Object aroundMethod(ProceedingJoinPoint pjp) throws Throwable {

        System.out.println("待切身份:"+pjp.getArgs()[0]);
        System.out.println("執行工具:"+pjp.getSignature().getName());

        //獲得傳遞對象,並做處理
        太監臏 太監 = (太監臏) pjp.getArgs()[0];
        SimpleDateFormat timeFormat = new SimpleDateFormat("yyyy-MM-dd");
        太監.set敬事日期(timeFormat.format(new Date()));

        //此處可以傳遞更改后的參數
        Object obj = pjp.proceed(new Object[]{太監});

        return obj;

    }

}
  • @Aspect,定義切面類,用於處理程序中的切面編程操作。
  • @Pointcut("execution(public * 紫禁城.內務府.敬事房.利器庫..*.軍刺切(..))"),定義切點處,對那些方法進行執行切面操作。除了這樣的操作外,還可以定義成自定義註解。那麼後續只要把某個你需要的方法上面添加這樣的自定義註解,就可以被 AOP 攔截。
  • @Before("監管員()")@After("監管員()"),記錄切面執行前後的記錄。
  • @Around("監管員()"),用於環繞方法增強,可以這裏去處理方法中的一些屬性信息,比如添加給某個字段添加時間。太監.set敬事日期(timeFormat.format(new Date()));

2. 設置切面可執行方法

紫禁城.內務府.敬事房.利器庫.切除器具.java

@Component("切除")
public class 切除器具 {

    public 太監臏 軍刺切(太監臏 太監){
        太監.set性別(宦官.太監.name());
        System.out.println("... 啊 ... ...老子被切面了!"+太監.get姓名());
        return 太監;
    }

}
  • 這裏類的方法就是上面定義的切點,Pointcut,也就是會被切面處理的方法。

3. 執行切面操作類

紫禁城.內務府.敬事房.執刀人.張三豐執刀.java

public class 張三豐執刀 {

    public static void main(String[] args) {

        ApplicationContext ctx = new ClassPathXmlApplicationContext("皇太極.xml");

        太監刑 太監行刑 = ctx.getBean("太監刑",太監刑.class);

        太監臏 太監 = new 太監臏();
        太監.set姓名("小德張");
        太監.set年齡("9");
        太監.set性別(宦官.男.name());

        太監 = 太監行刑.執行切除(太監);

        System.out.println("\r\n切除狀態:"+太監);
    }

}
  • 首先這裏定義了獲取 Spring 註解的 Application,用於我們獲取 Bean
  • 接下來定義一個對象類,主要傳遞具體參數信息交給執行切面的方法,進行操作。
  • 最後輸出結果信息,也就是我們可以看到具體被切面操作的方法。

四、測試結果

1. 執行內容

啟動方法;紫禁城.內務府.敬事房.執刀人.張三豐執刀.java

待切身份:太監臏 [姓名=小德張, 年齡=9, 性別=男, 敬事日期=]
執行工具:軍刺切
敬事前:---------準備下刀... ...
... 啊 ... ...老子被切面了!小德張
敬事後:---------切面完成... ...

切除狀態:太監臏 [姓名=小德張, 年齡=9, 性別=太監, 敬事日期=2020-05-05]

Process finished with exit code 0

2. 效果圖

五、總結

  • 漢字編程,好奇可以試試,但別真的用到項目里。本文也只是通過這樣的例子,向你展示學習過程的樂趣,建立一些學習過程的好感。

  • 最近加了很多剛入門學習編程的小夥伴,有很多小問號。比如;

    我是非常建議先跑起來,多寫代碼后再慢慢的去探究原理!

  • 最近聽到一首詩,不錯;廿四橋邊廿四風,憑欄猶憶舊江東。夕陽返照桃花渡,柳絮飛來片片紅。,白色的柳絮在夕陽桃花的映襯下就是成了片片紅。只要你敢學識淵博,就敢讓你擁有翻江倒海之力。

六、彩蛋

CodeGuide | 程序員編碼指南 Go!

本代碼庫是作者小傅哥多年從事一線互聯網 Java 開發的學習歷程技術匯總,旨在為大家提供一個清晰詳細的學習教程,側重點更傾向編寫Java核心內容。如果本倉庫能為您提供幫助,請給予支持(關注、點贊、分享)!

七、推薦閱讀

  • 用Java實現JVM虛擬機(漢字真的可以處理為關鍵字進行編碼)
  • 重學 Java 設計模式:實戰單例模式(Effective Java 作者推薦使用枚舉的方式解決單例模式)
  • 重學 Java 設計模式:實戰原型模式(多套試卷亂序題目)
  • 重學 Java 設計模式:實戰建造者模式(裝修套餐選配)
  • 重學 Java 設計模式:實戰抽象工廠模式(Redis集群使用升級)
  • 重學 Java 設計模式:實戰工廠方法模式(多種商品發獎)

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

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

網動廣告出品的網頁設計,採用精簡與質感的CSS語法,提升企業的專業形象與簡約舒適的瀏覽體驗,讓瀏覽者第一眼就愛上她。

分類
發燒車訊

解讀四大亮點——這輛車帶你尊享貴賓禮遇_網頁設計公司

※綠能、環保無空污,成為電動車最新代名詞,目前市場使用率逐漸普及化

台中景泰電動車行只是一個單純的理由,將來台灣的環境,出門可以自由放心的深呼吸,讓空氣回歸自然的乾淨,減少污染,留給我們下一代有好品質無空污的優質環境

2。異地維修享7天五星級酒店住宿權而當車輛在異地發生事故需要長時間維修時,沃爾沃、捷豹路虎和謳歌三大品牌會提供5星級酒店客房,免費供房的天數3-5天不等。奔馳、寶馬也不落後,都有這一服務,但免費住宿的標準稍低,為四星級酒店。

10月21日,萬眾矚目的上汽大眾pHIDEON 輝昂在中國第一高樓——上海中心正式上市。當晚,pHIDEON輝昂上市的消息就迅速登上了各大汽車媒體的頭條。但當各大媒體都把焦點放在售價上的同時,卻忽略了另一個重點,那就是與售價同時公布的針對輝昂車主專屬的“尊享權益”。這些權益到底能讓車主得到什麼密切的利益呢?小編就在這裏整理了四條和車主密切相關的權益,並利用数字來做進一步解讀。

亮點一:

最高4年/12萬公里質量擔保服務和全程無憂免費保養

長久以來,與豪華品牌車型價格“相配”的高昂保養費用,讓很多准車主們望而卻步。而這次輝昂針對其車主推出的專屬“尊享權益”。在眾多的尊享權益中,最讓人驚喜的是上汽大眾針對所有輝昂車主推出的質量擔保期內,所有保養手冊上列明的保養項目全都執行免費保養政策。而說到質量擔保期,除了入門款的車型按照國家規定提供3年10萬公里的常規質量擔保,其他7款車型,均可享受到廠家額外贈送的1年/2萬公里延長質量擔保期的服務,也就是說御尊豪華版及以上車型可享受4年/12萬公里質量擔保服務和免費保養。這一舉措不僅显示了對其自身產品的自信,而且真心誠意為消費者減輕用車養車的成本。為了能直觀表現這一免費政策,我們計算一下,傳統德系三強BBA車型,它們的保養費用到底為多少?通過對比保養費用,就能發現,這一權益能為輝昂車主省了多少後期養車費用。

與高配車型車主對保養數據不敏感相比,低配車主對於車輛保養這事兒還是比較在乎的,上述表格數據對應車型分別為:E200L運動型、A6L TFSI技術型、520Li典雅型。可以看出,除了奧迪贈送首保之外,奔馳和寶馬車主均不能享此項優惠。它們行駛6萬公里后的保養費用總額分別為12,468元、17,280.76元和14,586元。隨着車況的下降,行駛12萬公里的車輛保養費用,不只是簡單的翻倍而已。

除了保養方面輝昂車主可以節省一大筆支出外,輝昂的延保也是誠意十足。輝昂御尊版車型提供3年10萬公里質保的前提下,剩餘的7款車型均為4年12萬公里質保服務,

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

網站的第一印象網頁設計,決定了客戶是否繼續瀏覽的意願。台北網動廣告製作的RWD網頁設計,採用精簡與質感的CSS語法,提升企業的專業形象與簡約舒適的瀏覽體驗,讓瀏覽者第一眼就愛上它。

等於是廠家額外提供了1年/2萬公里的免費延保服務。而BBA方面,因為沒有官方的延保政策可循,媒體通過走訪多家4S店的形式,發現他們的一年延保費用大致都在5,000元左右,而這樣直觀的對比,表明了上汽大眾對輝昂車主足夠“大方”;更可看做輝昂車主在行駛12萬公里的過程中,僅需支付日常的油費和保險開銷,其他費用幾乎為零。

亮點二:

專屬品牌保險權益驚喜頗多

既然提到了保險,我們也來說一下輝昂的品牌保險權益。從本次公布的信息中不難發現,購買輝昂品牌車險的上汽大眾輝昂車主不但可以享受到不限里程不限次數的事故現場緊急維修救援服務,還能享受2人國內15天旅行的額外保障服務和事故維修期間的出行補償。小編就維修期補償這一權益,為大家做一下全面解析:

1.本地維修享7天(最高300元/天)打車報銷

有豪華車消費者反映,與豪華車銷售保持增長趨勢不同的是,豪華車的售後服務並無多大改善。比如說當車輛出現故障需要較長時間維修時,寶馬、雷克薩斯不會為車主提供任何權益;其他豪華品牌通常是調配經銷商處的試駕車給車主臨時使用。雖說這樣做也是出於給車主解決困難的目的,但並沒有考慮到如果車主對車輛性能並不了解,就會存在安全隱患。

上汽大眾在這一點上則顯得更為細心,如果輝昂在本地出現維修情況,車主即可享受7天打車費報銷權益,每天最高300元。這樣一來,既不會存在因對試駕車性能不熟悉引發意外事故的隱患,還可以在一周時間里依舊享受着便捷的有車生活,省時省心、無後顧之憂。

2.異地維修享7天五星級酒店住宿權

而當車輛在異地發生事故需要長時間維修時,沃爾沃、捷豹路虎和謳歌三大品牌會提供5星級酒店客房,免費供房的天數3-5天不等。奔馳、寶馬也不落後,都有這一服務,但免費住宿的標準稍低,為四星級酒店。豪華品牌中唯有奧迪不提供這一服務。

上汽大眾雖然是進軍豪華轎車市場的後來者,在異地住宿服務上反倒是表現得最有誠意。假如pHIDEON 輝昂出現異地維修情況,車主即可享受最長7天入住五星級酒店的住宿權益。以北上廣知名的幾家五星級酒店為例,平均算下來,7天房費至少有9205元。這可是一筆不小的開銷,上汽大眾願意出這份錢,既是對車主身在異地等待修車焦慮心情的撫慰,亦能使車主無論身在何處都能享受到貴賓級的尊榮體驗。

亮點三:

首付低至30% 兩年零利率信貸優本站聲明:網站內容來源於http://www.auto6s.com/,如有侵權,請聯繫我們,我們將及時處理

※如何讓商品強力曝光呢? 網頁設計公司幫您建置最吸引人的網站,提高曝光率!

以設計的實用美學觀點,規劃出舒適、美觀的視覺畫面,有效提昇使用者的心理期待,營造出輕鬆、愉悅的網站瀏覽體驗。