標籤: 租車

  • 使用四叉樹優化碰撞檢測

    使用四叉樹優化碰撞檢測

    四叉樹是干什麼的?

    百度百科
    四元樹又稱四叉樹是一種樹狀數據結構,在每一個節點上會有四個子區塊。四元樹常應用於二維空間數據的分析與分類。 它將數據區分成為四個象限。數據範圍可以是方形或矩形或其他任意形狀。
    從定義我們可以看出重點信息:

    1. 樹狀結構
    2. 四個區塊
    3. 分類
    4. 矩形

    圖示講解

    講解之前需要先說明一下四叉樹是用來做什麼的,明白了原理才好理解它的行為。
    使用四叉樹就是使用分類的方法,減少碰撞節點的個數,只取出與給定碰撞體相同區域或者壓在碰撞體所在區域邊上的對象。

    1. 將遊戲屏幕分為四個區域。
    2. 插入對象
    3. 插入的對象超過了我們設置的閾值時,劃分
    4. 插入的對象再次超過了我們設置的閾值時,繼續分。

    分析

    插入

    從上面的圖示我們可以很好理解四叉樹的原理。涉及的都是插入操作。
    那麼插入操作具體都做了什麼呢?

    從代碼中我們可以看出:

    1. 當插入第一個對象的時候只走了2;這個時候沒有子樹,所以不會走1,因為objects(管理的對象)的長度還沒有超過我們設置的閾值MAX_OBJECTS,所以也不會走3。
    2. 一直插入,當objects中的數量,超過了我們設置的閾值MAX_OBJECT,就會開始劃分,產生子樹,有了nodes,劃分之後將自己管理的節點插入到子樹中。再此之前,都不會走1,因為還沒有產生子樹。
    3. 劃分之後再次插入新對象,如果對象可以獲得對應的象限,就會走1 不會走2和3,如果沒有獲得對應的象限才會走2,3(沒有獲得的情況可能是你創建的對象在屏幕外,遊戲中很多情況是敵人從屏幕外走進屏幕的,具體可參考我做的《星際迷航》或者《星際戰》遊戲)。

    更新對象

    我是把四插入作為了對象管理器使用,要不然對象也需要更新,所以有了這一步操作。如果不這樣你需要自己創建對象管理器,一個一個放進去,刪除。通過四叉樹直接管理省了不少事情。

    更新象限信息。

    這是一個遞歸操作,更新象限做的事情比較多了。

    1. 檢查對象是否存活,如果死亡就回收,我這裏使用了對象池,所以對象實現了poolAble接口。

    2. 判斷對象的所佔區域是否在四叉樹的區域內
      這裏需要說明的是一個四叉樹本身的區域是它管理的四個象限這麼大。也就是一個四叉樹管理四個象限

      不在管理區域的話需要判斷當前this是否為根節點,如果是說明對象已經出屏了。(這個時候可以通過對象實現的isVisible接口來控制是否回收,因為不是所有在屏幕外的都要回收,比如要進入屏幕的敵人,是不可能回收的,所以需要自己用isVisible接口來控制)。如果不是就將對象放入根節點,重新劃分。

    3. 在管理區域內,就看看在四叉樹管理的哪個象限里。更新象限信息。

      如果沒有變化什麼都不過,如果有變化,先判斷象限是否為-1,為什麼會出現-1,也就是不在四個象限的任何一個象限?因為壓線了。此番操作后的結果如下圖。

    根據給定矩形獲取對象列表

    1. 第一個是步長,用於獲取深度,當然深度越長,處理的時間越長,獲取的對象也精細。這個可以根據自己遊戲的同屏四叉樹層級而定了。
    2. 如果通過obj的rect獲得對象所在象限如果獲得了對應的象限,用獲得的象限的四叉樹再獲取。如果壓線的話就需要將碰撞的兩個象限的內容都取出來。
    3. 返回四叉樹中沒有分割象限的對象。

    怎麼用呢?

    自然就是把要碰撞的對象傳給retrieve函數獲得需要碰撞的對象列表進行碰撞檢測了。
    也就是文章靠頭說的:
    使用四叉樹就目的是為了減少碰撞節點的個數。使用的是分類的方法。
    至於用什麼樣的碰撞檢測函數,不是四叉樹關心的事情,

    至於用幾個四叉樹管理對象,也不是四叉樹關心的事情。

    結語

    想要demo的同學可以去我的微店或者官方creator商城購買《跨引擎遊戲框架》源碼,跟demo是一個項目。買過的同學請加我好友,群已經建好,有更新我會群里直接發包。

    源碼購買入口:

    demo展示:

    項目截圖:

    框架的相關模塊教程可以到《我的專輯》遊戲開發進階教程中獲取。
    後續還會推出更多與框架有關的教程:如:戰鬥框架,教學框架等等。並附帶完整的遊戲實現(飛行射擊遊戲為例,學會做飛行射擊遊戲不是目的,目的是通過這一款遊戲,你可以獲得做其他所有類型的遊戲的思路)。希望可以在不餓死自己的前提下幫助更過的朋友們快速找到開發思路。

    長按下方二維碼,關注《微笑遊戲》公眾號,獲取更多精彩內容。

    歡迎掃碼關注公眾號《微笑遊戲》,瀏覽更多內容。

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

    【其他文章推薦】

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

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

    ※回頭車貨運收費標準

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

    ※超省錢租車方案

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

    聚甘新

  • C# WPF – MVVM實現OPC Client管理系統

    C# WPF – MVVM實現OPC Client管理系統

    前言

    本文主要講解採用WPF MVVM模式設計OPC Client的過程,算作對於WPF MVVM架構的學習記錄吧!不足之處請不吝賜教,感謝!

    涉及知識點

    • C#基礎
    • Xaml基礎
    • 命令、通知和數據綁定
    • Prism+Blend
    • MahApps.Metro(第三方框架)
    • OPC

    項目實現功能

    • 用戶登陸(模擬登陸過程,未連接數據庫)
    • OPC同步讀寫、異步讀寫操作等

    開發環境

    • Window 10
    • Visual Studio 2019
    • .Net Framework 4.8

    成品效果圖

    項目詳解

    MVVM框架搭建

    為了節省開發時間,在事件綁定上使用了Prism框架,OPC通信方面使用了OPCDAAuto.dll類庫,二者均可以通過Nuget方式安裝到項目中。

    • 定義了一個DelegateCommand類用來處理屬性和命令;
    • 定義了一個NotificationObject類用來通知屬性和命令的改變;

    注意:在使用事件綁定時,需要添加引用 xmlns:i=”http://schemas.microsoft.com/xaml/behaviors”,然後根據控件對應事件的名稱設置綁定命令即可。

    比如我們想給ComboBox的SelectionChanged事件設置一個事件綁定,可這麼寫

    xaml代碼:

    <ComboBox
        x:Name="CombServerList"
        Width="120"
        Margin="{StaticResource ControlMargin}"
        ItemsSource="{Binding ServerList}">
        <!--  事件綁定  -->
        <i:Interaction.Triggers>
              <i:EventTrigger EventName="SelectionChanged">
                 <i:InvokeCommandAction Command="{Binding SelectionChangedCommand}" CommandParameter="{Binding ElementName=CombServerList}" />
              </i:EventTrigger>
         </i:Interaction.Triggers>
    
    </ComboBox>

    View Code

    VM代碼:

            public ICommand SelectionChangedCommand
            {
                get
                {
                    return new Prism.Commands.DelegateCommand<ComboBox>((combobox) =>
                    {
                       // 業務邏輯
                    });
                }
            }

    View Code

    相關類的定義代碼如下:

     public class DelegateCommand : ICommand
        {
            public event EventHandler CanExecuteChanged;
    
            /// <summary>
            /// 判斷判斷命令是否可以被執行
            /// </summary>
            /// <param name="parameter"></param>
            /// <returns></returns>
            public bool CanExecute(object parameter)
            {
                if (this.CanExecuteFunc != null)
                {
                    this.CanExecuteFunc(parameter);
                }
                else
                {
                    return true;
                }
                return false;
            }
    
            /// <summary>
            /// 執行相關的函數或者命令
            /// </summary>
            /// <param name="parameter"></param>
            public void Execute(object parameter)
            {
                if (this.ExecuteAction != null)
                {
                    this.ExecuteAction(parameter);
                }
                else
                {
                    return;
                }
            }
    
            /// <summary>
            /// 聲明一個委託用來執行命令對應的方法
            /// </summary>
            public Action<object> ExecuteAction { get; set; }
    
            /// <summary>
            /// 聲明一個方法,用來判斷命令是否可以被執行
            /// </summary>
            public Func<object, bool> CanExecuteFunc { get; set; }
    
        }

    DelegateCommand

    public class NotificationObject : INotifyPropertyChanged
        {
            /// <summary>
            /// 實現接口
            /// </summary>
            public event PropertyChangedEventHandler PropertyChanged;
    
            /// <summary>
            /// 通知屬性的改變
            /// </summary>
            /// <param name="propertyName"></param>
            public void RaisePropertyChanged(string propertyName)
            {
                if (this.PropertyChanged != null)
                {
                    this.PropertyChanged.Invoke(this, new PropertyChangedEventArgs(propertyName));
                }
            }
        }

    NotificationObject

    UI界面搭建

    這裏主要採用第三方開源框架MahApps.Metro,可以通過NuGet方式安裝到項目中,這裏不再展開講解,感興趣的朋友可以參考 MahApps.Metro – Quick Start

    OPC相關處理

    大致分為如下幾步:

    • 獲取OPC服務列表
    • 連接OPC服務
    • 創建分組
    • 獲取項目列表
    • 添加項目到分組中
    • 對項目的內容進行讀寫操作

    比較簡單,不再展開了。

    登陸界面

    這裏我們說一說登陸界面的實現,由於追求PURE MVVM,所以這裡有三點需要說明一下:

    • PasswordBox綁定
    • 圓形頭像
    • 登陸窗體切換

    PasswordBox綁定:自定義幫助類,使用PasswordBoxBehavior實現綁定;

    圓形頭像:自定義樣式,增加Image圓角屬性;

    登陸窗體切換:藉助prism的shell。

    至此,已全部結束。

      作者:Jeremy.Wu
      出處:https://www.cnblogs.com/jeremywucnblog/
      本文版權歸作者和博客園共有,歡迎轉載,但未經作者同意必須保留此段聲明,且在文章頁面明顯位置給出原文連接,否則保留追究法律責任的權利。

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

    【其他文章推薦】

    ※超省錢租車方案

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

    ※回頭車貨運收費標準

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

    FB行銷專家,教你從零開始的技巧

    聚甘新

  • 特斯拉帶動電池需求,住友化學擬擴產四倍

    特斯拉(Tesla)雖然傳出供貨不及、營收下滑等問題,但仍明顯帶動全球市場對於電動車的關注與需求。看好電動車用鋰電池的需求量將繼續成長,日本住友化學(Sumitomo Chemical)計畫在2018年時將位於南韓的分隔膜(separator)產能擴增至2016年初水準的4倍。

    根據《日經》報導,電動車市場的擴大,直接推動電動車用鋰電池的供應鏈強度,從材料到電池包都成為產業關注的焦點。特斯拉的車用電池由日本Panasonic所提供,而Panasonic供應給特斯拉的電池所使用的分隔膜,則由住友化學供應。因應特斯拉打算在2018年將年產能提高到50萬輛、2020年增至100萬輛,住友化學預期Panasonic的分隔膜需求會在接下來暴漲,因此決定擴產。

    正極與負極材料、分隔膜、電解液是鋰電池的四大關鍵材料,且日廠佔有絕對的市佔率。除住友化學供應分隔膜給 Panasonic 之外,Toray也有供應分隔膜給Panasonic、LG Chem,且預計在2018年底時將產能提高70%。另一分隔膜廠商旭化成計畫在2020年底前倍增分隔膜產能,住友金屬礦山打算在近年將正極材料產能擴大兩倍、昭和電工則規畫在今年底前提高負極材料產能80%。

    MoneyDJ引用日本市調機構富士經濟的說法,認為全球電動車市場會在2020年左右急速擴大,到2035年時成長到567萬輛,較2015年飆漲近16倍之多。其中,又以中國、歐洲市場的成長幅度最為明顯。

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

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

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

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

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

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

    ※超省錢租車方案

    聚甘新

  • Formula E 香港開賽,在地團隊推出新車

    Formula E 香港開賽,在地團隊推出新車

    由FIA於2012年成立推行的Formula E電動方程式賽車,今年10月8、9日將在香港開賽。香港全城不僅摩拳擦掌準備迎接賽事,一支在地團隊也將展示一輛「100%香港製造」的未來電動概念車。

    Formula E的第一場正式賽事於2014年在中國北京舉行,至今已巡迴亞、歐、美洲多個國家,2015-2016賽季的參賽車隊共有九隊。Formula E與正規方程式賽車最大的差別之處,在於所有賽車都是電動車,希望藉此鼓勵電動車技術發展。

    香港中環海濱區將於10月8、9日舉辦Formula E本季賽事的其中一場比賽。除了引人注目的賽事之外,賽車場附近的eVillage空間也將展出一輛概念電動車,搭載智能化安全駕駛輔助系統、再生能源、電腦視覺等功能。系統亦可收集交通數據,即時提示交通路況。

    這輛概念電動車由香港科學園公司整合九個科技團隊的技術所打造,是香港第一輛100%港產電動車。科學園公司也表示將與香港本地的一家巴士公司合作,研究將電動車計入應用於巴士上,並預計在今年年底推出自動駕駛巴士。

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

    【其他文章推薦】

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

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

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

    ※超省錢租車方案

    FB行銷專家,教你從零開始的技巧

    聚甘新

  • 特斯拉在台產學合作,台科大設充電站

    特斯拉在台產學合作,台科大設充電站

    今年9月正式登台的美國電動車品牌特斯拉(Tesla)宣布與台灣科技大學(台科大)推動產學合作,在台科大校園內設置電動車充電站,作為學生實習場所,未來還將提供學生產業實習的機會,共同培育電動車產業人才。

    台科大校長廖慶榮在與特斯拉聯合舉辦的產學合作簽約暨校園充電站啟用儀式上表示,全球電動車市場不斷擴張,是未來性極佳的產業。與特斯拉的產學合作將能協助電力、電子、機械等相近領域的學生接觸電動車產業,行銷、推廣廣領域的學生也能受惠。

    特斯拉在台科大校園內設置了6座特斯拉汽車專用的「目的地充電站」,包括1座位於校門口的展示用充電站、1座供學生實習,另外4座位於國際大樓B2停車場,開放民眾使用。每透過充電站充電1小時,最多可行駛100公里。

    特斯拉的充電站分成超級充電站、家用充電站、目的地充電站三種。本次設置在台科大的目的地充電站,是目前分布最普遍的一種。

    (照片提供:台科大)

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

    【其他文章推薦】

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

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

    ※超省錢租車方案

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

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

    聚甘新

  • 利基應用突破,崧騰五年車用佔比衝兩成

    車用接單突破,電源開關模組廠商崧騰(3484)車用高壓連接器,8月起開始放量,另開關模組也切入大陸合資車廠,預計11、12月開始交貨。董事長張俊雲(附圖)表示,今(2016)年算是打入車用客戶很好的開始,2017-2018年會看到比較明顯的效果,五年內期許車用佔比能超過兩成。

    崧騰成立於1992年,初期是以單純的開關鍵為主,其後在為日系工具機客戶開發機構與控制器,建立成功模式下,開始整合機構模具、電子控制器與軟硬體的能力,並逐步擴大利基領域的轉型;以今年前8月來看,資訊與消費電子的營收占比已掉到35~36%,取而代之的是工具機占比超過四成,家電占比則拉高至15~17%。

    而今年崧騰在車用上也有重大突破。張俊雲透露,該公司在車用的產品包括車用電源插座、電池連接器、高壓連接器及開關模組等,以高壓連接器來說,因需承受至少170伏特以上的電壓測試,且車廠供應鏈封閉,過去幾乎都是如美商安費諾等的天下,但公司歷經一年以上的開發與認證,去年起已陸續打入電動機車及中美電動車大廠客戶,且8月起單月出貨已有萬顆水準,同時未來配合電源廠客戶台達電(2308)的開發進度,產品還有機會擴及電動車或充電座上其他機構零件。

    除了高壓連接器,張俊雲說,在傳統車廠方面,崧騰也已接獲大陸合資車廠開關模組訂單,預計11、12月交貨。他說,今年是車用很好的開始,2017-2018年會看到比較明顯的成果,期許五年內車用佔比能突破兩成水準。

    除利基應用的開花結果外,配合全球客戶的東南亞布局,崧騰也在2013年8月設立柬埔寨廠,工廠坐落於金邊奇倉工業區,2014年2月正式量產,生產端子座注射、線材加工、成品組裝等自動化相對較低的製程,現有月產能共計1,500萬個,對集團單月營業額貢獻80-100萬美元,占比近一成。

    張俊雲說,包括台達電、日本客戶與美系工具機大廠,都逐步建置東南亞的供貨基地,主要考量不外乎人工成本較低與人力穩定度較高,而該公司當時赴柬國設廠,考慮的也是約當大陸及泰國三分之一的人工成本、政治相對穩定、沒有外匯管制、占比六成的勞動人口等;其對東南亞兩個據點泰國及柬埔寨的長期期許是,前者能擔綱集團在東協的銷售據點,後者則是製造中心,並能與中國大陸的華南及華東生產基地,並駕齊驅。

    法人也估計,崧騰今年第三季營收可望創下新高,第四季小幅衰退,但下半年在營收規模與毛利率提升下,獲利將較上半年近倍成長,全年仍力拼本業獲利持續加溫,整體盈餘優於去年水準。

    本文由嘉實資訊 MoneyDJ 授權使用 記者 蕭燕翔 報導

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

    【其他文章推薦】

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

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

    ※回頭車貨運收費標準

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

    ※超省錢租車方案

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

    聚甘新

  • 別賜死蘋果車?傳庫克沒死心、擬收購McLaren超跑

    別賜死蘋果車?傳庫克沒死心、擬收購McLaren超跑

    先前一度傳出蘋果電動車開發案「泰坦計畫」(Project Titan)胎死腹中,蘋果將放棄硬體,轉向研發自駕車技術。不過新消息顯示,蘋果似乎還沒死心,向英國超跑車商McLaren提親。

    巴倫(Barronˋs)、英國金融時報21日報導,內情人士透露,蘋果考慮收購McLaren、或進行策略投資,雙方好幾個月前開始洽談。據了解,蘋果對McLaren的工程技術和專利極感興趣,估計若真要併購McLaren,價格可能為10~15億英鎊。不過相關人士強調,最近蘋果電動車發展方向改變,不確定是否繼續協商。

    消息傳出後,McLaren發布聲明,表示未與蘋果討論投資提案,不過沒有說明蘋果是否曾接洽過該公司。

    McLaren出面否認,仍然止不住市場議論。Creative Strategies分析師Ben Bajarin以特斯拉和英國超跑Lotus結盟為例,說明可行性。他指出,2004年特斯拉與Lotus合作,發布電動跑車「Tesla Roadster」,定價十萬美元。儘管Roadster賣不到3,000輛,卻替之後的特斯拉暢銷車款「Model S」打下基礎。Bajarin稱,特斯拉從高檔跑車出發,蘋果或許也會如此。

    富國銀行(Wells Fargo)的Maynard Um則認為,蘋果看上的不是硬體,而是McLaren的感測器技術。他在報告稱,McLaren超跑聞名於世,但是蘋果青睞的應是旗下的McLaren Applied Technologies部門。McLaren跑車利用偵測器蒐集胎壓、煞車溫度、衝擊力道等資料,透過預測分析,提升汽車維修和表現;此一技術可運用於許多領域,如能源業、健保業等。

    紐約時報9月初報導,知情人士透露,蘋果發展電動車計畫,因潛在競爭者眾且技術難度高而大打退堂鼓,策略面臨修正,部分泰坦研發案已提前結案,並連帶資遣數十名工作人員。

    蘋果七月請回老將Bob Mansfield主導泰坦計畫,重心從硬體製造移往自駕車應用科技,裁員是策略轉向的一部份。谷歌在更早之前就開始研發自駕車,且已上路測試好幾年,重心同樣放在谷歌最擅長的軟體研發與應用。

    本文由嘉實資訊 MoneyDJ 授權使用 記者 陳苓 報導

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

    【其他文章推薦】

    ※超省錢租車方案

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

    ※回頭車貨運收費標準

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

    FB行銷專家,教你從零開始的技巧

    聚甘新

  • ASP.NET Core 對Controller進行單元測試

    單元測試對我們的代碼質量非常重要。很多同學都會對業務邏輯或者工具方法寫測試用例,但是往往忽略了對Controller層寫單元測試。我所在的公司沒見過一個對Controller寫過測試的。今天來演示下如果對Controller進行單元測試。以下內容默認您對單元測試有所了解,比如如何mock一個接口。在這裏多叨叨一句,面向接口的好處,除了能夠快速的替換實現類(其實大部分接口不會有多個實現),最大的好處就是可以進行mock,可以進行單元測試。

    測試Action

    下面的Action非常簡單,非常常見的一種代碼。根據用戶id去獲取用戶信息然後展示出來。下面看看如何對這個Action進行測試。

       public class UserController : Controller
        {
            private readonly IUserService _userService;
            public UserController(IUserService userService)
            {
                _userService = userService;
            }
    
            public IActionResult UserInfo(string userId)
            {
                if (string.IsNullOrEmpty(userId))
                {
                    throw new ArgumentNullException(nameof(userId));
                }
    
                var user = _userService.Get(userId);
                return View(user);
            }
          
        }
    

    測試代碼:

      [TestMethod()]
            public void UserInfoTest()
            {
    
                var userService = new Mock<IUserService>();
                userService.Setup(s => s.Get(It.IsAny<string>())).Returns(new User());
    
                var ctrl = new UserController(userService.Object);
                //對空參數進行assert
                Assert.ThrowsException<ArgumentNullException>(() => {
                    var result = ctrl.UserInfo(null);
                });
                //對空參數進行assert
                Assert.ThrowsException<ArgumentNullException>(() => {
                    var result = ctrl.UserInfo("");
                });
    
                var result = ctrl.UserInfo("1");
                Assert.IsNotNull(result);
                Assert.IsInstanceOfType(result, typeof(ViewResult));
            }
    

    我們對一個Action進行測試主要的思路就是模擬各種入參,使測試代碼能夠到達所有的分支,並且Assert輸出是否為空,是否為指定的類型等。

    對ViewModel進行測試

    我們編寫Action的時候還會涉及ViewModel給視圖傳遞數據,這部分也需要進行測試。修改測試用例,加入對ViewModel的測試代碼:

      [TestMethod()]
            public void UserInfoTest()
            {
                var userService = new Mock<IUserService>();
                userService.Setup(s => s.Get(It.IsAny<string>())).Returns(new User()
                {
                    Id = "x"
                }) ;
    
                var ctrl = new UserController(userService.Object);
                Assert.ThrowsException<ArgumentNullException>(() => {
                    var result = ctrl.UserInfo(null);
                });
                Assert.ThrowsException<ArgumentNullException>(() => {
                    var result = ctrl.UserInfo("");
                });
    
                var result = ctrl.UserInfo("1");
                Assert.IsNotNull(result);
                Assert.IsInstanceOfType(result, typeof(ViewResult));
                //對viewModel進行assert
                var vr = result as ViewResult;
                Assert.IsNotNull(vr.Model);
                Assert.IsInstanceOfType(vr.Model, typeof(User));
                var user = vr.Model as User;
                Assert.AreEqual("x", user.Id);
            }
    

    對ViewData進行測試

    我們編寫Action的時候還會涉及ViewData給視圖傳遞數據,這部分同樣需要測試。修改Action代碼,對ViewData進行賦值:

       public IActionResult UserInfo(string userId)
            {
                if (string.IsNullOrEmpty(userId))
                {
                    throw new ArgumentNullException(nameof(userId));
                }
    
                var user = _userService.Get(userId);
    
                ViewData["title"] = "user_info";
    
                return View(user);
            }
          
    

    修改測試用例,加入對ViewData的測試代碼:

       [TestMethod()]
            public void UserInfoTest()
            {
                var userService = new Mock<IUserService>();
                userService.Setup(s => s.Get(It.IsAny<string>())).Returns(new User()
                {
                    Id = "x"
                }) ;
    
                var ctrl = new UserController(userService.Object);
                Assert.ThrowsException<ArgumentNullException>(() => {
                    var result = ctrl.UserInfo(null);
                });
                Assert.ThrowsException<ArgumentNullException>(() => {
                    var result = ctrl.UserInfo("");
                });
    
                var result = ctrl.UserInfo("1");
                Assert.IsNotNull(result);
                Assert.IsInstanceOfType(result, typeof(ViewResult));
    
                var vr = result as ViewResult;
                Assert.IsNotNull(vr.Model);
                Assert.IsInstanceOfType(vr.Model, typeof(User));
                var user = vr.Model as User;
                Assert.AreEqual("x", user.Id);
                //對viewData進行assert
                Assert.IsTrue(vr.ViewData.ContainsKey("title"));
                var title = vr.ViewData["title"];
                Assert.AreEqual("user_info", title);
            }
    

    對ViewBag進行測試

    因為ViewBag事實上是ViewData的dynamic類型的包裝,所以Action代碼不用改,可以直接對ViewBag進行測試:

         [TestMethod()]
            public void UserInfoTest()
            {
                var userService = new Mock<IUserService>();
                userService.Setup(s => s.Get(It.IsAny<string>())).Returns(new User()
                {
                    Id = "x"
                }) ;
    
                var ctrl = new UserController(userService.Object);
                Assert.ThrowsException<ArgumentNullException>(() => {
                    var result = ctrl.UserInfo(null);
                });
                Assert.ThrowsException<ArgumentNullException>(() => {
                    var result = ctrl.UserInfo("");
                });
    
                var result = ctrl.UserInfo("1");
                Assert.IsNotNull(result);
                Assert.IsInstanceOfType(result, typeof(ViewResult));
    
                var vr = result as ViewResult;
                Assert.IsNotNull(vr.Model);
                Assert.IsInstanceOfType(vr.Model, typeof(User));
                var user = vr.Model as User;
                Assert.AreEqual("x", user.Id);
    
                Assert.IsTrue(vr.ViewData.ContainsKey("title"));
                var title = vr.ViewData["title"];
                Assert.AreEqual("user_info", title);
                //對viewBag進行assert
                string title1 = ctrl.ViewBag.title;
                Assert.AreEqual("user_info", title1);
            }
    

    設置HttpContext

    我們編寫Action的時候很多時候需要調用基類里的HttpContext,比如獲取Request對象,獲取Path,獲取Headers等等,所以有的時候需要自己實例化HttpContext以進行測試。

        var ctrl = new AccountController();
        ctrl.ControllerContext = new ControllerContext();
        ctrl.ControllerContext.HttpContext = new DefaultHttpContext();
    

    對HttpContext.SignInAsync進行mock

    我們使用ASP.NET Core框架進行登錄認證的時候,往往使用HttpContext.SignInAsync進行認證授權,所以單元測試的時候也需要進行mock。下面是一個典型的登錄Action,對密碼進行認證后調用SignInAsync在客戶端生成登錄憑證,否則跳到登錄失敗頁面。

       public async Task<IActionResult> Login(string password)
            {
                if (password == "123")
                {
                    var claims = new List<Claim>
                    {
                      new Claim("UserName","x")
                    };
                    var authProperties = new AuthenticationProperties
                    {
                    };
                    var claimsIdentity = new ClaimsIdentity(
                      claims, CookieAuthenticationDefaults.AuthenticationScheme);
                    await HttpContext.SignInAsync(
                        CookieAuthenticationDefaults.AuthenticationScheme,
                        new ClaimsPrincipal(claimsIdentity),
                        authProperties);
                    return Redirect("login_success");
                }
    
                return Redirect("login_fail");
            }
    

    HttpContext.SignInAsync其實個時擴展方法,SignInAsync其實最終是調用了IAuthenticationService里的SignInAsync方法。所以我們需要mock的就是IAuthenticationService接口,否者代碼走到HttpContext.SignInAsync會提示找不到IAuthenticationService的service。而IAuthenticationService本身是通過IServiceProvider注入到程序里的,所以同時需要mock接口IServiceProvider。

        [TestMethod()]
            public async Task LoginTest()
            {
                var ctrl = new AccountController();
    
                var authenticationService = new Mock<IAuthenticationService>();
                var sp = new Mock<IServiceProvider>();
                sp.Setup(s => s.GetService(typeof(IAuthenticationService)))
                    .Returns(() => {
                        return authenticationService.Object;
                    });
                ctrl.ControllerContext = new ControllerContext();
                ctrl.ControllerContext.HttpContext = new DefaultHttpContext();
                ctrl.ControllerContext.HttpContext.RequestServices = sp.Object;
    
               var result = await ctrl.Login("123");
                Assert.IsNotNull(result);
                Assert.IsInstanceOfType(result, typeof(RedirectResult));
                var rr = result as RedirectResult;
                Assert.AreEqual("login_success", rr.Url);
    
                result = await ctrl.Login("1");
                Assert.IsNotNull(result);
                Assert.IsInstanceOfType(result, typeof(RedirectResult));
                rr = result as RedirectResult;
                Assert.AreEqual("login_fail", rr.Url);
            }
    

    對HttpContext.AuthenticateAsync進行mock

    HttpContext.AuthenticateAsync同樣比較常用。這個擴展方法同樣是在IAuthenticationService里,所以測試代碼跟上面的SignInAsync類似,只是需要對AuthenticateAsync繼續mock返回值success or fail。

         public async Task<IActionResult> Login()
            {
                if ((await HttpContext.AuthenticateAsync()).Succeeded)
                {
                    return Redirect("/home");
                }
    
                return Redirect("/login");
            }
    

    測試用例:

    
            [TestMethod()]
            public async Task LoginTest1()
            {
                var authenticationService = new Mock<IAuthenticationService>();
                //設置AuthenticateAsync為success
                authenticationService.Setup(s => s.AuthenticateAsync(It.IsAny<HttpContext>(), It.IsAny<string>()))
                    .ReturnsAsync(AuthenticateResult.Success(new AuthenticationTicket(new System.Security.Claims.ClaimsPrincipal(), "")));
                var sp = new Mock<IServiceProvider>();
                sp.Setup(s => s.GetService(typeof(IAuthenticationService)))
                    .Returns(() => {
                        return authenticationService.Object;
                    });
    
                var ctrl = new AccountController();
                ctrl.ControllerContext = new ControllerContext();
                ctrl.ControllerContext.HttpContext = new DefaultHttpContext();
                ctrl.ControllerContext.HttpContext.RequestServices = sp.Object;
    
                var act = await ctrl.Login();
                Assert.IsNotNull(act);
                Assert.IsInstanceOfType(act, typeof(RedirectResult));
                var rd = act as RedirectResult;
                Assert.AreEqual("/home", rd.Url);
                //設置AuthenticateAsync為fail
                authenticationService.Setup(s => s.AuthenticateAsync(It.IsAny<HttpContext>(), It.IsAny<string>()))
                   .ReturnsAsync(AuthenticateResult.Fail(""));
    
                act = await ctrl.Login();
                Assert.IsNotNull(act);
                Assert.IsInstanceOfType(act, typeof(RedirectResult));
                rd = act as RedirectResult;
                Assert.AreEqual("/login", rd.Url);
    
            }
    

    Filter進行測試

    我們寫Controller的時候往往需要配合很多Filter使用,所以Filter的測試也很重要。下面演示下如何對Fitler進行測試。

        public class MyFilter: ActionFilterAttribute
        {
            public override void OnActionExecuting(ActionExecutingContext context)
            {
                if (context.HttpContext.Request.Path.Value.Contains("/abc/"))
                {
                    context.Result = new ContentResult() {
                        Content = "拒絕訪問"
                    };
                }
    
                base.OnActionExecuting(context);
            }
        }
    

    對Filter的測試最主要的是模擬ActionExecutingContext參數,以及其中的HttpContext等,然後對預期進行Assert。

           [TestMethod()]
            public void OnActionExecutingTest()
            {
                var filter = new MyFilter();
                var actContext = new ActionContext(new DefaultHttpContext(),new RouteData(), new ActionDescriptor());
                actContext.HttpContext.Request.Path = "/abc/123";
                var listFilters = new List<IFilterMetadata>();
                var argDict = new Dictionary<string, object>();
                var actExContext = new ActionExecutingContext(
                    actContext ,
                    listFilters ,
                    argDict ,
                    new AccountController()
                    );
                 filter.OnActionExecuting(actExContext);
    
                Assert.IsNotNull(actExContext.Result);
                Assert.IsInstanceOfType(actExContext.Result, typeof(ContentResult));
                var cr = actExContext.Result as ContentResult;
                Assert.AreEqual("拒絕訪問", cr.Content);
    
                actContext = new ActionContext(new DefaultHttpContext(), new RouteData(), new ActionDescriptor());
                actContext.HttpContext.Request.Path = "/1/123";
                listFilters = new List<IFilterMetadata>();
                argDict = new Dictionary<string, object>();
                actExContext = new ActionExecutingContext(
                    actContext,
                    listFilters,
                    argDict,
                    new AccountController()
                    );
                filter.OnActionExecuting(actExContext);
                Assert.IsNull(actExContext.Result);
            }
    

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

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

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

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

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

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

    ※超省錢租車方案

    聚甘新

  • Linux nohup命令詳解,終端關閉程序依然可以在執行!

    Linux nohup命令詳解,終端關閉程序依然可以在執行!

    大家好,我是良許。

    在工作中,我們很經常跑一個很重要的程序,有時候這個程序需要跑好幾個小時,甚至需要幾天,這個時候如果我們退出終端,或者網絡不好連接中斷,那麼程序就會被中止。而這個情況肯定不是我們想看到的,我們希望即使終端關閉,程序依然可以在跑。

    這時我們就可以使用 nohup 這個命令。

    nohup 命令是英語詞組 no hangup 的縮寫,意思是不掛斷,也就是指程序不退出。這個命令會使程序忽略 HUP 信號,保證程序能夠正常進行。HUP 信號有些人可能比較陌生,它是在終端被中止的時候向它所關聯的進程所發出的信號,進程收到這個信號后就會中止運行。所以如果你不希望進程被這個信號幹掉的話,就可以忽略這個信號。而 nohup 命令做的就是這個事情。

    本文我們將詳細介紹 nohup 命令的具體用法。

    nohup命令基本語法

    nohup 命令的基本語法如下:

    $ nohup command arguments
    

    或者:

    $ nohup options
    

    如果你想要得到更多關於 nohup 的用法介紹,可以查看它的幫助頁面:

    $ nohup --help
    

    如果你需要查看它的版本號,可以使用 --version 選項。

    $ nohup --version
    

    使用nohup命令啟動一個程序

    如果你需要運行一個程序,即使對應的 Shell 被退出后依然保持運行,可以這樣使用 nohup 運行這個程序:

    $ nohup command
    

    當這個程序進行起來之後,這個程序對應的 log 輸出及其錯誤日誌都將被記錄在 nohup.out 文件里,這個文件一般位於家目錄或者當前目錄。

    重定向程序的輸出

    如果我不想把程序的輸出保存在家目錄或者當前目錄,我想保存在我指定的路徑,並且自定義文件名,要怎麼操作?這時我們就可以使用重定向操作 >

    比如,我現在有個腳本 myScript.sh 我想把它的輸出保存在家目錄下的 output 目錄下,文件名為 myOutput.txt ,可以這樣運行:

    $ nohup ./myScript.sh > ~/output/myOutput.txt
    

    使用nohup命令後台啟動一個程序

    如果想讓程序在後台運行,可以加上 & 符號。但這樣運行之後,程序就無影無蹤了。想要讓程序重新回到終端,可以使用 fg 命令。

    這個命令的輸出 log 將保存在 nohup.out 文件里,你可以使用 cat 或其它命令查看。第二行里 8699 這個数字代表這個命令對應的進程號,也就是 pid 。我們可以使用 ps 命令來找到這個進程。

    使用nohup同時運行多個程序

    如果你需要同時跑多個程序,沒必要一個個運行,直接使用 && 符號即可。比如,你想同時跑 mkdir ,ping,ls 三個命令,可以這樣運行:

    $ nohup bash -c 'mkdir files &&
    ping -c 1 baidu.com && ls'> output.txt
    

    終止跑在後台的進程

    上面有提到,nohup 命令結合 & 符號可以使進程在後台運行,即使關閉了終端依然不受影響。這時,如果想要終止這個進程,要怎麼操作呢?

    最簡單的當屬 kill 命令,相信大家用過很多次了。

    $ kill -9 PID
    

    那要如何找到進程對應的 pid 呢?我們可以使用 ps 命令。

    $ ps aux | grep myScript.sh
    

    或者你使用 pgrep 命令也行。

    接下來,再使用 kill 命令就可以終止該進程了。

    $ kill -9 14942
    

    公眾號:良許Linux

    有收穫?希望老鐵們來個三連擊,給更多的人看到這篇文章

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

    【其他文章推薦】

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

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

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

    ※超省錢租車方案

    FB行銷專家,教你從零開始的技巧

    聚甘新

  • SpringBoot + Mybatis + Redis 整合入門項目

    SpringBoot + Mybatis + Redis 整合入門項目

    這篇文章我決定一改以往的風格,以幽默風趣的故事博文來介紹如何整合 SpringBoot、Mybatis、Redis。

    很久很久以前,森林里有一隻可愛的小青蛙,他邁着沉重的步伐走向了找工作的道路,結果發現許多的招聘要求都要會 Redis。

    小青蛙就想啥是 Redis 呢,為什麼要用 Redis 呢?難道是因為 Mysql 的幣格不夠高嗎,小青蛙點開了收藏已久的網站:十萬個為什麼

    發現原來隨着使用網站的用戶越來越多,表中的數據也越來越多,查詢速度越來越慢。

    MySql 的性能遇到了瓶頸,所以許多網站都用 Redis 作緩存。

     

    然而能作緩存的不僅只有 Redis,還有 Memcache,那為什麼要用 Redis 呢?

    1、性能方面:它們都是將數據存放在內存中,所以性能基本相似。

    2、數據類型方面:Redis 支持五種數據數據類型:字符串、散列、列表、集合、有序集合,而 Memcache 僅僅支持簡單的 key-value。

    3、數據持久化方面:Redis 可以通過 RDB快照、AOF日誌 等方式進行數據持久化,但是 Memcache 不可以。

    4、數據備份方面:Redis  支持 master-slave 主從模式的數據備份。

     

    在了解到許多 Redis 的好處后,小青蛙已經迫不及待的想了解它了。

    為了更好的使用 Redis,了解 Redis 的五種數據類型適應場景是很有必要的。

    1、String 類型:一個 key 對應一個 value,而 value 不僅僅是 String,也可以是数字、甚至是一個序列化對象。

    2、Hash 類型:一個 key 對應 多個 field,一個 field 對應 yige value,實際上該類型最適合存儲序列化對象。

               key 相當於數據庫表名字,field  相當於主鍵,value 也就是序列化對象。

    3、List 類型:簡單的字符串列表,按照插入順序排序,該結構類似於數據結構中的雙向鏈表,可以從頭部插也可以從尾部插。

    4、Set 類型:它是字符串集合,只不過它是無序的且不存在重複的字符串,但是它可以實現 交集、並集、差集。

    5、ZSet 類型:它也是字符串集合,它和 Set 的區別是該集合的元素存在 score 屬性,按照 score 屬性的高低排序。可以應用在排行榜上。

    值得注意的是,不要習慣性的認為 Redis 字符串只能存字符串,實際上,它可以存儲任何序列化后的對象,當然也可以讀出來。

     

    小青蛙知道了 Redis 的五種數據類型應用場景后,迫不及待的想要實踐它了。

    為了知道如何讓它作為緩存,以及如何操作數據,小青蛙打開了珍藏已久的視頻網站來學習:青蛙愛學習

    在該視頻網站上,小青蛙沉迷其中無法自拔,額,呸呸。緩過神來,發現了一個很好的視頻。小青蛙幽默的說:快進我的收藏夾吃灰去吧。

    小青蛙向來都不是一個收藏從未停止,學習從未開始的青蛙。它模仿着視頻建了一個 SpringBoot 項目。

    此時,小青蛙想為什麼要勾選 Lombok 呢,因為它可以簡化類,並且提供了 log 等功能。

    之後小青蛙為了連上 Mysql 和 Redis 就開始配置 application.properties 文件:

    # 數據源配置
    spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver
    spring.datasource.url=jdbc:mysql://localhost:3306/demo?serverTimezone=UTC&characterEncoding=utf-8
    spring.datasource.username=root
    spring.datasource.password=lemon@mango
    
    # Redis 配置
    spring.redis.database=0
    spring.redis.host=127.0.0.1
    spring.redis.port=6379
    spring.redis.password=
    spring.redis.lettuce.pool.max-active=8
    spring.redis.lettuce.pool.max-wait=-1
    spring.redis.lettuce.pool.max-idle=8
    spring.redis.lettuce.pool.min-idle=0
    spring.redis.lettuce.shutdown-timeout=100

    配置好該文件后,需要 redisTemplate 模板 Bean,因為自動配置的 redisTemplate Bean 沒有提供序列化操作:(因為是入門版的,所以這樣最好理解)

    package com.demo.config;
    
    import org.springframework.context.annotation.Bean;
    import org.springframework.context.annotation.Configuration;
    import org.springframework.data.redis.connection.RedisConnectionFactory;
    import org.springframework.data.redis.core.RedisTemplate;
    import org.springframework.data.redis.serializer.GenericJackson2JsonRedisSerializer;
    import org.springframework.data.redis.serializer.StringRedisSerializer;
    
    @Configuration
    public class RedisConfig {
        
        @Bean
        public RedisTemplate<String, Object> redisTemplate(RedisConnectionFactory factory) {
            RedisTemplate<String, Object> template = new RedisTemplate<String, Object>();
                    template.setConnectionFactory(factory);
                    GenericJackson2JsonRedisSerializer genericJsonRedisSerializer = new GenericJackson2JsonRedisSerializer();
                    template.setKeySerializer(new StringRedisSerializer());
                    template.setValueSerializer(genericJsonRedisSerializer);
                    template.setHashKeySerializer(genericJsonRedisSerializer);
                    template.setHashValueSerializer(genericJsonRedisSerializer);
                    template.afterPropertiesSet();
                    return template;
        }
    }

    至此就整合完成了,小青蛙心想這就完事了?!!!,不信?那就來演示一下:(先建一個簡單的類測試一下)

    package com.test.serviceImpl;
    
    import org.springframework.data.redis.core.RedisTemplate;
    import org.springframework.stereotype.Service;
    import org.springframework.beans.factory.annotation.Autowired;
    import lombok.extern.slf4j.Slf4j;
    
    @Service
    @Slf4j
    public class JustForTest {
        
        @Autowired
        private RedisTemplate redisTemplate;
        
        public void test(String username) {
            
            if(redisTemplate.hasKey(username)) {
                log.info((String)redisTemplate.opsForValue().get(username));
                log.info("get value from redis");
            }else {
                String password = "password";
                log.info(password);
                log.info("get value from mysql");
                log.info("set value to redis");
                redisTemplate.opsForValue().set(username, password);
            }
            
        }
    
    }
    package com.test;
    
    import org.junit.jupiter.api.Test;
    import org.springframework.boot.test.context.SpringBootTest;
    import org.springframework.beans.factory.annotation.Autowired;
    import com.test.serviceImpl.JustForTest;
    
    @SpringBootTest
    class TestApplicationTests {
        
        @Autowired
        private JustForTest justFortest;
    
        @Test
        void contextLoads() {
            justFortest.test("username");
        }
    }

    哦嚯,報錯了,原來是 pom.xml 中少了 commons.pool 依賴,咱給它加上:

            <dependency>
                <groupId>org.apache.commons</groupId>
                <artifactId>commons-pool2</artifactId>
            </dependency>

    再來一次:

    再再來一次:

    可以看到確實存入 Redis 了,小青蛙便去 Redis 數據庫中看看有沒有:

    事實證明確實整合完畢了,小青蛙仍然表示不解,說好的是SpringBoot、Mybatis、Redis的整合呢,怎麼只看到 Redis 的?

    小青蛙剛這麼想,然後視頻里就說了,心急吃不了熱豆腐,需要慢慢來。緊接着,小青蛙就看到了完整的項目結構:

    為了讓青蛙們只關注有關整合的部分,視頻里僅僅只給出 serviceImpl 中的代碼:

    package com.demo.serviceImpl;
    
    import org.springframework.security.core.userdetails.UserDetails;
    import org.springframework.security.core.userdetails.User;
    import org.springframework.security.core.userdetails.UserDetailsService;
    import org.springframework.security.core.userdetails.UsernameNotFoundException;
    import org.springframework.beans.factory.annotation.Autowired;
    import com.demo.pojo.SimpleUser;
    import com.demo.dao.SimpleUserDao;
    import org.springframework.data.redis.core.RedisTemplate;
    import org.springframework.stereotype.Service;
    import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
    import lombok.extern.slf4j.Slf4j;
    
    @Service
    @Slf4j
    @SuppressWarnings({"rawtypes","unchecked"})
    public class SimpleUserServiceImpl implements UserDetailsService {
    
        @Autowired
        private RedisTemplate redisTemplate;
        @Autowired
        private SimpleUserDao userDao;
        @Override 
        public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
            // TODO Auto-generated method stub
            if(redisTemplate.opsForHash().hasKey("user",username)) {
                SimpleUser user = (SimpleUser)redisTemplate.opsForHash().get("user",username);
                return new User(user.getUsername(),user.getPassword(),user.getAuthorities());
            }else {
                SimpleUser user = userDao.findUserByUsername(username);
                if(user != null) {
                    redisTemplate.opsForHash().put("user", "username", user);
                    return new User(user.getUsername(),user.getPassword(),user.getAuthorities());
                }else {
                    throw new UsernameNotFoundException("Username or Password is not correct");
                }
            }
        }
        
        public int addSimpleUser(SimpleUser user) {
            user.setPassword(new BCryptPasswordEncoder().encode(user.getPassword()));
            return userDao.addSimpleUser(user);
        }
    }

    和上面測試的簡單小例子相似,僅僅是把 String 換成了對象。小青蛙對 Mysql 的表結構和Redis中存入的數據比較感心趣:

    由於對 String 類型帶有 ” 符號,所以需要對其進行轉義。

    小青蛙是一個有分享精神的蛙,每次它覺得有價值的東西,它都會分享給它的朋友們,項目地址為:GitHub

    小青娃逐漸的弄懂了怎麼去進行整合,但是它還是不太明白為什麼這樣就能整合,也就是 know how but don’t konw why!

    小青蛙知道:萬事開頭難,但它不知道的是,後面也很難…從此,小青蛙踏上了探尋源碼的道路!呱呱呱……

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

    【其他文章推薦】

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

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

    ※超省錢租車方案

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

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

    聚甘新