TLS1.2協議設計原理

目錄

  • 前言
  • 為什麼需要TLS協議
  • 發展歷史
  • 協議設計目標
  • 記錄協議
    • 握手步驟
    • 握手協議
      • Hello Request
      • Client Hello
      • Server Hello
      • Certificate
      • Server Key Exchange
      • Certificate Request
      • Server Hello Done
      • Client Certificate
      • Client Key Exchange
      • Certificate Verify
      • Finished
    • 改變密碼標準協議
    • 警報協議
    • 應用程序數據協議
  • 結語
  • 參考文獻

前言

最近對TLS1.2協議處理流程進行了學習及實現,本篇文章對TLS1.2的理論知識和處理流程進行分析,TLS協議的實現建議直接看The Transport Layer Security (TLS) Protocol Version 1.2

為什麼需要TLS協議

通常我們使用TCP協議或UDP協議進行網絡通信。TCP協議提供一種面向連接的、可靠的字節流服務。但是TCP並不提供數據的加密,也不提供數據的合法性校驗。

我們通常可以對數據進行加密、簽名、摘要等操作來保證數據的安全。目前常見的加密方式有對稱加密和非對稱加密。使用對稱加密,雙方使用共享密鑰。

但是對於部署在互聯網上的服務,如果我們為每個客戶端都使用相同的對稱加密密鑰,那麼任何人都可以將數據解密,那麼數據的隱私性將得不到保障。

如果我們使用非對稱密鑰加密,客戶端使用服務端的公鑰進行公鑰加密,服務端在私鑰不泄露的情況下,只有服務端可以使用私鑰可以對數據進行解密,從而保障數據的隱私性,但是非對稱加密比對稱加密的成本高得多。

我們可以採用對稱加密和非對稱加密相結合的方式實現數據的隱私性的同時性能又不至於太差。

  1. 首先客戶端和服務端通過一個稱為密鑰交換的流程進行密鑰協商及交換密鑰所系的信息。
  2. 通過公鑰加密對稱密鑰保證對稱密鑰的安全傳輸。
  3. 服務端使用私鑰解密出對稱密鑰。
  4. 最後雙方使用協商好的對稱密鑰對數據進行加解密。

TLS協議就是實現了這一過程安全協議。TLS是在TCP之上,應用層之下實現的網絡安全方案。在TCP/IP四層網絡模型中屬於應用層協議。TLS協議在兩個通信應用程序之間提供數據保密性和數據完整性,另外還提供了連接身份可靠性方案。

UDP則使用DTLS協議實現安全傳輸,和TLS協議類似。

發展歷史

TLS協議的前身SSL協議是網景公司設計的主要用於Web的安全傳輸協議,這種協議在Web上獲得了廣泛的應用。

  • 1994年,網景公司設計了SSL協議的1.0版,因為存在嚴重的安全漏洞,未公開。
  • 2.0版本在1995年2月發布,但因為存在數個嚴重的安全漏洞(比如使用MD5摘要、握手消息沒有保護等),2011年RFC 6176 標準棄用了SSL 2.0。
  • 3.0版本在1996年發布,是由網景工程師完全重新設計的,同時寫入RFC,較新版本的SSL/TLS基於SSL 3.0。2015年,RFC 7568 標準棄用了SSL 3.0。
  • 1999年,IETF將SSL標準化,並將其稱為TLS(Transport Layer Security)。從技術上講,TLS 1.0與SSL 3.0的差異非常微小。
  • TLS 1.1在RFC 4346中定義,於2006年4月發表,主要修復了CBC模式的BEAST攻擊等漏洞。

    微軟、Google、蘋果、Mozilla四家瀏覽器業者將在2020年終止支持TLS 1.0及1.1版。

  • TLS 1.2在RFC 5246中定義,於2008年8月發表,添加了增加AEAD加密算法,如支持GCM模式的AES。
  • TLS 1.3在RFC 8446中定義,於2018年8月發表,砍掉了AEAD之外的加密方式。

協議設計目標

  1. 加密安全:TLS應用於雙方之間建立安全連接,通過加密,簽名,數據摘要保障信息安全。
  2. 互操作性:程序員在不清楚TLS協議的情況下,只要對端代碼符合RFC標準的情況下都可以實現互操作。
  3. 可擴展性:在必要時可以通過擴展機制添加新的公鑰和機密方法,避免創建新協議。
  4. 相對效率:加密需要佔用大量CPU,尤其是公鑰操作。TLS協議握手完成后,通過對稱密鑰加密數據。TLS還集成了會話緩存方案,減少需要從頭建立連接的情況。

記錄協議

TLS協議是一個分層協議,第一層為TLS記錄層協議(Record Layer Protocol),該協議用於封裝各種高級協議。目前封裝了4種協議:握手協議(Handshake Protocol)、改變密碼標準協議(Change Cipher Spec Protocol)、應用程序數據協議(Application Data Protocol)和警報協議(Alert Protocol)。

Change Cipher Spec Protocol在TLS1.3被去除。

記錄層包含協議類型、版本號、長度、以及封裝的高層協議內容。記錄層頭部為固定5字節大小。

在TLS協議規定了,如接收到了未定義的協議協議類型,需要發送一個unexpected_message警報。

握手步驟

  • 當客戶端連接到支持TLS協議的服務端要求創建安全連接並列出了受支持的算法套件(包括加密算法、散列算法等),握手開始。
  • 服務端從客戶端的算法套件列表中指定自己支持的一個算法套件,並通知客戶端,若沒有則使用一個默認的算法套件。
  • 服務端發回其数字證書,此證書通常包含服務端的名稱、受信任的證書頒發機構(CA)和服務端的公鑰。
  • 客戶端確認其頒發的證書的有效性。
  • 為了生成會話密鑰用於安全連接,客戶端使用服務端的公鑰加密隨機生成的密鑰,並將其發送到服務端,只有服務端才能使用自己的私鑰解密。
  • 利用隨機數,雙方生成用於加密和解密的對稱密鑰。這就是TLS協議的握手,握手完畢后的連接是安全的,直到連接(被)關閉。如果上述任何一個步驟失敗,TLS握手過程就會失敗,並且斷開所有的連接。

握手協議

TLS 握手協議允許服務端和客戶端相互進行身份驗證,並在應用程序協議傳輸或接收其第一個字節數據之前協商協議版本、會話ID、壓縮方法、密鑰套件、以及加密密鑰。

完整的TLS握手流程,流程如下

  Client                                                       Server

  ClientHello                  -------->
                                                          ServerHello
                                                          Certificate*
                                                    ServerKeyExchange*
                                                  CertificateRequest*
                                <--------              ServerHelloDone
  Certificate*
  ClientKeyExchange
  CertificateVerify*
  [ChangeCipherSpec]
  Finished                     -------->
                                                    [ChangeCipherSpec]
                                <--------                     Finished
  Application Data             <------->             Application Data
  • 表示可選步驟或與實際握手情況相關。比如重建已有連接,服務端無需執行Certificate,再比如使用RSA公鑰加密時,無需ServerKeyExchange。
    握手協議消息必須按上面流程的發送數據進行發送,否則需要以致命錯誤告知對方並關閉連接。

完整的握手流程有時候也被稱為2-RTT流程,即完整的握手流程需要客戶端和服務端交互2次才能完成握手。

交互應用請求到響應的交互時間被稱為往返時間(Round-trip Time)

握手協議的結構如下,其中協議頭的ContentType固定為22,接下來是TLS版本號,TLS1.2為0303,最後是用2字節表示長度。

握手協議類型包含以下:

  • hello_request:0
  • client_hello:1
  • server_hello:2
  • certificate:3
  • server_key_exchange :12
  • certificate_request:13
  • server_hello_done:14
  • certificate_verify:15
  • client_key_exchange:16
  • finished:20

Hello Message是具體的握手協議類型內容,不同協議內容有所不同。

Hello Request

Hello Request消息用於客戶端與服務端重新協商握手,該消息可能由服務端在任何時刻發送。Hello Request消息非常簡單,沒有其他冗餘信息。

當客戶端收到了服務端的Hello Request時可以有以下4種行為:

  • 當客戶端正在協商會話,可以忽略該消息。
  • 若客戶端未在協商會話但不希望重新協商時,可以忽略該消息。
  • 若客戶端未在協商會話但不希望重新協商時,可以發送no_renegotiation警報。
  • 若客戶端希望重新協商會話,則需要發送ClientHello重新進行TLS握手。

服務端發送完HelloRequest消息,可以有以下幾種行為:

  • 服務端發送了HelloRequest消息,但未收到ClientHello時,可以通過致命連接警報關閉連接。
  • 服務端發送了HelloRequest消息,必須等待握手協商處理完成后才可以繼續處理應用數據消息。

Finished和Certificate的握手消息驗證不包括該消息的hash。

Client Hello

當客戶端首次與服務端建立連接或需要重新協商加密握手會話時,需要將Client Hello作為第一條消息發送給服務端。

Client Hello消息包含了許多重要信息,包括客戶端版本號、客戶端隨機數、會話ID、密鑰套件、壓縮方式、擴展字段等。

  • 客戶端版本號:客戶端支持的最新TLS版本號,服務端會根據該協議號進行協議協商。
  • 32位隨機數:客戶端生成的32位隨機數。前4位是Unix時間戳,該時間戳為1970年1月1日0點以來的秒數。不過TLS並沒有強制要求校驗該時間戳,因此允許定義為其他值。後面28位為一個隨機數。

    通過前4字節填寫時間方式,有效的避免了周期性的出現一樣的隨機數。使得”隨機”更加”隨機”。
    在TLS握手時,客戶端和服務端需要協商數據傳輸時的加密密鑰。為了保證加密密鑰的安全性。密鑰需要通過客戶端和服務端一起生成。客戶端和服務端都提供一個32位的隨機數,通過該隨機數使用基於HMAC的PRF算法生成客戶端和服務端的密鑰。

  • 會話ID:用於表示客戶端和服務端之間的會話。實際的會話ID是由服務端定義的,因此即使是新的連接,服務端返回的會話ID可能也會和客戶端不一致,由於會話ID是明文傳輸的,因此不能存放機密信息。
    • 若會話ID是新的,則客戶端和服務端需要建立完整的TLS握手連接流程。
    • 若會話ID是較早連接的會話ID,則服務端可以選擇無需執行完整的握手協議。
  • 算法套件:客戶端將支持的加密算法組合排列后發送給服務端,從而和服務端協商加密算法。服務端根據支持算法在ServerHello返回一個最合適的算法組合。
    算法套件的格式為TLS_密鑰交換算法_身份認證算法_WITH_對稱加密算法_消息摘要算法,比如TLS_DHE_RSA_WITH_AES_256_CBC_SHA256,密鑰交換算法是DHE,身份認證算法是RSA,對稱加密算法是AES_256_CBC,消息摘要算法是SHA256,由於RSA又可以用於加密也可以用於身份認證,因此密鑰交換算法使用RSA時,只寫一個RSA,比如TLS_RSA_WITH_AES_256_CBC_SHA256
  • 壓縮方式:用於和服務端協商數據傳輸的壓縮方式。由於TLS壓縮存在安全漏洞,因此在TLS1.3中已經將TLS壓縮功能去除,TLS1.2算法也建議不啟用壓縮功能。
  • 擴展字段:可以在不改變底層協議的情況下,添加附加功能。客戶端使用擴展請求其他功能,服務端若不提供這些功能,客戶端可能會中止握手。對於擴展字段的詳細定義可以看Transport Layer Security (TLS) Extensions

客戶端發送完 ClientHello 消息后,將等待 ServerHello 消息。 服務端返回的任何握手消息(HelloRequest 除外)將被視為致命錯誤。

Server Hello

當服務端接收到ClientHello,則開始TLS握手流程, 服務端需要根據客戶端提供的加密套件,協商一個合適的算法簇,其中包括對稱加密算法、身份驗證算法、非對稱加密算法以及消息摘要算法。若服務端不能找到一個合適的算法簇匹配項,則會響應握手失敗的預警消息。

  • 版本號:服務端根據客戶端發送的版本號返回一個服務端支持的最高版本號。若客戶端不支持服務端選擇的版本號,則客戶端必須發送protocol_version警報消息並關閉連接。

    若服務端接收到的版本號小於當前支持的最高版本,且服務端希望與舊客戶端協商,則返回不大於客戶端版本的服務端最高版本。
    若服務端僅支持大於client_version的版本,則必須發送protocol_version警報消息並關閉連接。
    若服務端收到的版本號大於服務端支持的最高版本的版本,則必須返回服務端所支持的最高版本。

  • 32位隨機數:服務端生成的32位隨機數,生成方式和客戶端一樣。服務端生成隨機數的可以有效的防範中間人攻擊,主要是通過防止重新握手后的重放攻擊。

  • 會話ID:用於表示客戶端和服務端之間的會話。若客戶端提供了會話ID,則可以校驗是否與歷史會話匹配。

    • 若不匹配,則服務端可以選擇直接使用客戶端的會話ID或根據自定義規則生成一個新的會話ID,客戶端需要保存服務端返回的會話ID當作本次會話的ID。

    • 若匹配,則可以直接執行1-RTT握手流程,返回ServerHello后直接返回ChangeCipherSpecFinished消息。

          Client                                                Server
      
          ClientHello                   -------->
                                                          ServerHello
                                                      [ChangeCipherSpec]
                                          <--------             Finished
          [ChangeCipherSpec]
          Finished                      -------->
          Application Data              <------->     Application Data
      

      在Finished消息中和完整握手一樣都需要校驗VerifyData。

  • 算法套件:服務端根據客戶端提供的算法套件列表和自己當前支持算法進行匹配,選擇一個最合適的算法組合,若沒有匹配項,則使用默認的TLS_RSA_WITH_AES_128_CBC_SHA

    TLS1.2協議要求客戶端和服務端都必須實現密碼套件TLS_RSA_WITH_AES_128_CBC_SHA

  • 壓縮方式:用於和服務端協商數據傳輸的壓縮方式。由於TLS壓縮存在安全漏洞,因此在TLS1.3中已經將TLS壓縮功能去除,TLS1.2算法也建議不啟用壓縮功能。

  • 擴展字段:服務端需要支持接收具有擴展和沒有擴展的ClientHello。服務端響應的擴展類型必須是ClientHello出現過才行,否則客戶端必須響應unsupported_extension嚴重警告並中斷握手。

