記近一年線上項目經驗及架構變更記錄

簡介

M項目, 是一個電子社保業務系統,2019.8月團隊接手了這個項目的開發工作,到2020.7月客戶的業務量翻了4倍,工作日同時在線員工數量40人,以下記錄總結2019.8-至今項目的架構變化,以及項目中積累的一些經驗。

[2019.8] 項目接手後的初始架構

物理架構

M項目的原始物理架構非常的簡單,屬於最簡單的單機單體系統,大部分服務都寄宿在一臺雙核,8G內存的虛擬機中(包含MySQL數據庫服務和文件存儲服務),只有郵件發送服務使用的是第三方服務SendGrid。相對於客戶最多10人同時在線的需求,日均300張發票的業務場景,此虛擬機的配置和物理架構足夠支撐客戶的業務。

邏輯架構

項目初期的邏輯架構也非常簡單,有2個可用站點,分別是業務系統站點Gateway和項目宣傳站點Portal。所有的業務都封裝在Gateway API中。數據持久化使用了單機版的MySQL實例。

其中Gateway API是項目的核心部分,程序的所有業務代碼都集中於此,小到發送郵件,創建PDF, 大到提交發票,社保索賠,支付訂單都放置與此。這種設計方式很適合初期堆功能,雖然後期客戶發展,這種設計造成了很大的問題,但是在項目初期,我覺着這種方式還是非常適合幫助客戶快速拓展業務的。

在邏輯架構上,我覺着唯一存在問題就是Gateway API中寄宿的Job, 這裏的Job,其實是一個定時任務組件,定期執行一定的業務操作,例如當客戶的社保即將到期,需要發送提醒郵件。這裏的問題是Gateway API是寄宿在VMIIS中的,衆所周知,IIS有自己的回收機制,所以在Gateway API中寄宿一個Job是非常不明智的,因爲很容易出現Job線程被回收,定時任務不能正常執行。這裏爲了避免IIS回收,之前的團隊添加了一個Windows定時任務,這個定時任務每10分鐘調用一次Gateway API來確保Job不被IIS回收掉, 我們稱之爲“土辦法”。實際上這裏將定時任務寄宿在Windows Service纔是更合理的方式。

代碼分層

代碼分層方面,項目從開始就使用了DDD的一套標準分層架構並引入了CQRS,這套分層架構非常的複雜,需要很高的成本,對開發人員的要求很高,特別不適合新開始的項目,特別是在不知道業務前景和未來規模的時候。架構設計的太複雜,會導致功能推進的速度很慢。實際實踐中也印證了這一觀點,當剛接手這個項目的時候,爲了添加一個新功能,我們也確實花費超過正常編碼2-3倍的時間,雖然熟練之後,能逐步縮小這個倍數,但是相對於一些簡單分層就夠,開發速度上還是有一定的差距。

實事求是的講,除了沒有使用DDD中的聚合概念,這套架構的設計上是沒有太大問題的,甚至在後期客戶業務量暴增的時候起到了很大的作用,例如,後來我們在添加業務日誌的時候,由於項目使用了CommandBus, 這個功能實現起來非常的容易。所以我們也不能說當時使用這套分層架構就完全沒有什麼益處,但是總體上來說, 我覺着項目初期還是應該採取一些簡單分層架構,使用架構演進的方式,根據客戶業務來不斷調整架構。

[2019.9] 啓用GCP Cloud Storage和GCP MySQL

接手項目之後,團隊接到的第一個任務是提升項目的安全性和可靠性。爲了通過政府驗收,每個電子社保系統需要保證系統中數據和文檔的安全性,所有的數據都需要保留歷史,所有的文檔,票據都需要保留在澳洲本土至少7年.

所以之前在單服務器中安裝獨立MySQL服務,以及使用服務器自身的文件存儲來保存發票和相關文檔顯然是非常危險的.如果稍有不慎,服務器硬盤損壞,就可能造成無法挽回的損失.

所以這裏,團隊選擇改用GCP MySQL作爲數據庫服務,使用了GCP Cloud Storage作爲文件存儲服務.

針對數據庫服務,我們啓用了Failover機制,當主庫宕機,系統會自動切換到Failover數據庫,保證系統運行。這裏主庫和Failover庫啓用了自動同步,最大程度的保證了數據安全性.

針對文件存儲服務,雖然GCP Storage給出了11個9的可用性保證,但是團隊還是啓用了Replication機制,來保證一個文件同時在2個Bucket中保留備份,最大程度的保證了文檔的安全性

