分類
發燒車訊

特斯拉要求員工 4/29 復工,加州工廠可能提前恢復生產

特斯拉已通知部分員工,要求 4 月 29 日重返工作崗位,雖然加州超級工廠所在地的公共衛生部門還沒有公告解除和放鬆居家隔離。特斯拉要求員工提前復工是為了 5 月 3 日前準備好恢復生產。

特斯拉加州超級工廠所在地 Fremont 的公共衛生部門仍然沒有解除居家隔離,超級工廠屬於非必要生產製造工廠,僅可維持最基本營運,生產線處於停產狀態。特斯拉計劃在 5 月 4 日恢復生產,但在正式復工前主管已通知部分員工,要求 4 月 29 日到工廠報到,主要是沖壓、油漆業務員工。特斯拉沒有透露提前復工的員工數量。

Fremont 所屬 Alameda 郡新聞發言人 Ray Kelly 表示,目前居家隔離沒有任何調整,預期 5 月 3 日前公共衛生部門會說明,可能延長、放鬆或完全解除。

特斯拉加州工廠在 3 月 19 日接到政府部門通知,必須停止生產,特斯拉沒有第一時間配合政府部門調整生產線,繼續生產了 5 天,法務團隊也持續與政府部門溝通,特斯拉認為電動車生產屬於必要業務,不能因居家隔離停產,同時為員工提供必要的健康保障措施。停產前特斯拉會測量進入工廠的員工體溫,並向部分員工發放口罩。

政府部門強制要求停工後,特斯拉通知所有員工,除必要工作外,所有員工在家上班,部分無法在家工作的員工列為休假,降低員工薪水,並停止與外部承包商臨時性員工合作。

儘管員工對復工非常期待,但工廠環境和作業方式將使降低傳染變得困難。截至 4 月 26 日,Alameda 郡共報告 1,468 例武漢肺炎病例,52 例死亡,特斯拉工廠大部分員工都住在這區。

選擇在 4 月 29 日部分員工提前復工,對特斯拉意義不僅是工廠恢復營運,也是 4 月 29 日發表第一季財報的日子,投資者會很樂見工廠恢復生產。加州超級工廠生產 Model Y、Model 3、Model X、Model S 電動車,據 Credit Suisse 的分析師估計,關閉後特斯拉每週至少虧損 3 億美元。

關於汽車製造公司何時恢復營運、保障員工安全仍有許多爭議,豐田、Volkswagen、Hyundai 等汽車廠商都計劃 5 月初恢復生產,但汽車工人聯合會代表認為,過早恢復生產對員工非常危險。

(合作媒體:。首圖來源:)

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

【其他文章推薦】

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

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

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

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

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

分類
發燒車訊

急轉彎!特斯拉取消提前復工,股價由漲轉跌

先前消息傳出,電動車大廠特斯拉(Tesla)要求員工於 29 日返回工作崗位,以便讓加州佛利蒙(Fremont)組裝廠恢復生產。但隨著該廠所在的舊金山灣區六郡宣佈延長居家禁令,特斯拉政策急轉彎,已通知員工無需提前復工。

CNBC 報導,根據特斯拉的內部信件,24 日和週末,特斯拉管理層要求數十名員工於 29 日重返工作崗位,讓佛利蒙廠產線重啟運作。不過,由於舊金山灣區六郡(舊金山、聖克拉拉、聖馬刁、馬林、康特拉哥斯,以及佛利蒙廠所在的阿拉米達郡)27 日宣佈,將「就地避難」(shelter-in-place)令的期限由 5 月 4 日延長至 5 月底,特斯拉隨後也取消本週的復工計畫。

特斯拉人力資源部週一發送內部訊息向員工表示:「按照執行領導團隊的指示,我們將不會在 4 月 29 日星期三恢復工作。請忽略關於本週復工的所有訊息和指示。」

報導指出,特斯拉佛利蒙廠生產 Model S 和 Model X 車型,以及較新的 Model 3 和 Model Y 車型,銷往北美和歐洲市場。位於中國上海的組裝廠,先前也因疫情短暫關閉約兩週,但在當地政府的協助下迅速恢復運作,目前每週營運 6 天。

在佛利蒙廠有望提前復工的利多消息帶動下,27 日特斯拉股價大漲 10.15% 收 798.75 美元,但取消復工的消息公佈後,盤後股價挫跌 2.10% 至 782.00 美元。今年以來,特斯拉股價累計飆漲 90.98%。

《華爾街日報》先前報導,根據特斯拉 4 月 2 日資料,2020 年第一季交車數達 88,400 輛,較去年同期成長 40%,雖然疫情導致整體車市市況不振,但特斯拉仍維持原先銷售目標。

今年初,特斯拉執行長馬斯克(Elon Musk)預估,特斯拉今年電動車銷量將維持強勁成長,比去年至少成長 36%,全球總交車數可望「輕鬆突破」(comfortably exceed)50 萬輛。

特斯拉預計 29 日發表 2020 年第一季財報。

(本文內文由  授權使用;首圖來源: CC BY 2.0)

延伸閱讀:

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

【其他文章推薦】

※想知道網站建置網站改版該如何進行嗎?將由專業工程師為您規劃客製化網頁設計後台網頁設計

※不管是台北網頁設計公司台中網頁設計公司,全省皆有專員為您服務

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

※帶您來看台北網站建置台北網頁設計,各種案例分享

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

分類
發燒車訊

吃到飽變吃不飽?電動機車商用資費為何如此難算?

近日 Gogoro 電池月租吃到飽方案引發爭議,對於如何定義商用,以及如何舉證開罰,各界都有不同看法,但 Gogoro 先是強調不再寬待,昨夜又臨時發表聲明政策轉彎,然而品牌形象已經產生傷害。究竟 Gogoro 為何如此堅持,而電動機車的資費又應該怎麼設計會更合理呢?

近日由於網友貼出了一張 Gogoro 寄送的「違規使用通知信」,而讓吃到飽方案成為爭論焦點。我們快速整理一下目前的重點。

  1. Gogoro 月租 899 吃到飽方案,禁止商業使用。
  2. 連續兩個月里程超過 1,600 公里,將被視為商業使用而開罰。
  3. 用戶收到通知後,可以回寄照片證明是用於出遊或長程通勤,即可免罰。
  4. 有路人開始檢舉外送員騎 gogoro 送餐。
  5. Gogoro 發公開信,5/10 起若被檢舉,無論里程長短,將直接變更為商用方案。
  6. Gogoro 修改標準,需連續兩個月里程超過 1,600 公里且被檢舉商業使用才開罰。

且不談這次資費爭議,我們此時可以想的一件事情是,如果燃油車終將被淘汰,電動車需要怎樣的能源費用標準才合理?

假設以每月 1,600 公里為使用里程來計算,目前各種能源方案以 Gogoro 商業型最貴,七期燃油車最便宜,充電式機車在光陽調降月租費用之後,如果採用兩顆電池方案,再加上全部在家充電,費用也相當便宜。

每月騎 1,600 公里,機車能源費用比較。(圖片來源:科技新報製)

不過 IONEX 方案並未說明是否可作為商業使用,而且月租費 398 方案限定綁約兩年,期滿後回到原價 598 元,這個方案還提供 2,000 公里里程,算是相當優惠,如果能夠在家充電的話,是一個不錯的選項。(充電時間約 4 小時)

而燃油車在油價狂降的此刻,商用優勢更為明顯,即使九五汽油價格回升到 30 元,每月費用仍然不到一千元,當然前提是要騎乘七期燃油車,才有每公升 50 公里的低油耗表現。

Gogoro 商業方案的天價,讓人望之卻步,為什麼會訂出這麼高的金額呢?雖然 Gogoro 官方並未明說,但顯然換電站建置與電池成本,如果在頻繁換電情況下,確實讓 Gogoro 電網不堪負荷,而原本換電的優勢也因為電池來不及充飽而打折,因此官方才祭出強硬手腕。

Gogoro 第二次政策轉彎,重新定義吃到飽違約標準。(Source:)

但 Gogoro 滿街跑對於官方來說又是最佳宣傳,所以之前才會容許模糊地帶存在,但是當其他車主開始檢舉之後,官方也不得不有所回應。經過兩次轉彎,最新的定調是,連續兩個月里程超過 1,600 公里且經檢舉才會視為商業使用。換句話說,如果偶爾兼差外送,並不會被追討違約金。

按照 Gogoro 官方說法,為了 99% 的用戶著想,他們願意放寬認定標準,但也看得出來,換電站與電池流通量不足,才是這次爭議真正的核心。否則何必為了 0.3% 的極少數用戶,而鬧出滿城風雨。

而充電式機車像是 e-moving 推出的商用版 ie PICKUP,則看準 Gogoro 在這個領域的不足,期望能夠搶佔商用電動機車市場,電池租賃方案分別為 399 元/月基礎型(家充不限里程)、599 元/月輕量型提供 100 分鐘超級充電時數、799 元/月進階型提供 400 分鐘,合約皆為 2 年一簽,車輛定價則為 83,800 元。