RFC 7568要求客戶端和服務端握手時不能發送{3,0}版本,任何收到帶有協議Hello消息的一方版本設置為{3,0}必須響應protocol_version警報消息並關閉連接。

通過ClientHelloServerHello,客戶端和服務端就協商好算法套件和用於生成密鑰的隨機數。

Certificate

假設客戶端和服務端使用默認的TLS_RSA_WITH_AES_128_CBC_SHA算法,在ServerHello完成后,服務端必須將本地的RSA證書傳給客戶端,以便客戶端和服務端之間可以進行非對稱加密保證對稱加密密鑰的安全性。
RSA的證書有2個作用:

  • 客戶端可以對服務端的證書進行合法性進行校驗。
  • Client Key Exchange生成的pre-master key進行公鑰加密,保證只有服務端可以解密,確保對稱加密密鑰的安全性。

發送給客戶端的是一系列證書,服務端的證書必須排列在第一位,排在後面的證書可以認證前面的證書。
當客戶端收到了服務端的ServerHello時,若客戶端也有證書需要服務端驗證,則通過該握手請求將客戶端的證書發送給服務端,若客戶端沒有證書,則無需發送證書請求到服務端。

證書必須為X.509v3格式。

Server Key Exchange

使用RSA公鑰加密,必須要保證服務端私鑰的安全。若私鑰泄漏,則使用公鑰加密的對稱密鑰就不再安全。同時RSA是基於大數因式分解。密鑰位數必須足夠大才能避免密鑰被暴力破解。

1999年,RSA-155 (512 bits) 被成功分解。
2009年12月12日,RSA-768 (768 bits)也被成功分解。
在2013年的稜鏡門事件中,某個CA機構迫於美國政府壓力向其提交了CA的私鑰,這就是十分危險的。

相比之下,使用DH算法通過雙方在不共享密鑰的情況下雙方就可以協商出共享密鑰,避免了密鑰的直接傳輸。DH算法是基於離散對數,計算相對較慢。而基於橢圓曲線密碼(ECC)的DH算法計算速度更快,而且用更小的Key就能達到RSA加密的安全級別。ECC密鑰長度為224~225位幾乎和RSA2048位具有相同的強度。

ECDH:基於ECC的DH算法。

另外在DH算法下引入動態隨機數,可以避免密鑰直接傳輸。同時即使密鑰泄漏,也無法解密其他消息,因為雙方生成的動態隨機數無法得知。

在密碼學中該特性被稱為前向保密

DHE: 通過引入動態隨機數,具有前向保密的DH算法。
ECDHE:通過引入動態隨機數,具有前保密的ECDH算法。

Certificate Request

當需要TLS雙向認證的時候,若服務端需要驗證客戶端的證書,則向客戶端發送Certificate Request請求獲取客戶端指定類型的證書。

  • 服務端會指定客戶端的證書類型。
  • 客戶端會確定是否有合適的證書。

Server Hello Done

當服務端處理Hello請求結束時,發送Server Hello Done消息,然後等待接收客戶端握手消息。客戶端收到服務端該消息,有必要時需要對服務端的證書進行有效性校驗。

Client Certificate

當客戶端收到了服務端的CertificateRequest請求時,需要發送Client Certificate消息,若客戶端無法提供證書,則仍要發送此消息,消息內容可以不包含證書。

Client Key Exchange

客戶端接收到ServerHelloDone消息后,計算密鑰,通過發送Client Key Exchange消息給服務端。客戶端和服務端通過Key Exchange消息交換密鑰,使得雙方的主密鑰協商達成一致。

以RSA的密鑰協商為例。在ClientHelloServerHello分別在客戶端和服務端創建了一個32位的隨機數。客戶端接收到Server Hello Done消息時,生成最後一個48位的預主密鑰。通過服務端提供的證書進行公鑰加密,以保證只有服務端的私鑰才能解密。

其中預主密鑰的前2位要求使用Client Hello傳輸的TLS版本號(存在一些TLS客戶端傳遞的時協商后的TLS版本號,對該版本號檢查時可能會造成握手失敗)。

需要注意的是,若RSA證書的填空格式不正確,則可能會存在一個漏洞導致客戶端發送的PreMasterSecret被中間人解密造成數據加密的對賬密鑰泄漏。可以看下Attacking RSA-based Sessions in SSL/TLS

Certificate Verify

若服務端要求客戶端發送證書,且客戶端發送了非0長度的證書,此時客戶端想要證明自己擁有該證書,則需要使用客戶端私鑰簽名一段數據發送給服務端繼續驗證。該數據為客戶端收發的所有握手數據的hash值(不包括本次消息)。

Finished

當發送完Change Cipher Spec消息后必須立即發送該消息。當該消息用於驗證密鑰交換和身份驗證過程是否成功。

Finished消息是第一個使用協商的算法簇進行加密和防篡改保護的消息。一旦雙方都通過了該消息驗證,就完成了TLS握手。
VerifyData為客戶端收發的所有握手數據的hash值(不包括本次消息)。與Certificate Verify的hash值可能會不一樣。如果發送過Certificate Verify消息,服務端的握手消息會包含Certificate Verify握手的數據。

需要注意的是,握手數據不包括協議頭的握手協議明文數據(服務端返回Finished的驗證握手數據是包含接收到客戶端的Finished的明文hash值)。

Finished消息數據加密和Appilication Data一致,具體數據加密在Application Data段進行說明。

改變密碼標準協議

改變密碼標準協議是為了在密碼策略中發出信號轉換信號。 該協議由一條消息組成,該消息在當前(不是掛起的)連接狀態下進行加密和壓縮。 消息由值 1 的單個字節組成。

在接收到該協議后,所有接收到的數據都需要解密。

警報協議

警報消息傳達消息的嚴重性(警告或致命)和警報的說明。具有致命級別的警報消息會導致立即終止連接。
若在改變密碼標準協議前接收到警報消息,是明文傳輸的,無需解密。

與其他消息一樣,警報消息按當前連接狀態指定進行加密和壓縮。在接收到改變密碼標準協議後接收到警報協議,則需要進行解密。解密后即為警報協議明文格式。

加密的Alert消息和加密數據一樣,都需要遞增加密序號,在數據解密時,遞增解密序號。

應用程序數據協議

當客戶端和服務端Finished發送完畢並驗證通過後,握手就結束了。後續所有數據都會使用握手協商的對稱密鑰進行數據加密。

TLS協議實現了數據加密和MAC計算。一般來說有3種加密模式,分別為:

  1. Mac-then-Encrypt:在明文上計算MAC,將其附加到數據,然後加密明和+MAC的完整數據。
  2. 加密和MAC:在明文上計算MAC,加密明文,然後將MAC附加到密文的末尾
  3. Encrypt-then-Mac:加密明文,然後在密文上計算MAC,並將其附加到密文。

TLS協議使用的是Mac-then-Encrypt。首先將加密的序號、ContentType、數據長度、數據進計算HMAC-SHA256摘要。然後將摘要拼接到數據后,通過PKCS7格式對摘要+MAC數據進行填充對其和加密塊大小一致。最後摘要+MAC+對其填充塊進行加密。

需要注意的是應用程序數據消息有最大長度限制2^14 + 2048,當超過長度后,數據需要分段傳輸。每一段都當作單獨的數據段進行單獨MAC地址並加密。

結語

TLS1.2版本是目前最常用的TLS協議,TLS1.3版本於2018年發表,目前並沒有廣泛使用。
使用TLS1.2需要注意以下幾點:

  1. 若使用RSA非對稱加密,則需要盡可能使用2048位長度的密鑰。
  2. 盡可能可以使用具有前向安全性的加密算法,如ECDHE算法進行非對稱加密。
  3. 使用AEAD認證加密(GCM)代替CBC塊加密。

參考文獻

  1. The Transport Layer Security (TLS) Protocol Version 1.2
  2. TLS發展歷史
  3. 前向安全性
  4. TLS/SSL 協議詳解 (30) SSL中的RSA、DHE、ECDHE、ECDH流程與區別
  5. TLS Extensions
  6. Session會話恢復:兩種簡短的握手總結SessionID&SessionTicket
  7. Why using the premaster secret directly would be vulnerable to replay attack?
  8. Why does the SSL/TLS handshake have a client and server random?
  9. Transport Layer Security (TLS) Extensions
  10. 什麼是AEAD加密
  11. Padding Oracle Attacks
  12. Lucky13攻擊
  13. Padding Oracle Attack
  14. ssl perfect forward secrecy
  15. Transport Layer Security (TLS) Session Resumption without Server-Side State
  16. Elliptic Curve Cryptography (ECC) Cipher Suites for Transport Layer Security (TLS)
  17. DTLS協議中client/server的認證過程和密鑰協商過程

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

【其他文章推薦】

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

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

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

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

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

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

2016國際IoT車用光電技術趨勢研討會論壇

隨著車輛科技進化,光電產品扮演更重要的角色,全球車用光電市場在2016-2019年間將以年複合成長率17.36%擴大。 由於未來智慧交通、智慧車(Smart Car)仍需要光電技術作為解決方案,「車輛、光電、LED照明」三大跨領域產業合作新紀元。未來「IOT×車用光電」將匯集國內LED上中下游產業與汽車零組件供應廠商, 包括車用LED/LD/OLED產品、車用顯示產品、車用光學影像產品、車用電子系統,以及材料及製程/檢測設備等,展現車用光電發展的產品技術新趨勢。
今年首度舉辦的「國際IOT車用光電技術趨勢研討會論壇」在規劃上特別著重在國內業界相當重視的智慧車用感測及IOT技術、智慧車燈技術、車用顯示及光學元件技術、智慧車 聯網技術等相關議題的分享。 為突顯國內產業在此一應用技術上的發展成果,也特別在展覽會場中規劃出IOT車用光電專區邀集國內大廠共同展出,為國內目前最專業與豐富的研討會議, 敬邀您親自前來交流。

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

【其他文章推薦】

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

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

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

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

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

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

福特多平臺工廠生產最新的插電式混合動力車

福特公司稱,該公司已開始生產最新的插電式,這是其全球製造策略的最新舉措。北美製造部門負責人Jim Tetreault表示,未來在全球範圍內,福特的工廠將具有多平臺、多種動力總成系統和多種車體風格。

福特介紹,這家密歇根裝配廠目前是世界上唯一一家在同一條生產線上製造汽油動力車、混合動力車、插電式混合動力車和電動車的工廠。

密歇根裝配廠是福特公司策略的最新例證,該公司的策略是計畫重組工廠和培訓工人製造更廣泛的車型。此舉將降低福特的生產成本,同時又能夠更快地適應消費者的需求變化。

密歇根裝配廠可在兩個平臺上生產五種車體風格,這種靈活性是福特既能提供電動車也能生產混合動力車的關鍵因素。Jim Tetreault表示:“我們不想專門用一條生產線來生產電動車,也不希望把所有的資本都用在一條單一的車型生產線上。”

福特曾在2009年5月宣佈花費5.5億美元全面翻修這傢俱有55年歷史的老廠,該廠曾生產Expedition車型和林肯領航員SUV。目前則生產C-MAX混合動力車、插電式混合動力車及汽油版福克斯。

密歇根裝配廠的車身製造車間有超過80%的模具可以焊接各種不同的車身樣式,而裝配區域的佈局得到了改進,使工人有更多的時間來安裝複雜的高壓電線或電動車電池。

福特透露,公司今年還設計生產了關鍵的電氣化零部件。Van Dyke Transmission工廠開始生產混合動力傳動裝置,而Rawsonville工廠負責組裝電池組。這些改革為福特節省了20%的開發成本,隨著這些工廠變得更有效率,成本將會進一步下降。例如在Rawsonville工廠,工人從每小時完成9項任務到現在每小時可以完成19項任務。

通用和日產公司則專為雪佛蘭沃藍達(Volt)和日產聆風(Leaf)創建了獨立平臺。由於銷售低迷,通用公司在今年早些時候不得不停產數日。

而福特的策略則與之不同,在“一體式製造”戰略指引下,福特預計三年內,每個組裝廠平均可生產4.5種車型,目前為3.6種。Jim表示,隨著時間的推移,福特將使更多的工廠擁有多個車型平臺,既可生產轎車,又可生產跨界車。

福特在加拿大安大略省Oakville的工廠可以在兩個平臺上製造三種車體風格,在美國肯塔基州路易斯維爾的工廠可以在三個平臺上製造六種車體風格。福特正在努力使其他工廠能夠建立多個平臺,但新的燃油經濟性和安全性要求是該公司面臨的挑戰。

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

【其他文章推薦】

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

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

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

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

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

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

RocketMQ(1)—架構原理及環境搭建

一、架構簡述

RocketMQ阿里開源的一個分佈式消息傳遞和流媒體平台,具有低延遲,高性能和可靠性, 萬億級容量和靈活的可伸縮性。跟其它中間件相比,RocketMQ的特點是JAVA實現,在發生宕機和其它故障時消息丟失率更低

它由四個部分組成:nameserver,broker,生產者和消費者。它們中的每一個都可以水平擴展,而沒有單個故障點。  

Nameserver:提供輕量級的服務發現和路由。生產者和消費者通過nameserver獲取broker信息。它幾乎是無狀態的,nameserver結點之間沒有任何的數據同步,broker註冊信息時會註冊到每一個nameserver結點上面,所以每個nameserver節點都記錄了完整broker信息,提供相應的讀寫服務,並支持快速的存儲擴展。

Broker:通過提供輕量級的topic和queue機制來存儲消息。與nameserver中的每個節點建立長連接,定時註冊topic等信息到nameserver上面。broker一般都是主從模式,因為消息是真實存儲消息的地方,避免一個結點掛了,導致這個節點數據全部丟失。

Producer:與nameserver集群中的一個結點建立長連接,定期的拉取broker 的topic路由信息,再將消息發送到對應broker的topic上面

Consumer:與nameserver集群中的一個結點建立長連接,定期的拉取broker 的topic路由信息,再去消費對應broker的topic信息

 

 

二、環境搭建

 1.官網下載:http://rocketmq.apache.org/release_notes/release-notes-4.7.0/

2.解壓 unzip rocketmq-all-4.7.0-bin-release.zip

3.修改啟動參數配置。默認的jvm參數內存設置特別大,如果自己機器不行的話需要手動改下bin目錄下的啟動參數文件:runbroker.sh 和runserver.sh文件 我的虛擬機內存分配不大,改成256m 256m 128m

   這是默認的

 

 