這裏爲了幫助客戶減少開支,團隊同樣使用了GCP Cloud Storage過期策略

  • 客戶上傳的發票附件,系統生成的政府社保記錄,每個生成的客戶對賬單,都使用永不過期策略
  • 系統產生的中間文件,臨時日誌文件都使用1天有效期的過期策略, 過期之後自動移除

這次架構變更的不足之處是,針對Web服務器的災備沒有做好計劃和安排,沒有做到及時的調整架構,此時其實已經察覺到了一些災備方面的問題,如果此時Web服務器宕機,團隊很難在5-10分鐘內啓動一臺新主機來支撐業務,所有的發佈代碼都沒有備份,重新發布代碼需要大量時間.

PS: 此處的另外一個嚴重問題是,客戶站點的域名管理是在另外一家澳洲公司手上,他們只負責將域名指向到指定的公網IP, 但是我們在創建GCP雲主機的時候,沒有購買固定公網IP, 所以一旦主機崩潰,重建新主機,也需要通過第三方公司重新配置域名指向,才能恢復系統使用。這就更難在短時間之後恢復一個已經宕機的系統了。這裏的正確做法應該是購買固定公網IP, 將固定公網IP綁定到主機上,這樣就算重建主機,也能在不通過第三方的情況下,恢復業務

其實此時是修改架構的最好時機,但是由於經驗不足,大好的機會就錯過了,後面由於業務量暴增,大部分時間都在處理新功能和性能方面的問題,所以最終災備部分拖了很久才實現,現在想想以這種裸奔的方式維護一個生產項目,特別是和“錢”相關的項目,還是挺可怕的。

[2019.10] 分離PDF生成服務

在M系統中,有許多PDF生成的場景:

  • 客戶每月交易清單(Client Statement)
  • 當月所有客戶交易清單(All client statements in 1 month)
  • 客戶歷史交易清單(All statements of one client)
  • 供應商匯款清單(Payment Remittance)
  • 自動生成發票(System genereted invoice)

在最初的項目中,開發團隊使用了wkhtmltopdf組件,以及開源庫DinkToPDF來生成項目中所需的PDF文件. 這種方式的問題是,wkhtmltopdf組件不是標準的.NET託管庫,它是一個非託管的C++程序集,寄宿在IIS中有相當大的風險,如果IIS將其回收,會導致整站的PDF生成功能癱瘓。這個問題在客戶業務量小的時候,沒有什麼大問題,就算出問題,也可以通過Stop/Start站點的方式恢復功能,但是在業務量大的時候,就變成了一個很大的問題,用戶和系統的交互非常的頻繁,你很難有機會去做一個Stop/Start站點的操作,而且這樣手動處理生產問題的方式有點太粗暴了。

除此之外,另外一個非常嚴重的問題就是,所有的PDF文件都是用戶請求之後立刻生成的,沒有任何預生成的機制,這種方式會嚴重阻塞Web應用,甚至超時。你可以想想一下,客戶下載每月所有客戶交易清單(All client statements in 1 month)的時候,需要首先生成每個客戶的交易清單,然後將他們組合,如果有2000個客戶,每個客戶的清單PDF大小是200KB, 那麼組裝和下載的文件大小就是接近400MB,這也極大的佔用的服務器的帶寬。

在和客戶討論過實際需求之後,團隊決定將PDF生成服務從Gateway中分離出來,並將每月的客戶交易清單改爲每月1號凌晨閒時預生成,生成之後保存在GCP Cloud Storage中, 每次用戶需要下載的文件時候,服務器端通過GCP SDK生成一個授權URL, 客戶端通過這個授權URL從GCP Cloud Storage直接下載所需文件,這樣也極大的保證的文檔的安全性,只有授權的用戶才能訪問這些文件。

這樣的做的好處有2點:

  • 避免了用戶在忙時佔用大量服務器資源,阻塞網站
  • 所有的文件都是從GCP Cloud Storage中直接下載,減少了Web服務器帶寬的佔用

從這裏開始架構中加入了RabbitMQ消息隊列,這裏使用消息隊列的原因是,在站點失去了立即生成PDF的功能之後,就必須保證所有的PDF都正常生成,否則用戶在界面下載文件的時候會報錯,使用了消息隊列之後,所有的未完成的PDF生成請求都會記錄在消息隊列中,就算PDF Generator崩潰,可以重新啓動一個實例,繼續處理請求,不會導致任何文件生成請求丟失。

