標籤: USB CONNECTOR

  • Appium+python自動化(四十一)-Appium自動化測試框架綜合實踐 – 即將落下帷幕(超詳解)

    Appium+python自動化(四十一)-Appium自動化測試框架綜合實踐 – 即將落下帷幕(超詳解)

    1.簡介

      今天我們緊接着上一篇繼續分享Appium自動化測試框架綜合實踐 – 代碼實現。到今天為止,大功即將告成;框架所需要的代碼實現都基本完成。

    2.data數據封裝

    2.1使用背景

    在實際項目過程中,我們的數據可能是存儲在一個數據文件中,如txt,excel、csv文件類型。我們可以封裝一些方法來讀取文件中的數據來實現數據驅動。

    2.2案例

    將測試賬號存儲在account.csv文件,內容如下:

    account.csv

    hg2018

    hg2018

    hg2019

    zxw2019

    666

    222

    參考代碼

    2.3enumerate()簡介

    enumerate()是python的內置函數

    • enumerate在字典上是枚舉、列舉的意思
    • 對於一個可迭代的(iterable)/可遍歷的對象(如列表、字符串),enumerate將其組成一個索引序列,利用它可以同時獲得索引和值
    • enumerate多用於在for循環中得到計數。

    2.4enumerate()使用

    如果對一個列表,既要遍歷索引又要遍曆元素時,首先可以這樣寫:

    參考代碼
    list = ["", "", "一個", "測試","數據"]
    
    for i in range(len(list)):
    
        print(i,list[i])

    上述方法有些累贅,利用enumerate()會更加直接和優美:

    參考代碼
    list1 = ["", "", "一個", "測試","數據"]
    
    for index, item in enumerate(list1):
    
            print(index,item)

    3.數據讀取方法封裝

      數據讀取方法也屬於公共方法,這裏我們首先實現一下,然後將其封裝到裡邊即可。

    3.1數據讀取方法實現的參考代碼

    import csv
    
    
         def get_csv_data(csv_file,line):
    
            with open(csv_file, 'r', encoding='utf-8-sig') as file:
    
                reader=csv.reader(file)
    
                for index, row in enumerate(reader,1):
    
                    if index == line:
    
                        return row
    
     
    
        csv_file='../data/account.csv'
    
        data=get_csv_data(csv_file,3)
    
        print(data)

    3.2封裝

    將其封裝在公共方法中,在其他地方用到的時候,直接導入調用即可。

    4.utf-8與utf-8-sig兩種編碼格式的區別

    UTF-8以字節為編碼單元,它的字節順序在所有系統中都是一樣的,沒有字節序的問題,也因此它實際上並不需要BOM(“ByteOrder Mark”)。但是UTF-8 with BOM即utf-8-sig需要提供BOM。

    5.config文件配置

    各種配置文件都放在這個目錄下。

    5.1日誌文件配置 

    主要是一些日誌信息的配置。

    log.config

     參考代碼
    [loggers]
    keys=root,infoLogger
    
    [logger_root]
    level=DEBUG
    handlers=consoleHandler,fileHandler
    
    [logger_infoLogger]
    handlers=consoleHandler,fileHandler
    qualname=infoLogger
    propagate=0
    
    [handlers]
    keys=consoleHandler,fileHandler
    
    [handler_consoleHandler]
    class=StreamHandler
    level=INFO
    formatter=form02
    args=(sys.stdout,)
    
    [handler_fileHandler]
    class=FileHandler
    level=INFO
    formatter=form01
    args=('../logs/runlog.log', 'a')
    
    [formatters]
    keys=form01,form02
    
    [formatter_form01]
    format=%(asctime)s %(filename)s[line:%(lineno)d] %(levelname)s %(message)s
    
    [formatter_form02]
    format=%(asctime)s %(filename)s[line:%(lineno)d] %(levelname)s %(message)s

    6.測試用例封裝

    這裏宏哥舉例給小夥伴們演示:封裝註冊和登錄兩個測試用例。

    6.1測試用例執行開始結束操作封裝

    測試用例執行開始和結束的封裝,其他模塊用到直接導入,調用即可。

    myunit.py

    參考代碼
    # coding=utf-8
    # 1.先設置編碼,utf-8可支持中英文,如上,一般放在第一行
    
    # 2.註釋:包括記錄創建時間,創建人,項目名稱。
    '''
    Created on 2019-11-20
    @author: 北京-宏哥   QQ交流群:707699217
    Project:Appium自動化測試框架綜合實踐 - 代碼實現
    '''
    # 3.導入模塊
    import unittest
    from kyb_testProject.common.desired_caps import appium_desired
    import logging
    from time import sleep
    
    class StartEnd(unittest.TestCase):
        def setUp(self):
            logging.info('=====setUp====')
            self.driver=appium_desired()
    
        def tearDown(self):
            logging.info('====tearDown====')
            sleep(5)
            self.driver.close_app()

    6.2註冊用例

    開始註冊用例代碼邏輯的實現。

    test_register.py

    參考代碼
    # coding=utf-8
    # 1.先設置編碼,utf-8可支持中英文,如上,一般放在第一行
    
    # 2.註釋:包括記錄創建時間,創建人,項目名稱。
    '''
    Created on 2019-11-20
    @author: 北京-宏哥   QQ交流群:707699217
    Project:Appium自動化測試框架綜合實踐 - 代碼實現
    '''
    # 3.導入模塊
    from kyb_testProject.common.myunit import StartEnd
    from kyb_testProject.businessView.registerView import RegisterView
    import logging,random,unittest
    
    class RegisterTest(StartEnd):
        def test_user_register(self):
            logging.info('======test_user_register======')
            r=RegisterView(self.driver)
    
            username = 'bjhg2019' + 'fly' + str(random.randint(1000, 9000))
            password = 'bjhg2020' + str(random.randint(1000, 9000))
            email = 'bjhg' + str(random.randint(1000, 9000)) + '@163.com'
    
            self.assertTrue(r.register_action(username,password,email))
    
    if __name__ == '__main__':
        unittest.main()

    6.3登錄用例

    開始登錄用例代碼邏輯的實現。

    test_login.py

    參考代碼
    # coding=utf-8
    # 1.先設置編碼,utf-8可支持中英文,如上,一般放在第一行
    
    # 2.註釋:包括記錄創建時間,創建人,項目名稱。
    '''
    Created on 2019-11-13
    @author: 北京-宏哥   QQ交流群:707699217
    Project:Appium自動化測試框架綜合實踐 - 代碼實現
    '''
    # 3.導入模塊
    from kyb_testProject.common.myunit import StartEnd
    from kyb_testProject.businessView.loginView import LoginView
    import unittest
    import logging
    
    class TestLogin(StartEnd):
        csv_file='../data/account.csv'
    
        @unittest.skip('test_login_zxw2018')
        def test_login_zxw2018(self):
            logging.info('======test_login_zxw2018=====')
            l=LoginView(self.driver)
            data=l.get_csv_data(self.csv_file,2)
    
            l.login_action(data[0],data[1])
            self.assertTrue(l.check_loginStatus())
    
        # @unittest.skip('skip test_login_zxw2017')
        def test_login_zxw2017(self):
            logging.info('======test_login_zxw2017=====')
            l=LoginView(self.driver)
            data = l.get_csv_data(self.csv_file, 1)
    
            l.login_action(data[0], data[1])
            self.assertTrue(l.check_loginStatus())
    
        @unittest.skip('test_login_error')
        def test_login_error(self):
            logging.info('======test_login_error=====')
            l = LoginView(self.driver)
            data = l.get_csv_data(self.csv_file, 3)
    
            l.login_action(data[0], data[1])
            self.assertTrue(l.check_loginStatus(),msg='login fail!')
    
    if __name__ == '__main__':
        unittest.main()

    7.小結

    到此,Appium自動化測試框架就差下一篇就全部完成了,聰明的你都懂了嗎???嘿嘿!慢慢地來吧。

    下節預告

    下一篇,講解執行測試用例,生成測試報告,以及自動化平台,請關注宏哥,敬請期待!!!

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

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

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

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

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

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

  • Netty學習篇⑤–編、解碼源碼分析

    Netty學習篇⑤–編、解碼源碼分析

    前言

    學習Netty也有一段時間了,Netty作為一個高性能的異步框架,很多RPC框架也運用到了Netty中的知識,在rpc框架中豐富的數據協議及編解碼可以讓使用者更加青睞;
    Netty支持豐富的編解碼框架,其本身內部提供的編解碼也可以應對各種業務場景;
    今天主要就是學習下Netty中提供的編、解碼類,之前只是簡單的使用了下Netty提供的解碼類,今天更加深入的研究下Netty中編、解碼的源碼及部分使用。

    編、解碼的概念

    • 編碼(Encoder)

      編碼就是將我們發送的數據編碼成字節數組方便在網絡中進行傳輸,類似Java中的序列化,將對象序列化成字節傳輸
    • 解碼(Decoder)

      解碼和編碼相反,將傳輸過來的字節數組轉化為各種對象來進行展示等,類似Java中的反序列化
      如:
      // 將字節數組轉化為字符串
      new String(byte bytes[], Charset charset)

    編、解碼超類

    ByteToMessageDecoder: 解碼超類,將字節轉換成消息

    解碼解碼一般用於將獲取到的消息解碼成系統可識別且自己需要的數據結構;因此ByteToMessageDecoder需要繼承ChannelInboundHandlerAdapter入站適配器來獲取到入站的數據,在handler使用之前通過channelRead獲取入站數據進行一波解碼;
    ByteToMessageDecoder類圖

    源碼分析

    通過channelRead獲取入站數據,將數據緩存至cumulation數據緩衝區,最後在傳給decode進行解碼,在read完成之後清空緩存的數據

    1. 獲取入站數據

    /**
    *  通過重寫channelRead方法來獲取入站數據
    */
    @Override
    public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
        // 檢測是否是byteBuf對象格式數據
        if (msg instanceof ByteBuf) {
            // 實例化字節解碼成功輸出集合 即List<Object> out
            CodecOutputList out = CodecOutputList.newInstance();
            try {
                // 獲取到的請求的數據
                ByteBuf data = (ByteBuf) msg;
                // 如果緩衝數據區為空則代表是首次觸發read方法
                first = cumulation == null;
                if (first) {
                    // 如果是第一次read則當前msg數據為緩衝數據
                    cumulation = data;
                } else {
                    // 如果不是則觸發累加,將緩衝區的舊數據和新獲取到的數據通過        expandCumulation 方法累加在一起存入緩衝區cumulation
                    // cumulator 累加類,將緩衝池中數據和新數據進行組合在一起
                    // private Cumulator cumulator = MERGE_CUMULATOR;
                    cumulation = cumulator.cumulate(ctx.alloc(), cumulation, data);
                }
                // 將緩衝區數據cumulation進行解碼
                callDecode(ctx, cumulation, out);
            } catch (DecoderException e) {
                throw e;
            } catch (Throwable t) {
                throw new DecoderException(t);
            } finally {
                // 在解碼完畢后釋放引用和清空全局字節緩衝區
                if (cumulation != null && !cumulation.isReadable()) {
                    numReads = 0;
                    cumulation.release();
                    cumulation = null;
                    // discardAfterReads為netty中設置的讀取多少次后開始丟棄字節 默認值16
                    // 可通過setDiscardAfterReads(int n)來設置值不設置默認16次
                } else if (++ numReads >= discardAfterReads) {
                    // We did enough reads already try to discard some bytes so we not risk to see a OOME.
                    // 在我們讀取了足夠的數據可以嘗試丟棄一些字節已保證不出現內存溢出的異常
                    // 
                    // See https://github.com/netty/netty/issues/4275
                    // 讀取次數重置為0
                    numReads = 0;
                    // 重置讀寫指針或丟棄部分已讀取的字節
                    discardSomeReadBytes();
                }
                // out為解碼成功的傳遞給下一個handler
                int size = out.size();
                decodeWasNull = !out.insertSinceRecycled();
                // 結束當前read傳遞到下個ChannelHandler
                fireChannelRead(ctx, out, size);
                // 回收響應集合 將insertSinceRecycled設置為false;
                // insertSinceRecycled用於channelReadComplete判斷使用
                out.recycle();
            }
        } else {
            // 不是的話直接fire傳遞給下一個handler
            ctx.fireChannelRead(msg);
        }
    }
    
    2. 初始化字節緩衝區計算器: Cumulator主要用於全局字節緩衝區和新讀取的字節緩衝區組合在一起擴容
    public static final Cumulator MERGE_CUMULATOR = new Cumulator() {
        
        /**
        * alloc ChannelHandlerContext分配的字節緩衝區
        * cumulation 當前ByteToMessageDecoder類全局的字節緩衝區
        * in 入站的字節緩衝區
        **/
        @Override
        public ByteBuf cumulate(ByteBufAllocator alloc, ByteBuf cumulation, ByteBuf in) {
            final ByteBuf buffer;
            // 如果全局ByteBuf寫入的字節+當前入站的字節數據大於全局緩衝區最大的容量或者全局緩衝區的引用數大於1個或全局緩衝區只讀
            if (cumulation.writerIndex() > cumulation.maxCapacity() - in.readableBytes()
                || cumulation.refCnt() > 1 || cumulation.isReadOnly()) {
                // Expand cumulation (by replace it) when either there is not more room in the buffer
                // or if the refCnt is greater then 1 which may happen when the user use slice().retain() or
                // duplicate().retain() or if its read-only.
                //
                // See:
                // - https://github.com/netty/netty/issues/2327
                // - https://github.com/netty/netty/issues/1764
                // 進行擴展全局字節緩衝區(容量大小 = 新數據追加到舊數據末尾組成新的全局字節緩衝區)
                buffer = expandCumulation(alloc, cumulation, in.readableBytes());
            } else {
                buffer = cumulation;
            }
            // 將新數據寫入緩衝區
            buffer.writeBytes(in);
            // 釋放當前的字節緩衝區的引用
            in.release();
            
            return buffer;
        }
    };
    
    
    /**
    * alloc 字節緩衝區操作類
    * cumulation 全局累加字節緩衝區
    * readable 讀取到的字節數長度
    */
    // 字節緩衝區擴容方法
    static ByteBuf expandCumulation(ByteBufAllocator alloc, ByteBuf cumulation, int readable) {
        // 舊數據
        ByteBuf oldCumulation = cumulation;
        // 通過ByteBufAllocator將緩衝區擴大到oldCumulation + readable大小
        cumulation = alloc.buffer(oldCumulation.readableBytes() + readable);
        // 將舊數據重新寫入到新的字節緩衝區
        cumulation.writeBytes(oldCumulation);
        // 舊字節緩衝區引用-1
        oldCumulation.release();
        return cumulation;
    }
    3. ByteBuf釋放當前字節緩衝區的引用: 通過調用ReferenceCounted接口中的release方法來釋放
    @Override
    public boolean release() {
        return release0(1);
    }
    
    @Override
    public boolean release(int decrement) {
        return release0(checkPositive(decrement, "decrement"));
    }
    
    /**
    * decrement 減量
    */
    private boolean release0(int decrement) {
        for (;;) {
            int refCnt = this.refCnt;
            // 當前引用小於減量
            if (refCnt < decrement) {
                throw new IllegalReferenceCountException(refCnt, -decrement);
            }
            // 這裏就利用里線程併發中的知識CAS,線程安全的設置refCnt的值
            if (refCntUpdater.compareAndSet(this, refCnt, refCnt - decrement)) {
                // 如果減量和引用量相等
                if (refCnt == decrement) {
                    // 全部釋放
                    deallocate();
                    return true;
                }
                return false;
            }
        }
    }

    4. 將全局字節緩衝區進行解碼

    /**
    * ctx ChannelHandler的上下文,用於傳輸數據與下一個handler來交互
    * in 入站數據
    * out 解析之後的出站集合 (此出站不是返回給客戶端的而是傳遞給下個handler的)
    */
    protected void callDecode(ChannelHandlerContext ctx, ByteBuf in, List<Object> out) {
        try {
            // 如果入站數據還有沒解析的
            while (in.isReadable()) {
                // 解析成功的出站集合長度
                int outSize = out.size();
                // 如果大於0則說明解析成功的數據還沒被消費完,直接fire掉給通道中的後續handler繼續                消費
                if (outSize > 0) {
                    fireChannelRead(ctx, out, outSize);
                    out.clear();
    
                    // Check if this handler was removed before continuing with decoding.
                    // 在這個handler刪除之前檢查是否還在繼續解碼
                    // If it was removed, it is not safe to continue to operate on the buffer.
                    // 如果移除了,它繼續操作緩衝區是不安全的
                    //
                    // See:
                    // - https://github.com/netty/netty/issues/4635
                    if (ctx.isRemoved()) {
                        break;
                    }
                    outSize = 0;
                }
                // 入站數據字節長度
                int oldInputLength = in.readableBytes();
                // 開始解碼數據
                decodeRemovalReentryProtection(ctx, in, out);
    
                // Check if this handler was removed before continuing the loop.
                // 
                // If it was removed, it is not safe to continue to operate on the buffer.
                //
                // See https://github.com/netty/netty/issues/1664
                if (ctx.isRemoved()) {
                    break;
                }
    
                // 解析完畢跳出循環
                if (outSize == out.size()) {
                    if (oldInputLength == in.readableBytes()) {
                        break;
                    } else {
                        continue;
                    }
                }
    
                if (oldInputLength == in.readableBytes()) {
                    throw new DecoderException(
                        StringUtil.simpleClassName(getClass()) +
                        ".decode() did not read anything but decoded a message.");
                }
    
                if (isSingleDecode()) {
                    break;
                }
            }
        } catch (DecoderException e) {
            throw e;
        } catch (Throwable cause) {
            throw new DecoderException(cause);
        }
    }
    
    final void decodeRemovalReentryProtection(ChannelHandlerContext ctx, ByteBuf in, List<Object> out) throws Exception {
            // 設置解碼狀態為正在解碼  STATE_INIT = 0; STATE_CALLING_CHILD_DECODE = 1;             STATE_HANDLER_REMOVED_PENDING = 2; 分別為初始化; 解碼; 解碼完畢移除
            decodeState = STATE_CALLING_CHILD_DECODE;
            try {
                // 具體的解碼邏輯(netty提供的解碼器或自定義解碼器中重寫的decode方法)
                decode(ctx, in, out);
            } finally {
                // 此時decodeState為正在解碼中 值為1,返回false
                boolean removePending = decodeState == STATE_HANDLER_REMOVED_PENDING;
                // 在設置為初始化等待解碼
                decodeState = STATE_INIT;
                // 解碼完成移除當前ChannelHandler標記為不處理
                // 可以看看handlerRemoved源碼。如果緩衝區還有數據直接傳遞給下一個handler
                if (removePending) {
                    handlerRemoved(ctx);
                }
            }
        }
    5. 執行channelReadComplete
    @Override
    public void channelReadComplete(ChannelHandlerContext ctx) throws Exception {
        // 讀取次數重置
        numReads = 0;
        // 重置讀寫index
        discardSomeReadBytes();
        // 在channelRead meth中定義賦值 decodeWasNull = !out.insertSinceRecycled();
        // out指的是解碼集合List<Object> out; 咱們可以點進
        if (decodeWasNull) {
            decodeWasNull = false;
            if (!ctx.channel().config().isAutoRead()) {
                ctx.read();
            }
        }
        // fire掉readComplete傳遞到下一個handler的readComplete
        ctx.fireChannelReadComplete();
    }
    
    /**
    *  然後我們可以搜索下insertSinceRecucled在什麼地方被賦值了
    * Returns {@code true} if any elements where added or set. This will be reset once {@link #recycle()} was called.
    */
    boolean insertSinceRecycled() {
        return insertSinceRecycled;
    }
    
    
    // 搜索下insert的調用我們可以看到是CodecOutputList類即為channelRead中的out集合,眾所周知在    decode完之後,解碼數據就會被調用add方法,此時insertSinceRecycled被設置為true
    private void insert(int index, Object element) {
        array[index] = element;
        insertSinceRecycled = true;
    }
    
    
    /**
    * 清空回收數組內部的所有元素和存儲空間
    * Recycle the array which will clear it and null out all entries in the internal storage.
    */
    // 搜索recycle的調用我么可以知道在channelRead的finally邏輯中 調用了out.recycle();此時        insertSinceRecycled被設置為false
    void recycle() {
        for (int i = 0 ; i < size; i ++) {
            array[i] = null;
        }
        clear();
        insertSinceRecycled = false;
        handle.recycle(this);
    }
    

    至此ByteToMessageDecoder解碼類應該差不多比較清晰了!!!

    MessageToByteEncoder: 編碼超類,將消息轉成字節進行編碼發出

    何謂編碼,就是將發送數據轉化為客戶端和服務端約束好的數據結構和格式進行傳輸,我們可以在編碼過程中將消息體body的長度和一些頭部信息有序的設置到ByteBuf字節緩衝區中;方便解碼方靈活的運用來判斷(是否完整的包等)和處理業務;解碼是繼承入站數據,反之編碼應該繼承出站的數據;接下來我們看看編碼類是怎麼進行編碼的;
    MessageToByteEncoder類圖如下

    源碼分析

    既然是繼承出站類,我們直接看看write方法是怎麼樣的

    /**
    * 通過write方法獲取到出站的數據即要發送出去的數據
    * ctx channelHandler上下文
    * msg 發送的數據 Object可以通過繼承類指定的泛型來指定
    * promise channelPromise異步監聽,類似ChannelFuture,只不過promise可以設置監聽的結果,future只能通過獲取監聽的成功失敗結果;可以去了解下promise和future的區別
    */
    @Override
    public void write(ChannelHandlerContext ctx, Object msg, ChannelPromise promise) throws Exception {
        ByteBuf buf = null;
        try {
            // 檢測發送數據的類型 通過TypeParameterMatcher類型匹配器
            if (acceptOutboundMessage(msg)) {
                @SuppressWarnings("unchecked")
                I cast = (I) msg;
                // 分配字節緩衝區 preferDirect默認為true
                buf = allocateBuffer(ctx, cast, preferDirect);
                try {
                    // 進行編碼
                    encode(ctx, cast, buf);
                } finally {
                    // 完成編碼后釋放對象的引用
                    ReferenceCountUtil.release(cast);
                }
                // 如果緩衝區有數據則通過ctx發送出去,promise可以監聽數據傳輸並設置是否完成
                if (buf.isReadable()) {
                    ctx.write(buf, promise);
                } else {
                    // 如果沒有數據則釋放字節緩衝區的引用併發送一個empty的空包
                    buf.release();
                    ctx.write(Unpooled.EMPTY_BUFFER, promise);
                }
                buf = null;
            } else {
                // 非TypeParameterMatcher類型匹配器匹配的類型直接發送出去
                ctx.write(msg, promise);
            }
        } catch (EncoderException e) {
            throw e;
        } catch (Throwable e) {
            throw new EncoderException(e);
        } finally {
            if (buf != null) {
                buf.release();
            }
        }
    }
    
    // 初始化設置preferDirect為true
    protected MessageToByteEncoder() {
        this(true);
    }
    protected MessageToByteEncoder(boolean preferDirect) {
        matcher = TypeParameterMatcher.find(this, MessageToByteEncoder.class, "I");
        this.preferDirect = preferDirect;
    }

    編碼: 重寫encode方法,根據實際業務來進行數據編碼

    // 此處就是我們需要重寫的編碼方法了,我們和根據約束好的或者自己定義好想要的數據格式發送給對方
    
    // 下面是我自己寫的demo的編碼方法;頭部設置好body的長度,服務端可以根據長度來判斷是否是完整的包,僅僅自學寫的簡單的demo非正常線上運營項目的邏輯
    public class MyClientEncode extends MessageToByteEncoder<String> {
    
        @Override
        protected void encode(ChannelHandlerContext ctx, String msg, ByteBuf out) throws Exception {
            if (null != msg) {
                byte[] request = msg.getBytes(Charset.forName("UTF-8"));
                out.writeInt(request.length);
                out.writeBytes(request);
            }
        }
    }

    編碼類相對要簡單很多,因為只需要將發送的數據序列化,按照一定的格式進行發送數據!!!

    項目實戰

    項目主要簡單的實現下自定義編解碼器的運用及LengthFieldBasedFrameDecoder的使用

    • 項目結構如下
      │  hetangyuese-netty-06.iml
      │  pom.xml
      │
      ├─src
      │  ├─main
      │  │  ├─java
      │  │  │  └─com
      │  │  │      └─hetangyuese
      │  │  │          └─netty
      │  │  │              ├─client
      │  │  │              │      MyClient06.java
      │  │  │              │      MyClientChannelInitializer.java
      │  │  │              │      MyClientDecoder.java
      │  │  │              │      MyClientEncode.java
      │  │  │              │      MyClientHandler.java
      │  │  │              │      MyMessage.java
      │  │  │              │
      │  │  │              └─server
      │  │  │                      MyChannelInitializer.java
      │  │  │                      MyServer06.java
      │  │  │                      MyServerDecoder.java
      │  │  │                      MyServerDecoderLength.java
      │  │  │                      MyServerEncoder.java
      │  │  │                      MyServerHandler.java
      │  │  │
      │  │  └─resources
      │  └─test
      │      └─java
      
    • 服務端

      Serverhandler: 只是簡單的將解碼的內容輸出

      public class MyServerHandler extends ChannelInboundHandlerAdapter {
      
          @Override
          public void channelActive(ChannelHandlerContext ctx) throws Exception {
              System.out.println("客戶端連接成功 time: " + new Date().toLocaleString());
          }
      
          @Override
          public void channelInactive(ChannelHandlerContext ctx) throws Exception {
              System.out.println("客戶端斷開連接 time: " + new Date().toLocaleString());
          }
      
          @Override
          public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
              String body = (String) msg;
              System.out.println("content:" + body);
          }
      
          @Override
          public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
              // 出現異常關閉通道
              cause.printStackTrace();
              ctx.close();
          }
      }

      解碼器

      public class MyServerDecoder extends ByteToMessageDecoder {
      
          // 此處我頭部只塞了長度字段佔4個字節,別問為啥我知道,這是要客戶端和服務端約束好的
          private static int min_head_length = 4;
      
          @Override
          protected void decode(ChannelHandlerContext ctx, ByteBuf in, List<Object> out) throws Exception {
              // 解碼的字節長度
              int size = in.readableBytes();
              if(size < min_head_length) {
                  System.out.println("解析的數據長度小於頭部長度字段的長度");
                  return ;
              }
              // 讀取的時候指針已經移位到長度字段的尾端
              int length = in.readInt();
              if (size < length) {
                  System.out.println("解析的數據長度與長度不符合");
                  return ;
              }
      
              // 上面已經讀取到了長度字段,後面的長度就是body
              ByteBuf decoderArr = in.readBytes(length);
              byte[] request = new byte[decoderArr.readableBytes()];
              // 將數據寫入空數組
              decoderArr.readBytes(request);
              String body = new String(request, Charset.forName("UTF-8"));
              out.add(body);
          }
      }

      將解碼器加入到channelHandler中:記得加到業務handler的前面否則無效

      public class MyChannelInitializer extends ChannelInitializer<SocketChannel> {
      
          @Override
          protected void initChannel(SocketChannel ch) throws Exception {
              ch.pipeline()
      //                .addLast(new MyServerDecoderLength(10240, 0, 4, 0, 0))
      //                .addLast(new LengthFieldBasedFrameDecoder(10240, 0, 4, 0, 0))
                      .addLast(new MyServerDecoder())
                      .addLast(new MyServerHandler())
              ;
          }
      }
    • 客戶端

      ClientHandler

      public class MyClientHandler extends ChannelInboundHandlerAdapter {
      
          @Override
          public void channelActive(ChannelHandlerContext ctx) throws Exception {
              System.out.println("與服務端連接成功");
              for (int i = 0; i<10; i++) {
                  ctx.writeAndFlush("hhhhh" + i);
              }
          }
      
          @Override
          public void channelInactive(ChannelHandlerContext ctx) throws Exception {
              System.out.println("與服務端斷開連接");
          }
      
          @Override
          public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
              System.out.println("收到服務端消息:" +msg+ " time: " + new Date().toLocaleString());
          }
      
          @Override
          public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
              cause.printStackTrace();
              ctx.close();
          }
      }

      編碼器

      public class MyClientEncode extends MessageToByteEncoder<String> {
      
          @Override
          protected void encode(ChannelHandlerContext ctx, String msg, ByteBuf out) throws Exception {
              if (null != msg) {
                  byte[] request = msg.getBytes(Charset.forName("UTF-8"));
                  out.writeInt(request.length);
                  out.writeBytes(request);
              }
          }
      }

      將編碼器加到ClientHandler的前面

      public class MyClientChannelInitializer extends ChannelInitializer<SocketChannel> {
      
          @Override
          protected void initChannel(SocketChannel ch) throws Exception {
              ch.pipeline()
                      .addLast(new MyClientDecoder())
                      .addLast(new MyClientEncode())
                      .addLast(new MyClientHandler())
              ;
      
          }
      }
    • 服務端運行結果
      MyServer06 is start ...................
      客戶端連接成功 time: 2019-11-19 16:35:47
      content:hhhhh0
      content:hhhhh1
      content:hhhhh2
      content:hhhhh3
      content:hhhhh4
      content:hhhhh5
      content:hhhhh6
      content:hhhhh7
      content:hhhhh8
      content:hhhhh9
    • 如果不用自定義的解碼器怎麼獲取到body內容呢

      將自定義編碼器換成LengthFieldBasedFrameDecoder(10240, 0, 4, 0, 0)

      public class MyChannelInitializer extends ChannelInitializer<SocketChannel> {
      
          @Override
          protected void initChannel(SocketChannel ch) throws Exception {
              ch.pipeline()
      //                .addLast(new MyServerDecoderLength(10240, 0, 4, 0, 0))
                      .addLast(new LengthFieldBasedFrameDecoder(10240, 0, 4, 0, 0))
      //                .addLast(new MyServerDecoder())
                      .addLast(new MyServerHandler())
              ;
          }
      }
      
      // 怕忘記的各個參數的含義在這在說明一次,自己不斷的修改每個值觀察結果就可以更加深刻的理解
      /**
      * maxFrameLength:消息體的最大長度,好像默認最大值為1024*1024
      * lengthFieldOffset 長度字段所在字節數組的下標 (我這是第一個write的所以下標是0)
      * lengthFieldLength 長度字段的字節長度(int類型佔4個字節)
      * lengthAdjustment 長度字段補償的數值 (lengthAdjustment =  數據包長度 - lengthFieldOffset - lengthFieldLength - 長度域的值),解析需要減去對應的數值
      * initialBytesToStrip 是否去掉長度字段(0不去除,對應長度域字節長度)
      */
      public LengthFieldBasedFrameDecoder(
                  int maxFrameLength,
                  int lengthFieldOffset, int lengthFieldLength,
                  int lengthAdjustment, int initialBytesToStrip)
      結果: 前都帶上了長度
      MyServer06 is start ...................
      客戶端連接成功 time: 2019-11-19 17:53:42
      收到客戶端發來的消息:   hhhhh0, time: 2019-11-19 17:53:42
      收到客戶端發來的消息:   hhhhh1, time: 2019-11-19 17:53:42
      收到客戶端發來的消息:   hhhhh2, time: 2019-11-19 17:53:42
      收到客戶端發來的消息:   hhhhh3, time: 2019-11-19 17:53:42
      收到客戶端發來的消息:   hhhhh4, time: 2019-11-19 17:53:42
      收到客戶端發來的消息:   hhhhh5, time: 2019-11-19 17:53:42
      收到客戶端發來的消息:   hhhhh6, time: 2019-11-19 17:53:42
      收到客戶端發來的消息:   hhhhh7, time: 2019-11-19 17:53:42
      收到客戶端發來的消息:   hhhhh8, time: 2019-11-19 17:53:42
      收到客戶端發來的消息:   hhhhh9, time: 2019-11-19 17:53:42

      如果我們在客戶端的長度域中做手腳 LengthFieldBasedFrameDecoder(10240, 0, 4, 0, 0)

      舊: out.writeInt(request.length);
      新: out.writeInt(request.length + 1);
      // 看結果就不正常,0後面多了一個0;但是不知道為啥只解碼了一次??? 求解答
      MyServer06 is start ...................
      客戶端連接成功 time: 2019-11-19 17:56:55
      收到客戶端發來的消息:   hhhhh0 , time: 2019-11-19 17:56:55
      
      // 正確修改為 LengthFieldBasedFrameDecoder(10240, 0, 4, -1, 0)
      // 結果:
      MyServer06 is start ...................
      客戶端連接成功 time: 2019-11-19 18:02:18
      收到客戶端發來的消息:   hhhhh0, time: 2019-11-19 18:02:18
      收到客戶端發來的消息:   hhhhh1, time: 2019-11-19 18:02:18
      收到客戶端發來的消息:   hhhhh2, time: 2019-11-19 18:02:18
      收到客戶端發來的消息:   hhhhh3, time: 2019-11-19 18:02:18
      收到客戶端發來的消息:   hhhhh4, time: 2019-11-19 18:02:18
      收到客戶端發來的消息:   hhhhh5, time: 2019-11-19 18:02:18
      收到客戶端發來的消息:   hhhhh6, time: 2019-11-19 18:02:18
      收到客戶端發來的消息:   hhhhh7, time: 2019-11-19 18:02:18
      收到客戶端發來的消息:   hhhhh8, time: 2019-11-19 18:02:18
      收到客戶端發來的消息:   hhhhh9, time: 2019-11-19 18:02:18

      捨棄長度域 :LengthFieldBasedFrameDecoder(10240, 0, 4, 0, 4)

      // 結果
      MyServer06 is start ...................
      客戶端連接成功 time: 2019-11-19 18:03:44
      收到客戶端發來的消息:hhhhh0, time: 2019-11-19 18:03:44
      收到客戶端發來的消息:hhhhh1, time: 2019-11-19 18:03:44
      收到客戶端發來的消息:hhhhh2, time: 2019-11-19 18:03:44
      收到客戶端發來的消息:hhhhh3, time: 2019-11-19 18:03:44
      收到客戶端發來的消息:hhhhh4, time: 2019-11-19 18:03:44
      收到客戶端發來的消息:hhhhh5, time: 2019-11-19 18:03:44
      收到客戶端發來的消息:hhhhh6, time: 2019-11-19 18:03:44
      收到客戶端發來的消息:hhhhh7, time: 2019-11-19 18:03:44
      收到客戶端發來的消息:hhhhh8, time: 2019-11-19 18:03:44
      收到客戶端發來的消息:hhhhh9, time: 2019-11-19 18:03:44
      分析源碼示例中的 lengthAdjustment = 消息字節長度 – lengthFieldOffset-lengthFieldLength-長度域中的值
    • 源碼中的示例
       * <pre>
       * lengthFieldOffset   =  0
       * lengthFieldLength   =  2
       * <b>lengthAdjustment</b>    = <b>-2</b> (= the length of the Length field)
       * initialBytesToStrip =  0
       *
       * BEFORE DECODE (14 bytes)         AFTER DECODE (14 bytes)
       * +--------+----------------+      +--------+----------------+
       * | Length | Actual Content |----->| Length | Actual Content |
       * | 0x000E | "HELLO, WORLD" |      | 0x000E | "HELLO, WORLD" |
       * +--------+----------------+      +--------+----------------+
       * </pre>
      長度域中0x000E為16進制,轉換成10進制是14,說明消息體長度為14;根據公式:14-0-2-14 = -2
      * <pre>
       * lengthFieldOffset   = 0
       * lengthFieldLength   = 3
       * <b>lengthAdjustment</b>    = <b>2</b> (= the length of Header 1)
       * initialBytesToStrip = 0
       *
       * BEFORE DECODE (17 bytes)                      AFTER DECODE (17 bytes)
       * +----------+----------+----------------+      +----------+----------+----------------+
       * |  Length  | Header 1 | Actual Content |----->|  Length  | Header 1 | Actual Content |
       * | 0x00000C |  0xCAFE  | "HELLO, WORLD" |      | 0x00000C |  0xCAFE  | "HELLO, WORLD" |
       * +----------+----------+----------------+      +----------+----------+----------------+
       * </pre>
      從上的例子可以知道;lengthAdjustment(2) = 17- 12(00000C)-lengthFieldOffset(0) - lengthFieldLength(3);

      …….等等

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

    【其他文章推薦】

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

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

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

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

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

    ※試算大陸海運運費!

  • Thrift總結(四)Thrift實現雙向通信

    Thrift總結(四)Thrift實現雙向通信

    前面介紹過 Thrift 安裝和使用,介紹了Thrift服務的發布和客戶端調用,可以查看我之前的文章:

    但是,之前介紹的都是單向的客戶端發送消息,服務端接收消息。而客戶端卻得不到服務器的響應。

    那如果我們要實現雙向通信(即:客戶端發送請求,服務端處理返回,服務端發送消息,客戶端處理返回)的功能,該怎麼實現呢?

     

    其實在不涉及語言平台的制約,WebService或是webapi 就可以實現這種客戶端發起請求,服務端的處理的單向流程。

    然而,實際場景中,可能我們的某些業務需求,更需要服務端能夠響應請求並處理數據。下面我通過一個demo案例,介紹下Thrift 是如何實現雙向通信的。

     

    一、安裝Thrift

    這裏不再贅述,戳這裏查看我上篇文章的介紹:

     

    二、編寫Thrift IDL文件 

    編寫thrift腳本,命名為student.thrift  如下:

    service HelloWorldService{
        void SayHello(1:string msg);
    }

    生成service 的方法,之前的文章有介紹,這裏就不介紹了。

     

    三、編寫服務端代碼

    創建HelloThrift.Server 服務端工程,添加HelloWorldBidirectionServer類,HelloWorldBidirectionServer 實現了Iface接口用於接收客戶端消息,並有一個客戶端傳輸層對象集合用於記錄所有已連接的客戶端。

     public class HelloWorldBidirectionServer : HelloWorldBidirectionService.Iface
        {
            public void Run(int port)
            {
                try
                {
                    TServerTransport transport = new TServerSocket(port);
    
                    TTransportFactory transportFac = new TTransportFactory();
    
                    TProtocolFactory inputProtocolFactory = new TBinaryProtocol.Factory();
                    TThreadPoolServer server = new TThreadPoolServer(getProcessorFactory(), transport, transportFac, inputProtocolFactory);
    
                    server.Serve();
                }
                catch (Exception ex)
                {
                    Console.WriteLine(ex.Message);
                }
            }
    
            public static List<TTransport> TransportCollection = new List<TTransport>();
    
            public void SayHello(string msg)
            {
                Console.WriteLine(string.Format("{0:yyyy/MM/dd hh:mm:ss} 服務端接收到消息: {1}", DateTime.Now, msg));
            }
    
            public void SayToClient(string msg)
            {
                try
                {
                    foreach (TTransport trans in TransportCollection)
                    {
                        TBinaryProtocol protocol = new TBinaryProtocol(trans);
                        HelloWorldBidirectionService.Client client = new HelloWorldBidirectionService.Client(protocol);
                        //Thread.Sleep(1000);
                        client.SayHello(msg);
                        //Console.WriteLine("發給了客戶端喲");
                    }
                }
                catch (Exception ex)
                {
                    Console.WriteLine(ex.Message);
                }
            }
    
            public TProcessorFactory getProcessorFactory()
            {
                return new HelloWorldBidirectionProcessor();
            }
        }
    
        public class HelloWorldBidirectionProcessor : TProcessorFactory
        {
            public TProcessor GetProcessor(TTransport trans, TServer server = null)
            {
                if (trans.IsOpen)
                {
                    HelloWorldBidirectionServer.TransportCollection.Add(trans);
                    Console.WriteLine("客戶端連上。");
                }
    
                HelloWorldBidirectionServer srv = new HelloWorldBidirectionServer();
                return new global::HelloWorldBidirectionService.Processor(srv);
            }
        }

     

    四、編寫客戶端代碼

    首先創建HelloThrift.Client客戶端項目,添加接收服務端消息的類HelloWorldBidirectionClient,裏面只有一個實現Iface接口的方法:

      public class HelloWorldBidirectionClient
        {
            static HelloWorldBidirectionService.Client client = null;
            public void ConnectAndListern(int port, string ip = "127.0.0.1")
            {
                //Tsocket: TCP/IP Socket接口
                TSocket tSocket = new TSocket(ip, port);
                //消息結構協議
                TProtocol protocol = new TBinaryProtocol(tSocket);
                try
                {
                    if (client == null)
                    {
                        client = new global::HelloWorldBidirectionService.Client(protocol);
                        tSocket.Open();//建立連接
                        StartListern(tSocket);//啟動監聽線程
                    }
                }
                catch (Exception ex)
                {
                    Console.WriteLine(ex.Message);
                }
            }
    
            public void Say(string msg)
            {
                if (client != null)
                    client.SayHello(msg);
            }
    
            void StartListern(TSocket tSocket)
            {
                Thread t = new Thread(new ParameterizedThreadStart(Run));
                t.Start(tSocket);
            }
    
            public void Run(object tSocket)
            {
                HelloWorldBidirectionService.Processor process = new HelloWorldBidirectionService.Processor(new HelloWorldBidirectionFace());
    
                try
                {
                    while (process.Process(new TBinaryProtocol((TSocket)tSocket), new TBinaryProtocol((TSocket)tSocket)))
                    {
                        Console.WriteLine("消息接收完成,等下一波,阻塞中......");
                    }
                }
                catch (Exception ex)
                {
                    Console.WriteLine("連接斷開..." + ex.Message);
                }
            }
    
        }
        class HelloWorldBidirectionFace : HelloWorldBidirectionService.Iface
        {
            public void SayHello(string msg)
            {
                Console.WriteLine(string.Format("{0:yyyy/MM/dd hh:mm:ss} 收到服務端響應消息 {1}", DateTime.Now, msg));
    
            }
        }

     實現客戶端,ConnectAndListern方法可以與服務端建立連接,並開啟客戶端端口監聽來自服務端的信息。Say方法可將消息發送至服務端。

     

    五、測試

     測試效果如下:

     

     

     

    六、最後

      1. 關於使用Thrift 構建我們自己的rpc 的方法,這裏基本講完了。其他的方法本文就不再演示了,調用起來都是一樣。  

      2. 後續會簡單討論一下Thrift 框架的通信原理。

      3. 源代碼下載,

     

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

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

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

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

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

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

  • 美國蒙大拿州密蘇拉(Missoula)聯邦地區法官克里斯坦森(Dana Christensen)與環保人士及美國原住民站在同一陣線

    美國蒙大拿州密蘇拉(Missoula)聯邦地區法官克里斯坦森(Dana Christensen)與環保人士及美國原住民站在同一陣線,駁回美國魚類暨野生動物管理局(US Fish and Wildlife Service)將灰熊從瀕危物種名單除名的決定。

    環保人士主張,根據瀕臨滅絕物種保護法,對這些灰熊與蒙大拿州和下48州(Lower 48)的其他灰熊族群採取差別待遇,是生物學上靠不住且非法行為,法官也同意這類說法。

    環保人士說,儘管灰熊數量有所回升,倘若沒有受到聯邦持續保護,牠們的復育情況就會受到影響。此外,氣候變遷導致灰熊食物供給出現變化和人為死亡率高,也對灰熊生存構成威脅。

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

    【其他文章推薦】

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

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

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

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

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

    ※試算大陸海運運費!

  • 北極白鯨驚奇現身泰晤士河 再不回家專家憂安危

    摘錄自2018年9月26日中央社報導

    據英國各大媒體報導,一隻迷途的白鯨現身英國泰晤士河,第一次被目擊是25日在肯特郡(Kent)格雷夫森德(Gravesend)的泰晤士河段,當時白鯨正在駁船附近覓食,被暱稱為「班尼」(Benny),距離其原棲息地北極圈水域達數千公里。26日白鯨再次現身同個地點,引發是否迷航及可能遇險的擔憂。

    鯨豚保育協會(WDC)海洋哺乳類動物科學家羅特(Rob Lott)表示,這隻白鯨正受到監控,以防牠擱淺。「但白鯨停留在泰晤士河口的時間愈長,就愈令人擔心。」

    海洋生物保育慈善團體ORCA的保育學家巴比(Lucy Babey)表示:「這是白鯨出現在英國的最南端紀錄。」

    英國海洋生物救援組織表示,英國最後一次發現白鯨蹤跡是3年前在北英格蘭的諾森伯蘭(Northumberland)海岸,以及北愛爾蘭,但極為罕見。

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

    【其他文章推薦】

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

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

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

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

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

  • 特斯拉擴產,2018目標年產50萬輛車

    特斯拉擴產,2018目標年產50萬輛車

    因應龐大需求,電動車龍頭廠商特斯拉(Tesla)表示將積極擴產,目標在2018年達成年產量50萬輛車。

    特斯拉2015年的產量約為5萬輛,但Model 3甫一推出就獲得40萬輛的超大筆訂單。為滿足這些預購車主的需求,特斯拉表示將積極擴產五倍,將原先2020年年產量50萬輛的目標提前到2018年實現,從中可望取得可觀的美國政府補助。而2016年,特斯拉預計將交車8~9萬輛。

    特斯拉執行長Elon Musk於5月4日發表公司經營狀況時表示已將自己的辦公桌「移至生產線尾端,整個團隊都全力以赴」,彰顯急速擴張的決心。但他也坦言這個目標極具挑戰,若能成功,全球電動車市場將產生結構性的變化。

    特斯拉財報:首季虧損

    特斯拉在4日所發表的財報顯示,該公司今年第一季營收較去年同期大幅增加22%,為11.5億美元。但由於公司正在高速擴張,使營業費用比去年同期增加四成,至5億美元;加上Model X交車進度略有落後,使首季出現虧損,淨損為2.83億美元,高於去年第一季的淨損1.54億美元。扣除非常態性項目後,相當於每股虧損0.57美元。

    未見起色的財報直接衝擊特斯拉股情。4日當天,特斯拉股價一度下滑4.2%,為222.56美元。不過,當Musk再宣布目標於2018年擴產至50萬輛後,股價再度回彈,最高漲至每股239.72美元。

    特斯拉能否如期實現年產能50萬輛的目標,仍待觀察。近期有兩位高階副總即將離職,且特斯拉營運燒錢速度快於盈利回收,也是一隱憂。但特斯拉表示目前仍不需要依靠融資。

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

    【其他文章推薦】

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

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

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

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

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

    ※試算大陸海運運費!

  • 中國新能源汽車零部件發展規劃成果集中亮相北京10月車展

    中國新能源汽車零部件發展規劃成果集中亮相北京10月車展

    2016年10月13—16日,以“選擇·行動——未來從現在開始”為主題的2016(第四屆)節能與新能源汽車產業發展規 劃成果展覽會、中國國際汽車新能源及技術應用展覽會將在北京國家會議中心舉辦。展會由中國國家工業和資訊化部支援,科學技術部、中國國際貿易促進委員會批准,是貫徹落實國務院《節能與 新能源汽車產業發展規劃(2012—2020年)》,推動中國節能與新能源汽車產業發展的重要舉措之一。本屆展會將全方面覆蓋節能環保汽車、純電動 汽車、混合動力汽車、插電式混合動力和燃料電池汽車,以及電池、電機、電控等關鍵零部件和充電設施等產品,眾多國際知名汽車企業、零部件和充電設施供應 商將攜旗下新品參展。

    汽車產業是國民經濟的重要支柱產業,也是實現新一輪技術革命和產業變革的重要載體。中國汽車產業快速發展,產銷增速連續七年居世界第一位,新能源汽車產業初具規模,15年中國已成為全球最大的新能源汽車生產以及銷售市場.

    但同時,據中國國家863“節能與新能源汽車”重大項目監理諮詢專家組王秉剛調研,中國目前具有生產傳統汽車ABS系統能力的工廠主要有亞太、元豐與伯特利等幾家,他們是未來最有希望成為中國自主電動汽車制動系統的生產企業,與國外同類企業相比,他們規模都不大,在同類產品的市場佔有率不足5%,在年產量達2000多萬輛的中國汽車產業裡,如此重要的底盤部件,自主企業生產的比例低到差不多可以忽略不計的程度。中國汽車產業空心化可見一斑!這裡舉的僅是制動系統的例子,其它零部件也存在類似情況,就連備受關注的動力電池也未必樂觀,已經有國外大電池企業,在部分地方政府優惠政策的支援下在國內建廠。

    中國新能源汽車零部件企業正加強關鍵核心技術研發,推進技術創新,竭盡全力避免傳統汽車產業存在的核心技術缺失、產業空心化現象,在新能源汽車行業重蹈覆轍。據車展合作單位盛大超越展覽公司嶽巍介紹,“新能源汽車零部件主要包括電機、驅動控制系統和電源系統,目前中國在新能源汽車零部件上的技術創新有序推進,整體研發水平不斷提升,中國企業的電機及驅動控制系統在新能源客車(如宇通、安凱、中通等)及乘用車(比亞迪、北汽、江淮等)上已得到廣泛應用,這些成果大部分將在北京10月新能源汽車展上展出,中國的電機及驅動控制系統參展企業包括:德沃仕、英威騰、中冶南方、超同步、合普動力、合康動力、福工動力、八達等。這些企業有的具有國外及科研院所的專業背景,有的是從電梯控制、機床控制發展而來。比如合普動力是低速電動車電機驅動控制的領軍企業,隨著高速新能源汽車市場的不斷成長,已開發了電動客車及乘用車電機驅動控制產品”。

    盛大超越展覽公司電池及BMS展區經理耿忱也對中國企業抱有積極信心,他說,”北京10月新能源汽車成果展上的中國電池企業非常給力,沃特瑪、寧德時代、中航鋰電、力神電池、比克電池、萬向、山東威能、神工光電、內蒙古稀奧科、實聯長宜等知名電池企業展示了車用動力電池技術及市場應用的發展成果,部分電池展商還帶來了實際應用整車配合展示”。他還表示,“環宇賽爾、科隆集團、均勝普瑞等一批新的中國電池及BMS展商將有望加盟2016北京10月新能源汽車成果展,給主機廠提供更加前沿和多元化的產品解決方案“。

    展會相關

    2016中國國際汽車新能源及技術應用展覽會
    節能與新能源汽車產業發展規劃成果展覽會
    行業地位:中國最大高速新能源汽車展
    展覽規模:30,000平方米(2015年)
    觀眾數量:60,000人次(2015年)
    展覽週期:每年一屆,2013年首屆
    連絡人:岳巍 先生
    手機:+86 135 5286 5285
    展會網址:
     

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

    【其他文章推薦】

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

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

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

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

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

  • abp(net core)+easyui+efcore實現倉儲管理系統——ABP WebAPI與EasyUI結合增刪改查之一(二十七)

    abp(net core)+easyui+efcore實現倉儲管理系統——ABP WebAPI與EasyUI結合增刪改查之一(二十七)

     



     

    一.前言

           通過前面的文章的學習,我們已經有實現了傳統的ASP.NET Core MVC+EasyUI的增刪改查功能。本篇文章我們要實現了使用ABP提供的WebAPI方式+EasyUI來實現增刪改查的功能。本文中我們將不在使用DataGrid表格控件,而是使用樹形表格(TreeGrid)控件。

    二、樹形表格(TreeGrid)介紹

           我先上圖,讓我們來看一下功能完成之後的組織管理信息列表頁面。如下圖。

           這個組織管理列表頁面使用TreeGrid來實現的。我們接下來介紹一下TreeGrid。

         首先、在定義TreeGrid時有兩個屬性必須要有一個是idField,這個要唯一;另一個是treeField的定義,這是樹節點的值,必須要有。 

         其次、easyui加載treegrid的json數據格式有三種,我就介紹我常用的這種。其他兩種方式,請查看easyui的相關文檔。

      {"total":7,"rows":[
    
               {"id":1,"name":"All Tasks","begin":"3/4/2010","end":"3/20/2010","progress":60,"iconCls":"icon-ok"},      
    {"id":2,"name":"Designing","begin":"3/4/2010","end":"3/10/2010","progress":100,"_parentId":1,"state":"closed"},
    {"id":21,"name":"Database","persons":2,"begin":"3/4/2010","end":"3/6/2010","progress":100,"_parentId":2},
    {"id":22,"name":"UML","persons":1,"begin":"3/7/2010","end":"3/8/2010","progress":100,"_parentId":2}, {"id":23,"name":"Export Document","persons":1,"begin":"3/9/2010","end":"3/10/2010","progress":100,"_parentId":2},
    {"id":3,"name":"Coding","persons":2,"begin":"3/11/2010","end":"3/18/2010","progress":80},
    {"id":4,"name":"Testing","persons":1,"begin":"3/19/2010","end":"3/20/2010","progress":20} ],"footer":[ {"name":"Total Persons:","persons":7,"iconCls":"icon-sum"} ]}

         下面介紹一下上面數據中的幾個重要屬性:

         1)  _parentId :字段_parentId必不可少,且名稱唯一。記得前面有“_” ,他是用來記錄父級節點,沒有這個屬性,是沒法展示父級節點 其次就是這個父級節點必須存在,不然信息也是展示不出來,在後台遍歷組合的時候,如果父級節點不存在或為0時,此時 _parentId 應該不賦值,或設為“”。如果賦值 “0” 則在表格中不显示數據。

        2) state:是否展開

         3) checked:是否選中(用於複選框)

        4) iconCls:選項前面的圖標,如果自己不設定,父級節點默認為文件夾圖標,子級節點為文件圖標

     

        下面我們來開始實現組織管理頁面的相關功能。首先我們要創建一個組織信息實體。

    三、創建Org實體

           1. 在Visual Studio 2017的“解決方案資源管理器”中,右鍵單擊“ABP.TPLMS.Core”項目的“Entitys”文件夾,在彈出菜單中選擇“添加” >

     > “類”。 將類命名為 Org,然後選擇“添加”。

          2.創建Org類繼承自Entity<int>,通過實現審計模塊中的IHasCreationTime來實現保存創建時間。根據TreeGrid所需要的數據格式的要求。代碼如下:

    using Abp.Domain.Entities;
    using Abp.Domain.Entities.Auditing;
    using System;
    using System.Collections.Generic;
    using System.ComponentModel.DataAnnotations;
    using System.Text;
     
    
    namespace ABP.TPLMS.Entitys
    {
    
       public partial class Org : Entity<int>, IHasCreationTime
    {
          int m_parentId = 0;
            public Org()
            {
    
                this.Id = 0;
                this.Name = string.Empty;
                this.HotKey = string.Empty;
                this.ParentId = 0;
                this.ParentName = string.Empty;
    
                this.IconName = string.Empty;
    
                this.Status = 0;
    
                this.Type = 0;
    
                this.BizCode = string.Empty;
                this.CustomCode = string.Empty;
                this.CreationTime = DateTime.Now;
                this.UpdateTime = DateTime.Now;
                this.CreateId = 0;
    
                this.SortNo = 0;
    
            }
    
            [Required]
            [StringLength(255)]
            public string Name { get; set; }
    
            [StringLength(255)]
            public string HotKey { get; set; }
    
            public int ParentId { get { return m_parentId; } set { m_parentId = value; } }
    
            [Required]
            [StringLength(255)]
            public string ParentName { get; set; }
    
            public bool IsLeaf { get; set; }
    
            public bool IsAutoExpand { get; set; }
    
            [StringLength(255)]
            public string IconName { get; set; } 
    
            public int Status { get; set; }
    
            public int Type { get; set; }
    
            [StringLength(255)]
            public string BizCode { get; set; }
    
            [StringLength(100)]
            public string CustomCode { get; set; }
    
            public DateTime CreationTime { get; set; }
            public DateTime UpdateTime { get; set; }
            public int CreateId { get; set; }
    
            public int SortNo { get; set; }
    public int? _parentId {
                get {
                    if (m_parentId == 0)                
    
                    {
                        return null;
                    }
    
                    return m_parentId;
                }           
    
            }
        }
    }

          3.定義好實體之後,我們去“ABP.TPLMS.EntityFrameworkCore”項目中的“TPLMSDbContext”類中定義實體對應的DbSet,以應用Code First 數據遷移。添加以下代碼

     

    using Microsoft.EntityFrameworkCore;
    using Abp.Zero.EntityFrameworkCore;
    using ABP.TPLMS.Authorization.Roles;
    using ABP.TPLMS.Authorization.Users;
    using ABP.TPLMS.MultiTenancy;
    using ABP.TPLMS.Entitys;
     
    
    namespace ABP.TPLMS.EntityFrameworkCore
    {
    
        public class TPLMSDbContext : AbpZeroDbContext<Tenant, Role, User, TPLMSDbContext>
        {
    
            /* Define a DbSet for each entity of the application */
        
            public TPLMSDbContext(DbContextOptions<TPLMSDbContext> options)
                : base(options)
    
            {
            }
    
            public DbSet<Module> Modules { get; set; }
            public DbSet<Supplier> Suppliers { get; set; }
      public DbSet<Cargo> Cargos { get; set; }
              public DbSet<Org> Orgs { get; set; }
    
        }
    }

     

     

     

          4.從菜單中選擇“工具->NuGet包管理器器—>程序包管理器控制台”菜單。

         5. 在PMC中,默認項目選擇EntityframeworkCore對應的項目后。輸入以下命令:Add-Migration AddEntityOrg,創建遷移。如下圖。

     

           6. 在上面的命令執行完畢之後,創建成功后,會在Migrations文件夾下創建時間_AddEntityOrg格式的類文件,這些代碼是基於DbContext指定的模型。如下圖。

     

         7.在程序包管理器控制台,輸入Update-Database,回車執行遷移。執行成功后,如下圖。

     

         8. 在SQL Server Management Studio中查看數據庫,Orgs表創建成功。

     

     

     

     

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

    【其他文章推薦】

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

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

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

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

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

    ※試算大陸海運運費!

  • 前端每周學習分享–第12期

    1.VuePress

    大家看過不少Vue.js及其子項目的文檔,一定發現了它們風格完全一致,界面清爽,讀起來很舒服,它們都使用了vuepress。

    VuePress是尤大為了支持 Vue 及其子項目的文檔需求而寫的一個靜態網站生成工具,廣泛用於編寫技術文檔 ,可以部署在github上做個人博客。

    原理:

    在構建過程中,會創建應用程序的服務器渲染版本,通過訪問每個路由,來渲染相應的 HTML。

    其中, 每個 markdown 文件都使用 編譯為 HTML,然後作為 Vue 組件的模板進行處理。這允許你直接在 markdown 文件中使用 Vue,在需要嵌入動態內容時,這種使用方式非常有用。

    十分實用的特性:

    • md文件可內嵌vue代碼
    • 可自定義主題
    • 利用service worker做離線緩存
    • 多語言支持
    • 基於git的最近更新

    官方文檔:

    快速搭建:

    2.WebWorker

    web worker是運行在後台的jacvascript,利用類似線程的消息傳遞實現并行,獨立於其他腳本,不會影響頁面的性能。

    web worker能夠長時間運行,有理想的啟動性能以及理想的內存消耗。

    worker 創建后,它可以向它的創建者指定的事件監聽函數傳遞消息,這樣該worker生成的所有任務都會接收到這些消息。

    webworker有專用線程dedicated worker(單窗口專用),sharedWorker(可多窗口共享),以及後來的service worker(目前瀏覽器支持程度還不高)。

    2.1.dedicated worker

    使用方法:

    worker線程里監聽onmessage,

    頁面線程里創建worker對象:const myworker = new Worker("worker.js")

    發送消息:postMessage(msg)

    接受消息:onmessage = function(e){const msg = e.data}

    msg的數據格式自行定義。

    終止worker:myworker.terminate()

    例如下面的示例,worker會接收頁面上輸入的兩個数字,計算出乘積后返回結果。

    worker.js

    onmessage = function(e) {
      console.log('Worker: Message received from main script');
      let result = e.data[0] * e.data[1];
      if (isNaN(result)) {
        postMessage('Please write two numbers');
      } else {
        let workerResult = 'Result: ' + result;
        console.log('Worker: Posting message back to main script');
        postMessage(workerResult);
      }
    }

    index.html里

    const first = document.querySelector('#number1');
    const second = document.querySelector('#number2');
    const result = document.querySelector('.result');
    if (window.Worker) {
        const myWorker = new Worker("worker.js");
    
        first.onchange = function() {
          myWorker.postMessage([first.value, second.value]);
          console.log('Message posted to worker');
        }
    
        second.onchange = function() {
          myWorker.postMessage([first.value, second.value]);
          console.log('Message posted to worker');
        }
    
        myWorker.onmessage = function(e) {
            result.textContent = e.data;
            console.log('Message received from worker');
        }
    } else {
        console.log('Your browser doesn\'t support web workers.')
    }

    2.2.shared worker

    共享進程可以連接到多個不同的頁面,這些頁面必須屬於相同的域(相同的協議,主機以及端口)

    在火狐中,共享進程不能在私有與公共文檔間進行共享。

    SharedWorker.port返回一個MessagePort對象,用來進行通信和對共享進程進行控制。

    創建共享進程對象:const myWorker = new SharedWorker("worker.js");

    獲取端口:

    發送消息:myWorker.port.postMessage(msg)

    接收消息:myWorker.port.onmessage = function(e) {const msg = e.data}

    worker線程獲取端口:onconnect = function(e) {const port = e.ports[0]}

    啟動端口:port.start()

    2.3.service worker

    Service Worker 可以理解為一個介於客戶端和服務器之間的一個代理服務器 ,常用於做離線資源緩存

    出於對安全問題的考慮,Service Worker 只能被使用在 https 或者本地的 localhost 環境下。

    暫時沒有仔細學這塊,可以閱讀。

    參考文章:

    3.代碼相關

    3.1.元素內文本垂直居中

    已知元素高度的話,可以設置line-height:元素高度.

    如果元素高度未知,就不能使用line-height了。

    有人會想使用line-height:100%,會發現這是不行的,這個百分比是相對當前字體尺寸,而不是元素高度。

    我使用了flex布局實現

        display: flex;
        align-items:center;
        justify-content:center;

    還可以設置padding來使文本看起來垂直居中

    padding: 50px 20px;

    3.2.微信小程序自定義placeholder的隱藏時機

    在一個searchBar組件中,有一個自定的placeholder如下:

    <!-- <view
    ​        wx:if="{{!inputValue.length}}"
    ​        class="placeholder" >
    ​        {{placeholder}}
    ​     </view> -->

    原生的placeholder不是在觸發bindinput時隱藏,而是在輸入鍵盤按鈕點擊時。使用inputValue.length來判斷显示自定義的placeholder會在某些輸入法中導致拼音預覽和自定義placeholder重疊(因為拼音显示的時候value值還沒變)

    最後選擇棄用這個自定義placeholder,使用input組件的placeholder屬性,並使用placeholder-class來設置它的樣式。

    3.3.關於微信小程序原生組件的坑

    原生組件有camera、canvas、input (僅在focus時表現為原生組件) 、live-player、live-pusher、map、textarea、video、cover-view、cover-image。

    所以當你用canvas畫圖表、使用地圖、播放視頻甚至做文本輸入時,都是可能遇到相關坑點的。

    1. 關於原生組件、組件之間的層級關係、

    ​ 原生組件的層級始終高於普通組件,不論普通組件的z-index設置了多少。

    ​ 后插入的原生組件可以覆蓋之前的原生組件。

    ​ 原生組件之間的相對層級關係可以通過z-index來調整。

    ​ 原生組件會遮擋vconsole彈出的調試面板。

    ​ cover-view和cover-image可以覆蓋在部分原生組件上。

    1. cover-view的使用

    ​ cover-view在做地圖、畫布、視頻上的彈出層時是會用到的,但它有很多使用限制。

    ​ cover-view只能內嵌cover-view、cover-image、button,其他元素在真機上就會被cover-view給覆蓋住,如果想內嵌radio、picker等就只能自己用這3個可內嵌的元素來實現。

    ​ cover-view不支持iconfont,也不支持單邊border、background-imageshadowoverflow: visible等。

    1. input的使用

    ​ input在不聚焦時是佔位元素,會被原生組件遮擋,聚焦時才使用原生組件渲染。這就會出現input設置了更高的z-index,不聚焦時仍會被其他原生組件遮住。

    ​ 要解決這個問題,可以使用textarea來代替input。

    ​ 我的一個解決方案是,加一個標誌位來記錄input是否聚焦,當不聚焦時,显示一個承載value值的cover-view(它需要綁定一個觸發聚焦的點擊事件),聚焦時,就显示input組件。

    3.4.多個標籤頁之間的通信方案

    1. 使用websocket

    2. 使用localstorage或者cookie

    3. 使用sharedworker

    我遇到的問題是需要在新窗口打開當前網站的新窗口時,能繼承上一個窗口的vuex的狀態樹里的某些數據。這不需要和服務器打交道,最好就在本地。

    最後使用localstorage來做,在跳轉新窗口前更新localstorage,在新窗口根組件掛載時取出數據。

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

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

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

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

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

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

  • 工信部第285批新車公示218款新能源入選

    根據《中華人民共和國行政許可法》和《國務院對確需保留的行政審批專案設定行政許可的決定》的規定,工信部日前將許可的汽車、摩托車、三輪汽車和低速貨車生產企業及產 品(第285批)予以了公告,共有218款新能源車型入選。進入該公告的新能源汽車可開展生產銷售,但是要獲得補貼,還需再獲得《新能源汽車推廣應用推薦車型目錄》准入。

    純電動轎車/乘用車方面,北汽、長城、禦捷馬、卡威、吉利等12款車型入選。

    插電式乘用車方面,比亞迪、寶馬、之諾、上汽等7款車型入選。

    純電動客車方面,安凱、江淮、安源、北奔、北方、福田、比亞迪、白雲、五菱、陸地方舟、尼歐凱、友誼、青年、海格、開沃、依維柯、飛燕、大通、象牌、野馬、華新、金龍、金旅、宇通、黃河、中通、中植汽車、穗通等28個品牌75款車型入選。

    插電式混動客車方面, 安凱、海格、易聖達、金龍、金旅、宇通6個品牌21款車型入選。

    新能源專用車方面,福田、北京、大運、黃海、東風、華神、揚子江、東風、福建、環球、藍速、田野、中悅 、卡威、陸地方舟、青年曼、康迪、五菱、暢達、躍進、金龍、凱馬、時風、太行成功、東風、金杯、邢牛、海德、解放、神州、宇通、長帆汽車、迪馬、炫虎等33個品牌103款車型入選。

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

    【其他文章推薦】

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

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

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

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

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

    ※試算大陸海運運費!