4.啟動nameserver: nohup sh mqnamesrv ‐n 192.168.0.67:9876 &   (將日誌輸出當前目錄的nohub.out文件,方便查看啟動日誌,ip是當前機器的ip)

 

 

 

 5.啟動broker:nohup sh mqbroker ‐n 192.168.0.67:9876 autoCreateTopicEnable=true &  (autoCreateTopicEnable=true 自動創建topic,如果不設置true的話,生產者發送消息的時候如果沒有topic就會發送失敗,需要提前把topic創建好,設置true會在發送時自動創建topic,192.168.0.67:9876 是name server)

也可以使用配置文件啟動broker:nohup sh mqbroker ‐n 192.168.0.67:9876 ‐c conf/broker.conf &

簡單看下默認的配置文件中的一些參數:

#集群名字
brokerClusterName = DefaultCluster
#broker名字,集群中主從都要用這個名字,才會組成一個集群
brokerName = broker-a
#id為0的是master  非0的slava
brokerId = 0
#消息處理時間,凌晨4點
deleteWhen = 04
#消息保存時間默認48小時,48小時之後的凌晨4點就會清理
fileReservedTime = 48
#集群主從之間數據同步方式 
#異步只需要發到master成功就返回客戶端段成功,性能高,但是如果master掛了 slave還未同步就會丟失消息。根據自身業務場景選擇合適方式
brokerRole = ASYNC_MASTER
#消息刷盤機制,和主從數據同步類似,同步就是說需要寫進磁盤了才返回成功。異步就是寫進內存了就返回成功,後面再去落盤。
flushDiskType = ASYNC_FLUSH
#自動創建topic
autoCreateTopicEnable=true

使用配置文件啟動:nohup sh bin/mqbroker ‐n 192.168.0.67:9876 -c conf/broker.conf &

broker 192.168.0.67:10911關聯的nameserver是192.168.0.67:9876

 

 

至此一個單機的rocketMQ的環境就搭建好了   正常退出: sh mqshutdown broker  和  sh mqshutdown namesrv

 

測試下消息發送,使用rocketMQ提供的測試腳本:

export NAMESRV_ADDR=192.168.0.67:9876

生產者腳本

sh bin/tools.sh org.apache.rocketmq.example.quickstart.Producer 消費者腳本

sh bin/tools.sh org.apache.rocketmq.example.quickstart.Consumer 發送消息:  

 

 消費消息:

 

三、控制台搭建

1.下載rocketMQ的擴展包,master分支:https://github.com/apache/rocketmq-external 

 2.啟動rocketmq-console-ng模塊

 

 3.修改此模塊配置:

  3.1 maven依賴rocketMQ版本改成自己部署版本對應的,我部署的MQ是最新的4.7.0版本

 

 

  3.2 配置文件中配置namserver地址和控制台數據存放地址

 

 正常來說改完這兩個地方就可以直接啟動控制台的這個springboot程序了。

但是因為我用的MQ是最新的4.7版本,控制台對應的還沒有更新到最新的。編譯都有會報錯的地方

1.DefaultMQPullConsumer這個類已經不推薦使用了,並且4.7.0中有兩個類似的構造器,原來代碼直接傳了一個null,第二個參數無法識別是哪個構造器的。修改下把第二參數強轉String或者RPCHook

 

 

2.MQAdminExt這個接口中加了新方法,但是控制台中MQAdminExtImpl還沒有實現對應的方法。這個問題在github上幾天前已經有人提了Issues了,我這裡是自己添加一下默認實現然後服務就可以正常啟動了,還不確定後續後面有什麼影響沒有,至少可以啟動了

 

 

 

 

之前使用測試腳本發送的消息 以及topic都可以在控制台看到了

 

 

 

 

 

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

【其他文章推薦】

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

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

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

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

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

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

專家針對武漢肺炎警告 都市人長期處於空污下 恐增加感染死亡風險

環境資訊中心綜合外電;姜唯 編譯;林大利 審校

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

【其他文章推薦】

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

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

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

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

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

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

程序員的命名素養

引言

今天來聊聊命名相關內容。

在日常工作中,項目、類、方法、表等等等等,都需要我們起名來標識區分。好的名字讓人賞心悅目,不好的名字讓人看的想吐。

最近工作有幸寫了node、前端、php、sql、scala,也見識了公司各位前輩們的命名功底。其中不乏abc命名、拼音命名、蹩腳英文命名,更有不少從別的地方粘過來連名都不改的操作。

命名沒有對錯,只是規範一點,可以提高可讀性、可維護性。

命名原則

拼寫正確

拼寫正確是可讀的基礎。

play shiftplay shit自己體會一下

清新明了,見名知意

根據要表達的內容命名,一針見血。

getNameById 根據id獲取名稱
ClassLoader 類加載器
MYSQL_USERNAME mysql用戶名 

如上幾個例子,我們一眼就知道要表達什麼,可讀性高。

使用英文字母命名

在編程中,英文還是較為主流的,最好使用單詞來命名,再不濟也是用拼音來命名。

不論是拼音或單詞,清晰表意是首要。

保持一致

在一個項目中,應該使用統一的規範來命名。

無規矩不成方圓。

合理使用動詞名詞

類名、變量名通常應使用名詞。如ClassLoaderuserId

對於方法名、函數名,應包含動詞。如handleClickgenerateUniqueId

命名方法

常見的命名方法有駝峰命名法、匈牙利命名法、帕斯卡命名法、中/下劃線命名法

駝峰命名法Camel-Case

駝峰命名法,又叫小駝峰命名法,如名稱所表達的意思,指混合使用大小寫字母老表示名字。

userIdgetCompanyNameById

應用很廣泛。

匈牙利命名法

基本原則是:變量名=屬性+類型+對象描述。通過名稱可以直觀的了解他的所屬、類型等信息。

是早期的命名方式,早期IDE沒有很智能的時候,這種命名是很有必要的。

iNum,表示int類型的num

現在依舊很少有人用了。

帕斯卡命名法

又叫大駝峰命名法,就是把駝峰命名的首字母大寫了。

ClassLoader

中/下劃線命名法

單詞全部小寫,單詞和單詞間用中劃線或下劃線分割。

user_idpython-flask-demo

下劃線命名在數據庫中較為常見。

常量命名法

這個不是官方的方法,但是常量一般是由固定規範的。

格式:所有單詞的所有字母都是大寫,單詞之間用下戶線連接。

APOLLO_NAMESPAC

總結

好的命名習慣是每個程序員必備的基本素養。

寫代碼時,好的命名會讓思路更加清洗,代碼寫的更加絲滑。

代碼就是程序員的形象,從命名的細節開始,讓自己更帥一些。

個人公眾號:碼農峰,定時推送行業資訊,持續發布原創技術文章,歡迎大家關注。

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

【其他文章推薦】

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

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

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

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

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

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

重識Java8函數式編程

前言

最近真的是太忙忙忙忙忙了,很久沒有更新文章了。最近工作中看到了幾段關於函數式編程的代碼,但是有點費解,於是就準備總結一下函數式編程。很多東西很簡單,但是如果不總結,可能會被它的各種變體所困擾。接觸Lambda表達式已經很久了,但是也一直是處於照葫蘆畫瓢的階段,所以想自己去編寫相關代碼,也有些捉襟見肘。

1. Lambda表達式的不同形式

// 基本形式
參數 -> 主體

1.1 形式一

Runnable noArguments = () -> System.out.println("Hello World");

該形式的Lambda表達式不包含參數,使用空括號()表示沒有參數。它實現了Runnable接口,該接口也只有一個run方法,沒有桉樹,且返回類型為void。

1.2 形式二

ActionListener oneArgument = event -> System.out.println("button clicked");

該形式的Lambda表達式包含且只包含一個參數,可省略參數的符號。

1.3 形式三

Runnable multiStatement = () -> {
	System.out.print("Hello"); 
    System.out.println(" World"); 
};

Lambda表達式的主體不僅可以使一個表達式,而且也可以是一段代碼塊,使用大括號{}將代碼塊括起來。該代碼塊和普通方法遵循的規則別無二致,可以用返回或拋出異常來退出。只有以行代碼的Lambda表達式也可以使用大括號,用以明確Lambda表達式從何處開始,到哪裡結束。

1.4 形式四

BinaryOperator<Long> add = (x, y) -> x + y;

Lambda表達式也可以表示包含多個參數的方法,上面的Lambda表達式並不是將兩個数字相加,而是創建了一個函數,用來計算兩個数字相加的結果。變量add的類型時BinaryOperator ,它不是兩個数字的和,而是將兩個数字相加的那行代碼。

1.5 形式五

BinaryOperator<Long> addExplicit = (Long x, Long y) -> x + y;

到目前為止,所有Lambda表達式中的參數類型都是由編譯器推斷得出的。但有時最好也可以显示聲明參數類型,此時就需要使用小括號將參數括起來,多個參數的情況也是如此。

2. 引用值,而不是變量

如果你曾使用過匿名內部類,也許遇到過這樣的情況:需要引用它所在方法里的變量。這是,需要將變量聲明為final。

final String name = getUserName(); 
button.addActionListener(new ActionListener() {
	public void actionPerformed(ActionEvent event) { 
        System.out.println("hi " + name); 
    } 
});

將變量聲明為 final,意味着不能為其重複賦 值。同時也意味着在使用 final 變量時,實際上是在使用賦給該變量的一個特定的值。

Java 8 雖然放鬆了這一限制,可以引用非 final 變量,但是該變量在既成事實上必須是 final(意思就是你不能再次對該變量賦值)。雖然無需將變量聲明為 final,但在 Lambda 表達式中,也無法用作非終態變量。如 果堅持用作非終態變量,編譯器就會報錯。 既成事實上的 final 是指只能給該變量賦值一次。換句話說,Lambda 表達式引用的是值, 而不是變量。

例如:

String name = getUserName(); 
button.addActionListener(event -> System.out.println("hi " + name));

3. 函數接口

在 Java 里,所有方法參數都有固定的類型。假設將数字 3 作為參數傳給一個方法,則參數 的類型是 int。那麼,Lambda 表達式的類型又是什麼呢?

使用只有一個方法的接口來表示某特定方法並反覆使用,是很早就有的習慣。使用 Swing 編寫過用戶界面的人對這種方式都不陌生,這裏無需再標新立異,Lambda 表達式也使用同樣的技巧,並將這種接口稱為函數接口。

接口中單一方法的命名並不重要,只要方法簽名和 Lambda 表達式的類型匹配即可。可在函數接口中為參數起一個有意義的名字,增加代碼易讀性,便於更透徹 地理解參數的用途。

3.1 Java中重要的函數接口

接口 參數 返回類型 示例
Predicate T boolean 判斷是否
Consumer T void 輸出一個值
Function<T,R> T T 獲得對象的名字
Supplier None T 工廠方法
UnaryOperator T T 邏輯非(!)
BinaryOperator (T, T) T 求兩個數的乘積(*)

3.2 函數接口定義

定義函數接口需要使用到註解@FunctionalInterface

例如:

@FunctionalInterface
public interface MyFuncInterface {
	void print();
}

使用:

public class MyFunctionalInterfaceTest {
    public static void main(String[] args) {
        doPrint(() -> System.out.println("java"));
    }

    public static void doPrint(MyFuncInterface my) {
        System.out.println("請問你喜歡什麼編程語言?");
        my.print();
    }
}

說明:

這隻是一個很簡單的例子,有人覺得為什麼要搞這麼複雜,去定義一個接口?這個問題還是讀者在平時的工作中去感悟吧,總之,先學會怎麼用它。不至於看了別人寫的代碼都看不懂。

至於我個人的理解,可以簡單聊聊。以前寫過JavaScript,裏面有一種語法就是將自定義函數B作為參數傳遞到另外一個函數A裏面,在函數A裏面會執行你自定義的函數B邏輯,我當時就非常喜歡這種特性,因為每個人關於函數B的實現可能不一樣,亦或者場景不一樣也會導致函數B的實現不一樣。我覺得Java8的這個函數式編程就是對這一特性的補充。

4. 流

流的常用操作有很多,例如collect(toList())mapfiltermaxmin等,下面介紹一下flatMapreduce

4.1 flatMap

flatMap 方法可用 Stream 替換值,然後將多個 Stream 連接成一個 Stream。

List<Integer> together = Stream.of(asList(1, 2), asList(3, 4)) 				 
    .flatMap(numbers -> numbers.stream())
    .collect(toList()); 
assertEquals(asList(1, 2, 3, 4), together);

調用 stream 方法,將每個列錶轉換成 Stream 對象,其餘部分由 flatMap 方法處理。 flatMap 方法的相關函數接口和 map 方法的一樣,都是 Function 接口,只是方法的返回值 限定為 Stream 類型罷了。

4.2 reduce

reduce 操作可以實現從一組值中生成一個值。對於 count、min 和 max 方 法,因為常用而被納入標準庫中。事實上,這些方法都是 reduce 操作。

如何通過 reduce 操作對 Stream 中的数字求和。以 0 作起點——一個空Stream 的求和結果,每一步都將 Stream 中的元素累加至 accumulator,遍歷至 Stream 中的 最後一個元素時,accumulator 的值就是所有元素的和。

int count = Stream.of(1, 2, 3)
    .reduce(0, (acc, element) -> acc + element); 
assertEquals(6, count);

Lambda 表達式的返回值是最新的 acc,是上一輪 acc 的值和當前元素相加的結果。reducer 的類型是前面已介紹過的 BinaryOperator。

5. Optional

reduce 方法的一個重點尚未提及:reduce 方法有兩種形式,一種如前面出現的需要有一 個初始值,另一種變式則不需要有初始值。沒有初始值的情況下,reduce 的第一步使用 Stream 中的前兩個元素。有時,reduce 操作不存在有意義的初始值,這樣做就是有意義的,此時,reduce 方法返回一個 Optional 對象。

Optional 是為核心類庫新設計的一個數據類型,用來替換 null 值。人們對原有的 null 值有很多抱怨。人們常常使用 null 值表示值不存在,Optional 對象能更好地表達這個概念。使用 null 代 表值不存在的最大問題在於 NullPointerException。一旦引用一個存儲 null 值的變量,程 序會立即崩潰。使用 Optional 對象有兩個目的:首先,Optional 對象鼓勵程序員適時檢查變量是否為空,以避免代碼缺陷;其次,它將一個類的 API 中可能為空的值文檔化,這比閱讀實現代碼要簡單很多。