PS: 這裏其實沒有考慮死信的問題,後續做Email Service的時候才發現了這個問題。

完成這一部分修改之後,系統的架構圖如下:

[2019.10] 追加業務日誌工具 Log Tools

Log Tools是團隊在10月份添加一個功能,最初只是作爲輔助開發工具,記錄一下程序日誌,幫助團隊開發任務的時候,追蹤Bug。隨着項目的不斷進行和客戶業務的不斷擴大,客戶和團隊也越來越依賴這個工具,追蹤一些線上問題。這個算是本項目中團隊的亮點之一了。

  • 客戶可以通過日誌工具
    • 追蹤員工工作情況,登錄/登出歷史,操作日誌
    • 追蹤系統發出的郵件歷史和詳情
    • [管理員]災備相關的系統還原
  • 團隊可以根據日誌來推斷Bug原因,有些Bug的錯誤信息,甚至可以從工具日誌中直接瞭解到,這在一個金融系統中尤爲重要,如果沒有日誌,你很難從結果上判斷出客戶的操作步驟,以及產生Bug的原因。

引入日誌工具後的架構圖如下:

在Log Tools的實現上,團隊充分利用了當前現有的架構,藉助CommandBus非常容易的就實現了一個日誌記錄工具,因爲所有業務操作的入口都是CommandBus, 所以在CommandBusSend方法中可以記錄所有想要追蹤的信息。