光陽 IONEX 的電池租用方案費用較低,但需要用戶自行在家充電,或是找快充站付費充電。(圖片來源:)

那麼充電式機車會是商用機車的新未來嗎?這仍要取決於未來充電式機車的性能是否有充足進步,以 IONEX 為例,定價 66,800 元新台幣,極速在 60 km/h 以下,在理想狀態下的滿電續航里程為 60 km,而快充到滿需要一個小時(額外付費),要作為商業使用,恐怕還有所不足。更何況當前資費方案,其實是因為用戶量極少,才推出的短期優惠,未來如果用戶增加,會否漲價,或是加入禁止商用條款也未可知。

電動車要商用化的另一項挑戰,來自於維修保養體系,對於商業用戶來說,時間就是金錢,而據點少、難預約的電動機車服務站,在這一點就輸給發展許久的油車一大截了。

以目前兩種電動機車的型態來看,換電系統對於使用者來說比較符合商用需求,但營運商成本較高;充電系統雖然有價格優勢,卻輸在車輛性能與時間彈性上。在可見的將來,全面禁用燃油車幾乎已是定局,若要讓商用機車能夠全面電動化,勢必需要更多的基礎建設(充電站、換電站、保修據點)才能拉低成本與里程焦慮,在那之前,恐怕難有比現在更好的作法。

最終我們建議,Gogoro 不該繼續在模糊地帶打轉,而是仔細估算商用方案的定價,相信如果能夠將方案價格調降到 1,500 元以下,或是與外送平台、快遞業者合作推優惠方案,讓商用族群可以正正當當的「吃到飽」,而不是每個月精算里程才是正途。試想,如果滿街的外送員都騎電動車,不正是電動車的一大勝利嗎?

(合作媒體:。首圖來源:)

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

【其他文章推薦】

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

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

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

南投搬家前需注意的眉眉角角,別等搬了再說!

新北清潔公司,居家、辦公、裝潢細清專業服務

分類
發燒車訊

「居家避疫」未解封,特斯拉宣布延期復工至少一週

美國加州尚未解除居家避疫(Shelter in Place)令,電動車大廠特斯拉(Tesla)位於加州費利蒙(Fremont)的組裝廠生產因此陷入停頓。5 月 1 日,特斯拉以電子郵件通知正在休無薪假的美國員工,將延期復工至少一週,具體時間待定。

CNBC、路透社報導,由於舊金山灣區六郡(包括舊金山、聖克拉拉、聖馬刁、馬林、康特拉哥斯,以及佛利蒙廠所在的阿拉米達郡)下達居家避疫令,特斯拉佛利蒙廠已於 3 月 24 日暫停生產。因疫情未完全降溫,當地政府 4 月 27 日宣布,將居家避疫令期限由 5 月 4 日延長至 5 月底,特斯拉隨後也取消原定復工計畫。

特斯拉北美人力資源主管 Valerie Capers Workman 在內部電子郵件表示,主管通知復工日期之前,休無薪假中的員工將保持休假狀態,預計需至少再等一週。在休無薪假期間,員工保有領取失業救濟金的資格,而在家工作或維持工廠基本營運的員工也將維持現有作業方式,直到另行通知。

受疫情因素影響,4 月 7 日,特斯拉宣布啟動無薪假機制,所有時薪制員工休假至 5 月 4 日,可在家工作或任職必要職務的月薪制員工,依據職等不同,暫時減薪 10%~30% 不等,其餘無法在家工作,且未分配到必要工作的月薪制員工,也必須休無薪假。

在此之前,特斯拉已通知人力派遣公司,針對加州費利蒙廠及內華達州的電動車電池廠實施裁員,影響約數百名約聘員工。

1 日特斯拉股價重挫 10.30% 收 701.32 美元,原因是特斯拉執行長(Elon Musk)在 Twitter 發文稱,他認為特斯拉的股價過高。今年以來,特斯拉股價累計飆漲 67.65%,遠優於大盤標普 500 指數同期間挫跌 12.38%。

(本文內文由  授權使用;首圖來源:Windell Oskay from Sunnyvale, CA, USA [], )

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

【其他文章推薦】

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

網頁設計一頭霧水??該從何著手呢? 找到專業技術的網頁設計公司,幫您輕鬆架站!

※想知道最厲害的台北網頁設計公司推薦台中網頁設計公司推薦專業設計師”嚨底家”!!

※幫你省時又省力,新北清潔一流服務好口碑

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

分類
發燒車訊

談反應式編程在服務端中的應用,數據庫操作優化,提速 Upsert

反應式編程在客戶端編程當中的應用相當廣泛,而當前在服務端中的應用相對被提及較少。本篇將介紹如何在服務端編程中應用響應時編程來改進數據庫操作的性能。

開篇就是結論

接續上一篇《談反應式編程在服務端中的應用,數據庫操作優化,從 20 秒到 0.5 秒》之後,這次,我們帶來了關於利用反應式編程進行 upsert 優化的案例說明。建議讀者可以先閱讀一下前一篇,這樣更容易理解本篇介紹的方法。

同樣還是利用批量化的思路,將單個 upsert 操作批量進行合併。已達到減少數據庫鏈接消耗從而大幅提升性能的目的。

業務場景

在最近的一篇文章《十萬同時在線用戶,需要多少內存?——Newbe.Claptrap 框架水平擴展實驗》中。我們通過激活多個常駐於內存當中的 Claptrap 來實現快速驗證 JWT 正確性的目的。

但,當時有一個技術問題沒有得到解決:

Newbe.Claptrap 框架設計了一個特性:當 Claptrap Deactive 時,可以選擇將快照立即保存到數據庫。因此,當嘗試從集群中關閉一個節點時,如果節點上存在大量的 Claptrap ,那麼將產生大量的數據庫 upsert 操作。瞬間推高數據庫消耗,甚至導致部分錯誤而保存失敗。

一點點代碼

有了前篇的 IBatchOperator,那麼留給這篇的代碼內容就非常少了。

首先,按照使用上一篇的 IBatchOperator 編寫一個支持操作的 Repository,形如以下代碼:

public class BatchUpsert : IUpsertRepository
{
private readonly IDatabase _database;
private readonly IBatchOperator<(int, int), int> _batchOperator;

public BatchUpsert(IDatabase database)
{
_database = database;
var options = new BatchOperatorOptions<(int, int), int>
{
BufferCount = 100,
BufferTime = TimeSpan.FromMilliseconds(50),
DoManyFunc = DoManyFunc
};
_batchOperator = new BatchOperator<(int, int), int>(options);
}

private Task<int> DoManyFunc(IEnumerable<(int, int)> arg)
{
return _database.UpsertMany(arg.ToDictionary(x => x.Item1, x => x.Item2));
}

public Task UpsertAsync(int key, int value)
{
return _batchOperator.CreateTask((key, value));
}
}

然後,只要實現對應數據庫的 UpsertMany 方法,便可以很好地完成這項優化。

各種數據庫的操作

結合 Newbe.Claptrap 現在項目的實際。目前,被支持的數據庫分別有 SQLite、PostgreSQL、MySql 和 MongoDB。以下,分別對不同類型的數據庫的批量 Upsert 操作進行說明。

由於在 Newbe.Claptrap 項目中的 Upsert 需求都是以主鍵作為對比鍵,因此以下也只討論這種情況。

SQLite

根據官方文檔,使用 INSERT OR REPLACE INTO 便可以實現主鍵衝突時替換數據的需求。

具體的語句格式形如以下:

INSERT OR REPLACE INTO TestTable (id, value)
VALUES
(@id0,@value0),
...
(@idn,@valuen);

因此只要直接拼接語句和參數調用即可。需要注意的是,SQLite 的可傳入參數默認為 999,因此拼接的變量也不應大於該數量。

官方文檔:INSERT

PostgreSQL

眾所周知,PostgreSQL 在進行批量寫入時,可以使用高效的 COPY 語句來完成數據的高速導入,這遠遠快於 INSERT 語句。但可惜的是 COPY 並不能支持 ON CONFLICT DO UPDATE 子句。因此,無法使用 COPY 來完成 upsert 需求。

因此,我們還是回歸使用 INSERT 配合 ON CONFLICT DO UPDATE 子句,以及 unnest 函數來完成批量 upsert 的需求。

具體的語句格式形如以下:

INSERT INTO TestTable (id, value)
VALUES (unnest(@ids), unnest(@values))
ON CONFLICT ON CONSTRAINT TestTable_pkey
DO UPDATE SET value=excluded.value;

其中的 ids 和 values 分別為兩個等長的數組對象,unnest 函數可以將數組對象轉換為行數據的形式。