下面我們舉例說明 Optional 對象的 API,從而切身體會一下它的使用方法。使用工廠方法 of,可以從某個值創建出一個 Optional 對象。Optional 對象相當於值的容器,而該值可以 通過 get 方法提取。

Optional<String> a = Optional.of("a"); 
assertEquals("a", a.get());

Optional 對象也可能為空,因此還有一個對應的工廠方法 empty,另外一個工廠方法 ofNullable 則可將一個空值轉換成 Optional 對象。下面的代碼同時展示 了第三個方法 isPresent 的用法(該方法表示一個 Optional 對象里是否有值)。

Optional emptyOptional = Optional.empty(); 
Optional alsoEmpty = Optional.ofNullable(null); assertFalse(emptyOptional.isPresent());

使用 Optional 對象的方式之一是在調用 get() 方法前,先使用 isPresent 檢查 Optional 對象是否有值。使用 orElse 方法則更簡潔,當 Optional 對象為空時,該方法提供了一個 備選值。如果計算備選值在計算上太過繁瑣,即可使用 orElseGet 方法。該方法接受一個 Supplier 對象,只有在 Optional 對象真正為空時才會調用。

assertEquals("b", emptyOptional.orElse("b")); 
assertEquals("c", emptyOptional.orElseGet(() -> "c"));

最後

實踐是檢驗真理的唯一標準,多寫代碼,多思考,你的代碼才會越來越好。

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

【其他文章推薦】

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

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

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

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

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

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

記一次uboot升級過程的兩個坑

背景

之前做過一次uboot的升級,當時留下了一些記錄,本文摘錄其中比較有意思的兩個問題。

啟動失敗問題

問題簡述

uboot代碼中用到了一個庫,考慮到庫本身跟uboot版本沒什麼關係,就直接把舊的庫文件拷貝過來使用。結果編譯鏈接是沒問題,啟動卻會卡住。

消失的打印

為了明確卡住的位置,就去修改了庫的源碼,添加一些打印(此時還是在舊版本uboot下編譯的),結果發現卡住的位置或隨着添加打印的變化而變化,且有些打印語句,添加后未打印出來。

我決定先從這些神秘消失的打印入手。

分析下uboot中的printf實現,最底層就是寫寄存器,是一個同步的函數,也沒什麼可疑的地方。

為了確認打印不出來的時候,到底有沒有調用到printf,我決定給printf增加一個計數器,在gd結構體中,增加一個printf_count字段,初始化為0,每次打印時執行printf_count++並打印出值。

設計這個試驗,本意是確認未打印出來時是否確實也調用到了printf,但卻有了別的發現,實驗結果中printf_count值會異常變化,不是按打印順序遞增,而是會突變成很大的異常值。

printf_countgd結構體的成員,那就是gd的問題了。進一步將uboot全局結構體gd的地址打印出來。確認了原因是gd結構體的指針變化了。

這也可以解釋部分打印消失的現象,原因是我們在gd中有另一個字段,用於控制打印等級。當gd被改動了,printf就可能解析出錯,誤以為打印等級為0而提前返回。

gd的實現

那麼好端端的,gd為什麼會被改了呢?這就要先看看gd到底是怎麼實現的了。

uboot中維護了一個全局的結構體gd。在代碼中加入

DECLARE_GLOBAL_DATA_PTR;

即可使用gd指針訪問這個全局結構體,許多地方都會藉助gd來保存傳遞信息。

進一步看看這個宏的定義

舊版本uboot:
#define DECLARE_GLOBAL_DATA_PTR        register volatile gd_t *gd asm ("r8")

新版本uboot:
#define DECLARE_GLOBAL_DATA_PTR        register volatile gd_t *gd asm ("r9")

居然不一樣,一個是將gd的值放到r8寄存器,一個是放在r9寄存器。

那麼就可以猜測到,庫是在舊版本uboot中編譯出來的,可能使用了r9,那麼放到新版本uboot中去,就會破壞r9寄存器中保存的gd值,導致一系列依賴gd的代碼不能正常工作。

驗證改動

為了求證,將庫反彙編出來,發現確實避開了r8寄存器,但使用了r9寄存器。

說明uboot在指定gd寄存器的同時,還有某種方法讓其他代碼不使用這個寄存器。

那是不是把舊uboot中的這個r8改成r9,重新編譯庫就可以了呢?試一下,還是不行。

那麼禁止其他代碼使用r8寄存器肯定就是通過別的方式實現的了。簡單粗暴地在舊版本uboot下搜索r8,去掉.c .h等類型后,很容易發現了

./arch/arm/cpu/armv7/config.mk:24:PLATFORM_RELFLAGS += -fno-common -ffixed-r8 -msoft-floa

-ffixed-r8修改為-ffixed-r9,重新編譯出庫,這回就可以正常工作了,打印正常,啟動正常。反彙編出來也可以看到,新編譯出來的庫用了r8沒有用r9

當然更好的改法,是直接在新版本的uboot中編譯,這是最可靠的。

追本溯源

話說回來,為什麼兩個版本的uboot,會使用不同的寄存器呢?難道有什麼坑?

這就得去翻一下git記錄了。

commit fe1378a961e508b31b1f29a2bb08ba1dac063155
Author: Jeroen Hofstee <jeroen@myspectrum.nl>
Date:   Sat Sep 21 14:04:41 2013 +0200

    ARM: use r9 for gd
    
    To be more EABI compliant and as a preparation for building
    with clang, use the platform-specific r9 register for gd
    instead of r8.
    
    note: The FIQ is not updated since it is not used in u-boot,
    and under discussion for the time being.
    
    The following checkpatch warning is ignored:
    WARNING: Use of volatile is usually wrong: see
    Documentation/volatile-considered-harmful.txt
    
    Signed-off-by: Jeroen Hofstee <jeroen@myspectrum.nl>
    cc: Albert ARIBAUD <albert.u.boot@aribaud.net>

git記錄中,也可以確認完整地將r8切換到r9,都需要做哪些修改

diff --git a/arch/arm/config.mk b/arch/arm/config.mk
index 16c2e3d1e0..d0cf43ff41 100644
--- a/arch/arm/config.mk
+++ b/arch/arm/config.mk
@@ -17,7 +17,7 @@ endif
 
 LDFLAGS_FINAL += --gc-sections
 PLATFORM_RELFLAGS += -ffunction-sections -fdata-sections \
-                     -fno-common -ffixed-r8 -msoft-float
+                     -fno-common -ffixed-r9 -msoft-float
 
 # Support generic board on ARM
 __HAVE_ARCH_GENERIC_BOARD := y
diff --git a/arch/arm/cpu/armv7/lowlevel_init.S b/arch/arm/cpu/armv7/lowlevel_init.S
index 82b2b86520..69e3053a42 100644
--- a/arch/arm/cpu/armv7/lowlevel_init.S
+++ b/arch/arm/cpu/armv7/lowlevel_init.S
@@ -22,11 +22,11 @@ ENTRY(lowlevel_init)
        ldr     sp, =CONFIG_SYS_INIT_SP_ADDR
        bic     sp, sp, #7 /* 8-byte alignment for ABI compliance */
 #ifdef CONFIG_SPL_BUILD
-       ldr     r8, =gdata
+       ldr     r9, =gdata
 #else
        sub     sp, #GD_SIZE
        bic     sp, sp, #7
-       mov     r8, sp
+       mov     r9, sp
 #endif
        /*
         * Save the old lr(passed in ip) and the current lr to stack
diff --git a/arch/arm/include/asm/global_data.h b/arch/arm/include/asm/global_data.h
index 79a9597419..e126436093 100644
--- a/arch/arm/include/asm/global_data.h
+++ b/arch/arm/include/asm/global_data.h
@@ -47,6 +47,6 @@ struct arch_global_data {
 
 #include <asm-generic/global_data.h>
 
-#define DECLARE_GLOBAL_DATA_PTR     register volatile gd_t *gd asm ("r8")
+#define DECLARE_GLOBAL_DATA_PTR     register volatile gd_t *gd asm ("r9")
 
 #endif /* __ASM_GBL_DATA_H */
diff --git a/arch/arm/lib/crt0.S b/arch/arm/lib/crt0.S
index 960d12e732..ac54b9359a 100644
--- a/arch/arm/lib/crt0.S
+++ b/arch/arm/lib/crt0.S
@@ -69,7 +69,7 @@ ENTRY(_main)
        bic     sp, sp, #7      /* 8-byte alignment for ABI compliance */
        sub     sp, #GD_SIZE    /* allocate one GD above SP */
        bic     sp, sp, #7      /* 8-byte alignment for ABI compliance */
-       mov     r8, sp          /* GD is above SP */
+       mov     r9, sp          /* GD is above SP */
        mov     r0, #0
        bl      board_init_f
 
@@ -81,15 +81,15 @@ ENTRY(_main)
  * 'here' but relocated.
  */
 