CommandBus.cs

    public void Send<T>(T command) where T : ICommand
    {
        var logger = InjectContainer.GetInstance<ILogFactory>().GetLogger();
        var commandValidator = InjectContainer.GetInstance<ICommandValidator<T>>();

        ...

        if (command != null)
        {
            logger.WriteJson("Request command", command);
        }

        if (commandValidator != null)
        {
            logger.WriteBusinessLog("Validate request", "Gateway is trying to validate the request.");
            ...

            logger.WriteBusinessLog("Validate request", "Validation passed.");
        }

        var handler = InjectContainer.GetInstance<ICommandHandler<T>>();

        if (handler != null)
        {
            try
            {
                handler.Execute(command);
            }
            catch (Exception ex)
            {
                ...

                logger.WriteUnhandledErrorLog(ex.ToString());
                throw ex;
            }
            finally
            {
                handler.Dispose();
            }
        ...
    }

如果想針對某個業務操作啓用日誌,只需要在對應控制器的action方法簽名部分使用DomainLogger特性登記一下需要記錄的業務操作類型即可。

    [Route("Create")]
    [HttpPost]
    [RequestRole(AuthRoleType.Staff, AuthRoleType.Admin)]
    [RequestPermission(PermissionFeatureType.Claims, PermissionActionType.Add, "Requires create support items permission")]
    [DomainLogger("Create Claim Batch")]
    public IActionResult Create([FromBody]Claim_Create_Model dto)
    {
        var command = BuildCommand<CreateClaimCommand>();

        command.Invoice_Item_IDs = dto.invoice_item_ids;

        CommandBus.Send(command);

        return CommandResult(command.ExecuteResult);
    }

[2019.11] 從Goolge Cloud轉移到AWS, 分離測試和生產環境

2019年11月,團隊進行了一次整體站點的遷移,整個M站點從Google Cloud轉移到了AWS。這其中的主要原因是Google Cloud的很多服務不太專業, 中間由於Failover數據庫出現了一次非程序導致的崩潰問題,提了多次Ticket沒有解決,最終還是團隊自己在Google Cloud論壇中自己找到的解決的方法,所以團隊決定進行整站的遷移,使用穩定性更強,服務更好的AWS.

這裏服務轉移的對應關係如下:

  • GCP VM Instance -> AWS EC2
  • GCP Storage -> AWS S3
  • GCP MySQL -> AWS RDS MySQL

變更之後的架構圖:

這次變更的時候,團隊吸取了之前教訓,在創建AWS EC2 Instance的時候,購買了固定公網IP, 這樣我們可以將這個公網IP綁定到任意AWS EC2實例或者負載均衡器上。

PS: 使用AWS EC2的時候購買固定公網IP其實還是挺重要的,因爲如果沒有指定固定公網IP, AWS EC2實例在Stop/Start之後,公網IP會重新分配, 這點要特別注意。

在這次變更中,我們還通過購買額外的AWS EC2實例和AWS RDS MySQL實例,分離了測試和生產環境,這樣進一步的提高的Gateway站點的業務處理能力,減少了開發對生產環境的印象。

[2020.3] Email Service變更, SendGrid不再支持BCC

在2020年3月份,團隊收到第三方郵件提供商SendGrid的預警郵件,他們的郵件服務將不再支持BCC暗送功能,但是在M客戶的實際業務中,BCC暗送的使用非常的頻繁,這一次的功能變更導致我們不得不拋棄第三方郵件服務SendGrid, 改用基於Mailkit開發的郵件發送服務。爲了保證郵件服務的健壯性,團隊依然使用了RabbitMQ消息隊列來傳遞待發送的郵件請求。

這裏看似沒有什麼大問題,但是實際操作中團隊遇到了一些沒有預想到的錯誤。

  • 客戶使用的是Office 365郵箱,Office 365單賬戶每日最多發送10000封郵件,超出之後郵件服務異常,這在之前使用SendGrid發送郵件時是從來沒有遇到過的。
  • 當消息隊列中某個發送郵件請求處理異常的時候,會導致整個消息隊列阻塞,一直處理這個異常的請求,而團隊沒有加入死信隊列的設置,導致隊列積壓,最多了時候積壓了1000+封郵件。

Office 365單賬戶單日10000封郵件問題

Office 365郵箱,單賬戶每日最多發送10000個郵件請求,超出之後,剩餘額度會在第二天0點重置。在客戶業務少的時候,團隊並沒有發現這個問題,直到2020.4.1日客戶首次單日發送數量超過10000之後,團隊才發現這個問題。

PS: Office 365郵箱的每日10000封發件閾值是無法更改的,就算你講郵箱賬號升級到最高級別,此閾值也沒有任何變化

針對這個問題團隊的解決方式是,使用多個主賬號基於Shared Box輪換髮送郵件來解決。

這裏需要說明一下項目使用的發件箱是幾個Shared Box, 所謂的Shared Box就是可以掛靠在多個主郵箱下面的共享郵箱,使用主郵箱賬戶登錄之後,可以使用Shared Box郵箱發件,這裏主郵箱會作爲主體來計算剩餘的發件額度。

已下圖爲例,[email protected][email protected]是2個Shared Box, [email protected][email protected]是2個主賬號。當你使用[email protected][email protected]登錄郵箱之後,你就可以使用[email protected][email protected]作爲發件人發送郵件,產生的額度是算在主賬號[email protected][email protected]下面的,不會算在Shared Box郵箱上,這樣你就可以用[email protected][email protected]賬號以[email protected][email protected]的名義發送20000封郵件

所以我們藉此可以根據客戶每日大概的發件數量,添加多個主賬號,並分配相同的Shared Box,當一個主賬號由於額度用滿導致發送失敗之後,我們可以自動切換到下一個可用的主賬號。這樣單日的10000封郵件的額度問題就解決了。

PS: 之前一直有個疑問就是SendGrid爲啥沒有這個問題?後來研究發現,SendGrid發件使用的是匿名服務器,發件人你填誰的都可以,發件不會經過發送人的郵件服務器,只是作爲收件人收到郵件的時候,會收到提示,“當前郵件不是從真實的發件服務器發送過來的”。因此SendGrid也就根本沒有什麼10000封郵件請求的限制了。

異常消息的處理

2020.4月某天團隊發現生產事故,消息隊列中堆積了1000多封郵件,具體的原因是有一個異常消息進入隊列,一直沒有被消費掉,導致後續的郵件發送請求無法及時處理。此時團隊才意識到此處功能缺少了一個死信隊列的防護。

PS: 所謂的死信隊列 , 即一個隊列中的消息變爲死信之後,它能被重新publish到另外一個死信Exchange中,死信Exchange將消息轉發到另外一個隊列中,這個隊列就是死信隊列

使用死信隊列的好處就是當出現不能及時消費的消息是,隊列不會被阻塞,後續的消息可以繼續被消費,團隊可以針對死信隊列中的消息做調試和處理,手動消費掉這些異常的消息。

不過團隊在當前項目中,沒有直接使用死信隊列,而是設計一個更爲完善的處理方式。團隊爲郵件發送功能設計裏3個隊列,EmailRequestQueueEmailRequestFailureQueueEmailRequestUnavailableQueue

  • EmailRequestQueue中存放了所有待發送的郵件請求
  • EmailRequestFailureQueue中存放了所有發送失敗3次的郵件請求
  • EmailRequestUnavailableQueue中存放了所有的異常請求

當一個正常的郵件請求進入隊列,處理程序處理請求時發生異常,且異常次數小於3時,程序會將消息ACK, 隨後消息請求上記錄失敗次數+1, 並將其重新放入EmailRequestQueue隊列中。

這樣就算針對當前請求,處理程序出現異常,後續的郵件請求也可以正常被處理,當其他消息都處理完成之後,處理程序纔會來重新嘗試處理之前異常的消息,相當與是重試了一次。

當消息重試了3次都不能正常處理的,程序會將其ACK,並轉發到EmailRequestFailureQueue中,並向團隊發送Slack消息/郵件提醒,有異常發件請求。

針對這部分的消息,團隊需要立即手動調試查找失敗原因並手動消耗掉。

最後,如果一個非法的請求進入隊列,程序會將其ACK,並轉發到EmailRequestUnavailableQueue中,避免阻塞消息隊列,這種消息一般都是由於錯誤格式的郵件請求導致的,所以團隊並不需要立刻處理,每日統一處理一次即可。

完成以上修改之後,架構圖如下:

[2020.6] 使用Slack作爲通知服務

Slack是一個以聊天羣組爲基礎的企業溝通和協作工具,Slack提供了非常豐富的接口,可以和大部分的工具集成,而且提供了多平臺的客戶端,保證了消息能及時準確的接收。開發人員甚至可以自己創建一個Slack App, 將自己的程序與Slack集成在一起。

使用Slack的免費版本,

  • 開發人員可以創建最多5個Slack App
  • 每個頻道內可以發送的消息是無限的,但是僅保留最新的10000個消息

團隊選擇Slack的初衷,是作爲日誌工具和運維工具, 在測試環境和生產環境產生的所有異常日誌都可以直接輸出到Slack頻道。在使用Slack之前,團隊一直使用郵件通知來處理類似問題,但是問題是有時候郵箱服務會不穩定,Slack相較於郵件通知穩定性更好。

接入Slack之後的架構圖:

[2020.6] 修改文件上傳方式

Gateway系統原先的文件上傳方式,是非常傳統的Web文件上傳方式,客戶通過瀏覽器發送請求,將文件流發送給服務器,服務器再負責將文件流保存到其他存儲位置,由於當前項目中團隊使用了AWS S3作爲存儲服務,所以服務器在接收到客戶發送的請求之後,又將當前請求的文件流通過AWS S3 SDK發送到了AWS S3

從整個流程上,不難發現,這種上傳方式,文件流其實被傳輸了2遍,而且上傳的速度很大程度取決於服務器的帶寬,而當前項目的主機是一臺m2.xlarge, 它的網絡配置是Low(後續升級到了m2.x2large, 網絡配置是Low to Moderate), 即低網絡帶寬,所以當用戶大規模上傳文件的時候,就會佔用服務器的大量帶寬,導致服務器響應速度大幅度下降。

所以這裏團隊修改了上傳文件的方式,改爲客戶通過瀏覽器,將文件流直接上傳到AWS S3, 然後根據上傳的結果,將文件基本信息保存到服務器, 這樣完全釋放了服務器應對大規模文件上傳的帶寬壓力。

PS: 這裏的文件上傳藉助了AWS S3 Javascript SDK

當然這種方式還是有2個需要注意的問題

  • 使用這種上傳方式,需要保證所有的客戶網絡都能直接訪問到AWS S3,如果是國內項目或者客戶網絡屏蔽AWS,可能沒有辦法這麼實現。
  • [重點]使用AWS S3 Javascript SDK上傳文件,需要前端代碼中放置一個Access key, 請務必保證這個Key的授權範圍,不要把不必要權限,特別是不要把FullAccess權限應用到這個Key上,因爲前臺代碼人人可以獲得,如果這個Key的權限過高,會將整個雲資源處於非常危險的境地。這裏最安全的做法是僅給出S3的PUTPOST的權限,僅能上傳到一個指定桶即可。

[2020.7] Gateway中將Job移出

從2019.8-2020.7, MPG的業務量增長了4倍,同時在線的員工數量又原來的10+變爲了現在的40人(這還沒有包含社保客戶和商店用戶的在線數量),由於訪問量的增大,網站性能和業務處理速度大幅度下降,縱向升級的方式的瓶頸越發明顯,性能問題變成比業務功能開發更緊迫的問題。項目需要開始往分佈式項目發展,Web App需要實現橫向擴展。

這裏阻礙實現橫向擴展的一個問題就是整個項目的定時任務功能都是寄宿在Web App實例中的,如果保持現有架構,這裏就要處理多Web App中定時任務的併發問題,這看起來非常不合理。因此定時任務的功能勢必要移出Web App服務器,放到一個額外主機Service服務器中。

由於之前我們已經從Gateway API中移除了PDF GeneratorEmail Service,所以這裏自然就可以將這些服務都轉移到新主機中,這樣也減小了Web App服務器的一些壓力。

PS: 其實這裏應該將RabbitMQ也換成獨立PAAS服務,或者移動到Service主機中,這樣Web App服務器就變成了一個更純粹的Web host。

更新後代碼架構圖:

經驗和教訓

生產環境如何實現近似無Downtime部署?

對於生產環境,特別是高併發、高可用的生產項目,項目部署都是一個非常頭疼的問題。以當前項目爲例,在2019年的時候,由於業務量小,客戶員工都在一個辦公室上班,如果生產環境發現bug,團隊可以在更改代碼之後,發佈hotfix的時候,通知所有員工暫停辦公,然後使用Stop站點->發佈代碼->Start站點這種粗暴的方式來發布項目。進入2020年之後,隨着客戶業務量和同時在線人數的激增,以及疫情導致的遠程辦公,團隊已經不可能選擇使用Stop站點->發佈代碼->Start站點的方式來發布項目了,如果稍有不慎,就會打斷客戶員工正在執行的操作,造成不可挽回的錯誤。但是如果放棄白天部署代碼,就意味着起碼要承受1天的錯誤數據,這對於一個與“錢”相關的系統來說,簡直就是無法想象的。

PS: Stop站點->發佈代碼->Start站點這幾步就算操作的速度再快,也會花費幾秒,甚至幾十秒的時間,更不用說有的請求未結束,導致程序集被佔用,導致新代碼無法複製的問題。

團隊必須實現一種無Downtime的優雅部署方式。

如果是一個已經啓用負載均衡的分佈式項目,實現起來可能更容易一些,我們可以通過創建新實例->發佈新代碼->修改負載權重,將新請求轉發到新實例-> 移除舊實例的方式完成無Downtime部署。

由於MPG現在還沒有達到需要負載均衡的程度,所以這裏團隊不能使用此方案,只能使用了IIS的回收機制,實現了一種近似無Downtime的部署方式。

其實和很多人一樣,我之前一直認爲IIS的回收和停止是沒有區別的,後來在幾位大神的指點下,自己做了下測試,確實如很多人所說,IIS的回收效果並不像直接停止站點那樣粗暴,當IIS執行回收操作的時候,未結束的請求還是會使用舊代碼來處理,但是新進入的請求,會自動使用新代碼來處理,這樣就保證了未結束的請求不會被意外中斷,新進入的請求也能正確的處理。

那麼我們的發佈流程就可以變爲這個樣子:

  • 如果服務器中舊代碼的目錄是A, 團隊會創建一個新的目錄B, 並將新代碼發佈到目錄B
  • 直接切換當前站點的映射目錄,由A->B
  • 回收當前站點的應用連接池

這3步操作甚至可以做成腳本自動化完成。大部分場景下都沒有問題,只是在測試的時候發現,如果有長連接請求存在,回收之後,當前請求拿不到正確相應的問題,所以這裏只能說是一種近似無Downtime的發佈方式,但總體上講比Stop站點->發佈代碼->Start站點安全的多。

PS: 這裏其實使用Linux主機,配合nginx, consul, consul template和容器化技術也能實現類似的效果,但是當前項目還沒有完成容器化的改造,所以這裏也不考慮。

.NET Core項目中使用了大量Windows支持的路徑格式,Linux不支持

當前項目中使用了很多的路徑拼接,拼接的格式都是Windows路徑格式。

    public class FileFolderDefine
    {
        public static string LogFileFolder = "\\wwwroot\\log";

        public static string TempFolder = "\\wwwroot\\cache\\temp\\";

        public static string ClientApplicationDocumentFolder = "\\wwwroot\\upload\\client-application-document\\";

        public static string ClientDocumentFolder = "\\wwwroot\\upload\\client-document\\";

        public static string ClientStandingAuthorityDocumentFolder = "\\wwwroot\\upload\\client-standing-authority-document\\";

        public static string ClientNoteDocumentFolder = "\\wwwroot\\upload\\client-note-document\\";

        ...
    }

這樣寫的缺點就是當前項目只能跑在Windows上,不能在Linux上,甚至Docker容器中運行。這裏正確的寫法應該是使用Path.Combine方法進行路徑拼接,這樣纔是最完備的方式。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章