注意,可能會出現 ON CONFLICT DO UPDATE command cannot affect row a second time 錯誤。

因此如果嘗試使用上述方案,需要在傳入數據庫之前,先在程序中去重一遍。而且,通常來說,在程序中進行一次去重可以減少向數據庫中傳入的數據,這本身也很有意義。

官方文檔:unnest 函數
官方文檔:Insert 語句

MySql

MySql 與 SQLite 類似,支持 REPLACE 語法。具體語句形式如下:

REPLACE INTO TestTable (id, value)
VALUES
(@id0,@value0),
...
(@idn,@valuen);

官方文檔:REPLACE 語句

MongoDB

MongoDB 原生支持 bulkWrite 的批量傳輸模式,也支持 replace 的 upsert 語法。因此操作非常簡單。

那麼這裏展示一下 C# 操作方法:

private async Task SaveManyCoreMany(
IDbFactory dbFactory,
IEnumerable<StateEntity> entities)
{
var array = entities as StateEntity[] ?? entities.ToArray();
var items = array
.Select(x => new MongoStateEntity
{
claptrap_id = x.ClaptrapId,
claptrap_type_code = x.ClaptrapTypeCode,
version = x.Version,
state_data = x.StateData,
updated_time = x.UpdatedTime,
})
.ToArray();

var client = dbFactory.GetConnection(_connectionName);
var db = client.GetDatabase(_databaseName);
var collection = db.GetCollection<MongoStateEntity>(_stateCollectionName);

var upsertModels = items.Select(x =>
{
var filter = new ExpressionFilterDefinition<MongoStateEntity>(entity =>
entity.claptrap_id == x.claptrap_id && entity.claptrap_type_code == x.claptrap_type_code);
return new ReplaceOneModel<MongoStateEntity>(filter, x)
{
IsUpsert = true
};
});
await collection.BulkWriteAsync(upsertModels);
}

這是從 Newbe.Claptrap 項目業務場景中給出的代碼,讀者可以結合自身需求進行修改。

官方文檔:db.collection.bulkWrite ()

通用型解法

優化的本質是減少數據庫鏈接的使用,盡可能在一個鏈接內完成更多的工作。因此如果特定的數據庫不支持以上數據庫類似的操作。那麼還是存在一種通用型的解法:

  1. 以盡可能快地方式將數據寫入一臨時表
  2. 將臨時表的數據已連表 update 的方式更新的目標表
  3. 刪除臨時表

UPDATE with a join

性能測試

以 SQLite 為例,嘗試對 12345 條數據進行 2 次 upsert 操作。

單條併發:1 分 6 秒

批量處理:2.9 秒

可以在該鏈接找到測試的代碼。

樣例中不包含有 MySql、PostgreSQL 和 MongoDB 的樣例,因為沒有優化之前,在不提高連接池的情況下,一併發基本就爆炸了。所有優化的結果是直接解決了可用性的問題。

所有的示例代碼均可以在代碼庫中找到。如果 Github Clone 存在困難,也可以點擊此處從 Gitee 進行 Clone

常見問題解答

此處對一些常見的問題進行解答。

客戶端是等待批量操作的結果嗎?

這是一個很多網友提出的問題。答案是:是的。

假設我們公開了一個 WebApi 作為接口,由瀏覽器調用。如果同時有 100 個瀏覽器同時發出請求。

那麼這 100 個請求會被合併,然後寫入數據庫。而在寫入數據庫之前,這些客戶端都不會得到服務端的響應,會一直等待。

這也是該合併方案區別於普通的 “寫隊列,后寫庫” 方案的地方。

原理上講,這種和 bulkcopy 有啥不一樣?

兩者是不相關,必須同時才有作用的功能。
首先,代碼中的 database.InsertMany 就是你提到的 bulkcopy。

這個代碼的關鍵不是 InsertMany ,而是如何將單次的插入請求合併。
試想一下,你可以在 webapi 上公開一個 bulkcopy 的 API。
但是,你無法將來自不同客戶端的請求合併在同一個 API 裏面來調用 bulkcopy。
例如,有一萬個客戶端都在調用你的 API,那怎麼合併這些 API 請求呢?

如果如果通過上面這種方式,雖然你只是對外公開了一個單次插入的 API。你卻實現了來自不同客戶端請求的合併,變得可以使用 bulkcopy 了。這在高併發下很有意義。

另外,這符合開閉的原理,因為你沒有修改 Repository 的 InsertOne 接口,卻實現了 bulkcopy
的效果。

如果批量操作中一個操作異常失敗是否會導致被合併的其他操作全部失敗?

如果業務場景是合併會有影響,那當然不應該合併。

批量操作一個失敗,當然是一起失敗,因為底層的數據庫事務肯定也是一起失敗。

除非批量接口也支持對每個傳入的 ID 做區別對待。典型的,比如 mongodb 的 bulkcopy 可以返回哪些成功哪些失敗,那麼我們就有能力設置不同的 Tcs 狀態。

哪些該合併,哪些不該合併,完全取決於業務。樣例給出的是如果要合併,應該怎麼合併。不會要求所有都要合併。

Insert 和 Upsert 都說了,那 Delete 和 Select 呢?

筆者籠統地將該模式稱為 “反應式批量處理”。要確認業務場景是否應用該模式,需要具備以下這兩個基本的要求:

  • 業務下游的批量處理是否會比累積的單條處理要快,如果會,那可以用
  • 業務上游是否會出現短時間的突增頻率的請求,如果會,那可以用

當然,還需要考量,比如:下游的批量操作能否卻分每個請求的結果等等問題。但以上兩點是一定需要考量的。

那麼以 Delete 為例:

  • Delete Where In 的速度會比 Delete = 的速度快嗎?試一下
  • 會有突增的 Delete 需求嗎?想一下

小小工具 Zeal

筆者是一個完整存儲過程都寫不出來的人。能夠查閱到這些數據庫的文檔,全靠一款名為 Zeal 的離線文檔查看免費軟件。推薦給您,您也值得擁有。

Zeal 官網地址:https://zealdocs.org/

最後但是最重要!

最近作者正在構建以反應式Actor模式事件溯源為理論基礎的一套服務端開發框架。希望為開發者提供能夠便於開發出 “分佈式”、“可水平擴展”、“可測試性高” 的應用系統 ——Newbe.Claptrap

本篇文章是該框架的一篇技術選文,屬於技術構成的一部分。如果讀者對該內容感興趣,歡迎轉發、評論、收藏文章以及項目。您的支持是促進項目成功的關鍵。

如果你對該項目感興趣,你可以通過 github issues 提交您的看法。

如果您無法正常訪問 github issue,您也可以發送郵件到 newbe-claptrap@googlegroups.com 來參与我們的討論。

點擊鏈接 QQ 交流【Newbe.Claptrap】:https://jq.qq.com/?_wv=1027&k=5uJGXf5。

您還可以查閱本系列的其他選文:

  • Newbe.Claptrap – 一套以 “事件溯源” 和 “Actor 模式” 作為基本理論的服務端開發框架
  • 十萬同時在線用戶,需要多少內存?——Newbe.Claptrap 框架水平擴展實驗
  • 談反應式編程在服務端中的應用,數據庫操作優化,從 20 秒到 0.5 秒
  • 談反應式編程在服務端中的應用,數據庫操作優化,提速 Upsert
  • Newbe.Claptrap 項目周報 1 – 還沒輪影,先用輪跑

GitHub 項目地址:https://github.com/newbe36524/Newbe.Claptrap

Gitee 項目地址:https://gitee.com/yks/Newbe.Claptrap

 

  • 本文作者: newbe36524
  • 本文鏈接: https://www.newbe.pro/Newbe.Claptrap/Reactive-In-Server-2/
  • 版權聲明: 本博客所有文章除特別聲明外,均採用 BY-NC-SA 許可協議。轉載請註明出處!

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

【其他文章推薦】

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

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

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

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

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

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

分類
發燒車訊

SpringBoot2.x的依賴管理

前提

這篇文章是《SpringBoot2.x入門》專輯的第1篇文章,使用的SpringBoot版本為2.3.1.RELEASEJDK版本為1.8

主要梳理一下SpringBoot2.x的依賴關係和依賴的版本管理,依賴版本管理是開發和管理一個SpringBoot項目的前提。

SpringBoot其實是通過starter的形式,對spring-framework進行裝箱,消除了(但是兼容和保留)原來的XML配置,目的是更加便捷地集成其他框架,打造一個完整高效的開發生態。

SpringBoot依賴關係

因為個人不太喜歡Gradle,所以下文都以Maven舉例。

