分類: 3C資訊

  • 我買中國品牌的車不只是因為便宜!

    我買中國品牌的車不只是因為便宜!

    觀致耗資1600萬與超跑製造商柯尼塞克共同研發的無凸輪軸發動機(代號QamFree),同等排量下,要比傳統的發動機的功率大幅提升45%,最大扭矩大幅提高47%。假如測試通過,進行量產,將成為未來發動機技術的主要發展方向,彷彿為全世界指明了一條比混合動力更為通暢的道路。

    自主品牌亮瞎眼睛的地方還多着!

    當然了,自主品牌能夠亮瞎眼的地方遠不止這兩點。力壓奔馳、寶馬,在歐洲新車安全評鑒協會Euro-NCAp所進行的2013年最佳碰撞車型評選(小型家用車類)一舉奪得第一名的觀致3,讓一眾歪果仁瞠目結舌。觀致耗資1600萬與超跑製造商柯尼塞克共同研發的無凸輪軸發動機(代號QamFree),同等排量下,要比傳統的發動機的功率大幅提升45%,最大扭矩大幅提高47%。假如測試通過,進行量產,將成為未來發動機技術的主要發展方向,彷彿為全世界指明了一條比混合動力更為通暢的道路。吧唧這麼多,我想說的其實很簡單:自主品牌比你想象中牛逼多了!趕緊去4S店下訂吧!

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

    【其他文章推薦】

    ※超省錢租車方案

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

    ※回頭車貨運收費標準

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

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

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

  • Vue —— 精講 VueX (1)

    Vue —— 精講 VueX (1)

    大綱

    這一講我們最主要的就是學習vue中的數據管理VueX,這個是一個大殺器

    demo源代碼地址 https://github.com/BM-laoli/BMlaoli-learn-VueX

    一、回顧一些Promise相關的東西

    Promise 有幾個比較重要的方法,最重要的還是有一個叫做all的方法,這個也是非常的強大的

    假設我們目前要求,希望能按順序的拿到先後的兩個ajax那麼我應該怎麼處理呢

    Promse.all( [
    new Promose( ( resolve,rejcet ) => {
            $.ajax({
                url:'xxxx',
                data:'xxxx',
                sucess:(res) => {
                    resolve(res)
                }
            })
            $.ajax({
                url:'xxxx',
                data:'xxxx',
                sucess:(res) => {
                    resolve(res)
                }
            })
        })
    
    ]).then( results => {
        consel.log(results)
        // 這樣拿到的就是一個數組了, 先後的順序就是裏面的值
    } )
    

    注意啊這裏對promise的深入的解釋說明

    1. 首先我們的兩個回調resolve 還有reject注意啊,
    這兩個回調回調函數是 在傳入的時候定義的,但是調用是在promse里調的!這兩個參數是函數!!函數!!回調函數!
    
    

    一、概念

    Vue官方介紹
    絕大多數的管方都非常喜歡用概念來解釋概念,這就有點難搞了,我這個概念的都不懂,你又給我搞另一個概念
    實際上那個Vuex就是一個大管家,統一進行管理,全局的單例模式

    1.最通俗的解釋

    Vuex實際上就是一個 用來放 一些組件共享的數據的,實際上這可能是是下面這些情況

    1. 登錄
      假設我們目前有50+頁面。我們都每一個頁面都要發送接口請求而且這些請求需要token,那麼如果我是登錄的,我就需要在每一個頁面拿到我的登錄token這樣就造成了數據的傳來傳去非常麻煩,如果我們有一個公共的地方來放這些東西就好了

    2. 購物車。收藏
      也會有這種組件之間打出傳值的情況發生,那麼我如何管理這些東西呢,這個就是一個問題

    綜上所述,我們需要使用Vuex*

    二、如何入門的使用

    2.簡單的使用

    這裏假設有這樣的一個需求:我們目前有兩個組件App.vue 還有BMlaoli.vue 我呢,他們之間有層級的關係,app裏面有一個變量叫做contuend 我希望我在app裏面對countend的操作能夠動態的傳遞到我們的BMlaoli里,而且不使用父子組件傳值,那麼我們如何做呢?親看下面講演

    1. 首先我們需要有兩個組件
      他們都是最基礎的樣子

    App

    <template>
      <div id="app">
        <h1> 我是vueapp </h1>
      </div>
    </template>
    
    <script>
    
    export default {
      name: 'App',
      components: {
      }
    }
    </script>
    
    <style>
    </style>
    
    

    BMlaoli

    
    <template>
        <div>
            <h1>我是bm界面</h1>
        </div>
    </template>
    
    <script>
        export default {
            
        }
    </script>
    
    <style lang="sass" scoped>
    
    </style>
    
    1. app的業務邏輯
    <template>
      <div id="app">
        <p>{{contuned}}</p>
        
          <button @click="contuned ++" >+</button>
          <button @click="contuned --" >-</button>
    
      </div>
    </template>
    
    <script>
    
    import bmlao from '@/components/Bmlaoli';
    
    export default {
      name: 'App',
      components: {
        bmlao,
      },
      data() {
        return {
          contuned: 100
        }
      },
    }
    </script>
    
    <style>
    </style>
    
    

    但是問題來了,我目前希望你們在app裏面做的更改可以反映到我的Bm組件里,而且不通過父子組件的方式,那麼我該怎麼做呢?實際上非常的簡單

    這個時候我們就需要一個 ‘第三者來處理這個東西’,這個第三者就是這個Vuex。

    1. vueX的引入

    實際上,如果你有手動的安裝使用配VueRouter的經驗的話。這Vuex也是差不多的都是一樣的使用方法

    第一步:npm install vuex
    第二步:創建一個文件夾sote里寫一個index.js
    第三部:在index裏面安裝
    第四部:在main里掛載就好了

    index.js

    import Vue from 'vue'
    import Vuex from 'vuex'
    // 安裝
    Vue.use(Vuex)
    
    // 使用
    const store = new Vuex.Store({
        state:{},
        mutations: {
        },
        actions:{},
        getters:{},
        modules:{}
    
    })
    
    // 倒出
    export default store
    
    
    

    main.js

    import Vue from 'vue'
    import App from './App.vue'
    
    // 導入
    import Store from './store'
    
    Vue.config.productionTip = false
    
    // 掛載
    new Vue({
      Store,
      render: h => h(App),
    }).$mount('#app')
    
    

    非常的簡單

    1. app里的業務邏輯
    <template>
      <div id="app">
        <p>{{ $store.state.contuned }}</p>
        
          <button @click="$store.state.contuned ++" >+</button>
          <button @click="$store.state.contuned --" >-</button>
        
        <h1>------bmlaoli的界面--------</h1>
    
        <bmlao></bmlao>
    
      </div>
    </template>
    
    <script>
    
    import bmlao from '@/components/Bmlaoli';
    
    export default {
      name: 'App',
      components: {
        bmlao,
      },
      data() {
        return {
          // contuned: 100
        }
      },
    }
    </script>
    
    <style>
    </style>
    
    

    三、正確的操作state的方式

    1.需要注意的地方

    $store.state.contuned

    需要非常說的就是 請你不要這樣去修改vuex里的值,而是通過如下的方式去修改,詳細見官方api說明

    1. 概述我們的更改邏輯
      view視圖提交(Dispatch) —-> actions處理異步操作(commit) —–> Muations 記錄你的修改 ,方便以後追蹤(Mutate) —–> state修改(render)

    2. 代碼邏輯
      /state/index.js

        state:{
            contuned:1000
        },
        mutations: {
            increment(state){
                state.contuned++
            },
            decrement(state){
                state.contuned--
            },
        },
        actions:{},
        getters:{},
        modules:{}
    

    /app.vue

    <template>
      <div id="app">
        <p>{{ $store.state.contuned }}</p>
        
          <button @click="additon" >+</button>
          <button @click="subraction" >-</button>
        
        <h1>------bmlaoli的界面--------</h1>
    
        <bmlao></bmlao>
    
      </div>
    </template>
    
    <script>
    
    import bmlao from '@/components/Bmlaoli';
    
    export default {
      name: 'App',
      components: {
        bmlao,
      },
      data() {
        return {
          // contuned: 100
        }
      },
      methods: {
        additon() {
          this.$store.commit('increment')
        },
        subraction() {
          this.$store.commit('decrement')
          
        },
      },
    }
    </script>
    
    <style>
    </style>
    
    
    
    1. 除了使用this.$store.state.XXX或缺vuex的數據之外,我們還有一種方法,也是開發和工作中,比較常見的東西,那就是使用map進行各種數據的映射,它可以映射全部的vuex裏面的東西
    // 假設我們現在就使用map把東西數據,state裏面的東西,映射到我們的computed裏面
    improt { mapState } form 'vuex
    computed {
          ...mapState( ['XXX'] )
             // 但是我們不推薦使用上面得方式,我們更加推薦使用對象器別名的方式
          ...mapState( { xCount:'Count' } )      
    }
    
    ===> 這樣你就得到這些State ,除了state之外,其它的mutation 還有getter也是一樣的原理
    
    1. 深入立即map的映射原理,

    一個優秀的程序員,不應該只是停留在會用的層面,還應該靈活的掌握其中的原理,只有掌握了原理,才能做到行雲流水的開發.工具永遠只是工具,只有自己變強才是王道

    ====> 我們一點點的分析,
    // 1. 首先我們得computed需要接受函數
    computed:{
          XXXX:() => {  return this.$stroe.state.XXX }
     }
    // 2. 我們要寫一個方法mapState
    function mapState( array ){
         let obj = {}
         array.forEach( stateKey => { obj[stateKey] = () => this.$store.state[stateKey] }  )
         return obj
    }
    // 以上就是內部的實現原理
    

    這樣我們就能開發者工具追綜這些東西的變化了

    四、核心概念解讀

    vueX中有五個核心

    1.單一狀態樹

    1. 管理系統 現實生活中的例子
      我們先來舉一個例子,在我們國家一個人有很多的信息會被記錄到檔案管理的各個部門,車貸房貸,身份證 ,戶口 ,結婚登記,這些信息都分佈式的存放在各個局,地產局,戶口部門……,這樣對於我們的人 來說, 我們的數據來來源就是多樣的,多數據源,但是這樣有問題,就是一起管理的時候是不好管理的,你可能需要去這個地方蓋章,去哪個地方改造,如果不通過又要重新回來蓋章,XXXX太麻煩了。
    2. vuex的管理邏輯
      在我們的vue中確確實實 ,你可以new 多個Vuex但是,我們是不推薦的,因為這樣管理起來就會非常的麻煩,我們的vuex推薦是 只使用一個vuex來管理共享的數據源,這個設計理念就是;單一數據源(也叫單一狀態樹)

    2.getter

    這個東西類似於計算屬性
    有時候我們需要從 store 中的 state 中派生出一些狀態,例如對列表進行過濾並計數:
    高階函數 ,返回函數的調用

    1. 需求,還是原來的案例,我希望我獲取的contuned的平方

    當然了,你這樣也是可以的

      <h2>{{ $store.state.contuned * $store.state.contuned }}</h2>
    

    但是很low 是不啦,如果你要寫很多很多的複雜邏輯操作,那不就涼涼了嗎,所以這裏引申出我們的getter,字面理解就是獲取的時候,對數據做一些手腳,那麼我們看看如何使用

    1. 明確一下,我們的操作基本上都是在我們的vuex文件裏面進行的

    在getter裏面搞事情
    store/index.js

    
    import Vue from 'vue'
    import Vuex from 'vuex'
    // 安裝
    Vue.use(Vuex)
    
    // 使用
    const store = new Vuex.Store({
        state:{
            contuned:1000
        },
        mutations: {
            increment(state){
                state.contuned++
            },
            decrement(state){
                state.contuned--
            },
        },
        actions:{},
        getters:{
            powerCounter(state){
                return state.contuned * state.contuned 
            }
        },
        modules:{}
    
    })
    
    // 倒出
    export default store
    
    

    使用的時候就非常簡單了
    /bmlaoli.vue

      <h2>{{ $store.getters.powerCounter }}</h2>
    

    現在我們又有了另一個需求,如果我想傳遞參數,怎麼辦,我希望我過濾出一些數據,而且我們希望我們是指定條件的過濾
    這裏就涉及到我們的傳遞參數的問題了
    store/index.js

    
      fliter(state,getters){
        console.log(getters)//這裏的getters實際上就是你的整個外面的getters對象 
    
      // 如果你要傳遞參數,你只能返回函數的調用
          return age => {
            state.students.filter( s => s.age >= age )
          }
        }
    
    

    /bmlaoli.vue

    原數據
     <h2>{{ $store.getters.students }}</h2>
    過濾之後
     <h2>{{ $store.getters.fliter(40) }}</h2>
    

    3.mutation

    vuex唯一更新狀態的方式,就是在這裏,如果你要更改數據,vuex唯一的更改方式就是 mutation

    3.1 概念

    事件類型(函數名)
    回調函數(回調函數,具體的業務代碼)

    mutations: {
    //      increment 事件類型
    // (state){ 回調函數
                // state.contuned++
            // },
            increment(state){
                state.contuned++
            },
    
            decrement(state){
                state.contuned--
            },
        },
    

    3.2 傳遞參數payload負載

    1. 單個參數
    2. 多參數(傳遞對象)

    需求:我們希望點擊更改狀態的時候的時候可傳入參數
    /sotre/index.js

    
       mutations: {
            increment(state){
                state.contuned++
            },
            decrement(state){
                state.contuned--
            },
            incrementCour(state,palyload){
                consle.log(palyload)//拿到了一個傳遞過來的對象
            }
        },
    
    

    bmlaoliu.vue3

    addcCount(parmas){
    this.$sore.commit( 'incrementCour' ,palyload)
    }
    

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

    【其他文章推薦】

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

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

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

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

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

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

  • javascript 面向對象學習(三)——this,bind、apply 和 call

    this 是 js 里繞不開的話題,也是非常容易混淆的概念,今天試着把它理一理。

    this 在非嚴格模式下,總是指向一個對象,在嚴格模式下可以是任意值,本文僅考慮非嚴格模式。記住它總是指向一個對象對於理解它的意義很重要。this 在實際使用中,大致分為以下幾種情況:

    1. 函數作為對象的方法調用時,this 指向調用該函數的對象

    var obj = {
        name: 'jack',
        getName: function() {
            console.log(this === obj) // true
            console.log(this.name)  // jack
        }
    }
    obj.getName()

    這個應該很好理解,不多說了。

    2. 函數作為普通函數被調用時,this 指向全局對象。在瀏覽器中,全局對象是window。

    var name = 'global'
    function getName() {
        console.log(this === window) // true
        console.log(this.name) // global
    }
    getName()

    我的理解是上面的代碼可以改寫為

    window.name = 'global'
    window.getName = function() {
        console.log(this === window) // true
        console.log(this.name) // global
    }
    window.getName()

    這樣其實與情況1是一樣的,相當於函數作為對象的方法調用,只不過這裏的對象是全局對象。

    《Javascript 設計模式與開發實踐》一書中有個例子如下:

    window.name = 'globalName';
    var myObject = {
        name: 'seven',
        getName: function(){
            return this.name
        } 
    }
    
    var getName = myObject.getName
    console.log(getName())  // globalName

    getName 是定義在myObject 對象中的方法,在調用getName 方法時,打印出的卻是全局對象的name,而不是myObject對象的name,這再次證明了 this 並非指向函數被聲明時的環境對象,而是指向函數被調用時的環境對象

    3. 函數作為構造函數調用時,指向構造出的新對象

    function Person(name) {
        this.name = name  
    }
    
    var jack = new Person('Jack')
    console.log(jack.name) // Jack
    var rose = new Person('Rose')
    console.log(rose.name) // Rose

    這裏創建了兩個不同名字的對象,打印出的name也是不一樣的,說明構造函數的 this 會根據創建對象的不同而變化。需要注意的是,如果構造函數里返回了一個Object類型的對象,那麼this會指向這個對象,而不是利用構造函數創建出的對象。我們在構造函數一章里也提到過,new 操作符所做的最後一步就是返回新對象,而如果我們顯式地返回一個對象,就會覆蓋這步操作,this也就不再指向新對象。

    4. 函數作為事件處理函數調用時,指向觸發事件的元素

    document.getElementById("myBtn").addEventListener("click", function(e){
        console.log(this === e.currentTarget) // true
    });

    5. 箭頭函數

    由於箭頭函數沒有this,它的 this 是繼承父執行上下文裏面的 this。執行上下文後面再討論,現在只要知道簡單對象(非函數)是沒有執行上下文的。

    var obj = {
        name:  'obj',
        getName: function() {
    console.log(this) // 執行上下文里的 this
    return (()=>{ console.log(this.name) }) } } var fn = obj.getName() fn() // obj

    按照情況2來處理的話,this 指向全局對象,應該輸出 undefined,結果並不是。與普通函數不同,箭頭函數的 this 是在函數被聲明時決定的,而不是函數被調用時。在這裏,父執行上下文是 getName 函數,也就繼承了 getName 的 this,即 obj。

    利用 bind、apply、call 改變 this 指向

    bind、apply、call 都是定義在 Function 原型對象上的方法,所有函數對象都能繼承這個方法,三者都能用來改變 this 指向,我們來看看它們的聯繫與區別。

    function fn() {
        console.log(this.name)
    }
    
    // bind
    var bindfn = fn.bind({name: 'bind'})
    bindfn() // bind // apply
    fn.apply({name: 'apply'}) // apply // call
    fn.call({name: 'call'}) // call

    我們定義了一個函數fn,然後分別調用了它的 bind、apply、call 方法,並傳入一個對象參數,通過打印出的內容可以看到 this 被綁定到了參數對象上。bind 似乎有些不同,多了一步 bindfn() 調用,這是因為 bind 方法返回的是一個函數,不會立即執行,而調用 apply 和 call 方法會立即執行。

    下面再來看一下 fn 函數存在參數的情況:

    function fn(a, b, c) {
        console.log(a, b, c)
    }
    
    var bindfn = fn.bind(null, 'bind');
    bindfn('A', 'B', 'C');           // bind A B
    
    fn.apply(null, ['apply', 'A']) // apply A undefined
    
    fn.call(null, 'call', 'A');  // bind A undefined

    bindfn 打印出的結果是fn調用bind方法時的傳遞的參數加上bindfn傳遞的參數,參數 ‘C’ 被捨棄掉了。調用 apply 和 call 方法打印出的則是傳遞給它們的參數,不一樣的是,apply 的參數是一個數組(或類數組),call 則是把參數依次傳入函數。這時候再看它們的定義應該會好理解很多:

    bind() 方法創建一個新的函數,在 bind() 被調用時,這個新函數的 this 被指定為 bind() 的第一個參數,而其餘參數將作為新函數的參數,供調用時使用。

    apply() 方法調用一個具有給定 this 值的函數,以及作為一個數組(或類數組對象)提供的參數。

    call() 方法使用一個指定的 this 值和單獨給出的一個或多個參數來調用一個函數。

    我們可以利用它們來借用其他對象的方法。已知函數的參數列表 arguments 是一個類數組對象,比如上例中函數 fn 的參數 a, b, c,因為它不是一個真正的數組,不能調用數組方法,這時借用 apply/call 方法(bind 也可以,就是用得比較少)將 this 指向 arguments 就能借用數組方法:

    (function(){
        Array.prototype.push.call(arguments, 'c')
        console.log(arguments) // ['a', 'b', 'c']
    })('a','b')

    值得一提的是,push 方法並不是只有數組才能調用,一個對象只要滿足1.可讀寫 length 屬性;2.對象本身可存取屬性. 就可以利用 call / apply 調用 push 方法。

     

    參考:

    《Javascript 設計模式與開發實踐》

    https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Operators/this

    http://www.imooc.com/article/80117

    https://blog.csdn.net/weixin_42519137/article/details/88053339

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

    【其他文章推薦】

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

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

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

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

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

  • Linux系統如何設置開機自動運行腳本?

    Linux系統如何設置開機自動運行腳本?

    大家好,我是良許。

    在工作中,我們經常有個需求,那就是在系統啟動之後,自動啟動某個腳本或服務。在 Windows 下,我們有很多方法可以設置開機啟動,但在 Linux 系統下我們需要如何操作呢?

    Linux 下同樣可以設置開機啟動,但可能需要我們敲一些命令(可能也有 UI 界面的設置方法,但我不熟,我更多是玩命令)。下面我們就介紹三種簡單但可行的開機啟動設置方法。

    方法一:修改 /etc/rc.d/rc.local 文件

    /etc/rc.d/rc.local 文件會在 Linux 系統各項服務都啟動完畢之後再被運行。所以你想要自己的腳本在開機后被運行的話,可以將自己腳本路徑加到該文件里。

    但是,首先需要確認你有運行這個文件的權限。

    $ chmod +x /etc/rc.d/rc.local
    

    為了演示,我們創建了一個腳本,當它被執行之後,將在家目錄下寫入有特定信息的文件。

    $ vim auto_run_script.sh
    
    #!/bin/bash
    date >> /home/alvin/output.txt
    hostname >> /home/alvin/output.txt
    

    保存退出后,再給它賦予可執行權限:

    $ chmod +x auto_run_script.sh
    

    然後,我們再將腳本添加到 /etc/rc.d/rc.local 文件最後一行:

    $ vim /etc/rc.d/rc.local
    
    /home/alvin/auto_run_script.sh
    

    接下來,我們就可以試試效果了。直接重啟系統就可以了:

    $ sudo reboot
    

    重啟之後,就會在家目錄下看到腳本執行的結果了。

    方法二:使用 crontab

    大家知道,crontab 是 Linux 下的計劃任務,當時間達到我們設定的時間時,可以自動觸發某些腳本的運行。

    我們可以自己設置計劃任務時間,然後編寫對應的腳本。但是,有個特殊的任務,叫作 @reboot ,我們其實也可以直接從它的字面意義看出來,這個任務就是在系統重啟之後自動運行某個腳本。

    那它將運行的是什麼腳本呢?我們如何去設置這個腳本呢?我們可以通過 crontab -e 來設置。

    $ crontab -e
    
    @reboot /home/alvin/auto_run_script.sh
    

    然後,直接重啟即可。運行的效果跟上面類似。

    方法三:使用 systemd 服務

    以上介紹的兩種方法,在任何 Linux 系統上都可以使用。但本方法僅適用於 systemd 系統。如何區分是不是 systemd 系統?很簡單,只需運行 ps aux 命令,查看 pid 為 1 的進程是不是 systemd 。

    為了實現目的,我們需要創建一個 systemd 啟動服務,並把它放置在 /etc/systemd/system/ 目錄下。

    我們創建的 systemd 啟動服務如下。請注意,這時後綴是 .service ,而不是 .sh

    $ vim auto_run_script.service
    
    [Unit]
    Description=Run a Custom Script at Startup
    After=default.target
    
    [Service]
    ExecStart=/home/alvin/auto_run_script.sh
    
    [Install]
    WantedBy=default.target
    

    從服務的內容可以看出來,我們最終還是會調用 /home/alvin/auto_run_script.sh 這個腳本。

    然後,我們再把這個腳本放置在 /etc/systemd/systerm/ 目錄下,之後我們再運行下面兩條命令來更新 systemd 配置文件,並啟動服務。

    $ systemctl daemon-reload
    $ systemctl enable auto_run_script.service
    

    萬事俱備之後,我們就可以重啟系統啦。

    $ reboot
    

    公眾號:良許Linux

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

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

    【其他文章推薦】

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

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

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

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

    ※回頭車貨運收費標準

  • 宇宙第一IDE是誰?

    宇宙第一IDE是誰?

    更多精彩文章,盡在碼農翻身

    微服務把我坑了

    如何降低程序員的工資?

    程序員,你得選准跑路的時間!

    兩年,我學會了所有的編程語言!

    一直CRUD,一直996,我煩透了,我要轉型

    字節碼萬歲!

    上帝託夢給我說:一切皆文件

    Javascript: 一個屌絲的逆襲

    Node.js :我只需要一個店小二

    我是一個線程

    TCP/IP之大明郵差

    一個故事講完Https

    CPU 阿甘

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

    【其他文章推薦】

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

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

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

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

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

    ※超省錢租車方案

  • git push 錯誤,回滾 push操作

    git push 錯誤,回滾 push操作

    作者:
    故事我忘了

    個人微信公眾號:
    程序猿的月光寶盒

    目錄

    • 0.記一次使用git push后,覆蓋了同事代碼的糗事
    • 1.還原案發現場的準備工作
      • 1.1 新建分支
        • 注意:
      • 1.2. 分支提交到遠程Git倉庫
    • 2.糗事發生契機
      • 2.1 假設文件是這個html文件,然後你上傳到遠程分支
      • 2.2 這時我用另一電腦修改這個文件,並提交到遠程,故意模仿他人操作,如圖,在遠程分支上Linux已經更新過
      • 2.3 本地文件也做不一樣的修改,假設自己再不知情的情況下做push操作必然會引起版本衝突
      • 2.4 此時本地我已經做了版本合併,所以,再次pull
    • 3. 正事來了.回滾吧
      • 3.1 將win給回滾調,留下Linux的代碼
        • 步驟
          • 1. 在目標分支上copy revision number:
          • 2. 右擊項目依次選中:git->Repository->Reset HEAD
    • 4.提交
      • git reset soft,hard,mixed之區別深解
        • 3.再次push
    • 5:驗證 上一步的強制push git push -f

    0.記一次使用git push后,覆蓋了同事代碼的糗事

    前言:

    ​ 都在WebStorm中操作,Idea或者PyCharm同理

    ​ 為了高度還原尷尬現場,這裡在原有項目上新建分支,然後都在分支上操作,一方面怕自己搞炸了,一方面真實環境就是如此

    1.還原案發現場的準備工作

    1.1 新建分支

    注意:

    這裏創建的分支僅僅在本地倉庫

    1.2. 分支提交到遠程Git倉庫

    遠程查看確認,確實有,說明分支已經創建

    2.糗事發生契機

    ​ 這時候別人可能會和你改同一文件

    2.1 假設文件是這個html文件,然後你上傳到遠程分支

    注意這時候都是在剛創建的那個分支操作

    ​ 可以看到遠程分支已經有了

    2.2 這時我用另一電腦修改這個文件,並提交到遠程,故意模仿他人操作,如圖,在遠程分支上Linux已經更新過

    2.3 本地文件也做不一樣的修改,假設自己再不知情的情況下做push操作必然會引起版本衝突

    Remote changes need to be merged before pushing

    推送前需要合併遠程更改

    ​ 這時你點了合併

    ​ 上圖,把你的和他的都合併提交,但是出現如下警告

    Push has been cancelled, because there were conflicts during update. Check that conflicts were resolved correctly, and invoke push again.

    Push已被取消,因為在更新期間有衝突。檢查衝突是否已正確解決,並再次調用pull。

    2.4 此時本地我已經做了版本合併,所以,再次pull

    3. 正事來了.回滾吧

    ​ 現在,你被告知Linux的為正確的修改,並且你上一步的提交影響到他了,要回滾pushLinux操作的階段

    3.1 將win給回滾調,留下Linux的代碼

    步驟

    1. 在目標分支上copy revision number

    2. 右擊項目依次選中:git->Repository->Reset HEAD

    Reset Type選Hard,To Commit 寫剛複製的版本號,

    然後點擊Reset按鈕

    這時候,代碼已經回到了老的版本,這個時候不能提交代碼,提交也是會衝突的。

    4.提交

    1.可以使用命令強制提交

      git push -f

    或者

    2.使用Idea,(我使用的是這個方法)

      在最新的commit上複製版本號

    使用mixed類型,將上面複製的版本號粘貼進來:

    git reset soft,hard,mixed之區別深解

    git reset soft,hard,mixed之區別深解

    又出來這個提示

    3.再次push

    ​ 此時 代碼是最新的正確的,

    ​ 也就是Linux操作的正確修改

    5:驗證 上一步的強制push git push -f

    1.說明

      將程序從錯誤的復原,回滾到win操作

    2.步驟

      按照上面的步驟進行操作。

      在後面提交的時候,直接強制提交,

    則效果是:

    可以看到一開始的

    對應遠程的文件

    至此就恢復以及修改了,Linux端只要pull一下就行了,就是最新代碼

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

    【其他文章推薦】

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

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

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

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

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

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

  • 深度學習-神經網絡

    深度學習-神經網絡

    目錄

    • 深度學習-神經網絡
      • 摘要
      • 神經網絡
        • 淺層神經網絡
        • 深層神經網絡
        • 激活函數
        • 反向傳播
          • 損失(loss)
      • 卷積神經網絡
        • 局部感受野
        • 卷積核
        • 共享權值
        • 池化
      • 遷移學習
        • 對抗網絡

    深度學習-神經網絡

    摘要

    機器學習人工智能的核心,而深度學習又是機器學習的核心。三者關係可用如下圖來表示。

    人工神經網絡(Artificial Neural Networks,簡寫為ANNs)也簡稱為神經網絡(NNs)或稱作連接模型(Connection Model),它是一種模仿動物神經網絡行為特徵,進行分佈式并行信息處理的算法數學模型。

    首先認識一下人腦的神經元之間的聯繫

    神經網絡仿照人腦的神經元結構之間的聯繫,當某個神經元的軸突電信號強度達到一定程度時,就會觸發將信號傳遞到下一個神經元。在傳遞的過程中加上一些對數據處理的操作,從而達到處理傳遞信息的目的。上面的信號其實就是數值或者多維矩陣。

    神經網絡

    神經網絡又分為兩種,一種是淺層神經網絡,另一種是深層神經網絡,顧名思義可知,深層神經網絡要比淺層神經網絡複雜。

    淺層神經網絡

    深層神經網絡

    上面兩圖中的圓圈則代表神經元,連線則代表上一層神經元對下一層神經元的信號傳遞

    淺層神經網絡相比,深層神經網絡具有更複雜的模型結構,並且層次結構較多,神經網絡採用全連接的方式將神經元之間的信號進行傳遞,淺層神經網絡則是非全連接,或者單層全連接。

    • 全連接:是上一層的每個神經元都連接到下一層所有的神經元
    • 非全連接:一個神經元連接一個或者下一層的其中幾個神經元

    每個神經元鏈接下一層多個神經元,由於不同神經元對該神經元的信號的Value不同,所以通過設置權重(Weight)的方式來降低或提高該神經元傳遞過來的信號。

    其關係滿足 y = Wx+ B

    • w:weight權重,對該神經元的重視(需求)程度,也就是上面所說的數據處理階段,一般總weight(w1+w2+w3)為1
    • x:傳遞過來的信號量的值
    • b:為偏移量,對於在線性分類問題,偏移量是不可或缺的。

    上圖如果沒有偏移量的話,該直線就會過原點。

    顯然分類效果就不會好,通過加上一個大於0的偏移量b使得分類器往左平移。

    • y:是加權后的值,激活函數的參數x就是所有y的和

    激活函數

    激活函數是神經網絡模型中上一層神經元到下一層神經元數值的處理,上一層神經元通過線性函數(加權求和)得到的數值y,所有的y也是具有線性函數特性的,激活函數就是將這些數值非線性化,把y當作x帶入到激活函數中。

    • 線性函數關係為 y = Wx + B
    • 非線性函數即不是一條直線的函數,例如冪函數,指數函數,對數函數等

    激活函數又類似於高等數學中的符號函數(sgn),(sgn不是激活函數)

    • x>0,則輸出f(x)=1,傳給下一個神經元
    • x<0,則輸出f(x)=-1,傳給下一個神經元
    • x=0,則輸出f(x)=0,傳給下一個神經元

    經典的激活函數是Sigmoid 函數,其函數關係為f(x) = 1/(1-e^x)

    • x較大,則輸出f(x)=1,傳給下一個神經元
    • x較小,則輸出f(x)=0,傳給下一個神經元
    • 所有的值均壓縮在-1~1之間

    反向傳播

    反向傳播的基本思想就是在執行完所有層后,通過計算輸出層期望值之間的誤差來逆向調整權重參數,從而使損失減小,誤差變小。

    損失(loss)

    在程序運行過程中,模型生成好之後,會進行模型準確性評估,計算它的損失值,損失值越小,模型就越好。損失函數是計算損失函數的方法,也有一下幾種。

    • 0-1損失函數(0-1 lossfunction):
    • 平方損失函數(quadraticloss function)
    • 絕對損失函數(absoluteloss function)
    • 對數損失函數(logarithmicloss function)或對數似然損失函數(log-likelihood loss function)

    卷積神經網絡

    卷積神經網絡(Convolutional Neural Network,CNN)是一種前饋型的神經網絡,其在大型圖像處理方面有出色的表現,目前已經被大範圍使用到圖像分類、定位等領域中。相比於其他神經網絡結構,卷積神經網絡需要的參數相對較少,使的其能夠廣泛應用。

    首先看下卷積網絡結構流程圖

    回顧深層神經網絡,因為每個層都是全連接的,假設有一個64×64×3的圖像(64×64是二維平面上的像素值,3為第三維RGB的值),每個節點的權重是不同的,則下一層的每個神經元將會計算64×64×3個節點,若下一層有n個節點的神經元則計算量還需乘n,訓練起來較為複雜。

    卷積神經網絡主要有三個特點

    • 局部感受野(Local Receptive Fields)
    • 共享權值(Shared Weights)
    • 池化(Pooling)

    局部感受野

    在動物器官中,大腦皮層有不同的感受區域,如聽覺區,視覺區等,每塊區域都有自己特殊的神經元,當有聽覺信號時會傳到大腦皮層的聽覺區的神經元,因此每個神經元對應的感受區域叫感受野.
    那麼在神經網絡中,不同的卷積核會對應不同的感受區域,在卷積時彼此之間無聯繫。

    卷積神經網絡的卷積層每個節點與上一層某個區域通過卷積核連接,而與這塊區域以外的區域無連接。

    從右邊圖可見,每個圓圈代表一個卷積核,每個卷積核只需要關注自己的局部感受區域。

    卷積核

    卷積核到底是什麼?那就來觀察一下每個卷積核局部作用域。

    上圖是取一個卷積和其對應的局部感受野,中間的那一層就是卷積核,3×3的卷積核對源數據中左上角的3×3的矩陣進行點乘,就得到了一個數值,叫做該區域的卷積層的值。然而,這隻是源數據中的部分區域,源數據是7×7的矩陣,所以卷積核再通過移動映射的方式多次卷積,如下圖。
    該圖中在卷積層的值與源數據之間還應有個3×3的卷積核,未畫出。

    通過上圖可以看出,如果一個7×7×1像素的圖片通過一個3×3×1的卷積核卷積,則被卷積后的值為5×5×1 !這裏注意一點,還是以圖片為例,若是圖片像素是三維的(彩色圖片),則像素為7×7×3的圖片必須使用三維的卷積核,並且第三維度也是3。

    • stride:步長
    • padding:擴展像素,填充像素
    • in:輸入的模式數,
    • out:輸出的模式數
    • W H:卷積核寬度長度

    共享權值

    每個卷積核連接8×8個節點也就64個權重值,然而還有8×8×2個卷積核呢!這裏出現了共享權值,共享權值就是每個卷積核對自己區域的權重比值都是相同的,所以僅需要給出64個權重比值即可,極大的減少了計算量。

    池化

    池化層也叫採樣層,作用是降低數據維度,主要有兩個池化方式

    • 最大池化
    • 平均池化

    以最大池化為例,有4個2×2的矩陣,則取每個矩陣中的最大值作為該矩陣的特徵。

    另一個就是平均池化,就是取矩陣中的平均值來作為該矩陣的特徵

    遷移學習

    對抗網絡

    G生成的圖像被D識別為真圖像loss越小,D對於G生成的圖像的判別正確率越高越好

    繼續更新,點個關注哦

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

    【其他文章推薦】

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

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

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

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

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

  • Logstash下字段以及嵌套Json字段類型轉換

    Logstash下字段以及嵌套Json字段類型轉換

     

    前言

    從filebeat傳輸到Logstash的數據,某個字段需要由string類型裝換成float類型。但是不管怎麼改logstash的配置文件都不生效,其實官方文檔都有,但是具體細節方面的東西就得自己不斷的實踐驗證最後達到自己想要的目標了。整整一天,都在弄這一個,中間實在想放棄了。但是就如張靚穎的“終於等到你,還好沒放棄”,最後在某一篇博文得到了啟發,才解決。

     

    這裏類型轉換分兩個類型:

    1)字段是單純的字段,也就是直接在_source下的

    2)字段是在json里的,在_source下還有嵌套一層json里的字段

     

    一、單一字段

    可以從下面的圖中看出,字段就在頂層機構_source下,這種情況下的Logstash配置文件設置如下:

    filter {
         mutate {
         convert => { "request_time" => "float" }
         convert => { "upstream_response_time" => "float" }
         }
    }

     

     

     

     

    二、嵌套Json下的字段

    如果需要轉換的字段是在非頂級結構下,是在一個JSON里,因為在filebeat做decode的時候指定了,如我需要轉換的字段是在jsonn的json字段里:

    processors:
     - decode_json_fields:
        fields: ["message"]    #要進行解析的字段
        process_array: false   #數組是否解碼,默認值:false
        max_depth: 3           #解碼深度,默認值:1
        target: "jsonn"          #json內容解析到指定的字段,如果為空(“”),則解析到頂級結構下
        overwrite_keys: false  #如果解析出的json結構中某個字段在原始的event(在filebeat中傳輸的一條數據為一個event)中也存在,是否覆蓋

     

     

     

    這種情況下的Logstash配置文件設置如下:

    filter {
        mutate {
          convert => { "[jsonn][request_time]" => "float" }
          convert => { "[jsonn][upstream_response_time]" => "float" }
       }
    }
    

     

    注意:

    [jsonn][request_time] 不是[jsonn].[request_time],也不是jsonn.request_time沒有點.

     

    受啟發的鏈接:

    https://stackoverflow.com/questions/30369148/logstash-remove-deep-field-from-json-file

     

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

    【其他文章推薦】

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

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

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

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

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

  • CSS中的float和margin的混合使用

    CSS中的float和margin的混合使用

    在最近的學習中,在GitHub上找了一些布局練習,我發現了我自己對布局超級不熟悉(很難受)。

    在以前的學習CSS過程中,感覺使用CSS就記住各個屬性的屬性值以及作用就OK了,但是實際上呢?呵呵一笑。不說了,太傷心了,進入正題吧!

    最近我使用float和margin布局,加深了我對這兩個一起使用的理解。(新生可以看一下,大神請忽略

    float屬性

    float: left | right | none | inherit
    

    當然最常用的還是前面兩個:向左浮動向右浮動

    浮動最主要的特點:脫標

    脫離標準流,處於更加高級的層面,影響父元素和後面元素的布局,這裏就不具體介紹了。

    margin屬性

    這裏主要講margin-leftmargin-right

    margin-left: 設置元素的左外邊距。
    margin-right: 設置元素的右外邊距。
    

    總的來說,這個兩個屬性的字面理解還是很容易的,但是越簡單的東西越不要小看。

    重點

    以下代碼:

    html:

    <div class="box">
        <div class="zi_box1">1</div>
        <div class="zi_box2">2</div>
        <div class="zi_box3">3</div>
        <div class="clear"></div>
    </div>
    

    CSS:

    .box {
                background-color: #555555;
                width: 600px;
                height: 200px;
            }
            .zi_box1 {
                float: left;
                background-color: #c23232;
                width: 200px;
                height: 100px;
            }
            .zi_box2 {
                float: left;
                background-color: chartreuse;
                width: 200px;
                height: 100px;
            }
    
            .zi_box3 {
                float: left;
                background-color: blue;
                width: 200px;
                height: 100px;
            }
            .clear {
                clear: both;
            }
    

    最後實現的效果圖:

    三個子盒子充滿父盒子,因為但他們寬度可以在父盒子裏面撐開。

    如果父盒子撐不開呢?

    加大一個子盒子的寬度,序號為3的盒子

    zi_box3 {
        width: 300px;
    }
    

    效果圖如下:

    那麼第三個盒子則會另外起一行。

    結合margin使用時

    在第一代碼的基礎上,增加一個margin值

    zi_box1 {
        margin-left: 20px;
    }
    

    這時候,由於三個盒子的寬度加上margin值大於父盒子的寬度,所以盒子3就會另起一行

    反之,給盒子3設置一個外邊距的值,盒子1和盒子2不設置外邊距,是不是盒子3也會另外起一行呢?答案是肯定的,因為他們的寬度已經超過父盒子的值了。

    實現三列布局

    在不改變DOM的順序的情況下,使盒子3盒子1盒子2的順序呢?是不是就可以充分使用margin這個屬性了。最開始白痴的我(很少練習布局吧,大神就不要噴我了,我只是個菜鳥)

    白痴代碼

    .zi_box1 {
         margin-left: 200px;       
    }
    .zi_box2 {
         margin-left: 200px;       
    }
    
    .zi_box3 {
         margin0left: -400px;
    }
    //這裏很天真的想法,以為每個元素是單獨行動
    

    這樣寫的效果圖:

    我當時就傻了,這是什麼玩意。

    但是在最後的摸索中,我知道原因了,最最最最重要的就是DOM的執行順序

    造成這樣的原因就是:盒子1先解析,margin-left: 200px,那麼這樣盒子3也就去了第二行; 再盒子2解析,margin-left:200px,那麼盒子2也去了第二行,因為第一行已經有600px這麼寬的長度了。最後解析盒子3,margin-left:-400px,盒子向前移動400px,不就造成了這樣的效果圖嘛。

    這樣想的,就是指考慮片面的,而不是全局的

    實現三列布局的最終代碼

    .zi_box1 {
         margin-left: 200px;       
    }
    .zi_box2 {
         margin-left: 0px;      
    }
    
    .zi_box3 {
         margin0left: -600px;
    }
    

    效果圖

    可以簡單的這樣理解

    盒子1向右移動200px,那麼盒子2和盒子3也會向右移動200px,具體的效果圖如下

    那麼盒子3移動到前面去,是不是需要600px的距離啊(是不是很容易懂,嘻嘻),當然這隻是我的片面理解,也不完全是對的。
    在這種思維模式下,還要注意一點:當超出的部分盒子還是會遵守float的規則的。

    那麼float: right和margin-right是一樣的道理。

    這是我的第一篇博客,寫的太菜,不要笑我喲。

    喜歡我的話,點個關注吧!

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

    【其他文章推薦】

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

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

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

    ※超省錢租車方案

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

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

  • 學習ASP.NET Core(10)-全局日誌與xUnit系統測試

    學習ASP.NET Core(10)-全局日誌與xUnit系統測試

    上一篇我們介紹了數據塑形,HATEOAS和內容協商,並在制器方法中完成了對應功能的添加;本章我們將介紹日誌和測試相關的概念,並添加對應的功能

    一、全局日誌

    在第一章介紹項目結構時,有提到.NET Core啟動時默認加載了日誌服務,且在appsetting.json文件配置了一些日誌的設置,根據設置的日誌等級的不同可以進行不同級別的信息的显示,但它無法做到輸出固定格式的log信息至本地磁盤或是數據庫,所以需要我們自己手動實現,而我們可以藉助日誌框架實現。

    ps:在第7章節中我們記錄的是數據處理層方法調用的日誌信息,這裏記錄的則是ASP.NET Core WebAPI層級的日誌信息,兩者有所差異

    1、引入日誌框架

    .NET程序中常用的日誌框架有log4net,serilog 和Nlog,這裏我們使用Serilog來實現相關功能,在BlogSystem.Core層使用NuGet安裝Serilog.AspNetCore,同時還需要搜索Serilog.Skins安裝希望支持的功能,這裏我們希望添加對文件和控制台的輸出,所以選擇安裝的是Serilog.Skins.File和Serilog.Skins.Console

    需要注意的是Serilog是不受appsetting.json的日誌設置影響的,且它可以根據命名空間重寫記錄級別。還有一點需要注意的是需要手動對Serilog對象進行資源的釋放,否則在系統運行期間,無法打開日誌文件。

    2、系統添加

    在BlogSystem.Core項目中添加一個Logs文件夾,並在Program類中進行Serilog對象的添加和使用,如下:

    3、全局添加

    1、這個時候其實系統已經使用Serilog替換了系統自帶的log對象,如下圖,Serilog會根據相關信息進行高亮显示:

    2、這個時候問題就來了,我們怎麼才能進行全局的添加呢,總不能一個方法一個方法的添加吧?還記得之前我們介紹AOP時提到的過濾器Filter嗎?ASP.NET Core中一共有五類過濾器,分別是:

    • 授權過濾器Authorization Filter:優先級最高,用於確定用戶是否獲得授權。如果請求未被授權,則授權過濾器會使管道短路;
    • 資源過濾器Resource Filter:授權后運行,會在Authorization之後,Model Binding之前執行,可以實現類似緩存的功能;
    • 方法過濾器Action Filter:在控制器的Action方法執行之前和之後被調用,可以更改傳遞給操作的參數或更改從操作返回的結果;
    • 異常過濾器Exception Filter:當Action方法執行過程中出現了未處理的異常,將會進入這個過濾器進行統一處理;
    • 結果過濾器Result Filter:執行操作結果之前和之後運行,僅在action方法成功執行后才運行;

    過濾器的具體執行順序如下:

    3、這裏我們可以藉助異常過濾器實現全局日誌功能的添加;在在BlogSystem.Core項目添加一個Filters文件夾,添加一個名為ExceptionFilter的類,繼承IExceptionFilter接口,這裡是參考老張的哲學的簡化版本,實現如下:

    using Microsoft.AspNetCore.Http;
    using Microsoft.AspNetCore.Mvc;
    using Microsoft.AspNetCore.Mvc.Filters;
    using Microsoft.Extensions.Logging;
    using Serilog;
    using System;
    
    namespace BlogSystem.Core.Filters
    {
        public class ExceptionsFilter : IExceptionFilter
        {
            private readonly ILogger<ExceptionsFilter> _logger;
    
            public ExceptionsFilter(ILogger<ExceptionsFilter> logger)
            {
                _logger = logger;
            }
    
            public void OnException(ExceptionContext context)
            {
                try
                {
                    //錯誤信息
                    var msg = context.Exception.Message;
                    //錯誤堆棧信息
                    var stackTraceMsg = context.Exception.StackTrace;
                    //返回信息
                    context.Result = new InternalServerErrorObjectResult(new { msg, stackTraceMsg });
                    //記錄錯誤日誌
                    _logger.LogError(WriteLog(context.Exception));
                }
                catch (Exception e)
                {
                    Console.WriteLine(e);
                    throw;
                }
                finally
                {
                    //記得釋放,否則運行時無法打開日誌文件
                    Log.CloseAndFlush();
                }
    
            }
    
            //返回500錯誤
            public class InternalServerErrorObjectResult : ObjectResult
            {
                public InternalServerErrorObjectResult(object value) : base(value)
                {
                    StatusCode = StatusCodes.Status500InternalServerError;
                }
            }
    
            //自定義格式內容
            public string WriteLog(Exception ex)
            {
                return $"【異常信息】:{ex.Message} \r\n 【異常類型】:{ex.GetType().Name} \r\n【堆棧調用】:{ex.StackTrace}";
            }
        }
    }
    

    4、在Startup類的ConfigureServices方法中進行異常處理過濾器的註冊,如下:

    5、我們在控制器方法中拋出一個異常,分別查看效果如下,如果覺得信息太多,可調整日誌記錄級別:

    二、系統測試

    這裏我們從測試的類別出發,了解下測試相關的內容,並添加相關的測試(介紹內容大部分來自微軟官方文檔,為了更易理解,從個人習慣的角度進行了修改,如有形容不當之處,可在評論區指出)

    1、測試說明及分類

    1、自動測試是確保軟件應用程序按照作者期望執行操作的一種絕佳方式。軟件應用有多種類型的測試,包括單元測試、集成測試、Web測試、負載測試和其他測試。單元測試用於測試個人軟件的組件或方法,並不包括如數據庫、文件系統和網絡資源類的基礎結構測試。

    當然我們可以使用編寫測試的最佳方法,如測試驅動開發(TDD)所指的先編寫單元測試,再編寫該單元測試要檢查的代碼,就好比先編寫書籍的大綱,再編寫書籍。其主要目的是為了幫助開發人員編寫更簡單,更具可讀性的高效代碼。兩者區別如下(來自Edison Zhou)

    2、以深度(測試的細緻程度)和廣度(測試的覆蓋程度)區分, 測試分類如下(此處內容來自solenovex):

    Unit Test 單元測試:它可以測試一個類或者一個類的某個功能,但其覆蓋程度較低;

    Integration Test 集成測試:它的細緻程度沒有單元測試高,但是有較好的覆蓋程度,它可以測試功能的組合,以及像數據庫或文件系統這樣的外部資源;

    Subcutaneous Test 皮下測試 :其作用區域為UI層的下一層,有較好的覆蓋程度,但是深度欠佳;

    UI測試:直接從UI層進行測試,覆蓋程度很高,但是深度欠佳

    3、在編寫單元測試時,盡量不要引入基礎結構依賴項,這些依賴項會降低測試速度,使測試更加脆弱,我們應當將其保留供集成測試使用。可以通過遵循显示依賴項原則和使用依賴項注入避免應用程序中的這些依賴項,還可以將單元測試保留在單獨的項目中與集成測試相分離,以確保單元測試項目沒有引用或依賴於基礎結構包。

    總結下常用的單元測試和集成測試,單元測試會與外部資源隔離,以保證結果的一致性;而集成測試會依賴外部資源,且覆蓋面更廣。

    2、測試的目的及特徵

    1、為什麼需要測試?我們從以單元測試為例從4個方面進行說明:

    • 時間人力成本:進行功能測試時,通常涉及打開應用程序,執行一系列需要遵循的步驟來驗證預期的行為,這意味着測試人員需要了解這些步驟或聯繫熟悉該步驟的人來獲取結果。對於細微的更改或者是較大的更改,都需要重複上述過程,而單元測試只需要按一下按鈕即可運行,無需測試人員了解整個系統,測試結果也取決於測試運行程序而非測試人員。
    • 防止錯誤回歸:程序更改後有時會出現舊功能異常的問題,所以測試時不僅要測試新功能還要確保舊功能的正常運行。而單元測試可以確保在更改一行代碼后重新運行整套測試,確保新代碼不會破壞現有的功能。
    • 可執行性:在給定某個輸入的情況下,特定方法的作用或行為可能不會很明顯。比如,輸入或傳遞空白字符串、null后,該方法會有怎樣的行為?而當我們使用一套命名正確的單元測試,並清楚的解釋給定的輸入和預期輸出,那麼它將可以驗證其有效性。
    • 減少代碼耦合:當代碼緊密耦合時,會難以進行單元測試,所以以創建單元測試為目的時,會在一定程度上要求我們注意代碼的解耦

    2、優質的測試需要符合哪些特徵,同樣以單元測試為例:

    • 快速:成熟的項目會進行數千次的單元測試,所以應當花費非常少的時間來運行單元測試,一般來說在幾毫秒
    • 獨立:單元測試應當是獨立的,可以單獨運行,不依賴文件系統或數據庫等外部因素
    • 可重複:單元測試的結果應當保持一致,即運行期間不進行更改,返回的結果應該相同
    • 自檢查:測試應當在沒有人工交互的情況下,自動檢測是否通過
    • 及時:編寫單元測試不應該花費過多的時間,如果花費時間較長,應當考慮另外一種更易測試的設計

    在具體的執行時,我們應當遵循一些最佳實踐規則,具體請參考微軟官方文檔單元測試最佳做法

    3、xUnit框架介紹

    常用的單元測試框架有MSTestxUnitNUnit,這裏我們以xUnit為例進行相關的說明

    3.1、測試操作

    首先我們要明確如何編寫測試代碼,一般來說,測試分為三個主要操作:

    • Arrange:意為安排或準備,這裏可以根據需求進行對象的創建或相關的設置;
    • Act:意為操作,這裏可以執行獲取生產代碼返回的結果或者是設置屬性;
    • Assert:意為斷言,這裏可以用來判斷某些項是否按預期進行,即測試通過還是失敗

    3.2、Assert類型

    Assert時通常會對不同類型的返回值進行判斷,而在xUnit中是支持多種返回值類型的,常用的類型如下:

    boolean:針對方法返回值為bool的結果,可以判斷結果是true或false

    string:針對方法返回值為string的結果,可以判斷結果是否相等,是否以某字符串開頭或結尾,是否包含某些字符,並支持正則表達式

    數值型:針對方法返回值為數值的結果,可以判斷數值是否相等,數值是否在某個區間內,數值是否為null或非null

    Collection:針對方法返回值為集合的結果,可以針對集合內所有元素或至少一個元素判斷其是否包含某某字符,兩個集合是否相等

    ObjectType:針對方法返回值為某種類型的情況,可以判斷是否為預期的類型,一個類是否繼承於另一個類,兩個類是否為同一實例

    Raised event:針對事件是否執行的情況,可以判斷方法內部是否執行了預期的事件

    3.3、常用特性

    在xUnit中還有一些常用的特性,可作用於方法或類,如下:

    [Fact]:用來標註該方法為測試方法

    [Trait(“Name”,”Value”)]:用來對測試方法進行分組,支持標註多個不同的組名

    [Fact(Skip=”忽略說明…”)]:用來修飾需要忽略測試的方法

    3.4 、性能相關

    在測試時我們應當注意性能上的問題,針對一個對象供多個方法使用的情況,我們可以使用共享上下文

    • 針對一個對象供同一類中的多個方法使用時,可以將該對象提取出來,使用IClassFixture 對象將其注入到構造函數中
    • 針對一個對象供多個測試類使用的情況,可以使用ICollectionFixture 對象和[CollectionDefinition(“…”)]定義該對象

    需要注意在使用IClassFixtureICollectionFixture對象時應當避免多個測試方法之間相互影響的情況

    3.5、數據驅動測試

    在進行測試方法時,通常我們會指定輸入值和輸出值,如希望多測試幾種情況,我們可以定義多個測試方法,但這顯然不是一個最佳的實現;在合理的情況下,我們可以將參數和數據分離,如何實現?

    • 方法一:使用[Theory]替換[Fact],將輸入輸出參數提取為方法參數,並使用多個[InlineData(“輸入參數”,”輸出參數)]來標註方法
    • 方法二:使用[Theory]替換[Fact],針對測試方法新增一個測試數據類,該類包含一個靜態屬性IEumerable<object[]>,將數據封裝為一個list后賦值給該屬性,並使用[MemberData(nameof(數據類的屬性),MemberType=typeof(數據類))]標註測試方法即可;
    • 方法三:使用外部數據如數據庫數據/Excel數據/txt數據等,其實現原理與方法二相同,只是多了一個數據獲取封裝為list的步驟;
    • 方法四:自定義一個Attribute,繼承自DataAttribute,實現其對應的方法,使用yield返回object類型的數組;使用時只需要在測試方法上方添加[Theory][自定義Attribute]即可

    4、測試項目添加

    4.1、添加測試項目

    首先我們右鍵項目解決方案選擇添加一個項目,輸入選擇xUnit後進行添加,項目命名為BlogSystem.Core.Test,如下:

    項目添加完成后我們需要添加對測試項目的引用,在解決方案中右擊依賴項選擇添加BlogSystem.Core;這裏我們預期對Controller進行測試,但後續有可能會添加其他項目的測試,所以我們建立一個Controller_Test文件夾保證項目結構相對清晰。

    4.2、添加測試方法

    在BlogSystem.Core.Test項目的Controller_Test文件夾下新建一個命名為UserController_Should的方法;在微軟的《單元測試的最佳做法》文檔中有提到,測試命名應該包括三個部分:①被測試方法的名稱②測試的方案③方案預期行為;實際使用時也可以對照測試的方法進行命名,這裏我們先不考慮最佳命名原則,僅對照測試方法進行命名,如下:

    using Xunit;
    
    namespace BlogSystem.Core.Test.Controller_Test
    {
        public class UserController_Should
        {
            [Fact]
            public void Register_Test()
            {
                
            }
        }
    }
    

    4.3、方案選擇

    1、在進行測試時,我們可以根據實際情況使用以下方案來進行測試:

    • 方案一:直接new一個Controller對象,調用其Action方法直接進行測試;適用於Controller沒有其他依賴項的情況;
    • 方案二:當有多個依賴項時,可以藉助工具來模擬實例化時的依賴項,如Moq就是一個很好的工具;當然這需要一定的學習成本;
    • 方案三:模擬Http請求的方式來調用API進行測試;NuGet中的Microsoft.AspNetCore.TestHost就支持這類情況;
    • 方案四:自定義方法實例化所有依賴項;將測試過程種需要用到的對象放到容器中並加載,其實現較為複雜;

    這裏我們以測試UserController為例,其構造函數包含了接口服務實例和HttpContext對象實例,Action方法內部又有數據庫連接操作,從嚴格意義上來講測試這類方法已經脫離了單元測試的範疇,屬於集成測試,但這類測試一定程度上可以節省我們大量的重複勞動。這裏我們選擇方案三進行相關的測試。

    2、如何使用TestHost對象?先來看看它的工作流程,首先它會創建一個IHostBuilder對象,並用它創建一個TestServer對象,TestServer對象可以創建HttpClient對象,該對象支持發送及響應請求,如下圖所示(來自solenovex):

    在嘗試使用該對象的過程中我們會發現一個問題,創建IHostBuilder對象時需要指明類似Startup的配置項,因為這裡是測試環境,所以實際上會與BlogSystem.Core中的配置類StartUp存在一定的差異,因而這裏我們需要為測試新建立一個Startup配置類。

    4.4、方法實現

    1、我們在測試項目中添加名為TestServerFixture 的類和名為TestStartup的類,TestServerFixture 用來創建HttpClient對象並做一些準備工作,TestStartup類為配置類。然後使用Nuget安裝Microsoft.AspNetCore.TestHost;TestServerFixture 和TestStartup實現如下:

    using Autofac.Extensions.DependencyInjection;
    using BlogSystem.Core.Helpers;
    using BlogSystem.Model;
    using Microsoft.AspNetCore.Hosting;
    using Microsoft.AspNetCore.TestHost;
    using Microsoft.Extensions.Hosting;
    using System;
    using System.Net.Http;
    
    namespace BlogSystem.Core.Test
    {
        public static class TestServerFixture
        {
            public static IHostBuilder GetTestHost()
            {
                return Host.CreateDefaultBuilder()
               .UseServiceProviderFactory(new AutofacServiceProviderFactory())//使用autofac作為DI容器
               .ConfigureWebHostDefaults(webBuilder =>
               {
                   webBuilder.UseTestServer()//建立TestServer——測試的關鍵
                   .UseEnvironment("Development")
                   .UseStartup<TestStartup>();
               });
            }
    
            //生成帶token的httpclient
            public static HttpClient GetTestClientWithToken(this IHost host)
            {
                var client = host.GetTestClient();
                client.DefaultRequestHeaders.Add("Authorization", $"Bearer {GenerateJwtToken()}");//把token加到Header中
                return client;
            }
    
            //生成JwtToken
            public static string GenerateJwtToken()
            {
                TokenModelJwt tokenModel = new TokenModelJwt { UserId = userData.Id, Level = userData.Level.ToString() };
                var token = JwtHelper.JwtEncrypt(tokenModel);
                return token;
            }
    
            //測試用戶的數據
            private static readonly User userData = new User
            {
                Account = "jordan",
                Id = new Guid("9CF2DAB5-B9DC-4910-98D8-CBB9D54E3D7B"),
                Level = Level.普通用戶
            };
    
        }
    }
    
    using Autofac;
    using Autofac.Extras.DynamicProxy;
    using BlogSystem.Common.Helpers;
    using BlogSystem.Common.Helpers.SortHelper;
    using BlogSystem.Core.AOP;
    using BlogSystem.Core.Filters;
    using BlogSystem.Core.Helpers;
    using Microsoft.AspNetCore.Authentication.JwtBearer;
    using Microsoft.AspNetCore.Builder;
    using Microsoft.AspNetCore.Hosting;
    using Microsoft.AspNetCore.Http;
    using Microsoft.AspNetCore.Mvc.Formatters;
    using Microsoft.Extensions.Configuration;
    using Microsoft.Extensions.DependencyInjection;
    using Microsoft.Extensions.Hosting;
    using Microsoft.IdentityModel.Tokens;
    using System;
    using System.IO;
    using System.Linq;
    using System.Reflection;
    using System.Text;
    
    namespace BlogSystem.Core.Test
    {
        public class TestStartup
        {
            private readonly IConfiguration _configuration;
    
            public TestStartup(IConfiguration configuration)
            {
                _configuration = GetConfig(null);
                //傳遞Configuration對象
                JwtHelper.GetConfiguration(_configuration);
            }
    
            public void ConfigureServices(IServiceCollection services)
            {
                //控制器服務註冊
                services.AddControllers(setup =>
                {
                    setup.ReturnHttpNotAcceptable = true;//開啟不存在請求格式則返回406狀態碼的選項
                    var jsonOutputFormatter = setup.OutputFormatters.OfType<SystemTextJsonOutputFormatter>()?.FirstOrDefault();//不為空則繼續執行
                    jsonOutputFormatter?.SupportedMediaTypes.Add("application/vnd.company.hateoas+json");
                    setup.Filters.Add(typeof(ExceptionsFilter));//添加異常過濾器
                }).AddXmlDataContractSerializerFormatters()//開啟輸出輸入支持XML格式
    
                //jwt授權服務註冊
                services.AddAuthentication(options =>
                {
                    options.DefaultAuthenticateScheme = JwtBearerDefaults.AuthenticationScheme;
                    options.DefaultChallengeScheme = JwtBearerDefaults.AuthenticationScheme;
                }).AddJwtBearer(x =>
                {
                    x.TokenValidationParameters = new TokenValidationParameters
                    {
                        ValidateIssuerSigningKey = true, //驗證密鑰
                        IssuerSigningKey = new SymmetricSecurityKey(Encoding.ASCII.GetBytes(_configuration["JwtTokenManagement:secret"])),
    
                        ValidateIssuer = true, //驗證發行人
                        ValidIssuer = _configuration["JwtTokenManagement:issuer"],
    
                        ValidateAudience = true, //驗證訂閱人
                        ValidAudience = _configuration["JwtTokenManagement:audience"],
    
                        RequireExpirationTime = true, //驗證過期時間
                        ValidateLifetime = true, //驗證生命周期
                        ClockSkew = TimeSpan.Zero, //緩衝過期時間,即使配置了過期時間,也要考慮過期時間+緩衝時間
                    };
                });
    
                //註冊HttpContext存取器服務
                services.AddHttpContextAccessor();
    
                //自定義判斷屬性隱射關係
                services.AddTransient<IPropertyMappingService, PropertyMappingService>();
    
                services.AddTransient<IPropertyCheckService, PropertyCheckService>();
            }
    
            //configureContainer訪問AutoFac容器生成器
            public void ConfigureContainer(ContainerBuilder builder)
            {
                //獲取程序集並註冊,採用每次請求都創建一個新的對象的模式
                var assemblyBll = Assembly.LoadFrom(Path.Combine(AppContext.BaseDirectory, "BlogSystem.BLL.dll"));
                var assemblyDal = Assembly.LoadFrom(Path.Combine(AppContext.BaseDirectory, "BlogSystem.DAL.dll"));
    
                builder.RegisterAssemblyTypes(assemblyDal).AsImplementedInterfaces().InstancePerDependency();
    
                //註冊攔截器
                builder.RegisterType<LogAop>();
                //對目標類型啟用動態代理,並注入自定義攔截器攔截BLL
                builder.RegisterAssemblyTypes(assemblyBll).AsImplementedInterfaces().InstancePerDependency()
               .EnableInterfaceInterceptors().InterceptedBy(typeof(LogAop));
            }
    
            public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
            {
                if (env.IsDevelopment())
                {
                    app.UseDeveloperExceptionPage();
                }
                else
                {
                    app.UseExceptionHandler(builder =>
                    {
                        builder.Run(async context =>
                        {
                            context.Response.StatusCode = 500;
                            await context.Response.WriteAsync("Unexpected Error!");
                        });
                    });
                }
    
                app.UseRouting();
    
               //添加認證中間件
                app.UseAuthentication();
    
                //添加授權中間件
                app.UseAuthorization();
    
                app.UseEndpoints(endpoints =>
                {
                    endpoints.MapControllers();
                });
            }
    
            private IConfiguration GetConfig(string environmentName)
            {
                var path = Microsoft.DotNet.PlatformAbstractions.ApplicationEnvironment.ApplicationBasePath;
    
                IConfigurationBuilder builder = new ConfigurationBuilder().SetBasePath(path)
                   .AddJsonFile("appsettings.json", optional: true, reloadOnChange: true);
    
                if (!string.IsNullOrWhiteSpace(environmentName))
                {
                    builder = builder.AddJsonFile($"appsettings.{environmentName}.json", optional: true);
                }
    
                builder = builder.AddEnvironmentVariables();
    
                return builder.Build();
            }
        }
    }
    

    2、這裏對UserController中的註冊、登錄、獲取用戶信息方法進行測試,實際上這裏的斷言並不嚴謹,會產生什麼後果?請繼續往下看

    using BlogSystem.Model.ViewModels;
    using Microsoft.AspNetCore.Mvc;
    using Microsoft.AspNetCore.TestHost;
    using Microsoft.Extensions.Hosting;
    using Newtonsoft.Json;
    using System.Net;
    using System.Net.Http;
    using System.Text;
    using System.Threading.Tasks;
    using Xunit;
    
    namespace BlogSystem.Core.Test.Controller_Test
    {
        public class UserController_Should
        {
            const string _mediaType = "application/json";
            readonly Encoding _encoding = Encoding.UTF8;
    
    
            /// <summary>
            /// 用戶註冊
            /// </summary>
            [Fact]
            public async Task Register_Test()
            {
                // 1、Arrange
                var data = new RegisterViewModel { Account = "test", Password = "123456", RequirePassword = "123456" };
    
                StringContent content = new StringContent(JsonConvert.SerializeObject(data), _encoding, _mediaType);
    
                using var host = await TestServerFixture.GetTestHost().StartAsync();//啟動TestServer
    
                // 2、Act
                var response = await host.GetTestClient().PostAsync($"http://localhost:5000/api/user/register", content);
    
                var result = await response.Content.ReadAsStringAsync();
    
                // 3、Assert
                Assert.DoesNotContain("用戶已存在", result);
            }
    
            /// <summary>
            /// 用戶登錄
            /// </summary>
            [Fact]
            public async Task Login_Test()
            {
                var data = new LoginViewModel { Account = "jordan", Password = "123456" };
    
                StringContent content = new StringContent(JsonConvert.SerializeObject(data), _encoding, _mediaType);
    
                var host = await TestServerFixture.GetTestHost().StartAsync();//啟動TestServer
    
                var response = await host.GetTestClientWithToken().PostAsync($"http://localhost:5000/api/user/Login", content);
    
                var result = await response.Content.ReadAsStringAsync();
    
                Assert.DoesNotContain("賬號或密碼錯誤!", result);
            }
    
            /// <summary>
            /// 獲取用戶信息
            /// </summary>
            [Fact]
            public async Task UserInfo_Test()
            {
                string id = "jordan";
    
                using var host = await TestServerFixture.GetTestHost().StartAsync();//啟動TestServer
    
                var client = host.GetTestClient();
    
                var response = await client.GetAsync($"http://localhost:5000/api/user/{id}");
    
                var result = response.StatusCode;
    
                Assert.True(Equals(HttpStatusCode.OK, result)|| Equals(HttpStatusCode.NotFound, result));
            }
        }
    }
    

    4.5、異常及解決

    1、添加完上述的測試方法后,我們使用打開Visual Studio自帶的測試資源管理器,點擊運行所有測試,發現提示錯誤無法加載BLL?在原先的BlogSystem.Core的StartUp類中我們是加載BLL和DAL項目的dll來達到解耦的目的,所以做了一個將dll輸出到Core項目bin文件夾的動作,但是在測試項目的TestStarup類中,我們是無法加載到BLL和DAL的。我嘗試將BLL和DAL同時輸出到兩個路徑下,但未找到對應的方法,所以這裏我採用了最簡單的解決方法,測試項目添加了對DAL和BLL的引用。再次運行,如下圖,似乎成功了??

    2、我們在測試方法內部打上斷點,右擊測試方法,選擇調試測試,結果發現response參數為空,只應Assert不嚴謹導致看上去沒有問題;在各種查找后,我終於找到了解決辦法,在TestStarup類的ConfigureServices方法內部service.AddControllers方法最後加上這麼一句話即可解決 .AddApplicationPart(Assembly.LoadFrom(Path.Combine(AppContext.BaseDirectory, "BlogSystem.Core.dll")))

    3、再次運行測試方法,成功!但是又發現了另外一個問題,這裏我們只是測試,但是數據庫中卻出現了我們測試添加的test賬號,如何解決?我們可以使用Microsoft.EntityFrameworkCore.InMemory庫 ,它支持使用內存數據庫進行測試,這裏暫未添加,有興趣的朋友可以自行研究。

    本章完~

    本人知識點有限,若文中有錯誤的地方請及時指正,方便大家更好的學習和交流。

    本文部分內容參考了網絡上的視頻內容和文章,僅為學習和交流,視頻地址如下:

    老張的哲學,系列教程一目錄:.netcore+vue 前後端分離

    我想吃晚飯,ASP.NET Core搭建多層網站架構【12-xUnit單元測試之集成測試】

    solenovex,使用 xUnit.NET 對 .NET Core 項目進行單元測試

    solenovex,ASP.NET Core Web API 集成測試

    微軟官方文檔,.NET Core 和 .NET Standard 中的單元測試

    Edison Zhou,.NET單元測試的藝術

    聲明

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

    【其他文章推薦】

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

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

    ※超省錢租車方案

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

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

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