-       ldr     sp, [r8, #GD_START_ADDR_SP]     /* sp = gd->start_addr_sp */
+       ldr     sp, [r9, #GD_START_ADDR_SP]     /* sp = gd->start_addr_sp */
        bic     sp, sp, #7      /* 8-byte alignment for ABI compliance */
-       ldr     r8, [r8, #GD_BD]                /* r8 = gd->bd */
-       sub     r8, r8, #GD_SIZE                /* new GD is below bd */
+       ldr     r9, [r9, #GD_BD]                /* r9 = gd->bd */
+       sub     r9, r9, #GD_SIZE                /* new GD is below bd */
 
        adr     lr, here
-       ldr     r0, [r8, #GD_RELOC_OFF]         /* r0 = gd->reloc_off */
+       ldr     r0, [r9, #GD_RELOC_OFF]         /* r0 = gd->reloc_off */
        add     lr, lr, r0
-       ldr     r0, [r8, #GD_RELOCADDR]         /* r0 = gd->relocaddr */
+       ldr     r0, [r9, #GD_RELOCADDR]         /* r0 = gd->relocaddr */
        b       relocate_code
 here:
 
@@ -111,8 +111,8 @@ clbss_l:cmp r0, r1                  /* while not at end of BSS */
        bl red_led_on
 
        /* call board_init_r(gd_t *id, ulong dest_addr) */
-       mov     r0, r8                  /* gd_t */
-       ldr     r1, [r8, #GD_RELOCADDR] /* dest_addr */
+       mov     r0, r9                  /* gd_t */
+       ldr     r1, [r9, #GD_RELOCADDR] /* dest_addr */
        /* call board_init_r */
        ldr     pc, =board_init_r       /* this is auto-relocated! */

啟動慢問題

問題簡述

填了幾個坑之後,新的uboot可以啟動到內核了,但發現啟動速度非常慢,內核啟動速度慢了接近10倍!明明是同一個內核,為什麼差異這麼大。

排查寄存器

初步排查了下設備樹配置,以及uboot跳轉內核前的一些關鍵寄存器,確實在兩個版本的uboot中有所不同,但具體去看這些不同,發現都不會影響速度,將一些驅動對齊之後寄存器差異基本就消失了。

差異的分界

那再細看,kernel的速度有差異,uboot呢?在哪個時間點之後,速度開始產生差異?

嘗試在兩個版本的uboot中插入一些操作,對比時間戳,發現兩個uboot在某個節點之後的速度確實有區別。

進一步排查,原來是在打開cache操作之後,舊uboot的速度就會比新uboot快。嘗試將舊ubootcache關掉,則二者基本一致。嘗試將舊uboot操作cache的代碼,移植到新uboot,未發生改變。

此時可確認新uboot的開cache有問題。但覺得這個跟kernel啟動慢沒關係。因為uboot進入kernel之前都會關cache,由kernel自己去重新打開。

也就是不管是用哪份uboot,也不管uboot中是否開了cache,對kernel階段都應該沒有影響才對。

於是記錄下來uboot的這個問題,待後續修復。先繼續找kernel啟動慢的原因。(注:現在看來當時的做法是有問題的,這裏的異常這麼明顯,應該設法追蹤下去找出原因才對)

鎖定uboot

uboot的嫌疑非常大,但還不能完全確認,因為uboot之前還有一級spl。是否會是spl的問題呢?

嘗試改用新spl+舊uboot,啟動速度正常。而新spl+新uboot的啟動速度則很慢,其他因素都不變,說明問題確實出在uboot階段。

多做or少做

當時到這一步就卡住了,直接比較兩份uboot的代碼不太現實,差異太大了。

後來我就給自己提了個問題,到底新uboot是多做了某件事情,還是少做了某件事情?

換個說法,目前已知

spl --> 舊uboot --> kernel(速度快)
spl --> 新uboot --> kernel(速度快)

但到底是以下的情況A還是情況B呢?

A: spl(速度慢) --> 舊uboot(做了某個會提升速度的操作) --> kernel(速度快)
   spl(速度慢) --> 新uboot(少做了某個會提升速度的操作) --> kernel(速度慢)

B: spl(速度快) --> 舊uboot(沒做特殊操作) --> kernel(速度快)
   spl(速度快) --> 新uboot(多做了某個會限制速度的操作) --> kernel(速度慢)

為了驗證,我決定讓spl直接啟動內核,看看內核到底是快是慢。

支持過程碰到了一些小問題

1.spl沒有能力加載這麼大的kernel

解決:此時不需要kernel能完全啟動,只需要能加載啟動一段,足以體現出啟動速度是否正常即可,於是裁剪出一個非常小kernel來輔助實驗。

2.kernel需要dtb

解決:內核有一個CONFIG_BUILD_ARM_APPENDED_DTB_IMAGE選項。選上重新編譯。編譯后再用ddkerneldtb拼接到一起,作為新的kernel。這樣,spl就只需要加載一個文件並跳轉過去即可。

試驗結果,spl啟動的kernel和使用新uboot啟動的kernel速度一致,均比舊uboot啟動的kernel慢。

說明,舊uboot中做了某個關鍵操作,而新uboot沒做。

找出關鍵操作

那接下來的任務就是,找出舊uboot中的這個關鍵操作了。

怎麼找呢?有了上一步的成果,我們可以使用以下方法來排查

  1. spl加載kernel和舊uboot

  2. spl跳轉到舊uboot,此時kernel其實已經在dram中準備好了,隨時可以啟動

  3. 在舊uboot的啟動流程各個階段,嘗試直接跳轉到kernel,觀察啟動速度

  4. 如果在舊ubootA點跳轉kernel啟動慢,B點跳轉啟動快,則說明關鍵操作位於AB點之間。

方法有了,很快就鎖定到start.S,進一步在start.S中揪出了這段代碼

#if defined(CONFIG_ARM_A7)
@set SMP bit
    mrc     p15, 0, r0, c1, c0, 1
    orr        r0, r0, #(1<<6)
    mcr        p15, 0, r0, c1, c0, 1
#endif

ubootstart.S中沒有這段代碼,嘗試在新ubootstart.S中添加此操作,速度立馬恢復正常了。

再全局搜索下,原來這個新版本uboot中,套路是在board_init中進行此項設置的,而這個平台從舊版本移植過來,就沒有設置 SMP bit, 補上即可。

SMP bit是什麼

SMP 是指對稱多處理器,看起來這個 bit 會影響多核的 cache一致性,此處沒有再深入研究。

但可以知道,對於單處理器的情況,也需要設置這個bit才能正常使用cache

貼下arm的圖和描述:

[6]	SMP	

Signals if the Cortex-A9 processor is taking part in coherency or not.

In uniprocessor configurations, if this bit is set, then Inner Cacheable Shared is treated as Cacheable. The reset value is zero.

搜下kernel的代碼,發現也是有地方調用了的。不過這個芯片是單核的,根本就沒配置CONFIG_SMP

#ifdef CONFIG_SMP
	ALT_SMP(mrc	p15, 0, r0, c1, c0, 1)
	ALT_UP(mov	r0, #(1 << 6))		@ fake it for UP
	tst	r0, #(1 << 6)			@ SMP/nAMP mode enabled?
	orreq	r0, r0, #(1 << 6)		@ Enable SMP/nAMP mode
	orreq	r0, r0, r10			@ Enable CPU-specific SMP bits
	mcreq	p15, 0, r0, c1, c0, 1
#endif

總結

整理出來一方面是記錄這兩個bug,另一方面也是想記錄下當時的一些操作。

畢竟同樣的bug可能以後都不會碰到了,但解bug的方法和思路卻是可以積累復用的。

blog: https://www.cnblogs.com/zqb-all/p/13172546.html
公眾號:https://sourl.cn/shT3kz

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

【其他文章推薦】

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

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

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

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

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

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

基於領域驅動設計(DDD)超輕量級快速開發架構,ASP.NET MVC 5 SmartCode Scaffolding for Visual Studio.Net

 

smartadmin.core.urf 這個項目是基於asp.net core 3.1(最新)基礎上參照領域驅動設計(DDD)的理念,並參考目前最為了流行的abp架構開發的一套輕量級的快速開發web application 技術架構,專註業務核心需求,減少重複代碼,開始構建和發布,讓初級程序員也能開發出專業並且漂亮的Web應用程序

域驅動設計(DDD)是一種通過將實現與不斷髮展的模型相連接來滿足複雜需求的軟件開發方法。域驅動設計的前提如下:

  • 將項目的主要重點放在核心領域和領域邏輯上;
  • 將複雜的設計基於領域模型;
  • 啟動技術專家和領域專家之間的創造性合作,以迭代方式完善解決特定領域問題的概念模型。

最終的核心思想還是SOLID,只是實現的方式有所不同,ABP可能目前對DDD設計理念最好的實現方式。但對於小項目我還是更喜歡 URF.Core https://github.com/urfnet/URF.Core 這個超輕量級的實現。

同時這個項目也就是我2年前的一個開源項目 ASP.NET MVC 5 SmartCode Scaffolding for Visual Studio.Net 的升級版,支持.net core.目前沒有把所有功能都遷移到.net core,其中最重要的就是代碼生成這塊。再接下來的時間里主要就是完善代碼生成的插件。當然也要看是否受歡迎,如果反應一般,我可能不會繼續更新。

Demo 網站

 演示站點 
賬號:demo 密碼:123456

GitHub 源代碼 https://github.com/neozhu/smartadmin.core.urf

喜歡請給個 Star 每一顆Star都是鼓勵我繼續更新的動力 謝謝
如果你用於自己公司及盈利性的項目,希望給與金錢上的贊助,並且保留原作者的版權

分層

smartadmin.core.urf遵行DDD設計模式來實現應用程序的四層模型

  • 表示層(Presentation Layer):用戶操作展示界面,使用SmartAdmin – Responsive WebApp模板+Jquery EasyUI
  • 應用層(Application Layer):在表示層與域層之間,實現具體應用程序邏輯,業務用例,Project:StartAdmin.Service.csproj
  • 域層(Domain Layer):包括業務對象(Entity)和核心(域)業務規則,應用程序的核心,使用EntityFrmework Core Code-first + Repository實現
  • 基礎結構層(Infrastructure Layer):提供通用技術功能,這些功能主要有第三方庫來支持,比如日誌:Nlog,服務發現:Swagger UI,事件總線(EventBus):dotnetcore/CAP,認證與授權:Microsoft.AspNetCore.Identity,後面會具體介紹

內容

 

域層(Domain Layer)

  • 實體(Entity,BaseEntity) 通常實體就是映射到關係數據庫中的表,這裏說名一下最佳做法和慣例:
  1. 在域層定義:本項目就是(SmartAdmin.Entity.csproj)
  2. 繼承一個基類 Entity,添加必要審計類比如:創建時間,最後修改時間等
  3. 必須要有一個主鍵最好是GRUID(不推薦複合主鍵),但本項目使用遞增的int類型
  4. 字段不要過多的冗餘,可以通過定義關聯關係
  5. 字段屬性和方法盡量使用virtual關鍵字。有些ORM和動態代理工具需要

 

  • 存儲庫(Repositories) 封裝基本數據操作方法(CRUD),本項目應用 URF.Core實現
  • 域服務
  • 技術指標
  • 應用層

    • 應用服務:用於實現應用程序的用例。它們用於將域邏輯公開給表示層,從表示層(可選)使用DTO(數據傳輸對象)作為參數調用應用程序服務。它使用域對象執行某些特定的業務邏輯,並(可選)將DTO返回到表示層。因此,表示層與域層完全隔離。對應本項目:(SmartAdmin.Service.csproj)
    • 數據傳輸對象(DTO):用於在應用程序層和表示層或其他類型的客戶端之間傳輸數據,通常,使用DTO作為參數從表示層(可選)調用應用程序服務。它使用域對象執行某些特定的業務邏輯,並(可選)將DTO返回到表示層。因此,表示層與域層完全隔離.對應本項目:(SmartAdmin.Dto.csproj)
    • Unit of work:管理和控制應用程序中操作數據庫連接和事務 ,本項目使用 URF.Core實現
  • 基礎服務層

    • UI樣式定義:根據用戶喜好選擇多種頁面显示模式
    • 租戶管理:使用EntityFrmework Core提供的Global Filter實現簡單多租戶應用
    • 賬號管理: 對登錄系統賬號維護,註冊,註銷,鎖定,解鎖,重置密碼,導入、導出等功能
    • 角色管理:使用Microsoft身份庫管理角色,用戶及其權限管理
    • 導航菜單:系統主導航欄配置
    • 角色授權:配置角色显示的菜單
    • 鍵值對配置:常用的數據字典維護,如何正確使用和想法後面會介紹
    • 導入&導出配置:使用Excel導入導出做一個可配置的功能
    • 系統日誌:asp.net core 自帶的日誌+Nlog把所有日誌保存到數據庫方便查詢和分析
    • 消息訂閱:集中訂閱CAP分佈式事件總線的消息
    • WebApi: Swagger UI Api服務發現和在線調試工具
    • CAP: CAP看板查看發布和訂閱的消息

快速上手開發

  • 開發環境
    • Visual Studio .Net 2019
    • .Net Core 3.1
    • Sql Server(LocalDb)
  • 附加數據庫

    使用SQL Server Management Studio 附加.\src\SmartAdmin.Data\db\smartadmindb.mdf 數據庫(如果是localdb,那麼不需要修改數據庫連接配置)

  • 打開解決方案

第一個簡單的需求開始 
新增 Company 企業信息 完成CRUD 導入導出功能

  • 新建實體對象(Entity)

在SmartAdmin.Entity.csproj項目的Models目錄下新增一個Company.cs類

 1 //記住:定義實體對象最佳做法,繼承基類,使用virtual關鍵字,盡可能的定義每個屬性,名稱,類型,長度,校驗規則,索引,默認值等
 2 namespace SmartAdmin.Data.Models
 3 {
 4     public partial class Company : URF.Core.EF.Trackable.Entity
 5     {
 6         [Display(Name = "企業名稱", Description = "歸屬企業名稱")]
 7         [MaxLength(50)]
 8         [Required]
 9         //[Index(IsUnique = true)]
10         public virtual string Name { get; set; }
11         [Display(Name = "組織代碼", Description = "組織代碼")]
12         [MaxLength(12)]
13         //[Index(IsUnique = true)]
14         [Required]
15         public virtual string Code { get; set; }
16         [Display(Name = "地址", Description = "地址")]
17         [MaxLength(128)]
18         [DefaultValue("-")]
19         public virtual string Address { get; set; }
20         [Display(Name = "聯繫人", Description = "聯繫人")]
21         [MaxLength(12)]
22         public virtual string Contect { get; set; }
23         [Display(Name = "聯繫電話", Description = "聯繫電話")]
24         [MaxLength(20)]
25         public virtual string PhoneNumber { get; set; }
26         [Display(Name = "註冊日期", Description = "註冊日期")]
27         [DefaultValue("now")]
28         public virtual  DateTime RegisterDate { get; set; }
29     }
30 }
31 //在 SmartAdmin.Data.csproj 項目 SmartDbContext.cs 添加
32 public virtual DbSet<Company> Companies { get; set; }

View Code

  • 添加服務對象 Service

在項目 SmartAdmin.Service.csproj 中添加ICompanyService.cs,CompanyService.cs 就是用來實現業務需求 用例的地方

  1 //ICompany.cs
  2 //根據實際業務用例來創建方法,默認的CRUD,增刪改查不需要再定義
  3 namespace SmartAdmin.Service
  4 {
  5   // Example: extending IService<TEntity> and/or ITrackableRepository<TEntity>, scope: ICustomerService
  6   public interface ICompanyService : IService<Company>
  7   {
  8     // Example: adding synchronous Single method, scope: ICustomerService
  9     Company Single(Expression<Func<Company, bool>> predicate);
 10     Task ImportDataTableAsync(DataTable datatable);
 11     Task<Stream> ExportExcelAsync(string filterRules = "", string sort = "Id", string order = "asc");
 12   }
 13 }
 14 // 具體實現接口的方法
 15 namespace SmartAdmin.Service
 16 {
 17   public class CompanyService : Service<Company>, ICompanyService
 18   {
 19     private readonly IDataTableImportMappingService mappingservice;
 20     private readonly ILogger<CompanyService> logger;
 21     public CompanyService(
 22       IDataTableImportMappingService mappingservice,
 23       ILogger<CompanyService> logger,
 24       ITrackableRepository<Company> repository) : base(repository)
 25     {
 26       this.mappingservice = mappingservice;
 27       this.logger = logger;
 28     }
 29 
 30     public async Task<Stream> ExportExcelAsync(string filterRules = "", string sort = "Id", string order = "asc")
 31     {
 32       var filters = PredicateBuilder.FromFilter<Company>(filterRules);
 33       var expcolopts = await this.mappingservice.Queryable()
 34              .Where(x => x.EntitySetName == "Company")
 35              .Select(x => new ExpColumnOpts()
 36              {
 37                EntitySetName = x.EntitySetName,
 38                FieldName = x.FieldName,
 39                IgnoredColumn = x.IgnoredColumn,
 40                SourceFieldName = x.SourceFieldName
 41              }).ToArrayAsync();
 42 
 43       var works = (await this.Query(filters).OrderBy(n => n.OrderBy(sort, order)).SelectAsync()).ToList();
 44       var datarows = works.Select(n => new
 45       {
 46         Id = n.Id,
 47         Name = n.Name,
 48         Code = n.Code,
 49         Address = n.Address,
 50         Contect = n.Contect,
 51         PhoneNumber = n.PhoneNumber,
 52         RegisterDate = n.RegisterDate.ToString("yyyy-MM-dd HH:mm:ss")
 53       }).ToList();
 54       return await NPOIHelper.ExportExcelAsync("Company", datarows, expcolopts);
 55     }
 56 
 57     public async Task ImportDataTableAsync(DataTable datatable)
 58     {
 59       var mapping = await this.mappingservice.Queryable()
 60                         .Where(x => x.EntitySetName == "Company" &&
 61                            (x.IsEnabled == true || (x.IsEnabled == false && x.DefaultValue != null))
 62                            ).ToListAsync();
 63       if (mapping.Count == 0)
 64       {
 65         throw new  NullReferenceException("沒有找到Work對象的Excel導入配置信息,請執行[系統管理/Excel導入配置]");
 66       }
 67       foreach (DataRow row in datatable.Rows)
 68       {
 69 
 70         var requiredfield = mapping.Where(x => x.IsRequired == true && x.IsEnabled == true && x.DefaultValue == null).FirstOrDefault()?.SourceFieldName;
 71         if (requiredfield != null || !row.IsNull(requiredfield))
 72         {
 73           var item = new Company();
 74           foreach (var field in mapping)
 75           {
 76             var defval = field.DefaultValue;
 77             var contain = datatable.Columns.Contains(field.SourceFieldName ?? "");
 78             if (contain && !row.IsNull(field.SourceFieldName))
 79             {
 80               var worktype = item.GetType();
 81               var propertyInfo = worktype.GetProperty(field.FieldName);
 82               var safetype = Nullable.GetUnderlyingType(propertyInfo.PropertyType) ?? propertyInfo.PropertyType;
 83               var safeValue = (row[field.SourceFieldName] == null) ? null : Convert.ChangeType(row[field.SourceFieldName], safetype);
 84               propertyInfo.SetValue(item, safeValue, null);
 85             }
 86             else if (!string.IsNullOrEmpty(defval))
 87             {
 88               var worktype = item.GetType();
 89               var propertyInfo = worktype.GetProperty(field.FieldName);
 90               if (string.Equals(defval, "now", StringComparison.OrdinalIgnoreCase) && (propertyInfo.PropertyType == typeof(DateTime) || propertyInfo.PropertyType == typeof(Nullable<DateTime>)))
 91               {
 92                 var safetype = Nullable.GetUnderlyingType(propertyInfo.PropertyType) ?? propertyInfo.PropertyType;
 93                 var safeValue = Convert.ChangeType(DateTime.Now, safetype);
 94                 propertyInfo.SetValue(item, safeValue, null);
 95               }
 96               else if (string.Equals(defval, "guid", StringComparison.OrdinalIgnoreCase))
 97               {
 98                 propertyInfo.SetValue(item, Guid.NewGuid().ToString(), null);
 99               }
100               else if (string.Equals(defval, "user", StringComparison.OrdinalIgnoreCase))
101               {
102                 propertyInfo.SetValue(item, "", null);
103               }
104               else
105               {
106                 var safetype = Nullable.GetUnderlyingType(propertyInfo.PropertyType) ?? propertyInfo.PropertyType;
107                 var safeValue = Convert.ChangeType(defval, safetype);
108                 propertyInfo.SetValue(item, safeValue, null);
109               }
110             }
111           }
112           this.Insert(item);
113         }
114       }
115     }
116 
117     // Example, adding synchronous Single method
118     public Company Single(Expression<Func<Company, bool>> predicate)
119     {
120       
121       return this.Repository.Queryable().Single(predicate);
122 
123     }
124   }
125 }

View Code

  • 添加Controller

MVC Controller

  1 namespace SmartAdmin.WebUI.Controllers
  2 {
  3   public class CompaniesController : Controller
  4   {
  5     private  readonly ICompanyService companyService;
  6     private readonly IUnitOfWork unitOfWork;
  7     private readonly ILogger<CompaniesController> _logger;
  8     private readonly IWebHostEnvironment _webHostEnvironment;
  9     public CompaniesController(ICompanyService companyService,
 10           IUnitOfWork unitOfWork,
 11           IWebHostEnvironment webHostEnvironment,
 12           ILogger<CompaniesController> logger)
 13     {
 14       this.companyService = companyService;
 15       this.unitOfWork = unitOfWork;
 16       this._logger = logger;
 17       this._webHostEnvironment = webHostEnvironment;
 18     }
 19 
 20     // GET: Companies
 21     public IActionResult Index()=> View();
 22     //datagrid 數據源
 23     public async Task<JsonResult> GetData(int page = 1, int rows = 10, string sort = "Id", string order = "asc", string filterRules = "")
 24     {
 25       try
 26       {
 27         var filters = PredicateBuilder.FromFilter<Company>(filterRules);
 28         var total = await this.companyService
 29                              .Query(filters)
 30                              .AsNoTracking()
 31                              .CountAsync()
 32                               ;
 33         var pagerows = (await this.companyService
 34                              .Query(filters)
 35                               .AsNoTracking()
 36                            .OrderBy(n => n.OrderBy(sort, order))
 37                            .Skip(page - 1).Take(rows)
 38                            .SelectAsync())
 39                            .Select(n => new
 40                            {
 41                              Id = n.Id,
 42                              Name = n.Name,
 43                              Code = n.Code,
 44                              Address = n.Address,
 45                              Contect = n.Contect,
 46                              PhoneNumber = n.PhoneNumber,
 47                              RegisterDate = n.RegisterDate.ToString("yyyy-MM-dd HH:mm:ss")
 48                            }).ToList();
 49         var pagelist = new { total = total, rows = pagerows };
 50         return Json(pagelist);
 51       }
 52       catch(Exception e) {
 53         throw e;
 54         }
 55 
 56     }
 57     //編輯 
 58     [HttpPost]
 59     [ValidateAntiForgeryToken]
 60     public async Task<JsonResult> Edit(Company company)
 61     {
 62       if (ModelState.IsValid)
 63       {
 64         try
 65         {
 66           this.companyService.Update(company);
 67 
 68           var result = await this.unitOfWork.SaveChangesAsync();
 69           return Json(new { success = true, result = result });
 70         }
 71          catch (Exception e)
 72         {
 73           return Json(new { success = false, err = e.GetBaseException().Message });
 74         }
 75       }
 76       else
 77       {
 78         var modelStateErrors = string.Join(",", this.ModelState.Keys.SelectMany(key => this.ModelState[key].Errors.Select(n => n.ErrorMessage)));
 79         return Json(new { success = false, err = modelStateErrors });
 80         //DisplayErrorMessage(modelStateErrors);
 81       }
 82       //return View(work);
 83     }
 84     //新建
 85     [HttpPost]
 86     [ValidateAntiForgeryToken]
 87    
 88     public async Task<JsonResult> Create([Bind("Name,Code,Address,Contect,PhoneNumber,RegisterDate")] Company company)
 89     {
 90       if (ModelState.IsValid)
 91       {
 92         try
 93         {
 94           this.companyService.Insert(company);
 95        await this.unitOfWork.SaveChangesAsync();
 96           return Json(new { success = true});
 97         }
 98         catch (Exception e)
 99         {
100           return Json(new { success = false, err = e.GetBaseException().Message });
101         }
102 
103         //DisplaySuccessMessage("Has update a Work record");
104         //return RedirectToAction("Index");
105       }
106       else
107        {
108         var modelStateErrors = string.Join(",", this.ModelState.Keys.SelectMany(key => this.ModelState[key].Errors.Select(n => n.ErrorMessage)));
109         return Json(new { success = false, err = modelStateErrors });
110         //DisplayErrorMessage(modelStateErrors);
111       }
112       //return View(work);
113     }
114     //刪除當前記錄
115     //GET: Companies/Delete/:id
116     [HttpGet]
117     public async Task<JsonResult> Delete(int id)
118     {
119       try
120       {
121         await this.companyService.DeleteAsync(id);
122         await this.unitOfWork.SaveChangesAsync();
123         return Json(new { success = true });
124       }
125      
126       catch (Exception e)
127       {
128         return Json(new { success = false, err = e.GetBaseException().Message });
129       }
130     }
131     //刪除選中的記錄
132     [HttpPost]
133     public async Task<JsonResult> DeleteChecked(int[] id)
134     {
135       try
136       {
137         foreach (var key in id)
138         {
139           await this.companyService.DeleteAsync(key);
140         }
141         await this.unitOfWork.SaveChangesAsync();
142         return Json(new { success = true });
143       }
144       catch (Exception e)
145       {
146         return Json(new { success = false, err = e.GetBaseException().Message });
147       }
148     }
149     //保存datagrid編輯的數據
150     [HttpPost]
151     public async Task<JsonResult> AcceptChanges(Company[] companies)
152     {
153       if (ModelState.IsValid)
154       {
155         try
156         {
157           foreach (var item in companies)
158           {
159             this.companyService.ApplyChanges(item);
160           }
161           var result = await this.unitOfWork.SaveChangesAsync();
162           return Json(new { success = true, result });
163         }
164         catch (Exception e)
165         {
166           return Json(new { success = false, err = e.GetBaseException().Message });
167         }
168       }
169       else
170       {
171         var modelStateErrors = string.Join(",", ModelState.Keys.SelectMany(key => ModelState[key].Errors.Select(n => n.ErrorMessage)));
172         return Json(new { success = false, err = modelStateErrors });
173       }
174 
175     }
176     //導出Excel
177     [HttpPost]
178     public async Task<IActionResult> ExportExcel(string filterRules = "", string sort = "Id", string order = "asc")
179     {
180       var fileName = "compnay" + DateTime.Now.ToString("yyyyMMddHHmmss") + ".xlsx";
181       var stream = await this.companyService.ExportExcelAsync(filterRules, sort, order);
182       return File(stream, "application/vnd.ms-excel", fileName);
183     }
184     //導入excel
185     [HttpPost]
186     public async Task<IActionResult> ImportExcel()
187     {
188       try
189       {
190         var watch = new Stopwatch();
191         watch.Start();
192         var total = 0;
193         if (Request.Form.Files.Count > 0)
194         {
195           for (var i = 0; i < this.Request.Form.Files.Count; i++)
196           {
197             var model = Request.Form["model"].FirstOrDefault() ?? "company";
198             var folder = Request.Form["folder"].FirstOrDefault() ?? "company";
199             var autosave = Convert.ToBoolean(Request.Form["autosave"].FirstOrDefault());
200             var properties = (Request.Form["properties"].FirstOrDefault()?.Split(','));
201             var file = Request.Form.Files[i];
202             var filename = file.FileName;
203             var contenttype = file.ContentType;
204             var size = file.Length;
205             var ext = Path.GetExtension(filename);
206             var path = Path.Combine(this._webHostEnvironment.ContentRootPath, "UploadFiles", folder);
207             if (!Directory.Exists(path))
208             {
209               Directory.CreateDirectory(path);
210             }
211             var datatable = await NPOIHelper.GetDataTableFromExcelAsync(file.OpenReadStream(), ext);
212             await this.companyService.ImportDataTableAsync(datatable);
213             await this.unitOfWork.SaveChangesAsync();
214             total = datatable.Rows.Count;
215             if (autosave)
216             {
217               var filepath = Path.Combine(path, filename);
218               file.OpenReadStream().Position = 0;
219 
220               using (var stream = System.IO.File.Create(filepath))
221               {
222                 await file.CopyToAsync(stream);
223               }
224             }
225 
226           }
227         }
228         watch.Stop();
229         return Json(new { success = true, total = total, elapsedTime = watch.ElapsedMilliseconds });
230       }
231       catch (Exception e) {
232         this._logger.LogError(e, "Excel導入失敗");
233         return this.Json(new { success = false,  err = e.GetBaseException().Message });
234       }
235         }
236     //下載模板
237     public async Task<IActionResult> Download(string file) {
238       
239       this.Response.Cookies.Append("fileDownload", "true");
240       var path = Path.Combine(this._webHostEnvironment.ContentRootPath, file);
241       var downloadFile = new FileInfo(path);
242       if (downloadFile.Exists)
243       {
244        var fileName = downloadFile.Name;
245        var mimeType = MimeTypeConvert.FromExtension(downloadFile.Extension);
246        var fileContent = new byte[Convert.ToInt32(downloadFile.Length)];
247         using (var fs = downloadFile.Open(FileMode.Open, FileAccess.Read, FileShare.Read))
248         {
249           await fs.ReadAsync(fileContent, 0, Convert.ToInt32(downloadFile.Length));
250         }
251         return this.File(fileContent, mimeType, fileName);
252       }
253       else
254       {
255         throw new FileNotFoundException($"文件 {file} 不存在!");
256       }
257     }
258 
259     }
260 }

View Code

  • 新建 View

MVC Views\Companies\Index

  1 @model SmartAdmin.Data.Models.Company
  2 @{
  3   ViewData["Title"] = "企業信息";
  4   ViewData["PageName"] = "Companies_Index";
  5   ViewData["Heading"] = "<i class='fal fa-window text-primary'></i> 企業信息";
  6   ViewData["Category1"] = "組織架構";
  7   ViewData["PageDescription"] = "";
  8 }
  9 <div class="row">
 10   <div class="col-lg-12 col-xl-12">
 11     <div id="panel-1" class="panel">
 12       <div class="panel-hdr">
 13         <h2>
 14           公司信息
 15         </h2>
 16         <div class="panel-toolbar">
 17           <button class="btn btn-panel bg-transparent fs-xl w-auto h-auto rounded-0" data-action="panel-collapse" data-toggle="tooltip" data-offset="0,10" data-original-title="Collapse"><i class="fal fa-window-minimize"></i></button>
 18           <button class="btn btn-panel bg-transparent fs-xl w-auto h-auto rounded-0" data-action="panel-fullscreen" data-toggle="tooltip" data-offset="0,10" data-original-title="Fullscreen"><i class="fal fa-expand"></i></button>
 19         </div>
 20 
 21       </div>
 22       <div class="panel-container show">
 23         <div class="panel-content py-2 rounded-bottom border-faded border-left-0 border-right-0  text-muted bg-subtlelight-fade ">
 24           <div class="row no-gutters align-items-center">
 25             <div class="col">
 26               <!-- 開啟授權控制請參考 @@if (Html.IsAuthorize("Create") -->
 27               <div class="btn-group btn-group-sm">
 28                 <button onclick="append()" class="btn btn-default">
 29                   <span class="fal fa-plus mr-1"></span> 新增
 30                 </button>
 31               </div>
 32               <div class="btn-group btn-group-sm">
 33                 <button name="deletebutton" disabled onclick="removeit()" class="btn btn-default">
 34                   <span class="fal fa-times mr-1"></span> 刪除
 35                 </button>
 36               </div>
 37               <div class="btn-group btn-group-sm">
 38                 <button name="savebutton" disabled onclick="acceptChanges()" class="btn btn-default">
 39                   <span class="fal fa-save mr-1"></span> 保存
 40                 </button>
 41               </div>
 42               <div class="btn-group btn-group-sm">
 43                 <button name="cancelbutton" disabled onclick="rejectChanges()" class="btn btn-default">
 44                   <span class="fal fa-ban mr-1"></span> 取消
 45                 </button>
 46               </div>
 47               <div class="btn-group btn-group-sm">
 48                 <button onclick="reload()" class="btn btn-default"> <span class="fal fa-search mr-1"></span> 查詢 </button>
 49                 <button type="button" class="btn btn-default dropdown-toggle dropdown-toggle-split" data-toggle="dropdown" aria-haspopup="true" aria-expanded="false">
 50                   <span class="sr-only">Toggle Dropdown</span>
 51                 </button>
 52                 <div class="dropdown-menu dropdown-menu-animated">
 53                   <a class="dropdown-item js-waves-on" href="javascript:void()"> 我的記錄 </a>
 54                   <div class="dropdown-divider"></div>
 55                   <a class="dropdown-item js-waves-on" href="javascript:void()"> 自定義查詢 </a>
 56                 </div>
 57               </div>
 58               <div class="btn-group btn-group-sm hidden-xs">
 59                 <button type="button" onclick="importExcel.upload()" class="btn btn-default"><span class="fal fa-cloud-upload mr-1"></span> 導入 </button>
 60                 <button type="button" class="btn btn-default  dropdown-toggle dropdown-toggle-split waves-effect waves-themed" data-toggle="dropdown" aria-haspopup="true" aria-expanded="false">
 61                   <span class="sr-only">Toggle Dropdown</span>
 62                 </button>
 63                 <div class="dropdown-menu dropdown-menu-animated">
 64                   <a class="dropdown-item js-waves-on" href="javascript:importExcel.downloadtemplate()">
 65                     <span class="fal fa-download"></span> 下載模板
 66                   </a>
 67                 </div>
 68               </div>
 69               <div class="btn-group btn-group-sm hidden-xs">
 70                 <button onclick="exportexcel()" class="btn btn-default">
 71                   <span class="fal fa-file-export mr-1"></span>  導出
 72                 </button>
 73               </div>
 74 
 75             </div>
 76 
 77           </div>
 78 
 79         </div>
 80         <div class="panel-content">
 81           <div class="table-responsive">
 82             <table id="companies_datagrid">
 83             </table>
 84           </div>
 85         </div>
 86       </div>
 87     </div>
 88   </div>
 89 </div>
 90 <!-- 彈出窗體form表單 -->
 91 <div id="companydetailwindow" class="easyui-window"
 92      title="明細數據"
 93      data-options="modal:true,
 94                     closed:true,
 95                     minimizable:false,
 96                     collapsible:false,
 97                     maximized:false,
 98                     iconCls:'fal fa-window',
 99                     onBeforeClose:function(){
100                       var that = $(this);
101                       if(companyhasmodified()){
102                         $.messager.confirm('確認','你確定要放棄保存修改的記錄?',function(r){
103                         if (r){
104                           var opts = that.panel('options');
105                           var onBeforeClose = opts.onBeforeClose;
106                           opts.onBeforeClose = function(){};
107                           that.panel('close');
108                           opts.onBeforeClose = onBeforeClose;
109                           hook = false;
110                         }
111                         });
112                         return false;
113                       }
114                     },
115                     onOpen:function(){
116                        $(this).window('vcenter');
117                        $(this).window('hcenter');
118                     },
119                     onRestore:function(){
120                     },
121                     onMaximize:function(){
122                     }
123                     " style="width:820px;height:420px;display:none">
124   <!-- toolbar -->
125   <div class="panel-content py-2 rounded-bottom border-faded border-left-0 border-right-0  text-muted bg-subtlelight-fade sticky-top">
126     <div class="d-flex flex-row-reverse pr-4">
127       <div class="btn-group btn-group-sm mr-1">
128         <button name="saveitembutton" onclick="savecompanyitem()" class="btn btn-default">
129           <i class="fal fa-save"></i> 保存
130         </button>
131       </div>
132       <div class="btn-group btn-group-sm mr-1" id="deleteitem-btn-group">
133         <button onclick="deletecompanyitem()" class="btn btn-danger">
134           <i class="fal fa-trash-alt"></i> 刪除
135         </button>
136       </div>
137     </div>
138   </div>
139   <div class="panel-container show">
140     <div class="container">
141       <div class="panel-content">
142         <form id="company_form"
143               class="easyui-form form-horizontal p-1"
144               method="post"
145               data-options="novalidate:true,
146                             onChange: function(target){
147                               hook = true;
148                               $('button[name*=\'saveitembutton\']').prop('disabled', false);
149                              },
150                              onLoadSuccess:function(data){
151                                hook = false;
152                                $('button[name*=\'saveitembutton\']').prop('disabled', true);
153                              }">
154           @Html.AntiForgeryToken()
155           <!--Primary Key-->
156           @Html.HiddenFor(model => model.Id)
157           <fieldset class="form-group">
158             <!-- begin row -->
159             <!--名稱-->
160             <div class="row h-100 justify-content-center align-items-center">
161               <label class="col-md-2 pr-1 form-label text-right text-danger">@Html.DisplayNameFor(model => model.Name)</label>
162               <div class="col-md-4 mb-1 pl-1">
163                 <input id="@Html.IdFor(model => model.Name)"
164                        name="@Html.NameFor(model => model.Name)"
165                        value="@Html.ValueFor(model => model.Name)"
166                        tabindex="0" required
167                        class="easyui-textbox"
168                        style="width:100%"
169                        type="text"
170                        data-options="prompt:'@Html.DescriptionFor(model => model.Name)',
171                                  required:true,
172                                  validType: 'length[0,50]'
173                                  " />
174               </div>
175               <label class="col-md-2 pr-1 form-label text-right text-danger">@Html.DisplayNameFor(model => model.Code)</label>
176               <div class="col-md-4 mb-1 pl-1">
177                 <input id="@Html.IdFor(model => model.Code)"
178                        name="@Html.NameFor(model => model.Code)"
179                        value="@Html.ValueFor(model => model.Code)"
180                        tabindex="1" required
181                        class="easyui-textbox"
182                        style="width:100%"
183                        type="text"
184                        data-options="prompt:'@Html.DescriptionFor(model => model.Code)',
185                                  required:true,
186                                  validType: 'length[0,12]'
187                                  " />
188               </div>
189               <label class="col-md-2 pr-1 form-label text-right">@Html.DisplayNameFor(model => model.Address)</label>
190               <div class="col-md-4 mb-1 pl-1">
191                 <input id="@Html.IdFor(model => model.Address)"
192                        name="@Html.NameFor(model => model.Address)"
193                        value="@Html.ValueFor(model => model.Address)"
194                        tabindex="2"
195                        class="easyui-textbox"
196                        style="width:100%"
197                        type="text"
198                        data-options="prompt:'@Html.DescriptionFor(model => model.Address)',
199                                  required:false,
200                                  validType: 'length[0,50]'
201                                  " />
202               </div>
203               <label class="col-md-2 pr-1 form-label text-right">@Html.DisplayNameFor(model => model.Contect)</label>
204               <div class="col-md-4 mb-1 pl-1">
205                 <input id="@Html.IdFor(model => model.Contect)"
206                        name="@Html.NameFor(model => model.Contect)"
207                        value="@Html.ValueFor(model => model.Contect)"
208                        tabindex="3"
209                        class="easyui-textbox"
210                        style="width:100%"
211                        type="text"
212                        data-options="prompt:'@Html.DescriptionFor(model => model.Contect)',
213                                  required:false,
214                                  validType: 'length[0,12]'
215                                  " />
216               </div>
217               <label class="col-md-2 pr-1 form-label text-right">@Html.DisplayNameFor(model => model.PhoneNumber)</label>
218               <div class="col-md-4 mb-1 pl-1">
219                 <input id="@Html.IdFor(model => model.PhoneNumber)"
220                        name="@Html.NameFor(model => model.PhoneNumber)"
221                        value="@Html.ValueFor(model => model.PhoneNumber)"
222                        tabindex="4"
223                        class="easyui-textbox"
224                        style="width:100%"
225                        type="text"
226                        data-options="prompt:'@Html.DescriptionFor(model => model.PhoneNumber)',
227                                  required:false,
228                                  validType: 'length[0,20]'
229                                  " />
230               </div>
231               <label class="col-md-2 pr-1 form-label text-right text-danger">@Html.DisplayNameFor(model => model.RegisterDate)</label>
232               <div class="col-md-4 mb-1 pl-1">
233                 <input id="@Html.IdFor(model => model.RegisterDate)"
234                        name="@Html.NameFor(model => model.RegisterDate)"
235                        value="@Html.ValueFor(model => model.RegisterDate)"
236                        tabindex="5" required
237                        class="easyui-datebox"
238                        style="width:100%"
239                        type="text"
240                        data-options="prompt:'@Html.DescriptionFor(model => model.RegisterDate)',
241                                  required:true,
242                                  formatter:dateformatter" />
243               </div>
244             </div>
245           </fieldset>
246         </form>
247       </div>
248     </div>
249   </div>
250 </div>
251 
252  
253 @await Component.InvokeAsync("ImportExcel", new ImportExcelOptions { entity="Company",
254   folder="Companies",
255   url="/Companies/ImportExcel",
256   tpl="/Companies/Download"
257 
258 
259 })
260 
261 @section HeadBlock {
262   <link href="~/css/notifications/toastr/toastr.css" rel="stylesheet" asp-append-version="true" />
263   <link href="~/css/formplugins/bootstrap-daterangepicker/bootstrap-daterangepicker.css" rel="stylesheet" asp-append-version="true" />
264   <link href="~/js/easyui/themes/insdep/easyui.css" rel="stylesheet" asp-append-version="true" />
265 }
266 @section ScriptsBlock {
267   <script src="~/js/dependency/moment/moment.js" asp-append-version="true"></script>
268   <script src="~/js/notifications/toastr/toastr.js"></script>
269   <script src="~/js/formplugins/bootstrap-daterangepicker/bootstrap-daterangepicker.js" asp-append-version="true"></script>
270   <script src="~/js/easyui/jquery.easyui.min.js" asp-append-version="true"></script>
271   <script src="~/js/easyui/plugins/datagrid-filter.js" asp-append-version="true"></script>
272   <script src="~/js/easyui/plugins/columns-ext.js" asp-append-version="true"></script>
273   <script src="~/js/easyui/plugins/columns-reset.js" asp-append-version="true"></script>
274   <script src="~/js/easyui/locale/easyui-lang-zh_CN.js" asp-append-version="true"></script>
275   <script src="~/js/easyui/jquery.easyui.component.js" asp-append-version="true"></script>
276   <script src="~/js/plugin/filesaver/FileSaver.js" asp-append-version="true"></script>
277   <script src="~/js/plugin/jquery.serializejson/jquery.serializejson.js" asp-append-version="true"></script>
278   <script src="~/js/jquery.custom.extend.js" asp-append-version="true"></script>
279   <script src="~/js/jquery.extend.formatter.js" asp-append-version="true"></script>
280   <script>
281         var $dg = $('#companies_datagrid');
282         var EDITINLINE = true;
283         var company = null;
284     var editIndex = undefined;
285     //下載Excel導入模板
286 
287     //執行導出下載Excel
288     function exportexcel() {
289       const filterRules = JSON.stringify($dg.datagrid('options').filterRules);
290       console.log(filterRules);
291       $.messager.progress({ title: '請等待',msg:'正在執行導出...' });
292       let formData = new FormData();
293       formData.append('filterRules', filterRules);
294       formData.append('sort', 'Id');
295       formData.append('order', 'asc');
296       $.postDownload('/Companies/ExportExcel', formData).then(res => {
297         $.messager.progress('close');
298         toastr.success('導出成功!');
299       }).catch(err => {
300         //console.log(err);
301         $.messager.progress('close');
302         $.messager.alert('導出失敗', err.statusText, 'error');
303       });
304 
305     }
306             //彈出明細信息
307     function showdetailswindow(id, index) {
308       const company = $dg.datagrid('getRows')[index];
309       opencompanydetailwindow(company, 'Modified');
310     }
311         function reload() {
312                $dg.datagrid('uncheckAll');
313                $dg.datagrid('reload');
314         }
315             //新增記錄
316        function append() {
317                 company = {
318                     Address: '-',
319                     RegisterDate: moment().format('YYYY-MM-DD HH:mm:ss'),
320                 };
321                 if (!EDITINLINE) {
322                     //彈出新增窗口
323                     opencompanydetailwindow(company, 'Added');
324                 } else {
325                     if (endEditing()) {
326                         //對必填字段進行默認值初始化
327                        $dg.datagrid('insertRow',
328                             {
329                                 index: 0,
330                                 row: company
331                             });
332                         editIndex = 0;
333                        $dg.datagrid('selectRow', editIndex)
334                             .datagrid('beginEdit', editIndex);
335                         hook = true;
336                     }
337                 }
338         }
339             //刪除編輯的行
340         function removeit() {
341                 if (this.$dg.datagrid('getChecked').length <= 0 && EDITINLINE) {
342                     if (editIndex !== undefined) {
343                         const delindex = editIndex;
344                        $dg.datagrid('cancelEdit', delindex)
345                             .datagrid('deleteRow', delindex);
346                         hook = true;
347                     } else {
348                         const rows =$dg.datagrid('getChecked');
349                         rows.slice().reverse().forEach(row => {
350                             const rowindex =$dg.datagrid('getRowIndex', row);
351                            $dg.datagrid('deleteRow', rowindex);
352                             hook = true;
353                         });
354                     }
355                 } else {
356                     deletechecked();
357                 }
358         }
359             //刪除該行
360         function deleteRow(id) {
361             $.messager.confirm('確認', '你確定要刪除該記錄?', result => {
362                 if (result) {
363                     dodeletechecked([id]);
364                 }
365             })
366         }
367             //刪除選中的行
368          function deletechecked() {
369                 const id =$dg.datagrid('getChecked').filter(item => item.Id != null && item.Id > 0).map(item => {
370                     return item.Id;
371                 });
372                 if (id.length > 0) {
373                     $.messager.confirm('確認', `你確定要刪除這 <span class='badge badge-icon position-relative'>${id.length} </span> 行記錄?`, result => {
374                         if (result) {
375                             dodeletechecked(id);
376                         }
377                     });
378                 } else {
379                     $.messager.alert('提示', '請先選擇要刪除的記錄!', 'question');
380                 }
381         }
382             //執行刪除
383         function dodeletechecked(id) {
384             $.post('/Companies/DeleteChecked', { id: id })
385                 .done(response => {
386                     if (response.success) {
387                         toastr.error(`成功刪除[${id.length}]行記錄`);
388                         reload();
389                     } else {
390                         $.messager.alert('錯誤', response.err, 'error');
391                     }
392                 })
393                 .fail((jqXHR, textStatus, errorThrown) => {
394                     $.messager.alert('異常', `${jqXHR.status}: ${jqXHR.statusText} `, 'error');
395                 });
396         }
397             //開啟編輯狀態
398     function onClickCell(index, field) {
399 
400       company = $dg.datagrid('getRows')[index];
401       const _actions = ['action', 'ck'];
402       if (!EDITINLINE || $.inArray(field, _actions) >= 0) {
403         return;
404       }
405 
406       if (editIndex !== index) {
407         if (endEditing()) {
408           $dg.datagrid('selectRow', index)
409             .datagrid('beginEdit', index);
410           hook = true;
411           editIndex = index;
412           const ed = $dg.datagrid('getEditor', { index: index, field: field });
413           if (ed) {
414             ($(ed.target).data('textbox') ? $(ed.target).textbox('textbox') : $(ed.target)).focus();
415           }
416         } else {
417           $dg.datagrid('selectRow', editIndex);
418         }
419       }
420     }
421             //關閉編輯狀態
422     function endEditing() {
423 
424             if (editIndex === undefined) {
425                 return true;
426             }
427             if (this.$dg.datagrid('validateRow', editIndex)) {
428                 $dg.datagrid('endEdit', editIndex);
429                 return true;
430             } else {
431                 const invalidinput = $('input.validatebox-invalid', $dg.datagrid('getPanel'));
432                 const fieldnames = invalidinput.map((index, item) => {
433                     return $(item).attr('placeholder') || $(item).attr('id');
434                 });
435                 $.messager.alert('提示', `${Array.from(fieldnames)} 輸入有誤.`, 'error');
436                 return false;
437             }
438         }
439             //提交保存後台數據庫
440         function acceptChanges() {
441             if (endEditing()) {
442                 if ($dg.datagrid('getChanges').length > 0) {
443                     const inserted = $dg.datagrid('getChanges', 'inserted').map(item => {
444                         item.TrackingState = 1;
445                         return item;
446                     });
447                     const updated = $dg.datagrid('getChanges', 'updated').map(item => {
448                         item.TrackingState = 2
449                         return item;
450                     });
451                     const deleted = $dg.datagrid('getChanges', 'deleted').map(item => {
452                         item.TrackingState = 3
453                         return item;
454                     });
455                     //過濾已刪除的重複項
456                     const changed = inserted.concat(updated.filter(item => {
457                         return !deleted.includes(item);
458                     })).concat(deleted);
459                     //$.messager.progress({ title: '請等待', msg: '正在保存數據...', interval: 200 });
460                     $.post('/Companies/AcceptChanges', { companies: changed })
461                         .done(response => {
462                             //$.messager.progress('close');
463                             //console.log(response);
464                             if (response.success) {
465                                 toastr.success('保存成功');
466                                 $dg.datagrid('acceptChanges');
467                                 reload();
468                                 hook = false;
469                             } else {
470                                 $.messager.alert('錯誤', response.err, 'error');
471                             }
472                         })
473                         .fail((jqXHR, textStatus, errorThrown) => {
474                             //$.messager.progress('close');
475                             $.messager.alert('異常', `${jqXHR.status}: ${jqXHR.statusText} `, 'error');
476                         });
477                 }
478             }
479         }
480         function rejectChanges() {
481             $dg.datagrid('rejectChanges');
482             editIndex = undefined;
483             hook = false;
484         }
485     $(document).ready(function () {
486       //定義datagrid結構
487       $dg.datagrid({
488         rownumbers: true,
489         checkOnSelect: false,
490         selectOnCheck: false,
491         idField: 'Id',
492         sortName: 'Id',
493         sortOrder: 'desc',
494         remoteFilter: true,
495         singleSelect: true,
496         method: 'get',
497         onClickCell: onClickCell,
498         clientPaging: false,
499         pagination: true,
500         striped: true,
501         filterRules: [],
502         onHeaderContextMenu: function (e, field) {
503           e.preventDefault();
504           $(this).datagrid('columnMenu').menu('show', {
505             left: e.pageX,
506             top: e.pageY
507           });
508         },
509         onBeforeLoad: function () {
510           const that = $(this);
511           document.addEventListener('panel.onfullscreen', () => {
512             setTimeout(() => {
513               that.datagrid('resize');
514             }, 200)
515           })
516         },
517         onLoadSuccess: function (data) {
518           editIndex = undefined;
519           $("button[name*='deletebutton']").prop('disabled', true);
520           $("button[name*='savebutton']").prop('disabled', true);
521           $("button[name*='cancelbutton']").prop('disabled', true);
522         },
523         onCheck: function () {
524           $("button[name*='deletebutton']").prop('disabled', false);
525         },
526         onUncheck: function () {
527           const checked = $(this).datagrid('getChecked').length > 0;
528           $("button[name*='deletebutton']").prop('disabled', !checked);
529         },
530         onSelect: function (index, row) {
531           company = row;
532         },
533         onBeginEdit: function (index, row) {
534           //const editors = $(this).datagrid('getEditors', index);
535 
536         },
537         onEndEdit: function (index, row) {
538           editIndex = undefined;
539         },
540         onBeforeEdit: function (index, row) {
541           editIndex = index;
542           row.editing = true;
543           $("button[name*='deletebutton']").prop('disabled', false);
544           $("button[name*='cancelbutton']").prop('disabled', false);
545           $("button[name*='savebutton']").prop('disabled', false);
546           $(this).datagrid('refreshRow', index);
547         },
548         onAfterEdit: function (index, row) {
549           row.editing = false;
550           editIndex = undefined;
551           $(this).datagrid('refreshRow', index);
552         },
553         onCancelEdit: function (index, row) {
554           row.editing = false;
555           editIndex = undefined;
556           $("button[name*='deletebutton']").prop('disabled', true);
557           $("button[name*='savebutton']").prop('disabled', true);
558           $("button[name*='cancelbutton']").prop('disabled', true);
559           $(this).datagrid('refreshRow', index);
560         },
561         frozenColumns: [[
562           /*開啟CheckBox選擇功能*/
563           { field: 'ck', checkbox: true },
564           {
565             field: 'action',
566             title: '操作',
567             width: 85,
568             sortable: false,
569             resizable: true,
570             formatter: function showdetailsformatter(value, row, index) {
571               if (!row.editing) {
572                 return `<div class="btn-group">\
573                                                          <button onclick="showdetailswindow('${row.Id}',  ${index})" class="btn btn-primary btn-sm btn-icon waves-effect waves-themed" title="查看明細" ><i class="fal fa-edit"></i> </button>\
574                                                          <button onclick="deleteRow('${row.Id}',${index})" class="btn btn-primary btn-sm btn-icon waves-effect waves-themed" title="刪除記錄" ><i class="fal fa-times"></i> </button>\
575                                                     </div>`;
576               } else {
577                 return `<button class="btn btn-primary btn-sm btn-icon waves-effect waves-themed" disabled title="查看明細"  ><i class="fal fa-edit"></i> </button>`;
578               }
579             }
580           }
581         ]],
582         columns: [[
583 
584           {    /*名稱*/
585             field: 'Name',
586             title: '<span class="required">@Html.DisplayNameFor(model => model.Name)</span>',
587             width: 200,
588             hidden: false,
589             editor: {
590               type: 'textbox',
591               options: { prompt: '@Html.DescriptionFor(model => model.Name)', required: true, validType: 'length[0,50]' }
592             },
593             sortable: true,
594             resizable: true
595           },
596           {    /*組織代碼*/
597             field: 'Code',
598             title: '<span class="required">@Html.DisplayNameFor(model => model.Code)</span>',
599             width: 120,
600             hidden: false,
601             editor: {
602               type: 'textbox',
603               options: { prompt: '@Html.DescriptionFor(model => model.Code)', required: true, validType: 'length[0,12]' }
604             },
605             sortable: true,
606             resizable: true
607           },
608           {    /*地址*/
609             field: 'Address',
610             title: '@Html.DisplayNameFor(model => model.Address)',
611             width: 200,
612             hidden: false,
613             editor: {
614               type: 'textbox',
615               options: { prompt: '@Html.DescriptionFor(model => model.Address)', required: false, validType: 'length[0,50]' }
616             },
617             sortable: true,
618             resizable: true
619           },
620           {    /*聯繫人*/
621             field: 'Contect',
622             title: '@Html.DisplayNameFor(model => model.Contect)',
623             width: 120,
624             hidden: false,
625             editor: {
626               type: 'textbox',
627               options: { prompt: '@Html.DescriptionFor(model => model.Contect)', required: false, validType: 'length[0,12]' }
628             },
629             sortable: true,
630             resizable: true
631           },
632           {    /*聯繫電話*/
633             field: 'PhoneNumber',
634             title: '@Html.DisplayNameFor(model => model.PhoneNumber)',
635             width: 120,
636             hidden: false,
637             editor: {
638               type: 'textbox',
639               options: { prompt: '@Html.DescriptionFor(model => model.PhoneNumber)', required: false, validType: 'length[0,20]' }
640             },
641             sortable: true,
642             resizable: true
643           },
644           {   /*註冊日期*/
645             field: 'RegisterDate',
646             title: '<span class="required">@Html.DisplayNameFor(model => model.RegisterDate)</span>',
647             width: 140,
648             align: 'right',
649             hidden: false,
650             editor: {
651               type: 'datebox',
652               options: { prompt: '@Html.DescriptionFor(model => model.RegisterDate)', required: true }
653             },
654             formatter: dateformatter,
655             sortable: true,
656             resizable: true
657           },
658         ]]
659       }).datagrid('columnMoving')
660         .datagrid('resetColumns')
661         .datagrid('enableFilter', [
662           {   /*Id*/
663             field: 'Id',
664             type: 'numberbox',
665             op: ['equal', 'notequal', 'less', 'lessorequal', 'greater', 'greaterorequal']
666           },
667           {     /*註冊日期*/
668             field: 'RegisterDate',
669             type: 'dateRange',
670             options: {
671               onChange: value => {
672                 $dg.datagrid('addFilterRule', {
673                   field: 'RegisterDate',
674                   op: 'between',
675                   value: value
676                 });
677 
678                 $dg.datagrid('doFilter');
679               }
680             }
681           },
682         ])
683         .datagrid('load', '/Companies/GetData');
684     }
685     );
686 
687   </script>
688   <script type="text/javascript">
689     //判斷新增編輯狀態
690     var MODELSTATE = 'Added';
691     var companyid = null;
692     function opencompanydetailwindow(data, state) {
693       MODELSTATE = state;
694       initcompanydetailview();
695       companyid = (data.Id || 0);
696       $("#companydetailwindow").window("open");
697       $('#company_form').form('reset');
698       $('#company_form').form('load', data);
699     }
700     //刪除當前記錄
701     function deletecompanyitem() {
702       $.messager.confirm('確認', '你確定要刪除該記錄?', result => {
703         if (result) {
704           const url = `/Companies/Delete/${companyid}`;
705           $.get(url).done(res => {
706             if (res.success) {
707               toastr.success("刪除成功");
708               $("#companydetailwindow").window("close");
709               reload();
710             } else {
711               $.messager.alert("錯誤", res.err, "error");
712             }
713           });
714         }
715       });
716     }
717     //async 保存數據
718     async function savecompanyitem() {
719       const $companyform = $('#company_form');
720       if ($companyform.form('enableValidation').form('validate')) {
721         let company = $companyform.serializeJSON();
722         let url = '/Companies/Edit';
723         //判斷是新增或是修改方法
724         if (MODELSTATE === 'Added') {
725           url = '/Companies/Create';
726         }
727         var token = $('input[name="__RequestVerificationToken"]', $companyform).val();
728         //$.messager.progress({ title: '請等待', msg: '正在保存數據...', interval: 200 });
729         $.ajax({
730           type: "POST",
731           url: url,
732           data: {
733             __RequestVerificationToken: token,
734             company: company
735           },
736           dataType: 'json',
737           contentType: 'application/x-www-form-urlencoded; charset=utf-8'
738         })
739           .done(response => {
740             //$.messager.progress('close');
741             if (response.success) {
742               hook = false;
743               $companyform.form('disableValidation');
744               $dg.datagrid('reload');
745               $('#companydetailwindow').window("close");
746               toastr.success("保存成功");
747             } else {
748               $.messager.alert("錯誤", response.err, "error");
749             }
750           })
751           .fail((jqXHR, textStatus, errorThrown) => {
752             //$.messager.progress('close');
753             $.messager.alert('異常', `${jqXHR.status}: ${jqXHR.statusText} `, 'error');
754           });
755       }
756     }
757     //關閉窗口
758     function closecompanydetailwindow() {
759       $('#companydetailwindow').window('close');
760     }
761 
762     //判斷是否有沒有保存的記錄
763     function companyhasmodified() {
764       return hook;
765     }
766 
767 
768     function initcompanydetailview() {
769       //判斷是否显示功能按鈕
770       if (MODELSTATE === 'Added') {
771         $('#deleteitem-btn-group').hide();
772       } else {
773         $('#deleteitem-btn-group').show();
774       }
775 
776       //回車光標移動到下個輸入控件
777       //日期類型 註冊日期
778       $('#RegisterDate').datebox('textbox').bind('keydown', function (e) {
779         if (e.keyCode == 13) {
780           $(e.target).emulateTab();
781         }
782       });
783     }
784   </script>
785 }

View Code

 

上面View層的代碼非常的複雜,但都是固定格式,可以用scaffold快速生成

  • 配置依賴注入(DI),註冊服務

打開 startup.cs 在 public void ConfigureServices(IServiceCollection services) 註冊服務 services.AddScoped<IRepositoryX, RepositoryX>(); 
services.AddScoped<ICustomerService, CustomerService>();

  • 更新數據庫

EF Core Code-First 同步更新數據庫 
在 Visual Studio.Net 
Package Manager Controle 運行 
PM>:add-migration create_Company 
PM>:update-database 
PM>:更新完成

  • Debug 運行項目 

高級應用

CAP 分佈式事務的解決方案及應用場景 
nuget 安裝組件 
PM> Install-Package DotNetCore.CAP 
PM> Install-Package DotNetCore.CAP.RabbitMQ 
PM> Install-Package DotNetCore.CAP.SqlServer \

  • 配置Startup.cs
 1 public void ConfigureServices(IServiceCollection services)
 2     {
 3       services.AddCap(x =>
 4       {
 5         x.UseEntityFramework<SmartDbContext>();
 6         x.UseRabbitMQ("127.0.0.1");
 7         x.UseDashboard();
 8         x.FailedRetryCount = 5;
 9         x.FailedThresholdCallback = failed =>
10         {
11           var logger = failed.ServiceProvider.GetService<ILogger<Startup>>();
12           logger.LogError($@"A message of type {failed.MessageType} failed after executing {x.FailedRetryCount} several times, 
13                         requiring manual troubleshooting. Message name: {failed.Message.GetName()}");
14         };
15       });
16     }

View Code

 

  • 發布消息
  • 訂閱消息

roadmap

  • 完善主要的開發文檔
  • 支持My SQL數據庫
  • 還會繼續重構和完善代碼
  • 開發Scaffold MVC模板,生成定製化的Controller 和 View 減少開發人員重複工作
  • 完善授權訪問策略(policy-based authorization)
  • 開發Visual Sutdio.net代碼生成插件(類似國內做比較好的52abp)

我的聯繫方式,qq群,贊助二維碼

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

【其他文章推薦】

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

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

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

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

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

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

防堵武漢肺炎 美國家公園仍營業 遊客湧入憂成防疫破口

環境資訊中心綜合外電;姜唯 編譯;林大利 審校

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

【其他文章推薦】

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

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

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

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

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

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