SpringCloud的版本(SpringCloud的正式版是用倫敦地鐵站或者說倫敦某地名的英文名稱作為版本號,例如比較常用的F版本Finchley就是位於倫敦北部芬奇利)管理不同,SpringBoot的依賴組件發布版本格式是:X.Y.Z.RELEASE。因為SpringBoot組件一般會裝箱為starter,所以組件的依賴GAV一般為:org.springframework.boot:spring-boot-starter-${組件名}:X.Y.Z.RELEASE,其中X是主版本,不同的主版本意味着可以放棄兼容性,也就是SpringBoot1.xSpringBoot2.x不保證兼容性,而組件名一般是代表一類中間件或者一類功能,如data-redisspring-boot-starter-data-redis,提供Redis訪問功能)、jdbcspring-boot-starter-jdbc,提供基於JDBC驅動訪問數據庫功能)等等。以SpringBoot當前最新的發布版本2.3.1.RELEASEorg.springframework.boot:spring-boot-starter:jar:2.3.1.RELEASE為例,用mvn dependency:tree分析它的依賴關係如下:

這個依賴樹也印證了starter是基於Spring項目裝箱和擴展的。

SpringBoot依賴管理

如果使用Spring Initializr創建一個SpringBoot項目的話,那麼會發現項目的POM文件中會加入了一個parent元素:

<parent>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-parent</artifactId>
    <version>2.3.1.RELEASE</version>
    <relativePath/> <!-- lookup parent from repository -->
</parent>

其實spring-boot-starter-parent相當於作為了當前項目的父模塊,在父模塊裏面管理了當前指定的SpringBoot版本2.3.1.RELEASE所有依賴的第三方庫的統一版本管理,通過spring-boot-starter-parent上溯到最頂層的項目,會找到一個properties元素,裏面統一管理Spring框架和所有依賴到的第三方組件的統一版本號,這樣就能確保對於一個確定的SpringBoot版本,它引入的其他starter不再需要指定版本,同時所有的第三方依賴的版本也是固定的。如項目的POM文件如下:

<!-- 暫時省略其他的配置屬性 -->
<parent>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-parent</artifactId>
    <version>2.3.1.RELEASE</version>
    <relativePath/> <!-- lookup parent from repository -->
</parent>
<groupId>com.example</groupId>
<artifactId>demo</artifactId>
<version>0.0.1-SNAPSHOT</version>
<name>demo</name>
<dependencies>
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter</artifactId>
    </dependency>
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-test</artifactId>
        <scope>test</scope>
        <exclusions>
            <exclusion>
                <groupId>org.junit.vintage</groupId>
                <artifactId>junit-vintage-engine</artifactId>
            </exclusion>
        </exclusions>
    </dependency>
</dependencies>

這樣只需要修改parent元素中的版本號,就能全局更變所有starter的版本號。這種做法其實本質上是把當前項目作為spring-boot-starter-parent的子項目,其實在一定程度上並不靈活。這裏推薦使用另一種方式:通過dependencyManagement元素全局管理SpringBoot版本,適用於單模塊或者多模塊的Maven項目。項目的(父)POM文件如下:

<!-- spring-boot-guide 父POM -->
<properties>
    <spring.boot.version>2.3.1.RELEASE</spring.boot.version>
</properties>
<dependencyManagement>
    <dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-dependencies</artifactId>
            <version>${spring.boot.version}</version>
            <scope>import</scope>
            <type>pom</type>
        </dependency>
    </dependencies>
</dependencyManagement>

然後需要用到其他starter的時候,只需要在dependencies直接引入即可,不再需要指定版本號,版本號由dependencyManagement中定義的版本號統一管理。

<!-- spring-boot-guide/ch0-dependency 子POM -->
<dependencies>
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter</artifactId>
    </dependency>
</dependencies>

SpringBoot依賴覆蓋

有些特殊的情況,可能項目中大部分的starter使用的是相對低的版本,但是由於部分新的功能需要使用到更高版本的個別starter,則需要強制引入該高版本的starter。這裏舉一個例子,項目用到的SpringBoot組件的版本是2.1.5.RELEASE,使用的中間件服務Elasticsearch的版本是7.x,而spring-boot-starter-data-elasticsearch支持的版本如下:

理論上可以一下子升級SpringBoot2.3.1.RELEASE,其實也可以直接指定spring-boot-starter-data-elasticsearch的版本覆蓋掉全局的SpringBoot組件版本,這裏應用了Maven依賴調解原則

<!-- 父POM或者全局POM -->
<properties>
    <spring.boot.version>2.1.5.RELEASE</spring.boot.version>
</properties>
<dependencyManagement>
    <dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-dependencies</artifactId>
            <version>${spring.boot.version}</version>
            <scope>import</scope>
            <type>pom</type>
        </dependency>
    </dependencies>
</dependencyManagement>

<dependencies>
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-web</artifactId>
    </dependency>
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-data-elasticsearch</artifactId>
        <version>2.3.1.RELEASE</version>
    </dependency>
</dependencies>

這樣就能單獨提升spring-boot-starter-data-elasticsearch的版本為2.3.1.RELEASE,其他組件的版本依然保持為2.1.5.RELEASE

小結

目前有兩種常用的方式管理SpringBoot組件的版本(兩種方式二選一):

  1. 配置parent元素,通過項目繼承的方式指定SpringBoot組件的版本號,這是Spring Initializr生成的項目中默認的配置方式。
  2. 配置dependencyManagement元素(推薦此方式),通過(父)POM文件統一指定SpringBoot組件的版本號。

另外,SpringBoot1.x2.x之間有兼容性問題(最明顯的一點是2.x中刪除了1.x中大量的內建類,如果用到了這些SpringBoot中的內建類,容易出現ClassNotFoundException),降級或者升級都有比較大的風險。一般情況下,建議使用同一個大版本進行項目開發,如果確定需要進行大版本切換,請務必做完畢的功能測試。

(本文完 c-1-d e-a-20200628)

技術公眾號(《Throwable文摘》,id:throwable-doge),不定期推送筆者原創技術文章(絕不抄襲或者轉載):

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

【其他文章推薦】

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

網頁設計一頭霧水??該從何著手呢? 找到專業技術的網頁設計公司,幫您輕鬆架站!

※想要讓你的商品成為最夯、最多人討論的話題?網頁設計公司讓你強力曝光

※想知道最厲害的台北網頁設計公司推薦台中網頁設計公司推薦專業設計師”嚨底家”!!

新北清潔公司,居家、辦公、裝潢細清專業服務

分類
發燒車訊

pythonic context manager知多少

Context Managers 是我最喜歡的 python feature 之一,在恰當的時機使用 context manager 使代碼更加簡潔、清晰,更加安全,復用性更好,更加 pythonic。本文簡單介紹一下其使用方法以及常見使用場景。

本文地址:https://www.cnblogs.com/xybaby/p/13202496.html

with statement and context manager

Python’s with statement supports the concept of a runtime context defined by a context manager

new statement “with” to the Python language to make it possible to factor out standard uses of try/finally statements.

在 pep0343 中,通過引入 context manager protocol 來支持 With statement , context manager 是用來管理 context(上下文)的,即保證程序要保持一種特定的狀態 — 無論是否發生異常。可以說,context manager 簡化了對 try-finally 的使用,而且更加安全,更加便於使用。

Transforming Code into Beautiful, Idiomatic Python 中,指出了 context manager 的最顯著的優點:

  • Helps separate business logic from administrative logic
  • Clean, beautiful tools for factoring code and improving code reuse

最廣為人知的例子,就是通過 with statement 來讀寫文件,代碼如下:

with open('test.txt') as f:
    contect = f.read()
    handle_content(content)

上面的代碼幾乎等價於

f = open('test.txt') 
try:
    contect = f.read()
    handle_content(content)
finally:
    f.close()

注意,上面的finally的作用就是保證file.close一定會被調用,也就是資源一定會釋放。不過,很多時候,都會忘了去寫這個finally,而 with statement 就徹底避免了這個問題。

從上述兩段代碼也可以看出,with statement 更加簡潔,而且將核心的業務邏輯(從文件中讀取、處理數據)與其他邏輯(打開、關係文件)相分離,可讀性更強。

實現context manager protocol

一個類只要定義了__enter____exit__方法就實現了context manager 協議

object.__enter__(self)
Enter the runtime context related to this object. The with statement will bind this method’s return value to the target(s) specified in the as clause of the statement, if any.

object.__exit__(self, exc_type, exc_value, traceback)
Exit the runtime context related to this object. The parameters describe the exception that caused the context to be exited. If the context was exited without an exception, all three arguments will be None.

If an exception is supplied, and the method wishes to suppress the exception (i.e., prevent it from being propagated), it should return a true value. Otherwise, the exception will be processed normally upon exit from this method.

Note that __exit__() methods should not reraise the passed-in exception; this is the caller’s responsibility.

__enter__方法在進入這個 context 的時候調用,返回值賦值給 with as X 中的 X

__exit__方法在退出 context 的時候調用,如果沒有異常,后三個參數為 None。如果返回值為 True,則Suppress Exception,所以除非特殊情況都應返回 False。另外注意, __exit__方法本身不應該拋出異常。

