部落格

  • 蘋果電動車專案未止步,挖角、研發行動低調進行中

    市場瘋傳蘋果(Apple)將推出電動車,但至今仍只聞樓梯響。不過,海外媒體持續傳出蘋果電動車專案仍有大小動作,包含聘用新人、營運研發實驗室等,計畫看來並未胎死腹中。

    蘋果電動車的計畫代號傳聞為「Titan」,傳言將在2019年正式開始出貨。蘋果電動車被報導將搭載自動駕駛技術,也會研究採用載運工具共享服務。

    科技網站Electrek報導,蘋果將聘用原特斯拉(Tesla)工程開發副總裁Chris Porritt接替將要離職的Steve Zadesky,繼續執行與電動車專案相關的工作。而在這之前,蘋果已從特斯拉、福特、賓士、通用汽車等國際車廠與電池廠挖角專家,也聘用曾在福斯汽車、Nvidia等公司服務,專注於先進駕駛輔助系統與自動駕駛系統領域的專業人士。

    此外,MacRumors也引述德國FrankfurterAllgemeine Zeitung報導,指出蘋果正在德國柏林暗中營運一座研發實驗室,共有15~20名研發人員,主要來自德國汽車產業界,或許正是蘋果電動車專案的一部分。

    蘋果執行長Tim Cook過去曾參訪BMW的電動車i3產線,與電動車相關的傳言滿天飛。但蘋果對於電動車計畫始終守口如瓶,沒有正面回應。

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

    【其他文章推薦】

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

    網頁設計一頭霧水??該從何著手呢? 找到專業技術的網頁設計公司,幫您輕鬆架站!

    ※想知道最厲害的台北網頁設計公司推薦台中網頁設計公司推薦專業設計師”嚨底家”!!

    大陸寄台灣空運注意事項

    大陸海運台灣交貨時間多久?

    ※避免吃悶虧無故遭抬價!台中搬家公司免費估價,有契約讓您安心有保障!

  • 樂視超級汽車LeSEE亮相 首推無人駕駛功能

    樂視超級汽車LeSEE亮相 首推無人駕駛功能

    4月21日,在樂視體育生態中心,樂視舉辦“無破界 不生態”為主題的春季新品發佈會。面對全球2600名媒體記者和近8000名樂迷,樂視發佈無人駕駛超級汽車。

    賈躍亭與樂視超級汽車聯合創始人、全球副董事長丁磊先生用極具創意的方式揭幕了LeSEE品牌的首款概念樣車,並演示了超級汽車的自動駕駛功能。

    LeSEE的這款概念車有著獨具“互聯網感”的外形,前臉配有超大炫酷的LED屏,可向路人顯示車輛狀態。這款概念車主打智慧互聯概念,不僅可以實現自動駕駛功能,還可實現自我學習,具備人臉識別、情緒識別、環境識別和路徑識別等功能。

    由於時間關係,當晚的汽車亮相環節只有不到五分鐘,萬眾期待的超級汽車可以用“猶抱琵琶半遮面”來形容,觀眾們都大呼“不過癮”。丁磊表示,如果想親密接觸這款凝聚了樂視汽車生態理念的智能互聯網電動車,只需在5天之後的北京車展上一睹為快。

    據丁磊透露,除了這款承載了樂視互聯電動智慧共用交通生態系統的概念車,樂視在將在近期宣佈和國內一家主流汽車集團的合作,以及在美國矽谷成立全球首家汽車主機廠與互聯網公司共同成立的人工智慧研究院——“FF& Le Future”人工智慧研究院。

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

    【其他文章推薦】

    ※想知道網站建置網站改版該如何進行嗎?將由專業工程師為您規劃客製化網頁設計後台網頁設計

    ※不管是台北網頁設計公司台中網頁設計公司,全省皆有專員為您服務

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

    ※帶您來看台北網站建置台北網頁設計,各種案例分享

    小三通物流營運型態?

    ※快速運回,大陸空運推薦?

  • 三菱汽車承認汽車油耗測試造假

    據外媒報導,日商三菱汽車(Mitsubishi Motors)日前承認在汽車油耗測試中造假,涉及日本市場62.5萬輛汽車,包括日產汽車所供應的兩款車型。三菱汽車CEO相川哲郎為此在東京召開新聞發佈會,為公司不當行為致歉,並表示「感到羞恥。」

    測試的車型包括四款三菱汽車製造的微型車,包括三菱品牌eK Wagon和eK Space,以及同日產汽車合作開發的DayZ和DayZ Roox。這類微型車搭載發動機排量均不超過660cc,屬於日本市場特有的 KeiCar。其中,以三菱品牌銷售的車輛為157,000輛,日產品牌車輛為468,000輛,總計約625,000輛。

    根據三菱汽車方面的解釋,在測試中,車輛在輪胎和空氣阻力等方面進行舞弊,使得燃油經濟性測試結果好於真實情況,差距達到5%至10%左右。相川哲郎承認測試資料「蓄意為之」,存在誤導性。

    三菱汽車表示,目前所有四款車型已經停止生產和銷售,此外也在調查作弊行為是否涉及海外車輛。公司暫時還無法估計舞弊醜聞對業務的影響。此外,三菱還表示,自從2002年以來公司所使用的續航里程測試方法便和日本國家標準並不吻合。

    三菱曾表示將與日產合作開發電動車,也曾自行開發平價電動車款 i miev 。但並未引發熱烈的市場反應。

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

    【其他文章推薦】

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

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

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

    台灣寄大陸海運貨物規則及重量限制?

    大陸寄台灣海運費用試算一覽表

    台中搬家,彰化搬家,南投搬家前需注意的眉眉角角,別等搬了再說!

  • 長安李偉:汽車智慧化分為智慧駕駛、智慧互聯和智慧交互三大發展範疇

    未來汽車應該是什麼樣子?每個人都有自己的想像,每個車企都有各自的概念。而在差異化之中的相似之處是,許多企業都把智慧化作為發展方向,將無人駕駛作為嘗試的關鍵步驟。

    長安汽車副總裁李偉表示智慧汽車可分為三大發展範疇:智慧駕駛、智慧互聯和智慧交互,而智慧駕駛又可分為四級技術水準。

    智慧汽車的第一個範疇——智慧駕駛。對於長安汽車來講,智慧駕駛的一級技術已經成熟且在車上搭載,例如全速的自我調整巡航,緊急高速自動、緊急制動等,這些技術都已經在16款睿騁、CS75、逸動等車型上實現量產;智慧駕駛的二級技術,現在已經在做產品的研發和測試,二級技術主要是在一級自我調整巡航系統的基礎上升級,爭取把手解脫了,另外再加一個全自動倒車,二級的系統長安預計在2017年要量產;智慧駕駛三級技術水準是實現在高速路段的無人駕駛,從重慶到北京的整個無人駕駛汽車,實際上就是智慧駕駛的三級水準,長安汽車計畫在2018年實現整個技術儲備開發,全部匹配結束,2019年能夠得以上市。另外全自動化駕駛技術,就是智慧駕駛的最高級四級,長安努力爭取在2025年前能夠實現量產。

    智能汽車的第二個範疇——智能互聯。李偉簡單舉了個例子,“現在長安在美國MTC現場進行叫智慧互聯汽車,它實際上是車和車可以通訊,車和路可以通訊,車和交通信號可以通訊等等。大家如果設想一下,我們現在長安目前的無人駕駛狀態,實際上是靠車本身的信號識別來判斷我的交通情況。未來如果說我們城市是智慧城市,我的交通都是數位的信號,在一公里之前車就能感知我那邊的紅綠燈什麼時候變紅燈什麼時候變綠燈,除了前邊車之外還有什麼車,通過車和車的通訊就會知道,這樣整個交通就會更加更加智慧。未來智慧城市,車聯網和車更加融合,這個車就會更加智慧,沒看到就會知道是什麼前邊情況,從這個方面來講,傳統自動智慧駕駛汽車和智慧互聯再有機的融合,就會帶來更聰明的汽車,就會有更自動的汽車。”

    智能汽車的第三個範疇——智能交互。這個階段就是所謂的”人機交互”階段,需要發出什麼指令,不用操作,也不用說出來,只需要在腦袋裡一想,汽車就能執行相應命令。

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

    【其他文章推薦】

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

    網頁設計一頭霧水??該從何著手呢? 找到專業技術的網頁設計公司,幫您輕鬆架站!

    ※想知道最厲害的台北網頁設計公司推薦台中網頁設計公司推薦專業設計師”嚨底家”!!

    大陸寄台灣空運注意事項

    大陸海運台灣交貨時間多久?

    ※避免吃悶虧無故遭抬價!台中搬家公司免費估價,有契約讓您安心有保障!

  • 西門子聯手法雷奧打造電動汽車動力總成領域的全球領導企業

    西門子日前與法雷奧集團簽訂協定,雙方將共同組建一家從事高壓動力總成業務的合資企業,從而在創新和經濟型高壓元件及系統業務領域佔據全球領導地位。該合資企業的產品可搭載於各類電動汽車——包括混合動力、插電式混合動力和全電動汽車。在得到相關部門許可後,該合資公司預計將於 2016 年第四季度投入運營。

    法雷奧將向合資公司注入包括其動力總成系統集團(PTS)旗下的高壓電力電子業務部門的 200 名員工,其中 90 人位於法國。西門子方面則由其電動汽車動力傳動業務部門為合資公司調配 500 名員工,其中 370 人位於德國,130 人在中國。合資公司將全權負責開發、銷售和生產電氣化乘用車和輕型商用車所需的 60 V以上的高壓電機和電力電子產品。合資公司能提供更加廣泛的產品線,從混合動力傳動模組和解決方案(包括電機、增程器、DC/DC 變流器、逆變器和充電器),到全電氣化動力系統等。

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

    【其他文章推薦】

    ※專營大陸空運台灣貨物推薦

    台灣空運大陸一條龍服務

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

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

  • 一分鐘帶你了解下Spring Security!

    一分鐘帶你了解下Spring Security!

    一、什麼是Spring Security?

    Spring Security是一個功能強大且高度可定製的身份驗證和訪問控制框架,它是用於保護基於Spring的應用程序的實際標準。

    Spring Security是一個框架,致力於為Java應用程序提供身份驗證和授權。與所有Spring項目一樣,Spring Security的真正強大之處在於可以輕鬆擴展以滿足自定義要求。

    更多信息可以查看官網:https://spring.io/projects/spring-security

    二、Spring Security的主要功能

    • 認證:驗證用戶名和密碼是否合法(是否系統中用戶)
    • 授權:是系統用戶不代表你能使用某些功能,因為你可能沒有權限
    • 防禦會話固定,點擊劫持,跨站點請求偽造等攻擊
    • Servlet API集成
    • 與Spring Web MVC的可選集成

    三、快速入門

    新建一個SpringBoot的web項目spring-boot-security。

    案例1:接口不添加保護

    pom文件中不引入Spring Security,然後新建一個controller:

    @RestController
    public class AppController {
    
        @GetMapping("/hello")
        public String hello() {
            return "Hello,spring security!";
        }
    }

    然後打開瀏覽器訪問:http://localhost:8080/hello,成功后返回:

    Hello,spring security!

    案例2:接口添加保護

    1. pom文件添加依賴

    pom文件中引入Spring Security的starter:

    <dependency>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-starter-security</artifactId>
            </dependency>
    1. 訪問接口

    打開瀏覽器再次訪問http://localhost:8080/hello,會被重定向到登錄頁http://localhost:8080/login,截圖如下:

    要登錄系統,我們需要知道用戶名和密碼,Spring Security默認的用戶名是user,項目啟動的時候會生成默認密碼(在啟動日誌中可以看到),輸入用戶名和密碼后就可以訪問/hello接口了。

    當然也可以自定義用戶名密碼,在配置文件添加如下內容即可:

    spring.security.user.name=java_suisui
    spring.security.user.password=123456

    四、自定義認證和授權

    上面說過Spring Security的功能有“認證”和“授權”,下面通過一個簡單的例子實現下自定義的認證和授權。

    假設系統中有兩個角色:

    • ADMIN 可以訪問/admin下的資源
    • USER 可以訪問/user下的資源

    按照下面步驟操作即可。

    1. 新建一個配置類

    對於用戶名、密碼、登錄頁面、訪問權限等都可以在 WebSecurityConfigurerAdapter 的實現類中配置。

    WebSecurityConfig代碼如下:

    /**
     * 配置類
     * @Author java_suisui
     *
     */
    @EnableWebSecurity
    @Configuration
    public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
    
        @Override
        protected void configure(AuthenticationManagerBuilder auth) throws Exception {
            //配置內存中的 用戶名、密碼和角色
            auth.inMemoryAuthentication().passwordEncoder(new MyPasswordEncoder()).withUser("user").password("123456").roles("USER");
            auth.inMemoryAuthentication().passwordEncoder(new MyPasswordEncoder()).withUser("admin").password("123456").roles("ADMIN");
        }
    
        @Override
        protected void configure(HttpSecurity http) throws Exception {
            http.authorizeRequests()
                    .antMatchers("/login").permitAll()
                    .antMatchers("/user").hasRole("USER") //訪問 /user這個接口,需要有USER角色
                    .antMatchers("/admin").hasRole("ADMIN")
                    .anyRequest().authenticated() //剩餘的其他接口,登錄之後就能訪問
                    .and()
                    .formLogin().defaultSuccessUrl("/hello");
        }
    }
    1. 創建PasswordEncorder的實現類

    內存用戶驗證時,Spring Boot 2.0以上版本引用的security 依賴是 spring security 5.X版本,此版本需要提供一個PasswordEncorder的實例。

    MyPasswordEncoder代碼如下:

    public class MyPasswordEncoder implements PasswordEncoder {
        @Override
        public String encode(CharSequence rawPassword) {
            return rawPassword.toString();
        }
    
        @Override
        public boolean matches(CharSequence rawPassword, String encodedPassword) {
            return encodedPassword.equals(rawPassword);
        }
    }
    1. 登錄驗證

    瀏覽器打開http://localhost:8080/login,

    • 使用user登錄,可以訪問/user
    • 使用admin登錄,可以訪問/admin

    如果使用user登錄后訪問/admin,會報403錯誤,具體錯誤信息如下:

    Whitelabel Error Page
    This application has no explicit mapping for /error, so you are seeing this as a fallback.
    
    Tue Nov 19 16:26:28 CST 2019
    There was an unexpected error (type=Forbidden, status=403).
    Forbidden

    結果和我們預期的一致,說明簡單的自定義認證和授權功能已經實現了。

    完整源碼地址:

    推薦閱讀

    Java碎碎念,一個堅持原創的公眾號,為您提供一系列系統架構、微服務、Java、SpringBoot、SpringCloud等高質量技術文章。
    如果覺得文章不錯,希望可以隨手轉發或者”在看“哦,非常感謝哈!
    關注下方公眾號后回復「1024」,有驚喜哦!

    本文由博客一文多發平台 發布!

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

    【其他文章推薦】

    ※帶您來了解什麼是 USB CONNECTOR  ?

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

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

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

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

    ※試算大陸海運運費!

  • 在 ASP.NET Core 項目中使用 MediatR 實現中介者模式

    在 ASP.NET Core 項目中使用 MediatR 實現中介者模式

     一、前言

       最近有在看 DDD 的相關資料以及微軟的  這個項目中基於 DDD 的架構設計,在  這個示例服務中,可以看到各層之間的代碼調用與我們之前傳統的調用方式似乎差異很大,整個項目各個層之間的代碼全部是通過注入 IMediator 進行調用的,F12 查看源碼后可以看到該接口是屬於  這個組件的。既然要照葫蘆畫瓢,那我們就先來了解下如何在 ASP.NET Core 項目中使用 。

      代碼倉儲:

     二、Step by Step

        從 github 的項目主頁上可以看到作者對於這個項目的描述是基於中介者模式的 .NET 實現,是一種基於進程內的數據傳遞。也就是說這個組件主要實現的是在一個應用中實現數據傳遞,如果想要實現多個應用間的數據傳遞就不太適合了。從作者的 github 個人主頁上可以看到,他還是  這個 OOM 組件的作者,PS,如果你想要了解如何在 ASP.NET Core 項目中使用 AutoMapper,你可以查看我之前寫的這一篇文章()。而對於 MediatR 來說,在具體的學習使用之前,我們先來了解下什麼是中介者模式。

      1、什麼是中介者模式

      很多舶來詞的中文翻譯其實最終都會與實際的含義相匹配,例如軟件開發過程中的 23 種設計模式的中文名稱,我們其實可以比較容易的從中文名稱中得知出該設計模式具體想要實現的作用,就像這裏介紹的中介者模式。

      在我們通過代碼實現實際的業務邏輯時,如果涉及到多個對象類之間的交互,通常我們都是會採用直接引用的形式,隨着業務邏輯變的越來越複雜,對於一個簡單的業務抽象出的實現方法中,可能會被我們添加上各種判斷的邏輯或是對於數據的業務邏輯處理方法。

      例如一個簡單的用戶登錄事件,我們可能最終會抽象出如下的業務流程實現。

    public bool Login(AppUserLoginDto dto, out string msg)
    {
        bool flag = false;
        try
        {
            // 1、驗證碼是否正確
            flag = _redisLogic.GetValueByKey(dto.VerificationCode);
            if (!flag)
            {
                msg = "驗證碼錯誤,請重試";
                return false;
            }
    
            // 2、驗證賬戶密碼是否正確
            flag = _userLogic.GetAppUser(dto.Account.Trim(), dto.Password.Trim(), out AppUserDetailDto appUser);
            if (!flag)
            {
                msg = "賬戶或密碼錯誤,請重試";
                return false;
            }
    
            // 3、驗證賬戶是否可以登錄當前的站點(未被鎖定 or 具有登錄當前系統的權限...)
            flag = _authLogic.CheckIsAvailable(appUser);
            if (!flag)
            {
                msg = "用戶被禁止登錄當前系統,請重試";
                return false;
            }
    
            // 4、設置當前登錄用戶信息
            _authLogic.SetCurrentUser(appUser);
    
            // 5、記錄登錄記錄
            _userLogic.SaveLoginRecord(appUser);
    
            msg = "";
            return true;
        }
        catch (Exception ex)
        {
            // 記錄錯誤信息
            msg = $"用戶登錄失敗:{ex.Message}";
            return false;
        }
    }

      這裏我們假設對於登錄事件的實現方法存在於 UserAppService 這個類中,對於 redis 資源的操作在 RedisLogic 類中,對於用戶相關資源的操作在 UserLogic 中,而對於權限校驗相關的資源操作位於 AuthLogic 類中。

      可以看到,為了實現 UserAppService 類中定義的登錄方法,我們至少需要依賴於 RedisLogic、UserLogic 以及 AuthLogic,甚至在某些情況下可能在 UserLogic 和 AuthLogic 之間也存在着某種依賴關係,因此我們可以從中得到如下圖所示的類之間的依賴關係。

      一個簡單的登錄業務尚且如此,如果我們需要對登錄業務添加新的需求,例如現在很多網站的登錄和註冊其實是放在一起的,當登錄時如果判斷沒有當前的用戶信息,其實會催生創建新用戶的流程,那麼,對於原本的登錄功能實現,是不是會存在繼續添加新的依賴關係的情況。同時對於很多本身就很複雜的業務,最終實現出來的方法是不是會有更多的對象類之間存在各種的依賴關係,牽一發而動全身,後期修改測試的成本會不會變得更高。

      那麼,中介者模式是如何解決這個問題呢?

      在上文有提到,對於舶來詞的中文名稱,中文更多的會根據實際的含義進行命名,試想一下我們在現實生活中提到中介,是不是更多的會想到房屋中介這一角色。當我們來到一個新的城市,面臨着租房的問題,絕大多數的情況下,我們最終需要通過中介去達成我們租房的目的。在租房這個案例中,房屋中介其實就是一个中介者,他承接我們對於想要租的房子的各種需求,從自己的房屋數據庫中去尋找符合條件的,最終以一個橋樑的形式,連接我們與房東,最終就房屋的租住達成一致。

      而在軟件開發中,中介者模式則是要求我們根據實際的業務去定義一個包含各種對象之間交互關係的對象類,之後,所有涉及到該業務的對象都只關聯於這一个中介對象類,不再顯式的調用其它類。採用了中介者模式之後設計的登錄功能所涉及到的類依賴如下圖所示,這裏的 AppUserLoginEventHandler 其實就是我們的中介類。

      當然,任何事都會有利有弊,不會存在百分百完美的事情,就像我們通過房租中介去尋找合適的房屋,最終我們需要付給中介一筆費用去作為酬勞,採用中介者模式設計的代碼架構也會存在別的問題。因為在代碼中引入了中介者這一對象,勢必會增加我們代碼的複雜度,可能會使原本很輕鬆就實現的代碼變得複雜。同時,我們引入中介者模式的初衷是為了解決各個對象類之間複雜的引用關係,對於某些業務來說,本身就很複雜,最終必定會導致這个中介者對象異常複雜。

      畢竟,軟件開發的過程中不會存在銀彈去幫我們解決所有的問題。

      那麼,在本篇文章的示例代碼中,我將使用 MediatR 這一組件,通過引入中介者模式的思想來完成上面的用戶登錄這一案例。

      2、組件加載

      在使用 MediatR 之前,這裏簡單介紹下這篇文章的示例 demo 項目。這個示例項目的架構分層可以看成是介於傳統的多層架構與採用 DDD 的思想的架構分層。嗯,你可以理解成四不像,屬於那種傳統模式下的開發人員在往 DDD 思想上進行遷移的成品,具體的代碼分層說明解釋如下。

      01_Infrastructure:基礎架構層,這層會包含一些對於基礎組件的配置或是幫助類的代碼,對於每個新建的服務來說,該層的代碼幾乎都是差不多的,所以對於基礎架構層的代碼其實最好是發布到公有 or 私有的 Nuget 倉庫中,然後我們直接在項目中通過 Nuget 去引用。

      對於採用 DDD 的思想構建的項目來說,很多人可能習慣將一些實體的配置也放置在基礎架構層,我的個人理解還是應該置於領域層,對於基礎架構層,只做一些基礎組件的封裝。如果有什麼不對的地方,歡迎在評論區提出。

      02_Domain:領域層,這層會包含我們根據業務劃分出的領域的幾乎所有重要的部分,有領域對象(Domain Object)、值對象(Value Object)、領域事件(Domain Event)、以及倉儲(Repository)等等領域組件。

      這裏雖然我創建了 AggregateModels(聚合實體)這個文件夾,其實在這個項目中,我創建的還是不包含任何業務邏輯的貧血模型。同時,對於倉儲(Repository)在領域分層中是置於 Infrastructure(基礎架構層)還是位於 Domain(領域層),每個人都會有自己的理解,這裏我還是更傾向於放在 Domain 層中更符合其定位。

      03_Application:應用層,這一層會包含我們基於領域所封裝出的各種實際的業務邏輯,每個封裝出的服務應用之間並不會出現互相調用的情況。

      Sample.Api:API 接口層,這層就很簡單了,主要是通過 API 接口暴露出我們基於領域對外提供的各種服務。

      整個示例項目的分層結構如下圖所示。

      與使用其它的第三方組件的使用方式相同,在使用之前,我們需要在項目中通過 Nuget 添加對於 MediatR 的程序集引用。

      這裏需要注意,因為我們主要是通過引用 MediatR 來實現中介者模式,所以我們只需要在領域層和應用層加載 MediatR 即可。而對於 Sample.Api 這個 Web API 項目,因為需要通過依賴注入的方式來使用我們基於 MediatR 所構建出的各種服務,所以這裏我們還要添加 MediatR.Extensions.Microsoft.DependencyInjection 這個程序集到 Sample.Api 中。

    Install-Package MediatR
    Install-Package MediatR.Extensions.Microsoft.DependencyInjection

      3、案例實現

      首先我們在 Sample.Domain 這個類庫的 AggregateModels 文件夾下添加 AppUser(用戶信息)類 和 Address(地址信息) 類,這裏雖然並沒有採用 DDD 的思想去劃分領域對象和值對象,我們創建出來的都是不含任何業務邏輯的貧血模型。但是在用戶管理這個業務中,對於用戶所包含的聯繫地址信息,其實是一種無狀態的數據。也就是說對於同一個地址信息,不會因為置於多個用戶中而出現數據的二義性。因此,對於地址信息來說,是不需要唯一的標識就可以區分出這個數據的,所以這裏的 Address 類就不需要添加主鍵,其實也就是對應於領域建模中的值對象。

      這裏我是使用的 EF Core 作為項目的 ORM 組件,當創建好需要使用實體之後,我們在 Sample.Domain 這個類庫下面新建一個 SeedWorks 文件夾,添加自定義的 DbContext 對象和用於執行 EF Core 第一次生成數據庫時寫入預置種子數據的信息類。

      這裏需要注意,在 EF Core 中,當我們需要將編寫的 C# 類通過 Code First 創建出數據庫表時,我們的 C# 類必須包含主鍵信息。而對應到我們這裏的 Address 類來說,它更多的是作為 AppUser 類中的屬性信息來展示的,所以這裏我們需要對 EF Core 生成數據庫表的過程進行重寫。

      這裏我們在 SeedWorks 文件夾下創建一個新的文件夾 EntityConfigurations,在這裏用來存放我們自定義的 EF Core 創建表的規則。新建一個繼承於 IEntityTypeConfiguration<AppUser> 接口的 AppUserConfiguration 配置類,在接口默認 Configure 方法中,我們需要編寫映射規則,將 Address 類作為 AppUser 類中的字段進行显示,最終實現后的代碼如下所示。 

    public class AppUserConfiguration : IEntityTypeConfiguration<AppUser>
    {
        public void Configure(EntityTypeBuilder<AppUser> builder)
        {
            // 表名稱
            builder.ToTable("appuser");
    
            // 實體屬性配置
            builder.OwnsOne(i => i.Address, n =>
            {
                n.Property(p => p.Province).HasMaxLength(50)
                    .HasColumnName("Province")
                    .HasDefaultValue("");
    
                n.Property(p => p.City).HasMaxLength(50)
                    .HasColumnName("City")
                    .HasDefaultValue("");
    
                n.Property(p => p.Street).HasMaxLength(50)
                    .HasColumnName("Street")
                    .HasDefaultValue("");
    
                n.Property(p => p.ZipCode).HasMaxLength(50)
                    .HasColumnName("ZipCode")
                    .HasDefaultValue("");
            });
        }
    }

      當創建表的映射規則編寫完成后,我們就可以對 UserApplicationDbContext 類進行重寫 OnModelCreating 方法。在這個方法中,我們就可以去應用我們自定義設置的實體映射規則,從而讓 EF Core 按照我們的想法去創建數據庫,最終實現的代碼如下所示。

    public class UserApplicationDbContext : DbContext
    {
        public DbSet<AppUser> AppUsers { get; set; }
    
        public UserApplicationDbContext(DbContextOptions<UserApplicationDbContext> options)
            : base(options)
        {
        }
    
        /// <summary>
        ///
        /// </summary>
        /// <param name="modelBuilder"></param>
        protected override void OnModelCreating(ModelBuilder modelBuilder)
        {
            // 自定義 AppUser 表創建規則
            modelBuilder.ApplyConfiguration(new AppUserConfiguration());
        }
    }

      當我們創建好 DbContext 后,我們需要在 Startup 類的 ConfigureServices 方法中進行注入。在示例代碼中,我使用的是 MySQL 8.0 數據庫,將配置文件寫入到 appsettings.json 文件中,最終注入 DbContext 的代碼如下所示。

    public void ConfigureServices(IServiceCollection services)
    {
        // 配置數據庫連接字符串
        services.AddDbContext<UserApplicationDbContext>(options =>
            options.UseMySql(Configuration.GetConnectionString("SampleConnection")));
    }

      數據庫的連接字符串配置如下。

    {
      "ConnectionStrings": {
        "SampleConnection": "server=127.0.0.1;database=sample.application;user=root;password=123456@sql;port=3306;persistsecurityinfo=True;"
      }
    }

      在上文有提到,除了創建一個 DbContext 對象,我們還創建了一個 DbInitializer 類用於在 EF Core 第一次執行創建數據庫操作時將我們預置的信息寫入到對應的數據庫表中。這裏我們只是簡單的判斷下 AppUser 這張表是否存在數據,如果沒有數據,我們就添加一條新的記錄,最終實現的代碼如下所示。

    public class DbInitializer
    {
        public static void Initialize(UserApplicationDbContext context)
        {
            context.Database.EnsureCreated();
    
            if (context.AppUsers.Any())
                return;
    
            AppUser admin = new AppUser()
            {
                Id = Guid.NewGuid(),
                Name = "墨墨墨墨小宇",
                Account = "danvic.wang",
                Phone = "13912345678",
                Age = 12,
                Password = "123456",
                Gender = true,
                IsEnabled = true,
                Address = new Address("啦啦啦啦街道", "啦啦啦市", "啦啦啦省", "12345"),
                Email = "danvic.wang@yuiter.com",
            };
    
            context.AppUsers.Add(admin);
            context.SaveChanges();
        }
    }

      當我們完成種子數據植入的代碼,我們需要在程序啟動之前就去執行我們的代碼。因此我們需要修改 Program 類中的 Main 方法,實現在運行 web 程序之前去執行種子數據的植入。

    public class Program
    {
        public static void Main(string[] args)
        {
            var host = CreateWebHostBuilder(args).Build();
    
            using (var scope = host.Services.CreateScope())
            {
                // 執行種子數據植入
                //
                var services = scope.ServiceProvider;
                var context = services.GetRequiredService<UserApplicationDbContext>();
                DbInitializer.Initialize(context);
            }
        }
    
        public static IWebHostBuilder CreateWebHostBuilder(string[] args) =>
            WebHost.CreateDefaultBuilder(args)
                .UseStartup<Startup>();
    }

      這時,運行我們的項目,程序就會自動執行創建數據庫的操作,同時會將我們預設好的種子數據寫入到數據庫表中,最終實現的效果如下圖所示。

      基礎的項目代碼已經完成之後,我們就可以開始學習如何通過 MediatR 來實現中介者模式。在這一章的示例項目中,我們會使用到 MediatR 中兩個很重要的接口類型:IRequest 和 INotification。

      在 Github 上,作者針對這兩個接口做了如下的,這裏我會按照我的理解去進行使用。同時,為了防止我的理解出現了偏差,從而對各位造成影響,這裏貼上作者回復解釋的原文。

    Requests are for:
    1 request to 1 handler. Handler may or may not return a value
    Notifications are for:
    1 notification to n handlers. Handler may not return a value.
    
    
    In practical terms, requests are "commands", notifications are "events".
    Command would be directing MediatR to do something like "ApproveInvoiceCommand -> ApproveInvoiceHandler". Event would be
    notifications, like "InvoiceApprovedEvent -> SendThankYouEmailToCustomerHandler"

     

      對於繼承於 IRequest 接口的類來說,一個請求(request)只會有一個針對這個請求的處理程序(requestHandler),它可以返回值或者不返回任何信息;

      而對於繼承於 INotification 接口的類來說,一個通知(notification)會對應多個針對這個通知的處理程序(notificationHandlers),而它們不會返回任何的數據。

      請求(request)更像是一種命令(command),而通知(notification)更像是一種事件(event)。嗯,可能看起來更暈了,jbogard 這裏給了一個案例給我們進一步的解釋了 request 與 notification 之間的差異性。

      雙十一剛過,很多人都會瘋狂剁手,對於購買大件來說,為了能夠更好地擁有售後服務,我們在購買后肯定會期望商家給我們提供發票,這裏的要求商家提供發票就是一種 request,而針對我們的這個請求,商家會做出回應,不管能否開出來發票,商家都應當通知到我們,這裏的通知用戶就是一種 notification。

      對於提供發票這個 request 來說,不管最終的結果如何,它只會存在一種處理方式;而對於通知用戶這個 notification 來說,商家可以通過短信通知,可以通過公眾號推送,也可以通過郵件通知,不管採用什麼方式,只要完成了通知,對於這個事件來說也就已經完成了。    

      而對應於用戶登錄這個業務來說,用戶的登錄行為其實就是一個 request,對於這個 request 來說,我們可能會去數據庫查詢賬戶是否存在,判斷是不是具有登錄系統的權限等等。而不管我們在這個過程中做了多少的邏輯判斷,它只會有兩種結果,登錄成功或登錄失敗。而對於用戶登錄系統之後可能需要設置當前登錄人員信息,記錄用戶登錄日誌這些行為來說,則是歸屬於 notification 的。

      弄清楚了用戶登錄事件中的 request 和 notification 劃分,那麼接下來我們就可以通過代碼來實現我們的功能。這裏對於示例項目中的一些基礎組件的配置我就跳過了,如果你想要具體的了解這裏使用到的一些組件的使用方法,你可以查閱我之前的文章。

      首先,我們在 Sample.Application 這個類庫下面創建一個 Commands 文件夾,在下面存放用戶的請求信息。現在我們創建一個用於映射用戶登錄請求的 UserLoginCommand 類,它需要繼承於 IRequest<T> 這個泛型接口。因為對於用戶登錄這個請求來說,只會有可以或不可以這兩個結果,所以對於這個請求的響應的結果是 bool 類型的,也就是說,我們具體應該繼承的是 IRequest<bool>。

      對於用戶發起的各種請求來說,它其實只是包含了對於這次請求的一些基本信息,而對於 UserLoginCommand 這個用戶登錄請求類來說,它可能只會有賬號、密碼、驗證碼這三個信息,請求類代碼如下所示。

    public class UserLoginCommand : IRequest<bool>
    {
        /// <summary>
        /// 賬戶
        /// </summary>
        public string Account { get; private set; }
    
        /// <summary>
        /// 密碼
        /// </summary>
        public string Password { get; private set; }
    
        /// <summary>
        /// 驗證碼
        /// </summary>
        public string VerificationCode { get; private set; }
    
        /// <summary>
        /// ctor
        /// </summary>
        /// <param name="account">賬戶</param>
        /// <param name="password">密碼</param>
        /// <param name="verificationCode">驗證碼</param>
        public UserLoginCommand(string account, string password, string verificationCode)
        {
            Account = account;
            Password = password;
            VerificationCode = verificationCode;
        }
    }

      當我們擁有了存儲用戶登錄請求信息的類之後,我們就需要對用戶的登錄請求進行處理。這裏,我們在 Sample.Application 這個類庫下面新建一個 CommandHandlers 文件夾用來存放用戶請求的處理類。

      現在我們創建一個繼承於 IRequestHandler 接口的 UserLoginCommandHandler 類用來實現對於用戶登錄請求的處理。IRequestHandler 是一個泛型的接口,它需要我們在繼承時聲明我們需要實現的請求,以及該請求的返回信息。因此,對於 UserLoginCommand 這個請求來說,UserLoginCommandHandler 這個請求的處理類,最終需要繼承於 IRequestHandler<UserLoginCommand, bool>。

      就像上面提到的一樣,我們需要在這個請求的處理類中對用戶請求的信息進行處理,在 UserLoginCommandHandler 類中,我們應該在 Handle 方法中去執行我們的判斷邏輯,這裏我們會引用到倉儲來獲取用戶的相關信息。倉儲中的代碼這裏我就不展示了,最終我們實現后的代碼如下所示。

    public class UserLoginCommandHandler : IRequestHandler<UserLoginCommand, bool>
    {
        #region Initizalize
    
        /// <summary>
        /// 倉儲實例
        /// </summary>
        private readonly IUserRepository _userRepository;
    
        /// <summary>
        /// ctor
        /// </summary>
        /// <param name="userRepository"></param>
        public UserLoginCommandHandler(IUserRepository userRepository)
        {
            _userRepository = userRepository ?? throw new ArgumentNullException(nameof(userRepository));
        }
    
        #endregion Initizalize
    
        /// <summary>
        /// Command Handler
        /// </summary>
        /// <param name="request"></param>
        /// <param name="cancellationToken"></param>
        /// <returns></returns>
        public async Task<bool> Handle(UserLoginCommand request, CancellationToken cancellationToken)
        {
            // 1、判斷驗證碼是否正確
            if (string.IsNullOrEmpty(request.VerificationCode))
                return false;
    
            // 2、驗證登錄密碼是否正確
            var appUser = await _userRepository.GetAppUserInfo(request.Account.Trim(), request.Password.Trim());
            if (appUser == null)
                return false;
    
            return true;
        }
    }

      當我們完成了對於請求的處理代碼后,就可以在 controller 中提供用戶訪問的入口。當然,因為我們需要採用依賴注入的方式去使用 MediatR,所以在使用之前,我們需要將請求的對應處理關係注入到依賴注入容器中。

      在通過依賴注入的方式使用 MediatR 時,我們需要將所有的事件(請求以及通知)注入到容器中,而 MediatR 則會自動尋找對應事件的處理類,除此之外,我們也需要將通過依賴注入使用到的 IMediator 接口的實現類注入到容器中。而在這個示例項目中,我們主要是在 Sample.Domain、Sample.Application 以及我們的 Web Api 項目中使用到了 MediatR,因此,我們需要將這三個項目中使用到 MediatR 的類全部注入到容器中。

      一個個的注入會比較的麻煩,所以這裏我還是採用對指定的程序集進行反射操作,去獲取需要加載的信息批量的進行注入操作,最終實現后的代碼如下。

    public static IServiceCollection AddCustomMediatR(this IServiceCollection services, MediatorDescriptionOptions options)
    {
        // 獲取 Startup 類的 type 類型
        var mediators = new List<Type> { options.StartupClassType };
    
        // IRequest<T> 接口的 type 類型
        var parentRequestType = typeof(IRequest<>);
    
        // INotification 接口的 type 類型
        var parentNotificationType = typeof(INotification);
    
        foreach (var item in options.Assembly)
        {
            var instances = Assembly.Load(item).GetTypes();
    
            foreach (var instance in instances)
            {
                // 判斷是否繼承了接口
                //
                var baseInterfaces = instance.GetInterfaces();
                if (baseInterfaces.Count() == 0 || !baseInterfaces.Any())
                    continue;
    
                // 判斷是否繼承了 IRequest<T> 接口
                //
                var requestTypes = baseInterfaces.Where(i => i.IsGenericType
                    && i.GetGenericTypeDefinition() == parentRequestType);
    
                if (requestTypes.Count() != 0 || requestTypes.Any())
                    mediators.Add(instance);
    
                // 判斷是否繼承了 INotification 接口
                //
                var notificationTypes = baseInterfaces.Where(i => i.FullName == parentNotificationType.FullName);
    
                if (notificationTypes.Count() != 0 || notificationTypes.Any())
                    mediators.Add(instance);
            }
        }
    
        // 添加到依賴注入容器中
        services.AddMediatR(mediators.ToArray());
    
        return services;
    }

      因為需要知道哪些程序集應該進行反射獲取信息,而對於 Web Api 這個項目來說,它只會通過依賴注入使用到 IMediator 這一個接口,所以這裏需要採用不同的參數的形式去確定具體需要通過反射加載哪些程序集。

    public class MediatorDescriptionOptions
    {
        /// <summary>
        /// Startup 類的 type 類型
        /// </summary>
        public Type StartupClassType { get; set; }
    
        /// <summary>
        /// 包含使用到 MediatR 組件的程序集
        /// </summary>
        public IEnumerable<string> Assembly { get; set; }
    }

      最終,我們就可以在 Startup 類中通過擴展方法的信息進行快速的注入,實際使用的代碼如下,這裏我是將需要加載的程序集信息放在 appsetting 這個配置文件中的,你可以根據你的喜好進行調整。

    public class Startup
    {
        // This method gets called by the runtime. Use this method to add services to the container.
        public void ConfigureServices(IServiceCollection services)
        {
            // Config mediatr
            services.AddCustomMediatR(new MediatorDescriptionOptions
            {
                StartupClassType = typeof(Startup),
                Assembly = Configuration["Assembly:Mediator"].Split("|", StringSplitOptions.RemoveEmptyEntries)
            });
        }
    }

      在這個示例項目中的配置信息如下所示。

    {
      "Assembly": {
        "Function": "Sample.Domain",
        "Mapper": "Sample.Application",
        "Mediator": "Sample.Application|Sample.Domain"
      }
    }

      當我們注入完成后,就可以直接在 controller 中進行使用。對於繼承了 IRequest 的方法,可以直接通過 Send 方法進行調用請求信息,MediatR 會幫我們找到對應請求的處理方法,最終登錄 action 中的代碼如下。

    [ApiVersion("1.0")]
    [ApiController]
    [Route("api/v{version:apiVersion}/[controller]")]
    public class UsersController : ControllerBase
    {
        #region Initizalize
    
        /// <summary>
        ///
        /// </summary>
        private readonly IMediator _mediator;
    
        /// <summary>
        /// ctor
        /// </summary>
        /// <param name="mediator"></param>
        public UsersController(IMediator mediator)
        {
            _mediator = mediator ?? throw new ArgumentNullException(nameof(mediator));
        }
    
        #endregion Initizalize
    
        #region APIs
    
        /// <summary>
        /// 用戶登錄
        /// </summary>
        /// <param name="login">用戶登錄數據傳輸對象</param>
        /// <returns></returns>
        [HttpPost("login")]
        [ProducesResponseType(StatusCodes.Status200OK)]
        [ProducesResponseType(StatusCodes.Status401Unauthorized)]
        public async Task<IActionResult> Post([FromBody] AppUserLoginDto login)
        {
            // 實體映射轉換
            var command = new UserLoginCommand(login.Account, login.Password, login.VerificationCode);
    
            bool flag = await _mediator.Send(command);
    
            if (flag)
                return Ok(new
                {
                    code = 20001,
                    msg = $"{login.Account} 用戶登錄成功",
                    data = login
                });
            else
                return Unauthorized(new
                {
                    code = 40101,
                    msg = $"{login.Account} 用戶登錄失敗",
                    data = login
                });
        }
    
        #endregion APIs
    }

      當我們完成了對於用戶登錄請求的處理之後,就可以去執行後續的“通知類”的事件。與用戶登錄的請求信息類相似,對於用戶登錄事件的通知類也只是包含一些通知的基礎信息。在 Smaple.Domain 這個類庫下面,創建一個 Events 文件用來存放我們的事件,我們來新建一個繼承於 INotification 接口的 AppUserLoginEvent 類,用來對用戶登錄事件進行相關的處理。

    public class AppUserLoginEvent : INotification
    {
        /// <summary>
        /// 賬戶
        /// </summary>
        public string Account { get; }
    
        /// <summary>
        /// ctor
        /// </summary>
        /// <param name="account"></param>
        public AppUserLoginEvent(string account)
        {
            Account = account;
        }
    }

      在上文中有提到過,對於一個通知事件可能會存在着多種處理方式,所以這裏我們在 Smaple.Application 這個類庫的 DomainEventHandlers 文件夾下面會按照事件去創建對應的文件夾去存放實際處理方法。

      對於繼承了 INotification 接口的通知類來說,在 MediatR 中我們可以通過創建繼承於 INotificationHandler 接口的類去處理對應的事件。因為一個 notification 可以有多個的處理程序,所以我們可以創建多個的 NotificationHandler 類去處理同一個 notification。一個示例的 NotificationHandler 類如下所示。

    public class SetCurrentUserEventHandler : INotificationHandler<AppUserLoginEvent>
    {
        #region Initizalize
    
        /// <summary>
        ///
        /// </summary>
        private readonly ILogger<SetCurrentUserEventHandler> _logger;
    
        /// <summary>
        ///
        /// </summary>
        /// <param name="logger"></param>
        public SetCurrentUserEventHandler(ILogger<SetCurrentUserEventHandler> logger)
        {
            _logger = logger ?? throw new ArgumentNullException(nameof(logger));
        }
    
        #endregion Initizalize
    
        /// <summary>
        /// Notification handler
        /// </summary>
        /// <param name="notification"></param>
        /// <param name="cancellationToken"></param>
        /// <returns></returns>
        public Task Handle(AppUserLoginEvent notification, CancellationToken cancellationToken)
        {
            _logger.LogInformation($"CurrentUser with Account: {notification.Account} has been successfully setup");
    
            return Task.FromResult(true);
        }
    }

      如何去引發這個事件,對於領域驅動設計的架構來說,一個更好的方法是將各種領域事件添加到事件的集合中,然後在提交事務之前或之後立即調度這些域事件,而對於我們這個項目來說,因為這不在這篇文章考慮的範圍內,只是演示如何去使用 MediatR 這個組件,所以這裏我就採取在請求邏輯處理完成后直接觸發事件的方式。

      在 UserLoginCommandHandler 類中,修改我們的代碼,在確認登錄成功后,通過調用 AppUser 類的 SetUserLoginRecord 方法來觸發我們的通知事件,修改后的代碼如下所示。

    public class UserLoginCommandHandler : IRequestHandler<UserLoginCommand, bool>
    {
        #region Initizalize
    
        /// <summary>
        /// 倉儲實例
        /// </summary>
        private readonly IUserRepository _userRepository;
    
        /// <summary>
        ///
        /// </summary>
        private readonly IMediator _mediator;
    
        /// <summary>
        /// ctor
        /// </summary>
        /// <param name="userRepository"></param>
        /// <param name="mediator"></param>
        public UserLoginCommandHandler(IUserRepository userRepository, IMediator mediator)
        {
            _userRepository = userRepository ?? throw new ArgumentNullException(nameof(userRepository));
            _mediator = mediator ?? throw new ArgumentNullException(nameof(mediator));
        }
    
        #endregion Initizalize
    
        /// <summary>
        /// Command Handler
        /// </summary>
        /// <param name="request"></param>
        /// <param name="cancellationToken"></param>
        /// <returns></returns>
        public async Task<bool> Handle(UserLoginCommand request, CancellationToken cancellationToken)
        {
            // 1、判斷驗證碼是否正確
            if (string.IsNullOrEmpty(request.VerificationCode))
                return false;
    
            // 2、驗證登錄密碼是否正確
            var appUser = await _userRepository.GetAppUserInfo(request.Account.Trim(), request.Password.Trim());
            if (appUser == null)
                return false;
    
            // 3、觸發登錄事件
            appUser.SetUserLoginRecord(_mediator);
    
            return true;
        }
    }

      與使用 Send 方法去調用 request 類的請求不同,對於繼承於 INotification 接口的事件通知類,我們需要採用 Publish 的方法去調用。至此,對於一個採用中介者模式設計的登錄流程就結束了,SetUserLoginRecord 方法的定義,以及最終我們實現的效果如下所示。

    public void SetUserLoginRecord(IMediator mediator)
    {
        mediator.Publish(new AppUserLoginEvent(Account));
    }

     三、總結

      這一章主要是介紹了如何通過 MediatR 來實現中介者模式,因為自己也是第一次接觸這種思想,對於 MediatR 這個組件也是第一次使用,所以僅僅是採用案例分享的方式對中介者模式的使用方法進行了一個解釋。如果你想要對中介者模式的具體定義與基礎的概念進行進一步的了解的話,可能需要你自己去找資料去弄明白具體的定義。因為初次接觸,難免會有遺漏或錯誤,如果從文章中發現有不對的地方,歡迎在評論區中指出,先行感謝。

     四、參考

      1、

      2、

    本站聲明:網站內容來源於博客園,如有侵權,請聯繫我們,我們將及時處理
    【其他文章推薦】

    ※為什麼 USB CONNECTOR 是電子產業重要的元件?

    網頁設計一頭霧水??該從何著手呢? 找到專業技術的網頁設計公司,幫您輕鬆架站!

    ※想要讓你的商品成為最夯、最多人討論的話題?網頁設計公司讓你強力曝光

    ※想知道最厲害的台北網頁設計公司推薦台中網頁設計公司推薦專業設計師”嚨底家”!!

    ※專營大陸快遞台灣服務

    台灣快遞大陸的貨運公司有哪些呢?

  • 50行Python代碼實現視頻中物體顏色識別和跟蹤(必須以紅色為例)

    50行Python代碼實現視頻中物體顏色識別和跟蹤(必須以紅色為例)

    目前計算機視覺(CV)與自然語言處理(NLP)及語音識別並列為人工智能三大熱點方向,而計算機視覺中的對象檢測(objectdetection)應用非常廣泛,比如自動駕駛、視頻監控、工業質檢、醫療診斷等場景。

    目標檢測的根本任務就是將圖片或者視頻中感興趣的目標提取出來,目標的識別可以基於顏色、紋理、形狀。其中顏色屬性運用十分廣泛,也比較容易實現。下面就向大家分享一個我做的小實驗———通過OpenCV的Python接口來實現從視頻中進行顏色識別和跟蹤。

    下面就是我們完整的代碼實現(已調試運行):

    import numpy as np
    import cv2
    font = cv2.FONT_HERSHEY_SIMPLEX
    lower_green = np.array([35, 110, 106])  # 綠色範圍低閾值
    upper_green = np.array([77, 255, 255])  # 綠色範圍高閾值
    lower_red = np.array([0, 127, 128])  # 紅色範圍低閾值
    upper_red = np.array([10, 255, 255])  # 紅色範圍高閾值
    #需要更多顏色,可以去百度一下HSV閾值!
    # cap = cv2.VideoCapture('1.mp4')  # 打開視頻文件
    cap = cv2.VideoCapture(0)#打開USB攝像頭
    if (cap.isOpened()):  # 視頻打開成功
        flag = 1
    else:
        flag = 0
    num = 0
    if (flag):
        while (True):
            ret, frame = cap.read()  # 讀取一幀
           
            if ret == False:  # 讀取幀失敗
                break
            hsv_img = cv2.cvtColor(frame, cv2.COLOR_BGR2HSV)
            mask_green = cv2.inRange(hsv_img, lower_green, upper_green)  # 根據顏色範圍刪選
            mask_red = cv2.inRange(hsv_img, lower_red, upper_red) 
     # 根據顏色範圍刪選
            mask_green = cv2.medianBlur(mask_green, 7)  # 中值濾波
            mask_red = cv2.medianBlur(mask_red, 7)  # 中值濾波
            mask = cv2.bitwise_or(mask_green, mask_red)
            mask_green, contours, hierarchy = cv2.findContours(mask_green, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_NONE)
            mask_red, contours2, hierarchy2 = cv2.findContours(mask_red, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_NONE)
    
            for cnt in contours:
                (x, y, w, h) = cv2.boundingRect(cnt)
                cv2.rectangle(frame, (x, y), (x + w, y + h), (0, 255, 255), 2)
                cv2.putText(frame, "Green", (x, y - 5), font, 0.7, (0, 255, 0), 2)
    
            for cnt2 in contours2:
                (x2, y2, w2, h2) = cv2.boundingRect(cnt2)
                cv2.rectangle(frame, (x2, y2), (x2 + w2, y2 + h2), (0, 255, 255), 2)
                cv2.putText(frame, "Red", (x2, y2 - 5), font, 0.7, (0, 0, 255), 2)
            num = num + 1
            cv2.imshow("dection", frame)
            cv2.imwrite("imgs/%d.jpg"%num, frame)
            if cv2.waitKey(20) & 0xFF == 27:
                break
    cv2.waitKey(0)
    cv2.destroyAllWindows()

    如圖所示,我們將會檢測到紅色區域

    最終的效果圖:

    本站聲明:網站內容來源於博客園,如有侵權,請聯繫我們,我們將及時處理
    【其他文章推薦】

    USB CONNECTOR掌控什麼技術要點? 帶您認識其相關發展及效能

    ※評比前十大台北網頁設計台北網站設計公司知名案例作品心得分享

    ※智慧手機時代的來臨,RWD網頁設計已成為網頁設計推薦首選

    台灣海運大陸貨務運送流程

    兩岸物流進出口一站式服務

  • Zabbix-(四)郵件、釘釘告警通知

    Zabbix-(四)郵件、釘釘告警通知

    Zabbix-(四)郵件、釘釘告警通知

    一.前言

    在之前的文章里,通過Zabbix對主機的磁盤、CPU以及內存進行了監控,並在首頁Dashboard里創建了監控圖形,但是只有當我們登錄到Zabbix后才能看到監控到的問題(Problem),因此在本篇文章里,將利用觸發器(Trigger),以及媒介(Media)等配置項,實現當觸發器觸發時,通過不同媒介,如:郵件、釘釘,發送動作(Action),實現實時通知告警功能。

    準備

    • Zabbix Server (Zabbix 4.4)
    • 在Zabbix中已配置一些監控項和觸發器(這些配置可以參考我的)

    二.安裝相關環境

    由於使用到腳本告警媒介,本文中通過調用Python腳本觸發告警,因此需要在Zabbix Server主機上安裝pip以及相關模塊。(這裏Python使用Centos7自帶的Python2.7.5)

    1. 安裝pip

      # yum install -y epel-release
      
      # yum install -y python-pip
    2. 安裝requests模塊

      # pip install requests

    三.配置告警媒介類型

    Zabbix默認自帶了2種報警媒介類型(Media Type)电子郵件以及短信,我們將修改电子郵件類型配置,並新建腳本類型和Webhook類型。希望通過腳本、Webhook告警媒介發送釘釘消息。

    注:Webhook告警媒介是Zabbix 4.4的新特性

    1. 修改电子郵件告警媒介

      點擊【管理】-【報警媒介類型】-【Email】

      修改Email配置,我這裏用的是Outlook郵箱,具體SMTP服務器可以參考。使用其他郵箱也可以去對應官網查詢SMTP配置。

      測試發送郵箱,點擊【測試】

      輸入收件人郵箱

      收到郵件

    2. 新增腳本告警媒介

      新建Python腳本告警媒介,用戶釘釘告警

      點擊【創建媒體類型】

      進行配置

      配置項
      * 名稱 Python腳本
      類型 腳本
      * 腳本名稱 pythonScript.py
      腳本參數(參數1) {ALERT.MESSAGE}
      腳本參數(參數2) {ALERT.SENDTO}
      腳本參數(參數3) {ALERT.SUBJECT}

      接下來新建Python腳本,Zabbix Server配置文件中可以配置告警腳本路徑,默認為 /usr/lib/zabbix/alertscripts

      # 查看告警腳本路徑
      # cat zabbix_server.conf | grep AlertScriptsPath

      編寫告警腳本

      # cd /usr/lib/zabbix/alertscripts
      # vim pythonScript.py

      腳本內容

      #!/usr/bin/env python
      #coding:utf-8
      
      import requests,json,sys,os,datetime
      
      # 釘釘機器人地址
      webhook="https://oapi.dingtalk.com/robot/send?access_token=your_dingding_robot_access_token"
      
      # 對應{ALERT.SENDTO}, Zabbix告警媒介配置界面第2個參數
      user=sys.argv[2]
      
      # 對應{ALERT.MESSAGE}, Zabbix告警媒介配置界面第1個參數
      text=sys.argv[1]
      data={
          "msgtype": "text",
          "text": {
              "content": text
          },
          "at": {
              "atMobiles": [
                  user
              ],
              "isAtAll": False
          }
      }
      headers = {'Content-Type': 'application/json'}
      x=requests.post(url=webhook,data=json.dumps(data),headers=headers)

      給腳本可執行權限

      # chmod uo+x /usr/lib/zabbix/alertscripts/pythonScript.py

      測試腳本

      釘釘收到消息

    3. 新增Webhook告警媒介

      配置項
      * 名稱 Webhook
      類型 Webhook
      參數: (名稱)
      user {ALERT.SENDTO}
      subject {ALERT.SUBJECT}
      message {ALERT.MESSAGE}

      腳本:

      try {
          Zabbix.Log(4, 'params= '+value);
      
          params = JSON.parse(value);
          req = new CurlHttpRequest();
          data = {};
          result = {};
      
          req.AddHeader('Content-Type: application/json');
      
          data.msgtype = "text";
          //   對應 message參數
          data.text = {"content" : params.message};
          //   對應 user參數
          data.at = {"atMobiles": [params.user], "isAtAll": "false"};
      
          //   釘釘機器人
          resp = req.Post('https://oapi.dingtalk.com/robot/send?access_token=your_access_token',
              JSON.stringify(data)
          );
      } catch (error) {
          result = {};
      }
      
      return JSON.stringify(result);

      測試Webhook

    四.為用戶添加告警媒介

    需要將新增的告警媒介添加給用戶

    點擊【用戶】-【告警媒介】

    將上述步驟添加的告警媒介(Python腳本、Webhoob、Email),進行添加(收件人根據告警媒介類型填寫郵箱手機號),嚴重性也根據需要勾選。

    五.配置動作

    完成上述配置完成后,需要創建動作(Action),將觸發器(Trigger)告警媒介(Media Type)進行關聯,一旦觸發器觸發,那麼Zabbix會執行動作,再去執行告警媒介。

    1. 添加動作

      點擊【配置】-【動作】-【創建動作】

    2. 配置【動作】相關信息

      配置項
      * 名稱 告警動作
      新的觸發條件 【觸發器】【等於】【Template Disk Free Size: 磁盤剩餘空間觸發器】

      操作步驟如下圖:

      群組選擇 ->Linux servers

      主機選擇 -> Template Disk Free Size 模板()

      勾選觸發器 -> 磁盤剩餘空間觸發器 ()

      勾選後點擊【選擇】

    3. 配置【操作】相關信息

      點擊【操作】

      先配置以下信息

      配置項
      * 默認操作步驟持續時間 1h(保持默認)
      默認標題 告警: {EVENT.NAME}
      消息內容 【磁盤空間不足告警】
      告警事件: {EVENT.DATE} {EVENT.TIME}
      告警問題: {EVENT.NAME}
      告警主機: {HOST.IP} {HOST.NAME}
      告警級別: {EVENT.SEVERITY}
      磁盤剩餘:{ITEM.VALUE}

      上述配置表格【默認標題】和【消息內容】值中形如{EVENT.NAME}的內容是Zabbix中的宏(Marco),宏是一個變量,例如 {HOST.IP} 表示告警主機的IP地址,Zabbix自帶的宏可以參考

      繼續配置操作

      點擊【新的】

      【操作類型】選擇發送消息,【發送到用戶】添加Admin

      【僅送到】根據需要選擇之前配置的,本文選擇Email和Python腳本(這裏只能單選或全選,所以需要先選擇一個,因此需要多次)

      添加完成後點擊【添加】

    六.測試

    向被監控主機拷貝或下載大文件,使其磁盤剩餘空間低於觸發器監控閾值,等待觸發器觸發問題,查看儀錶盤、郵件等。

    儀錶盤

    釘釘

    郵件

    七.參考文檔

    本站聲明:網站內容來源於博客園,如有侵權,請聯繫我們,我們將及時處理
    【其他文章推薦】

    台北網頁設計公司這麼多,該如何挑選?? 網頁設計報價省錢懶人包”嚨底家”

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

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

    小三通海運與一般國際貿易有何不同?

    小三通快遞通關作業有哪些?

  • Dev 日誌 | 一次 Segmentation Fault 和 GCC Illegal Instruction 編譯問題排查

    Dev 日誌 | 一次 Segmentation Fault 和 GCC Illegal Instruction 編譯問題排查

    摘要

    筆者最近在重新整理和編譯 Nebula Graph 的第三方依賴,選出兩個比較有意思的問題給大家分享一下。

    Flex Segmentation Fault——Segmentation fault (core dumped)

    在編譯 Flex 過程中,遇到了 Segmentation fault:

    make[2]: Entering directory '/home/dutor/flex-2.6.4/src'
    ./stage1flex   -o stage1scan.c ./scan.l
    make[2]: *** [Makefile:1696: stage1scan.c] Segmentation fault (core dumped)

    使用 gdb 查看 coredump:

    Core was generated by `./stage1flex -o stage1scan.c ./scan.l'.
    Program terminated with signal SIGSEGV, Segmentation fault.
    #0  flexinit (argc=4, argv=0x7ffd25bea718) at main.c:976
    976             action_array[0] = '\0';
    (gdb) disas
    Dump of assembler code for function flexinit:
       0x0000556c1b1ae040 <+0>:     push   %r15
       0x0000556c1b1ae042 <+2>:     lea    0x140fd(%rip),%rax        # 0x556c1b1c2146
       ...
       0x0000556c1b1ae20f <+463>:   callq  0x556c1b1af460 <allocate_array> #這裏申請了buffer
       ...
    => 0x0000556c1b1ae24f <+527>:   movb   $0x0,(%rax) # 這裏向buffer[0]寫入一個字節,地址非法,掛掉了
       ...
    (gdb) disas allocate_array
    Dump of assembler code for function allocate_array:
       0x0000556c1b1af460 <+0>:     sub    $0x8,%rsp
       0x0000556c1b1af464 <+4>:     mov    %rsi,%rdx
       0x0000556c1b1af467 <+7>:     xor    %eax,%eax
       0x0000556c1b1af469 <+9>:     movslq %edi,%rsi
       0x0000556c1b1af46c <+12>:    xor    %edi,%edi
       0x0000556c1b1af46e <+14>:    callq  0x556c1b19a100 <reallocarray@plt> # 調用庫函數申請內存
       0x0000556c1b1af473 <+19>:    test   %eax,%eax # 判斷是否為 NULL
       0x0000556c1b1af475 <+21>:    je     0x556c1b1af47e <allocate_array+30># 跳轉至NULL錯誤處理
       0x0000556c1b1af477 <+23>:    cltq   # 將 eax 符號擴展至 rax,造成截斷
       0x0000556c1b1af479 <+25>:    add    $0x8,%rsp
       0x0000556c1b1af47d <+29>:    retq
       ...
    End of assembler dump.

    可以看到,問題出在了 allocate_array 函數。因為 reallocarray 返回指針,返回值應該使用 64 bit 寄存器rax,但 allocate_array 調用 reallocarray 之後,檢查的卻是 32 bit 的 eax,同時使用 cltq 指令將 eax 符號擴展 到 rax。原因只有一個:allocate_array 看到的 reallocarray 的原型,與 reallocarry 的實際定義不符。翻看編譯日誌,確實找到了 implicit declaration of function ‘reallocarray’ 相關的警告。configure 階段添加 CFLAGS=-D_GNU_SOURCE 即可解決此問題。

    注:此問題不是必現,但編譯/鏈接選項 -pie 和 內核參數 kernel.randomize_va_space 有助於復現。

    總結:

    • 隱式聲明的函數在 C 中,返回值被認為是 int
    • 關注編譯器告警,-Wall -Wextra 要打開,開發模式下最好打開 -Werror。

    GCC Illegal Instruction——internal compiler error: Illegal instruction

    前陣子,接到用戶反饋,在編譯 Nebula Graph 過程中遭遇了編譯器非法指令的錯誤,詳見(#978)[]

    錯誤信息大概是這樣的:

    Scanning dependencies of target base_obj_gch
    [ 0%] Generating Base.h.gch
    In file included from /opt/nebula/gcc/include/c++/8.2.0/chrono:40,
    from /opt/nebula/gcc/include/c++/8.2.0/thread:38,
    from /home/zkzy/nebula/nebula/src/common/base/Base.h:15:
    /opt/nebula/gcc/include/c++/8.2.0/limits:1599:7: internal compiler error: Illegal instruction
    min() _GLIBCXX_USE_NOEXCEPT { return FLT_MIN; }
    ^~~
    0xb48c5f crash_signal
    ../.././gcc/toplev.c:325
    Please submit a full bug report,
    with preprocessed source if appropriate.

    既然是 internal compiler error,想必是 g++ 本身使用了非法指令。為了定位具體的非法指令集及其所屬模塊,我們需要復現這個問題。幸運的是,下面的代碼片段就能觸發:

    #include <thread>
    int main() 
    {
        return 0;
    }

    非法指令一定會觸發 SIGILL,又因為 g++ 只是編譯器的入口,真正幹活的是 cc1plus。我們可以使用 gdb 來運行編譯命令,抓住子進程使用非法指令的第一現場:

    $ gdb --args /opt/nebula/gcc/bin/g++ test.cpp
    gdb> set follow-fork-mode child
    gdb> run
    Starting program: /opt/nebula/gcc/bin/g++ test.cpp
    [New process 31172]
    process 31172 is executing new program: /opt/nebula/gcc/libexec/gcc/x86_64-pc-linux-gnu/8.2.0/cc1plus
    Thread 2.1 "cc1plus" received signal SIGILL, Illegal instruction.
    [Switching to process 31172]
    0x00000000013aa0fb in __gmpn_mul_1 ()
    gdb> disas
    ...
    0x00000000013aa086 <+38>: mulx (%rsi),%r10,%r8
    ...

    Bingo!mulx 屬於 BMI2 指令集,報錯機器 CPU 不支持該指令集。
    仔細調查,引入該指令集的是 GCC 的依賴之一,GMP。默認情況下,GMP 會在 configure 階段探測當前機器的 CPU 具體類型,以期最大化利用 CPU 的擴展指令集,提升性能,但卻犧牲了二進制的可移植性。解決方法是,configure 之前,使用代碼目錄中的 configfsf.guess configfsf.sub 替換或者覆蓋默認的 config.guess 和 config.sub

    總結:

    • 某些依賴可能因為性能或者配置的原因,造成二進制的不兼容。
    • 缺省參數下,GCC 為了兼容性,不會使用較新的指令集。
    • 為了平衡兼容性和性能,你需要做一些額外的工作,比如像 glibc 那樣在運行時選擇和綁定某個具體實現。

    最後,如果你想嘗試編譯一下 Nebula 源代碼可參考以下方式:

    bash> git clone https://github.com/vesoft-inc/nebula.git
    bash> cd nebula && ./build_dep.sh N

    有問題請在 GitHub 或者微信公眾號上留言。

    附錄

    • Nebula Graph:一個開源的分佈式圖數據庫
    • GitHub:
    • 知乎:
    • 微博:

    本站聲明:網站內容來源於博客園,如有侵權,請聯繫我們,我們將及時處理
    【其他文章推薦】

    ※想知道網站建置網站改版該如何進行嗎?將由專業工程師為您規劃客製化網頁設計後台網頁設計

    ※不管是台北網頁設計公司台中網頁設計公司,全省皆有專員為您服務

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

    ※帶您來看台北網站建置台北網頁設計,各種案例分享

    小三通物流營運型態?

    ※快速運回,大陸空運推薦?