例子:BlockGuard

在看c++代碼(如mongodb源碼)的時候,經常看見其用 RAII 實現BlockGuard, 用以保證在離開 Block 的時候執行某些動作,同時,也提供手段來取消執行。

下面用python實現一下:

class BlockGuard(object):
	def __init__(self, fn, *args, **kwargs):
		self._fn = fn
		self._args = args
		self._kwargs = kwargs
		self._canceled = False

	def __enter__(self):
		return self

	def __exit__(self, exc_type, exc_value, traceback):
		if not self._canceled:
			self._fn(*self._args, **self._kwargs)
		self._fn = None
		self._args = None
		self._kwargs = None
		return False

	def cancel(self):
		self._canceled = True


def foo():
	print 'sth should be called'


def test_BlockGuard(cancel_guard):
	print 'test_BlockGuard'
	with BlockGuard(foo) as guard:
		if cancel_guard:
			guard.cancel()
	print 'test_BlockGuard  finish'

用yield實現context manager

標準庫 contextlib 中提供了一些方法,能夠簡化我們使用 context manager,如 contextlib.contextmanager(func) 使我們
無需再去實現一個包含__enter__ __exit__方法的類。

The function being decorated must return a generator-iterator when called. This iterator must yield exactly one value, which will be bound to the targets in the with statement’s as clause, if any.

例子如下:

from contextlib import contextmanager

@contextmanager
def managed_resource(*args, **kwds):
    # Code to acquire resource, e.g.:
    resource = acquire_resource(*args, **kwds)
    try:
        yield resource
    finally:
        # Code to release resource, e.g.:
        release_resource(resource)

>>> with managed_resource(timeout=3600) as resource:
...     # Resource is released at the end of this block,
...     # even if code in the block raises an exception

需要注意的是:

  • 一定要寫 try finally,才能保證release_resource邏輯一定被調用
  • 除非特殊情況,不再 catch exception,這就跟 __exit__ 一般不返回True一樣

例子: no_throw

這是業務開發中的一個需求, 比如觀察者模式,不希望因為其中一個觀察者出了 trace 就影響後續的觀察者,就可以這樣做:

from contextlib import contextmanager

@contextmanager
def no_throw(*exceptions):
	try:
		yield
	except exceptions:
		pass

def notify_observers(seq):
	for fn in [sum, len, max, min]:
		with no_throw(Exception):
			print "%s result %s" % (fn.__name__, fn(seq))

if __name__ == '__main__':
	notify_observers([])

在python 3.x 的 contexlib 中,就提供了一個contextlib.suppress(*exceptions), 實現了同樣的效果。

context manager 應用場景

context manager 誕生的初衷就在於簡化 try-finally,因此就適合應用於在需要 finally 的地方,也就是需要清理的地方,比如

  • 保證資源的安全釋放,如 file、lock、semaphore、network connection 等
  • 臨時操作的復原,如果一段邏輯有 setup、prepare,那麼就會對應 cleanup、teardown。

對於第一種情況,網絡連接釋放的例子,後面會結合 pymongo 的代碼展示。

在這裏先來看看第二種用途:保證代碼在一個臨時的、特殊的上下文(context)中執行,且在執行結束之後恢復到之前的上下文環境。

改變工作目錄

from contextlib import contextmanager
import os

@contextmanager
def working_directory(path):
    current_dir = os.getcwd()
    os.chdir(path)
    try:
        yield
    finally:
        os.chdir(current_dir)

with working_directory("data/stuff"):
    pass

臨時文件、文件夾

很多時候會產生一堆臨時文件,比如build的中間狀態,這些臨時文件都需要在結束之後清除。

from tempfile import mkdtemp
from shutil import rmtree

@contextmanager
def temporary_dir(*args, **kwds):
    name = mkdtemp(*args, **kwds)
    try:
        yield name
    finally:
        shutil.rmtree(name)

with temporary_dir() as dirname:
    pass

重定向標準輸出、標準錯誤

@contextmanager
def redirect_stdout(fileobj):
    oldstdout = sys.stdout
    sys.stdout = fileobj
    try:
        yield fieldobj
    finally:
        sys.stdout = oldstdout

在 python3.x 中,已經提供了 contextlib.redirect_stdout contextlib.redirect_stderr 實現上述功能

調整logging level

這個在查問題的適合非常有用,一般生產環境不會輸出 debug level 的日誌,但如果出了問題,可以臨時對某些制定的函數調用輸出debug 日誌

from contextlib import contextmanager
import logging

logger = logging.getLogger()
logger.setLevel(logging.INFO)

ch = logging.StreamHandler()
ch.setFormatter(logging.Formatter('%(asctime)s - %(levelname)s - %(message)s'))
logger.addHandler(ch)


@contextmanager
def change_log_level(level):
	old_level = logger.getEffectiveLevel()
	try:
		logger.setLevel(level)
		yield
	finally:
		logger.setLevel(old_level)


def test_logging():
	logger.debug("this is a debug message")
	logger.info("this is a info message")
	logger.warn("this is a warning message")

with change_log_level(logging.DEBUG):
	test_logging()

pymongo中的context manager使用

在 pymongo 中,封裝了好幾個 context manager,用以

  • 管理 semaphore
  • 管理 connection
  • 資源清理

而且,在 pymongo 中,給出了嵌套使用 context manager 的好例子,用來保證 socket 在使用完之後一定返回連接池(pool)。

# server.py
@contextlib.contextmanager
def get_socket(self, all_credentials, checkout=False):
    with self.pool.get_socket(all_credentials, checkout) as sock_info:
        yield sock_info
        
# pool.py
@contextlib.contextmanager
def get_socket(self, all_credentials, checkout=False):
    sock_info = self._get_socket_no_auth()
    try:
        sock_info.check_auth(all_credentials)
        yield sock_info
    except:
        # Exception in caller. Decrement semaphore.
        self.return_socket(sock_info)
        raise
    else:
        if not checkout:
            self.return_socket(sock_info)

可以看到,server.get_socket 調用了 pool.get_socket, 使用 server.get_socket 的代碼完全不了解、也完全不用關心 socket 的釋放細節,如果把 try-except-finally-else 的邏輯移到所有使用socket的地方,代碼就會很醜、很臃腫。

比如,在mongo_client 中需要使用到 socket:

with server.get_socket(all_credentials) as sock_info:
    sock_info.authenticate(credentials)

references

With statement

Context Managers

contextlib

what-is-the-python-with-statement-designed-for

Transforming Code into Beautiful, Idiomatic Python

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

【其他文章推薦】

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

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

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

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

※幫你省時又省力,新北清潔一流服務好口碑

分類
發燒車訊

如何在 asp.net core 3.x 的 startup.cs 文件中獲取注入的服務

一、前言

從 18 年開始接觸 .NET Core 開始,在私底下、工作中也開始慢慢從傳統的 mvc 前後端一把梭,開始轉向 web api + vue,之前自己有個半成品的 asp.net core 2.2 的項目模板,最近幾個月的時間,私下除了學習 Angular 也在對這個模板基於 asp.net core 3.1 進行慢慢補齊功能

因為涉及到底層框架大版本升級,由於某些 breaking changes 必定會造成之前的某些寫法沒辦法繼續使用,趁着端午節假期,在改造模板時,發現沒辦法通過構造函數注入的形式在 Startup 文件中注入某些我需要的服務了,因此本篇文章主要介紹如何在 asp.net core 3.x 的 startup 文件中獲取注入的服務

二、Step by Step

2.1、問題案例

這個問題的發現源於我需要改造模型驗證失敗時返回的錯誤信息,如果你有嘗試的話,在 3.x 版本中你會發現在 Startup 類中,我們沒辦法通過構造函數注入的方式再注入任何其它的服務了,這裏僅以我的代碼中需要解決的這個問題作為案例

在定義接口時,為了降低後期調整的複雜度,在接收參數時,一般會將參數包裝成一個 dto 對象(data transfer object – 數據傳輸對象),不管是提交數據,還是查詢數據,對於這個 dto 中的某些屬性,都會存在一定的卡控,例如 xxx 字段不能為空了,xxx 字段的長度不能超過 30

而在 asp.net core 中,因為會自動進行模型驗證,當不符合 dto 中的屬性要求時,接口會自動返回錯誤信息,默認的返回信息如下圖所示

可以看到,因為這裏其實是按照 rfc7231這個 RFC 協議返回的錯誤信息,這個並不符合我的要求,因此這裏我需要改寫這個返回的錯誤信息

自定義 asp.net core 的模型驗證錯誤信息方法有很多種,我的實現方法如下,因為我需要記錄請求的標識 Id 和錯誤日誌,所以這裏我需要將 ILoggerIHttpContextAccessor 注入到 Startup 類中

/// <summary>
/// 修改模型驗證錯誤返回信息
/// </summary>
/// <param name="services">服務容器集合</param>
/// <param name="logger">日誌記錄實例</param>
/// <param name="httpContextAccessor"></param>
/// <returns></returns>
public static IServiceCollection AddCustomInvalidModelState(this IServiceCollection services,
    ILogger<Startup> logger, IHttpContextAccessor httpContextAccessor)
{
    services.Configure<ApiBehaviorOptions>(options =>
    {
        options.InvalidModelStateResponseFactory = actionContext =>
        {
            // 獲取驗證不通過的字段信息
            //
            var errors = actionContext.ModelState.Where(e => e.Value.Errors.Count > 0)
                .Select(e => new ApiErrorDto
                {
                    Title = "請求參數不符合字段格式要求",
                    Message = e.Value.Errors.FirstOrDefault()?.ErrorMessage
                }).ToList();

            var result = new ApiReturnDto<object>
            {
                TraceId = httpContextAccessor.HttpContext.TraceIdentifier,
                Status = false,
                Error = errors
            };

            logger.LogError($"接口請求參數格式錯誤: {JsonConvert.SerializeObject(result)}");

            return new BadRequestObjectResult(result);
        };
    });

    return services;
}

在 asp.net core 2.x 版本中,你完全可以像在別的類中採用構造函數注入的方式一樣直接注入使用

public class Startup
{
    /// <summary>
    /// 日誌記錄實例
    /// </summary>
    private readonly ILogger<Startup> _logger;

    /// <summary>
    /// Http 請求實例
    /// </summary>
    private readonly IHttpContextAccessor _httpContextAccessor;

    /// <summary>
    /// ctor
    /// </summary>
    /// <param name="configuration"></param>
    /// <param name="logger"></param>
    /// <param name="httpContextAccessor"></param>
    public Startup(IConfiguration configuration, ILogger<Startup> logger, IHttpContextAccessor httpContextAccessor)
    {
        Configuration = configuration ?? throw new ArgumentNullException(nameof(configuration));
        _logger = logger ?? throw new ArgumentNullException(nameof(logger));
        _httpContextAccessor = httpContextAccessor ?? throw new ArgumentNullException(nameof(httpContextAccessor));
    }

    /// <summary>
    /// 配置實例
    /// </summary>
    public IConfiguration Configuration { get; }

    /// <summary>
    /// This method gets called by the runtime. Use this method to add services to the container.
    /// </summary>
    public void ConfigureServices(IServiceCollection services)
    {
        //注入的其它服務

        // 返回自定義的模型驗證錯誤信息
        services.AddCustomInvalidModelState(_logger, _httpContextAccessor);
    }
}

但是當你直接遷移到 asp.net core 3.x 版本后,你會發現程序會報如下的錯誤,很常見的一個依賴注入的錯誤,源頭直指我們通過構造函數注入的 ILoggerIHttpContextAccessor 接口

2.2、解決方法

根本原因

通過查閱 stackoverflow 發現了這樣的一個問題:How do I write logs from within Startup.cs,在最高贊的回答中提到了在泛型主機(GenericHostBuilder)中,沒辦法注入除 IConfiguration 之外的任何服務到 Startup類中,而泛型主機則是在 asp.net core 3.0 中添加的功能

查了下升級日誌,從中可以看到,在泛型主機中, Startup 類的構造函數注入只支持 IHostEnvironmentIWebHostEnvironmentIConfiguration ,嗯,不好好看別人文檔的鍋

為什麼使用 WebHostBuilder可以,換成 GenericHostBuilder 就不行了呢

按照正常的邏輯來說,對於一個 asp.net core 應用,原則上來說只有有一個根級(root)的依賴注入容器,但是因為我們在 Startup 類中通過構造函數注入的形式注入服務時,告訴程序了我需要這個服務的實例,從而導致在構建 WebHost 時存在了一個單獨的容器,並且這個容器只包含了我們需要使用到的服務信息,之後,因為會創建了一個包含完整服務的依賴注入容器,這裏就會存在一個服務哪怕是單例的也可能會存在註冊兩次的問題,這無疑有些不太合乎規範

在推行泛型主機之後,嚴格控制了只會存在一個依賴注入容器,而所有的服務都是在 Startup.ConfigureServices 方法執行完成后才會註冊到依賴注入容器中,因此沒辦法像之前一樣在根容器註冊完成之前通過構造函數注入的形式使用

解決方案

如果你需要在 Startup.Configure 方法中使用自定義的服務,因為這裏已經完成了各種服務的註冊,和之前一樣,我們直接在方法簽名中包含需要使用到的服務即可

public void Configure(IApplicationBuilder app, IHostEnvironment env, ILogger<Startup> logger)
{
    logger.LogInformation("在 Configure 中使用自定義的服務");
}

如果你需要在 Startup.ConfigureServices 中使用的話,則需要換一種方法

最簡單的方法,直接替換泛型主機為原來的 WebHostBuilder,這樣就可以直接在 Startup 類中注入各種服務接口了,不過,考慮到這一改動其實是在開倒車,所以這裏不推薦採用這種方法

既然沒辦法正向通過依賴注入容器來自動創建我們需要的服務實例,是不是可以通過服務容器,手動去獲取我們需要的服務,也就是被稱為服務定位(Service Locator)的方式來獲取實例

當然,這似乎與依賴注入的思想相左,對於依賴注入來說,我們將所有需要使用的服務定義好,在應用啟動前完成註冊,之後在使用時由依賴注入容器提供服務的實例即可,而服務定位則是我們已經知道存在這個服務了,從容器中獲取出來然後由自己手動的創建實例

雖然服務定位是一種反模式,但是在某些情況下,我們又不得不採用

這裏對於本篇文章開篇中需要解決的問題,我也是採用服務定位的方式,通過構建一個 ServiceProvider 之後,手動的從容器中獲取需要使用的服務實例,調整后的代碼如下

/// <summary>
/// 添加自定義模型驗證失敗時返回的錯誤信息
/// </summary>
/// <param name="services">服務容器集合</param>
/// <returns></returns>
public static IServiceCollection AddCustomInvalidModelState(this IServiceCollection services)
{
    // 構建一個服務的提供程序
    var provider = services.BuildServiceProvider();

    // 獲取需要使用的服務實例
    //
    var logger = provider.GetRequiredService<ILogger<Startup>>();
    var httpContextAccessor = provider.GetRequiredService<IHttpContextAccessor>();

    services.Configure<ApiBehaviorOptions>(options =>
    {
        options.InvalidModelStateResponseFactory = actionContext =>
        {
            // 獲取失敗信息
            //
            var errors = actionContext.ModelState.Where(e => e.Value.Errors.Count > 0)
                .Select(e => new ApiErrorMessageDto
                {
                    Title = "Request parameters do not meet the field requirements",
                    Message = e.Value.Errors.FirstOrDefault()?.ErrorMessage
                }).ToList();

            var result = new ApiResponseDto<object>
            {
                TraceId = httpContextAccessor.HttpContext.TraceIdentifier,
                Status = false,
                Error = errors
            };

            logger.LogError($"接口請求參數格式錯誤: {JsonSerializer.Serialize(result)}");

            return new BadRequestObjectResult(result);
        };
    });

    return services;
}

對於配置一些需要基於某些服務的服務,這裏也可以通過委託的形式獲取到需要使用的服務實例,示例代碼如下

public void ConfigureServices(IServiceCollection services)
{
    services.AddSingleton<IMyService>((container) =>
    {
        var logger = container.GetRequiredService<ILogger<MyService>>();
        return new MyService
        {
            Logger = logger
        };
    });
}

三、參考資料

  • ASP.NET Core 3.0 的新增功能

  • Generic Host restricts Startup constructor injection

  • 依賴注入模式

  • Avoiding Startup service injection in ASP.NET Core 3

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

【其他文章推薦】

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

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

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

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

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

分類
發燒車訊

上周熱點回顧(6.22-6.28)

熱點隨筆:

· 程序員敲代碼時耳機里聽的到底是什麼? (風的姿態)
· CPU明明8個核,網卡為啥拚命折騰一號核? (軒轅之風)
· 手把手教你基於SqlSugar4編寫一個可視化代碼生成器(生成實體,以SqlServer為例,文末附源碼) (熊澤-學習中的苦與樂)
· 在運行時生成C# .NET類 (芝麻麻雀)
· 因為我的一個低級錯誤,生產數據庫崩潰了將近半個小時 (鄙人薛某)
· C# 人臉識別庫 (View12138)
· 基於領域驅動設計(DDD)超輕量級快速開發架構 (阿新)
· .Net Core 中GC的工作原理 (她微笑的臉)
· 關於技術文章“標題黨”一事我想說兩句 (精緻碼農)
· 【故障公告】阿里雲 RDS 實例 CPU 100% 故障引發全站無法正常訪問 (博客園團隊)
· 思考:如何保證服務穩定性? (老_張)
· 只看到了別人28歲從字節跳動退休,背後的期權知識你知道嗎? (四猿外)

熱點新聞:

· 瘋王,任正非!
· VSCode彩虹屁插件:釘宮理惠,英雄聯盟版現已生成,你Pick哪一個?
· 全國首創!廣東人坐火車就像坐地鐵一樣方便了:無需提前買票
· 95后快遞小哥獲評“高層次人才”:杭州買房享受百萬元補貼
· 二線手機廠商墜落簡史:鎚子、魅族、金立已成過客
· 外賣員確診背後:年近50 每天接老婆下班 工作14小時
· 歷時26年!中國終於有了自己的全球導航系統
· 看!北斗三號最後一顆組網衛星在太空張開“翅膀” 畫面燃了
· 攻克地獄級難度!川藏鐵路拉林段120座橋樑主體工程全部完工
· 作家王小山控訴攜程欠錢不還:願意下跪懇請梁建章退租
· 知乎熱議:替代Matlab的國產軟件出現 半年內實現Matlab功能的70%
· 這個比QQ空間還古老的網站 是多少女孩的精神家園?

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

【其他文章推薦】

※想知道網站建置網站改版該如何進行嗎?將由專業工程師為您規劃客製化網頁設計後台網頁設計

※不管是台北網頁設計公司台中網頁設計公司,全省皆有專員為您服務

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

※帶您來看台北網站建置台北網頁設計,各種案例分享

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

分類
發燒車訊

Python 圖像處理 OpenCV (12): Roberts 算子、 Prewitt 算子、 Sobel 算子和 Laplacian 算子邊緣檢測技術

前文傳送門:

「Python 圖像處理 OpenCV (1):入門」

「Python 圖像處理 OpenCV (2):像素處理與 Numpy 操作以及 Matplotlib 显示圖像」

「Python 圖像處理 OpenCV (3):圖像屬性、圖像感興趣 ROI 區域及通道處理」

「Python 圖像處理 OpenCV (4):圖像算數運算以及修改顏色空間」

「Python 圖像處理 OpenCV (5):圖像的幾何變換」

「Python 圖像處理 OpenCV (6):圖像的閾值處理」

「Python 圖像處理 OpenCV (7):圖像平滑(濾波)處理」

「Python 圖像處理 OpenCV (8):圖像腐蝕與圖像膨脹」

「Python 圖像處理 OpenCV (9):圖像處理形態學開運算、閉運算以及梯度運算」

「Python 圖像處理 OpenCV (10):圖像處理形態學之頂帽運算與黑帽運算」

「Python 圖像處理 OpenCV (11):Canny 算子邊緣檢測技術」

引言

前文介紹了 Canny 算子邊緣檢測,本篇繼續介紹 Roberts 算子、 Prewitt 算子、 Sobel 算子和 Laplacian 算子等常用邊緣檢測技術。

Roberts 算子

Roberts 算子,又稱羅伯茨算子,是一種最簡單的算子,是一種利用局部差分算子尋找邊緣的算子。他採用對角線方向相鄰兩象素之差近似梯度幅值檢測邊緣。檢測垂直邊緣的效果好於斜向邊緣,定位精度高,對噪聲敏感,無法抑制噪聲的影響。

1963年, Roberts 提出了這種尋找邊緣的算子。 Roberts 邊緣算子是一個 2×2 的模版,採用的是對角方向相鄰的兩個像素之差。

Roberts 算子的模板分為水平方向和垂直方向,如下所示,從其模板可以看出, Roberts 算子能較好的增強正負 45 度的圖像邊緣。

\[dx = \left[ \begin{matrix} -1 & 0\\ 0 & 1 \\ \end{matrix} \right] \]

\[dy = \left[ \begin{matrix} 0 & -1\\ 1 & 0 \\ \end{matrix} \right] \]

Roberts 算子在水平方向和垂直方向的計算公式如下:

\[d_x(i, j) = f(i + 1, j + 1) – f(i, j) \]

\[d_y(i, j) = f(i, j + 1) – f(i + 1, j) \]

Roberts 算子像素的最終計算公式如下:

\[S = \sqrt{d_x(i, j)^2 + d_y(i, j)^2} \]

今天的公式都是小學生水平,千萬別再說看不懂了。

實現 Roberts 算子,我們主要通過 OpenCV 中的 filter2D() 這個函數,這個函數的主要功能是通過卷積核實現對圖像的卷積運算:

def filter2D(src, ddepth, kernel, dst=None, anchor=None, delta=None, borderType=None)
  • src: 輸入圖像
  • ddepth: 目標圖像所需的深度
  • kernel: 卷積核

接下來開始寫代碼,首先是圖像的讀取,並把這個圖像轉化成灰度圖像,這個沒啥好說的:

# 讀取圖像
img = cv.imread('maliao.jpg', cv.COLOR_BGR2GRAY)
rgb_img = cv.cvtColor(img, cv.COLOR_BGR2RGB)

# 灰度化處理圖像
grayImage = cv.cvtColor(img, cv.COLOR_BGR2GRAY)

然後是使用 Numpy 構建卷積核,並對灰度圖像在 x 和 y 的方向上做一次卷積運算:

# Roberts 算子
kernelx = np.array([[-1, 0], [0, 1]], dtype=int)
kernely = np.array([[0, -1], [1, 0]], dtype=int)

x = cv.filter2D(grayImage, cv.CV_16S, kernelx)
y = cv.filter2D(grayImage, cv.CV_16S, kernely)

注意:在進行了 Roberts 算子處理之後,還需要調用convertScaleAbs()函數計算絕對值,並將圖像轉換為8位圖進行显示,然後才能進行圖像融合:

# 轉 uint8 ,圖像融合
absX = cv.convertScaleAbs(x)
absY = cv.convertScaleAbs(y)
Roberts = cv.addWeighted(absX, 0.5, absY, 0.5, 0)

最後是通過 pyplot 將圖像显示出來:

# 显示圖形
titles = ['原始圖像', 'Roberts算子']
images = [rgb_img, Roberts]

for i in range(2):
    plt.subplot(1, 2, i + 1), plt.imshow(images[i], 'gray')
    plt.title(titles[i])
    plt.xticks([]), plt.yticks([])
plt.show()

最終結果如下:

Prewitt 算子

Prewitt 算子是一種一階微分算子的邊緣檢測,利用像素點上下、左右鄰點的灰度差,在邊緣處達到極值檢測邊緣,去掉部分偽邊緣,對噪聲具有平滑作用。

由於 Prewitt 算子採用 3 * 3 模板對區域內的像素值進行計算,而 Robert 算子的模板為 2 * 2 ,故 Prewitt 算子的邊緣檢測結果在水平方向和垂直方向均比 Robert 算子更加明顯。Prewitt算子適合用來識別噪聲較多、灰度漸變的圖像。

Prewitt 算子的模版如下:

\[dx = \left[ \begin{matrix} 1 & 0 & -1\\ 1 & 0 & -1\\ 1 & 0 & -1\\ \end{matrix} \right] \]

\[dy = \left[ \begin{matrix} -1 & -1 & -1\\ 0 & 0 & 0\\ 1 & 1 & 1\\ \end{matrix} \right] \]

在代碼實現上, Prewitt 算子的實現過程與 Roberts 算子比較相似,我就不多介紹,直接貼代碼了:

import cv2 as cv
import numpy as np
import matplotlib.pyplot as plt

# 讀取圖像
img = cv.imread('maliao.jpg', cv.COLOR_BGR2GRAY)
rgb_img = cv.cvtColor(img, cv.COLOR_BGR2RGB)

# 灰度化處理圖像
grayImage = cv.cvtColor(img, cv.COLOR_BGR2GRAY)

# Prewitt 算子
kernelx = np.array([[1,1,1],[0,0,0],[-1,-1,-1]],dtype=int)
kernely = np.array([[-1,0,1],[-1,0,1],[-1,0,1]],dtype=int)

x = cv.filter2D(grayImage, cv.CV_16S, kernelx)
y = cv.filter2D(grayImage, cv.CV_16S, kernely)

# 轉 uint8 ,圖像融合
absX = cv.convertScaleAbs(x)
absY = cv.convertScaleAbs(y)
Prewitt = cv.addWeighted(absX, 0.5, absY, 0.5, 0)

# 用來正常显示中文標籤
plt.rcParams['font.sans-serif'] = ['SimHei']

# 显示圖形
titles = ['原始圖像', 'Prewitt 算子']
images = [rgb_img, Prewitt]

for i in range(2):
    plt.subplot(1, 2, i + 1), plt.imshow(images[i], 'gray')
    plt.title(titles[i])
    plt.xticks([]), plt.yticks([])
plt.show()

從結果上來看, Prewitt 算子圖像銳化提取的邊緣輪廓,其效果圖的邊緣檢測結果比 Robert 算子更加明顯。

Sobel 算子

Sobel 算子的中文名稱是索貝爾算子,是一種用於邊緣檢測的離散微分算子,它結合了高斯平滑和微分求導。

Sobel 算子在 Prewitt 算子的基礎上增加了權重的概念,認為相鄰點的距離遠近對當前像素點的影響是不同的,距離越近的像素點對應當前像素的影響越大,從而實現圖像銳化並突出邊緣輪廓。

算法模版如下:

\[dx = \left[ \begin{matrix} 1 & 0 & -1\\ 2 & 0 & -2\\ 1 & 0 & -1\\ \end{matrix} \right] \]

\[dy = \left[ \begin{matrix} -1 & -2 & -1\\ 0 & 0 & 0\\ 1 & 2 & 1\\ \end{matrix} \right] \]

Sobel 算子根據像素點上下、左右鄰點灰度加權差,在邊緣處達到極值這一現象檢測邊緣。對噪聲具有平滑作用,提供較為精確的邊緣方向信息。因為 Sobel 算子結合了高斯平滑和微分求導(分化),因此結果會具有更多的抗噪性,當對精度要求不是很高時, Sobel 算子是一種較為常用的邊緣檢測方法。

Sobel 算子近似梯度的大小的計算公式如下:

\[G = \sqrt{d_X^2 + d_y^2} \]

梯度方向的計算公式如下:

\[\theta = \tan^{-1}(\frac {d_x}{d_y}) \]

如果以上的角度 θ 等於零,即代表圖像該處擁有縱向邊緣,左方較右方暗。

在 Python 中,為我們提供了 Sobel() 函數進行運算,整體處理過程和前面的類似,代碼如下:

import cv2 as cv
import matplotlib.pyplot as plt

# 讀取圖像
img = cv.imread('maliao.jpg', cv.COLOR_BGR2GRAY)
rgb_img = cv.cvtColor(img, cv.COLOR_BGR2RGB)

# 灰度化處理圖像
grayImage = cv.cvtColor(img, cv.COLOR_BGR2GRAY)

# Sobel 算子
x = cv.Sobel(grayImage, cv.CV_16S, 1, 0)
y = cv.Sobel(grayImage, cv.CV_16S, 0, 1)

# 轉 uint8 ,圖像融合
absX = cv.convertScaleAbs(x)
absY = cv.convertScaleAbs(y)
Sobel = cv.addWeighted(absX, 0.5, absY, 0.5, 0)

# 用來正常显示中文標籤
plt.rcParams['font.sans-serif'] = ['SimHei']

# 显示圖形
titles = ['原始圖像', 'Sobel 算子']
images = [rgb_img, Sobel]

for i in range(2):
    plt.subplot(1, 2, i + 1), plt.imshow(images[i], 'gray')
    plt.title(titles[i])
    plt.xticks([]), plt.yticks([])
plt.show()

Laplacian 算子

拉普拉斯( Laplacian )算子是 n 維歐幾里德空間中的一個二階微分算子,常用於圖像增強領域和邊緣提取。

Laplacian 算子的核心思想:判斷圖像中心像素灰度值與它周圍其他像素的灰度值,如果中心像素的灰度更高,則提升中心像素的灰度;反之降低中心像素的灰度,從而實現圖像銳化操作。

在實現過程中, Laplacian 算子通過對鄰域中心像素的四方向或八方向求梯度,再將梯度相加起來判斷中心像素灰度與鄰域內其他像素灰度的關係,最後通過梯度運算的結果對像素灰度進行調整。

Laplacian 算子分為四鄰域和八鄰域,四鄰域是對鄰域中心像素的四方向求梯度,八鄰域是對八方向求梯度。

四鄰域模板如下:

\[H = \left[ \begin{matrix} 0 & -1 & 0\\ -1 & 4 & -1\\ 0 & -1 & 0\\ \end{matrix} \right] \]

八鄰域模板如下:

\[H = \left[ \begin{matrix} -1 & -1 & -1\\ -1 & 4 & -1\\ -1 & -1 & -1\\ \end{matrix} \right] \]

通過模板可以發現,當鄰域內像素灰度相同時,模板的卷積運算結果為0;當中心像素灰度高於鄰域內其他像素的平均灰度時,模板的卷積運算結果為正數;當中心像素的灰度低於鄰域內其他像素的平均灰度時,模板的卷積為負數。對卷積運算的結果用適當的衰弱因子處理並加在原中心像素上,就可以實現圖像的銳化處理。

在 OpenCV 中, Laplacian 算子被封裝在 Laplacian() 函數中,其主要是利用Sobel算子的運算,通過加上 Sobel 算子運算出的圖像 x 方向和 y 方向上的導數,得到輸入圖像的圖像銳化結果。

import cv2 as cv
import matplotlib.pyplot as plt

# 讀取圖像
img = cv.imread('maliao.jpg', cv.COLOR_BGR2GRAY)
rgb_img = cv.cvtColor(img, cv.COLOR_BGR2RGB)

# 灰度化處理圖像
grayImage = cv.cvtColor(img, cv.COLOR_BGR2GRAY)

# Laplacian
dst = cv.Laplacian(grayImage, cv.CV_16S, ksize = 3)
Laplacian = cv.convertScaleAbs(dst)

# 用來正常显示中文標籤
plt.rcParams['font.sans-serif'] = ['SimHei']

# 显示圖形
titles = ['原始圖像', 'Laplacian 算子']
images = [rgb_img, Laplacian]

for i in range(2):
    plt.subplot(1, 2, i + 1), plt.imshow(images[i], 'gray')
    plt.title(titles[i])
    plt.xticks([]), plt.yticks([])
plt.show()

最後

邊緣檢測算法主要是基於圖像強度的一階和二階導數,但導數通常對噪聲很敏感,因此需要採用濾波器來過濾噪聲,並調用圖像增強或閾值化算法進行處理,最後再進行邊緣檢測。

最後我先使用高斯濾波去噪之後,再進行邊緣檢測:

import cv2 as cv
import numpy as np
import matplotlib.pyplot as plt

# 讀取圖像
img = cv.imread('maliao.jpg')
rgb_img = cv.cvtColor(img, cv.COLOR_BGR2RGB)

# 灰度化處理圖像
gray_image = cv.cvtColor(img, cv.COLOR_BGR2GRAY)

# 高斯濾波
gaussian_blur = cv.GaussianBlur(gray_image, (3, 3), 0)

# Roberts 算子
kernelx = np.array([[-1, 0], [0, 1]], dtype = int)
kernely = np.array([[0, -1], [1, 0]], dtype = int)
x = cv.filter2D(gaussian_blur, cv.CV_16S, kernelx)
y = cv.filter2D(gaussian_blur, cv.CV_16S, kernely)
absX = cv.convertScaleAbs(x)
absY = cv.convertScaleAbs(y)
Roberts = cv.addWeighted(absX, 0.5, absY, 0.5, 0)

# Prewitt 算子
kernelx = np.array([[1, 1, 1], [0, 0, 0], [-1, -1, -1]], dtype=int)
kernely = np.array([[-1, 0, 1], [-1, 0, 1], [-1, 0, 1]], dtype=int)
x = cv.filter2D(gaussian_blur, cv.CV_16S, kernelx)
y = cv.filter2D(gaussian_blur, cv.CV_16S, kernely)
absX = cv.convertScaleAbs(x)
absY = cv.convertScaleAbs(y)
Prewitt = cv.addWeighted(absX, 0.5, absY, 0.5, 0)

# Sobel 算子
x = cv.Sobel(gaussian_blur, cv.CV_16S, 1, 0)
y = cv.Sobel(gaussian_blur, cv.CV_16S, 0, 1)
absX = cv.convertScaleAbs(x)
absY = cv.convertScaleAbs(y)
Sobel = cv.addWeighted(absX, 0.5, absY, 0.5, 0)

# 拉普拉斯算法
dst = cv.Laplacian(gaussian_blur, cv.CV_16S, ksize = 3)
Laplacian = cv.convertScaleAbs(dst)

# 展示圖像
titles = ['Source Image', 'Gaussian Image', 'Roberts Image',
          'Prewitt Image','Sobel Image', 'Laplacian Image']
images = [rgb_img, gaussian_blur, Roberts, Prewitt, Sobel, Laplacian]
for i in np.arange(6):
   plt.subplot(2, 3, i+1), plt.imshow(images[i], 'gray')
   plt.title(titles[i])
   plt.xticks([]), plt.yticks([])
plt.show()

示例代碼

如果有需要獲取源碼的同學可以在公眾號回復「OpenCV」進行獲取。

參考

https://blog.csdn.net/Eastmount/article/details/89001702

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

【其他文章推薦】

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

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

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

南投搬家前需注意的眉眉角角,別等搬了再說!

新北清潔公司,居家、辦公、裝潢細清專業服務